Java集合概述(上) (3)

而HashMap,与ConcurrentHashMap一样,都存在Jdk8之前与Jdk8之后的区别。不过,我应该会以Jdk8之后为重点,毕竟现在SpringBoot2.x都要求Jdk8了。

数据组织方式 Jdk8之前 // jdk8之前,其底层是数组+链表 // 链表底层Entry是Map的内部接口 transient Entry<K, V>[] table; Jdk8之后 transient Node<K, V>[] table; static class Node<K, V> implements Map.Entry<K, V> { final int hash; final K key; V value; Node<K, V> next; } 数据处理方式 Jdk8之前的put方法(注释并不多,因为我没有源码,我是按照笔记图片,手撸的这段) public V put (K key, V value) { // HashMap采用延迟创建。判断当前table是否为空。如果为空,就根据默认值15,创建一个数组,并赋值给table if (table == EMPTY_TABLE) { inflateTable(threshold); } // 数据校验 if ( key == null) return putForNullKey(value); // 根据key,计算哈希值 int hash = hash(key); // 通过indexFor(内部貌似采用位运算),根据key的哈希值与数组长度,计算该K-V键值对在数组中的下标i int i = indexFor(hash, table.length); for (Entry<K, V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash = hash && ((k = e.key) || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } // 记录修改次数+1,类似版本号 modCount++; addEntry(hash, key, value, i); return null; } Jdk8之后的put方法 public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } // 计算key的哈希值(数据校验,key的哈希值,即其hashCode) static final int hash(Object key) { int h; // 通过其hashCode的高16位与其低16位的异或运算,既降低系统性能开销,又避免高位不参加下标运算造成的碰撞 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } // 执行主要put操作 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; // 从下面这个代码块,可以看出Java8后的HashMap等,代码晦涩不少 if ((tab = table) == null || (n = tab.length) == 0) // 如果table为null,或table.length为0(其中混杂了赋值语句),就进行进行初始化操作(通过resize()操作,这点与Spring的refresh()应用是一致的),并将其长度赋值给n(注意这里,都赋值给了局部变量,而非全局变量) n = (tab = resize()).length; // 根据key的hash值,计算其下标,并判断数组中对应下标位置是否为null if ((p = tab[i = (n - 1) & hash]) == null) // 如果对应位置为null,直接通过newNode方法(生成Node),设置数组对应i位置为对应新Node tab[i] = newNode(hash, key, value, null); else { // 如果对应位置不为null,那就需要进行链表操作,进而判断是否树化(红黑树),是否扩容等 Node<K,V> e; K k; // 通过hash与equals等,判断新添加值的key与已存在值的key是否真正相等 // 这里扩展两点:第一,判断对象是否相等,必须hashcode与equals都判断相等。前者避免两个对象只是值,但不是同一个对象(两位都是p9大佬,不代表两位就是同一个人)。后者避免哈希碰撞问题(即使是两个不同的对象的内存地址,也可能哈希值相等) // 第二,我看到这里的时候,比较担心,会不会出现value相等,但是hashCode不同,导致这里判断为false。然后我发现包装类型,早就重写了hashCode方法,如Integer的hashCode就直接返回value if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) // 如果相等,就直接更新对应Node即可 e = p; // 如果上面判断失败,则判断原有的数组元素,是不是已经树化(不再是Node类型,而是TreeNode,当然TreeNode依旧是由Node构成的) else if (p instanceof TreeNode) // 如果原有数组元素已经树化,那么就进行调用putTreeVal方法,将当前元素,置入目标红黑树中(其中涉及红黑树的旋转等操作) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); // 如果不是空,也不是相同元素,更不是红黑树,那说明那已经是一个链表(已经由多个元素),或即将成为链表(已经有一个元素,并即将添加一个新的元素) else { // 遍历对应链表元素,并通过binCount记录链表已存在的元素数 for (int binCount = 0; ; ++binCount) { // 如果e=p.next()为null,说明达到了链表的最后(e的前一个值为当前链表的最后一个元素) if ((e = p.next) == null) { // 通过newNode获得对应p的Node,并将其设置为链表的最后一个元素 p.next = newNode(hash, key, value, null); // 通过binCount,判断链表的长度是否达到了树化的阈值 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st // 达到阈值,则通过当前table数组与hash值,以及treefyBin方法,将当前数组位置的链表树化 treeifyBin(tab, hash); break; } // 在遍历过程中,找到了相同的元素,即跳过(因为内容相同) if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; // 该赋值操作,属于链表的操作,从而继续链表遍历 p = e; } } // 下面这段代码,就涉及到HashMap的putIfAbsent(也是调用putVal,只是第四个参数onlyIfAbsent不同) // 简单来说,就是遇到key相同的元素,怎么处理。put操作是直接赋值,而putIfAbsent则是判断对应key的value是否为null,如果是null,才会赋值。否则就不变(类似Redis) // 只不过,这个过程通过新增的第四个参数控制,从而确保同一套代码(putVal方法),实现两种不同功能(put与putIfAbsent) if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } // 版本号 ++modCount; // 一方面size前缀自增,另一方面,判断自增后的size是否超过阈值(默认16*0.75=12,数组容量*负载因子) if (++size > threshold) // 扩容(扩容2倍后,重排) resize(); // 空方法,为子类保留的,如LinkedHashMap afterNodeInsertion(evict); return null; }

这个方法可以算是HashMap的核心,毕竟通过这个方法,也算是摸到了HashMap的运行机制了。

流程简述:

如果HashMap的底层数组没有初始化,则通过resize()方法进行构建

对key计算hash值,然后再计算下标

如果数组对应下标位置为null(这里我认为不该用哈希碰撞),则直接放入对应位置

如果数组对应下标位置为TreeNode(即对应位置已经树化),则通过putTreeVal方法,将对应Node置入树中

否则遍历数组对应下标位置的链表,将对应Node置入

如果链表的长度超过阈值,则进行树化操作

如果节点存在旧值,直接替换

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwwdjj.html