Java Map

604 阅读2分钟

HashMap

无序的map集合,线程不安全

结构:

HashMap的存储结构为链表+数组

Map本身为Key:Value存储,HashMap使用Hash值确定每个值在数组的位置,所以是无序的,而且可能存在碰撞(不同的key有相同的hash值,导致多个key在数组中有相同的索引位置),出现碰撞的时候Node的设计就发挥作用了,最终构成一个链表。

首先捋一下HashMap的Put和Get的大体流程:

Put:
1、判断数组tab是否为空,若为空初始化(默认长度为16)

2、i=(n-1)&Hash 计算当前put的key:value键值対在数组tab的位置,n为tab数组的长度
i位置处为空直接将当前数据放入数组的i处即可
3、若i不为空即出现了碰撞,构建链表

Get:
1、tab[hash&&(n-1)]取出对应位置链表,比较第一个节点位置的key值与传入的key值是否相等,相等返回值
2、不相等遍历链表的元素挨个比较,直至获取到对应的值

关注一下几个重要的实现

1、计算数组索引 (n-1)&hash n为数组的长度
&:位运算与,二进制下,同为1为1,否则为0
则(n-1)&hash的取值范围就是 0-(n-1),就是数组长度为n的索引的取值范围;且索引位置取决于Hash低位的值\

2、Hash值的计算 hash^(h>>>16)\

:无符号右移,h>>>16相当于把高16位移到了低16位,高位补0\ ^:异或 同值取0 异值取1 例如0 0 -> 0, 1 1 -> 0, 1 0 -> 1
再看前边的计算数组索引,Hash位置取决于Hash低位的值,高位的值不参与运算,而通过hash^(h>>>16)使高位的值对低位的值产生了影响,减小了产生碰撞的几率。

3、HashMap的长度为什么一定是2的n次方
数组tab长度的初始化方法:取等于或者大于cap的2的n次方的值(大于取最近的值)
tab扩容的时候也是乘以2

计算分布、取值均匀

TreeMap

有序的Map集合,链表,线程不安全,数据存储在链表Entry[K,V] root,比较器comparator

LinkedHashMap

有序的map集合,线程不安全,双向链表+数组 重写了newNode方法

HashTable

有序(从大到小),链表+数组,synchronized保证线程安全性

ConcurrentHashMap

线程安全的,链表+数组,数组中单个slot元素个数超过8个时会将链表结构转换成红黑树,put时对单个slot头节点元素进行synchronized加锁,ConcurrentHashMap中的加锁粒度是针对slot节点的,rehash过程中加锁粒度也是如此