jdk1.8--HashMap简单笔记

2018-01-07  本文已影响0人  序幕周

一、概述

1.1 jdk_1.8之前HashMap都是基于数组+链表实现 非线程安全的。

1.1

1.2 缺点:如果出现hash碰撞(桶entry碰撞),就会退化成单链表!查找时间从O(1)到O(n)。最好不要出现hash碰撞。

2.1 jdk_1.8之前后HashMap都是基于数组+链表+红黑树实现的 非线程安全的。

2.1

因为jdk_1.8之前出现桶碰撞, 在链表中查找数据会出现很大的性能损失。所以jdk_1.8之后当出现hash值冲突时, 如果链表node节点大于8时不再采用链表,转而使用红黑树代替链表!提高查找效率。

关于性能提升参考:http://blog.csdn.net/lc0817/article/details/48213435/



二、源码解析

1 字段解析(注释来自百度翻译  勿怪)

/**

*表中,第一次使用初始化,并调整其大小为

*必要。分配时,长度总是两个幂。

*我们也允许在某些操作中允许长度为零

*目前不需要的自举机构。)

*/

transient Node[]table;  这个就是上图中的 hash数组。

/ * *

*此映射中包含的键值映射的数量。

* /

transient int size; 个人觉得就是Node<k,v>节点数量

/ * *

*调整大小的下一个尺寸值(容量*负载因子)。

*

* @系列

* /

/ /(javadoc的描述是真实的在序列化。此外,如果尚未分配表数组,则字段保留初始数组容量,或零表示 default_initial_capacity。)

int threshold;  个人理解为扩容阀值 如果 size(node节点) 大于这个值是 则对table数组进行扩容。

float loadFactor;  装载因子

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 默认初始化数组大小为16

static final float DEFAULT_LOAD_FACTOR = 0.75f;   默认装载因

/ * *

*使用树的bin计数阈值,而不是列表

*仓。当添加一个元素到一个

* bin至少有这么多节点。值必须更大。

*大于2,至少应符合假设的8

*树搬迁转换回平原垃圾桶后

*收缩。

* /

static final int TREEIFY_THRESHOLD = 8; 链表最大长度 大于这个长度,链表转化为红黑树

2. 构造函数

相关自己可以看下源码

知道存储的数据大小时最好指定大小

3. 关于Node类型

Node<k,v>[] table;

Node<K,V> 是一个HashMap的一个静态内部类。他是实现于Map.Entry<k,v>

重要参数:

final int hash;  hash值 

final K key; 存储的key

V value;   存储的值

Node<k,v> next;   指向的下一个节点的地址

4 存储方法

public V put(K key, V value) {

return putVal(hash(key), key, value, false, true);

}


static final int hash(Object key) {

int h;

 //通过这里我们也就可以发现key是可以为null的,如果为空返回0也就是table[0]的位置。

    return (key ==null) ?0 : (h = key.hashCode()) ^ (h >>>16);

}


final V putVal(int hash, K key, V value, boolean onlyIfAbsent,

              boolean evict) {

Node[] tab; Node p; int n, i;

//将table赋值给 tab 如果tab为null或者长度为0 则重新去初始化table(注意在resize里面 由于 现在tab和table 指向的是同一个地址 所以也是初始化tab)

if ((tab =table) ==null || (n = tab.length) ==0)

     n = (tab = resize()).length;

//tab[i = (n -1) & hash] 这样写还是第一次见, 反正意思通过 (n -1) & hash 得到数组下标的值 为空的时候 就去创建一个新的节点并保存到那个下标((n -1) & hash)上去

if ((p = tab[i = (n -1) & hash]) ==null)

     tab[i] = newNode(hash, key, value, null);

else {

    //一旦不为空 所以就hash碰撞了。需要组成 链表或者树。

     Node e; K k;

   //如果发现hash值(下标) 和 将要存储的key 都是一致的, 注意:p是通过(p = tab[i = (n -1) &   hash]) 得到的  将这个值赋给e,  后面会对e执行是否覆盖value的操作。

     if (p.hash == hash &&((k = p.key) == key || (key !=null && key.equals(k))))

           e = p;

     else if (p instanceof TreeNode)

          //判断 节点是否是红黑树类型 如果是执行红黑插入操作。

           e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);

     else {

         //到这里说明链表存储的,则对链表进行循环依次向后查找

          for (int binCount =0; ; ++binCount) {

                //将p指向的下一个节点赋值给 e 如果为null这是链表的最后一个节点了 则将创建一个新节点赋值给p的下一个节点即(next)

                if ((e = p.next) ==null) {

                        p.next = newNode(hash, key, value, null);

//如果冲突节点达到8个,调用treeifyBin(tab, hash),这个treeifyBin首先回去判断当前hash表的长度,如果不足64的话,实际上就只进行resize,扩容table,如果已经达到64,那么才会将冲突项存储结构改为红黑树。  

                        if (binCount >=TREEIFY_THRESHOLD -1)// -1 for 1st

                                treeifyBin(tab, hash);

                       break;

           }

//当e 不等于空 且他的hash值和key等于要存储的hash值 和key时则结束循环 后面会判断是否对value覆盖

if (e.hash == hash && ((k = e.key) == key || (key !=null && key.equals(k))))

break;

               //把e赋给p继续循环

                p = e;

            }

}

//如果e不等于空说明数或者链表存在或者插入 hash值和key一样的节点了, 这里就是进行对value判断是否用新的值覆盖以前的值!

if (e !=null) {// existing mapping for key

            V oldValue = e.value;

           //判断是否修改已插入节点的value  

            if (!onlyIfAbsent || oldValue ==null)

e.value = value;

            afterNodeAccess(e);

            return oldValue;

        }

}

//插入新节点后,hashmap的结构调整次数+1

++modCount;

    if (++size >threshold) //判断节点大小是否大于扩容阀值大于就执行扩容

resize();

    afterNodeInsertion(evict);

return null;

}

作为第一次写文章,大量参考其他文章只是对部分做了点个人理解和 详细解释!

不到之处欢迎指正。

发现对代码支持很差呢!

参考:JDK1.8 HashMap源码分析

上一篇下一篇

猜你喜欢

热点阅读