1.Set
Set 集合特点:
- 可以去除重复。
- 存取顺序不一致。
- 没有带索引的方法,所以不能使用普通 for 循环遍历,也不能通过索引来获取、删除 Set 集合里面的元素。
package com.qdu.myset;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
public class MySet1 {
public static void main(String[] args) {
Set<String> set = new TreeSet<>();
set.add("ccc");
set.add("aaa");
set.add("aaa");
set.add("bbb");
// Set集合是没有索引的,所以不能使用通过索引获取元素的方法
Iterator<String> it = set.iterator();
while (it.hasNext()) {
String s = it.next();
System.out.println(s);
}
System.out.println("-----------------------------------");
for (String s : set) {
System.out.println(s);
}
}
}
2.TreeSet
TreeSet 集合特点:
- 不包含重复元素
- 没有带索引的方法
- 可以将元素按照规则进行排序
package com.qdu.mytreeset;
import java.util.TreeSet;
public class MyTreeSet1 {
public static void main(String[] args) {
TreeSet<Integer> ts = new TreeSet<>();
ts.add(5);
ts.add(3);
ts.add(4);
ts.add(1);
ts.add(2);
System.out.println(ts); // [1, 2, 3, 4, 5]
}
}
2.1 自然排序
想要使用 TreeSet,需要制定排序规则。
自然排序 Comparable 的使用:
- 使用空参构造创建 TreeSet 集合。
- 自定义的 Student 类实现 Comparable 接口。
- 重写里面的 compareTo 方法。
- 如果返回值为负数,表示当前存入的元素是较小值,存左边
- 如果返回值为 0,表示当前存入的元素跟集合中元素重复了,不存
- 如果返回值为正数,表示当前存入的元素是较大值,存右边
package com.qdu.mytreeset;
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) {
// 按照对象的年龄进行排序
int result = this.age - o.age;
return result;
}
}
package com.qdu.mytreeset;
import java.util.TreeSet;
public class MyTreeSet2 {
public static void main(String[] args) {
TreeSet<Student> ts = new TreeSet<>();
Student s1 = new Student("zhangsan", 29);
Student s2 = new Student("lisi", 27);
Student s3 = new Student("wangwu", 23);
Student s4 = new Student("zhaoliu", 27);
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
System.out.println(ts);
// [Student{name='wangwu', age=23}, Student{name='lisi', age=27}, Student{name='zhangsan', age=29}]
}
}
要求:按照年龄从小到大排,如果年龄一样,则按照姓名首字母排序。如果姓名和年龄一样,才认为是同一个学生对象,不存入。
package com.qdu.mytreeset;
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) {
// 主要判断条件
int result = this.age - o.age;
// 次要判断条件
result = result == 0 ? this.name.compareTo(o.getName()) : result;
return result;
}
}
package com.qdu.mytreeset;
import java.util.TreeSet;
public class MyTreeSet2 {
public static void main(String[] args) {
TreeSet<Student> ts = new TreeSet<>();
Student s1 = new Student("zhangsan",29);
Student s2 = new Student("zhaoliu",27);
Student s3 = new Student("wangwu",23);
Student s4 = new Student("lisi",27);
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
System.out.println(ts);
// [Student{name='wangwu', age=23}, Student{name='lisi', age=27}, Student{name='zhaoliu', age=27}, Student{name='zhangsan', age=29}]
}
}
2.2 比较器排序
比较器排序 Comparator 的使用:
- TreeSet 的带参构造方法使用的是比较器排序对元素进行排序的。
- 比较器排序,就是让集合构造方法接收 Comparator 的实现类对象,重写
compare(T o1, T o2)
方法。
- 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写。
示例:自定义 Teacher 老师类,属性为姓名和年龄。请按照年龄排序,如果年龄一样,则按照姓名进行排序。姓名都使用英文字母表示。
package com.qdu.mytreeset;
public class Teacher {
private String name;
private int age;
public Teacher() {
}
public Teacher(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 "Teacher{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
package com.qdu.mytreeset;
import java.util.Comparator;
import java.util.TreeSet;
public class MyTreeSet4 {
public static void main(String[] args) {
TreeSet<Teacher> ts = new TreeSet<>(new Comparator<Teacher>() {
@Override
public int compare(Teacher o1, Teacher o2) {
// o1表示现在要存入的那个元素
// o2表示已经存入到集合中的元素
// 主要条件
int result = o1.getAge() - o2.getAge();
// 次要条件
result = result == 0 ? o1.getName().compareTo(o2.getName()) : result;
return result;
}
});
Teacher t1 = new Teacher("zhangsan",23);
Teacher t2 = new Teacher("lisi",22);
Teacher t3 = new Teacher("zhaoliu",24);
Teacher t4 = new Teacher("wangwu",24);
ts.add(t1);
ts.add(t2);
ts.add(t3);
ts.add(t4);
System.out.println(ts);
// [Teacher{name='lisi', age=22}, Teacher{name='zhangsan', age=23}, Teacher{name='wangwu', age=24}, Teacher{name='zhaoliu', age=24}]
}
}
2.3 两种比较方式小结
自然排序:自定义类实现 Comparable 接口,重写 compareTo 方法,根据返回值进行排序。
比较器排序:创建 TreeSet 对象的时候传递 Comparator 的实现类对象,重写 compare 方法,根据返回值进行排序。
在使用的时候,默认使用自然排序,当自然排序不满足现在的需求时,使用比较器排序。
两种方式中,关于返回值的规则:
- 如果返回值为负数,表示当前存入的元素是较小值,存左边
- 如果返回值为 0,表示当前存入的元素跟集合中元素重复了,不存
- 如果返回值为正数,表示当前存入的元素是较大值,存右边
示例:存入四个字符串 “c”, “ab”, “df”, “qwer”,按照长度排序,如果一样长则按照首字母排序。
package com.qdu.mytreeset;
import java.util.Comparator;
import java.util.TreeSet;
public class MyTreeSet5 {
public static void main(String[] args) {
TreeSet<String> ts = new TreeSet<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
int result = o1.length() - o2.length();
result = result == 0 ? o1.compareTo(o2) : result;
return result;
}
});
// TreeSet<String> ts = new TreeSet<>(
// (String o1, String o2) -> {
// int result = o1.length() - o2.length();
// result = result == 0 ? o1.compareTo(o2) : result;
// return result;
// }
// );
ts.add("c");
ts.add("ab");
ts.add("df");
ts.add("qwer");
System.out.println(ts); // [c, ab, df, qwer]
}
}
3.HashSet
HashSet 集合特点:
- 底层数据结构是哈希表
- 不能保证存储和取出的顺序完全一致
- 没有带索引的方法,所以不能使用普通 for 循环遍历
- 由于是 Set 集合,所以元素唯一
package com.qdu.myhashset;
import java.util.HashSet;
import java.util.Iterator;
public class HashSetDemo1 {
public static void main(String[] args) {
HashSet<String> hs = new HashSet<>();
hs.add("hello");
hs.add("world");
hs.add("java");
hs.add("java");
hs.add("java");
hs.add("java");
hs.add("java");
hs.add("java");
Iterator<String> it = hs.iterator();
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}
System.out.println("------------------------");
for (String s : hs) {
System.out.println(s);
}
}
}
3.1 哈希值
哈希值(哈希码值):是 JDK 根据对象的地址或者属性值算出来的 int 类型的整数。
Object 类中有一个方法可以获取对象的哈希值:
-
public int hashCode()
:根据对象的地址值计算出哈希值。
对象的哈希值特点:
(1) 如果没有重写 hashCode 方法,那么是根据对象的地址值计算出哈希值。
- 同一个对象多次调用 hashCode() 方法返回的哈希值是相同的。
- 不同对象的哈希值是不一样的。
(2) 如果重写了 hashCode 方法,一般都是通过对象的属性值计算出哈希值。
- 如果不同的对象属性值是一样的,那么计算出来的哈希值也是一样的。
package com.qdu.myhashset;
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 +
'}';
}
}
package com.qdu.myhashset;
public class HashSetDemo2 {
public static void main(String[] args) {
Student s1 = new Student("xiaozhi",23);
Student s2 = new Student("xiaomei",22);
// Object类中的hashCode方法,根据对象的地址值计算出哈希值
System.out.println(s1.hashCode()); // 1324119927
System.out.println(s1.hashCode()); // 1324119927
System.out.println(s2.hashCode()); // 990368553
}
}
示例:创建一个存储学生对象的集合,存储多个学生对象,使用程序实现在控制台遍历该集合。学生对象的成员变量值相同,就认为是同一个对象。
package com.qdu.hashsettest;
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 boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
if (age != student.age) return false;
return name != null ? name.equals(student.name) : student.name == null;
}
// 重写了hashCode方法,根据对象的属性值来计算哈希值,此时跟对象的地址值就没有任何关系了
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
package com.qdu.hashsettest;
import java.util.HashSet;
public class HashSetTest1 {
public static void main(String[] args) {
HashSet<Student> hs = new HashSet<>();
// 如果HashSet集合要存储自定义对象,那么必须重写hashCode和equals方法
Student s1 = new Student("xiaohei",23);
Student s2 = new Student("xiaohei",23);
Student s3 = new Student("xiaomei",22);
hs.add(s1);
hs.add(s2);
hs.add(s3);
for (Student student : hs) {
System.out.println(student);
}
// Student{name='xiaohei', age=23}
// Student{name='xiaomei', age=22}
}
}
3.2 底层原理
JDK8 之前(不包括JDK8),底层采用数组+链表实现。
JDK8以后,底层进行了优化,由数组+链表+红黑树实现。
HashSet1.7版本原理
HashSet1.8版本原理
4.小结