zoukankan      html  css  js  c++  java
  • HashMap

    一、HashMap底层实现

    简单的可以从以下两个纬度去理解HashMap的底层实现原理。

    • 数组:充当索引
    • 链表:处理碰撞

    HashMap用一个指针数组table,离散化key的作用,当加入一个 key 的时候,通过Hash算法,计算出 key所在的数组下标 i,如果table[i]位置的对象元素为null的时候,则直接将<key, value>加入即可;但是,如果table[i]位置已经被占用的话,则会发生冲突碰撞;此时,会在 table[i]上形成一个链表。

    如果table太小,就会发生频繁碰撞;此时,查询时间复杂度由O(1)变为O(n). 
    因此,Hash 表的尺寸和容量非常重要。每次当有新的数据要插入Hash 表时,都会检查容量有没有超过 thredshold,如果超过,需要扩容 Hash 表,这需要改变重新计算hash分桶的位置—— rehash,这种操作是比较耗时的。

    所以,在创建HashMap实例的时候需要预先估计一下需要处理的数据量的大小,提前将table的大小和装载因子load factor设置好,减少Hash碰撞的概率,同时也可以减少扩容hash表的次数,达到节约时间的目的。

    二、源码阅读

    当每次添加新元素都是在链表头部添加元素,那么,问题来了——为什么会造成死锁呢?按理说每次在链表头部添加元素的话,不可能出现死锁现象的。

    问题就出在rehash过程,当将旧table元素转移到新的newTable的时候,我们一块来看看transfer()函数的源码,分析一下原因。

    transfer()源码如下:

        /**
         * Transfers all entries from current table to newTable.
         */
        void transfer(Entry[] newTable, boolean rehash) {
            int newCapacity = newTable.length;
            //Step1 : 首先便利索引数组中的元素,Entry<K,V> e 存储了链表的入口元素
            for (Entry<K,V> e : table) {
                //Step2: 对链表上的每一个元素进行遍历,从Hash表的头部第一个元素开始
                while(null != e) {
                    Entry<K,V> next = e.next;
                    if (rehash) {
                        e.hash = null == e.key ? 0 : hash(e.key);
                    }
                    int i = indexFor(e.hash, newCapacity);
                    e.next = newTable[i];
                    newTable[i] = e;
                    e = next;
                }
            }
        }

    总结:

    1. 首先便利索引数组中的元素,获取到链表的入口节点
    2. 对链表上的每一个节点遍历:先将 e.next 指向新 Hash 表的第一个元素(如果是第一次就是 null),这时候新 Hash 的第一个元素是 e,但是 Hash 指向的却是 e 没转移时候的第一个,所以需要将 Hash 表的第一个元素指向 e.
    3. while循环遍历链表节点,直到全部转移到新的newTable
    4. for循环遍历table,可以理解为链表的入口头节点,直到所有索引数组全部转移到新的newTable

    可以看到转移过程是逆序的,转移前链表顺序是1->2->3,逆序转移后新的t顺序变成 3->2->1。现在就应该才得八九不离十了,是不是有可能在转移的过程中 出现1>2>3>1这种情况,形成一个头尾相连的链表。

  • 相关阅读:
    删除名字和参数
    更改NX TITLE为路径
    我自己写的创建刀具
    创建刀具,很有用的信息
    控件改名
    已知体的最大尺寸在一堆实体里面找这个体
    cam对象类型
    ORACLE导入导出工具的使用
    ORACLE表空间
    Statement与PreparedStatement的区别
  • 原文地址:https://www.cnblogs.com/java-chanjuan/p/8372124.html
Copyright © 2011-2022 走看看