哈希——什么是 hashmap?

本文详细介绍了HashMap的数据结构和工作原理,探讨了其内部实现机制,包括初始容量、加载因子、rehash操作等关键概念,并提供了四种构造函数的具体说明。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

考察点:哈希表
参考回答: HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。

HashMap 继承于 AbstractMap,实现了 Map、Cloneable、java.io.Serializable 接口。 HashMap 的实现不是同步的,这意味着它不是线程安全的。它的 key、value 都可以为 null。此 外,HashMap 中的映射不是有序的。

HashMap 的实例有两个参数影响其性能:“初始容量” 和 “加载因子”。容量 是哈希表 中桶的数量,初始容量 只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前 可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该 哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。 通常,默认加载因子是 0.75, 这是在时间和空间成本上寻求一种折衷。加载因子过高虽然减少 了空间开销,但同时也增加了查询成本(在大多数 HashMap 类的操作中,包括 get 和 put 操 作,都反映了这一点)。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便 最大限度地减少 rehash 操作次数。如果初始容量大于最大条目数除以加载因子,则不会发生 rehash 操作。

hashmap 共有 4 个构造函数:
// 默认构造函数。
HashMap()

// 指定“容量大小”的构造函数
HashMap(int capacity)

// 指定“容量大小”和“加载因子”的构造函数
HashMap(int capacity, float loadFactor)

// 包含“子 Map”的构造函数
HashMap(Map<? extends K, ? extends V> map)

理解:

  • HashMap 中的映射不是有序的
  • 它的 key、value 都可以为 null
  • 一般情况下,HashMap中的容量并不会放满,默认的加载因子为0.75,是时间与空间的折中,当map中的条目数达到了加载因子称最大容量,此时就需要将容量加倍,执行rehash(),重构哈希表。
  • 设置容量是应该考虑到所需条目数和加载因子,使初始容量大于最大条目数除以加载因子,从而减少rehash()操作

题目来自:牛客网

### Java HashMap 实现原理 #### 数据结构 `HashMap` 是基于 **哈希表** 的 `Map` 接口实现,用于存储对 (key-value pairs)[^3]。它的底层数据结构由数组和链表组成,在某些情况下还会涉及红黑树。 在 JDK 1.8 及以后版本中,`HashMap` 使用的是 **数组 + 链表 + 红黑树** 的组合形式[^2]。具体来说: - 数组是核心容器,每个位置称为桶 (bucket),通过计算 key 的 hash 定位到对应的 bucket。 - 当多个键映射到同一个 bucket 时,会发生冲突。此时,`HashMap` 使用链表来解决冲突问题。 - 如果某个链表长度超过一定阈(默认为 8),为了提高查询效率,该链表会被转换成红黑树[^4]。 #### 初始化与扩容机制 `HashMap` 默认初始容量为 16,并且总是使用 2 的幂次方作为容量大小。当实际存储的元素数量达到负载因子乘以当前容量时 (`size >= threshold`),触发扩容操作。扩容过程中,新容量变为原容量的两倍,并重新分配所有的键对到新的 buckets 中。 #### 存储单元 Node 类型 `HashMap` 定义了一个名为 `Node<K,V>` 的静态内部类表示单个存储单元——即节点[^1]。它实现了 `Map.Entry<K,V>` 接口,具有四个字段:`hash`, `key`, `value`, 和指向下一个节点的引用 `next`。 以下是简化版的 `Node` 结构: ```java static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node<K,V> next; Node(int hash, K key, V value, Node<K,V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } public final K getKey() { return key; } public final V getValue() { return value; } } ``` #### put 方法流程分析 调用 `put(K key, V value)` 插入或更新键对的过程如下: 1. 计算给定 key 的 hash ; 2. 利用此 hash 找到对应 bucket 的索引位置; 3. 若目标 bucket 尚未被占用,则直接创建一个新的 `Node` 并放置其中; 4. 否则遍历已存在的链表/红黑树查找是否有相同 key 的节点;若有,则替换旧;若无,则新增一个节点至链表尾部或者插入红黑树; 5. 检查是否需要调整为红黑树形态以及判断是否超出 load factor 导致需执行 resize 扩容动作。 #### 多线程安全性讨论 需要注意的是,标准 `HashMap` 不具备线程安全性特性。如果尝试在一个多线程环境中共享未经同步保护的实例可能会引发诸如死循环等问题(JDK 1.7 版本下的情况), 或者出现数据覆盖现象(适用于 JDK 1.8+) 。因此对于并发访问需求较高的场合建议考虑其他替代品如 `ConcurrentHashMap`. --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值