20集合(Set接口补充)
Posted shawnyue-08
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了20集合(Set接口补充)相关的知识,希望对你有一定的参考价值。
Set接口
Set集合:无序,不可重复的集合,(集合不包含一对元素e1和e2,使得e1.equals(e2)返回true),并且最多一个空元素null。
Set接口中,相比Collection父接口,没有定义额外的方法。
无序性:
不等于随机性,即在遍历输出中是存在某种顺序的。List为有序集合,而Set的无序性是指数据在底层结构中存储的位置和添加顺序无关,而是根据对象的hashCode()方法的返回值决定的。
不可重复性:
研究add(E e)方法,如何保证不可重复性?
每次要添加一个元素时,保证这个元素和集合中的元素都不相等才可以添加,这样,如果添加到第1000个元素时,需要和前面的999个元素进行equals()比较,显得很繁琐。
所以HashSet的add方法,底层是一个HashMap,首先根据元素所属类的hashCode()方法计算待添加元素的hash值,然后根据散列函数计算出该对象在HashMap中的索引位置,
- 如果该位置没有元素,那么添加到该位置即可;
- 如果计算出的位置已经有元素了,那么判断两个元素的键的hash值是否相同,
- 如果不同,那么在JDK 1.8中采用尾插法将新元素插入尾部,(数组 + 链表);(在JDK 1.7中采用头插法)
- 如果相同,那么使用equals()方法判断两个元素的键是否相等,
- 返回值为true,添加失败,(在HashMap源码中是进行value的替代,但是HashSet的value是同一个Object对象)
- 返回值为false,采用尾插法将新元素插入尾部。
Set接口的主要实现类:
HashSet、LinkedHashSet、TreeSet
package org.westos.demo;
import java.util.HashSet;
import java.util.Set;
/**
* @author lwj
* @date 2020/5/12 20:54
*/
public class MyTest {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
set.add("Hello World");
set.add("Hello Java");
set.add("Spring");
set.add("SpringMVC");
set.add("MyBatis");
set.add("Vue");
set.add("ElementUI");
set.add("MyBatis");
//增强for
for (String s : set) {
System.out.println(s);
}
//Hello World
//Hello Java
//Vue
//SpringMVC
//MyBatis
//ElementUI
//Spring
}
}
HashSet
实现Set接口,由哈希表(HashMap实例)支持,对集合的迭代次序不做任何保证,线程不安全,如果有多个线程并发访问,并且至少有一个线程修改该集合,那么必须在外部进行同步,这个类可以存储null元素。
Map接口存在一个内部接口
interface Entry<K,V> {}
HashMap的静态内部类Node<K,V>实现了该接口,包含了在HashMap中一个节点所需要的数据。hash、key、value、next域。
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
}
hashCode()和equals()
package org.westos.demo;
import java.util.HashSet;
/**
* @author lwj
* @date 2020/5/13 15:18
*/
public class MyTest2 {
public static void main(String[] args) {
HashSet<Person> set = new HashSet<>();
set.add(new Person("张三", 12));
set.add(new Person("张三", 12));
/*
1、当Person类没有重写hashCode()和equals()方法时,在添加第二个对象时,hashCode()方法默认采用Object类的native方法,
两个对象的hash值不相等,所以索引位置不同,肯定会添加成功;即使索引位置相同,hash值不同,也会添加成功
*/
//System.out.println(set);
//[Person{name=‘张三‘, age=12}, Person{name=‘张三‘, age=12}]
/*
2、当Person类只重写了equals()方法,没有重写hashCode()方法时,和第一种情况一致,hash值不同,肯定会添加成功
*/
//System.out.println(set);
//[Person{name=‘张三‘, age=12}, Person{name=‘张三‘, age=12}]
/*
3、当Person类重写了hashCode()和equals()方法后,两个对象的hash值相同,索引位置相同,那么需要用equals方法进行
比较,执行Person类的equals()方法,返回true,添加失败
*/
System.out.println(set);
//hash...
//hash...
//equals
//[Person{name=‘张三‘, age=12}]
//在add(E e)会调用HashMap的put()方法,hash(key),所以每添加一个元素就会调用一次该元素所在类的hashCode()方法
}
}
package org.westos.demo;
import java.util.Objects;
/**
* @author lwj
* @date 2020/5/13 15:19
*/
public class Person {
private String name;
private Integer age;
public Person() {
}
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public boolean equals(Object o) {
System.out.println("equals");
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return Objects.equals(name, person.name) &&
Objects.equals(age, person.age);
}
@Override
public int hashCode() {
System.out.println("hash...");
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Person{" +
"name=‘" + name + ‘‘‘ +
", age=" + age +
‘}‘;
}
}
构造方法
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable {
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();
//与Map的第二个参数关联的虚拟值Object类型,HashSet只借助了HashMap的key
public HashSet() {
map = new HashMap<>();
}
}
使用HashSet的无参构造器创建对象,初始化一个负载因子为0.75的HashMap。
和ArrayList一样,懒加载,在第一次add时再创建底层数组。
add(E e)
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
package org.westos.demo;
import java.util.HashSet;
/**
* @author lwj
* @date 2020/5/13 18:42
*/
public class MyTest3 {
public static void main(String[] args) {
HashSet<Integer> integers = new HashSet<>();
integers.add(123);
integers.add(456);
integers.add(789);
integers.forEach(System.out::println);
//789
//456
//123
//Set的无序性
}
}
LinkedHashSet
作为HashSet的子类,在遍历内部数据时,可以按照添加的顺序迭代。这个类允许null元素。线程不安全。
在添加数据的同时,每个Node还维护了两个引用,before和after,记录此数据的前一个节点和后一个节点。
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V> {
//LinkedHashMap的静态内部类Entry<K,V>在HashMap的静态内部类Node<K,V>的基础上增加了两个属性,Entry<K,V>类型的before和after,指向前后Entry节点
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
transient LinkedHashMap.Entry<K,V> head;
//类似LinkedList的head
transient LinkedHashMap.Entry<K,V> tail;
//类似LinkedList的tail
}
LinkedHashSet的底层是一个LinkedHashMap。使用无参构造器创建一个LinkedHashSet对象,
public LinkedHashSet() {
super(16, .75f, true);
}
调用父类HashSet的构造方法,
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
LinkedHashMap底层类似LinkedList存在head和tail属性。
就是在HashMap底层,每add一个元素,那么就用双向链表before和after属性把每个元素链接起来,所以在打印输出时,根据链表的前后顺序进行输出,显示出LinkedHashSet在迭代时的有序性,在内部还是无序的存储。
HashMap的putVal()方法中,newNode()方法创建的是HashMap的Node内部类的对象,
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
但是LinkedHashMap继承HashMap,重写newNode()方法,LinkedHashMap创建的是一个LinkedHashMap的Entry内部类的对象。
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
LinkedHashMap.Entry<K,V> p =
new LinkedHashMap.Entry<K,V>(hash, key, value, e);
linkNodeLast(p);
return p;
}
每创建一个节点,执行linkNodeLast方法,更新节点的before和after属性,维护LinkedHashMap的head和tail指针,。
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
LinkedHashMap.Entry<K,V> last = tail;
tail = p;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
}
package org.westos.demo;
import java.util.LinkedHashSet;
/**
* @author lwj
* @date 2020/5/13 19:19
*/
public class MyDemo {
public static void main(String[] args) {
LinkedHashSet<String> strings = new LinkedHashSet<>();
strings.add("System");
strings.add("Arrays");
strings.add("Objects");
strings.add("Collections");
strings.add("Properties");
strings.forEach(System.out::println);
//System
//Arrays
//Objects
//Collections
//Properties
//LinkedHashSet实现Set接口继承HashSet接口,内部在添加数据时是无序的,但是每个节点存在before和after属性指向前一个和后一个节点
}
}
TreeSet
按照添加对象的指定属性进行排序。(向TreeSet集合中添加元素要求是同一个类的对象,才能有比较。)线程不安全。
TreeSet的自然排序
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence
package org.westos.demo2;
import java.util.TreeSet;
/**
* @author lwj
* @date 2020/5/13 19:57
*/
public class TreeSetDemo {
public static void main(String[] args) {
TreeSet<String> strings = new TreeSet<>();
strings.add("Hello");
strings.add("Collections");
strings.add("Properties");
strings.add("Vue");
strings.add("ElementUI");
strings.add("javascript");
strings.forEach(System.out::println);
//Collections
//ElementUI
//Hello
//JavaScript
//Properties
//Vue
}
}
public final class Integer extends Number implements Comparable<Integer>
package org.westos.demo2;
import java.util.TreeSet;
/**
* @author lwj
* @date 2020/5/13 21:46
*/
public class TreeSetDemo2 {
public static void main(String[] args) {
TreeSet<Integer> integers = new TreeSet<>();
integers.add(12);
integers.add(-2);
integers.add(-10);
integers.add(21);
integers.forEach(System.out::println);
//-10
//-2
//12
//21
}
}
Comparable接口
public interface Comparable<T> {
public int compareTo(T o);
}
两种排序方式:(Java的比较器)
- 自然排序:比较两个对象是否相同的标准是,compareTo(T o)方法返回0,不再是equals()方法
- 定制排序:比较两个对象是否相同的标注:Comparator实现类对象的compare(T o1, T o2)方法返回0
package org.westos.demo2;
/**
* @author lwj
* @date 2020/5/13 21:59
*/
public class User implements Comparable<User> {
private String name;
private Integer age;
public User() {
}
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name=‘" + name + ‘‘‘ +
", age=" + age +
‘}‘;
}
/** 实现Comparable接口的compareTo(T o)方法 */
@Override
public int compareTo(User o) {
return this.name.compareTo(o.name) == 0 ? this.age.compareTo(o.age) : this.name.compareTo(o.name);
}
}
package org.westos.demo2;
import java.util.TreeSet;
/**
* @author lwj
* @date 2020/5/13 22:00
*/
public class TreeSetDemo3 {
public static void main(String[] args) {
TreeSet<User> users = new TreeSet<>();
users.add(new User("Jack", 10));
users.add(new User("Jerry", 20));
users.add(new User("Jack", 15));
//User类的compareTo(T o)方法的逻辑:姓名按照String类的自然排序;如果姓名相同,按照年龄从小到大排序
users.forEach(System.out::println);
//User{name=‘Jack‘, age=10}
//User{name=‘Jack‘, age=15}
//User{name=‘Jerry‘, age=20}
}
}
TreeSet的底层是TreeMap
public TreeSet() {
this(new TreeMap<E,Object>());
}
public TreeMap() {
comparator = null;
}
TreeMap存在一个comparator属性。
如果采用空参构造器,那么TreeSet存储的对象所在的类必须实现Comparable接口,实现compareTo(T o)方法。
TreeSet的定制排序
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
在构造方法中传入一个Comparator接口的子类对象。
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
}
函数式接口,参数需要子类对象时可以传递Lambda表达式。
package org.westos.demo2;
/**
* @author lwj
* @date 2020/5/13 22:15
*/
public class Student {
private String name;
private Integer age;
public Student() {
}
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name=‘" + name + ‘‘‘ +
", age=" + age +
‘}‘;
}
}
package org.westos.demo2;
import java.util.TreeSet;
/**
* @author lwj
* @date 2020/5/13 22:14
*/
public class TreeSetDemo4 {
public static void main(String[] args) {
TreeSet<Student> students = new TreeSet<>(((o1, o2) -> o1.getName().compareTo(o2.getName()) == 0 ?
o1.getAge().compareTo(o2.getAge()) :
o1.getName().compareTo(o2.getName())));
students.add(new Student("Jack", 13));
students.add(new Student("Jerry", 15));
students.add(new Student("Jerry", 14));
//姓名相同按照年龄从小到大排序
students.add(new Student("Jack", 13));
//姓名相同,比较年龄,年龄相同,compare(T o1, T o2)返回0,代表两个对象相同
students.forEach(System.out::println);
//Student{name=‘Jack‘, age=13}
//Student{name=‘Jerry‘, age=14}
//Student{name=‘Jerry‘, age=15}
}
}
TreeSet的练习
1、定义一个Employee类,包含name,age,birthday属性,使Employee实现Comparable接口,按照name排序;
package org.westos.demo3;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* @author lwj
* @date 2020/5/13 22:41
*/
public class Employee implements Comparable<Employee>{
private String name;
private Integer age;
private LocalDateTime birthday;
public Employee() {
}
public Employee(String name, Integer age, LocalDateTime birthday) {
this.name = name;
this.age = age;
this.birthday = birthday;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public LocalDateTime getBirthday() {
return birthday;
}
public void setBirthday(LocalDateTime birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "Employee{" +
"name=‘" + name + ‘‘‘ +
", age=" + age +
", birthday=" + birthday.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) +
‘}‘;
}
@Override
public int compareTo(Employee o) {
return this.name.compareTo(o.name);
}
}
这里在打印输出birthday字段时使用了格式化方法。
package org.westos.demo3;
import java.time.LocalDateTime;
import java.time.Month;
import java.util.TreeSet;
/**
* @author lwj
* @date 2020/5/13 22:43
*/
public class MyTest {
public static void main(String[] args) {
Employee cherry = new Employee("Cherry", 21, LocalDateTime.of(1998, Month.NOVEMBER, 18,
10, 8, 20));
Employee jerry = new Employee("Jerry", 20, LocalDateTime.of(1999, Month.AUGUST, 3,
18, 9, 12));
Employee shawn = new Employee("Shawn", 33, LocalDateTime.of(1977, Month.JANUARY, 2,
12, 45, 33));
TreeSet<Employee> employees = new TreeSet<>();
employees.add(cherry);
employees.add(jerry);
employees.add(shawn);
employees.forEach(System.out::println);
//按照姓名排序
//Employee{name=‘Cherry‘, age=21, birthday=1998-11-18 10:08:20}
//Employee{name=‘Jerry‘, age=20, birthday=1999-08-03 18:09:12}
//Employee{name=‘Shawn‘, age=33, birthday=1977-01-02 12:45:33}
}
}
创建TreeSet时传入Comparator对象,按照生日排序。
package org.westos.demo3;
import java.time.LocalDateTime;
import java.time.Month;
import java.util.Comparator;
import java.util.TreeSet;
/**
* @author lwj
* @date 2020/5/13 22:52
*/
@SuppressWarnings("all")
public class MyDemo {
public static void main(String[] args) {
Employee cherry = new Employee("Cherry", 21, LocalDateTime.of(1998, Month.NOVEMBER, 18,
10, 8, 20));
Employee jerry = new Employee("Jerry", 20, LocalDateTime.of(1999, Month.AUGUST, 3,
18, 9, 12));
Employee shawn = new Employee("Shawn", 33, LocalDateTime.of(1977, Month.JANUARY, 2,
12, 45, 33));
Comparator<Employee> comparator = new Comparator<Employee>() {
@Override
public int compare(Employee o1, Employee o2) {
return -o1.getBirthday().compareTo(o2.getBirthday());
//LocalDateTime的compareTo方法会根据年月日时分表毫秒进行比较
//默认是年月日越小,那么代表越小,但是年月日越小应该越大,所以前面加上负号
}
};
TreeSet<Employee> employees = new TreeSet<>(comparator);
employees.add(cherry);
employees.add(jerry);
employees.add(shawn);
//按照生日从大到小排序
employees.forEach(System.out::println);
//Employee{name=‘Jerry‘, age=20, birthday=1999-08-03 18:09:12}
//Employee{name=‘Cherry‘, age=21, birthday=1998-11-18 10:08:20}
//Employee{name=‘Shawn‘, age=33, birthday=1977-01-02 12:45:33}
}
}
2、重要
package org.westos.demo3;
import org.westos.demo.Person;
import java.util.HashSet;
/**
* @author lwj
* @date 2020/5/13 23:10
*/
public class Test {
public static void main(String[] args) {
//Person类已经重写了hashCode()和equals()方法
HashSet<Person> set = new HashSet<>();
Person p1 = new Person("AA", 1001);
Person p2 = new Person("BB", 1002);
set.add(p1);
set.add(p2);
System.out.println(set);
//[Person{name=‘BB‘, age=1002}, Person{name=‘AA‘, age=1001}]
p1.setName("CC");
System.out.println(p1);
//Person{name=‘CC‘, age=1001}
System.out.println(set);
//[Person{name=‘BB‘, age=1002}, Person{name=‘CC‘, age=1001}]
boolean remove = set.remove(p1);
//参数p1的hash值已经不等于AA,1001的hash值,所以没有在集合中找到,前面修改p1.name=CC,但是这个对象hash值已经改变,然而索引没变
System.out.println(remove);
//false
System.out.println(set);
//[Person{name=‘BB‘, age=1002}, Person{name=‘CC‘, age=1001}]
set.add(new Person("CC", 1001));
System.out.println(set);
//三个元素
//[Person{name=‘BB‘, age=1002}, Person{name=‘CC‘, age=1001}, Person{name=‘CC‘, age=1001}]
set.add(new Person("AA", 1001));
//这个对象的hash值相等,但是equals方法返回false,所以集合中存在四个元素
System.out.println(set);
//[Person{name=‘BB‘, age=1002}, Person{name=‘CC‘, age=1001}, Person{name=‘CC‘, age=1001}, Person{name=‘AA‘, age=1001}]
}
}
以上是关于20集合(Set接口补充)的主要内容,如果未能解决你的问题,请参考以下文章
Java集合框架 Set接口实现类--TreeSet补充: Comparator接口