hashMap解决hash冲突
jdk1.8中 hashMap采用数组+链表+红黑树的形式
首先hashMap会有一个数组 Node<K,V>[] table
作为hash桶,放值的时候函数调用put()->putVal()
,然后在putVal()
一通操作,如果没有发生hash冲突那将新建一个hash桶:
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
那么重点就是发生冲突是怎么解决的呢?其实它是通过链表+红黑树
去解决的,刚开始是链表
解决冲突,但是随着链表的长度增加搜索效率变慢,因此我们想到使用搜索二叉树
去解决这个问题,但是当出现某种极端情况时,搜索二叉树就会退化为链表
,因此又提出了平衡二叉树
(ALV),但是由于多种原因,这里我们一般使用的是红黑树
。那么在什么时候需要由链表转换为红黑树呢?且看下面这段代码:
// 这段代码就是处理hash冲突
Node<K, V> e;
K k;
// 这个判断是不是首结点
// 何谓首结点 在我看来应该是这个Node<K,V>[i]对应的这个结点
// 由于它下面连接着一个链表或者一个红黑树,这个首结点就是这颗树的根节点或者链表头结点
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// 判断连接的是不是红黑树,因为和链表结点插入方法不同
else if (p instanceof TreeNode)
e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);
// 这里就是下面连接的链表进行插入结点
else {
for (int binCount = 0;; ++binCount) {
//如果找到尾部,则表明添加的key-value没有重复,在尾部进行添加
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
/**
重点在此
TREEIFY_THRESHOLD = 8,(binCount从0开始)
如果链表的长度大于8则需要执行 treeifyBin(tab, hash);
*/
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
// 链表中存在key,则跳出循环
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//处理重复的键对应的值的问题
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
treeifyBin()
一定注意并不是链表长度大于8就一定将其转换成红黑树
// Replaces all linked nodes in bin at index for given hash
// unless table is too small, in which case resizes instead.
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
// 这里对Node<K,V>[] table数组扩容
// 因为hashMap认为如果数组的长度小于64时,且下面的链表长度大于8
// 他会觉得是数组长度不够所以需要扩容,容量每次都会乘以2
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) {
//这里转为红黑树
}
}
HashMap和LinkedHashMap,TreeMap之间的比较
HashMap
:存取无序,放入的顺序和读取出来的顺序不一致。
LinkedHashMap extends HashMap
:存读有序(维护一个双链表
实现有序的存取数据,也就是放入的顺序和读取的顺序一致),他可以通过构造方法指定accessOrder=false
插入有序(默认
),否则读取有序。如果为true
,那么获取这个map中某个key对应的值后,他会将其移到链表尾部。
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
这里放一张转载的LinkedHashMap数据结构图片
图片源地址,博文参考 https://segmentfault.com/a/1190000012964859
再看看TreeMap
TreeMap
它和HashMap
没什么关系,只是都继承自AbstractMap
,都实现了Cloneable
,Serializable
接口,但是TreeMap
实现了NavigableMap
,而NavigableMap
继承了SortedMap
,因此它可以实现自定义的排序功能,当然这是对放入的key
进行排序。当然默认
就是自然顺序
排序。它的底层就是红黑树
的数据结构
博文参考 https://www.cnblogs.com/LiaHon/p/11221634.html#%E4%B8%89-treemap%E6%9E%84%E9%80%A0