Java HashMap size() 方法(超详细)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 82w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2900+ 小伙伴加入学习 ,欢迎点击围观

在 Java 编程中,HashMap 是一种广泛使用的数据结构,它通过键值对(Key-Value)实现高效的数据存储与检索。而 HashMap 中的 size() 方法作为获取集合元素数量的核心方法,其使用场景和底层实现原理值得深入探讨。无论是处理用户数据统计、缓存管理,还是优化算法性能,理解 size() 方法的特性都至关重要。本文将从基础到进阶,结合代码示例与实际案例,带您全面掌握这一方法的使用与原理。


什么是 HashMap?

核心概念

HashMap 是 Java 集合框架中的一个非线程安全、基于哈希表实现的键值对容器。它通过哈希函数计算键的哈希值,并根据该值确定元素的存储位置。其核心特性包括:

  • 键唯一性:每个键(Key)必须唯一,重复的键会导致后添加的值覆盖原有的值。
  • 无序性:元素存储顺序与插入顺序无关,除非使用 LinkedHashMap 等有序实现。
  • 允许空值:可以存储 null 值,但键只能有一个 null

类比理解:图书馆书架

想象一个图书馆的书架系统:每个书架(桶,Bucket)可以存放多本书(键值对),而书籍的索引号(哈希值)决定了它们的存放位置。HashMap 的工作原理与此类似:通过哈希函数将键映射到特定的“书架”,从而快速定位数据。


size() 方法:获取键值对数量

方法定义与返回值类型

size() 方法是 HashMap 的一个实例方法,用于返回当前集合中键值对的数量。其定义如下:

public int size() {
    return size;
}
  • 返回值int 类型,表示键值对的总数。
  • 时间复杂度O(1),因为 HashMap 内部维护了一个计数器 size,直接返回该值即可。

基础用法示例

HashMap<String, Integer> scores = new HashMap<>();
scores.put("Alice", 90); // 添加第一个键值对
scores.put("Bob", 85);   // 添加第二个键值对
System.out.println("当前元素数量:" + scores.size()); // 输出:2

通过上述代码,我们可以直观地看到 size() 方法的简单用法。


size() 方法的底层实现原理

计数器 size 的维护

HashMap 内部通过一个名为 size 的变量实时记录元素数量。每当执行以下操作时,该变量会动态更新:

  • 添加元素:调用 put() 方法时,若键不存在,则 size 自增 1。
  • 删除元素:调用 remove() 方法时,size 自减 1。
  • 批量操作:如 clear() 会直接将 size 置为 0。

扩容对 size 的影响

HashMap 的元素数量超过扩容阈值(默认为容量的 0.75 倍)时,会触发扩容操作。扩容时,所有元素会被重新哈希到更大的桶数组中。然而,此过程仅改变元素的存储位置,不会影响键值对的总数,因此 size() 的返回值保持不变。

扩容过程的代码片段

// 简化版扩容逻辑
void resize() {
    // 新容量为原容量的 2 倍
    int newCapacity = oldCapacity << 1;
    Entry[] newTable = new Entry[newCapacity];
    
    // 遍历旧桶,将元素重新分配到新桶
    for (Entry e : oldTable) {
        if (e != null) {
            Entry<K,V> loHead = null, hiHead = null;
            Entry<K,V> loTail = null, hiTail = null;
            do {
                // 根据新容量重新计算哈希索引
                int i = indexFor(e.hash, newCapacity);
                if ((i & oldMask) == 0) {
                    if (loHead == null) {
                        loHead = e;
                        loTail = e;
                    } else {
                        loTail.next = e;
                        loTail = e;
                    }
                } else {
                    if (hiHead == null) {
                        hiHead = e;
                        hiTail = e;
                    } else {
                        hiTail.next = e;
                        hiTail = e;
                    }
                }
            } while ((e = e.next) != null);
        }
    }
    // 更新 table 指向新数组,但 size 不变
    table = newTable;
}

从代码中可以看出,扩容仅修改元素的存储结构,而键值对的总数始终由 size 变量准确记录。


实际应用场景与案例分析

场景 1:数据统计与条件判断

假设我们需要统计用户输入的单词数量,并根据数量执行不同操作:

HashMap<String, Integer> wordCount = new HashMap<>();
// 模拟用户输入的单词列表
String[] words = {"apple", "banana", "apple", "orange"};
for (String word : words) {
    wordCount.put(word, wordCount.getOrDefault(word, 0) + 1);
}

// 根据单词总数选择输出格式
if (wordCount.size() > 3) {
    System.out.println("高频词统计:");
} else {
    System.out.println("所有词统计:");
}
// 输出各词的计数
for (Map.Entry<String, Integer> entry : wordCount.entrySet()) {
    System.out.println(entry.getKey() + ":" + entry.getValue());
}

此案例中,size() 方法直接决定了输出格式的判断逻辑。

场景 2:缓存管理中的容量控制

在缓存系统中,size() 方法可用于监控缓存大小,防止内存溢出:

HashMap<String, byte[]> cache = new HashMap<>();
int MAX_CAPACITY = 1000; // 最大允许的键值对数量

public void putToCache(String key, byte[] value) {
    if (cache.size() >= MAX_CAPACITY) {
        // 清理旧数据或抛出异常
        System.out.println("缓存已满,无法添加新条目");
        return;
    }
    cache.put(key, value);
}

通过结合 size() 方法与阈值判断,可以有效控制资源占用。


使用 size() 方法的注意事项

注意点 1:线程安全问题

HashMap 是非线程安全的,若在多线程环境下使用 size() 方法,可能出现以下问题:

  • 不一致读取:在扩容或修改元素时,其他线程可能读取到中间状态的 size 值。
  • 解决方式:使用 ConcurrentHashMap 或手动加锁。

注意点 2:与迭代器的配合

在遍历 HashMap 时,若动态修改元素数量(如通过 remove()put()),可能导致 ConcurrentModificationException。此时,size() 的值可能已变化,但迭代器仍处于旧状态。

// 错误示例:遍历时修改集合
for (Map.Entry<String, Integer> entry : scores.entrySet()) {
    if (entry.getValue() < 90) {
        scores.remove(entry.getKey()); // 可能引发异常
    }
}
// 正确做法:使用迭代器的 remove() 方法
Iterator<Map.Entry<String, Integer>> iterator = scores.entrySet().iterator();
while (iterator.hasNext()) {
    Map.Entry<String, Integer> entry = iterator.next();
    if (entry.getValue() < 90) {
        iterator.remove(); // 安全删除
    }
}

总结

通过本文的讲解,我们深入理解了 HashMap size() 方法的定义、实现原理及实际应用。该方法凭借其 O(1) 的时间复杂度和直观的语义,成为 Java 开发中不可或缺的工具。无论是基础的数据统计,还是复杂的缓存管理,掌握 size() 方法的使用场景与潜在风险,都能帮助开发者编写出更高效、稳定的代码。

在后续学习中,建议读者进一步探索 HashMap 的其他核心方法(如 containsKey()get())及其底层实现,以构建更完整的 Java 集合知识体系。

最新发布