数组优点在于查找和访问容易,通过下标就可以直接访问,链表的优点在于增删元素容易,只要改变指针指向就能达到增删的效果,而哈希表就集合了数组和链表的优点,在数组中存入链表,这样既能方便访问,又能方便增添和删除元素。
1.创建一个链表类,作为内部类,实现键值对的增删逻辑,这里使用了泛型,优点在于可以存储任意类型的键和值,不需要在获取值时进行强制类型转换,编译器会检查类型一致性,避免类型错误
package ZFile.HashMapV1;
//定义一个泛型链表类
class LinkList<K, V> {
private class Node {
K key;
V value;
Node next;
//定义内部节点,包括键,值,指向下一个结点的指针
Node(K key, V value) {
this.key = key;
this.value = value;
this.next = null;
}
}
private Node head;
public LinkList() {
head = null;
}
//链表添加键值对逻辑
public void add(K key, V value) {
Node newNode = new Node(key, value);
if (head == null) {
head = newNode;//头结点为空,新节点作为头结点
} else {
Node temp = head;//临时节点
Node prev = null;//存临时节点的前一个结点
while (temp!= null) {
// 检查 temp.key 和 key 是否相等 同为 null 或同为某值
if (temp.key == null && key == null) {
temp.value = value;
return;
} else if (temp.key.equals(key)) {
temp.value = value;
return;
}
prev = temp;
temp = temp.next;
}
// 如果不存在相同 key 的节点,添加到末尾
prev.next = newNode;
}
}
public V getval(K key) {
Node temp = head;
while (temp!= null) {
// 检查 temp.key 为 null
if (temp.key == null) {
if (key == null) {
return temp.value;
}
} else if (temp.key.equals(key)) {
return temp.value;
}
temp = temp.next;
}
return null;
}
}
2.定义一个哈希表,设定一个数组初始长度为16(参考内置哈希表数组长度)
// 自定义哈希表
public class MHashMap<K, V> {
// 保存链表的数组
public LinkList<K, V>[] linkArr;
public static int len = 16;
// 初始数组 自定义初始长度
public MHashMap(int len) {
linkArr = new LinkList[len];
// 初始化
for (int i = 0; i < len; i++) {
linkArr[i] = new LinkList<>();
}
}
// 默认长度
public MHashMap() {
this(len);
}
3.实现键值对的输入put,获取get,存入方法运用到自定义哈希函数
// put
public void put(K key, V value) {
// 计算保存位置
int index = hash(key);
linkArr[index].add(key, value);
}
// get
public V get(K key) {
int index = hash(key);
return linkArr[index].getval(key);
}
// 哈希函数(散列函数)
public int hash(K key) {
if (key == null) {
return 0; //key == null 直接返回 0(固定存储在 linkArr[0])
}
return Math.abs(key.hashCode())%len;
}
}
4.测试输出
package ZFile.HashMapV1;
public class Hash {
public static void main(String[] args) {
MHashMap<String, Integer> hm = new MHashMap<>();
hm.put("b", 10);
hm.put("a", 10);
hm.put("a", 20);
hm.put("hello", 29);
hm.put("world", 99);
hm.put(null, 30);
System.out.println(hm.get("a"));
System.out.println(hm.get("b"));
System.out.println(hm.get("hello"));
System.out.println(hm.get("world"));
System.out.println(hm.get(null));
}
}
6.结果展示

7.不足与优化方向:
a.使用%len取模方法确保索引在 [0, len-1] 范围内,但是对于较大的哈希值可能会丢弃高位,产生哈希冲突,可以参考内置哈希函数采用高位扰动,h >>> 16:将 h 无符号右移 16 位(高位补 0),相当于取 h 的高 16 位。 h ^ (h >>> 16):将 高 16 位和低 16 位进行异或(XOR),使得高位信息能影响低位。
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
b.数组没有进行动态扩容,在 HashMap 中,扩容发生在
if (++size > threshold) {
resize(); // 触发扩容
}
//size当前键值对数量。
//threshold扩容阈值 = capacity * loadFactor(如默认 16 * 0.75 = 12)
c.当哈希冲突严重(很多个值对应同一个key,使得链表过长,查找时间复杂度退化为 O(n))没有做处理,处理方法如使用红黑树, 红黑树是一种 自平衡的二叉搜索树(BST),满足以下规则:
节点是红色或黑色。根节点是黑色。所有叶子节点(NIL)是黑色。红色节点的子节点必须是黑色(不能有连续红色节点)。从任一节点到其叶子节点的路径包含相同数量的黑色节点(黑高平衡)
1771

被折叠的 条评论
为什么被折叠?



