zoukankan      html  css  js  c++  java
  • HashMap实现原理

    static final int DEFAULT_INITIAL_CAPACITY = 16;// 默认初始容量为16,必须为2的幂  

    static final int MAXIMUM_CAPACITY = 1 << 30;// 最大容量为2的30次方  

    static final float DEFAULT_LOAD_FACTOR = 0.75f;// 默认加载因子0.75  

    transient Entry<K,V>[] table;// Entry数组,哈希表,长度必须为2的幂  

    transient int size;// 已存元素的个数  

    int threshold;// 下次扩容的临界值,size>=threshold就会扩容  threshold = (int)(capacity * loadFactor);  

    finalfloat loadFactor;// 加载因子  

    static final int TREEIFY_THRESHOLD = 8;//由链表转换成树的阈值

    static final int UNTREEIFY_THRESHOLD = 6;//由树转换成链表的阈值

    static final int MIN_TREEIFY_CAPACITY = 64;//被树化时最小的hash表容量,至少是TREEIFY_THRESHOLD的4倍

    数据结构:

    链表散列结构,即数据和链表的结合体。HashMap底层是一个数组结构,数组的每一项是一个链表,当新建一个HashMap时,就会初始化一个数组,数组元素是Map.Entry,拥有K-V对且持有一个指向下一个元素的引用,构成链表

    构造方法:

    HashMap():构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空HashMap

    HashMap(int nitialCapacity): 构造一个带指定初始容量和默认加载因子(0.75)的空HashMap

     HashMap(int initialCapacity, float loadFactor):构造一个带指定初始容量和加载因子的空HashMap

    HashMap(Map<? extendsK,? extendsV> m):构造一个映射关系与指定Map相同的HashMap

    存取实现:

    put元素时,先根据key的hashCode重新计算hash值,根据hash值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。

    存:

    当程序试图将一个key-value对放入HashMap中时,程序首先根据该 key 的 hashCode() 返回值决定该 Entry 的存储的bucket位置:如果两个 Entry 的 key 的 hashCode() 返回值相同,那它们的存储位置相同。如果这两个 Entry 的 key 通过 equals 比较返回 true,新添加 Entry 的 value 将覆盖集合中原有 Entry 的 value,但key不会覆盖。如果这两个 Entry 的 key 通过 equals 比较返回 false,新添加的 Entry 将与集合中原有 Entry 形成 Entry 链,而且新添加的 Entry 位于 Entry 链的头部。

    HashMap是在bucket中储存键对象和值对象,作为Map.Entry,equals比较Entry的key

    取:

    从HashMap中get元素时,首先计算key的hashCode,找到数组中对应位置的某一元素,然后通过key的equals方法在对应位置的链表中找到需要的元素。

    计算索引:

      底层数组长度一定是2的n次方

      int capacity = 1;  

          while (capacity < initialCapacity)  

              capacity <<= 1;

      比取模运算大大优化

      static int indexFor(int h, int length) {  

         return h & (length-1);  

      } 

    Hash:对key的hashCode进一步优化,进行高位处理,使得只有两个hash值相同的才会放入同一个位置上形成链表

    static final int hash(Object key) {
      int h;
      return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

    resize/rehash:

    当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,因为数组的长度是固定的。所以为了提高查询的效率,就要对HashMap的数组进行扩容,数组扩容这个操作也会出现在ArrayList中,这是一个常用的操作,而在HashMap数组扩容之后,最消耗性能的点就出现:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize。

    当HashMap中的元素个数超过数组大小*loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,这是一个折中的取值。也就是说,默认情况下,数组大小为16,那么当HashMap中元素个数超过16*0.75=12的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。

    Fail-Fast机制:

    HashMap不是线程安全的,因此如果在使用迭代器的过程中有其他线程修改了map,那么将抛出ConcurrentModificationException,这就是所谓fail-fast策略。

    实现是通过modCount域,modCount顾名思义就是修改次数,对HashMap内容的修改都将增加这个值,那么在迭代器初始化过程中会将这个值赋给迭代器的expectedModCount

    在迭代过程中,判断modCount跟expectedModCount是否相等,如果不相等就表示已经有其他线程修改了Map

    modCount声明为volatile,保证线程之间修改的可见性。

    在迭代器创建之后,如果从结构上对映射进行修改,除非通过迭代器本身的 remove 方法,其他任何时间任何方式的修改,迭代器都将抛出ConcurrentModificationException

    条件竞争:

    HashMap rehash时可能会产生条件竞争,是因为多线程,但是多线程不应该使用hashMap

    适合作为key:

    要不可变,String就和适合,并且String已经重写了hashCode()和equals()方法

    不可能还有利于线程安全

  • 相关阅读:
    错误 6 未能找到类型或命名空间名称“BLL”(是否缺少 using 指令或程序集引用?)
    linq 两个字段排序
    设置checkbox只读
    乐蜂网SALES倒计时原码展示
    Value cannot be null. Parameter name: source
    浅谈windows.onload()与$(document).ready()
    MVC之ViewData.Model
    td标签里内容不换行
    input type="image" 提交表单
    扩展Puppet – 建立Puppet CA集群
  • 原文地址:https://www.cnblogs.com/zawjdbb/p/7235934.html
Copyright © 2011-2022 走看看