HashMap(1.7)源码学习

一. 1.7 和1.8区别

  • 数据结构:
    • 1.7: 数组 + 链表
    • 1.8 : 数组 + 链表 + 红黑树
  • put:
    • 1.7: 头插法
    • 1.8: 尾插法
  • hash计算:
    • 1.7 : Objects.hashCode(getKey()) ^ Objects.hashCode(getValue())
    • 1.8: Objects.hashCode(getKey()) ^ Objects.hashCode(getValue()) -- > (h = key.hashCode()) ^ (h >>> 16)
  • 扩容:
    • 1.7: 再次哈希
    • 1.8: 分为 j 和 j + oldvalue
  • 初始化
    • 1.7: 默认有一个空的数组,table指向这个数组,当put时会判断是否为EMPTY_TABLE,然后进行初始化
    • 1.8: put时resize,然后对oldTab.length进行判断

二.源码部分

1.基本属性

AbstractMap<K, V> :AbstractMap 提供了 Map 的基本实现,使得我们以后要实现一个 Map 不用从头开始,只需要继承 AbstractMap, 然后按需求实现/重写对应方法即可。

Map是Java集合框架的根接口,另一个是Collection接口

Cloneable接口是一个标记接口,也就是没有任何内容

Serializable 接口之所以定义为空,是因为它只起到了一个标识的作用,告诉程序实现了它的对象是可以被序列化的,但真正序列化和反序列化的操作并不需要它来完成。

public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable
{

    /**
     * The default initial capacity - MUST be a power of two.
     * 默认的初始容量-必须是2的幂。
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    /**
     * 最大容量:1,073,741,824
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * The load factor used when none specified in constructor.
     * 默认负载因子0.75
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * An empty table instance to share when the table is not inflated.
     * 创建对象的时候默认table指向EMPTY_TABLE
     */
    static final Entry<?,?>[] EMPTY_TABLE = {};

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

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

//阈值
    int threshold;

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


    transient int modCount;

    static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;

2.构造函数

和1.8相同有4种情况

  • 空参: 默认容量16,加载因子0.75
  • 指定容量
  • 指定容量、负载因子
  • 已有集合传入

3.put

public V put(K key, V value) {
    //如果表没初始化,则去初始化
    if (table == EMPTY_TABLE) {
        inflateTable(threshold);
    }
    //key为空,抛出异常
    if (key == null)
        return putForNullKey(value);
    //计算hash值
    int hash = hash(key);
    //找到数组下标h & (length-1)
    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;
            //这个方法是留给LinkedHashMap实现的
            e.recordAccess(this);
            return oldValue;
        }
    }
    //找不到添加新的结点
    modCount++;
    addEntry(hash, key, value, i);
    return null;
}

4.addEntry

//put方法传入hash.key.vualue.和数组下标
void addEntry(int hash, K key, V value, int bucketIndex) {
    //添加前还是判断是否需要扩容
    if ((size >= threshold) && (null != table[bucketIndex])) {
        //2 * table.length
        resize(2 * table.length);
        //重新计算数组下标
        hash = (null != key) ? hash(key) : 0;
        bucketIndex = indexFor(hash, table.length);
    }
    //创建结点
    createEntry(hash, key, value, bucketIndex);
}
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++;
}

5.inflateTable()

//初始化表
private void inflateTable(int toSize) {
    // Find a power of 2 >= toSize
    //找到>=size的最小2的幂
    int capacity = roundUpToPowerOf2(toSize);
    //越界判断
    threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
    //为table新建个数组
    table = new Entry[capacity];
    initHashSeedAsNeeded(capacity);
}

6.扩容

  • 和1.8不一样的一小点是,因为1.7的参数直接为新容量的大小
  • 因为1.8没有默认的空表,通过capacity和threshold来区别各种情况,而1.7不需要进行初始化的操作逻辑就简单的多。
void resize(int newCapacity) {
    Entry[] oldTable = table;
    int oldCapacity = oldTable.length;
    //如果原来的容量已经为最大值,则将阈值也调整为最大,return
    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);
}
/**
 * Transfers all entries from current table to newTable.
 * 将所有表项从当前表转移到newTable。
 */
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;
            //如果是true就重新计算key的hash值(?防止哈希冲突)
            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;
        }
    }
}
posted @ 2022-02-19 13:55  ftfty  阅读(55)  评论(0编辑  收藏  举报