一、先说结论
HashMap
和 HashTable
都是 Map
接口的实现类。HashMap
采用数组、链表和红黑树的数据结构,非线程安全且无序,查找效率高,初始化默认容量为 2^4,每次扩容为原来的 2 倍;而 HashTable
采用数组和链表的数据结构,线程安全,键值均不允许为 null
,默认初始大小为 11,每次扩充为原来的 2n+1。
二、什么是Hash(哈希)?
哈希(Hash)是一种通过特定算法将输入数据转换为固定长度的输出数据的过程。这个固定长度的输出数据称为哈希值或哈希码。哈希算法的主要特征包括:
-
固定长度输出:无论输入数据的大小或长度,哈希函数总是生成固定长度的哈希值。例如,SHA-256 哈希函数总是生成 256 位长的哈希值。
-
唯一性:理想情况下,哈希函数对不同的输入应该产生不同的哈希值,即低碰撞性。尽管存在哈希碰撞(不同的输入产生相同的哈希值),好的哈希算法尽量减少碰撞的发生。
-
不可逆性:从哈希值不能反推出原始输入数据。哈希函数是单向的,这意味着你不能通过哈希值轻易地得到原始数据。
-
高效性:哈希函数的计算应该足够快,即使对于大的数据集也应高效。
哈希在计算机科学和信息技术中有广泛的应用,例如:
- 数据检索:在数据结构中,如哈希表,用于快速查找、插入和删除操作。
- 数据完整性:在文件传输中,使用哈希值来验证数据的完整性。
- 密码学:用于生成加密哈希,用于密码存储、数字签名等。
HashMap
和 HashTable
都是基于哈希(Hash)原理的 Map
接口实现类,它们利用哈希函数将键(Key)映射到表中的位置,从而实现快速的查找、插入和删除操作。
三、HashMap
HashMap
是 Java 集合框架中的一个重要实现类,广泛用于存储键值对。利用哈希函数、高效的冲突处理机制和动态扩容来实现快速的数据存储和查找,是 Java 中最常用的键值存储结构之一。
HashMap
的数据结构
- 数组:
HashMap
的底层是一个数组,每个数组元素称为一个桶(bucket)。 - 链表: 当多个键的哈希值相同时,这些键会被存储在同一个桶中,形成一个链表。
- 红黑树: 当某个桶中的链表长度超过一定阈值(默认是8)时,链表将被转换为红黑树,以提高查找效率。
HashMap
的哈希实现
1. 哈希函数
HashMap
使用键的hashCode
方法计算哈希码,然后通过扰动函数(perturbation function)进一步混合哈希码的高位和低位,以减少冲突。static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
这段代码将键的
hashCode
值与其高16位进行异或运算,以混合哈希码的高低位。2. 确定桶的位置
HashMap
使用(n - 1) & hash
来确定键值对在哈希表中的位置,其中n
是数组的长度。
int index = (n - 1) & hash;
3. 处理冲突
- 初始时,
HashMap
采用链表来处理冲突。当数组长度已经扩容到64且该桶中的链表长度超过8时,该链表会转换为红黑树(当长度低于6时变回链表)
Node<K,V> e;
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
4. 扩容机制
- 当
HashMap
中的元素个数超过容量和负载因子(默认为0.75)的乘积时,HashMap
会进行扩容,新的容量是旧容量的两倍。
void resize() {
Node<K,V>[] oldTable = table;
int oldCap = oldTable.length;
int newCap = oldCap << 1;
Node<K,V>[] newTable = (Node<K,V>[])new Node[newCap];
transfer(newTable);
}
5. HashMap
的特性
- 非线程安全:
HashMap
是非线程安全的,在多线程环境下使用时需要通过外部同步机制保证线程安全。 - 无序:
HashMap
中的键值对是无序的,存储顺序不保证与插入顺序一致。 - 允许 null:
HashMap
允许一个null
键和多个null
值。
四、HashTable
Hashtable
是 Java 集合框架中的一个重要 Map
实现类,用于存储键值对。它提供线程安全的键值存储功能,具有同步机制,并且不允许 null
键或值。
Hashtable
的数据结构
- 数组:
Hashtable
的底层也是一个数组,用于存储键值对。 - 链表: 当多个键的哈希值相同时,这些键值对会被存储在同一个桶中,形成一个链表。
Hashtable
的哈希实现
1. 哈希函数
Hashtable
使用键的hashCode
方法计算哈希码,然后通过对数组长度取模的方式确定在哈希表中的位置。
int index = (hash & 0x7FFFFFFF) % table.length;
这里的哈希码通过与 0x7FFFFFFF
进行与操作,确保哈希码为非负值,然后对表的长度取模,以确定桶的位置。
2. 处理冲突
Hashtable
使用链表来处理冲突。当多个键的哈希值相同时,它们会被存储在同一个桶中,通过链表链接。
3. Hashtable
的特性
- 线程安全:
Hashtable
是线程安全的,大多数方法都是同步的。这使得它在多线程环境下安全,但也可能因为同步开销而影响性能。 - 不允许
null
键或值: 与HashMap
不同,Hashtable
不允许键或值为null
。如果尝试插入null
键或值,将抛出NullPointerException
。 - 初始容量和扩容: 默认的初始容量为 11,每次扩容时,容量变为原来的 2n+1。