- 浏览: 233766 次
- 性别:
- 来自: 上海
文章分类
- 全部博客 (102)
- 开源软件 (1)
- 并发 (14)
- WEB (1)
- NIO (4)
- Socket (5)
- 应用服务器 (4)
- 集群 (0)
- 数据库 (1)
- JAVA基础 (17)
- 开源框架 (2)
- 业务知识 (1)
- JVM (9)
- Windows (1)
- LINUX (0)
- Jquery (0)
- JMS (0)
- Cache (0)
- Oracle (5)
- XML (0)
- EJB (0)
- WebService (0)
- Struts2 (1)
- Hibernate (1)
- Spring (0)
- 设计模式 (4)
- UML (0)
- JS (12)
- 网络爬虫 (0)
- 数据结构与算法 (1)
- EXT (1)
- DIV+CSS (2)
- 安全 (3)
- Android (9)
- LDAP (1)
- Mybatis (1)
最新评论
-
Dom_4j:
...
理解注解中的@Inherited -
s469799470:
demo少个ID
iframe父子页面交互问题 -
errorerror0:
...
iframe父子页面交互问题 -
errorerror0:
iframe父子页面交互问题 -
johnawm:
2012-12-18 wangshibei 写道CountD ...
CountDownLatch的使用
Set接口的实现类HashSet/TreeSet,它们内部都是用对应的HashMap/TreeMap实现的,
但EnumSet的实现与EnumMap没有任何关系,而是用极为精简和高效的位向量实现的,
除了实现机制,EnumSet的用法也有一些不同。与TreeSet/HashSet不同,
EnumSet是一个抽象类,不能直接通过new新建,EnumSet提供了若干静态工厂方法创建EnumSet类型的对象,比如:
public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType)
noneOf方法会创建一个指定枚举类型的EnumSet,不含任何元素。
创建的EnumSet对象的实际类型是EnumSet的子类
输出结果为
[RED, BLUE]
EnumSet还有很多其他静态工厂方法,如下所示(省略了修饰public static):
可以看到,EnumSet有很多重载形式的of方法,最后一个接受的的是可变参数,其他重载方法看上去是多余的,之所以有其他重载方法是因为可变参数的运行效率低一些。
EnumSet的应用:
下面,我们通过一个场景来看EnumSet的应用。
想象一个场景,在一些工作中,比如医生、客服,不是每个工作人员每天都在的,每个人可工作的时间是不一样的,比如张三可能是周一和周三,李四可能是周四和周六,给定每个人可工作的时间,我们可能有一些问题需要回答,比如:
有没有哪天一个人都不会来?
有哪些天至少会有一个人来?
有哪些天至少会有两个人来?
有哪些天所有人都会来,以便开会?
哪些人周一和周二都会来?
使用EnumSet,可以方便高效地回答这些问题,怎么做呢?我们先来定义一个表示工作人员的类Worker,如下所示:
每个工作人员的可工作时间用一个EnumSet表示。有了这个信息,我们就可以回答以上的问题了。
哪些天一个人都不会来?代码可以为:
days初始化为所有值,然后遍历workers,从days中删除可工作的所有时间,最终剩下的就是一个人都不会来的时间,这实际是在求worker时间并集的补集,输出为:
[SUNDAY]
有哪些天所有人都会来?就是求worker时间的交集,代码可以为:
.....
实现原理
位向量
EnumSet是使用位向量实现的,什么是位向量呢?就是用一个位表示一个元素的状态,用一组位表示一个集合的状态,每个位对应一个元素,而状态只可能有两种。
对于之前的枚举类Day,它有7个枚举值,一个Day的集合就可以用一个字节byte表示,最高位不用,设为0,最右边的位对应顺序最小的枚举值,从右到左,每位对应一个枚举值,1表示包含该元素,0表示不含该元素。
比如,表示包含Day.MONDAY,Day.TUESDAY,Day.WEDNESDAY,Day.FRIDAY的集合,位向量图示结构如下
0 0 0 1 0 1 1 1
周日 周六 周五 周四 周三 周二 周一
对应的整数是23。
位向量能表示的元素个数与向量长度有关,一个byte类型能表示8个元素,一个long类型能表示64个元素,那EnumSet用的长度是多少呢?
EnumSet是一个抽象类,它没有定义使用的向量长度,它有两个子类,RegularEnumSet和JumboEnumSet。RegularEnumSet使用一个long类型的变量作为位向量,long类型的位长度是64,而JumboEnumSet使用一个long类型的数组。如果枚举值个数小于等于64,则静态工厂方法中创建的就是RegularEnumSet,大于64的话就是JumboEnumSet。
我们来看EnumSet的实现,它有表示类型信息和所有枚举值的实例变量,如下所示:
静态工厂方法
我们来看EnumSet的静态工厂方法noneOf,代码为:
RegularEnumSet和JumboEnumSet的构造方法为:
它们都调用了父类EnumSet的构造方法,其代码为:
添加元素:
RegularEnumSet的add方法的代码为:
1L << ((Enum)e).ordinal())将元素e对应的位设为1,与现有的位向量elements相或,就表示添加e了。从集合论的观点来看,这就是求集合的并集。
JumboEnumSet的add方法的代码为:
与RegularEnumSet的add方法的区别是,它先找对应的数组位置,eOrdinal >>> 6就是eOrdinal除以64,eWordNum就表示数组索引,有了索引之后,其他操作与RegularEnumSet就类似了。
对于其他操作,JumboEnumSet的思路是类似的,主要算法与RegularEnumSet一样,主要是增加了寻找对应long位向量的操作,或者有一些循环处理,逻辑也都比较简单,后文就只介绍RegularEnumSet的实现了。
RegularEnumSet的addAll方法的代码为:
删除元素
remove方法的代码为:
~是取反,该代码将元素e对应的位设为了0,这样就完成了删除。
从集合论的观点来看,remove就是求集合的差,A-B等价于A∩B',B'表示B的补集。代码中,elements相当于A,(1L << ((Enum)e).ordinal())相当于B,~(1L << ((Enum)e).ordinal())相当于B',elements &= ~(1L << ((Enum)e).ordinal())就相当于A∩B',即A-B。
查看是否包含某元素:
查看是否包含集合中的所有元素
containsAll方法的代码为:
containsAll就是在检查参数c表示的集合是不是当前集合的子集。一般而言,集合B是集合A的子集,即B⊆A,等价于A'∩B是空集∅,A'表示A的补集
retainAll方法的代码为:
求补集
EnumSet的静态工厂方法complementOf是求补集,它调用的代码是:
-1L是64位全1的二进制(补码表示)
上面代码相当于:
elements &= -1L >>> (64-universe.length);
如果universe.length为7,则-1L>>>(64-7)就是二进制的1111111,与elements相与,就会将超出universe.length部分的右边的57位都变为0。
实现原理小结
以上就是EnumSet的基本实现原理,内部使用位向量,表示很简洁,节省空间,大部分操作都是按位运算,效率极高。
但EnumSet的实现与EnumMap没有任何关系,而是用极为精简和高效的位向量实现的,
除了实现机制,EnumSet的用法也有一些不同。与TreeSet/HashSet不同,
EnumSet是一个抽象类,不能直接通过new新建,EnumSet提供了若干静态工厂方法创建EnumSet类型的对象,比如:
public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType)
noneOf方法会创建一个指定枚举类型的EnumSet,不含任何元素。
创建的EnumSet对象的实际类型是EnumSet的子类
enum Color{ RED,BLUE,YELLOW } Set<Color> colorSet = EnumSet.noneOf(Color.class); colorSet.add(Color.RED); colorSet.add(Color.BLUE); System.out.println(colorSet);
输出结果为
[RED, BLUE]
EnumSet还有很多其他静态工厂方法,如下所示(省略了修饰public static):
// 初始集合包括枚举值中指定范围的元素 <E extends Enum<E>> EnumSet<E> range(E from, E to) // 初始集合包括指定集合的补集 <E extends Enum<E>> EnumSet<E> complementOf(EnumSet<E> s)
可以看到,EnumSet有很多重载形式的of方法,最后一个接受的的是可变参数,其他重载方法看上去是多余的,之所以有其他重载方法是因为可变参数的运行效率低一些。
EnumSet的应用:
下面,我们通过一个场景来看EnumSet的应用。
想象一个场景,在一些工作中,比如医生、客服,不是每个工作人员每天都在的,每个人可工作的时间是不一样的,比如张三可能是周一和周三,李四可能是周四和周六,给定每个人可工作的时间,我们可能有一些问题需要回答,比如:
有没有哪天一个人都不会来?
有哪些天至少会有一个人来?
有哪些天至少会有两个人来?
有哪些天所有人都会来,以便开会?
哪些人周一和周二都会来?
使用EnumSet,可以方便高效地回答这些问题,怎么做呢?我们先来定义一个表示工作人员的类Worker,如下所示:
enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY } class Worker { String name; Set<Day> availableDays; public Worker(String name, Set<Day> availableDays) { this.name = name; this.availableDays = availableDays; } public String getName() { return name; } public Set<Day> getAvailableDays() { return availableDays; } }
每个工作人员的可工作时间用一个EnumSet表示。有了这个信息,我们就可以回答以上的问题了。
哪些天一个人都不会来?代码可以为:
Set<Day> days = EnumSet.allOf(Day.class); for(Worker w : workers){ days.removeAll(w.getAvailableDays()); } } System.out.println(days);
days初始化为所有值,然后遍历workers,从days中删除可工作的所有时间,最终剩下的就是一个人都不会来的时间,这实际是在求worker时间并集的补集,输出为:
[SUNDAY]
Set<Day> days = EnumSet.noneOf(Day.class); for(Worker w : workers){ days.addAll(w.getAvailableDays()); } } S System.out.println(days); 输出为: [MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY
有哪些天所有人都会来?就是求worker时间的交集,代码可以为:
Set<Day> days = EnumSet.allOf(Day.class); for(Worker w : workers){ days.retainAll(w.getAvailableDays()); } } S System.out.println(days); 输出为: [TUESDAY]
.....
实现原理
位向量
EnumSet是使用位向量实现的,什么是位向量呢?就是用一个位表示一个元素的状态,用一组位表示一个集合的状态,每个位对应一个元素,而状态只可能有两种。
对于之前的枚举类Day,它有7个枚举值,一个Day的集合就可以用一个字节byte表示,最高位不用,设为0,最右边的位对应顺序最小的枚举值,从右到左,每位对应一个枚举值,1表示包含该元素,0表示不含该元素。
比如,表示包含Day.MONDAY,Day.TUESDAY,Day.WEDNESDAY,Day.FRIDAY的集合,位向量图示结构如下
0 0 0 1 0 1 1 1
周日 周六 周五 周四 周三 周二 周一
对应的整数是23。
位向量能表示的元素个数与向量长度有关,一个byte类型能表示8个元素,一个long类型能表示64个元素,那EnumSet用的长度是多少呢?
EnumSet是一个抽象类,它没有定义使用的向量长度,它有两个子类,RegularEnumSet和JumboEnumSet。RegularEnumSet使用一个long类型的变量作为位向量,long类型的位长度是64,而JumboEnumSet使用一个long类型的数组。如果枚举值个数小于等于64,则静态工厂方法中创建的就是RegularEnumSet,大于64的话就是JumboEnumSet。
我们来看EnumSet的实现,它有表示类型信息和所有枚举值的实例变量,如下所示:
final Class<E> elementType; final Enum[] universe; elementType表示类型信息,universe表示枚举类的所有枚举值。 EnumSet自身没有记录元素个数的变量,也没有位向量,它们是子类维护的。 对于RegularEnumSet,它用一个long类型表示位向量,代码为: private long elements = 0L; 它没有定义表示元素个数的变量,是实时计算出来的,计算的代码是: public int size() { return Long.bitCount(elements); } } 对于JumboEnumSet,它用一个long数组表示,有单独的size变量,代码为: private long elements[]; private int size = 0;
静态工厂方法
我们来看EnumSet的静态工厂方法noneOf,代码为:
public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) { Enum[] universe = getUniverse(elementType); if (universe == null) throw new ClassCastException(elementType + " not an enum"); if (universe.length <= 64) return new RegularEnumSet<>(elementType, universe); else return new JumboEnumSet<>(elementType, universe); }
RegularEnumSet和JumboEnumSet的构造方法为:
RegularEnumSet(Class<E>elementType, Enum[] universe) { super(elementType, universe); } JumboEnumSet(Class<E>elementType, Enum[] universe) { super(elementType, universe); elements = new long[(universe.length + 63) >>> 6]; }
它们都调用了父类EnumSet的构造方法,其代码为:
EnumSet(Class<E>elementType, Enum[] universe) { this.elementType = elementType; this.universe = universe; } }
添加元素:
RegularEnumSet的add方法的代码为:
public boolean add(E e) { typeCheck(e); long oldElements = elements; elements |= (1L << ((Enum)e).ordinal()); return elements != oldElements; }
1L << ((Enum)e).ordinal())将元素e对应的位设为1,与现有的位向量elements相或,就表示添加e了。从集合论的观点来看,这就是求集合的并集。
JumboEnumSet的add方法的代码为:
public boolean add(E e) { typeCheck(e); int eOrdinal = e.ordinal(); int eWordNum = eOrdinal >>> 6; long oldElements = elements[eWordNum]; elements[eWordNum] |= (1L << eOrdinal); boolean result = (elements[eWordNum] != oldElements); if (result) size++; return result; }
与RegularEnumSet的add方法的区别是,它先找对应的数组位置,eOrdinal >>> 6就是eOrdinal除以64,eWordNum就表示数组索引,有了索引之后,其他操作与RegularEnumSet就类似了。
对于其他操作,JumboEnumSet的思路是类似的,主要算法与RegularEnumSet一样,主要是增加了寻找对应long位向量的操作,或者有一些循环处理,逻辑也都比较简单,后文就只介绍RegularEnumSet的实现了。
RegularEnumSet的addAll方法的代码为:
public boolean addAll(Collection<? extends E> c) { if (!(c instanceof RegularEnumSet)) return super.addAll(c); RegularEnumSet es = (RegularEnumSet)c; if (es.elementType != elementType) { if (es.isEmpty()) return false; else throw new ClassCastException( es.elementType + " != " + elementType); } long oldElements = elements; elements |= es.elements; return elements != oldElements; }
删除元素
remove方法的代码为:
public boolean remove(Object e) { if (e == null) return false; Class eClass = e.getClass(); if (eClass != elementType && eClass.getSuperclass() != elementType) return false; long oldElements = elements; elements &= ~(1L << ((Enum)e).ordinal()); return elements != oldElements; }
~是取反,该代码将元素e对应的位设为了0,这样就完成了删除。
从集合论的观点来看,remove就是求集合的差,A-B等价于A∩B',B'表示B的补集。代码中,elements相当于A,(1L << ((Enum)e).ordinal())相当于B,~(1L << ((Enum)e).ordinal())相当于B',elements &= ~(1L << ((Enum)e).ordinal())就相当于A∩B',即A-B。
查看是否包含某元素:
public boolean contains(Object e) { if (e == null) return false; Class eClass = e.getClass(); if (eClass != elementType && eClass.getSuperclass() != elementType) return false; return (elements & (1L << ((Enum)e).ordinal())) != 0; //这里判断交集是否为0 即可。 }
查看是否包含集合中的所有元素
containsAll方法的代码为:
public boolean containsAll(Collection<?> c) { if (!(c instanceof RegularEnumSet)) return super.containsAll(c); RegularEnumSet es = (RegularEnumSet)c; if (es.elementType != elementType) return es.isEmpty(); return (es.elements & ~elements) == 0; / }
containsAll就是在检查参数c表示的集合是不是当前集合的子集。一般而言,集合B是集合A的子集,即B⊆A,等价于A'∩B是空集∅,A'表示A的补集
retainAll方法的代码为:
public boolean retainAll(Collection<?> c) { if (!(c instanceof RegularEnumSet)) return super.retainAll(c); RegularEnumSet<?> es = (RegularEnumSet<?>)c; if (es.elementType != elementType) { boolean changed = (elements != 0); elements = 0; return changed; } long oldElements = elements; elements &= es.elements; return elements != oldElements; }
求补集
EnumSet的静态工厂方法complementOf是求补集,它调用的代码是:
void complement() { if (universe.length != 0) { elements = ~elements; elements &= -1L >>> -universe.length; // Mask unused bits } }
-1L是64位全1的二进制(补码表示)
上面代码相当于:
elements &= -1L >>> (64-universe.length);
如果universe.length为7,则-1L>>>(64-7)就是二进制的1111111,与elements相与,就会将超出universe.length部分的右边的57位都变为0。
实现原理小结
以上就是EnumSet的基本实现原理,内部使用位向量,表示很简洁,节省空间,大部分操作都是按位运算,效率极高。
发表评论
-
枚举中valueOf用法
2018-01-14 11:21 4547Enum的特征如下: 1.它不能有public的构造函数,这样 ... -
mybatis源码学习总结-class.getResource方法与claasloader.getResource方法的区别
2018-01-14 10:49 1054Class.getResources(String path) ... -
使用自定义注解搭建简单框架
2017-05-01 00:54 552本文主要介绍如何使用Java运行时级别的注解配合反射来搭建框架 ... -
java注解处理器
2017-04-30 17:43 676注解处理器: Java SE5扩 ... -
理解注解中的@Inherited
2017-04-30 16:06 32421@Inherited: @Inherited 元注解是一 ... -
JAVA注解总结
2017-04-30 12:59 587元注解: 元注解 ... -
java泛型理解2
2017-01-07 22:54 489泛型类型注意细节: 1.泛型类型变量不能是基本数据类型 比如, ... -
JAVA泛型理解
2017-01-07 22:33 499泛型类型的擦除: ArrayList< ... -
逻辑运算与移位运算
2012-11-27 14:56 1291源码:正数的补码与原码相同例+7 源码:00000111 补码 ... -
关于数字签名基础知识
2012-10-08 17:40 13411.消息摘要 public class MessageDige ... -
JJd
2012-05-10 20:02 0//Access-Request报文 创建message ... -
HashMap原理
2012-04-29 17:27 1819概述: HashMap是基于哈希表的Map接口的非同步实现。此 ... -
使用内部类有什么好处
2012-03-17 12:41 1339使用内部类在java编程高级设计中是必须的,它能使你的代码更加 ... -
关于a& 0xff的运算
2011-11-21 11:23 1465byte是一个有符号数可以表示-128~+127,但是作为一个 ... -
java调用Windows命令行
2011-11-20 21:32 1744java来调用windows的命令,一般情况下下面两行代码即可 ... -
parseInt(String s, int radix)用法介绍
2011-11-19 22:13 6344parseInt(String s, int radix) , ... -
深入理解String.getBytes()中编码问题
2011-11-04 15:25 2568查看jdk的源码得知,String.getBytes()的源码 ...
相关推荐
在深入学习Java源码时,理解并掌握Java集合框架至关重要。这个框架包括接口、类和算法,它们使得数据结构如数组、链表、队列、栈等的使用变得简单而高效。 首先,我们来看一下集合框架的基础接口。`Collection`是...
Java 2,也被称为J2SE(Java 2 Platform, Standard Edition),是Java语言发展的一个重要里程...利用提供的源代码进行实验,参与在线社区的讨论,结合电子教案深入学习,你将能够全面掌握Java 2的核心技术和最佳实践。
通过深入学习这些Java JDK 6的相关知识点,开发者不仅可以熟练掌握Java编程,还能了解如何利用JDK 6的特性提高代码质量,提升开发效率。这份学习笔记将指导读者逐步探索和理解Java编程世界的深度和广度,为日后的...
本资源包提供了一些关于`Enum`枚举的深入学习材料,包括实例、常见问题以及相关的文档,非常适合Java开发者进行高级编程的学习和复习。 首先,枚举的基本语法是定义一个公共类,前面加上关键字`enum`。例如: ```...
在这个“扑克游戏”项目中,我们可以深入学习和理解Java集合框架在实际游戏开发中的应用。本篇将围绕这个项目,详细探讨Java集合框架中的各种类和接口,以及它们如何在游戏逻辑中发挥作用。 首先,项目名称“Poker...
《JDK1.6学习笔记书籍+PPT》是一份宝贵的学习资源,涵盖了Java...总而言之,《JDK1.6学习笔记书籍+PPT》是一份全面而深入的学习资源,对于Java开发者尤其是初学者来说,是提升技能、理解JDK1.6内部工作原理的宝贵教材。
Java集合框架是Java编程语言中一个至关重要的组成部分,它提供了数据结构和算法的抽象,使得开发者可以方便地存储和...通过深入学习和实践,我们可以更好地利用这些工具来解决复杂的问题,提高代码的可维护性和性能。
Java JDK 6是Java开发工具集的一个重要版本,它在2006年发布,引入了许多新特性,改进了性能,并且对API进行了扩展。...通过深入学习这些知识点,开发者可以更好地利用JDK 6进行高效的Java程序开发。
对于预习内容,建议深入学习各个Set实现类的源码,理解它们的内部工作机制,以及如何在实际项目中选择最佳的数据结构。同时,可以尝试编写一些示例代码,通过实验来观察和比较不同Set实现类在不同操作下的性能表现。...
了解并深入学习这些工具类的源码,对于提升编程技能、理解Java内部机制以及优化代码性能有着巨大的帮助。 首先,让我们逐一探讨这些工具类可能包括的内容: 1. **ArrayList和LinkedList**:这两个是Java集合框架中...
通过深入理解枚举的特性和用法,可以更好地应对各种编程场景,尤其是在处理固定值集合或需要类型安全的场合。在Java EE和Android开发中,枚举也扮演着不可或缺的角色,特别是在设计模式和框架的实现中。因此,掌握...
**正文** 《JDK_API_1_6_中文》是针对Java开发人员的重要参考资料,它详细阐述了Java Development Kit(JDK)1.6版本的API接口...通过深入学习和掌握这些知识,开发者能够更好地利用JDK 1.6进行高效、可靠的软件开发。
《Java JDK6.0 API参考手册》是Java开发工程师的重要参考资料,它详尽地阐述了JDK6.0版本中的各种API(Application...通过深入学习和理解这些API,开发者可以更好地利用Java 6的特性,编写出高效、稳定且易维护的代码。
通过深入学习这些集合类,你可以更好地理解如何根据实际需求选择合适的集合类型,以及如何高效地处理数据。无论你是初学者还是经验丰富的开发者,这个集合类的详细资料都将是你宝贵的参考资料。一起学习,不断提升你...
SCJP(Sun Certified Programmer for the Java Platform)是Java编程领域的一个认证考试,旨在验证考生对Java SE平台的基础...在准备过程中,结合权威教材如"经典TK"进行深入学习,将极大地提升通过SCJP考试的成功率。
Java学习笔记JDK6课件和课本代码是一个珍贵的学习资源,尤其适合初学者或希望深入理解JDK6特性的开发者。这份资料集包含了PPT课件和源代码,旨在帮助学习者全面掌握Java编程语言的基础知识和JDK6的新特性。 Java是...
学习这部分内容对于Java开发者至关重要,因为理解并熟练掌握Java集合框架和泛型,能有效地提升代码质量和效率,减少运行时错误。此外,了解枚举类型有助于编写更安全、更整洁的代码。通过深入研究和实践,开发者能够...
"良葛格"的Java JDK 6.0学习笔记旨在帮助初学者和有经验的程序员深入理解这一版本的Java语言特性,以及如何利用JDK 6.0进行开发。 一、JDK 6.0主要特性 1. **自动内存管理**:Java 6引入了更高效的垃圾回收机制,...
Java枚举是面向对象编程中的一个...通过学习上述知识点,你可以更好地理解和运用Java枚举,提高代码的可读性、安全性和可维护性。结合PPT和详细代码,你可以深入理解这些概念并进行实践,进一步巩固你的Java编程技能。
《JDK API 1.6 中文版详解》 JDK API 1.6 是Java开发工具包的一个重要版本,它包含了丰富的类库和接口,为开发者提供了...通过深入学习这份文档,开发者可以更高效地利用Java 1.6进行软件开发,提升代码质量和性能。