先看HashMap的定义:

public class HashMap<K,V>extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable

HashMap是AbstractMap的子类,实现了Map接口。

HashMap()

Constructs an empty HashMap with the default initial capacity (16) and the default load factor (0.75).

HashMap(int initialCapacity)

Constructs an empty HashMap with the specified initial capacity and the default load factor (0.75).

HashMap(int initialCapacity, float loadFactor)

Constructs an empty HashMap with the specified initial capacity and load factor.

HashMap(Map<? extends K,? extends V> m)

Constructs a new HashMap with the same mappings as the specified Map.

总共给出了4中构造方法。

1.HashMap()   不带参数,默认初始化大小为16,加载因子为0.75;

2.HashMap(int initialCapacity)  指定初始化大小;

3.HashMap(int initialCapacity ,float loadFactor)指定初始化大小和加载因子大小;

4.HashMap(Map<? extends K,? extends V> m) 用现有的一个map来构造HashMap。

先分析一下初始化代码

public HashMap(int initialCapacity,float loadFactor){  
   if(initialCapacity<0) //初始化大小小于0,抛出异常  
       throw new IllegalArgumentException("Illegal initial capacity: "  
       +initialCapacity);  
   if(initialCapacity>MAXIMUM_CAPACITY)//初始大小最大为默认最大值  
       initialCapacity=MAXIMUM_CAPACITY;  
   if(loadFactor <=0|| Float.isNaN(loadFactor)) //加载因子要在0到1之间  
       throw new IllegalArgumentException("Illegal load factor: "  
       +loadFactor);  
   this.loadFactor =loadFactor;  
   this.threshold=tableSizeFor(initialCapacity);  
   //threshold是根据当前的初始化大小和加载因子算出来的边界大小,  
   //当桶中的键值对超过这个大小就进行扩容  
}

threshold=initialCapacity*loadFactor,桶中的键值对超过这个界限就把桶的容量变大。

if ((tab = table) == null || (n = tab.length) == 0)  
   n = (tab = resize()).length;

这是第一次向桶中放入元素时(put方法)的一次扩容,因为初始化时并没有指定桶的大小

每次调用put方法后,都会进行验证,判断是否需要扩容

if (++size > threshold)  
           resize();

在看一下扩容的代码

Node<K,V>[] oldTab=table;    
int oldCap=(oldTab==null)?0:oldTab.length;// 第一次扩容时旧的桶大小为0  
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>=DEFALT_INITAL_CAPACITY)  
       newThr=oldThr<<1;   //如果旧的桶大小扩大一倍还没有超过最大值,  
                            //就把旧的桶大小和新的边界都乘2  
}  
else if(oldThr>0)    
   newCap=oldThr; //第一次扩容时会进入这里初始化桶的大小  
if(newThr==0){  //根据初始化桶数组大小和加载因子已经是否越界来设置新边界  
   float ft=(float)newCap*loadFactor;  
   newThr=(newCap<MAXIMUM_CAPACITY && ft<(float)MAXIMUM_CAPACITY?  
           (int)ft:Integer.MAX_VALUE);  
}

下面我们来讨论一个问题。

为什么默认初始化桶数组大小为16,为什么加载因子的大小为0.75,这两个值的选取有什么特点。

通过看上面的代码我们可以知道这两个值主要影响的threshold的大小,这个值的数值是当前桶数组需不需要扩容的边界大小,

我们都知道桶数组如果扩容,会申请内存空间,然后把原桶中的元素复制进新的桶数组中,这是一个比较耗时的过程。既然这样,那为何不把这两个值都设置大一些呢,threshold是两个数的乘积,设置大一些就不那么容易会进行扩容了啊。

原因是这样的,如果桶初始化桶数组设置太大,就会浪费内存空间,16是一个折中的大小,既不会像1,2,3那样放几个元素就扩容,也不会像几千几万那样可以只会利用一点点空间从而造成大量的浪费。

加载因子设置为0.75而不是1,是因为设置过大,桶中键值对碰撞的几率就会越大,同一个桶位置可能会存放好几个value值,这样就会增加搜索的时间,性能下降,设置过小也不合适,如果是0.1,那么10个桶,threshold为1,你放两个键值对就要扩容,太浪费空间了。

为什么HashMap初始大小为16,为什么加载因子大小为0.75,这两个值的选取有什么特点?的更多相关文章

  1. ArrayList、Vector、HashMap、HashSet的默认初始容量、加载因子、扩容增量

    当底层实现涉及到扩容时,容器或重新分配一段更大的连续内存(如果是离散分配则不需要重新分配,离散分配都是插入新元素时动态分配内存),要将容器原来的数据全部复制到新的内存上,这无疑使效率大大降低. 加载因 ...

  2. List、Map、set的加载因子,默认初始容量和扩容增量

    首先,这三个概念说下.初始大小,就是创建时可容纳的默认元素个数:加载因子,表示某个阀值,用0~1之间的小数来表示,当已有元素占比达到这个阀值后,底层将进行扩容操作:扩容方式,即指定每次扩容后的大小的规 ...

  3. Java集合类初始容量、加载因子、扩容增量

    当底层实现涉及到扩容时,容器或重新分配一段更大的连续内存(如果是离散分配则不需要重新分配,离散分配都是插入新元素时动态分配内存),要将容器原来的数据全部复制到新的内存上,这无疑使效率大大降低. 加载因 ...

  4. 关于new HashMap<>(1)中1的理解(hashMap的加载因子)

    新入公司,阅读代码的时候发现了一行代码,为 Map<String, String> map=new HashMap<>(1); 对于这个括号里面的1不能理解,于是查了资料,大概 ...

  5. List加载因子和扩容因子

    List.Map.set的加载因子,默认初始容量和扩容增量 首先,这三个概念说下.初始大小,就是创建时可容纳的默认元素个数:加载因子,表示某个阀值,用0~1之间的小数来表示,当已有元素占比达到这个阀值 ...

  6. ArrayList、Vector、HashMap、HashTable、HashSet的默认初始容量、加载因子、扩容增量

    这里要讨论这些常用的默认初始容量和扩容的原因是: 当底层实现涉及到扩容时,容器或重新分配一段更大的连续内存(如果是离散分配则不需要重新分配,离散分配都是插入新元素时动态分配内存),要将容器原来的数据全 ...

  7. [转]为什么Java中的HashMap默认加载因子是0.75

    前几天在一个群里看到有人讨论hashmap中的加载因子为什么是默认0.75. HashMap源码中的加载因子 static final float DEFAULT_LOAD_FACTOR = 0.75 ...

  8. HashMap默认加载因子为什么选择0.75?(阿里)

    Hashtable 初始容量是11 ,扩容 方式为2N+1; HashMap 初始容量是16,扩容方式为2N; 阿里的人突然问我为啥扩容因子是0.75,回来总结了一下: 提高空间利用率和 减少查询成本 ...

  9. HashMap 扩容 加载因子

    HashMap: public HashMap(int initialCapacity, float loadFactor) { //初始容量不能<0 if (initialCapacity & ...

随机推荐

  1. Linux CPU占用率监控工具小结

    关键词:top.perf.sar.ksar.mpstat.uptime.vmstat.pidstat.time.cpustat.munin.htop.glances.atop.nmon.pcp-gui ...

  2. 介绍一个比较了各种浏览器对于HTML5 等标准支持程度的网站

    可以选择浏览器种类,版本,比较的功能 网站地址:https://caniuse.com/#comparison

  3. js在前端json字符串和对象互相转化

    js在前端json字符串和对象互相转化 //对象转json串 注意:参数必须是对象,数组不起作用,对象格式{'0'=>'a'} JSON.stringify( arr ); //json字符串转 ...

  4. 【PAT】 B1006 换个格式输出整数

    超简单题 //直接将各位分开,分别用for循环输出 #include<stdio.h> int main(){ int num; scanf("%d",&num ...

  5. Spring事务管理2--声明式

    简述 1.Spring 的声明式事务管理在底层是建立在 AOP 的基础上.其本质是在方法前后进行拦截,然后在目标方法开始之前创建一个事务,在执行这目标方法结束后,根据执行情况提交或进行回滚事务. 2. ...

  6. ES5-ES6-ES7_对象的扩展

    简化的对象写法 省略同名的属性值,省略方法的function let x = 1; let y = 2; let point = { x, y, setX (x) { this.x = x } }; ...

  7. windows下python2和python3共享

    其实最好的解决方案使用 virtualenv http://pythonguidecn.readthedocs.io/zh/latest/dev/virtualenvs.html 不太好的方法,先安装 ...

  8. iptables nat 技术转发

    NAT 一. 什么是 NAT NAT(Network Address Translation)译为网络地址转换.通常路由器在转发我们的数据包时,仅仅会将源MAC地址换成自己的MAC地址,但是NAT技术 ...

  9. 【vue】vue +element 实现批量删除

    相关资料:http://blog.csdn.net/eagle_88/article/details/70238836 作者:smile.轉角 QQ:493177502

  10. 【vue】钩子函数生命周期

    图1 图2: 图3 相关资料:http://www.zhimengzhe.com/Javascriptjiaocheng/236707.html    https://segmentfault.com ...