zoukankan      html  css  js  c++  java
  • 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);
    }
    }
    }
    复制代码

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

    e.next=newTable[i];

    newTable[i]=e;

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

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

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

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

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

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

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

    问题:

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

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

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

  • 相关阅读:
    正经学C#_循环[do while,while,for]:[c#入门经典]
    Vs 控件错位 右侧资源管理器文件夹点击也不管用,显示异常
    asp.net core 获取当前请求的url
    在实体对象中访问导航属性里的属性值出现异常“There is already an open DataReader associated with this Command which must be
    用orchard core和asp.net core 3.0 快速搭建博客,解决iis 部署https无法登录后台问题
    System.Data.Entity.Core.EntityCommandExecution The data reader is incompatible with the specified
    初探Java设计模式3:行为型模式(策略,观察者等)
    MySQL教程77-CROSS JOIN 交叉连接
    MySQL教程76-HAVING 过滤分组
    MySQL教程75-使用GROUP BY分组查询
  • 原文地址:https://www.cnblogs.com/hqlong/p/6802487.html
Copyright © 2011-2022 走看看