Java中HashMap vs Hashtable:谁更胜一筹?深入底层一探究竟
如果你曾经思考过:“HashMap和Hashtable到底有什么区别?”——你并不孤单。这是Stack Overflow上最热门的问题之一,而且名副其实。这两个类都以“键-值”对的形式存储数据,都使用哈希技术,但它们却生活在不同的世界里。一个快速而现代,另一个古老、可靠但略显笨重。让我们深入它们的“引擎盖下”,一探究竟。
在本文中,我们不仅会列举它们的区别。我们将理解内部机制的工作原理,为什么一个类允许null而另一个不允许,以及何时应该使用ConcurrentHashMap来替代它们两者。
历史简介
Hashtable早在Java 1.0版本中就出现了。它是最早的集合类之一,并且从一开始就是同步的。在当时,多线程并不常见,但Java开发者决定以防万一。后来,在Java 2(1.2)中,Collections框架推出了新的集合,其中就包括HashMap。它是非同步的,但速度要快得多。从那时起,Hashtable就被认为是遗留(legacy)类,尽管它从未从语言中被移除。
主要区别:同步与线程
第一个也是最重要的区别是同步。Hashtable的方法(例如put()、get()、remove())都使用了synchronized关键字标记。这意味着两个线程不能同时在同一个Hashtable对象上执行同一个方法。这听起来像是一个优势,但在实践中,它变成了一个性能瓶颈。
// Hashtable — 所有方法都是同步的Hashtable<String, String> table = new Hashtable<>();table.put("key1", "value1"); // 线程安全,但在高并发下速度慢
// HashMap — 非同步HashMap<String, String> map = new HashMap<>();map.put("key1", "value1"); // 速度快,但非线程安全如果你在单线程应用(大多数情况如此)中工作,HashMap会运行得更快,因为它不需要花费时间获取和释放锁。如果你需要线程安全,最好不要使用Hashtable,而是使用ConcurrentHashMap(从Java 5开始提供)。它使用更细粒度的锁(分段锁或桶级锁),在多线程环境中运行速度要快得多。
Null,null,还是null
第二个重要区别是对null的处理。HashMap允许使用null作为键(只能有一个),也允许作为值(可以有任意多个)。而Hashtable如果尝试传入null,则会抛出NullPointerException。
HashMap<String, String> map = new HashMap<>();map.put(null, "nullKey"); // 正常map.put("key", null); // 正常System.out.println(map.get(null)); // "nullKey"
Hashtable<String, String> table = new Hashtable<>();// table.put(null, "value"); // NullPointerException!// table.put("key", null); // NullPointerException!为什么会这样?这完全取决于哈希的工作原理。在HashMap中,对于null键,会使用一个特殊的哈希码0,并将其放入第0个桶(bucket)中。而Hashtable使用旧的机制,对null调用hashCode()会导致错误。此外,Hashtable不支持null值,因为这会破坏同步操作的逻辑。
内部结构:哈希表底层如何工作
为了更深入地理解差异,让我们看看内部。HashMap和Hashtable都基于哈希表。这是一个“桶”数组,每个桶是一个链表(从Java 8开始,也可能是红黑树)。当你添加