HashMap

  • HashMap存储元素的关系结构图


    image.png
image.png
package collection.map;

import java.util.HashMap;
import java.util.Map;

/**
 * @author haishen
 * @date 2019/2/26
 */
public class HashMapTest {
    public static void main(String[] args) {
        Map<String, String> hashMap = new HashMap();
        hashMap.put(null, "1");
        hashMap.put("1", "12");
        hashMap.put("3",null);
        hashMap.get("3");
        System.out.println(hashMap.size());
    }
}
image.png
  • 设置默认的扩容因子,DEFAULT_LOAD_FACTOR = 0.75f;四分之三

put操作

image.png
  • 计算元素key 的hash值;
  • 调用putVal方法。
    • key和value是可以为null的。

计算key的hash值

image.png
  • key.hashCode()函数调用的是key键值类型自带的哈希函数,返回int型散列值(4个字节,32位);
  • 用这个散列值与这个散列值无符号右移16位的的值进行异或运算(相同为0,相反为1)。
  • 可以看出key是可以为null的。
image.png
  • “扰动函数”,右位移16位,正好是32bit的一半,自己的高半区和低半区做异或,就是为了混合原始哈希码的高位和低位,以此来加大低位的随机性。而且混合后的低位掺杂了高位的部分特征,这样高位的信息也被变相保留下来。参考

putVal执行逻辑

image.png
  • 如果表为空,或者表的长度为零,则先进行扩容逻辑;
  • 根据key 的hash 值和数组容量大小-1的与操作的结果索引结果,如果该索引位置没有元素,则将新元素添加进去;
  • 如果有元素,则说明出现了hash冲突,需要解决冲突,解决冲突的方式,
    • 如果存在的节点和新添加的节点的hash值和key相同,确定值老节点,后续更新value即可
    • 如果节点是树节点,走树节点解决冲突的逻辑;
    • 以链表的形式添加节点,如果链表的节点长度超过8,链表转换从红黑树的数据结构。
    • 如果链式节点索引到的节点和新添加的节点的hash值和key相同,确定是老的节点,后续更新value即可;
    • 最后是已存在的节点则更新对应的value值。
  • 判断当前map中记录的元素是否大于设置的阀值,如果大于,还需要进行扩容逻辑。

扩容逻辑

image.png
  • 根据老数组的容量大小判断需要扩容的大小,如果没有设置初始值,则扩容大小为16,如果有初始值,在扩容大小为原容量的2倍;
  • 创建一个新容量大小的数据;
  • 循环遍历老数组,将老素组中的元素放到新的数组中去;
  • 如果指定位置获取的元素没有后继节点,则将该老元素的赋值到该老元素的hash值与新数组大小减一进行与操作后的位置;
  • 如果是树节点,则走树节点重新分配逻辑;
  • 以上几种情况都不符合的话,说明该元素是有后继节点的,需要对后继节点进行重新hash;这个工程中定义了4个关键的Node对象,loHead(低位头节点)、loTail(低位尾节点)、hiHead(高位头节点)、hiTail(高位尾节点);
  • 整个doWhile循环体中做的事情就是判断拿到的节点的hsah值与老的数组容量的与操作结果是否为0,如果为零,则判断该元素还是在扩容前的位置,如果与操作结果不为0,则判断该元素的位置应该在扩容后的新增空间中,具体位置为该元素在(老数组容器中位置+老数组容量)的所指定的值;低位和高位的链式关系通过上面提到的4个关键的Node对象来指定;
  • 最后节点的后继节点遍历完成之后,跳出循环体,将最终的低位头节点设置到扩容前的位置,将最终的高位头节点设置到扩容后的位置。

解决疑问

  • 为什么判断拿到的节点的hsah值与老的数组容量的与操作结果是否为0,就可以区分元素是在扩容前还是在扩容后的位置?
  • 为什么扩容后的位置为老数组容器中位置+老数组容量的值?

解答

  • 首先明确,HashMap的容器大小为2的n次方;元素在数组中的位置是根据元素的hash值和和数组容量减一(对应的二进制值为全1)进行与操作后得到的值来确定的;元素的hash值的计算方式上文有提到,结论是这个值比较到大,大到会远远超出数组的大小,为了能够映射到数组内,所以进行了元素的hash值和数组容量减一的与操作,这个与操作的结果是映射到了数组容量范围内的值;
  • 所以如果元素的hash值小于老数组容量,即hash值不需要截取就对应数组的某一个位置,那么这个元素的hash值与老数组容量(高位为1,低位全为0)进行与操作肯定为零;当数组容量是原来的2倍时,按位与操作更是零,低位还是原来的值,高位是零,所以还在老数组的位置;
  • 所以如果元素的hash值不小于老数组容量,那么这个元素的hash值与老数组容量(高位为1,低位全为0)进行与操作时,按位与操作时低位还是原来的值,元素按位对应老数组容量最高位与操作结果为零时,高位为零,所以还在老数组的位置;元素对应老数组容量最高位不为零时,说明这个元素在数组容量最高位位置值的二进制值就是为1,在元素与新数组容量-1进行按位与操作获取索引位置时最高位为1,所以新所以的位置为(老数组容器中位置+老数组容量)。
  • 总结就是新容量是老容量的2倍,其容量减一(全1)相比时新容量高位多了个1,当元素的hash与容量减一进行按位与操作时,如果对应新容量减一的高位为1,那么与操作之后的位置就比老容量的位置多了个老容量的大小。

get 操作

image.png
  • 执行getNode方法,判断是否有返回值,最后返回查询结果。
image.png
  • 根据key的hash值和容器长度-1 进行与操作索引数组元素;
  • 如果索引的结果的hash值和key满足判断,这查找到节点,返回;
  • 否则,判断如果索引的节点有后继节点,
  • 判断如果过是树节点,走树节点查找逻辑;
  • 如果不是树节点,走链式遍历逻辑,遇到hash值以及key和参数相同的节点则返回。

replace操作

image.png
  • 通过getNode方法查找对应的节点,然后对value重新赋值。

remove操作

image.png
image.png

遍历操作

        Set keys = hashMap.keySet();
        Iterator ite = keys.iterator();
        while (ite.hasNext()) {
            String key = (String) ite.next();
            System.out.println("key:{" + key + "}--->value:{" + hashMap.get(key) + "}");
        }
image.png
  • iterator()对应的方法是KeyIterator;
image.png
  • KeyIterator继承父类HashIterator
image.png
  • HashIterato初始化时会通过doWhile操作,寻找第一个不为null的元素节点;
  • 执行nextNode方法时,判断当前节点是否有后继节点,如果没有从当前节点的索引+1的位置开始返回第一个不为null的节点;
  • 否则就是有后继节点,直接返回后继节点,有先循环遍历链表节点,遍历完成之后;
  • 所以hashmap的元素遍历顺序如下图,元素的输出顺序和添加顺序是不一致的。


    image.png

参考:

注意:JDK1.8后,除了对hashmap增加红黑树节点外,对原有造成死锁的关键原因点(新table复制在头端添加元素)改进为依次在末端添加新的元素。虽然JDK1.8后添加红黑树改进了链表过长查询遍历慢问题和resize时出现导致put死循环的bug,但还是非线性安全的,比如数据丢失等等。因此多线程情况下还是建议使用concurrenthashmap。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,142评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,298评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,068评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,081评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,099评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,071评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,990评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,832评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,274评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,488评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,649评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,378评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,979评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,625评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,643评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,545评论 2 352

推荐阅读更多精彩内容