美文网首页
jdk1.8--HashMap简单笔记

jdk1.8--HashMap简单笔记

作者: 序幕周 | 来源:发表于2018-01-07 16:02 被阅读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源码分析

    相关文章

      网友评论

          本文标题:jdk1.8--HashMap简单笔记

          本文链接:https://www.haomeiwen.com/subject/xlbpnxtx.html