Java学习笔记——Set集合及其子类
对Set集合的阐述
Set集合类似于一个罐子,我们可以依次把多个对象丢入Set集合,但是Set集合通常不记住元素的添加顺序。
Set集合与Colletcion基本相同,只不过Set集合不允许出现相同的元素,如果使用add方法添加是出现相同元素,则会返回false值并且该相同元素不会被加入。
Set集合就只有这些东西,我们主要讲的还是Set集合的三个实现类:HashSet、LinkedHashSet、TreSet。
一.HashSet类
HashSet类作为Set集合的一个实现类,主要是以Hash算法来存储集合中的元素,因此具有很好的存储以及查询性能。
主要原理是,在向HashSet集合中添加一个元素时,它会调用该对象的hashCode( )方法来得到该对象的哈希值,然后通过该对象的哈希值判断在HashSet集合中的存储位置。在后续添加中,如果两个元素如果equals( )方法比较成功,同时hashCode( )方法返回值也相等,则说明他们是相同内容的东西,因此无法添加成功。
但是如果equal( )方法返回值相等,hashCode( )返回值不相等,则该元素还是会被添加到集合中,这个会非常麻烦,在后面会讲到对于此类的解决方法。
public class HashSetDemo1 {
public static void main(String[] args) {
//正常情况
Set<String> set=new HashSet<String>();
set.add("Tempestissimo 11.50");
set.add("Tempestissimo 11.50");
set.add("Grievous Lady 11.30");
set.add("Feacture Ray 11.20");
set.add("Feacture Ray 11.20");
set.add("SAIKYO STRONGER 11.00");
set.add("Aegleseeker 11.00");
for(String a:set){
System.out.println(a);
}
}
}
HashSet类的特点
- 不能保证元素的排列顺序,顺序可能与添加顺序不同,顺序也有可能发生变化。
- HashSet不是同步的,如果多个线程同时访问一个HashSet,假设有两个或者两个以上线程同时修改了HashSet集合时,就必须通过代码来保证其同步。
- 集合元素值可以是null
对于使用HashSet集合时判断的标准
在判断时,若没有重写equals( )和hashCode( )方法,则会直接调用Object类中的这两个方法,而这样子做相当于没有比较,因此若我们要重写该对象对应类比较方法时,必须两个方法都要同时重写,即如果重写了equals( )方法,就必须重写hashCode( )方法,反之亦然
如果两个对象的equals( )方法返回的都是true,而hashCode( )方法返回值不相等,则会把这两个相同的对象存储在两个不同的位置。这样子与Set集合的规则 冲突;若如果hashCode( )方法返回的是true但是equals( )方法返回的值是false,则会导致性能下降。
其实很简单,把equals( )方法和hashCode方法( )都重写了就得了
要注意的一点是,在可变对象添加到了HashSet集合之后,不要再去修改集合中参与计算的hashCode( ),equals( )的实例变量,否则会导致HashSet( )无法正确 操作这一些元素。
public class HashSetDemo2 {
public static void main(String[] args) {
Set<SongInfo> set=new HashSet<SongInfo>();
SongInfo song1=new SongInfo("Tempestissimo",11.50f);
SongInfo song2=new SongInfo("Tempestissimo",11.50f);
SongInfo song3=new SongInfo("Grievous Lady",11.30f);
SongInfo song4=new SongInfo("Feacture Ray",11.20f);
SongInfo song5=new SongInfo("SAIKYO STRONGER",11.00f);
SongInfo song6=new SongInfo("SAIKYO STRONGER",11.00f);
SongInfo song7=new SongInfo("Aegleseeker",11.00f);
set.add(song1);
set.add(song2);
set.add(song3);
set.add(song4);
set.add(song5);
set.add(song6);
set.add(song7);
for(SongInfo a:set){
String songname=a.getSongname();
float rating=a.getRating();
System.out.println(songname+"-----"+rating);
}
}
}
//Songinfo类
public class SongInfo {
private String songname;
private float rating;
public SongInfo() {
}
public SongInfo(String songname, float rating) {
this.songname = songname;
this.rating = rating;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SongInfo songInfo = (SongInfo) o;
return Float.compare(songInfo.rating, rating) == 0 &&
songname.equals(songInfo.songname);
}
@Override
public int hashCode() {
return Objects.hash(songname, rating);
}
}
//这里重写了equals方法和hashCode方法
二,LinkedSet类
和HashSet差不多,最主要的是底层数据结构有个链表,因此记录了插入顺序,同时使用Hash算法来维护这个链表
public class LinkedSetDemo {
public static void main(String[] args) {
Set<String> set=new LinkedHashSet<String>();
set.add("Hello");
set.add("World");
set.add("World");
set.add("Java");
for(String a:set){
System.out.println(a);
}
}
}
输出结果
Hello
World
Java
三.TreeSet类
底层数据结构为红黑树,储存的数据有有序性。其排序有两种方式:自然排序和定制排序
1. 自然排序
自然排序是指利用集合元素的 compareTo( )方法来比较元素之间的大小关系,然后将集合元素按升序排序
注意,实现compareTo方法必须先实现Comparable接口
public class TreeSetDemo1 {
public static void main(String[] args) {
Set<SongInfo> set=new TreeSet<SongInfo>();
SongInfo song1=new SongInfo("Tempestissimo",11.50f);
SongInfo song2=new SongInfo("Tempestissimo",11.50f);
SongInfo song3=new SongInfo("Grievous Lady",11.30f);
SongInfo song4=new SongInfo("Feacture Ray",11.20f);
SongInfo song5=new SongInfo("SAIKYO STRONGER",11.00f);
SongInfo song6=new SongInfo("SAIKYO STRONGER",11.00f);
SongInfo song7=new SongInfo("Aegleseeker",11.00f);
set.add(song1);
set.add(song2);
set.add(song3);
set.add(song4);
set.add(song5);
set.add(song6);
set.add(song7);
for(SongInfo a:set){
String songname=a.getSongname();
float rating=a.getRating();
System.out.println(songname+"-----"+rating);
}
}
}
//输出结果
Tempestissimo-----11.5
Grievous Lady-----11.3
Feacture Ray-----11.2
SAIKYO STRONGER-----11.0
Aegleseeker-----11.0
SongInfo类
public class SongInfo implements Comparable{
public String songname;
public float rating;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SongInfo songInfo = (SongInfo) o;
return Float.compare(songInfo.rating, rating) == 0 &&
songname.equals(songInfo.songname);
}
@Override
public int hashCode() {
return Objects.hash(songname, rating);
}
@Override
//这里重写了compareTo方法
public int compareTo(Object o) {
SongInfo a = (SongInfo) o;
if (a.rating > this.rating) {
return 1;
}
else if(a.rating==this.rating&&a.songname!=this.songname){
return 1;
}
else return 0;
}
}
2. 比较器排序
如果想实现定制排序可以通过Comparator接口的帮助,提供一个Comparator对象与该TreeSet集合关联,由该对象来负责该集合元素的排序逻辑
例如下面的代码
public class TreeSetDemo {
public static void main(String[] args) {
TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
int num = s1.getName().length() - s2.getName().length();
int num2 = num == 0 ? s1.getName().compareTo(s2.getName())
: num;
int num3 = num2 == 0 ? s1.getAge() - s2.getAge() : num2;
return num3;
}
});
Student s1 = new Student("linqingxia", 27);
Student s2 = new Student("zhangguorong", 29);
Student s3 = new Student("wanglihong", 23);
Student s4 = new Student("linqingxia", 27);
Student s5 = new Student("liushishi", 22);
Student s6 = new Student("wuqilong", 40);
Student s7 = new Student("fengqingy", 22);
Student s8 = new Student("linqingxia", 29);
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
ts.add(s6);
ts.add(s7);
ts.add(s8);
for (Student s : ts) {
System.out.println(s.getName() + "---" + s.getAge());
}
}
}
//输出结果
总结
- Set和Collection实际上没有区别,只不过Set集合不记住元素的添加的顺序
- Set有三个子类:HashSet,LinkedSet和TreeSet
- HashSet的核心就是利用hash算法来存储元素的内容以及在判断相同的时候回引用指定类的equals和hashCode方法来判断是否为同一元素,因此要记住在实现自定义类的时候记得要重写equals和hashCode方法
- LinkedSet类唯一区别就是它会记录元素的添加顺序
- TreeSet利用红黑树作为存储结构,进行有序的存储,并且能进行自然排序和定制(比较器)排序