0

0

EnumSet和EnumMap在Java中的应用

P粉602998670

P粉602998670

发布时间:2025-09-22 10:25:01

|

985人浏览过

|

来源于php中文网

原创

EnumSet和EnumMap专为枚举设计,利用位向量和数组索引实现高效存储与访问,相比HashSet和HashMap避免了哈希开销,提升性能与内存效率,适用于权限、状态、配置等场景。

enumset和enummap在java中的应用

Java里,当我们处理枚举类型(Enum)的集合或映射时,

EnumSet
EnumMap
简直是为它们量身定做的利器。它们不仅仅是
Set
Map
接口的特殊实现,更是在性能、内存效率和类型安全上,针对枚举特性做到了极致优化。在我看来,如果你还在用
HashSet
HashMap
,那真是错失了Java提供的一份“隐藏福利”。它们的存在,就是为了让你的代码在处理枚举时,既优雅又高效。

解决方案

EnumSet
EnumMap
的核心应用场景,说白了,就是替代那些泛型集合,当你的集合元素或映射键是枚举类型时。

EnumSet:枚举的集合,高效且安全

想象一下,你需要表示一个用户拥有哪些权限,而这些权限都是枚举类型,比如

Permission.READ
,
Permission.WRITE
,
Permission.READ_WRITE
。你可能会本能地想到
HashSet
。但
EnumSet
在这里是更好的选择。

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

它是一个抽象类,提供了几种静态工厂方法来创建具体实例,比如

EnumSet.noneOf(Permission.class)
创建一个空集,
EnumSet.allOf(Permission.class)
包含所有枚举常量,或者
EnumSet.of(Permission.READ, Permission.WRITE)

EnumSet
的强大之处在于其内部实现。它不是通过哈希表来存储元素的,而是利用了枚举类型固定且有限的特性,将每个枚举常量映射到一个位(bit)上。这意味着,一个
EnumSet
实例实际上就是一个或几个
long
类型的位向量。添加、删除、检查元素是否存在,这些操作都变成了位运算,效率极高,几乎是O(1)的常数时间。而且,内存占用也极小。

public enum Permission {
    READ, WRITE, DELETE, EXECUTE, ADMIN
}

// 示例:用户权限管理
public class UserPermissions {
    private EnumSet permissions;

    public UserPermissions(EnumSet initialPermissions) {
        this.permissions = initialPermissions;
    }

    public void addPermission(Permission p) {
        permissions.add(p);
        System.out.println("Added " + p + ". Current permissions: " + permissions);
    }

    public void removePermission(Permission p) {
        permissions.remove(p);
        System.out.println("Removed " + p + ". Current permissions: " + permissions);
    }

    public boolean hasPermission(Permission p) {
        return permissions.contains(p);
    }

    public static void main(String[] args) {
        EnumSet defaultPermissions = EnumSet.of(Permission.READ, Permission.WRITE);
        UserPermissions user = new UserPermissions(defaultPermissions);

        System.out.println("Initial permissions: " + user.permissions); // Output: [READ, WRITE]
        user.addPermission(Permission.DELETE); // Output: Added DELETE. Current permissions: [READ, WRITE, DELETE]
        System.out.println("Has EXECUTE permission? " + user.hasPermission(Permission.EXECUTE)); // Output: false
        user.removePermission(Permission.WRITE); // Output: Removed WRITE. Current permissions: [READ, DELETE]

        EnumSet allAdminPermissions = EnumSet.allOf(Permission.class);
        System.out.println("All admin permissions: " + allAdminPermissions); // Output: [READ, WRITE, DELETE, EXECUTE, ADMIN]
    }
}

EnumMap:枚举键的映射,性能卓越

EnumSet
异曲同工,
EnumMap
则是为那些以枚举常量作为键的映射而生。当你需要根据枚举类型来存储一些配置、描述或者其他数据时,
EnumMap
HashMap
更加合适。

EnumMap
的内部实现,同样巧妙地利用了枚举的
ordinal()
方法。每个枚举常量都有一个从0开始的序号。
EnumMap
内部维护一个数组,数组的索引就是枚举常量的
ordinal()
值。这样一来,根据枚举常量查找对应的值,就变成了数组的直接索引访问,同样是O(1)的常数时间操作,无需哈希计算和处理冲突。

public enum TrafficLight {
    RED("Stop"),
    YELLOW("Prepare to stop"),
    GREEN("Go");

    private final String description;

    TrafficLight(String description) {
        this.description = description;
    }

    public String getDescription() {
        return description;
    }
}

// 示例:交通灯状态管理
public class TrafficLightSystem {
    private EnumMap durationMap; // 存储每个灯亮的时长

    public TrafficLightSystem() {
        durationMap = new EnumMap<>(TrafficLight.class);
        durationMap.put(TrafficLight.RED, 60);
        durationMap.put(TrafficLight.YELLOW, 5);
        durationMap.put(TrafficLight.GREEN, 45);
    }

    public int getDuration(TrafficLight light) {
        return durationMap.get(light);
    }

    public void updateDuration(TrafficLight light, int newDuration) {
        durationMap.put(light, newDuration);
        System.out.println("Updated " + light + " duration to " + newDuration + " seconds.");
    }

    public static void main(String[] args) {
        TrafficLightSystem system = new TrafficLightSystem();
        System.out.println("Red light duration: " + system.getDuration(TrafficLight.RED) + " seconds."); // Output: 60 seconds.
        system.updateDuration(TrafficLight.YELLOW, 7); // Output: Updated YELLOW duration to 7 seconds.
        System.out.println("Yellow light new duration: " + system.getDuration(TrafficLight.YELLOW) + " seconds."); // Output: 7 seconds.

        // 遍历EnumMap
        for (EnumMap.Entry entry : system.durationMap.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue() + " seconds (" + entry.getKey().getDescription() + ")");
        }
    }
}

简单来说,当你的集合或映射的“关键”是枚举时,

EnumSet
EnumMap
应该成为你的首选。它们利用了枚举的本质特性,提供了无与伦比的性能和内存效率,同时保持了Java集合框架的良好接口兼容性。

EnumSet为何比HashSet更高效,以及其内部实现原理?

这个问题,其实是很多Java开发者在初次接触

EnumSet
时都会思考的。我个人觉得,理解其背后的原理,能让你在实际开发中做出更明智的选择。

HashSet
的工作方式,是基于哈希表(hash table)。当你向
HashSet
中添加一个枚举常量时,它会计算该枚举常量的哈希码(
hashCode()
方法),然后根据哈希码找到对应的“桶”(bucket),将元素存入。查找、删除也都是类似的过程。这里面涉及到哈希码的计算、潜在的哈希冲突解决(链表或红黑树),这些操作虽然在平均情况下是O(1),但在最坏情况下,如果哈希函数设计不当或冲突严重,可能会退化到O(N)。而且,每个元素都需要封装成一个
Node
对象,这会带来额外的内存开销。

EnumSet
则完全不同。它是一个高度特化的
Set
实现,专门为枚举类型设计。它的秘密武器在于其内部实现是一个位向量(bit vector)。什么意思呢?每个枚举常量在定义时,都会被Java编译器赋予一个从0开始的序数(ordinal)。
EnumSet
就是利用这个序数来表示元素是否存在。

如果你的枚举类型常量数量不超过64个(这是因为一个

long
类型有64位),
EnumSet
内部就会使用一个
long
类型的变量来存储所有元素的状态。每个位代表一个枚举常量:如果对应的位是1,表示该枚举常量在集合中;如果是0,则不在。

例如,

Permission.READ
ordinal()
可能是0,
Permission.WRITE
可能是1。 那么,一个包含
READ
WRITE
EnumSet
,其内部的
long
值可能是
0b...0011
(二进制)。

  • add(Permission.DELETE)
    操作,就是将
    DELETE
    对应序数位的1。
  • remove(Permission.WRITE)
    操作,就是将
    WRITE
    对应序数位的0。
  • contains(Permission.READ)
    操作,就是检查
    READ
    对应序数位是否为1。

这些位操作都是CPU指令级别的,效率极高,是真正的O(1)常数时间。

如果枚举常量的数量超过64个,

EnumSet
会退化为
JumboEnumSet
(这是内部实现细节,我们通常不需要关心),它会使用一个
long[]
数组来存储位向量,每个
long
仍然管理64个枚举常量。但即便如此,它的性能和内存效率也远超
HashSet
,因为本质上还是位运算和数组索引。

宠物商店
宠物商店

目前,PetShop已经从最初的2.0、3.0等版本,发展到了最新的4.0版本。PetShop 4.0使用ASP.NET 2.0技术开发,其中加入了众多新增特性,因此,在性能、代码数量、可扩展性等方面有了重大改善。可以说,学习PetShop 4.0是深入掌握ASP.NET 2.0技术的捷径。本节将引领读者逐步了解PetShop 4.0的方方面面,包括应用程序安装、功能和用户界面简介、解决方案和体系

下载

所以,总结一下,

EnumSet
之所以高效,是因为:

  1. 位向量存储:直接将枚举常量映射到位,利用位运算实现集合操作。
  2. 无需哈希计算:避免了哈希码计算和哈希冲突处理的开销。
  3. 内存紧凑:只需一个或几个
    long
    变量,内存占用极小。

这就像是,

HashSet
是一个通用工具箱,里面有各种工具可以处理任何对象;而
EnumSet
则是一个专为螺丝刀设计的工具箱,里面的工具都是螺丝刀,并且每个螺丝刀都有自己的专属位置,你一伸手就能拿到,还不用担心拿错。

EnumMap如何利用枚举特性实现高性能,与HashMap有何不同?

EnumMap
的高性能秘诀,和
EnumSet
有着异曲同工之妙。它同样是利用了枚举类型固定且可序数化的特性,从而避免了
HashMap
中那些开销较大的操作。

HashMap
呢,和
HashSet
类似,也是基于哈希表实现的。当你用一个枚举常量作为键去
put
get
一个值时,
HashMap
会计算这个枚举键的哈希码,然后根据哈希码定位到哈希表中的一个“桶”,接着可能需要遍历链表或红黑树来找到或存储对应的键值对。这个过程中,哈希码计算、哈希冲突处理、以及对象(
Entry
)的创建和存储,都会带来一定的性能和内存开销。

EnumMap
则完全抛弃了哈希表的机制。它的内部实现,其实就是一个简单的数组(
Object[]
)。当你创建
EnumMap
时,你需要指定它将要映射的枚举类型。
EnumMap
会获取这个枚举类型的所有常量,并知道它们各自的
ordinal()
值。

当你要

put(key, value)
时,
EnumMap
会直接获取
key.ordinal()
的值,然后把
value
存储到内部数组的
key.ordinal()
索引位置。 当你要
get(key)
时,它也是直接获取
key.ordinal()
,然后从内部数组的
key.ordinal()
索引位置取出值。

// 内部简化示意,实际代码会更复杂,有边界检查和类型转换
// private final Object[] vals;
// vals = new Object[keyType.getEnumConstants().length];
// public V put(K key, V value) {
//     vals[key.ordinal()] = value;
//     return null; // 简化
// }
// public V get(Object key) {
//     return (V) vals[((Enum)key).ordinal()];
// }

这种基于数组索引的访问,效率是极高的,是纯粹的O(1)操作,比哈希表的平均O(1)还要快,因为它根本不需要计算哈希码,也不存在哈希冲突。内存开销也更小,因为它不需要为每个键值对创建额外的

Entry
对象,只需要一个数组来存储值。键本身就是枚举常量,不需要额外存储。

所以,

EnumMap
的优势在于:

  1. 数组索引访问:直接利用枚举常量的序数作为数组索引,实现极速查找和存储。
  2. 无哈希开销:完全避免了哈希码计算和冲突解决的复杂性。
  3. 内存效率:内部是一个紧凑的数组,减少了额外对象的创建。

在我看来,

EnumMap
就像是一个为你定制的抽屉柜,每个抽屉上都提前贴好了枚举常量的标签。你不需要思考哪个抽屉是哪个,直接根据标签(枚举常量)找到对应的抽屉(数组索引),然后存取物品(值),效率自然高得惊人。

在实际项目开发中,何时优先考虑EnumSet和EnumMap,有哪些常见误区?

在日常的Java项目开发中,

EnumSet
EnumMap
绝对是值得你优先考虑的工具,特别是在处理那些与枚举类型紧密相关的数据时。它们不仅仅是性能上的优化,更是一种代码清晰度和类型安全的提升。

何时优先考虑:

  1. 处理枚举集合时(EnumSet):

    • 权限管理: 比如一个用户可以拥有
      READ
      ,
      WRITE
      ,
      DELETE
      等权限,这些权限通常定义为枚举。用
      EnumSet
      来存储用户的权限集合,既高效又清晰。
    • 状态标记: 某个对象可能同时处于多种状态,例如
      ACTIVE
      ,
      PAUSED
      ,
      LOCKED
      。如果这些状态是枚举,
      EnumSet
      是一个理想的选择。
    • 配置选项: 应用程序的某个功能可能有一组开关或选项,这些选项用枚举表示,
      EnumSet
      可以方便地表示当前启用的选项。
    • 替代位域(Bit Field): 以前为了节省内存和提高效率,开发者可能会用整数的位来表示一组标志。但位域代码可读性差,容易出错。
      EnumSet
      提供了类型安全的替代方案,同时保持了位域的性能优势。
  2. 处理枚举键值映射时(EnumMap):

    • 枚举属性映射: 你可能需要为每个枚举常量存储一些额外的属性,比如
      TrafficLight.RED
      对应
      60
      秒,
      TrafficLight.YELLOW
      对应
      5
      秒。
      EnumMap
      是完美的选择。
    • 策略模式: 如果你的策略是根据枚举类型来选择的,比如
      Operation.ADD
      对应
      AdditionStrategy
      Operation.SUBTRACT
      对应
      SubtractionStrategy
      EnumMap
      可以用来存储这些策略实例。
    • 缓存: 当你需要缓存与每个枚举常量相关的数据时,
      EnumMap
      可以提供快速查找。
    • 统计计数: 统计每个枚举常量的出现次数,
      EnumMap
      会非常方便。

常见误区:

  1. 忘记线程安全性:
    EnumSet
    EnumMap
    都不是线程安全的。如果它们在多线程环境中被共享和修改,你需要外部同步(例如使用
    Collections.synchronizedSet
    Collections.synchronizedMap
    包装),或者使用
    ConcurrentHashMap
    (但它不支持
    EnumMap
    的内部优化)。
  2. 用于非枚举类型: 这是个低级错误,但值得一提。
    EnumSet
    EnumMap
    只能用于枚举类型。如果你尝试用非枚举类型创建它们,编译器会直接报错。它们的设计就是为了枚举,离开了枚举,就没有意义了。
  3. 滥用
    EnumSet.allOf()
    EnumSet.allOf(MyEnum.class)
    会创建一个包含所有枚举常量的集合。这在某些情况下很有用,但如果你只是想创建一个空集然后逐步添加,
    EnumSet.noneOf(MyEnum.class)
    才是更合适的起点。
  4. EnumMap
    的键类型声明为
    Enum
    而不是具体的枚举类型:
    // 错误或不推荐:这样写,虽然能编译,但失去了EnumMap的类型安全和部分优化
    // EnumMap map = new EnumMap<>(MyEnum.class);
    // 应该这样写:
    EnumMap map = new EnumMap<>(MyEnum.class);

    EnumMap
    的构造函数需要一个
    Class
    参数来指定键的实际枚举类型。如果你的泛型参数是
    Enum
    ,那么
    EnumMap
    内部就无法确定具体的枚举类型,从而无法进行数组大小的初始化和类型检查。虽然Java泛型擦除后运行时会处理,但最好还是声明为具体的枚举类型。

  5. 在枚举常量非常少的情况下过度优化: 当枚举常量只有一两个时,使用
    HashSet
    HashMap
    可能差异不大,甚至代码更简洁。但养成使用
    EnumSet
    /
    EnumMap
    的习惯总没错,因为它们不会带来负面影响,反而可能在未来枚举常量增多时自动获得性能收益。

在我看来,

EnumSet
EnumMap
是Java集合框架中非常精妙的设计。它们提醒我们,当数据结构与数据类型本身的特性完美结合时,就能爆发出惊人的效率。在你的代码中,如果遇到处理枚举的场景,不妨停下来思考一下,是不是有
EnumSet
EnumMap
的用武之地。很多时候,它们能让你的代码更健壮、更高效,也更“Java”。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
数据类型有哪几种
数据类型有哪几种

数据类型有整型、浮点型、字符型、字符串型、布尔型、数组、结构体和枚举等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

309

2023.10.31

php数据类型
php数据类型

本专题整合了php数据类型相关内容,阅读专题下面的文章了解更多详细内容。

222

2025.10.31

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1502

2023.10.24

treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

539

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

21

2025.12.22

深入理解算法:高效算法与数据结构专题
深入理解算法:高效算法与数据结构专题

本专题专注于算法与数据结构的核心概念,适合想深入理解并提升编程能力的开发者。专题内容包括常见数据结构的实现与应用,如数组、链表、栈、队列、哈希表、树、图等;以及高效的排序算法、搜索算法、动态规划等经典算法。通过详细的讲解与复杂度分析,帮助开发者不仅能熟练运用这些基础知识,还能在实际编程中优化性能,提高代码的执行效率。本专题适合准备面试的开发者,也适合希望提高算法思维的编程爱好者。

28

2026.01.06

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

1133

2023.10.19

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

213

2025.10.17

java入门学习合集
java入门学习合集

本专题整合了java入门学习指南、初学者项目实战、入门到精通等等内容,阅读专题下面的文章了解更多详细学习方法。

1

2026.01.29

热门下载

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

精品课程

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

共23课时 | 3万人学习

C# 教程
C# 教程

共94课时 | 7.9万人学习

Java 教程
Java 教程

共578课时 | 53.2万人学习

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

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