并发编程学习笔记(二十四、ConcurrentHashMap,Java7 HashMap简述)

目录:

  • HashMap结构
  • HashMap方法简述

HashMap结构

在Java7中我们可以更具HashMap的table属性及Entry内部类观察出,其底层是基于数组实现,并在发生Hash冲突的时候用链表解决,将相同hash值的key组层一个链表维护。

1 transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
2 
3 static class Entry<K,V> implements Map.Entry<K,V> {
4     final K key;
5     V value;
6     Entry<K,V> next;
7     int hash;
8 }

还有几个属性需要注意下:

  • capacity:数组容量,始终保持2^n次倍,可以扩容,扩容后大小为当前数组的2倍
  • loadFactory:负载因子,默认为0.75。
  • threshold:扩容阀值,等于capacity * loadFactory。

HashMap方法简述

java.util.HashMap#put:添加一个键值对到map中。

 1 public V put(K key, V value) {
 2     // 若数组为空,也就是添加第一个元素时,初始化数组容量
 3     if (table == EMPTY_TABLE) {
 4         inflateTable(threshold);
 5     }
 6     // 若key为null,则会把这个entry放到table[0]中
 7     if (key == null)
 8         return putForNullKey(value);
 9     // 1、求key的hash值
10     int hash = hash(key);
11     // 2、找到对应的数组下标
12     int i = indexFor(hash, table.length);
13     // 3、遍历整个map的链表,若有重复的key值则覆盖
14     for (Entry<K,V> e = table[i]; e != null; e = e.next) {
15         Object k;
16         if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
17             V oldValue = e.value;
18             e.value = value;
19             e.recordAccess(this);
20             return oldValue;
21         }
22     }
23 
24     modCount++;
25     // 4、不存在的key值将次添加到链表中
26     addEntry(hash, key, value, i);
27     return null;
28 }

可以看到一个key value对添加到map中有很多个步骤,接下来我们一步步的分析。

1、inflateTable(threshold):初始化数组容量。

 1 private void inflateTable(int toSize) {
 2     // toSize从上层函数的threshold可以看出,其值默认为16,或者是你自己初始化的大小
 3     // roundUpToPowerOf2会保证数组大小一定是2的n次方,假如你是new HashMap(20),那么capacity的值就位32
 4     int capacity = roundUpToPowerOf2(toSize);
 5 
 6     // 计算扩容阀值:capacity * loadFactor
 7     threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
 8     table = new Entry[capacity];
 9     initHashSeedAsNeeded(capacity);
10 }

这里有一个保持数组容量为2^n的要求,同样的Java8的HashMap和ConcurrentHashMap也是如此,只不过实现略有不同而已。

那为什么要这么做呢,其实啊某些东西的特殊要求要么就是需要满足既定的条件才能实现,要么就是这样要求能更加快捷的达到目的;而我们这个就属于后者,这样写能提高HashMap计算数组下标的效率。

因为模运算(%)的效率不如位运算(&),而sun公司的大佬又发现了hash & (capacity - 1) == hash % capacity。所以就有了这一要求

参考:https://blog.csdn.net/qq_44933374/article/details/98469424

——————————————————————————————————————————————————————————————————————

2、indexFor(hash, table.length):利用hash值计算数据放入的下标。

1 static int indexFor(int h, int length) {
2     // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
3     return h & (length-1);
4 }

——————————————————————————————————————————————————————————————————————

3、addEntry(hash, key, value, i):添加节点到链表中。

 1 void addEntry(int hash, K key, V value, int bucketIndex) {
 2     // HashMap大小已到阀值,且当前数组位置也已经有元素了;所以要扩容了
 3     if ((size >= threshold) && (null != table[bucketIndex])) {
 4         // 扩容
 5         resize(2 * table.length);
 6         // 扩容后重新计算hash值
 7         hash = (null != key) ? hash(key) : 0;
 8         // 重新计算扩容后的下标
 9         bucketIndex = indexFor(hash, table.length);
10     }
11 
12     // 将新值放入链表表头,然后增加HashMap大小
13     createEntry(hash, key, value, bucketIndex);
14 }
15 
16 void createEntry(int hash, K key, V value, int bucketIndex) {
17     Entry<K,V> e = table[bucketIndex];
18     table[bucketIndex] = new Entry<>(hash, key, value, e);
19     size++;
20 }

4、resize(2 * table.length):扩容。

 1 void resize(int newCapacity) {
 2     Entry[] oldTable = table;
 3     int oldCapacity = oldTable.length;
 4     if (oldCapacity == MAXIMUM_CAPACITY) {
 5         threshold = Integer.MAX_VALUE;
 6         return;
 7     }
 8 
 9     // 新的数组
10     Entry[] newTable = new Entry[newCapacity];
11     // 将原来数组中的值迁到新的且更大的数组中
12     transfer(newTable, initHashSeedAsNeeded(newCapacity));
13     table = newTable;
14     threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
15 }

代码比较简单,不多讲。

java.util.HashMap#get:根据key获取value。

 1 public V get(Object key) {
 2     if (key == null)
 3         return getForNullKey();
 4     Entry<K,V> entry = getEntry(key);
 5 
 6     return null == entry ? null : entry.getValue();
 7 }
 8 
 9 final Entry<K,V> getEntry(Object key) {
10     if (size == 0) {
11         return null;
12     }
13 
14     int hash = (key == null) ? 0 : hash(key);
15     for (Entry<K,V> e = table[indexFor(hash, table.length)];
16          e != null;
17          e = e.next) {
18         Object k;
19         if (e.hash == hash &&
20             ((k = e.key) == key || (key != null && key.equals(k))))
21             return e;
22     }
23     return null;
24 }

相对于put,get则更简单;根据key计算hash值,找到对应数组下标后遍历改数组的链表,直到找到==或equals的key。

虽然说不难,但想要看懂代码还是需要有链表基础的,哈哈。

posted @ 2020-07-06 23:05  被猪附身的人  阅读(153)  评论(0编辑  收藏  举报