引用于:

HashMap:

  1. public HashMap(int initialCapacity, float loadFactor) {
    //初始容量不能<0
    if (initialCapacity < 0)
    throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
    //初始容量不能 > 最大容量值,HashMap的最大容量值为2^30
    if (initialCapacity > MAXIMUM_CAPACITY)
    initialCapacity = MAXIMUM_CAPACITY;
    //负载因子不能 < 0
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
    throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
    // 计算出大于 initialCapacity 的最小的 2 的 n 次方值。
    int capacity = 1;
    while (capacity < initialCapacity)
    capacity <<= 1;
    this.loadFactor = loadFactor;
    //设置HashMap的容量极限,当HashMap的容量达到该极限时就会进行扩容操作
    threshold = (int) (capacity * loadFactor);
    //初始化table数组
    table = new Entry[capacity]; init();
    }

在这里提到了两个参数:初始容量,加载因子。

这两个参数是影响HashMap性能的重要参数,其中容量表示哈希表中桶的数量,初始容量是创建哈希表时的容量,

加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度,它衡量的是一个散列表的空间的使用程度,负载因子越大表示散列表的装填程度越高,反之愈小。

对于使用链表法的散列表来说,查找一个元素的平均时间是O(1+a),因此如果负载因子越大,对空间的利用更充分,然而后果是查找效率的降低;

如果负载因子太小,那么散列表的数据将过于稀疏,对空间造成严重浪费。系统默认负载因子为0.75,一般情况下我们是无需修改的。

加载因子:

loadFactor

扩容:

  1. 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) // 这里是关键,一旦大于等于threshold的数值
    resize(2 * table.length); // 将会引起容量2倍的扩大
    }
  1. 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]; // 新的容器空间
  2.  
  3. transfer(newTable); // 复制数据过去
  4.  
  5. table = newTable;
    threshold = (int)(newCapacity * loadFactor); // 重新计算threshold的值
    }
  1. void transfer(Entry[] newTable) {
    // 保留原数组的引用到src中,
    Entry[] src = table;
    // 新容量使新数组的长度
    int newCapacity = newTable.length;      
    // 遍历原数组
    for (int j = 0; j < src.length; j++) {
    // 获取元素e
    Entry<K,V> e = src[j];
    if (e != null) {
    // 将原数组中的元素置为null
    src[j] = null;
    // 遍历原数组中j位置指向的链表
    do {
    Entry<K,V> next = e.next;
    // 根据新的容量计算e在新数组中的位置
    int i = indexFor(e.hash, newCapacity);
    // 将e插入到newTable[i]指向的链表的头部
    e.next = newTable[i];
    newTable[i] = e;
    e = next;
    }
     while (e != null);
    }
    }
    }

通过上面的transfer方法可以看出,

e.next=newTable[i];

newTable[i]=e;

链表存储倒过来了,最先出来的会将其next指向null,后面的就指向前一个,当然数据只有原来的一部分。

===================================================================

随着HashMap中元素的数量越来越多,发生碰撞的概率就越来越大,所产生的链表长度就会越来越长,这样势必会影响HashMap的速度,

为了保证HashMap的效率,系统必须要在某个临界点进行扩容处理。

该临界点在当HashMap中元素的数量等于table数组长度*加载因子。

但是扩容是一个非常耗时的过程,因为它需要重新计算这些数据在新table数组中的位置并进行复制处理。

所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。

问题:

当重新调整HashMap大小的时候,确实存在条件竞争,因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。

在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。

如果条件竞争发生了,那么就死循环了。

HashMap 扩容机制的更多相关文章

  1. HashSet保证元素唯一原理以及HashMap扩容机制

    一.HashSet保证元素唯一原理: 依赖于hashCode()和equals()方法1.唯一原理: 1.1 当HashSet集合要存储元素的时候,会调用该元素的hashCode()方法计算哈希值 1 ...

  2. 面试笔记--HashMap扩容机制

    转载请注明出处 http://www.cnblogs.com/yanzige/p/8392142.html 扩容必须满足两个条件: 1. 存放新值的时候当前已有元素的个数必须大于等于阈值 2. 存放新 ...

  3. HashMap扩容机制

    1.什么是resize: resize就是重新计算容量:当我们不断的向HashMap对象里不停的添加元素时,HashMap对象内部的数组就会出现无法装载更多的元素,这是对象就需要扩大数组的长度,以便能 ...

  4. Java中HashMap扩容机制思考

    1. HashMap在什么条件下扩容 判断HashMap的数组Size大小如果超过loadFactor*capacity,就要扩容. 相关的类属性: capacity:当前数组容量,始终保持 2^n, ...

  5. java源码--HashMap扩容机制学习

    待完成 Java中hash算法细述 https://blog.csdn.net/majinggogogo/article/details/80260400 java HashMap源码分析(JDK8) ...

  6. 深入理解HashMap的扩容机制

    什么时候扩容: 网上总结的会有很多,但大多都总结的不够完整或者不够准确.大多数可能值说了满足我下面条件一的情况. 扩容必须满足两个条件: 1. 存放新值的时候当前已有元素的个数必须大于等于阈值 2. ...

  7. HashMap底层结构、原理、扩容机制

    https://www.jianshu.com/p/c1b616ff1130 http://youzhixueyuan.com/the-underlying-structure-and-princip ...

  8. 浅谈JAVA中HashMap、ArrayList、StringBuilder等的扩容机制

    JAVA中的部分需要扩容的内容总结如下:第一部分: HashMap<String, String> hmap=new HashMap<>(); HashSet<Strin ...

  9. HashMap的扩容机制以及默认大小为何是2次幂

    HashMap的Put方法 回顾HashMap的put(Key k, Value v)过程: (1)对 Key求Hash值,对n-1取模计算出Hash表数组下标 (2)如果没有碰撞,直接放入桶中,即H ...

随机推荐

  1. 【JAVA秒会技术之秒杀面试官】秒杀Java面试官——集合篇(一)

    [JAVA秒会技术之秒杀面试官]秒杀Java面试官——集合篇(一) [JAVA秒会技术之秒杀面试官]JavaEE常见面试题(三) http://blog.csdn.net/qq296398300/ar ...

  2. SVN学习(一)——SVN 检出文件步骤、图标显示及含义

    May, I come... 1. 创建一个目录用来存放检出得到的文件,例如MyCRM 2. 直接进入目录MyCRM,点右键 3. 可以看到检出得到的文件 此时文件图标上没有任何标识.可能你会想到通过 ...

  3. asp.net+mvc+easyui+sqlite 简单用户系统学习之旅(四)—— session传递登录人信息显示+用户注销

    前面的学习,已经做好了简单的登录和主页,涉及了三层软件架构,mvc,easyui的layout.tree.tab.window,sqlite,动软代码自动生成. 下面完善一下登录跳转主页后,在sout ...

  4. Android 关于ZXing的使用

    1.http://blog.csdn.net/ryantang03/article/details/7831826 2.http://blog.csdn.net/xiaanming/article/d ...

  5. JS防后退跳转

    location.replace("http://www.baidu.com");

  6. Atitit,通过pid获取进程文件路径 java php  c#.net版本大总结

    Atitit,通过pid获取进程文件路径 java php  c#.net版本大总结 1. 通过PID获取进程路径的几种方法2 1.1. GetModuleFileNameEx 想获得进程可执行文件的 ...

  7. 从C#到Swift,Swift学习笔记

    最近在学习IOS,我一直使用的是C#来开发,对Java .C.C++也有一定的了解.最近入手了一台Air,想试着学习IOS开发. 如果你不是C#和Java阵营的,如果你对Swift没有兴趣,就不用往下 ...

  8. size_t ssize_t loff_t 的区别

    Ssize_t 与size_t 跟踪linux源码得到以下宏: #ifndef _SIZE_T #define _SIZE_T typedef __kernel_size_t         size ...

  9. TPM Key相关概念

    1. Storage Keys:存储密钥,用来加密数据和其它密钥的通用非对称密钥,这里的其它密钥可以是另外一个存储密钥,也可以是绑定密钥或签名密钥.它本身是长度2048bit的RSA私钥:它既可以是可 ...

  10. 浅谈push推送的一点感受

    在手机已成为生活必不可分的一部分,push服务伴随而来.ios的apns,android随着谷歌退出中国市场,各家在android的推送不断展开.有厂商的推送,如小米.华为.魅族.oppo等,还有中间 ...