day04--------------------------------------------------------------
Map集合(映射)
双列集合 键值对应 键映射值
HashMap<k, v>
hashMap的key就是hashSet
底层1.7数组+链表
1.8数组+链表+红黑树
LinkedHashMap<k, v> extends HashMap
HashTable(淘汰)
遍历方式:获得key值->使用key值查找value
keySet()
Map集合内部结构
entry对象 -> 键值对
entrySet()
getKey()
getValue()
jdk9集合有一种新的初始化方式(了解)
of方法,一次性给集合添加多种元素
List<String> list = List.of("a","aw","h","d","b");
of方法只适用于List接口、Set接口、Map接口,不适应与它们的实现类
使用of方法实例化的集合不允许修改(内容和长度)
Debug追踪
HashMap键值冲突(哈希相同)时,如何处理
不同内容的对象,计算出来的数组索引却一致,这种情况成为哈希冲突
key不变,value覆盖
HashMap的数据结构
1.7:数组+单链表
(为何是单链表?查看源码,hashmap的数据结构entry是单链表结构,没有pre,只有next)
(hashmap可以存储null值和null键)(也是和hashtable的不同之处之一)
(如何实现一个数组+链表)
单链表[] 对象名 = new 单链表[];
(哈希算法-->目的:使每一个元素能均匀的落在数组上面,减少哈希冲突)
(已经出现哈希冲突后,元素怎么挂上链表呢?1.7采用头插法
1.8采用尾插法
因为在多线程的情况下头插法可能会造成闭环)
1.8:数组+单链表+红黑树
【hashCode怎么算出来的?】
使用的是 哈希冲突算法
源码(1.8): 针对key,取到它的hashcode,然后(hashCode >> 16) ^ hashCode (拿hashCode右移16位取到高16位,再对hashCode本身(也就是低16位)取异或)
目的是让每一位都尽可能的参与运算(让01出现的尽量平衡)
哈希码是一个int类型的数字:int数据4字节32位
hashCode右移16位,分成高16位和低16位,拿低16位对高16位取异或
目的是尽可能的让每一位都参与运算
为什么取异或?
因为在位操作里 异或 是取出'0'、'1'值尽量平均的操作
为减少hashMap冲突,针对数组长度,必须是一个偶数位(2的n次幂)
因为偶数位-1才能是一个奇数位,只有奇数位的二进制码的最后一位才是1
二进制码最后一位必须是1,取异或才有可能是0也可能是1
(最后一位如果为0,取异或必定为0-->全1为1,其他为0)
才能让高16位和低16位都参与运算
1.7的情况:尽管已经尽可能的优化了哈希冲突算法使其尽量的减少冲突,但由于数组下是链表结构,终究还是会影响效率,所以在1.8之后,数组下的链表到达7个元素后,会转换成"红黑树"结构
hashMap的扩容机制
默认数组装满后需要扩容,new一个新的数组装填
原来的算法:key的hash和old数组长度-1进行运算得到位置
(hashTable做法)扩容算法:key的hash和new数组长度-1进行同样的运算得到位置
但是这种操作不够效率,hashMap源码采用
e的hash对oldCap进行&操作,看值的倒数第5位是否等于0,再根据是否等于0,决定是否扩容
使用:老数组长度 & hash 去判断结果倒数第5位是否为0
因为:要判断原来的元素在新数组上是否移动,实际上只需要看 & 后的倒数第5位是否为0,为0不移动直接使用,为1移动
移动的大小刚好是原来的长度 --> oldCap
hashMap的默认域值是12
默认装载因子0.75 * (默认初始化大小1<<4=16) = 12
hashMap的数组默认长度 1 << 4 = 16 |
|