HashMap原理:
数组+链表/红黑树
变量:初始化大小16,最大容量2^30,负载因子0.75,链表转红黑树8,红黑树转链表6,最小转数容量32
为什么是数组?
效率更高 & 扩容2倍数,符合hashmap扩容原则,方便计算下标:(hashcode-1)&capacity
*ArrayList扩容倍数为1.5 capacity+capacity>1*
Node:
Key,value,hashcode,next
链表转红黑树8,红黑树转链表6 ?
给一个7作为缓冲,持续的转换性能不高
Hash函数:
h = key.hashcode() ^ (h>>16) 高16位+高16位异或低16
将大范围数变成小范围数
PUT
- 根据hash&(capacity-1)计算下标;
- 判断bucket中tab[下标]==null,如果为null,直接插入;
- 判断hash,key是否一样,一样则更新;
- 判断当前tab[下标]是否位treenode,是则以treenode的方式添加;
- 插入到链表中
- 判断是否需要转为Tree
- 扩容判断
GET
- 根据hash&(capacity-1)计算下标;
- 判断bucket中tab[下标]是否存在,不存在返回null;
- 判断hash,key是否一样,一样则返回;
- 判断next是否为null;
- 判断当前类型是否为tree,是则以tree的方式查找;
- 不是tree,则循环走3直到结束;
Resize
- 复制原来的Node到新的map, 之前低位的还是挪动到新Map的低位,之前高位的挪动到新Map的原位置=旧位置+旧长度
- 低位计算:(e.hash & oldCap) == 0
- LoHeader,Lotail,HiHeader,HiTail
并发问题/线程不安全:
- 多线程扩容,出现死循环问题;[++size][1.7头插入1.8尾插]
- 多线程PUT元素,丢失;
解决方案: hashtable[加synchronized锁,效率太低] ConcurrentHashMap[分段hash]
Key可以为null,但是只有一个;Value可以存在多个null
一般用不变量:Integer,String作为Key
使用不变类作为key时:
- 需使用final修饰,不能继承,以免修改,
- 构造器初始化成员深copy,
- 需要重写hashcode(), equals(),
- getter方法需要返回一个深copy,以免被修改/防止对象外泄
String的hashcode
h = 31 * h + val[i];
31*(31*(31*0+[0])+[1])+[2]……=[0]*31^(n-1)+[1]*31^(n-2)+……+[n-1]
31是一个不大不小的奇质数