Java寫(xiě)哈希表的完整實(shí)例代碼
一、什么叫哈希表(HashMap)?
哈希表的實(shí)質(zhì)是一種結(jié)合數(shù)組和鏈表優(yōu)勢(shì)的復(fù)合數(shù)據(jù)結(jié)構(gòu),類似于 Java 官方提供的 HashMap 集合類,不同的是它是我們基于哈希函數(shù) + 鏈表解決沖突的思想手動(dòng)實(shí)現(xiàn)的底層存儲(chǔ)結(jié)構(gòu)。因?yàn)槠浔举|(zhì)是 “數(shù)組 + 鏈表” 的組合存儲(chǔ)邏輯,而非原生的數(shù)據(jù)類型,所以需要通過(guò)定義哈希函數(shù)、鏈表節(jié)點(diǎn)、核心操作方法來(lái)賦予其可操作的能力,只有被實(shí)例化為對(duì)象時(shí),才能完成鍵值對(duì)的增刪查等操作。
哈希表以數(shù)組為底層基礎(chǔ)容器,通過(guò)哈希函數(shù)將鍵(Key)映射到數(shù)組的指定索引位置;當(dāng)多個(gè)鍵映射到同一索引時(shí)(哈希沖突),則通過(guò)鏈表將這些沖突的鍵值對(duì)串聯(lián)存儲(chǔ),既保留了數(shù)組隨機(jī)訪問(wèn)的高效性,又解決了數(shù)組固定長(zhǎng)度、沖突存儲(chǔ)的問(wèn)題。
二、定義自定義 HashMap 的方法?
(一)先定義核心組件
1. 節(jié)點(diǎn)類(Node)
訪問(wèn)修飾符 + class + 類名
節(jié)點(diǎn)類是哈希表中存儲(chǔ)鍵值對(duì)的最小單位,需要定義存儲(chǔ)鍵、值的屬性,以及指向下一個(gè)節(jié)點(diǎn)的引用(用于鏈表串聯(lián))。
(1)節(jié)點(diǎn)類的屬性:
理解:節(jié)點(diǎn)的屬性可以看成存儲(chǔ)單個(gè)鍵值對(duì)的核心特征(比如鍵 key、值 value,以及鏈表中下一個(gè)節(jié)點(diǎn)的引用 next)。定義格式:訪問(wèn)修飾符 + 數(shù)據(jù)類型 + 屬性名
(2)節(jié)點(diǎn)類的構(gòu)造方法:
理解:用于初始化節(jié)點(diǎn)的鍵和值,給 next 引用賦默認(rèn)值。
定義格式:訪問(wèn)修飾符 + 類名(參數(shù)類型 + 參數(shù)名,…){屬性賦值…}
2. 鏈表類(LinkList)
訪問(wèn)修飾符 + class + 類名
鏈表類用于解決哈希沖突,存儲(chǔ)數(shù)組同一索引下的所有沖突鍵值對(duì),需要定義鏈表的頭節(jié)點(diǎn)屬性,以及添加、查詢鍵值對(duì)的方法。
(1)鏈表類的屬性:
理解:鏈表的屬性是其核心特征(比如頭節(jié)點(diǎn) head,作為鏈表遍歷的起點(diǎn))。定義格式:訪問(wèn)修飾符 + 數(shù)據(jù)類型 + 屬性名
(2)鏈表類的方法:
理解:鏈表的方法是其核心行為(比如添加 / 覆蓋鍵值對(duì)、根據(jù)鍵查詢值)。
定義格式:訪問(wèn)修飾符 + 返回值類型 + 方法名(參數(shù)類型 + 參數(shù)名,…){方法體…}
3. 哈希表主類(MyHashMap)
訪問(wèn)修飾符 + class + 類名
哈希表主類是對(duì)外提供操作接口的核心類,需要定義存儲(chǔ)鏈表的數(shù)組、數(shù)組默認(rèn)長(zhǎng)度等屬性,以及構(gòu)造方法、哈希函數(shù)、put/get 核心方法。
(1)哈希表的屬性:
理解:哈希表的屬性是其核心特征(比如存儲(chǔ)鏈表的數(shù)組 linkLists、數(shù)組默認(rèn)長(zhǎng)度 len)。
定義格式:訪問(wèn)修飾符 + 數(shù)據(jù)類型 + 屬性名
(2)哈希表的方法:
理解:哈希表的方法是其核心行為(比如哈希函數(shù) hash ()、存儲(chǔ)鍵值對(duì) put ()、查詢值 get ())。
定義格式:訪問(wèn)修飾符 + 返回值類型 + 方法名(參數(shù)類型 + 參數(shù)名,…){方法體…}
class Node {
Object key;
Object value;
Node next;
// 構(gòu)造方法:初始化鍵值對(duì),next默認(rèn)null
public Node(Object key, Object value) {
this.key = key;
this.value = value;
this.next = null;
}
}class LinkList {
// 鏈表頭節(jié)點(diǎn)
private Node head;
// 添加/覆蓋鍵值對(duì):存在相同key則覆蓋value,不存在則新增節(jié)點(diǎn)
public void add(Object key, Object value) {
// 頭節(jié)點(diǎn)為空,直接創(chuàng)建新節(jié)點(diǎn)作為頭節(jié)點(diǎn)
if (head == null) {
head = new Node(key, value);
return;
}
// 遍歷鏈表,查找是否存在相同key
Node current = head;
while (current != null) {
// key相等(處理null key),覆蓋value
if (equals(key, current.key)) {
current.value = value;
return;
}
// 到鏈表尾部,退出循環(huán)
if (current.next == null) {
break;
}
current = current.next;
}
// 無(wú)相同key,在鏈表尾部新增節(jié)點(diǎn)
current.next = new Node(key, value);
}
// 根據(jù)key獲取對(duì)應(yīng)value,無(wú)則返回null
public Object get(Object key) {
Node current = head;
while (current != null) {
// 匹配key(處理null key)
if (equals(key, current.key)) {
return current.value;
}
current = current.next;
}
// 未找到對(duì)應(yīng)key
return null;
}
// 輔助方法:判斷兩個(gè)key是否相等(處理null值)
private boolean equals(Object k1, Object k2) {
if (k1 == null && k2 == null) {
return true;
}
if (k1 == null || k2 == null) {
return false;
}
return k1.equals(k2);
}
}public class MyHashMap {
//定義保存鏈表的數(shù)組
public LinkList[] linkLists;
public static int len = 16;
//自定義長(zhǎng)度
public MyHashMap(int len) {
linkLists = new LinkList[len];
//初始化數(shù)組,每個(gè)位置都創(chuàng)建空鏈表
for(int i=0;i<len;i++){
linkLists[i] = new LinkList();
}
}
//默認(rèn)長(zhǎng)度
public MyHashMap() {
this(len);
}
//put數(shù)據(jù):存儲(chǔ)鍵值對(duì),鍵重復(fù)則覆蓋值
public void put(Object key, Object value) {
//根據(jù)當(dāng)前key,利用哈希函數(shù)計(jì)算位置
int index = hash(key);
//取出對(duì)應(yīng)鏈表,保存鍵值對(duì)(處理重復(fù)鍵覆蓋)
linkLists[index].add(key, value);
}
//get 取出數(shù)據(jù):根據(jù)key獲取對(duì)應(yīng)value,無(wú)則返回null
public Object get(Object key){
int index = hash(key);
return linkLists[index].get(key);
}
//哈希函數(shù)(散列函數(shù)):計(jì)算key在數(shù)組中的索引,處理負(fù)數(shù)哈希值
public int hash(Object key) {
if (key == null) {
return 0; // null鍵固定放在索引0位置
}
int hashCode = key.hashCode();
// 處理負(fù)數(shù)哈希值,保證索引非負(fù)
return (hashCode & 0x7FFFFFFF) % linkLists.length;
}
public static void main(String[] args) {
MyHashMap hm = new MyHashMap();
hm.put("a",10);
hm.put("a",20); // 重復(fù)key,覆蓋值
hm.put("c",null);
hm.put(null, 99);
System.out.println(hm.get("a"));
System.out.println(hm.get("c"));
System.out.println(hm.get(null));
System.out.println(hm.get("d"));
}
}三、自定義 HashMap 核心邏輯解析
1. 哈希函數(shù)的作用
(1)核心功能:將任意類型的鍵(Key)映射為數(shù)組的索引,公式為 (key.hashCode() & 0x7FFFFFFF) % 數(shù)組長(zhǎng)度;(2)關(guān)鍵處理:
null 鍵特殊處理:固定映射到索引 0 位置,符合 Java 官方 HashMap 的設(shè)計(jì);
負(fù)數(shù)哈希值處理:通過(guò) & 0x7FFFFFFF 將哈希值轉(zhuǎn)為正數(shù),避免索引為負(fù)數(shù)的異常。
2. put 方法核心流程
(1)調(diào)用哈希函數(shù)計(jì)算鍵對(duì)應(yīng)的數(shù)組索引;(2)取出該索引位置的鏈表,調(diào)用鏈表的 add 方法;(3)鏈表 add 方法邏輯:
若鏈表為空,直接創(chuàng)建新節(jié)點(diǎn)作為頭節(jié)點(diǎn);
若鏈表非空,遍歷查找是否有相同 key,有則覆蓋 value,無(wú)則在鏈表尾部新增節(jié)點(diǎn)。
3. get 方法核心流程
(1)調(diào)用哈希函數(shù)計(jì)算鍵對(duì)應(yīng)的數(shù)組索引;(2)取出該索引位置的鏈表,調(diào)用鏈表的 get 方法;(3)鏈表 get 方法邏輯:遍歷鏈表匹配 key,匹配成功則返回對(duì)應(yīng) value,無(wú)匹配則返回 null。
四、補(bǔ)充說(shuō)明
- 哈希沖突解決:本文采用鏈地址法(鏈表)解決哈希沖突,這是 Java 官方 HashMap 的核心實(shí)現(xiàn)方式(JDK1.8 后,當(dāng)鏈表長(zhǎng)度超過(guò)閾值會(huì)轉(zhuǎn)為紅黑樹(shù),本文簡(jiǎn)化為純鏈表);
- 邊界處理:兼容 null 鍵和 null 值的存儲(chǔ)、查詢,處理了哈希值為負(fù)數(shù)的異常場(chǎng)景,保證索引合法性;
- 核心特性:實(shí)現(xiàn)了 HashMap 最核心的 “鍵唯一、值可重復(fù)、鍵重復(fù)覆蓋值” 的特性,與官方 HashMap 行為一致。
到此這篇關(guān)于Java寫(xiě)哈希表的文章就介紹到這了,更多相關(guān)Java寫(xiě)哈希表內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot配置使用H2數(shù)據(jù)庫(kù)的簡(jiǎn)單教程
H2是一個(gè)Java編寫(xiě)的關(guān)系型數(shù)據(jù)庫(kù),它可以被嵌入Java應(yīng)用程序中使用,或者作為一個(gè)單獨(dú)的數(shù)據(jù)庫(kù)服務(wù)器運(yùn)行。本文將介紹SpringBoot如何配置使用H2數(shù)據(jù)庫(kù)2021-05-05
Java 實(shí)戰(zhàn)項(xiàng)目之精品養(yǎng)老院管理系統(tǒng)的實(shí)現(xiàn)流程
讀萬(wàn)卷書(shū)不如行萬(wàn)里路,只學(xué)書(shū)上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實(shí)戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+Springboot+Maven+mybatis+Vue+Mysql實(shí)現(xiàn)一個(gè)精品養(yǎng)老院管理系統(tǒng),大家可以在過(guò)程中查缺補(bǔ)漏,提升水平2021-11-11
Jdk1.8 HashMap實(shí)現(xiàn)原理詳細(xì)介紹
這篇文章主要介紹了Jdk1.8 HashMap實(shí)現(xiàn)原理詳細(xì)介紹的相關(guān)資料,需要的朋友可以參考下2016-12-12
Java中的BlockingQueue阻塞隊(duì)列原理以及實(shí)現(xiàn)詳解
這篇文章主要介紹了Java中的BlockingQueue阻塞隊(duì)列原理以及實(shí)現(xiàn)詳解,在最常見(jiàn)的使用到這個(gè)阻塞隊(duì)列的地方,就是我們耳熟能詳?shù)木€程池里面了,作為我們線程池的一大最大參與者,也是AQS的一個(gè)具體實(shí)現(xiàn),需要的朋友可以參考下2023-12-12
java對(duì)象和json的來(lái)回轉(zhuǎn)換知識(shí)點(diǎn)總結(jié)
在本篇文章里小編給大家分享了一篇關(guān)于java對(duì)象和json的來(lái)回轉(zhuǎn)換知識(shí)點(diǎn)總結(jié)內(nèi)容,有興趣的朋友們可以學(xué)習(xí)下。2021-01-01
詳解Java Proxy動(dòng)態(tài)代理機(jī)制
今天給大家?guī)?lái)的是關(guān)于Java的相關(guān)知識(shí),文章圍繞著Java動(dòng)態(tài)代理機(jī)制展開(kāi),文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下2021-06-06
mybatis實(shí)現(xiàn)mapper代理模式的方式
本文向大家講解mybatis的mapper代理模式,以根據(jù)ide值查詢單條數(shù)據(jù)為例編寫(xiě)xml文件,通過(guò)mapper代理的方式進(jìn)行講解增刪改查,分步驟給大家講解的很詳細(xì),對(duì)mybatis mapper代理模式相關(guān)知識(shí)感興趣的朋友一起看看吧2021-06-06

