在 Java 中,HashMap
和 HashSet
都是非常常用的集合类。尽管它们在使用上有些相似,但它们的内部实现和应用场景有明显的区别。本文将深入解析 HashMap
和 HashSet
的实现细节,并通过源码分析和实际案例来详细说明它们的不同。
基本概念
HashMap
HashMap
是 Java 集合框架中的一个类,它实现了 Map
接口。它用于存储键值对,并允许快速地根据键检索值。它的主要特点包括:
- 键值对存储:每个元素都是一个键值对。
- 允许一个 null 键和多个 null 值。
- 无序:元素没有特定的顺序。
HashSet
HashSet
是 Java 集合框架中的一个类,它实现了 Set
接口。它用于存储唯一的对象,并且不允许存储重复的元素。它的主要特点包括:
- 唯一性:集合中的每个元素都是唯一的。
- 不允许存储 null 元素(在某些情况下)。
- 无序:元素没有特定的顺序。
底层实现
HashSet 底层基于 HashMap 实现
HashSet
的实现非常简单,因为它直接利用了 HashMap
的内部机制。HashSet
内部维护了一个 HashMap
实例,并且所有操作都是通过这个 HashMap
实现的。下面是 HashSet
的部分源码:
java
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable {
private transient HashMap<E,Object> map;
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
public HashSet() {
map = new HashMap<>();
}
public boolean add(E e) {
return map.put(e, PRESENT) == null;
}
// Other methods are omitted for brevity
}
从上面的源码可以看出,HashSet
通过一个 HashMap
实例 map
来存储元素。HashSet
的 add
方法实际上是调用 HashMap
的 put
方法来实现的,只不过它将所有的值都映射到一个固定的对象 PRESENT
上。
HashMap 的内部实现
HashMap
的实现相对复杂,它通过一个数组和链表的结合来实现。主要的数据结构如下:
- 数组:用于存储
HashMap
的桶(bucket)。 - 链表:用于处理哈希冲突(即不同的键计算出的哈希值相同)。
以下是 HashMap
的核心部分源码:
java
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
// Initial capacity and load factor
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// The array of bins.
transient Node<K,V>[] table;
// Initialize the table
void resize() {
...
}
// Hash function
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
// Add a new entry
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
...
}
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
// Other methods are omitted for brevity
}
在上面的代码中,HashMap
通过 table
数组来存储桶,每个桶可能包含多个节点(通过链表链接)。哈希函数 hash
用于计算键的哈希值,而 putVal
方法用于将键值对添加到 HashMap
中。
具体区别
接口实现
HashMap
实现了Map
接口,存储的是键值对。HashSet
实现了Set
接口,存储的是对象。
存储结构
HashMap
存储的是键值对,允许一个 null 键和多个 null 值。HashSet
仅存储对象,不允许存储重复的对象。
添加元素
HashMap
使用put
方法添加元素。HashSet
使用add
方法添加元素。
哈希计算
HashMap
使用键来计算哈希值。HashSet
使用对象本身来计算哈希值。
实际案例及应用场景
HashMap 应用场景
- 快速查找:例如,在实现缓存系统时,使用
HashMap
可以快速地根据键检索到缓存的值。 - 统计频率:在文本处理或数据分析中,使用
HashMap
可以统计单词或其他元素的频率。
HashSet 应用场景
- 去重:在处理数据时,使用
HashSet
可以快速去重,例如从一个列表中去除重复的元素。 - 集合操作:
HashSet
可以用于实现集合的交集、并集等操作。