HashMap的存储与数据落入底层原理

底层结构:哈希表/(数组+链表+红黑树)

关于红黑树:

红黑树是二叉树的升级版本,红黑树根据二叉树的原理来储存数据,但是会自动调节数据排列结构,从而优化查询速度。(平衡二叉树,查询效率比链表高。)

HashMap源码分析:

1、创建HashMap对象时候

默认的加载因子(DEFAULT_LOAD_FACTOR) 0.75

2、当第一次进行put操做时候

初始化一个Node类型的数组

数组名叫table类型是Node类型

(Node为HashMap内部类,组成为:

Key、value、next、hash

后判断进入resize()方法中(源码)。

final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; //table = null; int oldCap = (oldTab == null) ? 0 : oldTab.length; //oldCap = 0; int oldThr = threshold; //oldThr = 0 int newCap, newThr = 0; if (oldCap > 0) { if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; else { // 因为参数均为0,故进入这个方法体中 newCap = DEFAULT_INITIAL_CAPACITY; // DEFAULT_INITIAL_CAPACITY=16 默认值 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);// //16*0.75=12 } if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; ///赋值时候 @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];//newCap = 16 table = newTab; ///赋值时候 if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { oldTab[j] = null; if (e.next == null) newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { // preserve order Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; }

如何储存

if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null);

总结:

将数组的长度(n-1)和Key的哈希进行运算得出要存储得下标。

数组长度-1 按位与运算& Key的哈希

得出[0 数组长度-1] 下标

然后判断该下表数组是否为空,如果为空

则存入

第一次put结束

如果不为空,那么步入下面得方法中。

else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } }

当Key值完全相同的情况下(hash和内容)

根据判断可知,如果传递过来得值(所有属性)相等于原来得值

那么原来的 p的 值将给到 e,将value重新 赋值。

当Key值不完全相同的情况下(hash相同【也就是说下标相同】但是内容不同)

那么新的Key与他的value值将会挂载在 老的Key下面形成链表结构。(数组转链表)

总结:

新节点挂载到一个next属性为null的旧节点下

不会无限挂载,会转化成红黑树

当节点重复挂载在next后到达一定数量会将该链表结构转化为红黑树结构。

if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); //存入新的链表结构里 if (binCount >= TREEIFY_THRESHOLD - 1) //TREEIFY_THRESHOLD = 8-1 =7 //就是存了8个(binCount从0开始++) treeifyBin(tab, hash);//调用红黑二叉树方法 break; }

当同一个数组下标下挂载的节点个数超过8时不一定转成红黑树

如果触发了调用红黑二叉树的方法也不一定转换成红黑树,它会先判断

(MIN_TREEIFY_CAPACITY = 64)你的数组长度有没有超过64,如果超过64才会调用 否则会调用resize()方法,对数组进行扩容。

扩容后,重新计算系节点要存的下标。

总结:转为红黑树,有两个前提:

1.数组长度大于64

2.同一个下标下挂载的个数大于等于 8

扩容

后续进行数据填充(2-11次都不会扩容)

第二次扩容

当HashMap中数据量在添加第13个得时候触发HashMap得第二次扩容(在添加完成后【threshold = 12 】)

else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && //newCap= oldCap(16)*2 oldCap >= DEFAULT_INITIAL_CAPACITY) //DEFAULT_INITIAL_CAPACITY=16 newThr = oldThr << 1; //newThr = oldThr(12)*2 }

结论:

由源码可知,数组长度变为原来的2倍(变为32),阈值扩容为原来的2倍(变为24)

由此,第二次扩容结束

HashMap得get方法实现

总结:

通过key得哈希找到存入得节点,返回节点得value值

如何找到节点

通过getNode(hash(key), key)方法

源码如下:

final Node<K,V> getNode(int hash, Object key) { Node<K,V>[] tab; Node<K,V> first, e; int n; K k; if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { if (first.hash == hash && // always check first node ((k = first.key) == key || (key != null && key.equals(k)))) return first; if ((e = first.next) != null) { if (first instanceof TreeNode) return ((TreeNode<K,V>)first).getTreeNode(hash, key); do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null); } } return null; }

1.通过key得哈希和数组长度运算找出相应得下标

2.判断下该节点是否存在下一个结点(next是否为null),如果没有,说明该下标只有一个结点,直接返回即可。(value可以直接用对象点出来)

3.如果有下一个结点,说明该下标下是链表结构,那么循环去比较要找的key得哈希和key的内容,找到了则返回该结点。

4.如果没有一直往下遍历

5.如果都没有返回null

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值