两大常见容器之HashMap是如何实现的?

Posted 刘兆贤

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了两大常见容器之HashMap是如何实现的?相关的知识,希望对你有一定的参考价值。

                 本文来自刘兆贤的博客_CSDN博客-Java高级,Android旅行,Android基础领域博主 ,引用必须注明出处!

最近计划研究HashMap的内部实现,经过三天时间,终于可以写下一些东西。

数据结构:数组+链表。即每个数组中的单个元素,都是一个链表,该元素的下标由key的hash与数组长度决定,该链接的key的hash都相等。

特点:

  1. 它和HashTable大致相同(除了非线程安全和允许空键、空值),不保证排列顺序一直不变;
  2. 不要设置扩容因子过大或者过小,否则超过最大数量后,不再执行hash操作。
  3. 比较时,不再判断hashCode,而是根据key的hash获得下标。
  4. iterator方法执行后,如果map再执行修改,则iterator.remove时会报非同步修改错误。

扩容:

  1. 如果table数量大于0,且大于1<<30,则扩容因子threshold=Integer.MAX_VALUE,用于下次扩容的判断条件,返回table对象不变;
  2. 如果小于上述最大值,则扩容1倍,即threshold(首次为12=16*0.75)*2=24;(ArrayList默认为10个,扩容0.5倍)
  3. 如果e.next为null,则直接将e赋值在当前hash&(newCap-1)的下标位;如果是TreeNode,则使用红黑树的split方法;
  4. 如果e.next不为null,则将此后的对象,分成两个队列(e.next=newE);hash&oldCap==0则放在当前位置j,否则放在新位置(j+oldCap)

hashmap 查找速度:

O(1)+O(lgn),8个以内O(1),以外红黑树O(lgn)。

为什么HashMap扩容1倍?

因为可以减少hash冲突,避免形成链式结构,从而降低查询效率。比如11001&10111=10001 11001&11111=10001

增加/修改:

  1. 默认生成16个数据的数组
  2. 根据key的hash,定义下标,从下标1开始存数据,如果不存在则生成新的对象,如果存在则返回已有对象;
  3. 如果链表超过存储长度,则把链表转变成红黑树进行存储,modCount(修改数量)加1,
  4. 如果目前个数大于threshold(首次为12=16*0.75),则执行上述扩容操作。

借图一张,以表示插入过程

再借一张,表示数据结构

查找:

  1. 根据key的hash找到下标,如果key相等则查到一个对象O;
  2. 否则,如果O是TreeNode,则转成红黑树的查找方式;
  3. 否则,依次查找O.next,直到key相等;

删除:
先查找到目标对象,然后再删除;

  1. 如果对象O直接查出,则O赋值为O.next;
  2. 如果对象O是TreeNode,则按照红黑树的删除方式;
  3. 如果对象是在next队列中找到,则将O赋值为O.next。

一个疑问:
达到12个时,仅设置p.next,未对table进行赋值,为什么却出现在table里?因为table的值会最终汇总。


红黑树

10亿数据的快速查找方案:建立红黑树(旋转-例左转,则将该结点的右结点,转换成该结点的父结点,将右结点的左结点转成该结点的右结点、自平衡、上色),
查找/删除/插入平均和最差效率为O(logn)。

借图一张,表示从pivot左旋转。


性质:

  1. 每个结点,要么黑要么红;
  2. 根结点是黑色;
  3. 空结点都是黑色;
  4. 红色结点,两个子结点是黑色;
  5. 叶结点到空结点路径上,黑结点数量相等。

特点:

  1. 没有连续的红色结点,但可以有连续的黑色结点,根结点是黑色;
  2. 当前结点,总比左结点大,比右结点小;
  3. 最长路径,不超过最短路径的2倍;

三个问题:

  1. 颜色的出现,是为了实现平衡二叉树的特性。
  2. 什么情况下,需要变色?两个相邻结点为红色。
  3. 什么情况下,需要旋转?当变色已经不满足要求时。

位移符号

>>:带符号右移。正数右移高位补0,负数右移高位补1。

例:4 >> 1,结果是2;-4 >> 1,结果是-2。-2 >> 1,结果是-1。

>>>:无符号右移。无论是正数还是负数,高位通通补0。

对于正数而言,>>和>>>没区别。

对于负数而言,-2 >>> 1,结果是2147483647(Integer.MAX_VALUE),-1 >>> 1,结果是2147483647(Integer.MAX_VALUE)。

以下代码可以判断两个数的符号是否相等

return ((a >> 31) ^ (b >> 31)) == 0;

以上是关于两大常见容器之HashMap是如何实现的?的主要内容,如果未能解决你的问题,请参考以下文章

Java中的容器(集合)之HashMap源码解析

java:容器/集合(Map(HashMap,TreeMap))

java集合之HashMap与ConcurrentHashMap的自我理解

并发容器线程安全应对之道-ConcurrentHashMap

高并发编程原理与实战.线程安全.锁原理.同步容器.实战之JAVA架构

Android Gems — Java源码分析之HashMap和SparseArray