JDK7-HashMap源码分析

Posted 程序员小潘

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JDK7-HashMap源码分析相关的知识,希望对你有一定的参考价值。

为了方便阅读和写注释,笔者将 HashMap 源码单独拷出来了,推荐大家也这么做,阅读起来更加轻松,Debug 也很方便,更重要的是,你可以修改源码来测试。

继承关系图

阅读源码建议采用自上而下的结构,建议先看 Map 接口、然后 AbstractMap 抽象类、最后 HashMap。

整体结构分层

Map 接口

定义 Map 具备的功能,使用内部接口 Entry 来对单个映射关系进行封装,Entry 是 Map 的基本组成单元。

AbstractMap

实现了 Map 接口的抽象类,实现了绝大多数方法,put 和 entrySet 没有实现,因为这两个方法涉及到底层的实现逻辑和数据结构,必须交给子类去实现。

HashMap

HashMap 解析

笔者几乎给每个属性和方法都写上了注释,想要源码的朋友可以私信我。

各属性的含义

/**
 * 默认容量:1<<4 = 10000 = 16
 * 必须是2的幂次方数
 */

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

/**
 * 最大容量:1<<30 = 1000000000000000000000000000000 = 1073741824
 * 必须是2的幂次方数
 */

static final int MAXIMUM_CAPACITY = 1 << 30;

/**
 * 默认的负载因子,可通过构造器指定
 * 扩容时机:当前元素数量 >= 数组容量 * 负载因子
 */

static final float DEFAULT_LOAD_FACTOR = 0.75f;

/**
 * 空数组,在HashMap没有put元素时共享
 */

static final Entry<?,?>[] EMPTY_TABLE = {};

/**
 * 存放Entry的数组,默认是空的
 */

transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

/**
 * 当前包含的 key-value 映射关系的数量
 */

transient int size;

/**
 * 扩容的阈值,计算公式:容量 * 负载因子
 * 如果table是空的,表示第一次扩容,则根据初始大小来扩容
 */

int threshold;

/**
 * 扩容的负载因子
 * 如果不指定,则为0.75
 */

final float loadFactor;

/**
 * 记录HashMap在结构上修改的次数,和线程并发有关。
 */

transient int modCount;

/**
 * 容量的默认阈值,可以通过JVM参数来设置。
 * 超过该值,String类型的key会使用另外一种哈希算法,来减少哈希冲突。
 * 该值会影响哈希种子修改的时机。
 */

static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;

/**
 * 哈希种子,和计算Key的哈希算法有关
 * 当hashSeed!=0 且 Key的类型为String时,则采用Hashing.stringHash32()算法来计算哈希。
 * 目的:减少String类型作为Key所带来的哈希冲突。
 * 哈希种子何时会改变?去看 initHashSeedAsNeeded()
 */

transient int hashSeed = 0;

内部类 Holder

该类没有任何方法,只有一个属性ALTERNATIVE_HASHING_THRESHOLD,它只做了一件事:读取 JVM 参数,来设置修改哈希种子的时机。

/**
 * 该类只做了一件事:读取JVM参数,来设置修改哈希种子的时机。
 */

private static class Holder {

 /**
  * 容量超过该值时,采用其他哈希算法,默认Integer.MAX_VALUE,可通过JVM参数设置
  */

 static final int ALTERNATIVE_HASHING_THRESHOLD;

 static {
  String altThreshold = java.security.AccessController.doPrivileged(
    new sun.security.action.GetPropertyAction(
      "jdk.map.althashing.threshold"));

  int threshold;
  try {
   threshold = (null != altThreshold)
     ? Integer.parseInt(altThreshold)
     : ALTERNATIVE_HASHING_THRESHOLD_DEFAULT;

   // disable alternative hashing if -1
   if (threshold == -1) {
    threshold = Integer.MAX_VALUE;
   }

   if (threshold < 0) {
    throw new IllegalArgumentException("value must be positive integer.");
   }
  } catch(IllegalArgumentException failed) {
   throw new Error("Illegal value for 'jdk.map.althashing.threshold'", failed);
  }

  ALTERNATIVE_HASHING_THRESHOLD = threshold;
 }
}

内部类 Entry 实现了 Map.Entry 接口,将映射关系封装在 Entry 中。因为基于 Hash,所以还记录了 hash 值。因为基于链表结构,所以记录了指向下一个节点的 next。

除此之外,还预留了两个钩子函数给子类扩展。

/**
 * 实现了Map.Entry接口,将映射关系封装在Entry中。
 * 因为基于Hash,所以还记录了hash值。
 * 因为基于链表结构,所以记录了指向下一个节点的next。
 */

static class Entry<K,Vimplements Map.Entry<K,V{
 final K key;
 V value;
 Entry<K,V> next;
 int hash;

 /**
  * Creates new entry.
  */

 Entry(int h, K k, V v, Entry<K,V> n) {
  value = v;
  next = n;
  key = k;
  hash = h;
 }

 public final K getKey() {
  return key;
 }

 public final V getValue() {
  return value;
 }

 public final V setValue(V newValue) {
  V oldValue = value;
  value = newValue;
  return oldValue;
 }

 public final boolean equals(Object o) {
  if (!(o instanceof Map.Entry))
   return false;
  Map.Entry e = (Map.Entry)o;
  Object k1 = getKey();
  Object k2 = e.getKey();
  if (k1 == k2 || (k1 != null && k1.equals(k2))) {
   Object v1 = getValue();
   Object v2 = e.getValue();
   if (v1 == v2 || (v1 != null && v1.equals(v2)))
    return true;
  }
  return false;
 }

 public final int hashCode() {
  return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
 }

 public final String toString() {
  return getKey() + "=" + getValue();
 }

 /**
  * Entry添加时的钩子,也是为了给子类用的,例如:LinkedHashMap
  */

 void recordAccess(HashMap<K,V> m) {
 }

 /**
  * Entry删除时的钩子,也是为了给子类用的,例如:LinkedHashMap
  */

 void recordRemoval(HashMap<K,V> m) {
 }
}

构造器

/**
 * 指定初始大小、负载因子来构建Map
 * 初始化时,虽然扩容阈值 = 初始大小,
 * 但是第一次put时,扩容阈值会被重新设置为:初始大小 * 负载因子
 */

public HashMap(int initialCapacity, float loadFactor) {
 if (initialCapacity < 0)
  throw new IllegalArgumentException("Illegal initial capacity: " +
    initialCapacity);
 if (initialCapacity > MAXIMUM_CAPACITY)
  initialCapacity = MAXIMUM_CAPACITY;
 if (loadFactor <= 0 || Float.isNaN(loadFactor))
  throw new IllegalArgumentException("Illegal load factor: " +
    loadFactor);

 this.loadFactor = loadFactor;
 threshold = initialCapacity;
 init();
}

/**
 * 指定初始大小来构建Map
 */

public HashMap(int initialCapacity) {
 this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

/**
 * 初始大小16 负载因子0.75 来构建Map
 */

public HashMap() {
 this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}

/**
 * 根据Map来构建一个新的HashMap。
 * 初始大小根据旧Map的大小计算而来,负载因子0.75
 */

public HashMap(Map<? extends K, ? extends V> m) {
 this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
   DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
 inflateTable(threshold);
 putAllForCreate(m);
}

构造器的最后,会调用一个 init(),这个方法在 HashMap 中是空的,它其实是提供给子类用的钩子函数。

/**
 * 提供给子类初始化用的钩子函数,例如:LinkedHashMap。
 */

void init() {
}

数据结构

HashMap 采用数组+链表的结构来存放数据,画了一下结构图,大致如下图所示:

JDK7-HashMap源码分析
在这里插入图片描述

当我们调用 put()时,HashMap 做了什么?

整理了一下,put()方法内部的大致逻辑,如下图所示:

在这里插入图片描述

PUT

/**
 * put的逻辑
 */

public V put(K key, V value) {
 if (table == EMPTY_TABLE) {
  //第一次put时,table是空的,需要先扩容(懒加载)
  inflateTable(threshold);
 }
 if (key == null)
  //key为null的put逻辑
  return putForNullKey(value);

 //计算key的哈希
 int hash = hash(key);
 //计算放在table的哪个下标(放在哪个桶里)
 int i = indexFor(hash, table.length);

 //遍历链表,判断Key是否已存在
 for (Entry<K,V> e = table[i]; e != null; e = e.next) {
  Object k;
  if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
   //Key已存在,则覆盖
   //判断是否覆盖的逻辑可以记一下
   V oldValue = e.value;
   e.value = value;
   e.recordAccess(this);
   return oldValue;
  }
 }
 //Key不存在,新增操作
 modCount++;
 addEntry(hash, key, value, i);
 return null;
}

第一次 put 时,table 是空的,需要先扩容(懒加载)

/**
 * 容器初始化扩容
 * 容器的大小必须为2的幂次方数
 * roundUpToPowerOf2()目的:找到 >= toSize的2的幂次方数
 */

private void inflateTable(int toSize) {
 // Find a power of 2 >= toSize
 int capacity = roundUpToPowerOf2(toSize);

 threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
 table = new Entry[capacity];
 initHashSeedAsNeeded(capacity);
}

容器的大小必须是 2 的幂次方,通过 roundUpToPowerOf2()获得

/**
 * 找到 >= number 的2的幂次方数
 * 因为容器的大小必须为2的幂次方数
 * 又是位运算,这个算法很巧妙,可以多研究研究...
 */

private static int roundUpToPowerOf2(int number) {
 // assert number >= 0 : "number must be non-negative";
 return number >= MAXIMUM_CAPACITY
   ? MAXIMUM_CAPACITY
   : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
}

初始化时判断是否要修改哈希种子,涉及到后面的哈希算法

/**
 * 哈希种子是否要修改 -> 是否采用其他哈希算法
 * 触发时机:容量 >= Integer.MAX_VALUE (可通过JVM参数设置)
 */

final boolean initHashSeedAsNeeded(int capacity) {
 boolean currentAltHashing = hashSeed != 0;
 boolean useAltHashing = sun.misc.VM.isBooted() &&
   (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
 boolean switching = currentAltHashing ^ useAltHashing;
 if (switching) {
  hashSeed = useAltHashing
    ? Hashing.randomHashSeed(this)
    : 0;
 }
 return switching;
}

初始化完成后,如果 Key 为 null,单独处理

/**
 * Key为null时的put逻辑
 */

private V putForNullKey(V value) {
 for (Entry<K,V> e = table[0]; e != null; e = e.next) {
  //table[0]有元素占用 直接覆盖
  if (e.key == null) {
   V oldValue = e.value;
   e.value = value;
   //给子类用的钩子函数
   e.recordAccess(this);
   return oldValue;
  }
 }
 //table[0]无元素,添加Entry。
 //hash为0,且放在table的第0个下标。
 modCount++;
 addEntry(0null, value, 0);
 return null;
}

哈希算法

/**
 * 计算Key的哈希函数
 * 如果哈希种子不为0,且Key类型是String,则采用Hashing.stringHash32()哈希算法。
 * 哈希种子修改时机,去看 initHashSeedAsNeeded()。
 * 默认的哈希算法:拿到Key的hashCode,做了一次再散列。
 * 防止开发者重写的hashCode()分散性太差,而导致HashMap的哈希冲突。
 * Key为Null,总是会映射到数组的第0个下标。
 */

final int hash(Object k) {
 int h = hashSeed;
 if (0 != h && k instanceof String) {
  return Hashing.stringHash32((String) k);
 }
 h ^= k.hashCode();
 // This function ensures that hashCodes that differ only by
 // constant multiples at each bit position have a bounded
 // number of collisions (approximately 8 at default load factor).
 h ^= (h >>> 20) ^ (h >>> 12);
 return h ^ (h >>> 7) ^ (h >>> 4);
}

计算放在 table 的哪个下标(放在哪个桶里)

/**
 * 根据Key计算的哈希值来判断元素应该放到数组的哪个下标位置
 */

static int indexFor(int h, int length) {
 /**
  * Java采用的是位运算,并没有采用 取模运算。
  * 效果是一样的,但是位运算效率更高。
  */

 return h & (length-1);
}

如果 Key 存在则覆盖,不存在,则准备插入,实际插入前,会判断是否需要扩容。

/**
 * 将Entry添加到table[bucketIndex]之前的处理逻辑
 * 添加之前,先判断是否需要扩容。
 * 扩容逻辑:元素 >= 扩容阈值,且 对应桶里的元素不为空。
 * 因为:如果桶里元素为空,说明table并没有填充的均匀,只是链表太长,链表过长进行扩容没有意义。
 * 理想:元素的分散性好,桶内元素均匀,链表短。
 */

void addEntry(int hash, K key, V value, int bucketIndex) {
 if ((size >= threshold) && (null != table[bucketIndex])) {
  //扩容
  resize(2 * table.length);
  //扩容后需要重新计算哈希,因为哈希种子可能被修改
  hash = (null != key) ? hash(key) : 0;
  //重新计算需要被放到table的哪个下标位置
  bucketIndex = indexFor(hash, table.length);
 }
 //构建Entry,并添加到table[bucketIndex]中。
 createEntry(hash, key, value, bucketIndex);
}

扩容逻辑

/**
 * Map扩容,逻辑:当前容量 * 2
 * 创建新的数组,元素转移
 */

void resize(int newCapacity) {
 Entry[] oldTable = table;
 int oldCapacity = oldTable.length;
 //超过最大容量,不再扩容
 if (oldCapacity == MAXIMUM_CAPACITY) {
  threshold = Integer.MAX_VALUE;
  return;
 }
 Entry[] newTable = new Entry[newCapacity];
 //元素转移,从旧数组 -> 新数组、判断是否要采用其他哈希算法
 transfer(newTable, initHashSeedAsNeeded(newCapacity));
 table = newTable;
 //重新设置扩容阈值
 threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}

扩容后,需要进行元素转移,每次扩容都要判断是否需要重新哈希。

/**
 * 转移所有的Entry到新数组中
 * rehash:是否需要重新哈希,详情去看initHashSeedAsNeeded(),绝大多数为false
 */

void transfer(Entry[] newTable, boolean rehash) {
 int newCapacity = newTable.length;
 for (Entry<K,V> e : table) {
  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;
  }
 }
}

元素转移后,table 指向新数组,并重新设置扩容阈值。

扩容以后,对将要新插入的元素重新计算哈希,因为哈希种子可能被修改。还要重新计算桶下标,因为扩容后 table.length 已发生变化。

这些工作处理完成以后,才是真正的插入,新元素会插入到桶内链表的头节点,因为效率最高。

/**
 * 构建Entry,并添加到table[bucketIndex]中。
 * 如果桶内已经有元素,会直接添加到头节点中,因为效率最高,next指向旧的头节点。
 */

void createEntry(int hash, K key, V value, int bucketIndex) {
 Entry<K,V> e = table[bucketIndex];
 table[bucketIndex] = new Entry<>(hash, key, value, e);
 size++;
}

至此,PUT 操作全部结束。

PUT 完成后,HashMap 会调用 recordAccess 钩子函数,为了子类扩展。

HashMap 的问题

  • 为什么官方文档说 HashMap 不保证元素的顺序恒定不变?因为扩容后,元素转移时,链表的顺序会调换。
  • 为什么 HashMap 线程不安全?
在这里插入图片描述

GET

相较于 PUT,GET 的代码就简单很多了。

/**
 * 从容器中根据key获取映射的value
 * 返回Null有两种情况:
 * 1、Key不存在
 * 2、Key映射的value就是Null
 * 可以通过containsKey()判断。
 */

public V get(Object key) {
 if (key == null)
  return getForNullKey();
 Entry<K,V> entry = getEntry(key);
 return null == entry ? null : entry.getValue();
}

获取 Key 为 Null 的 value 单独处理,因为 Key 为 null 的元素强制放在 table[0]。

/**
 * 获取Key为null对应的value
 */

private V getForNullKey() {
 if (size == 0) {
  //容器为空,直接返回null
  return null;
 }
 //Key为null的元素强制放在table[0]里
 for (Entry<K,V> e = table[0]; e != null; e = e.next) {
  if (e.key == null)
   return e.value;
 }
 return null;
}

Key 不为 Null 就要去数组中寻找了,步骤一样的,先计算哈希,再计算桶下标,再遍历链表查找。

/**
 * 获取指定Key的映射关系
 */

final Entry<K,V> getEntry(Object key) {
 if (size == 0) {
  return null;
 }
 //先计算Key的哈希
 int hash = (key == null) ? 0 : hash(key);
 //判断在哪个桶里,然后遍历链表
 for (Entry<K,V> e = table[indexFor(hash, table.length)];
   e != null;
   e = e.next) {
  Object k;
  if (e.hash == hash &&
    ((k = e.key) == key || (key != null && key.equals(k))))
   return e;
 }
 return null;
}

REMOVE

删除的代码也比较简单。

/**
 * 从容器中删除指定Key的映射关系,并返回
 */

public V remove(Object key) {
 Entry<K,V> e = removeEntryForKey(key);
 return (e == null ? null : e.value);
}

先计算哈希,计算桶下标,遍历链表找到元素,修改一下链表的指向就可以了。

/**
 * 根据Key删除Entry
 * 先计算哈希,判断在哪个桶里,
 * 然后遍历链表,找到了就删除(重新设置链表指向)
 */

final Entry<K,V> removeEntryForKey(Object key) {
 if (size == 0) {
  return null;
 }
 int hash = (key == null) ? 0 : hash(key);
 int i = indexFor(hash, table.length);
 Entry<K,V> prev = table[i];
 Entry<K,V> e = prev;

 while (e != null) {
  Entry<K,V> next = e.next;
  Object k;
  if (e.hash == hash &&
    ((k = e.key) == key || (key != null && key.equals(k)))) {
   modCount++;
   size--;
   if (prev == e)
    //要删除的是链表头节点,直接头节点指向next即可。
    table[i] = next;
   else
    //否则,上一个节点的next,指向当前删除节点的next
    prev.next = next;
   e.recordRemoval(this);
   return e;
  }
  prev = e;
  e = next;
 }

 return e;
}

REMOVE 完成后,HashMap 会调用 recordRemoval 钩子函数,为了子类扩展。

其他一些方法

putAll、批量插入。

/**
 * 将指定容器中的所有元素添加到该容器中,Key存在会被替换。
 * 与putAllForCreate不同的是,该方法会进行扩容
 */

public void putAll(Map<? extends K, ? extends V> m) {
 int numKeysToBeAdded = m.size();
 if (numKeysToBeAdded == 0)
  return;

 if (table == EMPTY_TABLE) {
  inflateTable((int) Math.max(numKeysToBeAdded * loadFactor, threshold));
 }

 if (numKeysToBeAdded > threshold) {
  int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1);
  if (targetCapacity > MAXIMUM_CAPACITY)
   targetCapacity = MAXIMUM_CAPACITY;
  int newCapacity = table.length;
  while (newCapacity < targetCapacity)
   newCapacity <<= 1;
  if (newCapacity > table.length)
   resize(newCapacity);
 }

 for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
  put(e.getKey(), e.getValue());
}

putAllForCreate、不扩容的前提下批量插入,适用于已知长度的情况下提升性能。

/**
 * 不扩容的前提下,将旧Map里的元素,添加到当前容器。
 * 已知长度的情况下,一次性构建即可,没必要扩容,降低性能。
 */

private void putAllForCreate(Map<? extends K, ? extends V> m) {
 for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
  putForCreate(e.getKey(), e.getValue());
}

putForCreate、不扩容的插入,反序列化时有用。

/**
 * 逻辑和put类似,只是不会去扩容表。
 * 作用:反序列化时有用,一次性构建,而不是多次扩容。
 */

private void putForCreate(K key, V value) {
 int hash = null == key ? 0 : hash(key);
 int i = indexFor(hash, table.length);

 /**
  * Look for preexisting entry for key.  This will never happen for
  * clone or deserialize.  It will only happen for construction if the
  * input Map is a sorted map whose ordering is inconsistent w/ equals.
  */

 for (Entry<K,V> e = table[i]; e != null; e = e.next) {
  Object k;
  if (e.hash == hash &&
    ((k = e.key) == key || (key != null && key.equals(k)))) {
   e.value = value;
   return;
  }
 }

 createEntry(hash, key, value, i);
}

clone

/**
 * 重写clone方法,返回一个元素相同的新容器
 * @return
 */

public Object clone() {
 HashMap<K,V> result = null;
 try {
  result = (HashMap<K,V>)super.clone();
 } catch (CloneNotSupportedException e) {
  // assert false;
 }
 if (result.table != EMPTY_TABLE) {
  result.inflateTable(Math.min(
    (int) Math.min(
      size * Math.min(1 / loadFactor, 4.0f),
      // we have limits...
      HashMap.MAXIMUM_CAPACITY),
    table.length));
 }
 result.entrySet = null;
 result.modCount = 0;
 result.size = 0;
 result.init();
 result.putAllForCreate(this);

 return result;
}

内部迭代器抽象类:HashIterator,主要为了方便迭代所有的 Key、Value、Entry。

子类有:

  • ValueIterator
  • KeyIterator
  • EntryIterator

序列化和反序列化的处理

/**
 * 序列化时将内容写入到流中。
 */

private void writeObject(java.io.ObjectOutputStream s)
  throws IOException
{
 // Write out the threshold, loadfactor, and any hidden stuff
 s.defaultWriteObject();

 // Write out number of buckets
 if (table==EMPTY_TABLE) {
  s.writeInt(roundUpToPowerOf2(threshold));
 } else {
  s.writeInt(table.length);
 }

 // Write out size (number of Mappings)
 s.writeInt(size);

 // Write out keys and values (alternating)
 if (size > 0) {
  for(Map.Entry<K,V> e : entrySet0()) {
   s.writeObject(e.getKey());
   s.writeObject(e.getValue());
  }
 }
}

private static final long serialVersionUID = 362498820763181265L;

/**
 * 反序列化时从流中构建容器
 */

private void readObject(java.io.ObjectInputStream s)
  throws IOException, ClassNotFoundException
{
 // Read in the threshold (ignored), loadfactor, and any hidden stuff
 s.defaultReadObject();
 if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
  throw new InvalidObjectException("Illegal load factor: " +
    loadFactor);
 }

 // set other fields that need values
 table = (Entry<K,V>[]) EMPTY_TABLE;

 // Read in number of buckets
 s.readInt(); // ignored.

 // Read number of mappings
 int mappings = s.readInt();
 if (mappings < 0)
  throw new InvalidObjectException("Illegal mappings count: " +
    mappings);

 // capacity chosen by number of mappings and desired load (if >= 0.25)
 int capacity = (int) Math.min(
   mappings * Math.min(1 / loadFactor, 4.0f),
   // we have limits...
   HashMap.MAXIMUM_CAPACITY);

 // allocate the bucket array;
 if (mappings > 0) {
  inflateTable(capacity);
 } else {
  threshold = capacity;
 }

 init();  // Give subclass a chance to do its thing.

 // Read the keys and values, and put the mappings in the HashMap
 for (int i = 0; i < mappings; i++) {
  K key = (K) s.readObject();
  V value = (V) s.readObject();
  putForCreate(key, value);
 }
}

清空容器

/**
 * 清空容器,数组、链表仍然存在,只是将所有桶里的第0个元素置为空。
 */

public void clear() {
 modCount++;
 Arrays.fill(table, null);
 size = 0;
}

可能有部分代码没有贴出来,如果有遗漏,可以私信笔者。


以上是关于JDK7-HashMap源码分析的主要内容,如果未能解决你的问题,请参考以下文章

Android 逆向整体加固脱壳 ( DEX 优化流程分析 | DexPrepare.cpp 中 dvmOptimizeDexFile() 方法分析 | /bin/dexopt 源码分析 )(代码片段

Android 事件分发事件分发源码分析 ( Activity 中各层级的事件传递 | Activity -> PhoneWindow -> DecorView -> ViewGroup )(代码片段

《Docker 源码分析》全球首发啦!

mysql jdbc源码分析片段 和 Tomcat's JDBC Pool

Android 逆向ART 脱壳 ( DexClassLoader 脱壳 | DexClassLoader 构造函数 | 参考 Dalvik 的 DexClassLoader 类加载流程 )(代码片段

Android 逆向ART 脱壳 ( DexClassLoader 脱壳 | DexClassLoader 构造函数 | 参考 Dalvik 的 DexClassLoader 类加载流程 )(代码片段