HashMap1 HashMap2 HashMap3

结构

数组+链表

  1. 数组:key-value形式的Entry数组
  2. 链地址法解决哈希冲突,相同桶位的元素咦链表形式连接存储

image.png

  1. // 表,根据需要调整大小。长度必须是2的幂。
  2. transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE; // 数组
  3. static class Entry<K,V> implements Map.Entry<K,V> {
  4. final K key;
  5. V value;
  6. Entry<K,V> next; // 链表
  7. int hash;
  8. }

创建

  1. // 构造一个具有指定初始容量和加载因子的空HashMap
  2. public HashMap(int initialCapacity, float loadFactor) {
  3. if (initialCapacity < 0)
  4. throw new IllegalArgumentException("Illegal initial capacity: " +
  5. initialCapacity);
  6. if (initialCapacity > MAXIMUM_CAPACITY)
  7. initialCapacity = MAXIMUM_CAPACITY;
  8. if (loadFactor <= 0 || Float.isNaN(loadFactor))
  9. throw new IllegalArgumentException("Illegal load factor: " +
  10. loadFactor);
  11. this.loadFactor = loadFactor;
  12. threshold = initialCapacity;
  13. init();
  14. }
  15. // 构造一个空HashMap,具有指定的初始容量和默认加载因子(0.75)
  16. public HashMap(int initialCapacity) {
  17. this(initialCapacity, DEFAULT_LOAD_FACTOR);
  18. }
  19. // 构造一个具有默认初始容量(16)和默认负载系数(0.75)的空HashMap
  20. public HashMap() {
  21. this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
  22. }
  23. /**
  24. * 使用与指定Map相同的映射构造一个新的HashMap
  25. * HashMap使用默认的装载因子(0.75)创建,初始容量足以容纳指定映射中的映射
  26. */
  27. public HashMap(Map<? extends K, ? extends V> m) {
  28. // 创建一个指定容量,默认负载因子的HashMap
  29. this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
  30. DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
  31. // 初始化Entry[]数组
  32. inflateTable(threshold);
  33. // 将m中的元素转移到HashMap中
  34. putAllForCreate(m);
  35. }

默认创建一个容量是16、负载因子是0.75的HashMap,初始Entry[]数组为空{},在第一次进行put操作插入元素时完成Entry[]的初始化

put操作

  1. /**
  2. * 将指定的值与此映射中的指定键关联
  3. * 如果该映射先前包含了该键的映射,则旧值将被替换
  4. */
  5. public V put(K key, V value) {
  6. if (table == EMPTY_TABLE) {
  7. // 初始化Entry[]数组
  8. inflateTable(threshold);
  9. }
  10. // 添加null键的entry
  11. if (key == null)
  12. return putForNullKey(value);
  13. // 计算key的哈希值,hash()方法保证key的hashcode计算出来后散列性更强
  14. int hash = hash(key);
  15. // 计算出在entry[]的索引位置
  16. int i = indexFor(hash, table.length);
  17. // 遍历entry[i]位置的链表,判断是否已经存在该key,存在即覆盖
  18. for (Entry<K,V> e = table[i]; e != null; e = e.next) {
  19. Object k;
  20. // 覆盖相同key的entry的value值,并返回旧的entry的value值
  21. if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
  22. V oldValue = e.value;
  23. e.value = value;
  24. e.recordAccess(this);
  25. return oldValue;
  26. }
  27. }
  28. // 记录修改次数+1
  29. modCount++;
  30. // 添加元素到map中
  31. addEntry(hash, key, value, i);
  32. return null;
  33. }

putForNullKey

支持一个null键,多个null值

  1. private V putForNullKey(V value) {
  2. // 使用Entry[]的0号位置存放null键的entry
  3. for (Entry<K,V> e = table[0]; e != null; e = e.next) {
  4. // HashMap中已经存在null键entry
  5. if (e.key == null) {
  6. // 覆盖null键的entry值
  7. V oldValue = e.value;
  8. e.value = value;
  9. // 每当对HashMap中已经存在的键k调用put(k,v)覆盖条目中的值时,就会调用这个方法
  10. e.recordAccess(this);
  11. return oldValue;
  12. }
  13. }
  14. // 修改次数+1,用于多线程环境下数据被其他修改后快速抛出异常,fail-fast
  15. modCount++;
  16. // 添加元素到map中
  17. addEntry(0, null, value, 0);
  18. return null;
  19. }

addEntry

  1. // 添加元素到map中
  2. void addEntry(int hash, K key, V value, int bucketIndex) {
  3. // 判断是否需要扩容
  4. if ((size >= threshold) && (null != table[bucketIndex])) {
  5. // 扩容,容量*2
  6. resize(2 * table.length);
  7. hash = (null != key) ? hash(key) : 0;
  8. // 计算key应该存储在新entry[]的桶位
  9. bucketIndex = indexFor(hash, table.length);
  10. }
  11. // 添加entry到集合中
  12. createEntry(hash, key, value, bucketIndex);
  13. }

createEntry

  1. void createEntry(int hash, K key, V value, int bucketIndex) {
  2. // 该桶位头节点
  3. Entry<K,V> e = table[bucketIndex];
  4. // 插入该桶位中,next指针指向之前的头节点
  5. table[bucketIndex] = new Entry<>(hash, key, value, e);
  6. // 记录集合中元素个数加1
  7. size++;
  8. }

扩容操作

  1. Entry[]容量扩大2倍
  2. 将旧Entry[]元素移动到新的Entry[]数组中(是否rehash)

resize

该方法主要用于扩容

  1. /**
  2. * 将此map的内容重新散列到一个具有更大容量的新数组中
  3. * 当此映射中的键数达到其阈值时,将自动调用此方法
  4. * 如果当前容量为MAXIMUM_CAPACITY,该方法不会调整映射的大小,而是将阈值设置Integer.MAX_VALUE
  5. */
  6. void resize(int newCapacity) {
  7. // 存放旧的entry[]数值
  8. Entry[] oldTable = table;
  9. // 存放旧的entry[]数组容量
  10. int oldCapacity = oldTable.length;
  11. // 旧的数组容量 == 最大容量
  12. if (oldCapacity == MAXIMUM_CAPACITY) {
  13. // 扩容阈值设置为最大整数
  14. threshold = Integer.MAX_VALUE;
  15. return;
  16. }
  17. // 创建一个容量为newCapacity的新entry[]数组
  18. Entry[] newTable = new Entry[newCapacity];
  19. // 将旧entry[]数组中的元素迁移到新的entry[]数组中
  20. transfer(newTable, initHashSeedAsNeeded(newCapacity));
  21. // 设置HashMap的table变量指向新的entry[]数组
  22. table = newTable;
  23. // 重新设置扩容阈值
  24. threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
  25. }

transfer

该方法主要用于扩容时把旧entry数组中的全部元素迁移迁移到新的entry数组中

  1. void transfer(Entry[] newTable, boolean rehash) {
  2. // 新entry[]数组的容量
  3. int newCapacity = newTable.length;
  4. // entry数组的每个桶位
  5. for (Entry<K,V> e : table) {
  6. // 每个桶位中的链表遍历
  7. while(null != e) {
  8. // 链表的下一个entry节点
  9. Entry<K,V> next = e.next;
  10. // rehash绝大多数情况下都是false
  11. if (rehash) {
  12. e.hash = null == e.key ? 0 : hash(e.key);
  13. }
  14. // 计算出entry节点在新的entry[]数组中的索引
  15. int i = indexFor(e.hash, newCapacity);
  16. // 头插法将entry节点插入新entry[]数组中(这是多线程情景下导致成环的主要原因)
  17. e.next = newTable[i];
  18. newTable[i] = e;
  19. // 遍历下一个节点
  20. e = next;
  21. }
  22. }
  23. }

扩容一般是扩大2倍,这样的好处是:
容量n为2的幂次方,n-1的二进制会全为1,位运算时可以充分散列,避免不必要的哈希冲突。所以扩容必须2倍就是为了维持容量始终为2的幂次方
原先容量为a,现在容量为2a,那么原先索引为x,现在索引为x或者(x+a)

get操作

  1. // 返回指定键映射到的值,如果该map不包含该键的映射,则返回null
  2. public V get(Object key) {
  3. // key为null的查找
  4. if (key == null)
  5. return getForNullKey();
  6. // 根据key查找对应的entry节点
  7. Entry<K,V> entry = getEntry(key);
  8. // 从entry节点中获取对应的value值
  9. return null == entry ? null : entry.getValue();
  10. }

getForNullKey

  1. /**
  2. * 专门查找空键的方法。Null键映射到索引0
  3. *
  4. * 为了在两种最常用的操作(get和put)中提高性能,这个空情况被拆分为单独的方法,
  5. * 但在其他操作中与条件合并
  6. */
  7. private V getForNullKey() {
  8. // map中没有元素,直接返回null
  9. if (size == 0) {
  10. return null;
  11. }
  12. // table数组0号位置冲突链上查找key为null的entry对应的value值
  13. for (Entry<K,V> e = table[0]; e != null; e = e.next) {
  14. if (e.key == null)
  15. return e.value;
  16. }
  17. return null;
  18. }

getEntry

  1. // 根据key在HashMap中查找相关联的entry
  2. final Entry<K,V> getEntry(Object key) {
  3. // map中没有元素,直接返回null
  4. if (size == 0) {
  5. return null;
  6. }
  7. // 计算key的哈希值
  8. int hash = (key == null) ? 0 : hash(key);
  9. // 找到table索引位置,遍历该索引位置的冲突链
  10. for (Entry<K,V> e = table[indexFor(hash, table.length)];
  11. e != null;
  12. e = e.next) {
  13. Object k;
  14. // 找到对应的key的entry,返回这个entry节点
  15. if (e.hash == hash &&
  16. ((k = e.key) == key || (key != null && key.equals(k))))
  17. return e;
  18. }
  19. return null;
  20. }