JAVA基础知识之Set集合
Set集合的基本特征是不记录添加顺序,不允许元素重复(想想是为什么)。最常用的实现类是HashSet.
本文将要介绍以下内容
- HashSet类
 
- HashSe的特征
 - HashSet的equals和hashCode
 
- LinkedHashSet的特征
 - TreeSet的特征
 - EnumSet的特征
 
HashSet类
HashSet类直接实现了Set接口, 其底层其实是包装了一个HashMap去实现的。HashSet采用HashCode算法来存取集合中的元素,因此具有比较好的读取和查找性能。
HashSet的特征
- 不仅不能保证元素插入的顺序,而且在元素在以后的顺序中也可能变化(这是由HashSet按HashCode存储对象(元素)决定的,对象变化则可能导致HashCode变化)
 - HashSet是线程非安全的
 - HashSet元素值可以为NULL
 
HashSet的equals和HashCode
前面说过,Set集合是不允许重复元素的,否则将会引发各种奇怪的问题。那么HashSet如何判断元素重复呢?
HashSet需要同时通过equals和HashCode来判断两个元素是否相等,具体规则是,如果两个元素通过equals为true,并且两个元素的hashCode相等,则这两个元素相等(即重复)。
所以如果要重写保存在HashSet中的对象的equals方法,也要重写hashCode方法,重写前后hashCode返回的结果相等(即保证保存在同一个位置)。所有参与计算 hashCode() 返回值的关键属性,都应该用于作为 equals() 比较的标准。
试想如果重写了equals方法但不重写hashCode方法,即相同equals结果的两个对象将会被HashSet当作两个元素保存起来,这与我们设计HashSet的初衷不符(元素不重复)。
另外如果两个元素哈市Code相等但equals结果不为true,HashSet会将这两个元素保存在同一个位置,并将超过一个的元素以链表方式保存,这将影响HashSet的效率。
如果重写了equals方法但没有重写hashCode方法,则HashSet可能无法正常工作,比如下面的例子。
package colection.HashSet; import java.util.HashSet;
import java.util.Iterator; public class R {
public int count;
public R(int count) {
this.count = count;
} public String toString() {
return "R[count:" + count +" # hashCode:"+this.hashCode()+"]";
} public boolean equals(Object obj) {
if(this == obj) return true;
if(obj != null && obj.getClass() == R.class) {
R r = (R)obj;
return this.count == r.count;
}
return false;
}
/*
public int hashCode() {
return this.count;
}
*/
public static void main(String[] args) {
HashSet hs = new HashSet();
hs.add(new R(5));
hs.add(new R(-3));
hs.add(new R(9));
hs.add(new R(-2));
System.out.println(hs.contains(new R(-3)));
System.out.println(hs);
}
}
上面注释了hashCode方法,所以你将会看到下面的结果。
false
[R[count:9 # hashCode:14927396], R[count:5 # hashCode:24417480], R[count:-2 # hashCode:31817359], R[count:-3 # hashCode:13884241]]
取消注释,则结果就正确了
true
[R[count:5 # hashCode:5], R[count:9 # hashCode:9], R[count:-3 # hashCode:-3], R[count:-2 # hashCode:-2]]
LinkedHashSet的特征
LinkedHashSet是HashSet的一个子类,LinkedHashSet也根据HashCode的值来决定元素的存储位置,但同时它还用一个链表来维护元素的插入顺序,插入的时候即要计算hashCode又要维护链表,而遍历的时候只需要按链表来访问元素。查看LinkedHashSet的源码发现它是样的,
//LinkedHashSet 源码
public class LinkedHashSet extends HashSet
implements Set, Cloneable, Serializable
{ public LinkedHashSet(int i, float f)
{
super(i, f, true);
} ....
在JAVA7中, LinkedHashSet没有定义任何方法,只有四个构造函数,它的构造函数调用了父类(HashSet)的带三个参数的构造方法,父类的构造函数如下,
//HashSet构造函数
    HashSet(int i, float f, boolean flag)
    {
        map = new LinkedHashMap(i, f);
    }
......
由此可知,LinkedHashSet本质上也是从LinkedHashMap而来,LinkedHashSet的所有方法都继承自HashSet, 而它能维持元素的插入顺序的性质则继承自LinkedHashMap.
下面是一个LinkedHashSet维持元素插入顺序的例子,
package colection.HashSet;
import java.util.LinkedHashSet;
public class LinkedHashSets {
	public static void main(String[] args) {
		LinkedHashSet lhs = new LinkedHashSet();
		lhs.add("abc");
		lhs.add("efg");
		lhs.add("hij");
		System.out.println(lhs);
		lhs.remove(new String("efg"));
		lhs.add("efg");
		System.out.println(lhs);
	}
}
输入如下
[abc, efg, hij]
[abc, hij, efg]
TreeSet类的特征
TreeSet实现了SortedSet接口,顾名思义这是一种排序的Set集合,查看jdk源码发现底层是用TreeMap实现的,本质上是一个红黑树原理。 正因为它是排序了的,所以相对HashSet来说,TreeSet提供了一些额外的按排序位置访问元素的方法,例如first(), last(), lower(), higher(), subSet(), headSet(), tailSet().
TreeSet的排序分两种类型,一种是自然排序,另一种是定制排序。
自然排序(在元素中写排序规则)
TreeSet 会调用compareTo方法比较元素大小,然后按升序排序。所以自然排序中的元素对象,都必须实现了Comparable接口,否则会跑出异常。对于TreeSet判断元素是否重复的标准,也是调用元素从Comparable接口继承而来额compareTo方法,如果返回0则是重复元素(两个元素I相等)。Java的常见类都已经实现了Comparable接口,下面举例说明没有实现Comparable存入TreeSet时引发异常的情况。
package collection.Set;
import java.util.TreeSet;
class Err {
}
public class TreeSets {
	public static void main(String[] args) {
		TreeSet ts =  new TreeSet();
		ts.add(new Err());
		ts.add(new Err());
		System.out.println(ts);
	}
}
运行程序会抛出如下异常
Exception in thread "main" java.lang.ClassCastException: collection.Set.Err cannot be cast to java.lang.Comparable
at java.util.TreeMap.compare(Unknown Source)
at java.util.TreeMap.put(Unknown Source)
at java.util.TreeSet.add(Unknown Source)
at collection.Set.TreeSets.main(TreeSets.java:13)
将上面的Err类实现Comparable接口之后程序就能正常运行了
class Err implements Comparable {
	@Override
	public int compareTo(Object o) {
		// TODO Auto-generated method stub
		return 0;
	}
}
还有个重要问题是,因为TreeSet会调用元素的compareTo方法,这就要求所有元素的类型都相同,否则也会发生异常。也就是说,TreeSet只允许存入同一类的元素。例如下面这个例子就会抛出类型转换异常
package collection.Set;
import java.util.TreeSet;
class Err implements Comparable {
	@Override
	public int compareTo(Object o) {
		// TODO Auto-generated method stub
		return 0;
	}
}
public class TreeSets {
	public static void main(String[] args) {
		TreeSet ts =  new TreeSet();
		ts.add(1);
		ts.add("2");
		System.out.println(ts);
	}
}
运行结果
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at java.lang.String.compareTo(Unknown Source)
at java.util.TreeMap.put(Unknown Source)
at java.util.TreeSet.add(Unknown Source)
at collection.Set.TreeSets.main(TreeSets.java:18)
定制排序(在集合中写排序规则)
TreeSet还有一种排序就是定制排序,定制排序时候,需要关联一个 Comparator对象,由Comparator提供排序逻辑。下面就是一个使用Lambda表达式代替Comparator对象来提供定制排序的例子。 下面是一个定制排序的列子
package collection.Set; import java.util.Comparator;
import java.util.TreeSet; class M {
int age;
public M(int age) {
this.age = age;
} public String toString() {
return "M[age:" + age + "]";
} } class MyCommpare implements Comparator{ public int compare(Object o1, Object o2){
M m1 = (M)o1;
M m2 = (M)o2;
return m1.age > m2.age ? 1 : m1.age < m2.age ? -1 : 0;
} } public class TreeSets { public static void main(String[] args) {
TreeSet ts = new TreeSet(new MyCommpare());
ts.add(new M(5));
ts.add(new M(3));
ts.add(new M(9));
System.out.println(ts); }
}
当然将Comparator直接写入TreeSet初始化中也可以。如下。
package collection.Set; import java.util.Comparator;
import java.util.TreeSet; class M {
int age;
public M(int age) {
this.age = age;
} public String toString() {
return "M[age:" + age + "]";
} } public class TreeSets { public static void main(String[] args) {
TreeSet ts = new TreeSet(new Comparator() {
public int compare(Object o1, Object o2) {
M m1 = (M)o1;
M m2 = (M)o2;
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顾名思义就是专为枚举类型设计的集合,因此集合元素必须是枚举类型,否则会抛出异常。 EnumSet集合也是有序的,其顺序就是Enum类内元素定义的顺序。EnumSet存取的速度非常快,批量操作的速度也很快。EnumSet主要提供以下方法,allOf, complementOf, copyOf, noneOf, of, range等。注意到EnumSet并没有提供任何构造函数,要创建一个EnumSet集合对象,只需要调用allOf等方法,下面是一个EnumSet的例子。
package collection.Set; import java.util.EnumSet; enum Season
{
SPRING, SUMMER, FALL, WINTER
}
public class EnumSets { public static void main(String[] args) {
//必须用元素对象的类类型来初始化,即Season.class
EnumSet es1 = EnumSet.allOf(Season.class);
System.out.println(es1);
EnumSet es2 = EnumSet.noneOf(Season.class);
es2.add(Season.WINTER);
es2.add(Season.SUMMER);
System.out.println(es2);
EnumSet es3 = EnumSet.of(Season.WINTER, Season.SUMMER);
System.out.println(es3);
EnumSet es4 = EnumSet.range(Season.SUMMER, Season.WINTER);
System.out.println(es4);
EnumSet es5 = EnumSet.complementOf(es4);
System.out.println(es5);
}
}
执行结果
[SPRING, SUMMER, FALL, WINTER]
[SUMMER, WINTER]
[SUMMER, WINTER]
[SUMMER, FALL, WINTER]
[SPRING]
各种集合性能分析
- HashSet和TreeSet是Set集合中用得最多的I集合。HashSet总是比TreeSet集合性能好,因为HashSet不需要额维护元素的顺序。
 - LinkedHashSet需要用额外的链表维护元素的插入顺序,因此在插入时性能比HashSet低,但在迭代访问(遍历)时性能更高。因为插入的时候即要计算hashCode又要维护链表,而遍历的时候只需要按链表来访问元素。
 - EnumSet元素是所有Set元素中性能最好的,但是它只能保存Enum类型的元素
 
JAVA基础知识之Set集合的更多相关文章
- Java基础知识强化之集合框架笔记76:ConcurrentHashMap之 ConcurrentHashMap简介
		
1. ConcurrentHashMap简介: ConcurrentHashMap是一个线程安全的Hash Table,它的主要功能是提供了一组和Hashtable功能相同但是线程安全的方法.Conc ...
 - Java基础知识强化之集合框架笔记39:Set集合之HashSet存储字符串并遍历
		
1. HashSet类的概述: (1)不保证set的迭代顺序 (2)特别是它不保证该顺序恒久不变 HashSet底层数据结构是哈希表,哈希表依赖于哈希值存储,通过哈希值来确定元素的位置, 而保证元素 ...
 - Java基础知识强化之集合框架笔记27:ArrayList集合练习之去除ArrayList集合中的重复字符串元素
		
1. 去除ArrayList集合中的重复字符串元素(字符串内容相同) 分析: (1)创建集合对象 (2)添加多个字符串元素(包含重复的) (3)创建新的集合 (4)遍历旧集合,获取得到每一个元素 (5 ...
 - Java基础知识强化之集合框架笔记07:Collection集合的遍历之迭代器遍历
		
1. Collection的迭代器: Iterator iterator():迭代器,集合的专用遍历方式 2. 代码示例: package cn.itcast_03; import java.util ...
 - Java基础知识强化之集合框架笔记05:Collection集合的遍历
		
1.Collection集合的遍历 Collection集合直接是不能遍历的,所以我们要间接方式才能遍历,我们知道数组Array方便实现变量,我们可以这样: 使用Object[] toArray() ...
 - Java基础知识强化之集合框架笔记65:Map集合之集合多层嵌套的数据分析
		
1. 为了更符合要求: 这次的数据就看成是学生对象. 传智播客 bj 北京校区 jc 基础班 林青霞 27 风清扬 30 jy 就业班 赵雅芝 28 武鑫 29 sh 上海 ...
 - Java基础知识强化之集合框架笔记62:Map集合之HashMap嵌套HashMap
		
1. HashMap嵌套HashMap 传智播客 jc 基础班 陈玉楼 20 高跃 ...
 - Java基础知识强化之集合框架笔记04:Collection集合的基本功能测试
		
1. Collection集合的基本功能测试: package cn.itcast_01; import java.util.ArrayList; import java.util.Collectio ...
 - Java基础知识强化之集合框架笔记01:集合的由来与数组的区别
		
1. 集合的由来: 我们学习的是面向对象语言,而面向对象语言对事物的描述是通过对象体现的,为了方便对多个对象进行操作,我们就必须把这多个对象进行存储.而要想存储多个对象,就不能是一个基本的变量,而应该 ...
 - Java基础知识回顾之四 ----- 集合List、Map和Set
		
前言 在上一篇中回顾了Java的三大特性:封装.继承和多态.本篇则来介绍下集合. 集合介绍 我们在进行Java程序开发的时候,除了最常用的基础数据类型和String对象外,也经常会用到集合相关类. 集 ...
 
随机推荐
- PostgreSQL Performance Monitoring Tools
			
PostgreSQL Performance Monitoring Tools https://github.com/CloudServer/postgresql-perf-tools This pa ...
 - hduoj 4706 Children's Day 2013 ACM/ICPC Asia Regional Online —— Warmup
			
http://acm.hdu.edu.cn/showproblem.php?pid=4706 Children's Day Time Limit: 2000/1000 MS (Java/Others) ...
 - zjuoj 3609 Modular Inverse
			
http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3609 Modular Inverse Time Limit: 2 Seco ...
 - override与final
			
override 强调该函数是重写的父类的函数 final 指定该函数不能被重写 两者都是针对virtual 函数
 - Java Web项目里开发获取上个页面连接地址的问题
			
近期做的项目有个问题,就是需要获取上个页面连接地址,我用的IE浏览器,在用location.href连接到新地址的时候,在新地址页面用document.referrer的方法获取不到原地址,我测试了下 ...
 - bzoj3192 [JLOI2013]删除物品
			
用数组表示两个栈,将两个栈的栈顶并在一起,用树状数组维护一下操作即可. 代码 #include<cstdio> #include<algorithm> #include< ...
 - [python] No module named _sysconfigdata_nd
			
when setting python environment in Ubuntu13.04, i got this error: ImportError: No module named _sysc ...
 - android模拟器启动没有拨号功能
			
网上查询了很多资料, 其中一位网友给出的结论是android 4.3模拟器的bug, 如果在通讯录中添加好友,也是可以进行拨号的. 总结: 自认为是SDK安装程序不完整或设置AVD模拟器的时候设置项出 ...
 - 8007003Windows Update遇到未知错误
			
如果在检查更新时收到 Windows Update 错误 80070003,则需要删除 Windows 用于标识计算机更新的临时文件.若要删除临时文件,请停止 Windows Update 服务,删除 ...
 - 基于时间点恢复数据库stopat
			
create database newtestdb use newtestdbgo drop table t1go create table t1 (id int not null identity( ...