目录
HashMap底层数据结构
数组 + 链表 + 红黑树 。
或者直接说 哈希表 。
存储的基本原理
底层是一个数组(有下标,顺序存储,查询快)。
链表(哈希冲突时,新增的元素挂载到上一个结点下)。
红黑树(链表查询慢,红黑树是一种平衡二叉树,查询效率高)。
源码分析
代码
语句:HashMap<String,String> map = new HashMap<>();
创建一个HashMap<k,v>类型的集合。
语句:map.put("落花","流水");
给集合存入数据。 k = "落花" ,v = "流水" 。
一、进入 HashMap
this.loadFactor 是 HashMap 里的一个属性。
DEFAULT_LOAD_FACTOR 是一个常量,进入看一眼。
DEFAULT_LOAD_FACTOR -- 默认的加载因子。
此刻 DEFAULT_LOAD_FACTOR = 0.75f 。f 代表是 float 类型的。
loadFactor = 0.75f 。
二、进入 put
前面说过: key = "落花" 。value = "流水" 。
hash(key) -- 表示计算 key 的哈希值。(可以变相的理解为地址值)
然后是将 hash(key), key, value, 作为参数传到了 putVal 方法中。
接下来看一下putVal方法。
二、1. putVal
有这么一堆东西,咱们慢慢来看。
1、
这里,hash 是计算的 K 的哈希值,也就是 "落花" 的哈希值。
语句:Node<K,V>[] tab; Node<K,V> p; int n, i;
这是定义一个初始的 Node 类型的数组 tab 和 p 。
定义一个默认为 0 的 n 和 i 。
此时:tab = null 。
p = null 。
n = 0 。
i = 0 。
Node是啥,咱们进去看看。
Node咱们发现他是 HashMap 的一个内部类。
定义的属性有:
final int hash; -- key的哈希值 ,"落花" 的哈希值 。
final K key; -- "落花" 。
V value; -- "流水" 。
Node<K,V> next; -- 这个是记录下一个数据的节点用的。
还有好多方法,现在用不到。
继续往下执行。
记录变量:
1、tab = null 。
p = null 。
n = 0 。
i = 0 。
2、
语句:if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
此时 table 不知道是什么,进入看一眼:
发现 table 还是一个空的 Node 类型的数组。-- 这是一个底层数组
判断:(tab = table) == null 。成立。里面语句会被执行,但是咱们也看一眼右边是啥意思。(n = tab.length) == 0 。还是成立的。
执行:n = (tab = resize()).length;
咱们进入 resize 看一眼:
注意:重点来了,resize是核心代码。
3、
还是一堆东西,咱一步一步来。
3、1.
语句:Node<K,V>[] oldTab = table;
前面讲过,table 是个底层数组,空的。
所以此时:oldTab = null 。
语句:int oldCap = (oldTab == null) ? 0 : oldTab.length;
三元运算符,括号里的是true,返回冒号前面的值,false,返回冒号后面的值。
所以此时:oldCap = 0 。
语句:int oldThr = threshold;
进入 threshold :
发现 threshold = 0 。
此时:oldThr = 0 。
语句:int newCap, newThr = 0;
默认 newCap = 0 。newTer = 0 。
记录变量:
1、tab = null 。
p = null 。
n = 0 。
i = 0 。
3、1.oldTab = null 。
oldCap = 0 。
newCap = 0 。
threshold = 0 。
newTer = 0 。
3、2.
语句:if (oldCap > 0) {
判断 oldCap 是否大于 0 。此时 oldCap = 0 。所以条件不成立,不执行里面语句。
语句:else if (oldThr > 0)
判断 oldThr 是否大于 0 。此时 oldThr = 0 。所以条件不成立,不执行里面语句。
语句:newCap = DEFAULT_INITIAL_CAPACITY;
因为上面都不成立,所以必须执行 else 里的语句。
咱们进入DEFAULT_INITIAL_CAPACITY;看看是什么。
DEFAULT_INITIAL_CAPACITY 是一个常量。
1 << 4 。意思是 1 * 2的4次幂 == 16 。
所以此时 newCap = 16 。
语句:newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
前面得到:DEFAULT_LOAD_FACTOR = 0.75f 。
DEFAULT_INITIAL_CAPACITY = 16 。
16 乘以 0.75 等于 12 。然后强转为 int 类型 。
所以此时:newThr = 12 。
记录变量:
1、tab = null 。
p = null 。
n = 0 。
i = 0 。
3、1.oldTab = null 。
oldCap = 0 。
newCap = 0 。由
3、2.改为 newCap = 16 。threshold = 0 。
newTer = 0 。由
3、2.改为 newThr = 12 。
3、3.
判断 newThr 是否等于 0 。条件不成立,所以不执行里面语句。
3、4.
语句:threshold = newThr;
此时:threshold = 12 。
语句:Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
这行代码意思是:创建一个新的名为 newTab 的数组,数组长度是newCap。
也就是说创建了一个 Node 类型的长度为 16 的数组。
语句:table = newTab;
将 newTab 赋值给 table 这个底层数组。也就是说 table 指向了这个新数组。
后面代码先不看
最后返回这个新的数组。
记录变量:
1、tab = null 。
p = null 。
n = 0 。
i = 0 。
3、1.oldTab = null 。
oldCap = 0 。
newCap = 0 。由
3、2.改为 newCap = 16 。threshold = 0 。由
3、4.改为 threshold = 12 。newTer = 0 。由
3、2.改为 newThr = 12 。
总结
1. 创建HashMap对象时 -- 默认的加载因子 0.75f。
2. 第一次put时,初始化一个Node类型的数组,数组名叫 table。
3.Node是HashMap一个内部类,里面的组成:
Key 、value、next、hash
4.第一次put时,触发扩容机制resize()。
总结:
创建一个Node类型的数组,数组长度为16,threshold(阈值) 为12。