数据结构 - HashMap

148 阅读2分钟

1. 基本概念

public class HashMap<K,V> {
    // 核心成员变量
    transient Node<K,V>[] table;  // 哈希表数组
    int threshold;                 // 扩容阈值
    final float loadFactor;        // 负载因子
    int size;                      // 实际元素个数
}
graph TD
    A[HashMap] --> B[数组 + 链表]
    B --> C[数组]
    B --> D[链表]
    B --> E[红黑树]
    
    C --> F[散列分布]
    D --> G[解决冲突]
    E --> H[优化查询]

2. 数据结构

// 1. 数组:散列分布
Node<K,V>[] table;

// 2. 链表节点
static class Node<K,V> {
    final int hash;    // 哈希值
    final K key;       // 键
    V value;          // 值
    Node<K,V> next;   // 下一个节点
}

// 3. 红黑树节点
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
    TreeNode<K,V> parent;   // 父节点
    TreeNode<K,V> left;     // 左子节点
    TreeNode<K,V> right;    // 右子节点
    boolean red;            // 颜色
}

3. 重要参数

// 1. 初始容量:16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

// 2. 负载因子:0.75f
static final float DEFAULT_LOAD_FACTOR = 0.75f;

// 3. 树化阈值:8
static final int TREEIFY_THRESHOLD = 8;

// 4. 链化阈值:6
static final int UNTREEIFY_THRESHOLD = 6;

4. 核心方法

// 1. 添加元素
public V put(K key, V value) {
    // 1.1 计算哈希值
    int hash = hash(key);
    
    // 1.2 定位数组索引
    int index = (n - 1) & hash;
    
    // 1.3 插入或更新
    if (node == null) {
        // 新建节点
    } else {
        // 更新或链表插入
    }
}

// 2. 获取元素
public V get(Object key) {
    Node<K,V> e = getNode(hash(key), key);
    return e == null ? null : e.value;
}

5. 重要机制

5.1 哈希计算

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

5.2 扩容机制

// 触发条件:size > threshold
final Node<K,V>[] resize() {
    // 1. 新建2倍大小的数组
    // 2. 重新哈希
    // 3. 移动节点
}

6. 面试回答要点

6.1 基本介绍

HashMap 是基于哈希表实现的 Map 接口,特点是:
1. 存储键值对
2. 允许 null 键和 null 值
3. 非线程安全
4. 无序

6.2 数据结构

1. 数组 + 链表 + 红黑树
2. JDK 1.8 引入红黑树优化
3. 链表长度超过 8 转红黑树
4. 红黑树节点少于 6 转链表

6.3 重要参数

1. 初始容量:16
2. 负载因子:0.75
3. 扩容阈值:容量 * 负载因子
4. 树化阈值:8

6.4 工作原理

1. 计算 key 的 hash 值
2. 定位数组索引:(n-1) & hash
3. 解决哈希冲突:链表或红黑树
4. 动态扩容:翻倍扩容

7. 性能优化建议

// 1. 初始容量设置
HashMap<K,V> map = new HashMap<>(initialCapacity);

// 2. 重写 equals 和 hashCode
@Override
public boolean equals(Object o) {
    // 实现正确的相等性判断
}

@Override
public int hashCode() {
    // 实现分布均匀的哈希算法
}

8. 常见问题解答

8.1 为什么是 2 的幂容量?

1. 便于计算索引:(n-1) & hash
2. 散列更均匀
3. 扩容时重哈希更高效

8.2 为什么使用红黑树?

1. 链表查询 O(n)
2. 红黑树查询 O(log n)
3. 平衡树,性能稳定

8.3 线程安全问题

1. HashMap 非线程安全
2. 可使用 ConcurrentHashMap
3. 或 Collections.synchronizedMap()

9. 实际应用示例

// 1. 基本使用
HashMap<String, User> userMap = new HashMap<>();
userMap.put("user1", new User("张三"));
User user = userMap.get("user1");

// 2. 遍历方式
// 2.1 键值对遍历
for (Map.Entry<String, User> entry : userMap.entrySet()) {
    String key = entry.getKey();
    User value = entry.getValue();
}

// 2.2 键遍历
for (String key : userMap.keySet()) {
    // 处理键
}

// 2.3 值遍历
for (User value : userMap.values()) {
    // 处理值
}