Java8源码阅读之HashMap

一、HashMap的构造方法

HashMap默认大小16,加载因子是0.75。但new HashMap()的时候,默认是没有初始化容量的,它的table是一个长度为0的数组 (JDK8是null数组)。

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;//JDK7的写法。
    this.threshold = tableSizeFor(initialCapacity);//JDK8的写法
    init();//jdk7此方法里面是空的。jdk8中就没有此行。
}

二、HashMap增加元素

当我们第一次put数据的时候,才会初始化table

以下是JDK7put源码。在构造方法执行完成后,table为长度为0的数组。

transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

public Vput(Kkey,V value) {

if (table== EMPTY_TABLE) {

inflateTable(threshold);

}

Java8中的

transient Node<K,V>[]table;//默认为null这里的类型是Node,是Entry的子类

public Vput(Kkey,V value) {

return putVal(hash(key),key,value, false, true);

}

final V putVal(inthash,K key,V value, booleanonlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab;Node<K,V> p; int n,i;
    if ((tab =table) ==null || (n = tab.length) ==0)
        n = (tab = resize()).length; //这里是第一次初始化空间
    if ((p = tab[i = (n -1) & hash]) ==null)
        tab[i] = newNode(hash,key,value, null);//找到没占用的坑位
    else {
        Node<K,V> e;K k;
        if (p.hash== hash &&
            ((k = p.key) == key || (key !=null && key.equals(k))))
            e = p;
        else if (pinstanceof TreeNode)//解决JDK7中变成链表的问题
            e = ((TreeNode<K,V>)p).putTreeVal(this,tab,hash,key,value);
        else {
            for(intbinCount = 0; ;++binCount) {//遍历坑位深度
                if((e = p.next) ==null) {
                    p.next= newNode(hash,key,value, null);
                    if (binCount >=TREEIFY_THRESHOLD -1)// -1 for 1st
                        treeifyBin(tab,hash);
                    break;
                }
                if(e.hash== hash &&
                    ((k = e.key) == key || (key !=null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if(e != null) {// existing mapping for key
            VoldValue = e.value;
            if (!onlyIfAbsent || oldValue ==null)
                e.value= value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;//修改数据结构次数,用于快速迭代失败
    if (++size> threshold)
        resize();//插入之后,判断元素大于一定值时,就需要重新分配tab的大小,调整数据的位置结构。为什么不先调整再插入元素呢?因为不知道是插入数据还是更新数据。这个threshold是怎么来的呢。
    afterNodeInsertion(evict);
    return null;
}

 

三、调整数据位置threshold的前世今生

若使用new HashMap()无参构造函数,则threshold0,否则使用new HashMap(int capacity, float loadFactor),则threshold被设置为新值

this.threshold= tableSizeFor(initialCapacity);查看此方法

/**

* Returns a power of two size for the given target capacity.

*/

static final int tableSizeFor(intcap) {

int n = cap -1;

n |= n >>> 1;

n |= n >>> 2;

n |= n >>> 4;

n |= n >>> 8;

n |= n >>> 16;

return (n <0) ?1 : (n >=MAXIMUM_CAPACITY) ?MAXIMUM_CAPACITY : n +1;

}

这方法比较有意思,也是使用二进制的思路来处理。前面我们在定坑位的时候也使用位操作(这个比较简单理解)。而这个稍复杂一点。其实将最高位1一直向后复制,最后再加1。最后的结果就是和n最接近的2N次幂。

再回到上面if(size>threshold),则重新改变结构。即改变结构的限制不是你在构造函数时传入的容量值capacity

若使用HashMap()无参构造方法,那这里是不是有问题呀?当然不会了。因为在put的时候判断若tabnull的或空的,将会resize()。这时resize()方法里肯定有改变。

在看resize()方法前,我们知道size>threshold时进行重构数据,那这个threshold是什么东东?是我们指定的initialCapacity吗?

table的初始容量是大于等于初始容量值,却小于等于最小的2N次幂,且是成倍增长的。thresholdtablesize*loadFactor取整。这里不要被构造函数里的threshold=tableSizeFor(initialCapacity)这个所迷惑。

final Node<K,V>[]resize() {
    Node<K,V>[] oldTab =table;
    int oldCap = (oldTab ==null) ?0 : oldTab.length;
    int oldThr =threshold;
    int newCap,newThr = 0;
    if (oldCap >0) {
        if(oldCap >= MAXIMUM_CAPACITY) {
            threshold= Integer.MAX_VALUE;
            return oldTab;
        }
        else if((newCap = oldCap << 1) <MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1;// double threshold
    }
    else if(oldThr > 0)// initial capacity was placed in threshold
        newCap = oldThr;
    else {               // zero initial threshold signifies using defaults
        newCap =DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR* DEFAULT_INITIAL_CAPACITY);
    }
    if(newThr == 0) {
        floatft = (float)newCap *loadFactor;
        newThr = (newCap <MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY?
                  (int)ft : Integer.MAX_VALUE);
    }
    threshold= newThr;
    @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])newNode[newCap];
    table= newTab;
    if (oldTab !=null) {
        for(intj = 0;j < oldCap;++j) {

 

那么HashMap map=new HashMap(1);在初始化时,table=null,threshold=1loadFactor=0.75f。在调用put时,就会执行resize(),因为tablenull。此时分配table的容量为1threshold=1*0.75f=0,然后直接将数据放进去,然后判断size1,大于threshold(为0),此时再resize(),容量翻倍变成2threshold变成12*0.75),即数组由一个长度,然后换成另一个长度为2的新的数组,并且重新分配数据。即new HashMap(1)这样最好不要用,还不如直接new HashMap(2)。若目的只是放一个元素,最高效的写法,还不如写电脑new HashMap(1, 1);即不要使用默认的加载因子。

下面表格表示new HashMap(1);每次放入新成员的操作过程

 

HashTable需要增加一种功能。类似于StringBuild的方法ensureCapacityintminimumCapacity),在有些场景下减少resize()的次数。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值