HashMap的实现原理

HashMap是数组 + 链表 + 红黑树(JDK1.8新增红黑树部分)实现的

数据底层具体存储的是什么?这样存储方式的优点是什么?

1) 一个重要的字段 -- Node[] table 哈希桶数组,

Node 是HashMap的一个内部类,实现了Map.Entry 接口,本质上是一个键值对,上图中的每一个黑色原点就代表一个Node对象

2)HashMap就是使用哈希表来存储的,Java中采用链地址法(数组加链表的组合)来解决哈希冲突。

    链地址法: 在每个数组元素上都有一个链表结构,调用key 的hashCode() 方法得到其hashCode值,然后再通过Hash算法的后两步运算(高位运算和取模运算)来确定该键值对的存储位置,key的hash值相同的话就发生了哈希冲突, 如果哈希桶数组很大,就算较差的hash算法也会比较分散,效率较高,反之亦然本之上就是空间成本和时间成本之间的权衡。

其实就是根据实际情况确定哈希桶数组的大小,并在此基础上设计好hash算法减少哈希碰撞,即 hash算法和扩容机制。

在HashMap的默认构造里有几个重要的变量

int threshold         // 所能容纳键值对的极限
final float loadFactor  = 0.75        // 负载因子
int modCount        // 记录HashMap内部结构变化的次数,主要用于迭代的快速失败
int size        // 实际存储的键值对数量 

threshold = length * Load factor

Node[] table的初始化长度length(默认是16) Load factore 为负载因子(默认值为0.75) threshold = length * Load factor

length大小的值必须时2的n次方(合数),非常规的设计,一般把哈希桶的大小设计为素数,降低冲突,Hashtable初始化桶的大小是11 ,而HashMap采用这种非常规的设计,主要在于为了取模合扩容时做优化,同时为了减少冲突,HashMap定位哈希桶索引位置时,也加入了高位参与运算的过程。

也就是说 HashMap 最初始容量可以16个,但当他存储到16*0.75 = 12个后,再存储就会进行扩容,之前容量的2倍 也就32

size 实际存储大小

modCount 内部结构发生变化指的时结构发生变化,例如put新键值对,但某个key的value值被覆盖不属于结构变化。

在JDK8 中当链表长度超过8时,链表就会转为红黑树,利用红黑树快速增删改查的特点提高性能,

1) 确定hash桶数组索引位置

Hash算法的本质就三步:

  1. 取key的hashCode值;
  2. 高位运算;
  3. 取模运算。
⽅法⼀:
static final int hash(Object key) { //jdk1.8 & jdk1.7
 int h;
 // h = key.hashCode() 为第⼀步 取hashCode值
 // h ^ (h >>> 16) 为第⼆步 ⾼位参与运算
 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
⽅法⼆:
static int indexFor(int h, int length) { //jdk1.7的源码,jdk1.8没有这个⽅法 是实现原理⼀样的
 return h & (length-1); //第三步 取模运算
}

2) put 方法

public V put(K key, V value) {
2 // 对key的hashCode()做hash
3 return putVal(hash(key), key, value, false, true);
4 }
5
6 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
7 boolean evict) {
8 Node<K,V>[] tab; Node<K,V> p; int n, i;
// 步骤①:tab为空则创建
10 if ((tab = table) == null || (n = tab.length) == 0)
11 n = (tab = resize()).length;
12 // 步骤②:计算index,并对null做处理
13 if ((p = tab[i = (n - 1) & hash]) == null) 
14 tab[i] = newNode(hash, key, value, null);
15 else {
16 Node<K,V> e; K k;
17 // 步骤③:节点key存在,直接覆盖value
18 if (p.hash == hash &&
19 ((k = p.key) == key || (key != null && key.equals(k))))
20 e = p;
21 // 步骤④:判断该链为红⿊树
22 else if (p instanceof TreeNode)
23 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, 
value);
24 // 步骤⑤:该链为链表
25 else {
26 for (int binCount = 0; ; ++binCount) {
27 if ((e = p.next) == null) {
28 p.next = newNode(hash, key,value,null);
 //链表⻓度⼤于8转换为红⿊树进⾏处理
29 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st 
30 treeifyBin(tab, hash);
31 break;
32 }
 // key已经存在直接覆盖value
33 if (e.hash == hash &&
34 ((k = e.key) == key || (key != null &&
key.equals(k)))) break;
36 p = e;
37 }
38 }
39 
40 if (e != null) { // existing mapping for key
41 V oldValue = e.value;
42 if (!onlyIfAbsent || oldValue == null)
43 e.value = value;
44 afterNodeAccess(e);
45 return oldValue;
46 }
47 }
48 ++modCount;
49 // 步骤⑥:超过最⼤容量 就扩容
50 if (++size > threshold)
51 resize();
52 afterNodeInsertion(evict);
53 return null;
54 }

3) resize() 控制机制

扩容就是重新计算容量,当然数组是无法自动扩容的,其实就是用要给新得数组代替小容量的数组 //jdk1.7的,1.8是基于链表的

// todo

1 void resize(int newCapacity) { //传⼊新的容量
2     Entry[] oldTable = table; //引⽤扩容前的Entry数组
3     int oldCapacity = oldTable.length;
4     if (oldCapacity == MAXIMUM_CAPACITY) { //扩容前的数组⼤⼩如果已经达到最⼤(2^30)了
5     threshold = Integer.MAX_VALUE; //修改阈值为int的最⼤值(2^31-1),这样以后就不会扩容了
6     return;
7     }
8 
9     Entry[] newTable = new Entry[newCapacity]; //初始化⼀个新的Entry数组
10     transfer(newTable); //!!将数据转移到新的Entry数组⾥
11     table = newTable; //HashMap的table属性引⽤新的Entry数组
12     threshold = (int)(newCapacity * loadFactor);//修改阈值
13 }
1 void transfer(Entry[] newTable) {
2     Entry[] src = table; //src引⽤了旧的Entry数组
3     int newCapacity = newTable.length;
4     for (int j = 0; j < src.length; j++) { //遍历旧的Entry数组
5     Entry<K,V> e = src[j]; //取得旧Entry数组的每个元素
6     if (e != null) {
7         src[j] = null;//释放旧Entry数组的对象引⽤(for循环后,旧的Entry数组不再引⽤任何对象)
8         do {
9             Entry<K,V> next = e.next;
10            int i = indexFor(e.hash, newCapacity); //!!重新计算每个元素在数组中的位置
11            e.next = newTable[i]; //标记[1]
12            newTable[i] = e; //将元素放在数组上
13            e = next; //访问下⼀个Entry链上的元素
14             } while (e != null);
15         }
16     }
17 }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值