HasMap1.7底层源码解析

1.常用属性介绍

HashMap类属性

// 默认容量为16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
// 最大容量为2^30
static final int MAXIMUM_CAPACITY = 1 << 30;
// 默认加载因子0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 空数组常量
static final Entry<?,?>[] EMPTY_TABLE = {};

HashMap实例属性

// 哈希桶数组
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
// HashMap有效元素长度
transient int size;
// 阈值
int threshold;
// 加载因子
final float loadFactor;
// HashMap结构被修改次数
transient int modCount;

2.构造函数

2.1 无参构造

使用无参构造,默认会调用参数为容量和加载因子的有参构造,并传入默认容量16以及默认加载因子0.75

public HashMap() {
  //调用参数为容量、加载因子的有参构造,传入默认容量和默认加载因子
  this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}

2.2 有参构造

需要传入容量和加载因子

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;
    //HashMap中无意义,LinkedHashMap才实现
    init();
}

3.put()

public V put(K key, V value) {
	// 判断HashMap是否初始化
    if (table == EMPTY_TABLE) {
    	// 详见3.1 inflateTable方法(初始化)
        inflateTable(threshold);
    }
    if (key == null)
    	// 详见3.3 putForNullKey()
        return putForNullKey(value);
    // 详见3.9 hash()
    int hash = hash(key);
    // 根据hash值和hash桶数组长度计算index
    int i = indexFor(hash, table.length);
    // 遍历index=i的哈希桶
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        // 先判断hash值是否相等,在判断key值是否相等
        // 如果相等则覆盖原value,并将原value值返回
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }

    modCount++;
    // 新增元素,详见3.4
    addEntry(hash, key, value, i);
    return null;
}

3.1 inflateTable()

初始化哈希桶数组

// 初始化
private void inflateTable(int toSize) {
	// 找到一个大于等于toSize的2的次方,详见3.2 roundUpToPowerOf2()
	int capacity = roundUpToPowerOf2(toSize);
	// 设置阈值
	threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
	// 重新设置table
	table = new Entry[capacity];
	initHashSeedAsNeeded(capacity);
}

3.2 roundUpToPowerOf2()

对用户初始化的容量进行处理

private static int roundUpToPowerOf2(int number) {
    // 当number大于等于最大容量,取最大容量;反之小于最大容量时,判断是否大于1,
    // 如果大于1则取大于等于number的最小2次方数,小于1则取1
    // highestOneBit()方法的作用是将传入参数只保留最高位为1,其他全置为0
    return number >= MAXIMUM_CAPACITY
            ? MAXIMUM_CAPACITY
            : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
}

3.3 putForNullKey()

key为null时的元素存放

private V putForNullKey(V value) {
	// 将key为null的元素,默认是存入table[0]的哈希桶中
    for (Entry<K,V> e = table[0]; e != null; e = e.next) {
    // 循环遍历table[0],如果找到桶中某元素key为null,则将value覆盖,并返回原来的value值
        if (e.key == null) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
    modCount++;
    // 如果没有找到key为null的元素,则新增一个元素到index=0的哈希桶中
    // 详见3.4 addEntry()
    addEntry(0, null, value, 0);
    return null;
}

3.4 addEntry()

新增元素到对应bucketIndex的哈希桶中

void addEntry(int hash, K key, V value, int bucketIndex) {
	// 判断当前长度是否大于等于阈值
    if ((size >= threshold) && (null != table[bucketIndex])) {
    	// 扩容,详见3.5 resize()
        resize(2 * table.length);
        hash = (null != key) ? hash(key) : 0;
        bucketIndex = indexFor(hash, table.length);
    }
	// 详见3.8 createEntry()
    createEntry(hash, key, value, bucketIndex);
}

3.5 resize()

哈希桶数组扩容

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];
    // 将原数组的元素全部转移到新数组中,详见3.6 transfer()
    transfer(newTable, initHashSeedAsNeeded(newCapacity));
    table = newTable;
    threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}

3.6 transfer()

将原哈希桶数组转移到新哈希桶数组中

void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    // 循环遍历哈希桶数组,判断每个哈希桶中不为null的元素,将其一一转移至新数组中
    for (Entry<K,V> e : table) {
        while(null != e) {
            Entry<K,V> next = e.next;
            // 判断是否需要重新计算hash值
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            // 详见3.7 indexFor()
            int i = indexFor(e.hash, newCapacity);
            e.next = newTable[i];
            newTable[i] = e;
            e = next;
        }
    }
}

3.7 indexFor()

根据hash值和哈希桶数组长度计算对应的index值

static int indexFor(int h, int length) {
    // 等价于 h % length
    return h & (length-1);
}

3.8 createEntry()

创建新entry插入到对应index的哈希桶中

void createEntry(int hash, K key, V value, int bucketIndex) {
    Entry<K,V> e = table[bucketIndex];
    // 使用头插法将新元素插入到table[bucketIndex]哈希桶的头部
    table[bucketIndex] = new Entry<>(hash, key, value, e);
    size++;
}

3.9 hash()

计算key的hash值

final int hash(Object k) {
    int h = hashSeed;
    if (0 != h && k instanceof String) {
        return sun.misc.Hashing.stringHash32((String) k);
    }

    h ^= k.hashCode();

    // 扰动函数,减少hash值碰撞概率
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}

4.get()

根据key获取对应value值

public V get(Object key) {
    if (key == null)
   		// 详见4.1 getForNullKey()
        return getForNullKey();
    // 详见4.2 getEntry()
    Entry<K,V> entry = getEntry(key);

    return null == entry ? null : entry.getValue();
}

4.1 getForNullKey()

获取key为null时的值

private V getForNullKey() {
    if (size == 0) {
        return null;
    }
    // 因为key=null的元素默认就是存储在table[0]哈希桶中,直接循环遍历它
    // 如果有key为null则返回对应value,反之则返回null
    for (Entry<K,V> e = table[0]; e != null; e = e.next) {
        if (e.key == null)
            return e.value;
    }
    return null;
}

4.2 getEntry()

根据key获取对应value值的具体操作

final Entry<K,V> getEntry(Object key) {
    if (size == 0) {
        return null;
    }
	// 计算hash值
    int hash = (key == null) ? 0 : hash(key);
    // 根据hash值,以及哈希桶数组长度找到对应的哈希桶,并遍历该哈希桶
    for (Entry<K,V> e = table[indexFor(hash, table.length)];
         e != null;
         e = e.next) {
        Object k;
         // 判断hash和key是否相等
        if (e.hash == hash &&
            ((k = e.key) == key || (key != null && key.equals(k))))
            return e;
    }
    return null;
}

5.常见面试题

5.1 容量为什么是2的幂?

  • 这就是要说到indexFor()方法,该方法就是使用hash值与上哈希桶数组长度-1(hash & (length - 1)),它在哈希桶数组长度为2的幂的时候是等价于hash值取余哈希桶数组长度(hash % length),但是与操作的运行速度远远高于取余操作,因此容量为2的幂。

5.2 HashMap的扩容方式是什么?

  • 判断原哈希桶数组长度是否为最大容量,如果是最大容量则将阈值修改为int的最大值,反之则创建一个新的哈希桶数组长度为原来的两倍,循环遍历原哈希桶数组,判断是否需要rehash,在根据hash值以及新哈希桶数组长度计算新的index值,并根据index存入新哈希桶数组中对应的哈希桶中。最后根据新哈希桶长度乘以加载因子与最大默认长度+1比较,取较小值赋值给阈值属性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值