java8 hashmap 要点
所属分类 java
浏览量 1307
数组 + 链表 + 红黑树
允许null键/值
public class HashMap extends AbstractMap implements Map , Cloneable, Serializable
// 默认的初始容量是16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
// 最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
// 默认的填充因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 当桶(bucket)上的结点数大于这个值时转成红黑树
static final int TREEIFY_THRESHOLD = 8;
// 当桶(bucket)上的结点数小于这个值时树转链表
static final int UNTREEIFY_THRESHOLD = 6;
// 桶中结构转化为红黑树对应的数组的最小大小,如果当前容量小于它,不会将链表转化为红黑树,而是用resize()代替
static final int MIN_TREEIFY_CAPACITY = 64;
// 存储元素的数组,总是2的幂
transient Node[] table;
// 存放具体元素的集
transient Set> entrySet;
// 元素个数,注意不等于数组长度
transient int size;
// 修改次数
transient int modCount;
// 临界值 当实际节点个数超过临界值(容量*填充因子)时,会进行扩容
int threshold;
// 填充因子
final float loadFactor;
直接使用key的hashcode()作为hash很容易发生碰撞
综合考虑性能 和散列效果 把高16bit和低16bit进行异或
resize
Initializes or doubles table size.
because we are using power-of-two expansion, the elements from each bin must either stay at same index, or move with a power of two offset in the new table.
2倍扩容 元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置
扩容时,不需要重新计算hash, hash值高位新增的那个bit是1还是0,是0的话索引不变,是1的话索引变成“原索引+oldCap”,
直接拆分原链表为高低链表相比先保存数据再寻址追加效率更好。
put大致过程
对key的hashCode()做hash,然后再计算桶的index;
如果没碰撞直接放到桶bucket里;
如果碰撞了,以链表的形式存在buckets里;单链表尾部插入,扩容时保持元素原有顺序避免环
如果碰撞导致链表过长(大于等于TREEIFY_THRESHOLD),把链表转换成红黑树(若数组容量小于MIN_TREEIFY_CAPACITY,不进行转换而是进行resize操作)
如果节点已经存在则替换old value
如果表中实际元素个数超过阈值(超过load factor*current capacity),resize
桶为空 写入不加锁
no lock when adding to empty bin
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node(hash, key, value, null)))
break; // no lock when adding to empty bin
}
static final boolean casTabAt(Node[] tab, int i,
Node c, Node v) {
return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
桶的第一个节点 hash值为 MOVED
static final int MOVED = -1; // hash for forwarding nodes
static final int TREEBIN = -2; // hash for roots of trees
static final int RESERVED = -3; // hash for transient reservations
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
Helps transfer if a resize is in progress.
正常节点 synchronized 加锁处理
synchronized (f)
桶的第一个节点作为锁对象
链表末尾插入数据 或 红黑树插入数据
if (binCount != 0) {
// 当链表长度 超过 8时,转化成红黑树
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
Java8中hash计算 key hashCode 高16位异或低16位 ,保证高低bit都参与hash计算,且不会有太大的开销。
数组大小n总是2的整数次幂,使用位运算计算下标( hash & n-1)
分配内存统一放在resize()中,包括创建后首次put时初始化数组和存放元素个数超过阈值时扩容。
当链表长度>=8, 执行treeifyBin,当桶数量达到64时,将链表转为红黑树,否则,执行resize()。
判断Node是否符合,首先判断哈希值,key是否相等,同一个对象用==比,否则用equals()
上一篇
下一篇
并发机制的底层实现原理摘要
java7 hashmap 要点
常用hash算法及冲突解决方法
hashmap在java7和8中的区别
java位运算
HashMap和ConcurrentHashMap中的hash函数