Java基础---集合(4)Set接口及其实现类
一、Set接口
Set接口继承自Collection接口,是一个包含没有重复元素的集合,即Set接口实现类可以实现元素存储的唯一性。继承关系图如下:
二、HashSet实现类
1、概述
HashSet类通过一个哈希表支持(实际上是一个HashMap实例)实现了Set接口,它并没有保证集合的迭代顺序(是指集合元素取出的顺序和存储的顺序不同,如按 A,B,C,D 的顺序存储,打印出的结果可能是 B,C,A,D ),特别的是,它不能保证随着时间的推移,它的顺序将保持不变。
HashSet类也可以实现元素存储的唯一性。
继承关系图:
2、验证元素存储的无序性、唯一性
案例:
import java.util.HashSet;
public class Test1 {
public static void main(String[] args) {
HashSet<Integer> list = new HashSet<>(); //例1
list.add(8);
list.add(2);
list.add(1);
list.add(6);
list.add(5);
list.add(1);
list.add(2);
list.add(9);
System.out.println(list);
System.out.println("==============================");
//例2
HashSet<String> strings = new HashSet<>();
strings.add("aaa");
strings.add("ddd");
strings.add("aaaddhe");
strings.add("ddd");
strings.add("aaa");
strings.add("dchhd");
System.out.println(strings);
System.out.println("===============================");
//例3
HashSet<Student> students = new HashSet<>();
students.add(new Student("Alice",25));
students.add(new Student("Bob",25));
students.add(new Student("Alice",18));
students.add(new Student("Jack",19));
students.add(new Student("Lily",25));
students.add(new Student("Bob",25));
for(Student stu:students){
System.out.println(stu);
}
}
}
//运行结果
//分析
唯一性是靠元素重写HashCode()方法和equals()方法来保证的,如果自定义的类不重写,则不能保证,String和Integer默认重写这两个方法,所以上述例1和例2是有序输出的。而自定义的类的对象 Student 输出并没有保证顺序。
我们来看看Student对象的add()方法:
1)
public boolean add(E e) { return map.put(e, PRESENT)==null; }
2)
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
3)
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
以上三个方法可不必详细解读,只需要得出元素存储的唯一性由HashCode()方法和equals()方法
决定,所以我们要重写这两个方法,达到存储的唯一性效果。
现在,我们来重写Student类的HashCode()方法和equals()方法,实现存储的唯一性,在Student类中重写,如下:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
现在重新运行,就发现确实实现了唯一性。
3、理解元素存储唯一性的原理
上图中复写的hashCode()方法为:
@Override
public int hashCode() {
return O;
}
但是这并不是最优化的结果,这种返回0的做法,会大大的减缓运行,访问比较方法equals()次数很多,我们不妨来运行测试一下:
@Override
public boolean equals(Object o) {
System.out.println("调用equals方法"+this.name+"=="+this.age+"======="+o);
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
//return Objects.hash(name, age);
return 0;
}
//运行结果
所以,最终我们使用系统生成的最优化的重写方法:(前面有)
重新运行,得到:
三、LinkedHashSet实现类
LinkedHashSet类也是实现了Set接口,它的底层数据结构是链表和哈希表。
链表保证元素有序(集合元素取出的顺序和存储的顺序不同),哈希表保证元素唯一。
继承关系图:
简单示例:
import java.util.LinkedHashSet;
public class MyTest2 {
public static void main(String[] args) {
LinkedHashSet<Integer> list = new LinkedHashSet<>();
list.add(1);
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
System.out.println(list);
}
}
//输出结果与存储结果顺序一致,且唯一
四、TreeSet实现类
TreeSet类的继承关系图:
1、TreeSet集合的特点
底层数据结构是二叉树,元素唯一,且能排序。
案例演示: TreeSet存储Integer类型的元素并遍历,
存储下列元素: 20 , 18 , 23 , 22 , 17 , 24, 19 , 18 , 24
import java.util.TreeSet;
public class Test {
public static void main(String[] args) {
TreeSet<Integer> integers = new TreeSet<>();
// 20 , 18 , 23 , 22 , 17 , 24, 19 , 18 , 24
integers.add(20);
integers.add(18);
integers.add(23);
integers.add(22);
integers.add(17);
integers.add(24);
integers.add(19);
integers.add(18);
integers.add(24);
System.out.println(integers);
}
}
//运行结果
2、理解TreeSet类对象存储数据的原理
二叉树的数据结构 :先存入一个根结点, 分两个分支,分别是左孩子结点、右孩子结点;
存储元素时: 跟根结点比较, 小的放在左边, 大的放在右边;
如果相等就不存储
取元素的时候:按照中序遍历来读取元素,实现从小到大排序(即按照左中右的顺序来取)
如下图:
3、TreeSet集合的排序功能:
排序分为:自然排序、比较器排序,到底使用哪一种排序,取决于集合对象的构造方法的形式。
案例:分别用这两种排序实现
按照姓名的长度进行排序;
主要条件是姓名的长度;然后是姓名;然后是年龄
1)自然排序:
要求:
创建对象使用无参构造;
必须实现Comparable接口,然后重写里面的CompareTo()方法,根据自己的排序方式,返回正数、负数、或者0.
案例:
//Student类
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 +
'}';
}
//重写compareTo()方法
@Override
public int compareTo(Student s1) {
//先比较名字的长短
int num_len=this.getName().length()-s1.getName().length();
if(num_len==0){ //如果长度相等
int num_name = this.getName().compareTo(s1.getName());//比较姓名是否相同
if(num_name==0) {
int num_age=this.getAge()-s1.getAge();//比较年龄
if (num_age == 0) {
return 0;
} else {
return num_age; }
}else{
return num_name; }
}
else{
return num_len; }
}
}
上述重写的CompareTo()方法,可以用三元表达式写,比较简单明了,如下:
@Override
public int compareTo(Student s1) {
//先比较名字长短
int num=this.getName().length()-s1.getName().length();
//比较名字是否相同
int num1= num==0 ? this.name.compareTo(s1.name):num;
//比较年龄
int res=num1==0?this.age-s1.age:num1;
return res;
}
//测试类
import java.util.TreeSet;
public class Test1 {
public static void main(String[] args) {
//自然排序
TreeSet<Student> students = new TreeSet<>(); //创建对象:无参构造
students.add(new Student("Lucy",25));
students.add(new Student("Alice",25));
students.add(new Student("Jack",18));
students.add(new Student("LiMing",19));
students.add(new Student("WangFang",25));
students.add(new Student("Lily",25));
for(Student stu:students){
System.out.println(stu);
}
}
}
//运行结果
2) 使用比较器排序
要求:创建对象使用带参构造,构造方法需要一个比较器;
比较器实现Compartor接口,并重写里面的Compare()方法,根据自定义的排序方式返回 正数、负数、0;
方法1:创建一个MyClass类(任意类),实现Compartor接口,并重写Compare(),创建对象时,传递该类对象即可;
//Student类
import java.util.Comparator;
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 +
'}';
}
}
//MyClass类,实现Comparator接口,并重写Compare()
import java.util.Comparator;
public class MyClass implements Comparator<Student> {
@Override
public int compare(Student s1, Student s2) {
int num=s1.getName().length()-s2.getName().length();
//比较名字是否相同
int num1= num==0 ? s1.getName().compareTo(s2.getName()):num;
//比较年龄
int res=num1==0?s1.getAge()-s2.getAge():num1;
return res;
}
}
//测试类
import java.util.TreeSet;
public class Test {
public static void main(String[] args) {
//比较器排序,方法1
TreeSet<Student> students=new TreeSet<Student>(new MyClass());
students.add(new Student("Lucy",25));
students.add(new Student("Alice",25));
students.add(new Student("Jack",18));
students.add(new Student("LiMing",19));
students.add(new Student("WangFang",25));
students.add(new Student("Lily",25));
for(Student stu:students){
System.out.println(stu);
}
}
}
方法2:不需要重新创建一个类来实现Comparator接口,只需要在构造对象时传递一个匿名内部类,类中重写Compare()方法,即可,(Student类和上面相同)如下:
//测试类
import java.util.Comparator;
import java.util.TreeSet;
public class Test2 {
public static void main(String[] args) {
//匿名内部类
TreeSet<Student> students = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
int num=s1.getName().length()-s2.getName().length();
//比较名字是否相同
int num1= num==0 ? s1.getName().compareTo(s2.getName()):num;
//比较年龄
int res=num1==0?s1.getAge()-s2.getAge():num1;
return res;
}
});
students.add(new Student("Lucy",25));
students.add(new Student("Alice",25));
students.add(new Student("Jack",18));
students.add(new Student("LiMing",19));
students.add(new Student("WangFang",25));
students.add(new Student("Lily",25));
for(Student stu:students){
System.out.println(stu);
}
}
}
上一篇: 一个简单的Web服务器
下一篇: 关于单机部署fastdfs遇到的问题