针对于HashMap来说,主要有两个版本的区别JDK 1.7和JDK 1.8。先来看看JDK 1.7版本的底层实现:
在JDK 1.7中,首先是把元素放在一个数组里面,后来存放的数据元素,当出现Hash碰撞时,通过使用链表的方式进行存放。采用的是 数据 + 链表 的方式进行存储。但存储的元素足够大的时候,出现Hash碰撞的概率也越来越大,此时的链表长度将会越来越长,此时查询的复杂度提高。
在JDK 1.8中,针对链表进行改进,当链表长度达到8,数组长度达到64时,就会把链表转换为 红黑树结构。
问题一:什么是红黑树呢?
红黑树是一个自平衡的二叉查找树,也就是说红黑树的查找效率是非常的高,查找效率会从链表的o(n)降低为o(logn)。
问题二:为什么不一下子把整个链表变为红黑树呢?
这个问题的意思是这样的,就是说我们为什么非要等到链表的长度大于等于8的时候,才转变成红黑树?在这里可以从两方面来解释
- 构造红黑树要比构造链表复杂,在链表的节点不多的时候,从整体的性能看来, 数组+链表+红黑树的结构可能不一定比数组+链表的结构性能高。
- HashMap频繁的扩容,会造成底部红黑树不断的进行拆分和重组,这是非常耗时的。因此,也就是链表长度比较长的时候转变成红黑树才会显著提高效率。
默认限制常量:
HashMap中维护了以下几个常量控制初始化、链表和树的转化、扩容等默认限制:
// 声明了HashMap数组的初始化长度:16(必须是2的整数倍) static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 声明了HashMap的最大容量: static final int MAXIMUM_CAPACITY = 1 << 30; // 默认的加载因子(在进行数组扩容时使用,可以通过构造器传入) static final float DEFAULT_LOAD_FACTOR = 0.75f; // 链表树化阈值,达到8时进行树化操作: static final int TREEIFY_THRESHOLD = 8; // 树退化阈值,当树节点数小于6时,就会退化为链表: static final int UNTREEIFY_THRESHOLD = 6; // 树化要求的最小数组长度: static final int MIN_TREEIFY_CAPACITY = 64;
Node 数组:
HashMap结构中还维护了一个Node类型的table数组。该表在首次使用时初始化,并根据需要调整大小。分配时,长度始终是2的幂。
// 这个表在进行new HashMap()时并不会进行初始化, // 而是在第一次put元素时初始化 transient Node<K,V>[] table;
Node结构,本身是一个单链表结构:
static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node<K,V> next; Node(int hash, K key, V value, Node<K,V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } public final K getKey() { return key; } public final V getValue() { return value; } public final String toString() { return key + "=" + value; } public final int hashCode() { return Objects.hashCode(key) ^ Objects.hashCode(value); } public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } public final boolean equals(Object o) { if (o == this) return true; if (o instanceof Map.Entry) { Map.Entry<?,?> e = (Map.Entry<?,?>)o; if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) return true; } return false; } }
entrySet 集合:
HashMap中还维护了一个Map.Entry类型的Set集合:
transient Set<Map.Entry<K,V>> entrySet;
其中每个元素都是一个Map.Entry对象,该对象包含了键和值的对应关系。通过遍历entrySet()返回的Set集合,可以方便地获取HashMap中所有的键值对。

