HashMap 扩容 加载因子

HashMap:

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

扩容:

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倍的扩大
    }
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); // 重新计算threshold的值
    }
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);
             }
         }
     }

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

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

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

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

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

问题:

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

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

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

http://www.javacui.com/Theory/377.html

http://blog.csdn.net/zhangerqing/article/details/8193118

http://www.cnblogs.com/matrix-skygirl/archive/2013/01/17/2864919.html

时间: 2024-08-25 09:56:57

HashMap 扩容 加载因子的相关文章

关于new HashMap&lt;&gt;(1)中1的理解(hashMap的加载因子)

新入公司,阅读代码的时候发现了一行代码,为 Map<String, String> map=new HashMap<>(1); 对于这个括号里面的1不能理解,于是查了资料,大概了解了. 解释如下:这个1被称为加载因子. API: /** * Constructs an empty <tt>HashMap</tt> with the specified initial * capacity and the default load factor (0.75).

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

这里要讨论这些常用的默认初始容量和扩容的原因是: 当底层实现涉及到扩容时,容器或重新分配一段更大的连续内存(如果是离散分配则不需要重新分配,离散分配都是插入新元素时动态分配内存),要将容器原来的数据全部复制到新的内存上,这无疑使效率大大降低. 加载因子的系数小于等于1,意指  即当 元素个数 超过 容量长度*加载因子的系数 时,进行扩容. 另外,扩容也是有默认的倍数的,不同的容器扩容情况不同. List 元素是有序的.可重复 ArrayList.Vector默认初始容量为10 Vector:线程

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

这里要讨论这些常用的默认初始容量和扩容的原因是: 当底层实现涉及到扩容时,容器或重新分配一段更大的连续内存(如果是离散分配则不需要重新分配,离散分配都是插入新元素时动态分配内存),要将容器原来的数据全部复制到新的内存上,这无疑使效率大大降低. 加载因子的系数小于等于1,意指 即当 元素个数 超过 容量长度*加载因子的系数 时,进行扩容. 另外,扩容也是有默认的倍数的,不同的容器扩容情况不同. List 元素是有序的.可重复 ArrayList.Vector默认初始容量为10 Vector:线程安

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

首先,这三个概念说下.初始大小,就是创建时可容纳的默认元素个数:加载因子,表示某个阀值,用0~1之间的小数来表示,当已有元素占比达到这个阀值后,底层将进行扩容操作:扩容方式,即指定每次扩容后的大小的规则,比如翻倍等. 当底层实现涉及到扩容时,容器或重新分配一段更大的连续内存(如果是离散分配则不需要重新分配,离散分配都是插入新元素时动态分配内存),要将容器原来的数据全部复制到新的内存上,这无疑使效率大大降低. 加载因子的系数小于等于1,意指  即当 元素个数 超过 容量长度*加载因子的系数 时,进

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

内容转自:https://blog.csdn.net/Dazhu233/article/details/79596584 先看HashMap的定义: public class HashMap<K,V>extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable HashMap是AbstractMap的子类,实现了Map接口. HashMap() Constructs an empty Hash

理解 HashMap 加载因子 loadFactor

加载因子是表示Hsah表中元素的填满的程度.若:加载因子越大,填满的元素越多,好处是,空间利用率高了,但:冲突的机会加大了.反之,加载因子越小,填满的元素越少,好处是:冲突的机会减小了,但:空间浪费多了. 冲突的机会越大,则查找的成本越高.反之,查找的成本越小.因而,查找时间就越小. 因此,必须在 "冲突的机会"与"空间利用率"之间寻找一种平衡与折衷. 这种平衡与折衷本质上是数据结构中有名的"时-空"矛盾的平衡与折衷.

hashMap扩容

什么时候扩容:当向容器添加元素的时候,会判断当前容器的元素个数,如果大于等于阈值---即当前数组的长度乘以加载因子的值的时候,就要自动扩容啦. 扩容(resize):当HashMap对象内部的数组无法装载更多的元素时,对象就需要扩大数组的长度,以便能装入更多的元素.Java里的数组是无法自动扩容的,所以使用一个新的数组代替已有的容量小的数组. resize就是使用一个容量更大的数组来代替已有的容量小的数组,transfer()方法将原有Entry数组的元素拷贝到新的Entry数组里.如果发生了h

HashMap 扩容分析

使用HashMap时我们需要注意一下几点问题: 1.HashMap是常用的Java集合之一,是基于哈希表的Map接口的实现.与HashTable主要区别为不支持同步和允许null作为key和value.  2.HashMap非线程安全,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致.  3.如果需要满足线程安全,可以用 Collections的synchronizedMap方法使HashMap具有线程安全的能力,或者使用ConcurrentHashMap.  4.在JDK1

Android Bitmap 加载多张图片的缓存处理(精华二)

一般少量图片是很少出现OOM异常的,除非单张图片过~大~ 那么就可以用教程一里面的方法了通常应用场景是listview列表加载多张图片,为了提高效率一般要缓存一部分图片,这样方便再次查看时能快速显示~不用重新下载图片但是手机内存是很有限的~当缓存的图片越来越多,即使单张图片不是很大,不过数量太多时仍然会出现OOM的情况了~本篇则是讨论多张图片的处理问题 -----------------------------------------------------------------------