合理应用HashMap扩容

本文深入探讨了HashMap的工作原理,包括其内部结构、基本操作(如get和put)的性能特点,以及如何通过合理设置初始容量和加载因子来优化性能。
public class HashMap<K,V>extends AbstractMap<K,V>implements Map<K,V>, Cloneable, Serializable


此实现假定哈希函数将元素适当地分布在各桶之间,可为基本操作(get 和 put)提供稳定的性能。
迭代 collection 视图所需的时间与 HashMap 实例的“容量”(桶的数量)及其大小(键-值映射关系数)成比例。
所以,如果迭代性能很重要,则不要将初始容量设置得太高(或将加载因子设置得太低)。 


HashMap 的实例有两个参数影响其性能:初始容量 和加载因子。
容量 是哈希表中桶的数量,初始容量只是哈希表在创建时的容量。
加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。
当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。 


通常,默认加载因子 (.75) 在时间和空间成本上寻求一种折衷。
加载因子过高虽然减少了空间开销,但同时也增加了查询成本(在大多数 HashMap 类的操作中,包括 get 和 put 操作,都反映了这一点)。
在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少 rehash 操作次数。
如果初始容量大于最大条目数除以加载因子,则不会发生 rehash 操作。 


如果很多映射关系要存储在 HashMap 实例中,则相对于按需执行自动的 rehash 操作以增大表的容量来说,使用足够大的初始容量创建它将使得映射关系能更有效地存储。 


HashMap() 
          构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空 HashMap。


(如果能明确数据的个数:初始容量就比较好办,如果无法明确,那么要考虑他的每次增长是*2)


源码:

    /**
     * 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);
    }

    /**
     * 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);
    }



在Java中,`HashMap` 是一种非常常用的数据结构,用于存储键值对。当 `HashMap` 中的元素数量超过阈值(threshold)时,会触发扩容操作(resize)。扩容失败可能由多种原因导致,比如内存不足、负载因子设置不当、哈希冲突严重等。 下面是一个典型的 `HashMap` 扩容失败的场景以及解决方案。 --- ### 问题分析 #### 常见的扩容失败原因: 1. **内存不足**:当 `HashMap` 存储大量数据时,扩容可能导致内存溢出(`OutOfMemoryError`)。 2. **负载因子设置过高**:如果负载因子(load factor)设置得太高,可能导致频繁哈希碰撞,影响性能甚至无法正常扩容。 3. **哈希冲突严重**:如果多个键的哈希值相同,可能导致链表过长,进而影响扩容性能。 4. **并发修改异常**:在多线程环境下,未使用 `ConcurrentHashMap` 可能导致扩容时出现并发修改异常。 --- ### 示例代码:模拟 `HashMap` 扩容失败 以下是一个简单的 `HashMap` 使用示例,模拟在扩容时可能出现的问题: ```java import java.util.HashMap; public class HashMapResizeFailure { public static void main(String[] args) { // 设置初始容量为1,默认负载因子为0.75 HashMap<Integer, String> map = new HashMap<>(1, 0.75f); // 添加元素,触发扩容 for (int i = 0; i < 1000000; i++) { map.put(i, "value" + i); } System.out.println("Map size: " + map.size()); } } ``` #### 代码解释: - `HashMap<Integer, String> map = new HashMap<>(1, 0.75f);`:初始化一个初始容量为1,负载因子为0.75的 `HashMap`。 - `map.put(i, "value" + i);`:向 `HashMap` 中不断添加元素,触发多次扩容。 - 如果内存不足,可能会抛出 `OutOfMemoryError`。 --- ### 解决方案 #### 1. **合理设置初始容量和负载因子** - 初始容量应根据预期数据量进行估算,避免频繁扩容。 - 负载因子建议保持默认值 `0.75`,除非有特殊需求。 ```java // 估算初始容量为1000,负载因子为0.75 int initialCapacity = (int) Math.ceil(1000 / 0.75); HashMap<Integer, String> map = new HashMap<>(initialCapacity); ``` #### 2. **避免内存溢出** - 监控内存使用情况,避免一次性加载过多数据。 - 使用 `try-catch` 捕获 `OutOfMemoryError`: ```java try { // 可能导致内存溢出的操作 } catch (OutOfMemoryError e) { System.err.println("内存不足,无法继续扩容"); // 清理资源或进行降级处理 } ``` #### 3. **优化哈希函数** - 确保键对象的 `hashCode()` 方法实现良好,减少哈希冲突。 - 如果使用自定义类作为键,建议重写 `hashCode()` 和 `equals()` 方法。 ```java public class MyKey { private int id; public MyKey(int id) { this.id = id; } @Override public int hashCode() { return Integer.hashCode(id); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; MyKey other = (MyKey) obj; return id == other.id; } } ``` #### 4. **多线程环境使用 `ConcurrentHashMap`** - 如果在多线程环境中使用 `HashMap`,建议替换为 `ConcurrentHashMap`,避免扩容时的并发问题。 ```java import java.util.concurrent.ConcurrentHashMap; ConcurrentHashMap<Integer, String> map = new ConcurrentHashMap<>(); ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值