HashMap源码分析(hashmap 源码分析)

  本篇文章为你整理了HashMap源码分析(hashmap 源码分析)的详细内容,包含有hashmap源码分析和实现原理 hashmap 源码分析 hashmap1.7源码分析 hashmap的源码,实现原理,底层结构 HashMap源码分析,希望能帮助你了解 HashMap源码分析。

  主要过一遍HashMap中的常量、构造方法、put方法(hash、putVal、resize)

  当我们调用put时,实际上就是调用putVal

  

public V put(K key, V value) {

 

   return putVal(hash(key), key, value, false, true);

  

 

  putVal

  

/**

 

   * @param onlyIfAbsent 若为true,插入重复key的值时,不改变已存在的value

   * @param evict 若为false,则表为创建模式(在HashMap中因该没啥用,LinkedhashMap需要注意)

  final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {

   Node K,V [] tab;

   Node K,V

   int n, i; // n为table.length

   // 1. 新创建的HashMap先初始化

   if ((tab = table) == null (n = tab.length) == 0)

   n = (tab = resize()).length;

   // 2. 若指定位置不存在元素(没有hash冲突)

   if ((p = tab[i = (n - 1) hash]) == null) // n为hashMap的长度,即2的幂次,故:(n-1) hash = hash%n

   tab[i] = newNode(hash, key, value, null);

   else {

   // 3. 发生冲突时处理逻辑

   Node K,V

   K k; // 冲突位置上的key

   // 3.1 key相同则覆盖旧值

   if (p.hash == hash ((k = p.key) == key (key != null key.equals(k))))

   e = p;

   // 3.2 已有很多冲突值(已经转为树了),则put进该冲突树

   else if (p instanceof TreeNode)

   e = ((TreeNode K,V )p).putTreeVal(this, tab, hash, key, value);

   // 3.3 拉链法存放

   else {

   // 3.3.1 循环冲突链表,将新来的冲突的元素尾插到链表中

   for (int binCount = 0; ; ++binCount) {

   if ((e = p.next) == null) {

   p.next = newNode(hash, key, value, null);

   if (binCount = TREEIFY_THRESHOLD - 1) // TREEIFY_THRESHOLD = 8

   // 链表转红黑树

   treeifyBin(tab, hash);

   break;

   // 如果新插入的元素是与冲突链表上的key相同的,那么就要覆盖这个key对应的value

   if (e.hash == hash

   ((k = e.key) == key (key != null key.equals(k))))

   // beark后走【a】处的逻辑

   break;

   p = e;

   // 循环结束后,经历尾插后的e为空

   if (e != null) { // 【a】existing mapping for key

   V oldValue = e.value;

   if (!onlyIfAbsent oldValue == null)

   e.value = value;

   afterNodeAccess(e);// 这方法空的,没啥用,在LinkedHashMap中被重写

   return oldValue;

   ++modCount; // 记录被结构修改的次数

   // 如果当前保存的k-v数量(不包含冲突的)大于阈值 则扩容

   if (++size threshold)

   resize();

   afterNodeInsertion(evict);// 这方法空的,没啥用,在LinkedHashMap中被重写

   return null;

  

 

  resize

  初始化或加倍表的大小。

  如果为空,则按照字段阈值中持有的初始容量目标分配。

  否则,因为我们使用的是2的幂展开,所以每个bin中的元素要么必须保持相同的索引,要么在新表中以2的幂偏移量移动。

  

final Node K,V [] resize() {

 

   Node K,V [] oldTab = table;

   int oldCap = (oldTab == null) ? 0 : oldTab.length;

   int oldThr = threshold; // 类成员变量threshold,int类型默认值为0

   int newCap, newThr = 0;

   // 1.确定新阈值和新容量

   // 1.1 元素不为空

   if (oldCap 0) {

   if (oldCap = MAXIMUM_CAPACITY) { // MAXIMUM_CAPACITY = 1 30 = 1073741824

   threshold = Integer.MAX_VALUE;// MAX_VALUE = 2147483647

   return oldTab;

   // newCap扩为原来的2倍 ,若其在[16,1073741824)范围内threshold也翻倍

   else if ((newCap = oldCap 1) MAXIMUM_CAPACITY oldCap = DEFAULT_INITIAL_CAPACITY)

   newThr = oldThr 1;

   // 1.2 元素为空,且阈值有效(可能是调用HashMap(int initialCapacity,float loadFactor)初始化或被清空元素的hashmap)

   else if (oldThr 0)

   newCap = oldThr;

   // 1.3 新创建的HashMap 容量默认为16,阈值默认为(负载因子*容量)0.75*16=12

   else {

   newCap = DEFAULT_INITIAL_CAPACITY;

   newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);

   if (newThr == 0) {

   float ft = (float)newCap * loadFactor;

   newThr = (newCap MAXIMUM_CAPACITY ft (float)MAXIMUM_CAPACITY ?

   (int)ft : Integer.MAX_VALUE);

   threshold = newThr;

   @SuppressWarnings({"rawtypes","unchecked"})

   // 2.扩容

   Node K,V [] newTab = (Node K,V [])new Node[newCap];

   table = newTab;

   if (oldTab != null) {

   // 循环老的元素

   for (int j = 0; j oldCap; ++j) {

   Node K,V

   if ((e = oldTab[j]) != null) {

   oldTab[j] = null;

   // 2.1 该元素链表中没有存储冲突值,重新计算该元素存储位置,并赋值

   if (e.next == null)

   newTab[e.hash (newCap - 1)] = e;

   // 2.2 如果该元素保存到冲突值很多 以至于链表已经转成红了

   else if (e instanceof TreeNode)

   ((TreeNode K,V )e).split(this, newTab, j, oldCap);

   // 2.3 有产生冲突的值,但还没有转成树

   else { // preserve order

   // loHead 与 loTail用于修复并发情况下死循环问题

   Node K,V loHead = null, loTail = null;

   Node K,V hiHead = null, hiTail = null;

   Node K,V next;

   do {

   next = e.next;

   // 空键的hash为零,或出现1001 0110这种情况

   if ((e.hash oldCap) == 0) {

   if (loTail == null)

   loHead = e;

   else

   loTail.next = e;

   loTail = e;

   else {

   if (hiTail == null)

   hiHead = e;

   else

   hiTail.next = e;

   hiTail = e;

   } while ((e = next) != null);

   // 将冲突的链表拎出来,放到数组中

   if (loTail != null) {

   loTail.next = null;

   newTab[j] = loHead;

   if (hiTail != null) {

   hiTail.next = null;

   newTab[j + oldCap] = hiHead;

   return newTab;

  

 

  常量及成员变量

  

// 默认的初始容量 16(必须是2的幂)

 

  static final int DEFAULT_INITIAL_CAPACITY = 1 4;

  // 最大容量

  static final int MAXIMUM_CAPACITY = 1 30;

  // 在构造函数中没有指定时使用的负载因子。

  static final float DEFAULT_LOAD_FACTOR = 0.75f;

  // 在第一次使用时初始化,并根据需要调整大小

  // 长度总是2的幂

  transient Node K,V [] table;

  

 

  

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

 

  public HashMap() {

   this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted

  

 

  

public V put(K key, V value) {

 

   return putVal(hash(key), key, value, false, true);

  

 

  

// 计算 key.hashCode() 并将哈希的较高位传播(XOR)到较低位。 由于该表使用二次幂掩码,因此仅在当前掩码之上位变化的散列集将始终发生冲突。 (已知的例子是在小表中保存连续整数的 Float 键集。)因此,我们应用了一种变换,将高位的影响向下传播。 在位扩展的速度、实用性和质量之间存在折衷。 因为许多常见的散列集已经合理分布(所以不要从传播中受益),并且因为我们使用树来处理 bin 中的大量冲突,我们只是以最便宜的方式对一些移位的位进行异或,以减少系统损失, 以及合并最高位的影响,否则由于表边界,这些最高位将永远不会用于索引计算。

 

  static final int hash(Object key) {

   int h;

   return (key == null) ? 0 : (h = key.hashCode()) ^ (h 16);

  

 

  以上就是HashMap源码分析(hashmap 源码分析)的详细内容,想要了解更多 HashMap源码分析的内容,请持续关注盛行IT软件开发工作室。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: