互联网架构-Java8集合框架源码分析-042:深入Jdk7版本HashMap扩容源码分析

1 HashMap7深度源码分析课程介绍

课程主要内容:
1、HashMap空的Key底层是如何存放
2、HashMap每次扩容原理是什么?
3、HashMap如何实现减少index下标冲突问题
4、HashMap的加载因子为什么要是0.75而不是0.8

2 HashMap7Get方法深度源码分析

@Override
public V get(K key) {
    // 计算hash值
    int hash = hash(key);
    // 根据hash值计算数组下标存放的位置
    int index = indexFor(hash, tables.length);
    // 遍历链表
    for (Entry e = tables[index]; e != null; e = e.next) {
        Object k;
        // hashCode相同且值相同情况
        if (e.hash == hash && ((k = e.getKey()) == key || e.getKey().equals(key))) {
            return (V) e.getValue();
        }
    }
    return null;
}
public class Test001 {
    public static void main(String[] args) {
        MayiktHashMap<Object, String> objectStringMayiktHashMap = new MayiktHashMap<>();
        String a = "a";
        Integer b = new Integer(97);
        objectStringMayiktHashMap.put(a, "蚂蚁课堂A");
        objectStringMayiktHashMap.put(b, "蚂蚁课堂B");
        System.out.println(objectStringMayiktHashMap.get(a));//蚂蚁课堂A
        System.out.println(objectStringMayiktHashMap.get(b));//蚂蚁课堂B
    }
}

3 HashMap7添加Key为空源码分析

/**
 * key为null时,存放数组0对应链表首位置 put方法判断key为null调用
 *
 * @param value
 * @return
 */
private V putForNullKey(V value) {
    for (Entry<K, V> e = tables[0]; e != null; e = e.next) {
        if (e.getKey() == null) {
            V oldValue = e.getValue();
            e.setValue(value);
            return oldValue;
        }
    }
    // 否则情况下,存放到数组0对应链表首位置
    addEntry(0, null, value, 0);
    return null;
}
……
// get方法判断key为null调用
public V getForNullKey() {
    // 循环遍历数组0对应的链表(第一个链表)
    for (Entry e = tables[0]; e != null; e = e.next) {
        if (e.getKey() == null) {
            return (V) e.getValue();
        }
    }
    return null;
}
public class Test002 {
    public static void main(String[] args) {
        MayiktHashMap<Object, String> objectStringMayiktHashMap = new MayiktHashMap<>();
        objectStringMayiktHashMap.put(null, "蚂蚁课堂666");
        System.out.println(objectStringMayiktHashMap.get(null));//蚂蚁课堂666
    }
}

4 HashMap7巧妙运用位于运算

103&15=7, 103&16=0
104&15=8, 104&16=0
105%15=9, 105&16=0
如果长度不减1,可能发生index冲突,导致整个链表过长,查询效率降低
(注意:hashMap频繁发生hash冲突或者index冲突都会导致查询效率降低)

5 HashMap7位与运算二进制原理

h & (length - 1)巧妙运用
& - 参加运算的两个数据,按二进制位进行“与”运算。
运算规则:0&0=0, 0&1=0,1&0=0,1&1=1。(同1得1,否则为0)

h & (length-1); h为hash值,length-1
103&(16-1)即103&15
0110 0111
0000 1111
0000 0111
得到二进制为0000 0111,转换为十进制7

103&16
0110 0111
0001 0000
0000 0000
得到二进制为0000 0000,转换为十进制0

length-1 就是为了防止下标冲突存放到同一个链表中,查询效率会降低。

h & (length-1) 本质是取模运算。
位运算(&)效率要比取模运算(%)高很多,主要原因是位运算直接对内存数据进行操作,不需要转成十进制,因此处理速度非常快。
X % 2^n = X & (2^n–1)
2^n 表示2的n次方,也就是说,数X对2^n 取模==数X和(2^n–1)做按位与运算。

6 HashMap7底层扩容原理

void addEntry(int hash, K key, V value, int bucketIndex) {
    // 当size>=阈值 且 存在index冲突的情况下才开始扩容
    if ((size >= threshold) && (null != tables[bucketIndex])) {
        // 假设当前容量为16,阈值16*0.75=12,已经有12个元素均摊到下标1-13位置 新元素下标index=0,此时位置足够没必要扩容
        // 目的是保证每个数组都有存放元素,避免浪费资源
        // 新数组长度为原table长度*2
        resize(2 * tables.length);
        hash = (null != key) ? hash(key) : 0;
        // 计算扩容后index
        bucketIndex = indexFor(hash, tables.length);
    }
    createEntry(hash, key, value, bucketIndex);
}

void resize(int newCapacity) {
    // 获取原来的tables
    Entry[] oldTables = tables;
    // 获取原来tables长度
    int oldCapacity = oldTables.length;
    // 如果原数组长度为限制最大容量,阈值为Integer最大值
    if (oldCapacity == MAXIMUM_CAPACITY) {
        threshold = Integer.MAX_VALUE;
        return;
    }
    // 创建新容量数组
    Entry[] newTables = new Entry[newCapacity];
    // 假设原数组长度16,新数组长度32,原index=hash&15,新index=hash&32,下标不对应
    // 每次扩容的时候需要重新计算index值赋值到新tables中
    // 重新计算index值
    transfer(newTables);
    tables = newTables;
    // 新的扩容阈值为新容量*0.75 eg:32*0.75=24
    threshold = (int) Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}

/**
 * 重新计算index下标
 *
 * @param newTables
 */
private void transfer(Entry[] newTables) {
    // 获取新数组长度
    int newCapacity = newTables.length;
    // 遍历数组首链表节点
    for (Entry<K, V> e : tables) {
        // 遍历链表
        while (null != e) {
            // 获取下一个节点
            Entry<K, V> next = e.next;
            // 计算节点新位置
            int index = indexFor(e.hash, newCapacity);
            // 将e插入newTables[index]链表首节点
            e.next = newTables[index];
            newTables[index] = e;
            // 继续下一个节点处理
            e = next;
        }
    }
}

7 HashMap7断点调试扩容方法

public class Test003 {
    public static void main(String[] args) {
        MayiktHashMap<Object, Object> objectObjectHashMap = new MayiktHashMap<>();
        for (int i = 1; i <= 12; i++) {
            objectObjectHashMap.put("mayikt_" + i, "mayikt_" + i);
        }
        objectObjectHashMap.put("A", "每特");
    }
}

断点调试:
在这里插入图片描述

8 HashMap7为什么加载因子为0.75

为什么加载因子是0.75 而不是0.8 /1 呢?
加载因子越大,index下标冲突概率越大,反而空间利用率更高;
加载因子越小,index下标冲突概率越小,反而空间利用率不是非常高,频繁扩容;
index下标冲突概率越大,链表长度更长,查询的成本非常高。因此,必须在 "冲突的机会"与"空间利用率"之间寻找一种平衡与折衷。

附录 手写简单版本HashMap1.7实现基本功能源码

public class MayiktHashMap<K, V> implements MayiktMap<K, V> {

    // 默认初始容量 16
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

    // 加载因子 0.75f 作用:数组扩容
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    // 最大初始容量 10亿+
    static final int MAXIMUM_CAPACITY = 1 << 30;

    // 实际加载因子
    final float loadFactor;

    /**
     * 阈值 需要扩容的时候实际hashMap存放的大小
     * 容量*加载因子 当达到阈值情况下开始扩容
     */
    int threshold;

    //hashMap底层数组 初始为空
    transient Entry<K, V>[] tables = null;

    // 集合大小
    transient int size;

    public MayiktHashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }

    public MayiktHashMap(int initialCapacity, float loadFactor) {
        // 初始容量校验
        if (initialCapacity < 0) {
            throw new IllegalArgumentException("初始容量不符" + initialCapacity);
        }
        if (initialCapacity > MAXIMUM_CAPACITY) {
            initialCapacity = MAXIMUM_CAPACITY;
        }
        // 校验加载因子
        if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
            throw new IllegalArgumentException("加载因子不符" + loadFactor);
        }
        // 设置加载因子和实际hashMap存放的阈值
        this.loadFactor = loadFactor;
        threshold = initialCapacity;
        init();
    }

    /**
     * 定义空方法便于子类扩展功能
     */
    protected void init() {
    }

    /**
     * 注意:hash冲突或者index冲突,都会存放到同一个链表中
     *
     * @param key
     * @param value
     * @return
     */
    @Override
    public V put(K key, V value) {
        //数组为空,初始化数组
        if (tables == null) {
            inflateTable(threshold);
        }
        if (key == null) {
            return putForNullKey(value);
        }
        // 计算hash值
        int hash = hash(key);
        // 根据hash值计算数组下标存放的位置
        int index = indexFor(hash, tables.length);
        // 判断HashCode相同情况,循环遍历链表
        for (Entry e = tables[index]; e != null; e = e.next) {
            Object k;
            // hashCode相同且值相同情况
            if (e.hash == hash && ((k = e.getKey()) == key || e.getKey().equals(key))) {
                //获取原值value
                Object oldValue = e.getValue();
                //设置新value
                e.setValue(value);
                // 返回原value
                return (V) oldValue;
            }
        }
        // 值不同情况,添加元素
        addEntry(hash, key, value, index);
        return null;
    }

    @Override
    public V get(K key) {
        if (key == null) {
            return getForNullKey();
        }
        // 计算hash值
        int hash = hash(key);
        // 根据hash值计算数组下标存放的位置
        int index = indexFor(hash, tables.length);
        // 遍历链表
        for (Entry e = tables[index]; e != null; e = e.next) {
            Object k;
            // hashCode相同且值相同情况
            if (e.hash == hash && ((k = e.getKey()) == key || e.getKey().equals(key))) {
                return (V) e.getValue();
            }
        }
        return null;
    }

    public V getForNullKey() {
        // 循环遍历数组0对应的链表(第一个链表)
        for (Entry e = tables[0]; e != null; e = e.next) {
            if (e.getKey() == null) {
                return (V) e.getValue();
            }
        }
        return null;
    }

    /**
     * key为null时,存放数组0对应链表首位置
     *
     * @param value
     * @return
     */
    private V putForNullKey(V value) {
        for (Entry<K, V> e = tables[0]; e != null; e = e.next) {
            if (e.getKey() == null) {
                V oldValue = e.getValue();
                e.setValue(value);
                return oldValue;
            }
        }
        // 否则情况下,存放到数组0对应链表首位置
        addEntry(0, null, value, 0);
        return null;
    }

    void addEntry(int hash, K key, V value, int bucketIndex) {
        // 当size>=阈值 且 存在index冲突的情况下才开始扩容
        if ((size >= threshold) && (null != tables[bucketIndex])) {
            // 假设当前容量为16,阈值16*0.75=12,已经有12个元素均摊到下标1-13位置 新元素下标index=0,此时位置足够没必要扩容
            // 目的是保证每个数组都有存放元素,避免浪费资源
            // 新数组长度为原table长度*2
            resize(2 * tables.length);
            hash = (null != key) ? hash(key) : 0;
            // 计算扩容后index
            bucketIndex = indexFor(hash, tables.length);
        }
        createEntry(hash, key, value, bucketIndex);
    }

    void resize(int newCapacity) {
        // 获取原来的tables
        Entry[] oldTables = tables;
        // 获取原来tables长度
        int oldCapacity = oldTables.length;
        // 如果原数组长度为限制最大容量,阈值为Integer最大值
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
        // 创建新容量数组
        Entry[] newTables = new Entry[newCapacity];
        // 假设原数组长度16,新数组长度32,原index=hash&15,新index=hash&32,下标不对应
        // 每次扩容的时候需要重新计算index值赋值到新tables中
        // 重新计算index值
        transfer(newTables);
        tables = newTables;
        // 新的扩容阈值为新容量*0.75 eg:32*0.75=24
        threshold = (int) Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }

    /**
     * 重新计算index下标
     *
     * @param newTables
     */
    private void transfer(Entry[] newTables) {
        // 获取新数组长度
        int newCapacity = newTables.length;
        // 遍历数组首链表节点
        for (Entry<K, V> e : tables) {
            // 遍历链表
            while (null != e) {
                // 获取下一个节点
                Entry<K, V> next = e.next;
                // 计算节点新位置
                int index = indexFor(e.hash, newCapacity);
                // 将e插入newTables[index]链表首节点
                e.next = newTables[index];
                newTables[index] = e;
                // 继续下一个节点处理
                e = next;
            }
        }
    }

    private void createEntry(int hash, K key, V value, int bucketIndex) {
        // 获取原来Entry对象,如果获取为空,没有发生hash冲突
        Entry<K, V> next = tables[bucketIndex];
        // 新建一个Entry对象,放在tables对应数组位链表首个位置,如果原来Entry有值,新Entry指向原Entry
        tables[bucketIndex] = new Entry(hash, key, value, next);
    }

    /**
     * 计算hash值
     *
     * @param k
     * @return
     */
    final int hash(Object k) {
        int h = 0;
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }

        h ^= k.hashCode();

        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

    // 根据hash值计算index下标位置
    static int indexFor(int h, int length) {
        // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
        return h & (length - 1);
    }


    /**
     * 默认数组初始化
     *
     * @param toSize
     */
    private void inflateTable(int toSize) {
        int capacity = roundUpToPowerOf2(toSize);
        // 计算初始化容量
        threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
        // 数组中的容量初始化
        tables = new MayiktHashMap.Entry[capacity];
    }

    private static int roundUpToPowerOf2(int number) {
        // assert number >= 0 : "number must be non-negative";
        return number >= MAXIMUM_CAPACITY ? MAXIMUM_CAPACITY : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
    }

    @Override
    public int size() {
        return size;
    }


    class Entry<K, V> implements MayiktMap.Entry<K, V> {
        // key
        private K k;
        // value
        private V v;
        // next 指向下一个Entry
        private Entry<K, V> next;

        // 存放Entry对象的hash值
        private int hash;

        public Entry(int hash, K key, V value, Entry<K, V> next) {
            this.hash = hash;
            this.k = key;
            this.v = value;
            this.next = next;
            size++;
        }

        @Override
        public K getKey() {
            return this.k;
        }

        @Override
        public V getValue() {
            return this.v;
        }

        @Override
        public V setValue(V value) {
            this.v = value;
            return this.v;
        }

        public Entry(K k, V v) {
            this.k = k;
            this.v = v;
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值