哈希(Hash)是一种将数据映射到固定大小值的过程,通常是通过哈希函数实现的。哈希函数接收输入数据(例如字符串、文件或其他类型的数据),然后生成一个唯一的、固定长度的值,这个值被称为哈希值或哈希码。
哈希表(Hash Table)是一种数据结构,用于实现高效的数据存储和检索。它使用哈希函数将键(key)映射到表中的特定位置,从而支持快速的插入、删除和查找操作。这种结构的核心在于:
哈希表利用哈希函数将数据项映射到数组的索引位置,因此数据的检索时间复杂度接近常数时间 O(1)。这与传统的数据结构(如链表或数组)的线性查找相比,速度显著提高。
哈希函数将输入数据(键)转换为固定大小的哈希值,然后根据该哈希值确定数据存储的位置。良好的哈希函数具有以下特点:
均匀分布:哈希函数能够将数据均匀分布到哈希表的各个位置,减少了冲突(即不同的键映射到相同的位置)的概率。
快速计算:哈希函数的计算过程通常非常高效,能够快速将键转换为哈希值。
键:键是用于唯一标识哈希表中每个元素的标识符。它用于计算哈希值,并决定元素在哈希表中的位置。
值:值是与键关联的数据或信息。它是哈希表中实际存储的数据部分。
哈希值:哈希值(Hash Value)是哈希函数(Hash Function)将输入数据(如字符串、对象、文件等)映射到固定大小的值(通常是一个整数)后的结果。
哈希冲突(Hash Collision)是指不同的输入数据经过哈希函数处理后,得到相同的哈希值或哈希索引的情况。由于哈希函数将多个可能的输入映射到有限的输出空间(即哈希表的桶或槽),冲突在实际应用中是不可避免的。
哈希冲突会导致:
性能下降:冲突会增加查找、插入和删除操作的复杂性。
额外的存储开销:需要额外的存储来管理冲突处理结构(如链表)。
性能不稳定:冲突处理策略的效率会影响哈希表的整体性能。
1.链地址法:每个哈希表的槽位实际上是一个链表(或其他数据结构),所有映射到同一槽位的键值对都会被存储在这个链表中。当冲突频繁时,链表可能变得很长,查找效率可能下降到 O(n)。
2.开放地址法:在开放寻址法中,当冲突发生时,哈希表会尝试在哈希表中寻找其他位置来存储冲突的元素。
创建HashMap:首先,你需要导入 java.util.HashMap
类,并创建一个 HashMap.
import java.util.HashMap;
public class HashMapExample {
public static void main(String[] args) {
// 创建一个 HashMap 实例
HashMap map = new HashMap<>();
}
}
添加键值对:可以使用 put
方法将键值对添加到 HashMap
中
map.put("Alice", 30);
map.put("Bob", 25);
map.put("Charlie", 35);
获取值:可以使用 get
方法根据键获取对应的值
Integer ageOfAlice = map.get("Alice");
System.out.println("Alice's age: " + ageOfAlice); // 输出: Alice's age: 30
检查键值对:可以使用 containsKey
和 containsValue
方法检查 HashMap
是否包含某个键或值
boolean hasBob = map.containsKey("Bob");
boolean hasAge40 = map.containsValue(40);
System.out.println("Has Bob: " + hasBob); // 输出: Has Bob: true
System.out.println("Has age 40: " + hasAge40); // 输出: Has age 40: false
删除键值对:可以使用 remove
方法根据键删除一个键值对
map.remove("Bob");
遍历HashMap:遍历 HashMap
中的所有键值对,可以使用 entrySet
、keySet
或 values
方法:
for (HashMap.Entry entry : map.entrySet()) {
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
获取Map大小
int size = map.size();
System.out.println("Size of map: " + size);
清空Map
map.clear();
HashSet
是一个不允许重复元素的集合,它实现了 Set
接口。它的底层实现是基于哈希表;
构造方法:
HashSet set = new HashSet<>();
添加元素
set.add("Element");
删除元素
set.remove("Element");
检查是否包含元素
boolean contains = set.contains("Element");
遍历集合
for (String element : set) {
System.out.println(element);
}
获取集合大小
int size = set.size();
清空集合
set.clear();
HashMap与HashTable:
HashTable
和 HashMap
都实现了类似的哈希表数据结构,但它们在实现细节、线程安全机制以及性能方面有所不同。
HashTable:
HashTable
使用一个数组来存储数据。每个数组元素是一个链表的头部(Hashtable.Entry
)当发生哈希冲突(即不同的键被映射到相同的数组索引),HashTable
会将所有冲突的键值对存储在这个链表中。
HashTable
的方法是同步的(synchronized
),这意味着每个操作(如 put
和 get
)都被同步,以保证线程安全。具体实现是在每个方法的开头加上 synchronized
关键字,锁定整个哈希表对象。
HashMap:
HashMap
使用一个数组来存储数据。每个数组元素是一个链表的头(HashMap.Entry
)。从 Java 8 开始,当链表长度超过一定阈值时,HashMap
会将链表转换为红黑树,以提高性能(特别是在链表很长的情况下)。
HashMap
的方法不是同步的,因此在多线程环境中访问 HashMap
时,需要手动同步。如果多个线程同时修改 HashMap
,可能会导致数据不一致。
主要区别总结
同步机制:
HashTable
:方法同步,保证线程安全但性能较低。HashMap
:方法不同步,适用于单线程或需要外部同步的环境,性能较高。红黑树优化:
HashMap
:引入了链表到红黑树的转换,以优化长链表的性能。HashTable
没有这一优化。支持 null:
HashTable
:不允许 null 键或 null 值。HashMap
:允许一个 null 键和多个 null 值。扩容机制:
HashMap
在扩容时,哈希表的大小会翻倍,并重新计算元素的位置。HashTable
的扩容机制类似,但初始容量和负载因子不同。 HashMap
提供了更现代和高效的功能。因此,在大多数情况下,HashMap
是比HashTable
更优选的实现。