这两天在看HashMap的时候,被负载因子float loadFactor搞得很晕,经过一天的研究,最后理出了自己的一点个人见解。

在HashMap的底层存在着一个名字为table的Entry数组,在实例化HashMap的时候,会输入两个参数,一个是 int initCapacity(初始化数组大小,默认值是16),一个是float loadFactor(负载因子,默认值是0.75),首先会根据initCapacity计算出一个大于或者等于initCapacity且为2的幂的值capacity,例如initCapacity为15,那么capacity就会为16,还会算出一个临界值threshold,也就是capacity * loadFactor,贴出源代码

    /**
* Constructs an empty <tt>HashMap</tt> with the specified initial
* capacity and load factor.
*
* @param initialCapacity the initial capacity
* @param loadFactor the load factor
* @throws IllegalArgumentException if the initial capacity is negative
* or the load factor is nonpositive
*/
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); // Find a power of 2 >= initialCapacity
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1; this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor);
table = new Entry[capacity];
init();
}

    创建完HashMap之后,下面来看put方法,首先会根据key值计算出其HashCode值,然后在通过一个indexFor方法计算出此元素该存放于table数组的哪个数组之中(我猜想可能是通过对table.length的值取余的操作计算出来的),再检测此table的此坐标位置的entry链是否存在此key或者此key值,若存在,则更新此元素的value值。源代码如下:

    /**
* Associates the specified value with the specified key in this map.
* If the map previously contained a mapping for the key, the old
* value is replaced.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
* (A <tt>null</tt> return can also indicate that the map
* previously associated <tt>null</tt> with <tt>key</tt>.)
*/
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> 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;
}

    下面来看看addEntry方法,参数bucketIndex就是当前元素应该插入到entry数组的下标,先取出放在此位置的entry,然后把当前元素放入该数组中,当前元素的next指向之前取出元素,形成entry链表。(描述的不是很清楚,大概就是把新加入的entry当成头放入到数组当中,然后指向之前的链表),放入之后就去判断当前的size是否达到了threshold极限值,若达到了,将会进行扩容。源代码如下:

    /**
* Adds a new entry with the specified key, value and hash code to
* the specified bucket. It is the responsibility of this
* method to resize the table if appropriate.
*
* Subclass overrides this to alter the behavior of put method.
*/
void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
if (size++ >= threshold)
resize(2 * table.length);
}

    下面来看resize方法,方法比较简单就是生成一个新的table数组(entry数组),然后根据新的Capacity和负载因子去生成新的临界值。重点是里面有个transfer方法。源代码如下:

    /**
* Rehashes the contents of this map into a new array with a
* larger capacity. This method is called automatically when the
* number of keys in this map reaches its threshold.
*
* If current capacity is MAXIMUM_CAPACITY, this method does not
* resize the map, but sets threshold to Integer.MAX_VALUE.
* This has the effect of preventing future calls.
*
* @param newCapacity the new capacity, MUST be a power of two;
* must be greater than current capacity unless current
* capacity is MAXIMUM_CAPACITY (in which case value
* is irrelevant).
*/
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);
table = newTable;
threshold = (int)(newCapacity * loadFactor);
}

    因为table数组的容量增加了,那么相应的table的length也增加了,那么之前存储的元素的位置也就不一样了,比如之前的length是16,现在的length是32,那么hashCode模16和HashCode模32的结果很有可能会不一样,所以就只有重新去计算新的位置,方法是遍历数组,在遍历数组上的entry链。(此时就是所谓的rehash??)

    /**
* Transfers all entries from current table to newTable.
*/
void transfer(Entry[] newTable) {
Entry[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) {
Entry<K,V> e = src[j];
if (e != null) {
src[j] = null;
do {
Entry<K,V> next = e.next;
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
}
}
}

    总结:当负载因子较大时,去给table数组扩容的可能性就会少,所以相对占用内存较少(空间上较少),但是每条entry链上的元素会相对较多,查询的时间也会增长(时间上较多)。反之就是,负载因子较少的时候,给table数组扩容的可能性就高,那么内存空间占用就多,但是entry链上的元素就会相对较少,查出的时间也会减少。所以才有了负载因子是时间和空间上的一种折中的说法。所以设置负载因子的时候要考虑自己追求的是时间还是空间上的少。

注意:设置initCapacity的时候,尽量设置为2的幂,这样会去掉计算比initCapactity大,且为2的幂的数的运算。

    疑问:感觉transfer方法会相当的耗时,是不是不去扩容会比较好?

关于HashMap中的负载因子的更多相关文章

  1. 面试这么撩准拿offer,HashMap深度学习,扰动函数、负载因子、扩容拆分,原理和实践验证,让懂了就是真的懂!

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 得益于Doug Lea老爷子的操刀,让HashMap成为使用和面试最频繁的API,没 ...

  2. HashMap负载因子

    下面是HashMap的一个构造函数,两个参数initialCapacity,loadFactor 这关系HashMap的迭代性能. /** * Constructs an empty <tt&g ...

  3. HashMap负载因子为什么是0.75

    待写 HashMap负载因子为什么是0.75?HashMap有一个初始容量大小,默认是16static final int DEAFULT_INITIAL_CAPACITY = 1 << ...

  4. 我说我了解集合类,面试官竟然问我为啥HashMap的负载因子不设置成1!?

    在Java基础中,集合类是很关键的一块知识点,也是日常开发的时候经常会用到的.比如List.Map这些在代码中也是很常见的. 个人认为,关于HashMap的实现,JDK的工程师其实是做了很多优化的,要 ...

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

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

  6. java基础 - 什么是hashmap的负载因子,hashmap的容量(即桶个数)为什么是2的幂次

    HashMap的负载因子是指,比如容量为16,负载因子为0.75,则当HashMap的元素个数达到16*0.75=12时,触发扩容.(16和0.75是初始默认的容量和负载因子). HashMap的容量 ...

  7. HashMap 扩容 加载因子

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

  8. HashMap中的resize以及死链的情况

    之前我已经写过关于HashMap的内容了:http://www.cnblogs.com/wang-meng/p/7545725.html 我们都知道HashMap是线程不安全的, 如果多线程来访问会有 ...

  9. Java HashMap中在resize()时候的rehash,即再哈希法的理解

    HashMap的扩容机制---resize() 虽然在hashmap的原理里面有这段,但是这个单独拿出来讲rehash或者resize()也是极好的. 什么时候扩容:当向容器添加元素的时候,会判断当前 ...

随机推荐

  1. GO数据库

    Golang 数据库操作 Golang 数据库 MySQL Golang支持DB操作位于database包下,支持基本CRUD操作.事务和Prepared Statement,本文以MySQL为例. ...

  2. D3.js 其他选择元素方法

    在上一节中,已经讲解了 select 和 selectAll,以及选择集的概念.本节具体讲解这两个函数的用法. 假设在 body 中有三个段落元素: <p>Apple</p> ...

  3. OpenGL的glPushMatrix和glPopMatrix矩阵栈顶操作函数详解

    OpenGL中图形绘制后,往往需要一系列的变换来达到用户的目的,而这种变换实现的原理是又通过矩阵进行操作的.opengl中的变换一般包括视图变换.模型变换.投影变换等,在每次变换后,opengl将会呈 ...

  4. 全局变量报错:UnboundLocalError: local variable 'l' referenced before assignment

    总结: 内部函数,不修改全局变量可以访问全局变量 内部函数,修改同名全局变量,则python会认为它是一个局部变量 在内部函数修改同名全局变量之前调用变量名称(如print sum),则引发Unbou ...

  5. vim 学习记录2

    当前行进行替换:s/XXX/YYY/gXXX是需要替换的字符串,YYY是替换后的字符串. 全局替换:% s/XXX/YYY/g. 对指定部分进行替换用V进入visual模式,再进行:s/XXX/YYY ...

  6. ftp断点续传

    有时候ftp的文件太大了 容易断掉 使用shell下载 1 #!/bin/bash 2 cd /data2/GATK2/refSeqDB/1000genomePhase3 3 ftp -v -n 19 ...

  7. 关于e820cycles参数

    关于e820cycles参数http://bbs.wuyou.net/forum.php?mod=redirect&goto=findpost&ptid=327458&pid= ...

  8. 如何组织较大项目的MVC文件夹结构

    现在还用不到,拷贝下来备用,原文链接 2016 年 9 月 第 31 卷,第 9 期 ASP.NET Core - ASP.NET Core MVC 的功能切分 作者 Steve Smith | 20 ...

  9. Laravel 分页详解

    Laravel分页很简单,但功能又很强大噢! 首先在控制器的方法中使用paginate(页面显示条数)方法,传入页面显示的条数 然后在模板页面使用方法render()来生成html元素 appends ...

  10. 菜单滑动-menu swipe

    http://tympanus.net/codrops/ http://www.idangero.us/sliders/swiper/index.php //触摸滑动