今后学习一个知识点的四部曲,他能干什么?为什么用它(特点)?它的原理?它的缺点
HashMap
https://blog.csdn.net/programmer_at/article/details/82431293
1它能干什么?
它能有效率的存储和查询key-value对,而且它的key和value都允许为null,hashMap可以存储一个key为null,多个value为null的键值对(hashTable不允许)
2 特点
1 它实现了abstractMap接口,它的存储是无序的,可以接受为null的键值,不是线程安全的,非同步,
2 基于hashing原理,先对key进行hashCode()计算hash值,然后找到合适的位置来存储Entry,存储hash,key,value,next
3 hashMap的原理?
一个数组Table+红黑树+链表 实现,当前一个链表大于阈值(8)就变成红黑树,如果remove后小于默认的阈值(6)就变回链表 ,(原因:红黑树的平均查找长度是log(n),长度为8,查找长度为log(8)=3,链表的平均查找长度为n/2,当长度为8时,平均查找长度为8/2=4,这才有转换成树的必要;链表长度如果是小于等于6,6/2=3,虽然速度也很快的,但是转化为树结构和生成树的时间并不会太短。)
选择6和8的原因是:
中间有个差值7可以防止链表和树之间频繁的转换。假设一下,如果设计成链表个数超过8则链表转换成树结构,链表个数小于8则树结构转换成链表,如果一个HashMap不停的插入、删除元素,链表个数在8左右徘徊,就会频繁的发生树转链表、链表转树,效率会很低。
而且1.8会把新的节点放在链表尾部。
put()方法的过程
1计算key的hash值
2 计算在数组Table下的下标
3 新建一个Entry来存储kv对的信息
4 判断是否需要扩容
5 返回null(没有覆盖),返回旧值,覆盖了
如何计算key的hash值?
通过key的hashCode(),然后异或(hash值的右移16位)
然后通过不断的右移、与运算来使的hash值的散列性更好
如何计算在Table下的下标?
上一步求得hash值,对数组长度进行求余。
Java中用了一个很巧妙的位运算
(n - 1) & hash
因为数组的长度都是2的整数次方,4,8,之类的,长度减一的二进制都是低位全1,最高位为0,与它相当于对数组长度求模。
如果在table的此位置为null,直接newNode();
如果不为空,,遍历tab[i],判断hash值相同&&key相同,如果满足,则覆盖旧value,返回旧value,如果不满足,newCode(),再看看是否达到变成红黑树的阈值,
get()方法过程
1 根据key求出hash值
2 根据hash值求得table的下标
3 找到相对应的entry
4 返回value
先根据key值求出hash值,再根据key求出下标,如果下标处有一个链表或树,遍历链表,找hash值一样的,找到了就比较key值,如果都一样,就返回value,表示找到了,要不就返回null。如果key为空,hash值就为0
hashMap默认容量
1<<4 1向左移4位,就是2的4次方=16,如果自己传了一个值,他会找一个大于这个值最小的2的整数次方,最大是2的30次方。扩容因子默认为0.75 ,整个map里的元素大于容量的3/4,就会去扩容 ,且这个table[i]不为空.
缺点:
无序,不是线程安全的,可以看出hashMap的效率很大程度上取决于hash冲突,如果table上每个点都只有一个,那么插入和查询都是o(1),不然就是o(n)
ConcurrentHashMap
内容基本来自
https://blog.csdn.net/programmer_at/article/details/79715177
本文纯属学习记录,
它的特点
采用cas+synchronized保证高并发下的插入操作
底层使用数组+红黑树或者链表 实现
原理
初始化:
实例化时如果声明了table的大小,会自动调整到2的幂,默认大小为16
初始化会延缓到map的第一次put操作时执行(懒加载),并且初始化只会执行一次
put操作
进行懒初始化,put时会判断数组是否为空,如果是空则初始化
假设初始化完成,put采用cas+synchronized关键字来实现高并发插入操作
-当前bucket为空(即table[i]为null),cas(null,new node)插入节点Node
如果Map当前正在扩容,先协助扩容,再更新值 ,f.hash == MOVED(-1)
-出现hash冲突(哈希是通过对数据进行再压缩,提高效率的一种解决方法。但由于通过哈希函数产生的哈希值是有限的,而数据可能比较多,导致经过哈希函数处理后仍然有不同的数据对应相同的哈希值。这时候就产生了哈希冲突。),采用synchronized锁住这个头结点,遍历链表,若找到对应的node节点,则修改node节点的val,否则在链表末尾添加node节点;倘若当前节点是红黑树的根节点,在树结构上遍历元素,更新或增加节点。
hash算法
与HashMap类似
static final int HASH_BITS = 0x7fffffff; // 第一位为0,后面全是1的二进制,即为最大正数
static final int spread(int h) {return (h ^ (h >>> 16)) & HASH_BITS;}//h异或(h右移16位)再与最大正数
下标定位
与HashMap类似
int index = (n - 1) & hash // n为bucket的个数
什么时候扩容?
如果新增节点后,链表的长度>8,而且tab的长度>64,将链表转为红黑树,如果小于64,将tab扩容至原来的两倍,或者节点总数大于tab*0.75
为什么用红黑树不用avl树?
红黑是用非严格的平衡来换取增删节点时候旋转次数的降低,任何不平衡都会在三次旋转之内解决,而AVL是严格平衡树,因此在增加或者删除节点的时候,根据不同情况,旋转的次数比红黑树要多。
红黑树,读取略逊于AVL,维护强于AVL,空间开销与AVL类似,内容极多时略优于AVL,维护优于AVL。