详解Set集合中实现类:LinkedHashSet以及TreeSet
Set集合具有两个特点:无序性与不允许元素重复。前面我们也学习HashSet(作为Set接口的主要实现类),底层结构是哈希表。
JDK1.7:HashSet的底层数据结构为数组加链表,JDK1.8:HashSet的底层数据结构为数组加链表加红黑树。
HashSet集合的特点为元素无序,且元素是唯一的。要保证元素的唯一性,是靠元素重写hashCode()方法与equals()方法来进行保证的,如果元素不重写这两个方法,则无法保证元素的唯一性。
我们要合理的重写hashCode()方法,是为了减少调用equals()方法的次数,即减少元素碰撞的次数。
而且需要注意的是:使用HashSet方法存储数据时,在遍历时不能用普通for循环,因为Set接口中没有get()方法。可以使用增强for循环或者使用迭代器进行遍历。
public class MyTest {
public static void main(String[] args) {
HashSet<String> hashSet = new HashSet<>();
hashSet.add("aaa");
hashSet.add("aaa");
hashSet.add("bbb");
hashSet.add("bbb");
hashSet.add("ccc");
hashSet.add("ddd");
//使用迭代器进行遍历
Iterator<String> iterator = hashSet.iterator();
while (iterator.hasNext()) {
String s = iterator.next();
System.out.println(s);
}
System.out.println("==============");
//使用增强for循环进行遍历
for (String s : hashSet) {
System.out.println(s);
}
}
}
了解了HashSet之后,本节学习Set接口中另外两个实现类:LinkedHashSet以及TreeSet。
1.LinkedHashSet概述
HashSet是Set接口的典型表现,大多数情况下使用Set集合时都使用这个实现类。
HashSet和LinkedHashSet按照Hash算法来存储集合中的元素,因此具有很好的存取和查找的功能。
HashSet和LinkedHashSet集合判断两个元素是否相等的标准:两个对象hashCode()方法比较相等,并且两个对象的equals()方法返回值也相等视为重复的元素,不能进行添加。因此,存储到HashSet和LinkedHashSet的元素要重写hashCode()方法以及equals()方法。
LinkedHashSet是HashSet的子类,它在HashSet的基础上,在结点中增加两个属性before和after维护节点的前后添加顺序。
LinkedHashSet的底层数据结构链表加哈希表,元素有序且唯一。链表保证了元素有序(这里的有序,因为在节点处增加了before和after属性维护节点的前后添加顺序),哈希表保证元素唯一。
import java.util.LinkedHashSet;
public class MyTest2 {
public static void main(String[] args) {
//LinkedHashSet的底层数据结构链表加哈希表,元素有序且唯一。
// 链表保证了元素有序(这里的有序,因为在节点处增加了before和after属性维护节点的前后添加顺序),哈希表保证元素唯一。
LinkedHashSet<Integer> list = new LinkedHashSet<>();
list.add(100);
list.add(200);
list.add(300);
list.add(400);
list.add(500);
list.add(100);
list.add(200);
list.add(300);
list.add(400);
list.add(500);
//使用增强for循环进行遍历
for (Integer integer : list) {
System.out.println(integer);
}
}
}
运行后的结果为:
2.TreeSet概述
TreeSet是SortedSet接口的实现类,TreeSet可以确保集合元素的处于排序状态,同时元素也是唯一的。
使用元素的自然排序(Compareable)对元素进行排序,或者根据创建set时提供的Comparator进行排序,具体取决于使用的构造方法。
2.1 自然排序
public TreeSet()构造一个新的空 set,该 set 根据其元素的自然顺序进行排序。插入该 set 的所有元素都必须实现 Comparable 接口。另外,所有这些元素都必须是可互相比较的。
2.1.1 TreeSet存储常见引用数据类型
TreeSet存储Integer类型的元素并遍历
存储下列元素: 20 , 18 , 23 , 22 , 17 , 24, 19 , 18 , 24
public class MyTest {
public static void main(String[] args) {
///TreeSet:底层数据结是二叉树的结构,他能保证元素的唯一性,而且还能对元素进行排序。
TreeSet<Integer> treeset = new TreeSet<>();
treeset.add(20);
treeset.add(18);
treeset.add(23);
treeset.add(22);
treeset.add(17);
treeset.add(24);
treeset.add(19);
treeset.add(18);
treeset.add(24);
System.out.println("使用TreeSet排序后的集合为:");
for (Integer integer : treeset) {
System.out.println(integer);
}
}
}
运行后的结果为:
注意:使用TreeSet集合进行元素的自然排序,那么对元素有要求,要求这个元素 必须实现Comparable接口 否则无法进行自然排序
我们进入了Integer类,发现了实现了Compareable接口,重写了CompareTo方法,进而可以进行自然排序。
TreeSet保证元素唯一和自然排序的原理与图解:
2.1.2 TreeSet存储自定义类的对象进行自然排序
案例:定义一个自定义的Student类,成员属性为姓名和年龄,使用TreeSet排序方式中的自然排序方式,按照年龄大小对集合中的元素进行排序。
/*自然排序:采用空参构造,用的就是自然排序,自然排序对元素有要求,
要求元素实现 Comparable 接口,重写 compareTo这个方法
*/
public class Student implements Comparable<Student> {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Student o) {
//用学生的年龄进行大小进行排序,比较的逻辑由我们来写
//return 0;
//根据此方法返回值的正负0 来决定元素在二叉树中所存放的位置。
//如果年龄不相等,使用年龄的相减的返回值
int age = this.age - o.age;
//如果年龄相等的话,并不能说明是同一个对象,还需要比较姓名
int num=age==0? this.name.compareTo(o.name):age;
return num;
}
}
import java.util.TreeSet;
public class MyTest {
public static void main(String[] args) {
TreeSet<Student> treeset = new TreeSet<>();
treeset.add(new Student("张三",25));
treeset.add(new Student("张三丰",25));
treeset.add(new Student("李四",22));
treeset.add(new Student("王五",21));
treeset.add(new Student("赵六",23));
treeset.add(new Student("钱七",24));
treeset.add(new Student("陈八",20));
for (Student student : treeset) {
System.out.println("年龄:"+student.getAge()+"==="+"姓名:"+student.getName());
}
}
}
运行后的结果为:
练习:定义一个自定义的Student类,成员属性为姓名和年龄,使用TreeSet排序方式中的自然排序方式,按照姓名长度的大小对集合中的元素进行排序。
public class Student implements Comparable<Student> {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Student stu) {
//return 0;
//按照姓名的长度来排序
int num = this.name.length() - stu.name.length();
//姓名长度一样了,还的比较姓名内容一样不
int num2 = num == 0 ? this.name.compareTo(stu.name) : num;
//姓名长度一样了,内容一样了,还得比较年龄是否一样
int num3 = num2 == 0 ? this.age - stu.age : num2;
return num3;
}
}
import java.util.TreeSet;
public class MyTest {
public static void main(String[] args) {
TreeSet<Student> treeset = new TreeSet<>();
treeset.add(new Student("Jack",24));
treeset.add(new Student("Lebron",23));
treeset.add(new Student("Lebron",23));
treeset.add(new Student("Abubli",23));
treeset.add(new Student("Janey",25));
treeset.add(new Student("Tom",26));
treeset.add(new Student("Abubli",26));
for (Student student : treeset) {
System.out.println("姓名"+student.getName()+"====="+"年龄"+student.getAge());
}
}
}
运行后的结果为:
2.2 比较器排序
public TreeSet(Comparator<? super E> comparator)构造一个新的空 TreeSet,它根据指定比较器进行排序。插入到该 set 的所有元素都必须能够由指定比较器进行相互比较。
2.2.1 使用比较器进行对自定义类对象进行排序
定义一个自定义的Student类,成员属性为姓名和年龄,使用TreeSet排序方式中的比较器排序方式,按照年龄大小对集合中的元素进行排序。
//自定义类Student类
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
//定义实现比较器接口的子类
import java.util.Comparator;
//使用比较器排序,需要实现Comparator接口,并且重写compare方法
public class MyComparator implements Comparator<Student> {
@Override
public int compare(Student s1, Student s2) {
//根据年龄大小来进行排序
int num = s1.getAge() - s2.getAge();
//如果年龄相等不代表是同一个对象,还需要比较姓名的内容是否相同
int num2=num==0?s1.getName().compareTo(s2.getName()):num;
return num2;
}
}
import java.util.TreeSet;
public class MyTest {
public static void main(String[] args) {
/* TreeSet(Comparator < ? super E > comparator)
构造一个新的空 TreeSet,它根据指定比较器进行排序。*/
//如果使用有参构造,那就是比较器排序
MyComparator myComparator = new MyComparator();
TreeSet<Student> treeSet = new TreeSet<>(myComparator);
treeSet.add(new Student("Jack",24));
treeSet.add(new Student("Lebron",23));
treeSet.add(new Student("Lebron",23));
treeSet.add(new Student("Abubli",23));
treeSet.add(new Student("Janey",25));
treeSet.add(new Student("Tom",26));
treeSet.add(new Student("Abubli",26));
for (Student student : treeSet) {
System.out.println("年龄:"+student.getAge()+"姓名:"+student.getName());
}
}
}
运行后的结果为:
总而言之:比较器的方式,采用有参构造,传入比较器,重写compare()方法根据此方法,返回值的正负0,来决定元素放置的左右顺序。
2.2.2 使用比较器可以对ArrayList集合和数组进行排序
1.使用Comparator比较器可以用来比较ArrayList集合中元素大小,进行排序
注意:将集合中的元素进行排序的sort方法中需要传入实现Comparator接口的子类对象
import java.util.ArrayList;
import java.util.Comparator;
public class MyTest2 {
public static void main(String[] args) {
ArrayList<Integer> list= new ArrayList<>();
list.add(300);
list.add(500);
list.add(200);
list.add(300);
list.add(100);
list.add(400);
//对集合进行从大到小的排序
//将集合中的元素进行排序的sort方法中需要传入实现Comparator接口的子类对象
list.sort(new Comparator<Integer>() {
@Override
public int compare(Integer a, Integer b) {
return b-a;
}
});
System.out.println(list);
}
}
运行后的结果为:
2.使用Comparator比较器可以用来比较引用数据类型数组中元素大小,进行排序
数组工具类中的sort方法可以传入实现比较器的子类对象,并重写compare方法
import java.util.Arrays;
import java.util.Comparator;
public class MyTest3 {
public static void main(String[] args) {
Integer[] arr = {2, 3, 6, 5, 4, 1};
//数组工具类中的sort方法可以传入实现比较器的子类对象,并重写compare方法
Arrays.sort(arr,new Comparator<Integer>(){
@Override
public int compare(Integer a, Integer b) {
return b-a;
}
});
System.out.println(Arrays.toString(arr));
}
}
运行后的结果为:
–
3.练习:产生10个0-19之间的随机数要求随机数不能重复
编写一个程序,获取10个1至20的随机数,要求随机数不能重复。 并把最终的随机数输出到控制台。
选HashSet 可以不重复
选TreeSet 不重复还可以排序
分析:
(1)定义一个HashSet集合或者TreeSet集合
(2)产生随机数,把随机数添加到集合中
(3)判断集合的长度,使用while循环进行实现
import java.util.HashSet;
import java.util.Random;
import java.util.TreeSet;
public class MyTest {
public static void main(String[] args) {
/* 编写一个程序,获取10个0至19的随机数,要求随机数不能重复。
并把最终的随机数输出到控制台。
*/
Random random = new Random();
TreeSet<Integer> set = new TreeSet<>();
// HashSet<Integer> set = new HashSet<>();
while(set.size()<10){
int num = random.nextInt(20);
set.add(num);
}
System.out.println(set);
}
}
总结
本节主要介绍了Set集合的两个实现类LinkedHashSet以及TreeSet集合,其中LinkedHashSet是HashSet集合的子类。LinkedHashSet在HashSet的基础上,在结点中增加两个属性before和after维护节点的前后添加顺序。
而TreeSet需要我们掌握的是两种排序方法:使用空参构造的自然排序法以及使用比较器进行比较器排序。