0

0

Java中HashMap和HashTable的区别和使用

P粉602998670

P粉602998670

发布时间:2025-09-22 13:07:01

|

856人浏览过

|

来源于php中文网

原创

HashMap非线程安全但性能高,允许null键值;HashTable线程安全但性能差,不允许null键值;多线程场景推荐ConcurrentHashMap。

java中hashmap和hashtable的区别和使用

谈及Java集合框架中的

HashMap
HashTable
,很多初学者会觉得它们似乎差不多,毕竟都处理键值对。但深入一点,你会发现它们骨子里是两种不同的设计哲学,这直接决定了它们在不同场景下的表现和适用性。核心来说,
HashTable
是线程安全的、不允许
null
键值,且是遗留类;而
HashMap
则非线程安全、允许
null
键值,是更现代且性能通常更好的选择。

解决方案

理解

HashMap
HashTable
,最直接的切入点就是它们的线程安全性、对
null
键值的处理、以及性能表现。

HashTable
是一个同步的(synchronized)集合,这意味着它的所有公共方法都通过
synchronized
关键字进行修饰,确保了在多线程环境下,同一时间只有一个线程可以访问
HashTable
的任何方法。这自然保证了线程安全,但也带来了显著的性能开销,因为每次操作都需要获取和释放锁。此外,
HashTable
不允许
null
键和
null
值。如果尝试插入
null
,会抛出
NullPointerException
。它的迭代器也不是“快速失败”(fail-fast)的。从继承体系上看,
HashTable
继承自
Dictionary
抽象类,这是Java早期集合框架的一部分,现在已经不推荐使用了。

HashMap
则是一个非同步的(non-synchronized)集合,不保证线程安全。这意味着在多线程环境下,如果不进行外部同步处理,可能会出现数据不一致的问题。但作为交换,
HashMap
在单线程环境下的性能通常比
HashTable
要好得多,因为它避免了同步带来的额外开销。
HashMap
允许且只允许一个
null
键和任意数量的
null
值。它的迭代器是“快速失败”的,这意味着在迭代过程中如果集合被修改(除了迭代器自身的
remove
方法),会立即抛出
ConcurrentModificationException
,这有助于快速发现并发修改问题。
HashMap
实现了
Map
接口,是Java集合框架中
Map
家族的核心成员。

立即学习Java免费学习笔记(深入)”;

简而言之,如果你需要一个线程安全的键值对存储,并且不介意性能上的牺牲,

HashTable
可以工作,但更推荐使用
ConcurrentHashMap
Collections.synchronizedMap
。如果是在单线程环境,或者你能自行处理同步,那么
HashMap
无疑是更优的选择,它提供了更好的性能和更灵活的
null
值处理。

在多线程环境下,我该如何选择HashMap还是HashTable?

这确实是项目开发中一个很实际的问题。我个人在项目里,如果不是特别老旧的遗留代码,几乎不会主动去用

HashTable
为什么?因为它那个全局锁,简直是性能杀手。想象一下,即使是两个线程想访问
HashTable
的不同部分,比如一个线程要
put
一个键值对,另一个线程要
get
另一个键值对,它们都得排队,因为整个
HashTable
都被锁住了。这在并发量大的时候,性能瓶颈会非常明显。

所以,在多线程环境下,我的首选通常是

ConcurrentHashMap
。它是一个专门为并发设计的
Map
实现,通过“分段锁”(Java 7及以前)或者
CAS
操作和
synchronized
块(Java 8及以后)来提供比
HashTable
细粒度得多的锁机制。这意味着不同的线程可以同时操作
Map
的不同部分,大大提高了并发性能。它不仅线程安全,而且性能表现优秀。

如果你只是偶尔需要线程安全,或者你的应用场景对并发性能要求不是那么极致,也可以考虑使用

Collections.synchronizedMap(new HashMap<>())
。这个方法会返回一个线程安全的
Map
视图,它内部的所有操作都会被
synchronized
包裹。虽然它提供了线程安全,但其同步机制
HashTable
类似,也是对整个
Map
进行同步,因此在高并发场景下性能依然不如
ConcurrentHashMap

总结一下:

  • 高并发、高性能需求:
    ConcurrentHashMap
    是你的不二之选。
  • 低并发、简单线程安全需求:
    Collections.synchronizedMap(new HashMap<>())
    是一个可行的方案。
  • 遗留系统兼容或特殊场景:
    HashTable
    可能还会出现,但新代码不建议使用。
// 示例:使用ConcurrentHashMap
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;

public class ConcurrentMapExample {
    public static void main(String[] args) {
        Map concurrentMap = new ConcurrentHashMap<>();
        concurrentMap.put("key1", "value1");
        concurrentMap.put("key2", "value2");

        // 在多线程环境下安全地进行读写操作
        new Thread(() -> {
            concurrentMap.put("thread1_key", "thread1_value");
            System.out.println("Thread 1 added: " + concurrentMap.get("thread1_key"));
        }).start();

        new Thread(() -> {
            System.out.println("Thread 2 getting key1: " + concurrentMap.get("key1"));
        }).start();
    }
}

HashMap允许null键值,这在使用中有什么潜在的陷阱或优势?

HashMap
允许
null
键和
null
值,这确实为开发者提供了很大的灵活性,但也确实埋下了一些坑。

优势:

jQuery small2big图片缩放插件
jQuery small2big图片缩放插件

jQuery small2big图片缩放插件,兼容主流浏览器,php中文网推荐下载! 使用方法: 1、head区域引用文件css和js库 2、在文件中加入html代码, 一个li对应一个图片,images文件夹的图片按数字命名 1.jpg, 2.jpg, ...

下载
  1. 表示缺失或未知:
    null
    值可以很好地表示一个键存在,但它没有关联任何具体的值,或者这个值是未知的。比如,你可以用
    map.put("username", null)
    来表示用户存在,但其昵称信息尚未设置。
  2. 简化逻辑: 在某些情况下,无需为“没有值”的情况设计特殊的占位符对象,直接使用
    null
    可以简化代码逻辑。例如,
    map.get(key)
    返回
    null
    ,可以直接判断是键不存在,还是键存在但值为
    null

潜在的陷阱:

  1. NullPointerException
    风险: 这是最常见的陷阱。当你从
    HashMap
    中获取一个值时,如果键不存在,
    get
    方法会返回
    null
    。如果键存在但其关联的值就是
    null
    get
    方法也会返回
    null
    。这两种情况都返回
    null
    ,这就很麻烦了。如果你不加区分地直接对这个返回的
    null
    引用调用方法,就会触发
    NullPointerException

    Map map = new HashMap<>();
    map.put("name", null); // 键"name"存在,值为null
    // map.get("age") 会返回null,因为键"age"不存在
    
    String name = map.get("name");
    // System.out.println(name.length()); // 这里会抛出NullPointerException
    
    String age = map.get("age");
    // System.out.println(age.length()); // 这里同样会抛出NullPointerException
  2. 语义模糊:

    map.get(key)
    返回
    null
    时,你很难一眼判断是“键不存在”还是“键存在但值为
    null
    ”。这需要额外的检查,比如使用
    containsKey(key)
    来区分。

    if (map.containsKey("name")) {
        // 键"name"存在,值可能是null,也可能不是
        String value = map.get("name");
        if (value == null) {
            System.out.println("键'name'存在,但值为null");
        } else {
            System.out.println("键'name'存在,值为: " + value);
        }
    } else {
        System.out.println("键'name'不存在");
    }
  3. 一个

    null
    键的特殊性:
    HashMap
    只允许一个
    null
    键。如果你多次
    put(null, value)
    ,后面的值会覆盖前面的值。这通常不会造成问题,但如果预期有多个
    null
    键对应不同的值,那就会出错了。

为了避免这些陷阱,我通常会建议在使用

HashMap
时,尤其是在处理从外部或不可信来源获取的数据时,进行充分的
null
检查。Java 8引入的
Optional
类在一定程度上可以帮助我们更优雅地处理
null
值,虽然它不能直接应用于
Map.get()
的返回值,但可以在获取值后进行包装处理。

性能考量:为什么HashMap通常比HashTable快?

HashMap
之所以通常比
HashTable
快,核心原因在于它们的同步机制差异。这不仅仅是“一个同步一个不同步”那么简单,它背后涉及到并发控制的成本。

  1. 同步开销:

    HashTable
    的每个公共方法都被
    synchronized
    关键字修饰。这意味着每次调用
    put()
    get()
    remove()
    等方法时,线程都需要获取一个内部锁(通常是
    HashTable
    实例本身)。获取锁和释放锁本身就是一种开销,即使在单线程环境下,这种机制也依然存在,只是没有竞争而已。一旦进入多线程环境,如果多个线程尝试同时访问
    HashTable
    ,它们就必须排队等待,只有当前持有锁的线程执行完毕并释放锁后,其他线程才能有机会获取锁并执行操作。这种粗粒度的同步(整个对象一把锁)严重限制了并发性能。

  2. 无同步的自由:

    HashMap
    则完全没有内部同步机制。它假定使用者会自行处理并发问题,或者只在单线程环境中使用。因此,
    HashMap
    在执行
    put()
    get()
    等操作时,无需承担任何锁的获取和释放成本。在单线程环境下,这使得它能够以最快的速度执行操作。即使在多线程环境下,如果开发者能通过其他手段(比如外部锁、或者保证特定区域内无并发访问)来确保线程安全,
    HashMap
    的这种“自由”也能带来更高的性能。

  3. 迭代器:

    HashMap
    的迭代器是“快速失败”的,这意味着它在结构性修改(如添加或删除元素,而不是仅仅修改一个现有元素的值)发生时,会迅速抛出
    ConcurrentModificationException
    。这虽然不是直接的性能提升,但它能帮助开发者及早发现并发修改带来的潜在问题,避免了更隐蔽的bug。
    HashTable
    的迭代器不是快速失败的,在并发修改时可能会导致不确定的行为。

  4. 底层实现细节: 虽然两者都基于哈希表原理,但

    HashMap
    在设计上更现代,比如它在Java 8中引入了红黑树(当链表长度超过阈值时),以优化哈希冲突严重时的性能,将最坏情况下的时间复杂度从O(n)降低到O(log n)。
    HashTable
    则没有这样的优化。

所以,当你看到

HashMap
HashTable
的性能对比时,记住,
HashMap
把线程安全的责任交给了开发者,从而换取了更高的执行效率。而
HashTable
则把线程安全的责任揽到了自己身上,但代价是牺牲了性能。这是一个典型的“性能与安全”的权衡。

相关专题

更多
java
java

Java是一个通用术语,用于表示Java软件及其组件,包括“Java运行时环境 (JRE)”、“Java虚拟机 (JVM)”以及“插件”。php中文网还为大家带了Java相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

832

2023.06.15

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

737

2023.07.05

java自学难吗
java自学难吗

Java自学并不难。Java语言相对于其他一些编程语言而言,有着较为简洁和易读的语法,本专题为大家提供java自学难吗相关的文章,大家可以免费体验。

734

2023.07.31

java配置jdk环境变量
java配置jdk环境变量

Java是一种广泛使用的高级编程语言,用于开发各种类型的应用程序。为了能够在计算机上正确运行和编译Java代码,需要正确配置Java Development Kit(JDK)环境变量。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

397

2023.08.01

java保留两位小数
java保留两位小数

Java是一种广泛应用于编程领域的高级编程语言。在Java中,保留两位小数是指在进行数值计算或输出时,限制小数部分只有两位有效数字,并将多余的位数进行四舍五入或截取。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

398

2023.08.02

java基本数据类型
java基本数据类型

java基本数据类型有:1、byte;2、short;3、int;4、long;5、float;6、double;7、char;8、boolean。本专题为大家提供java基本数据类型的相关的文章、下载、课程内容,供大家免费下载体验。

446

2023.08.02

java有什么用
java有什么用

java可以开发应用程序、移动应用、Web应用、企业级应用、嵌入式系统等方面。本专题为大家提供java有什么用的相关的文章、下载、课程内容,供大家免费下载体验。

430

2023.08.02

java在线网站
java在线网站

Java在线网站是指提供Java编程学习、实践和交流平台的网络服务。近年来,随着Java语言在软件开发领域的广泛应用,越来越多的人对Java编程感兴趣,并希望能够通过在线网站来学习和提高自己的Java编程技能。php中文网给大家带来了相关的视频、教程以及文章,欢迎大家前来学习阅读和下载。

16925

2023.08.03

Java 桌面应用开发(JavaFX 实战)
Java 桌面应用开发(JavaFX 实战)

本专题系统讲解 Java 在桌面应用开发领域的实战应用,重点围绕 JavaFX 框架,涵盖界面布局、控件使用、事件处理、FXML、样式美化(CSS)、多线程与UI响应优化,以及桌面应用的打包与发布。通过完整示例项目,帮助学习者掌握 使用 Java 构建现代化、跨平台桌面应用程序的核心能力。

36

2026.01.14

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 2.5万人学习

C# 教程
C# 教程

共94课时 | 6.7万人学习

Java 教程
Java 教程

共578课时 | 46万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号