HashMap是Java中基于哈希表实现的键值对存储结构,允许存储null键和null值,不保证元素的顺序,具有查询和插入效率高的特点。它的核心存储逻辑围绕数组、链表和红黑树展开,通过哈希算法快速定位元素位置。

HashMap的核心存储结构
HashMap底层维护了一个Node数组,数组的每个位置称为一个桶。每个Node节点包含hash值、键、值以及指向下一个节点的引用,当发生哈希冲突时,相同桶位置的节点会以链表形式连接,当链表长度超过阈值时会转换为红黑树,提升查询效率。
Node节点的结构定义
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;
}
// 其他方法省略
}
HashMap存储数据的完整流程
当调用put(K key, V value)方法向HashMap中存储数据时,会按照以下逻辑执行:
1. 计算键的哈希值
首先会调用键的hashCode()方法获取原始哈希值,然后通过扰动函数对哈希值进行处理,减少哈希冲突的概率。扰动函数的逻辑是将原始哈希值的高16位与低16位进行异或运算,让哈希值的分布更均匀。
static final int hash(Object key) {
int h;
// 如果key为null,哈希值为0;否则将key的hashCode高16位与低16位异或
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
2. 定位数组桶的位置
得到处理后的哈希值后,通过(n - 1) & hash的运算确定当前键值对应该存放的数组索引位置,其中n是数组的长度,且数组长度始终是2的幂次方,这样位运算的效率比取模运算更高,同时能保证索引落在数组的有效范围内。
3. 处理桶位置的存储情况
定位到桶位置后,会有三种不同的情况:
- 如果当前桶位置为null,直接创建新的Node节点放入该位置。
- 如果当前桶位置已经有节点,先判断该节点的key是否与要插入的key相等(通过
hash值相等且(key == 要插入的key 或 key.equals(要插入的key))判断),如果相等则替换该节点的value值。 - 如果key不相等,判断当前桶位置是链表还是红黑树:如果是链表,遍历链表的所有节点,没有相同key则尾插法添加新节点,若链表长度达到树化阈值(默认8)且数组长度大于等于最小树化容量(默认64),则将链表转换为红黑树;如果是红黑树,则按照红黑树的规则插入节点。
4. 扩容判断
插入节点后,如果HashMap的元素数量超过了阈值(数组长度 * 负载因子,默认负载因子是0.75),就会触发数组扩容,扩容后的数组长度是原来的2倍,同时会重新计算所有节点的位置,将节点迁移到新的数组中。
存储流程代码示例
以下是一个简单的存储示例,展示HashMap的存储过程:
import java.util.HashMap;
public class HashMapStoreDemo {
public static void main(String[] args) {
// 创建初始容量为16的HashMap
HashMap<String, Integer> map = new HashMap<>();
// 存储键值对,触发哈希计算、桶定位等流程
map.put("apple", 10);
map.put("banana", 20);
map.put("apple", 30); // 键相同,会替换原来的value
// 输出结果,apple对应的值会被替换为30
System.out.println(map.get("apple")); // 输出30
System.out.println(map.get("banana")); // 输出20
}
}
HashMap存储的核心特性
HashMap的存储设计有以下几个核心特性:
- 允许存储null键和null值,null键的哈希值固定为0,会存放在数组索引0的位置。
- 不保证元素的迭代顺序,因为元素的位置由哈希值决定,扩容后元素的位置会发生变化。
- 线程不安全,如果需要在多线程环境下使用键值映射结构,可以考虑使用ConcurrentHashMap。
- 扩容操作会消耗一定的性能,所以在能预估元素数量的情况下,建议在创建HashMap时指定合适的初始容量,减少扩容次数。
总结
HashMap的存储过程本质是哈希表的应用,通过哈希算法快速定位存储位置,用链表和红黑树解决哈希冲突问题,通过扩容机制保证存储效率。理解HashMap的存储原理,能帮助开发者更好地规避使用中的问题,比如在自定义对象作为键时,需要正确重写hashCode()和equals()方法,否则可能导致存储和查询异常。