HashMap1.7源码

  1. HashMap重要成员变量默认值
public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable
{

    //默认的数组长度 2^4=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;

    //hash种子,初始为0
    transient int hashSeed = 0;
}

  1. HashMap构造方法
//调用空参构造方法,数组长度和负载因子使用默认值
public HashMap() {
	//this(16,0.75f);
    this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}

//空参调用有参构造方法
public HashMap(int initialCapacity, float loadFactor) {
	//校验数组长度,小于0抛出异常
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
    //校验数组长度,最大为2^30
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    //校验负载因子,小于等于0或非数值抛出异常
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
	//负载因子赋值0.75
    this.loadFactor = loadFactor;
    //数组扩容的阈值,暂时为数组长度16
    threshold = initialCapacity;
    //空方法
    init();
}

// init方法
void init() {
}
  1. put方法
// 调用put方法 参数为String类型的key和value
public V put(K key, V value) {
	// 当前数组为空则进行初始化
    if (table == EMPTY_TABLE) {
    	//初始化数组,长度为默认的16
        inflateTable(threshold);
    }
    // key如果是null,使用特殊的方法
    if (key == null)
        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;
        // 判断key的值是否相等
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
        	// key的值相等则写入新的value值,将旧value返回
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
	// 修改次数+1
    modCount++;
    // 下标位置为空或没有能够匹配的key值,创建Entry放入链表
    addEntry(hash, key, value, i);
    // 没有旧的value,返回null
    return null;
}

put方法中调用的方法inflateTable

private void inflateTable(int toSize) {
    // 找到大于等于指定数组长度的2的n次方
    int capacity = roundUpToPowerOf2(toSize);
	// 扩容阈值,数组长度*负载因子
    threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
    // 创建数组
    table = new Entry[capacity];
    // 是否重新赋值hashSeed 
    initHashSeedAsNeeded(capacity);
}

//roundUpToPowerOf2方法
private static int roundUpToPowerOf2(int number) {
    // 使用Integer的highestOneBit方法找到大于等于指定数组长度的2的n次方
    return number >= MAXIMUM_CAPACITY
            ? MAXIMUM_CAPACITY
            : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
}

//initHashSeedAsNeeded方法
final boolean initHashSeedAsNeeded(int capacity) {
	// hashSeed 初始值为0,false
    boolean currentAltHashing = hashSeed != 0;
    // 数组长度是否 >= 2^31-1 
    boolean useAltHashing = sun.misc.VM.isBooted() &&
            (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
    // 使用异或,currentAltHashing 为false,只有数组长度>= 2^31-1时返回true
    boolean switching = currentAltHashing ^ useAltHashing;
    //switching为true,则hashSeed重新赋值(一般不会出现)
    if (switching) {
        hashSeed = useAltHashing
            ? sun.misc.Hashing.randomHashSeed(this)
            : 0;
    }
    return switching;
}

// Integer.highestOneBit方法

put调用的方法putForNullKey

private V putForNullKey(V value) {
	// 当key为null时,数组下标指定为0
    for (Entry<K,V> e = table[0]; e != null; e = e.next) {
    	// 判断现在有没有key为null的Entry
        if (e.key == null) {
        	//value替换为新值,返回旧的value
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
    //修改次数+1
    modCount++;
    // table[0]中没有值或没有匹配到null的key,创建一个Entry放入table[0]
    addEntry(0, null, value, 0);
    return null;
}


put 和 putForNullKey 都调用的addEntry

//addEntry方法
void addEntry(int hash, K key, V value, int bucketIndex) {
	// 如果当前数组长度>=扩容阈值 并且 当前数组下标位置不为null
    if ((size >= threshold) && (null != table[bucketIndex])) {
    	// 数组扩容为当前长度*2
        resize(2 * table.length);
        // key是否为null,不为null计算hash值,null则直接hash值为0
        hash = (null != key) ? hash(key) : 0;
        // 根据hash值和新数组长度计算下标位置
        bucketIndex = indexFor(hash, table.length);
    }
	//创建Entry放入table中
    createEntry(hash, key, value, bucketIndex);
}

// createEntry方法
void createEntry(int hash, K key, V value, int bucketIndex) {
	// 获取当前数组下标位置的链表头
    Entry<K,V> e = table[bucketIndex];
    // 在链表头位置创建新的Entry,next指向原链表头(头插法)
    table[bucketIndex] = new Entry<>(hash, key, value, e);
    // 数组长度+1
    size++;
}

put 和 addEntry 都调用的indexFor

static int indexFor(int h, int length) {
    // 计算数组下标位置,数组长度必须为2的n次方
    return h & (length-1);
}

addEntry中调用的resize

void resize(int newCapacity) {
	// 原数组
    Entry[] oldTable = table;
    // 原数组长度
    int oldCapacity = oldTable.length;
    // 如果原数组长度为2^30,不进行扩容,把扩容阈值修改为2^31-1
    if (oldCapacity == MAXIMUM_CAPACITY) {
        threshold = Integer.MAX_VALUE;
        return;
    }
	// 根据新的数组长度,创建数组
    Entry[] newTable = new Entry[newCapacity];
    // 转移数组数据 
    // 调用initHashSeedAsNeeded方法,根据新数组的长度判断是否会hashSeed重新赋值
    transfer(newTable, initHashSeedAsNeeded(newCapacity));
    // table指向新数组
    table = newTable;
    // 计算新的扩容阈值
    threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}

// transfer方法
void transfer(Entry[] newTable, boolean rehash) {
	// 新数组长度
    int newCapacity = newTable.length;
    // 遍历原数组的Entry
    for (Entry<K,V> e : table) {
    	// Entry不为null
        while(null != e) {
        	// 先找到链表中下一个要转移的Entry
            Entry<K,V> next = e.next;
            // 如果hashSeed变了,重新进行hash计算
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            // 重新计算数组下标,结果分两种:1.与原下标一直 2.原下标+本次扩容了多长
            int i = indexFor(e.hash, newCapacity);
            /*原数组统一链表中的数据转移到新数组中时,所处链表顺序颠倒,因此多线程的情况下可能会出现环形链表问题*/
            // Entry的next指向新数组的链表头
            e.next = newTable[i];
            // Entry放入新数组的链表中
            newTable[i] = e;
            // 下一次进行操作的就是原数组链表的下一个
            e = next;
        }
    }
}
  1. get方法
public V get(Object key) {
	// 判断key是否为null
    if (key == null)
    	// 通过特殊方法获取key的value
        return getForNullKey();
    // key不为null,获取Entry
    Entry<K,V> entry = getEntry(key);
	//Entry是否匹配到,有的话返回对应的value,否则返回null
    return null == entry ? null : entry.getValue();
}

//getForNullKey方法
private V getForNullKey() {
	// 数组长度为0直接返回null
    if (size == 0) {
        return null;
    }
    // 遍历table[0]位置的链表
    for (Entry<K,V> e = table[0]; e != null; e = e.next) {
    	// 是否存在key为null
        if (e.key == null)
        	// 返回对应的value
            return e.value;
    }
    // 没有匹配到,返回null
    return null;
}

// getEntry方法
final Entry<K,V> getEntry(Object key) {
	// 数组长度为0直接返回null
    if (size == 0) {
        return null;
    }
	// key是否为null,不为null计算hash值,null则直接hash值为0
    int hash = (key == null) ? 0 : hash(key);
    // 计算数组下标,遍历下标位置的链表
    for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) {
        Object k;
        // 判断key是否相等
        if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
        	// 返回链表中的Entry
            return e;
    }
    // 没有匹配到,返回null
    return null;
}

问题

  1. 为什么数组长度为2的n次方?
    计算数组下标时,当数组长度为2的n次方时,hash/length等同于hash&(length-1)
static int indexFor(int h, int length) {
    // 计算数组下标位置,数组长度必须为2的n次方
    return h & (length-1);
}

假设数组长度为16,hash值为 0110 0010 1001(瞎写的)
16 转换为2进制 0000 0001 0000
15 转换为2进制 0000 0000 1111
使用hash值和15进行与运算
0110 0010 1001
0000 0000 1111
&
结果 0000 0000 1001

由上面的计算可以看出:只有在数组长度为2的n次方时,数组长度-1转换为2进制时才可以转换为低位全部为1的二进制,和hash值进行与运算时,就可以确保最终下标位置的结果为0-15了

  1. transfer方法进行数据转移到新数组时,为什么hashSeed变了就要重新进行hash计算呢?
    下面是计算hash值的方法,在扰动函数中用到了hashSeed的值
    (待补充)
final int hash(Object k) {
    int h = hashSeed;
    if (0 != h && k instanceof String) {
        return sun.misc.Hashing.stringHash32((String) k);
    }
	
    h ^= k.hashCode();
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值