HashMap详解+简单手写实现(哈希表)
1. 什么是 HashMap?
HashMap是Java集合框架中的一种数据结构,它实现了Map接口,用于存储键值对(Key-Value Pair)。HashMap允许null键和null值,并且不保证元素的顺序。
---
2. HashMap 的工作原理
2.1 内部结构
- 数组 + 链表/红黑树:HashMap内部使用一个数组(称为table)来存储数据,每个数组元素是一个链表或红黑树的头节点。
- 哈希函数:通过哈希函数将键(Key)映射到数组的索引位置。
2.2 插入数据
- 计算哈希值:使用键的hashCode()方法计算哈希值。
- 计算索引:通过哈希值和数组长度计算索引位置。
- 处理冲突:如果索引位置已经有元素,则通过链表或红黑树处理冲突。
- 插入数据:将键值对插入到链表或红黑树中。
2.3 查找数据
- 计算哈希值:使用键的hashCode()方法计算哈希值。
- 计算索引:通过哈希值和数组长度计算索引位置。
- 查找数据:在链表或红黑树中查找键值对。
2.4 扩容机制
- 负载因子:HashMap有一个负载因子(默认0.75),当元素数量超过容量 * 负载因子时,HashMap会进行扩容。
- 扩容过程:创建一个新的数组,将旧数组中的元素重新哈希到新数组中。
---
3. HashMap 的特点
3.1 优点
- 快速查找:通过哈希函数,HashMap可以在平均O(1)的时间复杂度内查找元素。
- 灵活:允许null键和null值。
3.2 缺点
- 无序:HashMap不保证元素的顺序。
- 线程不安全:HashMap不是线程安全的,多线程环境下需要使用ConcurrentHashMap。
---
4. 常见问题
4.1 HashMap 和 Hashtable 的区别?
- HashMap:允许null键和null值,线程不安全。
- Hashtable:不允许null键和null值,线程安全。
4.2 HashMap 的负载因子为什么是0.75?
- 负载因子:0.75是时间和空间的一个平衡点,既不会浪费太多空间,也不会导致频繁扩容。
4.3 HashMap 如何处理哈希冲突?
- 链表:Java 8之前,HashMap使用链表处理冲突。
- 红黑树:Java 8之后,当链表长度超过8时,HashMap会将链表转换为红黑树,提高查找效率。
---
5. 进一步优化与迭代方向
- 使用合适的初始容量:根据预估的元素数量设置初始容量,减少扩容次数。
- 选择合适的负载因子:根据实际需求调整负载因子,平衡时间和空间。
- 线程安全:在多线程环境下使用ConcurrentHashMap,避免线程安全问题。
6.手写一个哈希表
class HashNode<K,V> {
//定义哈希表的节点类
K key;
V value;
HashNode<K,V> next;//用于处理哈希冲突的链表
public HashNode(K key, V value){
this.key = key;
this.value = value;
this.next = null;
}
public static void main(String[] args) {
MyHashMap<String, Integer> map = new MyHashMap<>();
map.put("Apple", 1);
map.put("Banana", 2);
map.put("Orange", 3);
System.out.println(map.get("Banana")); // 输出: 2
map.remove("Banana");
System.out.println(map.get("Banana")); // 输出: null
System.out.println(map.size()); // 输出: 2
}
}
//计算键的哈希值
class MyHashMap<K,V> {
private static final int DEFAULT_CAPACITY = 16;//默认容量
private static final float LOAD_FACTOR = 0.75f; // 负载因子
private HashNode<K, V>[] table;//哈希表数组
private int size;//当前元素数量
public MyHashMap(){
table = new HashNode[DEFAULT_CAPACITY];
size = 0;
}
//计算键的哈希值
private int hash(K key){
return key == null ? 0 :Math.abs(key.hashCode() % table.length);
}
//插入键值对
public void put(K key,V value){
int index = hash(key);
HashNode<K,V> node = table[index];
//遍历链表,检查是否存在相同的键
while (node != null){
if(node.key.equals(key)){
node.value = value;//更新值
return;
}
node = node.next;
}
//插入到新节点到链表头部
HashNode<K,V> newNode = new HashNode<>(key,value);
newNode.next = table[index];
table[index] = newNode;
size++;
//检查是否需要扩容
if ((float) size/table.length > LOAD_FACTOR){
resize();
}
}
//查找键相对应的值
public V get(K key){
int index = hash(key);
HashNode<K,V> node = table[index];
//遍历链表,查找键
while (node != null){
if (node.key.equals(key)){
return node.value;
}
node = node.next;
}
return null;//未找到
}
//删除键值对
public void remove(K key){
int index = hash(key);
HashNode<K,V> node = table[index];
HashNode<K,V> prev = null;
//遍历链表,查找键
while (node != null){
if(node.key.equals(key)){
if(prev == null){
table[index] = node.next;//删除链表头部
}else {
prev.next = node.next;//删除链表的中间或尾部
}
size--;
return;
}
prev = node;
node = node.next;
}
}
//扩容哈希表
private void resize(){
int newCapacity = table.length*2;
HashNode<K,V>[] newTable = new HashNode[newCapacity];
//重新载入哈希所有元素
for (HashNode<K,V> node : table){
while(node != null){
int newIndex = hash(node.key);
HashNode<K,V> next = node.next;
node.next = newTable[newIndex];
newTable[newIndex] = node;
node = next;
}
}
table = newTable;
}
//获取当前元素数量
public int size(){
return size;
}
}