Java容器深入浅出之HashSet、TreeSet和EnumSet
Java集合中的Set接口,定义的是一类无顺序的、不可重复的对象集合。如果尝试添加相同的元素,add()方法会返回false,同时添加失败。Set接口包括3个主要的实现类:HashSet、TreeSet和EnumSet。
通过查看Java源码,事实上Java是先实现了Map,然后通过包装一个所有value都为null的集合,形成Set。
HashSet
HashSet基于Hash算法实现,因此存取和查找的性能较好。HashSet的主要特点如下:
1. 无顺序的。与添加顺序不同,并且可变。
2. 线程不安全。
3. 集合元素可以是null
4. HashSet是通过元素的HashCode返回值,来确定元素存储位置。
5. 不可重复。HashSet判断元素是否重复的标准是:该元素对象的HashCode()返回值相等,并且equals()方法相等。换句话说,如果两个元素的equals方法相同,但HashCode返回值不相同,HashSet依然可以添加成功。因此,需要注意:
5.1 用Set类保存的元素,尽量保证其equals相等的同时,HashCode返回的值也相等。
5.2 当保存引用类型对象的时候,尽量不要修改其实例变量,否则可能会导致Set集合操作失灵。
/*
* 测试HashSet的对象添加规则
*/
class A{
//重写类A的equals方法,恒返回true
public boolean equals(Object obj) {
return true;
}
}
class B{
//重写类B的HashCode方法,恒返回1
public int hashCode() {
return 1;
}
}
class C{
//重写类C的HashCode方法,恒返回2
public int hashCode() {
return 2;
}
//重写类C的equals方法,恒返回true
public boolean equals(Object obj) {
return true;
}
}
public class TestHashSet { public static void main(String[] args) { Set<Object> set = new HashSet<>();
//equals相同,HashCode不相同:
//两个对象添加到hash表的不同位置
set.add(new A());
set.add(new A());
//HashCode相同, equals不同
//此时Hash表会在该HashCode位置,使用链式结构保存这些对象,导致性能下降.
set.add(new B());
set.add(new B());
//C类对象只被添加了一次.
set.add(new C());
set.add(new C()); System.out.println(set); } }
LinkedHashSet
LinkedHashSet是HashSet的子类。虽然同样按照元素的HashCode来确定存储位置,但该类同时使用链表来维护添加顺序,也因此,其性能稍低,但是迭代访问元素时优势较大。
TreeSet
TreeSet底层采用的是红黑树的数据结构,并且是有序的集合。因此,TreeSet增加了访问第一个、前一个、后一个和最后一个的方法,并且提供了3个截取子树的方法。
TreeSet包括两种排序方法:自然排序和定制排序,默认使用自然排序。
 public class TestTreeSet {
     public static void main(String[] args) {
         TreeSet<Integer> nums = new TreeSet<>();
         nums.add(5);
         nums.add(2);
         nums.add(10);
         nums.add(-9);
         //输出,此时已按自然排序
         System.out.println(nums);
         System.out.println(nums.first());
         System.out.println(nums.last());
         //返回<4的子序列
         System.out.println(nums.headSet(4));
         //返回>=5的子序列
         System.out.println(nums.tailSet(5));
         //返回[-3, 4)的子序列
         System.out.println(nums.subSet(-3, 4));
     }
 }
TreeSet的自然排序
指的是TreeSet会调用元素的compareTo(Object o)方法来比较元素之间的大小关系,然后将集合按升序排列。
当把一个对象加入TreeSet集合中时,会调用compareTo方法与集合中其它对象比较大小,然后根据红黑树结构定位存储位置。如比较相等,则无法添加。
自然排序中判断两个对象是否相等的唯一标准:两个对象通过compareTo方法返回是否为0。
因此,当重写两个对象的equals方法时,应当重写其compareTo方法,确保有一致的结果。与HashSet类似,当保存引用类型对象的时候,尽量不要修改其实例变量,否则可能会导致操作失效。
TreeSet的定制排序
创建TreeSet集合的对象时,提供一个Comparator对象与该TreeSet集合关联,由该Comparator对象负责元素集合的排序逻辑。
定制排序中判断两个对象是否相等的标准:Comparator对象中比较两个元素是否返回0
class M{
    int age;
    public M(int age) {
        super();
        this.age = age;
    }
    @Override
    public String toString() {
        return "M [age=" + age + "]";
    }
}
public class TestTreeSetComparator {
    public static void main(String[] args) {
        TreeSet<M> ts = new TreeSet<>((o1, o2) -> {
            M m1 = (M)o1;
            M m2 = (M)o2;
            //自定义排序:M对象的age属性越大,对比结果反而越小
            return m1.age > m2.age ? -1 : m1.age < m2.age ? 1 : 0;
        });
        ts.add(new M(5));
        ts.add(new M(-3));
        ts.add(new M(9));
        System.out.println(ts);
    }
}
EnumSet
EnumSet专为枚举类设计,其元素必须是指定枚举类的枚举值,并且不能为null。
EnumSet为有序集合,顺序由枚举值在枚举类中的定义顺序决定。其常用方法如下:
 enum Season{
     SPRING, SUMMER, FALL, WINTER
 }
 public class TestEnumSet {
     public static void main(String[] args) {
         //创建一个指定枚举类Season的枚举集合
         EnumSet<Season> es1 = EnumSet.allOf(Season.class);
         System.out.println(es1);
         //创建一个指定枚举类Season的空集合
         EnumSet<Season> es2 = EnumSet.noneOf(Season.class);
         System.out.println(es2);
         //手动添加枚举值
         es2.add(Season.WINTER);
         es2.add(Season.SUMMER);
         System.out.println(es2);
         //创建一个指定枚举值的枚举集合
         EnumSet<Season> es3 = EnumSet.of(Season.SPRING, Season.FALL);
         System.out.println(es3);
         //创建一个指定枚举值及顺序的枚举集合
         EnumSet<Season> es4 = EnumSet.range(Season.SUMMER, Season.WINTER);
         System.out.println(es4);
         //创建一个es4集合的其余集合es5, 其中es4+es5=es1
         EnumSet<Season> es5 = EnumSet.complementOf(es4);
         System.out.println(es5);
     }
 }
关于HashSet和TreeSet的选择
作为两个Set接口的典型实现,一般的选择考虑点是:
1. 优先选择HashSet,快速满足日常添加、查询的操作。
2. 有迭代需求时,可以考虑LinkedHashSet
3. 当需要一个排序的Set的时候,再考虑TreeSet
4. 操作枚举类时,优先考虑EnumSet
当需要在多线程环境下使用集合,应该使用Collections工具类在创建集合时就封装为线程安全。
SortedSet set = Collections.synchronizedSortedSet(new TreeSet());
Java容器深入浅出之HashSet、TreeSet和EnumSet的更多相关文章
- Java容器深入浅出之Collection与Iterator接口
		Java中用于保存对象的容器,除了数组,就是Collection和Map接口下的容器实现类了,包括用于迭代容器中对象的Iterator接口,构成了Java数据结构主体的集合体系.其中包括: 1. Co ... 
- Java容器深入浅出之Map、HashMap、Hashtable及其它实现类
		在Java中,Set的底层事实上是基于Map实现的,Map内部封装了一个Entry内部接口,由实现类来封装key-value对,当value值均为null时,key的集合就形成了Set.因此,Map集 ... 
- Java容器深入浅出之数组
		写在前面 关于Java的学习,特别是对于非计算机专业的同学来说,我总是主张从实践中来,到实践中去的学习方法.Java本身是一门应用性特别强的高级编程语言,因此如果能在基于实际开发的经验基础上,对Jav ... 
- Java容器-引用数据类型排序+TreeSet、TreeMap底层实现
		目录 1.冒泡排序的实现 2.比较接口(普通数据类型.引用数据类型) 普通数据类型:冒泡排序 引用数据类型:包装类(Integer.String.Character.Date) 自定义类型:实体类:i ... 
- Java容器深入浅出之PriorityQueue、ArrayDeque和LinkedList
		Queue用于模拟一种FIFO(first in first out)的队列结构.一般来说,典型的队列结构不允许随机访问队列中的元素.队列包含的方法为: 1. 入队 void add(Object o ... 
- Java容器深入浅出之List、ListIterator和ArrayList
		List是Collection接口的子接口,表示的是一种有序的.可重复元素的集合. List接口的主要实现类ArrayList和Vector,底层都是维护了一套动态的,可扩展长度的Object[]数组 ... 
- Java容器深入浅出之String、StringBuffer、StringBuilder
		对字符串的花式处理一直是现代应用系统的主要操作之一,也是对Java基础知识考察的重要方面.事实上,Java字符串类的底层是通过数组来实现的.具体来说,String类是固定长度的数组,StringBuf ... 
- Java集合框架(二)—— HashSet、LinkedHashSet、TreeSet和EnumSet
		Set接口 前面已经简绍过Set集合,它类似于一个罐子,一旦把对象'丢进'Set集合,集合里多个对象之间没有明显的顺序.Set集合与Collection基本上完全一样,它没有提供任何额外的方法. Se ... 
- 30、Java中Set集合之HashSet、TreeSet和EnumSet
		Set集合是Collection的子集,Set集合与Collection基本相同,没有提供任何额外的方法,只是Set不允许包含重复的元素. Set集合3个实现类:HashSet.TreeSet.Enu ... 
随机推荐
- unable to locate package
			一.问题 在ubuntu上安装npm时 sudo apt-get install npm 出现了错误: unable to lcoate package npm 二.解决办法 更新下apt就好了 su ... 
- nginx 配置 ssl 双向证书
			CA 根证书制作 # 创建 CA 私钥 openssl genrsa -out ca.key 2048 #制作 CA 根证书(公钥) openssl req -new -x509 -days 3650 ... 
- awk高级进阶
			第1章 awk数组练习题 1.1 文件内容(仅第一行) [root@znix test]# head -1 secure-20161219 access.log ==> secure-20161 ... 
- File System Object(FSO对象)A
			FSO对象模型包含在Scripting 类型库 (Scrrun.Dll)中,它同时包含了Drive.Folder.File.FileSystemObject和TextStream五个对象: 1.Dri ... 
- [硬件配置]Ubuntu 16.04下使用NETGEAR Nighthawk AC1900 (A7000) WIFi USB适配器
			为了增强无人机与地面站之间的传输信号,组里买了这款WiFi信号接收器,无奈只有Windows和Mac OS版本的驱动程序.后来不知道从哪里得来的一个偏方可以安装Ubuntu下的驱动,特此记录. 内核降 ... 
- Unity 实现一个简单的 TPS 相机
			效果如下: 代码如下: public class TPSCamera : MonoBehaviour { /// <summary> /// 目标对象 /// </summary&g ... 
- XML学习(一)
			实体引用 在 XML 中,一些字符拥有特殊的意义. 如果您把字符 "<" 放在 XML 元素中,会发生错误,这是因为解析器会把它当作新元素的开始. 这样会产生 XML 错误: ... 
- http跳转https方法:百度云如何让http自动跳转到https【免费SSL证书使用FAQ】
			之前的一篇文章已经给大家提供了免费SSL证书的申请方法,这一篇文章是告诉大家在使用免费的SSL证书时可能会遇到的问题[怎么让http自动跳转到https以及http与https同时使用]的解决方法. ... 
- mysql添加一个字段(
			mysql添加一个字段(在指定的一个字段后面) 举个栗子:alter table inquiry add error_code varchar(3) after add_time; 说明:alter ... 
- tr命令详解
			基础命令学习目录 原文链接:https://www.cnblogs.com/ginvip/p/6354440.html 什么是tr命令?tr,translate的简写,translate的翻译: [t ... 
