博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
HashMap源码理解
阅读量:6706 次
发布时间:2019-06-25

本文共 6801 字,大约阅读时间需要 22 分钟。

  hot3.png

private static int roundUpToPowerOf2(int number) {    return number >= MAXIMUM_CAPACITY            ? MAXIMUM_CAPACITY            : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;}
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);}
  • Integer.highestOneBit方法是获取不大于本身的2^n的值,那该方法具体含义是:

  • 获取新的数组容量值,如果给定值大于等于最大的容量,则返回最大容量,否则:如果容量小于等于1,则返回1,否则返回大于等于给定值的最接近的2^n的值

  • 容量为什么要是2^n次方呢?且看如下代码:

     

  •  

    • 给方法是查找h(ash)在数组的索引位置;那现在看length为什么要是2^n次方呢?

    • 保证&之后的数据不大于length

    • length-1之后,最低的n位都是1,那与h的&运算之后的值即是h的最低n位

    • 采用length-1而不是直接length是因为2^n最低一位是0,那&运算之后数据都分布在偶数位,不是随机的

private void inflateTable(int toSize) {    // Find a power of 2 >= toSize    int capacity = roundUpToPowerOf2(toSize);    threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);    table = new Entry[capacity];    initHashSeedAsNeeded(capacity);}

扩容:先获取最接近并且>=指定容量的的数值作为扩容后的容量;扩容界限就是取容量*加载因子和最大容量+1的最小值;重新初始化table;扩容之后的table没有任何数据,所以在相关调用操作之后会有数据重新分配操作,比较耗时,所以在初始化一个hashmap的时候最好指定容量避免扩容操作发生

public V get(Object key) {    if (key == null)        return getForNullKey();    Entry
 entry = getEntry(key);    return null == entry ? null : entry.getValue();}

get方法:

  •     如果key==null,则单独获取

private V getForNullKey() {    if (size == 0) {        return null;    }    for (Entry
 e = table[0]; e != null; e = e.next) {        if (e.key == null)            return e.value;    }    return null;}

 

  • 如果size==0,则直接返回null;

  • 遍历table,判断key,返回value

final Entry
 getEntry(Object key) {    if (size == 0) {        return null;    }    int hash = (key == null) ? 0 : hash(key);    for (Entry
 e = table[indexFor(hash, table.length)];         e != null;         e = e.next) {        Object k;        if (e.hash == hash &&            ((k = e.key) == key || (key != null && key.equals(k))))            return e;    }    return null;}

 

根据key获取Entry:

  •     获取hash值

  • 获取数组索引

  • 获取数组中索引的第一个entry

  • 遍历entry,如果hash值相等并且(key相等或者equal),则返回当前entry即可

public boolean containsKey(Object key) {    return getEntry(key) != null;}

判断key是否存在只是调用getEntry方法判断是否为null即可

public V put(K key, V value) {    if (table == EMPTY_TABLE) {        inflateTable(threshold);    }    if (key == null)        return putForNullKey(value);    int hash = hash(key);    int i = indexFor(hash, table.length);    for (Entry
 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;            e.recordAccess(this);            return oldValue;        }    }    modCount++;    addEntry(hash, key, value, i);    return null;}

put方法:

  •     如果table为空数组,则先扩容到扩容界限(threshold)的数组,如果是默认的初始化方法,则threshold=容量;执行该方法之后。threshold=容量*加载因子;

  • 如果key==null,单独存入该值,putForNullKey

  • 根据hash和length获取table索引,找出第一个entry

  • 遍历entry,如果找到则重新设置新值,返回旧值,否则新添加一个entry(addEntry)

private V putForNullKey(V value) {    for (Entry
 e = table[0]; e != null; e = e.next) {        if (e.key == null) {            V oldValue = e.value;            e.value = value;            e.recordAccess(this);            return oldValue;        }    }    modCount++;    addEntry(0, null, value, 0);    return null;}
  • 存入key==null的value,直接查找table的第一个位置(index=0),如果找到则重新设置新值,返回旧值,否则添加新的entry;

  • key==null的值全部在table【0】的entry上

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);}

 

创建新的entry:

  • 先检查是否满足扩容条件:size>=threshold&&null!=table[bucketIndex];

  • 如果满足扩容,计算新的hash和数组索引(bucketIndex)

  • 创建新的entry(createEntry)

  • 注意事项:size是针对hash表里的所有数据的容量,而扩容是指数组扩容

     

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];    transfer(newTable, initHashSeedAsNeeded(newCapacity));    table = newTable;    threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);}
  • 先获取oldTable的length,如果已是最大长度,则无需扩容,并且将threshold设为Integer.MAX_VALUE,不可扩容!

  • 创建新的Entry数组,将旧的table数据添加到newTable中(transfer)

  • 设置新的table和threshold

void transfer(Entry[] newTable, boolean rehash) {    int newCapacity = newTable.length;    for (Entry
 e : table) {        while(null != e) {            Entry
 next = e.next;            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;        }    }}

简单理解为:数据拷贝

  • 获取新的数组长度,遍历table数组;

  • 遍历每个链表:重定向到新的数组和链表中

  • 现在遍历到[1,0]即table[1]下的第一个entry,计算得出在新的i=5,则该entry的下一个是newTable[5],newTable[5]=entry

  • 从以上可以看出,新进入newTable的数据在后进入的next下

void createEntry(int hash, K key, V value, int bucketIndex) {    Entry
 e = table[bucketIndex];    table[bucketIndex] = new Entry<>(hash, key, value, e);    size++;}

创建新的Entry:

  • 先根据索引(bucketIndex)获取数组中的元素(e)

  • 创建新的entry,位于链表第一个entry,而e是当前新的entry的next

public void putAll(Map
 m) {    int numKeysToBeAdded = m.size();    if (numKeysToBeAdded == 0)        return;    if (table == EMPTY_TABLE) {        inflateTable((int) Math.max(numKeysToBeAdded * loadFactor, threshold));    }    /*     * Expand the map if the map if the number of mappings to be added     * is greater than or equal to threshold.  This is conservative; the     * obvious condition is (m.size() + size) >= threshold, but this     * condition could result in a map with twice the appropriate capacity,     * if the keys to be added overlap with the keys already in this map.     * By using the conservative calculation, we subject ourself     * to at most one extra resize.     */    if (numKeysToBeAdded > threshold) {        int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1);        if (targetCapacity > MAXIMUM_CAPACITY)            targetCapacity = MAXIMUM_CAPACITY;        int newCapacity = table.length;        while (newCapacity < targetCapacity)            newCapacity <<= 1;        if (newCapacity > table.length)            resize(newCapacity);    }    for (Map.Entry
 e : m.entrySet())        put(e.getKey(), e.getValue());}

将一个集合添加到现有集合中:

  • 先获取要添加结合的元素数量

  • 如果现有集合是空集合,扩容:当前threshold和根据添加元素容量计算的新容量的最大值

  • 假如添加的集合元素数量>threshold,则判断当前table是否需要扩容

  • 此处是保守估计新的集合添加之后的容量:但是可以保证最多只有一次调用resize方法!

  • 如果numKeysToBeAdded <=threshold,即使在put方法导致扩容也至多有一次:扩容至两倍,那threshold也会变为两倍

  • 如果numKeysToBeAdded >threshold,如果:targetCapacity>table.length,则在put方法可能会导致resize,否则newCapacity必定大于table.length,在此处resize,put方法就不会resize了

转载于:https://my.oschina.net/monroe/blog/859722

你可能感兴趣的文章
再跨界!华为联袂设计先锋RICOSTRU,诠释科技新美学
查看>>
51Talk CFO赖佑明2019年1月1日退休 联席CFO徐珉接任
查看>>
微软牵手Apache Kafka,第一个将其引入云端生产环境
查看>>
北京地铁新机场线列车亮相调试 设计时速160公里/小时
查看>>
react-router browserHistory刷新页面404问题解决
查看>>
你知道 koa 中间件执行原理吗?
查看>>
Redis 中的事件循环
查看>>
css布局基础总结
查看>>
Koa源码解析
查看>>
webpack系列之一总览
查看>>
乌龙事件之chrome页面部分白屏
查看>>
FP 视角下的领域驱动设计
查看>>
玩转iOS开发:iOS中的Socket编程(二)
查看>>
如何打造BCH使用的刚性需求?
查看>>
一个小需求引发的思考
查看>>
JSX,了解一下?
查看>>
升级Swift4 0遇到的坑
查看>>
第一本Python神经网络编程译著图书终于来啦
查看>>
四两拨千斤式的攻击!如何应对Memcache服务器漏洞所带来的DDoS攻击?
查看>>
2017 Material design 第四章第二节《单位和尺寸》
查看>>