用拉链法实现哈希表

简介: 本文详解哈希表中拉链法的实现原理,通过简化版到完整版代码,介绍如何用链表解决哈希冲突。支持泛型、动态扩容缩容、键值增删查改及遍历所有key,结合Java内置LinkedList优化实现,直观展示哈希表核心机制。

前文 哈希表核心原理 中我介绍了哈希表的核心原理和几个关键概念,其中提到了解决哈希冲突的方法主要有两种,分别是拉链法和开放寻址法(也常叫做线性探查法):
本文就来具体介绍一下拉链法的实现原理和代码。
拉链法的简化版实现
哈希表核心原理 已经介绍过哈希函数和 key 的类型的关系,其中 hash 函数的作用是在 O(1)O(1) 的时间把 key 转化成数组的索引,而 key 可以是任意不可变的类型。
但是这里为了方便诸位理解,我先做如下简化:
1、我们实现的哈希表只支持 key 类型为 int,value 类型为 int 的情况,如果 key 不存在,就返回 -1。
2、我们实现的 hash 函数就是简单地取模,即 hash(key) = key % table.length。这样也方便模拟出哈希冲突的情况,比如当 table.length = 10 时,hash(1) 和 hash(11) 的值都是 1。
3、底层的 table 数组的大小在创建哈希表时就固定,不考虑负载因子和动态扩缩容的问题。
简化版代码
这里直接给出简化版的代码实现,你可以先看看,后面将通过可视化面板展示增删查改的过程
有了上面的铺垫,我们现在来看一个比较完善的 Java 代码实现,主要新增了以下几个功能:
1、使用了泛型,可以存储任意类型的 key 和 value。
2、底层的 table 数组会根据负载因子动态扩缩容。
3、使用了 哈希表基础 中提到的 hash 函数,利用 key 的 hashCode() 方法和 table.length 来计算哈希值。
4、实现了 keys() 方法,可以返回哈希表中所有的 key。
为了方便,我直接使用 Java 标准库的 LinkedList 作为链表,这样就不用自己手动处理增删链表节点的操作了。当然如果你愿意的话,可以参照前文 单/双链表代码实现 自己编写操作 KVNode 的逻辑。
Java
运行代码
复制代码
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
import java.util.LinkedList;
list.remove(node);
size--;

            // 缩容,当负载因子小于 0.125 时,缩容
            if (size <= table.length / 8) {
                resize(table.length / 4);
            }
            return;
        }
    }
}

/***** 查 *****/

// 返回 key 对应的 val,如果 key 不存在,则返回 null
public V get(K key) {
    if (key == null) {
        throw new IllegalArgumentException("key is null");
    }
    LinkedList<KVNode<K, V>> list = table[hash(key)];
    for (KVNode<K, V> node : list) {
        if (node.key.equals(key)) {
            return node.value;
        }
    }
    return null;
}

// 返回所有 key
public List<K> keys() {
    List<K> keys = new LinkedList<>();
    for (LinkedList<KVNode<K, V>> list : table) {
        for (KVNode<K, V> node : list) {
            keys.add(node.key);
        }
    }
    return keys;
}

/***** 其他工具函数 *****/

public int size() {
    return size;
}

// 哈希函数,将键映射到 table 的索引
private int hash(K key) {
    return (key.hashCode() & 0x7fffffff) % table.length;
}

private void resize(int newCap) {
    // 构造一个更大容量的 HashMap
    MyChainingHashMap<K, V> newMap = new MyChainingHashMap<>(newCap);
    // 穷举当前 HashMap 中的所有键值对
    for (LinkedList<KVNode<K, V>> list : table) {
        for (KVNode<K, V> node : list) {
            // 将键值对转移到新的 HashMap 中
            newMap.put(node.key, node.value);
        }
    }
    // 将当前 HashMap 的底层 table 换掉
    this.table = newMap.table;
}

public static void main(String[] args) {
    MyChainingHashMap<Integer, Integer> map = new MyChainingHashMap<>();
    map.put(1, 1);
    map.put(2, 2);
    map.put(3, 3);
    System.out.println(map.get(1)); // 1
    System.out.println(map.get(2)); // 2

    map.put(1, 100);
    System.out.println(map.get(1)); // 100

    map.remove(2);
相关文章
|
存储 缓存 网络协议
深入理解Linux网络——内核是如何接收到网络包的
一、相关实际问题 RingBuffer是什么,为什么会丢包 网络相关的硬中断、软中断是什么 Linux里的ksoftirqd内核线程是干什么
|
3月前
|
编译器 C语言 C++
【2026最新】CodeBlocks官网下载安装和汉化教程:免费C/C++IDE使用指南(超详细)
Code::Blocks是一款免费、开源、跨平台的C/C++集成开发环境,支持Windows、macOS和Linux系统,具备代码编辑、编译、调试、项目管理等功能,兼容GCC、Clang等多种编译器,适合学习、教学及中小型跨平台开发。
1108 0
|
存储 监控 Linux
深入理解epoll:高效I/O多路复用的核心技术(上)
深入理解epoll:高效I/O多路复用的核心技术
|
7月前
|
存储 缓存 弹性计算
阿里云服务器实例怎么选?经济型、通用算力型、计算型、通用型、内存型区别及选择参考
在我们通过阿里云的活动选购云服务器的时候会发现,可选的云服务器实例主要以经济型、通用算力型、计算型、通用型、内存型为主,相同实例可能又分为多个实例规格(例如通用算力型u1与u2i),另外,同配置的云服务器往往有多个不同的实例可选。本文为大家详细介绍阿里云的经济型、通用算力型、计算型、通用型和内存型实例的性能特点及适用场景,以供大家选择参考。
724 25
|
Java Maven Spring
如何在idea中创建Springboot项目? 手把手带你创建Springboot项目,稳!
文章详细介绍了在IDEA中创建Spring Boot项目的过程,包括选择Spring Initializr、配置项目属性、选择Spring Boot版本、导入依赖、等待依赖下载以及项目结构简介。
23099 1
|
数据采集 算法
matlab实现合成孔径成像的三种算法
matlab实现合成孔径成像的三种算法
|
消息中间件 前端开发 调度
C++20 协程——你还只是听过?觉得没时间了解,这里可以帮到你。五分钟 从没听过到使用的帮助手册
来源:协程是在C++20 标准中提出的一个新的工具。 它突破传统的程序在cpu中来回切换时需要更新和恢复PCB资源现场的耗时操作(多进程)或者COW(低级调度)操作时间。
652 0
|
11月前
|
安全 物联网 API
Windows 11 24H2 中文版、英文版 (x64、ARM64) 下载 (2025 年 7 月更新)
Windows 11 24H2 中文版、英文版 (x64、ARM64) 下载 (2025 年 7 月更新)
1733 0
|
NoSQL 测试技术 Redis
Redis批量删除Key的三种方式
Redis批量删除Key是优化数据库性能的重要操作,本文介绍三种高效方法:1) 使用通配符匹配(KEYS/SCAN+DEL),适合不同数据规模;2) Lua脚本实现原子化删除,适用于需要事务保障的场景;3) 管道批量处理提升效率。根据实际需求选择合适方案,注意操作不可逆,建议先备份数据,避免内存溢出或阻塞。
|
开发工具 C++ git
五分钟看懂推送本地项目到 GitHub新手菜鸡
五分钟看懂推送本地项目到 GitHub新手菜鸡