如果数组的元素数量超过阈值(数组容量*负载因子),则进行扩容(扩容2倍,重排)
Jdk8之后的get方法 public V get(Object key) { Node<K,V> e; return (e = getNode(hash(key), key)) == null ? null : e.value; } // 这里我觉得没什么说的。根据不同情况,分别从数组,红黑树,数组来获取目标元素 final Node<K,V> getNode(int hash, Object key) { Node<K,V>[] tab; Node<K,V> first, e; int n; K k; if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { if (first.hash == hash && // always check first node ((k = first.key) == key || (key != null && key.equals(k)))) return first; if ((e = first.next) != null) { if (first instanceof TreeNode) return ((TreeNode<K,V>)first).getTreeNode(hash, key); do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null); } } return null; } 小结就使用场景而言,《码出高效》给出这样一句话:
除局部方法或绝对线程安全的情形外,优先推荐ConcurrentHashMap。两者虽然性能相差无几,但后者解决了高并发下的线程安全问题。HashMap的死链问题及扩容数据丢失问题是慎用HashMap的两个主要原因。
这里,我忍不住站在Java工程师的角度,推荐《码出高效》以及配套的《阿里Java开发手册》。作为一名也算看过不少技术书籍的开发者,这两本书在我这儿,也算得上是优秀书籍了。
不过,文中也提到,这种情形,在Jdk8之后有所修复,改善。具体的,可以看看书籍(主要内容有点多)。
ConcurrentHashMapConcurrentHashMap部分,我将只描述Jdk8之后的版本。
而Jdk8之前的版本,其实底层就是类似HashTable的Segament组成的数组。通过分段锁,达成线程安全。算是HashTable与HashMap的折中方案。复杂度并不是很高,不过Jdk8之后的版本,就较为复杂。首先,引入红黑树,优化存储结构。其次,取消原有的分段锁设计,采用了更高效的线程安全设计方案(利用了无锁操作CAS与头节点同步锁等)。最后,使用了更优化的方式统计集合内的元素数量(引用自《码出高效》,我还真没注意到这点)。
数据组织方式 transient volatile Node<K,V>[] table; static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; volatile V val; volatile Node<K,V> next; Node(int hash, K key, V val, Node<K,V> next) { this.hash = hash; this.key = key; this.val = val; this.next = next; } // 此处省略其内部方法,感兴趣的,可以自行查看 }