Java 数据类型:集合接口Collection之Set接口HashSet类;LinkedHashSet;TreeSet 类

Collection 之 Set

实现类:

  • HashSet
  • TreeSet

特点:

  • 无序。
  • 元素不可重复。

(如果试图添加一个已经有的元素到一个Set集合中,那么会添失败,add()方法返回false,且新元素不会被加入)

Java 数据类型:集合接口Collection之Set接口HashSet类;LinkedHashSet;TreeSet 类

HashSet:

HashSet是Set接口的典型实现,大多数我们就是用的这个实现类。HashSet是按照Hash算法来存储集合中的元素,因此它具有非常好的存取和查找性能
特点:
  • 元素的值可以是null。
  • HashSet是不同步的,如果有多个线程访问一个HashSet我们必须通过代码来保证同步?

底层架构:

  • 底层是HashMap;
  • HashSet 就是HashMap键的集合
HashSet<String > hashSet = new HashSet<>();
/*
* HashSet底层是HashMap
*/ //源码HahsSet的构造方法: public HashSet() {
map = new HashMap<>();
}
简单使用:
import java.util.Collection;
import java.util.HashSet;
import java.util.Set; /**
* @ClassName HashSetExample
* @projectName: object1
* @author: Zhangmingda
* @description: XXX
* date: 2021/4/9.
*/
public class HashSetExample {
public static void main(String[] args) {
Set persons = new HashSet();
persons.add("张三");
persons.add("李四");
persons.add("王五");
System.out.println(persons); //[李四, 张三, 王五]
}
}

hashSet添加元素流程解析:

向HashSet中添加一个元素的时候,HashSet会调用对象的hashCode()方法来获得对应的hashCode,然后根据hashCode来运算这个元素应该存放的位置。
Java会先看算出的hashCode存储位置是否有元素,有元素然后调用元素的equals方法判断元素是否相同。
  • hashCode不同,不再调用equals判断元素是否相同。
  • hashCode相等,但是equals返回的是false,那么我们会在相同的位置添加两个元素(通过链表的方法存储)。
  • hashCode相等,equals返回也为true,不存储。
    注意了,一般在开发的时候,为了判断两个元素是否相等,我们需要重写equals和hashCode,并且要保证equals和hashCode的一致性,如果equals返回为true,那么他们的hashCode应该要相等
 Java 数据类型:集合接口Collection之Set接口HashSet类;LinkedHashSet;TreeSet 类

示例代码:

import java.io.PrintStream;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set; /**
* @ClassName Hash
* @projectName: object1
* @author: Zhangmingda
* @description: XXX
* date: 2021/4/9.
*/
public class HashSetDecideEqualsExample {
public static class User{
private String name; public User(String name) {
this.name = name;
} @Override
public boolean equals(Object o) {
System.out.println("调用equals");
// if (this == o) return true;
// if (o == null || getClass() != o.getClass()) return false;
// User user = (User) o;
// return Objects.equals(name, user.name);
return true;
} @Override
public int hashCode() {
System.out.println("调用hashCode");
return Objects.hash(name);
// return 1;
} @Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
} public static void main(String[] args) {
Set persons = new HashSet(); persons.add(new User("张三"));
persons.add(new User("李四"));
persons.add(new User("张三"));
persons.add(new User("王五"));
System.out.println(persons);
}
}
/**
* 输出如下:User张三hashCode相同,然后调用了equals,判断是否相同,仍相同,第二个张三User未添加
调用hashCode
调用hashCode
调用hashCode
调用equals
调用hashCode
[User{name='李四'}, User{name='张三'}, User{name='王五'}]
*/

LinkedHashSet:

特点:有序,按加入顺序
  • LinkedHashSet是HashSet的一个子类,LinkedHashSet集合也是根据元素的hashCode值来决定元素的存储位置
  • 有序:它使用链表维护元素的次序,这样使得元素看起来是以插入顺序保存的。当遍历LinkedHashSet集合里面的元素时,LinkedHashSet将会按照元素的添加顺序来访问集合里的元素。
  • 性能:略低于HashSet的性能,但迭代访问Set里面的元素的时候新能会更好,因为用链表维护了内部的顺序。
import java.util.LinkedHashSet;

public class LinkHashSetExample {
public static void main(String[] args) {
LinkedHashSet linkedHashSet = new LinkedHashSet();
linkedHashSet.add("d");
linkedHashSet.add("a");
linkedHashSet.add("c");
linkedHashSet.add(3);
linkedHashSet.add(2);
linkedHashSet.add(8);
System.out.println(linkedHashSet);//[d, a, c, 3, 2, 8]
}
}

TreeSet 类

TreeSet是SortedSet接口的实现类添加元素过程中自动排序。正如SortedSet名字所暗示的,TreeSet可以确保集合元素处于排序状态(利用红黑树数据结构)。

特点:添加元素时自动排序

  • 向TreeSet集合中添加元素就是吧元素作为键添加到底层的TreeMap中;
  • TreeSet 就是TreeMap键的集合
  • SortedSet:接口要求集合中元素必须是可比较的(要求元素的类实现Comparator接口)

与HashSet集合相比,TreeSet还提供了以下几个额外的方法:

  • Comparator comparator():如果TreeSet采用了定制排序,则该方法返回定制排序所使用的Comparator,如果采用TreeSet自然排序,那么返回null。
  • Object first():返回集合中第一个元素
  •  Object last():返回集合中的最后一个元素
  • Object lower(Object e):返回集合中指定位于指定元素之前的元素。
  • Object higher(Object e):返回集合中位于指定元素之后的元素。
  • SortedSet subSet(Object fromElement, Object toElement): 返回此Set的子集合,范围从fromElement(包含)到toElement(不包含)。
  • SortedSet headSet(Object toElement):返回此Set的子集,由小于toElement的元素组成。
  • SortedSet tailSet(Object fromElement):返回此Set的子集,由大于或者等于fromElement的元素组成。

【排序逻辑】

  • TreeSet中,判断是否同一个元素,根据Comparator/Comparable的比较结果返回是否为0,如果比较结果为0 认为是相同元素。

  • 在使用treeSet时,既可以在构造方法中指定Comparator类的匿名对象,也可以让元素的类实现Comparable接口。对TreeSet来说,他是先选择的Comparator,没有Comparator再选择Comparable。

  • 对于程序员来说,实现Comparable接口重写CompareTo方法进行排序,或者做相等判断。一般定义一个比较广泛的比较规则。可以通过在构造方法中指定Comparator定义各种不同的比较规则

自定义添加元素排序逻辑

场景1、在TreeSet构造方法中指定Comparator比较器.-->示例代码设置逆序排序

public class TreeSetTest {
public static void main(String[] args) {
TreeSet<String> treeSet = new TreeSet<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.compareTo(o1);
}
});
treeSet.add("c");
treeSet.add("b");
treeSet.add("d");
treeSet.add("a");
System.out.println(treeSet);//[d, c, b, a]
}
}
import java.util.Comparator;
import java.util.TreeSet; /**
* @ClassName TreeSetSortExample
* @projectName: object1
* @author: Zhangmingda
* @description:
* date: 2021/4/10.
*/
public class TreeSetSortExample {
public static class User {
int age; public User(int age) {
this.age = age;
} @Override
public String toString() {
return "User{" +
"age=" + age +
'}';
} public static void main(String[] args) {
//创建TreeSet时自定义排序方法
// TreeSet<User> users1 = new TreeSet<>(new Comparator<User>() {
// @Override
// public int compare(User user, User t1) {
// return t1.age - user.age; //逆序
// }
// });
TreeSet<User> users1 = new TreeSet<>((User u1,User u2) ->{return u2.age - u1.age;});//逆序
users1.add(user1);
users1.add(user);
users1.add(user2);
users1.add(user3);
System.out.println(users1); //[User{age=88}, User{age=7}, User{age=2}, User{age=-8}]
}
}

场景2、要求自定义元素的类实现Comparator接口

import java.util.TreeSet;

/**
* @ClassName TreeSetSortExample
* @projectName: object1
* @author: Zhangmingda
* @description: XXX
* date: 2021/4/10.
*/
public class TreeSetSortExample {
public static class User implements Comparable {
int age; public User(int age) {
this.age = age;
} @Override
public String toString() {
return "User{" +
"age=" + age +
'}';
} @Override
public int compareTo(Object o) {
User user = (User) o;
return this.age - user.age;
}
} public static void main(String[] args) {
TreeSet<User> users = new TreeSet<>();
User user = new User(2);
User user1 = new User(7);
User user2 = new User(88);
User user3 = new User(-8);
users.add(user1);
users.add(user);
users.add(user2);
users.add(user3);
System.out.println(users); //[User{age=-8}, User{age=2}, User{age=7}, User{age=88}]
}
}

EnumSet:

EnumSet是一个专为枚举类设计的集合类,EnumSet中所有元素都必须是指定枚举类型的枚举值,该枚举类型在创建EnumSet时显示或隐式指定。EnumSet不允许插入null元素,如果视图插入null元素,会抛出NullPointerException异常。
EnumSet提供了很多类方法来创建:
  • EnumSet allOf(Class elementType): 创建一个包含指定枚举类里所有枚举值的EnumSet集合。
  • EnumSet complementOf(EnumSet e): 创建一个其元素类型与指定EnumSet里元素类型相同的EnumSet集合,新EnumSet集合包含原EnumSet集合所不包含的、此类枚举类剩下的枚举值(即新EnumSet集合和原EnumSet集合的集合元素加起来是该枚举类的所有枚举值)。
  • EnumSet copyOf(Collection c): 使用一个普通集合来创建EnumSet集合。
  • EnumSet copyOf(EnumSet e): 创建一个指定EnumSet具有相同元素类型、相同集合元素的EnumSet集合。
  • EnumSet noneOf(Class elementType): 创建一个元素类型为指定枚举类型的空EnumSet。
  • EnumSet of(E first,E…rest): 创建一个包含一个或多个枚举值的EnumSet集合,传入的多个枚举值必须属于同一个枚举类。
  • EnumSet range(E from,E to): 创建一个包含从from枚举值到to枚举值范围内所有枚举值的EnumSet集合。
import java.util.EnumSet;

public class EnumSetTest {
private static enum Season {
SPRINT, SUMMER, FALL, WINTER
}
public static void main(String[] args) {
EnumSet es1 = EnumSet.allOf(Season.class);
System.out.println(es1);
EnumSet es2 = EnumSet.noneOf(Season.class);
System.out.println(es2);
es2.add(Season.SUMMER);
es2.add(Season.FALL);
System.out.println(es2);
}
}

Set性能分析:

HashSet和TreeSet是Set的两个经典实现。HashSet的性能总是比TreeSet好(特别是最常用的添加,查询元素等操作),因为TreeSet需要额外的红黑树算法来维护集合元素的次序。只有当需要一个保持排序的Set时,才能使用TreeSet,否则都应该使用HashSet
HashSet还有一个子类,LinkedHashSet,对于普通的插入,删除操作,LinkedHashSet比HashSet要慢一点,这是因为我们要维护链表,但是因为有了链表,遍历LinkedHashSet会快一些。
 
 
 
 
 
 
 
 
 
 

public class TreeSetTest02 {
public static void main(String[] args) {
//定义一个TreeSet集合存储Person,在TreeSet构造方法中指定Comparator比较器,根据年龄降序排序
TreeSet<Person> treeSet = new TreeSet<>(new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o2.getAge() - o1.getAge();
}
});

treeSet.add(new Person("zhangsan",20));
treeSet.add(new Person("lisi",23));
treeSet.add(new Person("wangwu",14));
treeSet.add(new Person("zhaoliu",35));
treeSet.add(new Person("tiaoqi",57));

System.out.println(treeSet.contains(new Person("Shabi",14)));//true
//这里根据Comparator比较器返回 0 即认为是同一个元素

TreeSet<Person> treeSet1 = new TreeSet<>();
//如果没有在构造方法指定Comparator比较器,要求元素的类实现Comparator接口
treeSet1.add(new Person("zhangsan",20));
treeSet1.add(new Person("lisi",23));
treeSet1.add(new Person("wangwu",14));
treeSet1.add(new Person("zhaoliu",35));
treeSet1.add(new Person("tiaoqi",57));
System.out.println(treeSet1.contains(new Person("Shabi",14)));//false
System.out.println(treeSet1);
}
}

//Person类实现Comparable接口,通过Comparable<Person>泛型指定比较元素的数据类型也是Persion
class Person implements Comparable<Person> {

private String name;
private int age;

public Person() {
}

public Person(String name, int age) {
this.name = name;
this.age = age;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
Objects.equals(name, person.name);
}

@Override
public int hashCode() {
return Objects.hash(name, age);
}

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", 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 int compareTo(Person o) {
if (this.age == o.getAge() && this.name == o.getName()){
return 0;}
else
return this.name.compareTo(o.getName());
//集合中存储多个对象的时候,根据返回值是否是0判断是否是同一个对象
}
}

上一篇:WPF关于Generic.xaml


下一篇:Java——(四)Collection之Set集合TreeSet类