Jdk1.7 HashMap源码分析与思考

本文深入剖析了Java中HashMap的数据结构、工作原理及其核心方法。从成员变量、构造函数到主要方法,如put、扩容、解决hash冲突等,全面解读HashMap的内部机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

HashMap类图

HashMap类图
根据类图可知,HashMap实现了三个接口继承了一个抽象类。

实现的接口概览

  • 接口如下:
  1. java.util.Map

    其中Map接口中定义了Map基本操作方法,详细接口描述请参考java.util.Map接口描述.
    Map接口中采用一个内部接口java.util.Map.Entry来封装每一个键值对,这样Map中的元素就变成了Map.Entry<K,V>的映射项。

  2. java.lang.Cloneable

    实现此接口可以对HashMap进行克隆, HashMap默认为浅克隆.
    更多了解请点击:
    HashMap的clone方法(浅克隆)
    HashMap对象的深层克隆

继承的抽象类概览

  • 抽象类如下
  1. java.util.AbstractMap

    AbstractMap 是 Map 接口的的实现类之一,也是 HashMap, TreeMap, ConcurrentHashMap 等类的父类。
    Java 集合深入理解(15):AbstractMap

HashMap 成员变量

HashMap定义了13个成员变量。
HashMap成员变量

成员变量1:DEFAULT_INITIAL_CAPACITY

    /**
    * The default initial capacity - MUST be a power of two.
    */
   static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

此代码段定义HashMap初始容量大小为16,必须为2的幂,源码中使用了移位运算符计算出HashMap的初始容。
问题1: 为什么源码中不直接写成16而要使用移位运算呢?.
答案1: 我的理解是因为操作系统最终会使用二进制进行计算,这样写省略了转换过程,提高了效率。
问题2: 为什么初始容量大小是16,而不是5(奇数代表),8(2的3次幂代表),10(偶数代表)?.
答案2: 待补充

成员变:2:MAXIMUM_CAPACITY

/**
    * The maximum capacity, used if a higher value is implicitly specified
    * by either of the constructors with arguments.
    * MUST be a power of two <= 1<<30.
    */
   static final int MAXIMUM_CAPACITY = 1 << 30;

定义HashMap最大容量,如果使用者指定大于这个值,HashMap会使用该容量。1<<30 为2 ^ 30=1073741824。
问题1: 那么为什么定义是2^30 ?.
答案1: 待补充

成员变:3:DEFAULT_LOAD_FACTOR

/**
  * The load factor used when none specified in constructor.
  */
 static final float DEFAULT_LOAD_FACTOR = 0.75f;

HashMap默认的负载因子,用于扩容。
问题1: 那么为什么定义是0.75f ?.
答案1: 待补充

成员变量4:EMPTY_TABLE

/**
  * An empty table instance to share when the table is not inflated.
  */
 static final Entry<?,?>[] EMPTY_TABLE = {};

定义共享的空表实例。
使用static final关键字修饰变量,说明此实例本身虚拟机加载后放入运行时数据区的方法区,全局共享,实例本身不可变,但是因为是容器类型的实例变量,所以容器中的对象是可以被修改的。

成员变量5:table

/**
  * The table, resized as necessary. Length MUST Always be a power of two.
  */
 transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

定义HashMap内部数据结构,类型为Entity,长度由容量指定。

成员变量:6:size

/**
  * The number of key-value mappings contained in this map.
  */
 transient int size;

HashMap容量大小变量
使用transient关键字说明此变量不需要序列化。

成员变量7:threshold

/**
  * The next size value at which to resize (capacity * load factor).
  * @serial
  */
 // If table == EMPTY_TABLE then this is the initial capacity at which the
 // table will be created when inflated.
 int threshold;

HashMap的极限容量,扩容临界点(容量和加载因子的乘积)

成员变量8:loadFactor

/**
  * The load factor for the hash table.
  *
  * @serial
  */
 final float loadFactor;

hash table负载因子变量定义
使用final关键字修饰后,赋值一次,赋值后不能再被改变

成员变量9:modCount

/**
  * The number of times this HashMap has been structurally modified
  * Structural modifications are those that change the number of mappings in
  * the HashMap or otherwise modify its internal structure (e.g.,
  * rehash).  This field is used to make iterators on Collection-views of
  * the HashMap fail-fast.  (See ConcurrentModificationException).
  */
 transient int modCount;

HashMap修改的次数变量定义。

成员变量10:ALTERNATIVE_HASHING_THRESHOLD_DEFAULT

/**
  * The default threshold of map capacity above which alternative hashing is
  * used for String keys. Alternative hashing reduces the incidence of
  * collisions due to weak hash code calculation for String keys.
  * <p/>
  * This value may be overridden by defining the system property
  * {@code jdk.map.althashing.threshold}. A property value of {@code 1}
  * forces alternative hashing to be used at all times whereas
  * {@code -1} value ensures that alternative hashing is never used.
  */
 static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;

默认的容器阀值大小。

成员变量11:hashSeed

 /**
  * A randomizing value associated with this instance that is applied to
  * hash code of keys to make hash collisions harder to find. If 0 then
  * alternative hashing is disabled.
  */
 transient int hashSeed = 0;

哈希种子(hashSeed),这个hashSeed用于优化哈希函数,默认为0是不使用替代哈希算法,但是也可以自己去设置hashSeed的值,以达到优化效果。

成员变量12:entrySet

// Views

 private transient Set<Map.Entry<K,V>> entrySet = null;

定义Map.Entry<K,V>集合。

成员变量13:serialVersionUID

private static final long serialVersionUID = 362498820763181265L;

随机生成的序列化ID

HashMap四个构造函数

构造函数1: HashMap()

/**
  * Constructs an empty <tt>HashMap</tt> with the default initial capacity
  * (16) and the default load factor (0.75).
  */
 public HashMap() {
     this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
 }

HashMap默认的构造函数,默认容量大小为16,负载因子为0.75,此两个成员变量在上文中有详细描述。

构造函数2:HashMap(int initialCapacity)

/**
  * Constructs an empty <tt>HashMap</tt> with the specified initial
  * capacity and the default load factor (0.75).
  *
  * @param  initialCapacity the initial capacity.
  * @throws IllegalArgumentException if the initial capacity is negative.
  */
 public HashMap(int initialCapacity) {
     this(initialCapacity, DEFAULT_LOAD_FACTOR);
 }

可以指定容量大小的HashMap构造,负载因子使用默认的0.75.

构造函数3: HashMap(Map<? extends K, ? extends V> m)

 /**
  * Constructs a new <tt>HashMap</tt> with the same mappings as the
  * specified <tt>Map</tt>.  The <tt>HashMap</tt> is created with
  * default load factor (0.75) and an initial capacity sufficient to
  * hold the mappings in the specified <tt>Map</tt>.
  *
  * @param   m the map whose mappings are to be placed in this map
  * @throws  NullPointerException if the specified map is null
  */
 public HashMap(Map<? extends K, ? extends V> m) {
     this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
                   DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
      //根据计算出来的扩容阀值初始化table,hashSeed, threshold            
     inflateTable(threshold);
 	 //将传入的Map数据循环放入此HashMap中
     putAllForCreate(m);
 }

构造函数4:HashMap(int initialCapacity, float loadFactor)

   /**
  * Constructs an empty <tt>HashMap</tt> with the specified initial
  * capacity and load factor.
  *
  * @param  initialCapacity the initial capacity
  * @param  loadFactor      the load factor
  * @throws IllegalArgumentException if the initial capacity is negative
  *         or the load factor is nonpositive
  */
 public HashMap(int initialCapacity, float loadFactor) {
   //如果容量小于0,则抛异常
     if (initialCapacity < 0)
         throw new IllegalArgumentException("Illegal initial capacity: " +
                                            initialCapacity);
        //如果设置的容量大于定义的最大容量,则取最大容量值                                    
     if (initialCapacity > MAXIMUM_CAPACITY)
         initialCapacity = MAXIMUM_CAPACITY;
         //如果负载因子小于0或者是非法值则抛异常
     if (loadFactor <= 0 || Float.isNaN(loadFactor))
         throw new IllegalArgumentException("Illegal load factor: " +
                                            loadFactor);
 	//初始化负载因子和扩容阀值
     this.loadFactor = loadFactor;
     threshold = initialCapacity;
     //空实现,不需关注
     init();
 }

其他三个构造器都会调用此构造器。

主要方法详解

public V put(K key, V value)方法

将指定的值与此映射中的指定键相关联。 如果映射先前包含键的映射,则替换旧值。源码如下:

 /**
  * Associates the specified value with the specified key in this map.
  * If the map previously contained a mapping for the key, the old
  * value is replaced.
  *
  * @param key key with which the specified value is to be associated
  * @param value value to be associated with the specified key
  * @return the previous value associated with <tt>key</tt>, or
  *         <tt>null</tt> if there was no mapping for <tt>key</tt>.
  *         (A <tt>null</tt> return can also indicate that the map
  *         previously associated <tt>null</tt> with <tt>key</tt>.)
  */
 public V put(K key, V value) {
    //如果为数组为空,则进行初始化操作。
     if (table == EMPTY_TABLE) {
         inflateTable(threshold);
     }
     if (key == null)
     //如果key为空,则将值value方到数组0位置上,如果0位置有值则替换
         return putForNullKey(value);
      //计算key的hash值
     int hash = hash(key);
     //根据hash值和存储数据的数组长度计算位置索引
     int i = indexFor(hash, table.length);
     //查找对应位置是否有值,如果有值则使用新值替换
     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))) {
             V oldValue = e.value;
             e.value = value;
             //HashMap 空实现,可忽略
             e.recordAccess(this);
             return oldValue;
         }
     }
 	//记录修改次数加1
     modCount++;
     //增加key-value映射到数组中
     addEntry(hash, key, value, i);
     return null;
 }

HashMap put方法流程图

Created with Raphaël 2.2.0 开始 存储单元为Entry类型 的数组table非空 输入的key是否为空 Entry数组0位子数据是否为空 将值value存储到数组的0位置 计算key的hash值 根据hash值和table数组长度计算位置索引index 索引位置有Entry数据 hash值和key都相等 新值覆盖旧值返回旧值 增加新的Entry到索引位置 数组大小大约等于扩容阀值并且索引位置非空 扩容到原容量的2倍且小于最大容量 构建一个新的Entry对象放到索引为bucketIndex的位置,并且将该位置原先的对象设置为新对象的next构成链表 结束 将值value更新到数组的0位置 HashMap容量小于1 << 30 容量大于1 取接近定义容量的最小2幂数 赋值容量值capacity 根据负载因子和容量capacity计算扩容阀值threshold 初始化存储key-value数组table容量 初始化HashSeed 如果容量非法则取1 返回定义的最大容量1 << 30 yes no yes no yes no yes no yes no yes no yes no yes no

初始化数组方法private void inflateTable(int toSize)

/**
  * Inflates the table.
  */
 private void inflateTable(int toSize) {
     // Find a power of 2 >= toSize
     //查找大于toSize的最小2的幂数,例如传入的toSize=23,那么capacity为2^6=32
     int capacity = roundUpToPowerOf2(toSize);
     //根据容量和负载因子计算扩展阈值,当容量达到此阀值时,HashMap进行扩容。
     threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
     //初始化EntiEntry<K,V>[] 数组存储大小
     table = new Entry[capacity];
     //初始化HashSeed
     initHashSeedAsNeeded(capacity);
 }

克隆和Map参数构造器中使用的关键方法private void putAllForCreate(Map<? extends K, ? extends V> m)方法

//循环遍历传入的Map集合,复制到新的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());
 }
 /**
  * This method is used instead of put by constructors and
  * pseudoconstructors (clone, readObject).  It does not resize the table,
  * check for comodification, etc.  It calls createEntry rather than
  * addEntry.
  */
 private void putForCreate(K key, V value) {
    //根据键计算hash值,如果为空则hash值为0
     int hash = null == key ? 0 : hash(key);
     //根据hash值和表长度计算在数组中的位置。
     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.
      */
      //1.如果当前存储的数组的位置存在数据,则替换新值
     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;
         }
     }
 	//2.否则插入数据
     createEntry(hash, key, value, i);
 }

HashMap扩容方法void addEntry(int hash, K key, V value, int bucketIndex)

/**
  * Adds a new entry with the specified key, value and hash code to
  * the specified bucket.  It is the responsibility of this
  * method to resize the table if appropriate.
  *
  * Subclass overrides this to alter the behavior of put method.
  */
 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;
         bucketIndex = indexFor(hash, table.length);
     }

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

HashMap解决hash冲突方法void createEntry(int hash, K key, V value, int bucketIndex)

/**
  * Like addEntry except that this version is used when creating entries
  * as part of Map construction or "pseudo-construction" (cloning,
  * deserialization).  This version needn't worry about resizing the table.
  *
  * Subclass overrides this to alter the behavior of HashMap(Map),
  * clone, and readObject.
  */
 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++;
 }

总结

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值