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()方法

不可能还有利于线程安全

 

posted on 2017-07-25 21:34  zawjdbb  阅读(177)  评论(0编辑  收藏  举报

导航