Bootstrap

JAVA面试题:HashMap和HashTable的区别

一、先说结论

        HashMapHashTable 都是 Map 接口的实现类。HashMap 采用数组、链表和红黑树的数据结构,非线程安全且无序,查找效率高,初始化默认容量为 2^4,每次扩容为原来的 2 倍;而 HashTable 采用数组和链表的数据结构,线程安全,键值均不允许为 null,默认初始大小为 11,每次扩充为原来的 2n+1。

二、什么是Hash(哈希)?

哈希(Hash)是一种通过特定算法将输入数据转换为固定长度的输出数据的过程。这个固定长度的输出数据称为哈希值或哈希码。哈希算法的主要特征包括:

  1. 固定长度输出:无论输入数据的大小或长度,哈希函数总是生成固定长度的哈希值。例如,SHA-256 哈希函数总是生成 256 位长的哈希值。

  2. 唯一性:理想情况下,哈希函数对不同的输入应该产生不同的哈希值,即低碰撞性。尽管存在哈希碰撞(不同的输入产生相同的哈希值),好的哈希算法尽量减少碰撞的发生。

  3. 不可逆性:从哈希值不能反推出原始输入数据。哈希函数是单向的,这意味着你不能通过哈希值轻易地得到原始数据。

  4. 高效性:哈希函数的计算应该足够快,即使对于大的数据集也应高效。

哈希在计算机科学和信息技术中有广泛的应用,例如:

  • 数据检索:在数据结构中,如哈希表,用于快速查找、插入和删除操作。
  • 数据完整性:在文件传输中,使用哈希值来验证数据的完整性。
  • 密码学:用于生成加密哈希,用于密码存储、数字签名等。

HashMapHashTable 都是基于哈希(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。

 

 

 

;