目录
Set接口的框架
Collection 接口:单列集合,用来存储一个一个的对象
Set 接口:存储无序的、不可重复的数据
HashSet:作为 Set 接口的主要实现类,线程不安全但效率高,可以储存 null 值
LinkedHashSet:作为 HashSet 的子类,遍历其内部数据时,可以按照添加的顺序 遍历。对于频繁的遍历操作,LinkedHashSet 的效率高于 HashSet
TreeSet:可以按照添加对象的指定属性,进行排序
如何理解Set接口的无序性与不可重复性
以 HashSet 为例说明:
1. 无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定
2. 不可重复性:保证添加的元素按照 equals()判断时,不能返回 true。即相同的元素只能添加一个
Set接口的使用要求
Set 接口中没有额外定义新的方法,使用的都是 Collection 中声明过的方法
1. 向 Set 中添加的数据,其所在类一定要重写 hashCode()和 equals()
2. 重写的 hashCode()和 equals()尽可能保持一致,相等的对象必须具有相等的散列码(哈希值)
HashSet的使用
import org.junit.Test;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class SetTest {
@Test
public void test(){
Set set = new HashSet();
set.add(456);
set.add(123);
set.add("AA");
set.add(true);
set.add("CC");
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
>>> AA
CC
456
123
true
我们向 HashSet 中添加元素 a ,首先调用元素 a 所在类的 hashCode()方法,计算元素 a 的哈希值,此哈希值接着通过某种算法计算出在 HashSet 底层数组中的存放位置(即为索引位置),判断数组此位置上是否已经有元素:
如果此位置上没有其他元素,则元素 a 添加成功
如果此位置上有其他元素 b(或以链表形式存在的多个元素),则比较元素 a 与元素 b 的哈 希值:
如果哈希值不相同,则元素 a 添加成功
如果哈希值相同,进而需要调用元素 a 所在类的 equals()方法:
equals()返回 true,元素 a 添加失败
equals()返回 false,则元素 a 添加成功
对于添加成功的后两种情况,元素 a 与已经存在指定索引位置上的数据以链表的方式存储
JDK 7.0:元素 a 放到数组中,返回原来的元素
JDK 8.0:原来的元素在数组中,指向元素 a
总结:七上八下
HashSet 底层:数组 + 链表的结构
LinkedHashSet的使用
LinkedHashSet 作为 HashSet 的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据
import org.junit.Test;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
public class SetTest {
@Test
public void test(){
Set set = new LinkedHashSet();
set.add(456);
set.add(123);
set.add("AA");
set.add(true);
set.add("CC");
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
>>> 456
123
AA
true
CC
TreeSet的使用
TreeSet 是 SortedSet 接口的实现类,TreeSet 可以确保集合元素处于排序状态
TreeSet 底层使用红黑树结构存储数据
TreeSet 两种排序方法:自然排序和定制排序,默认情况下,TreeSet 采用自然排序
TreeSet自然排序
自然排序中,比较两个对象是否相同的标准为:compareTo()返回 0,不再是 equals()
import org.junit.Test;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
public class SetTest {
@Test
public void test(){
Set set = new TreeSet();
set.add(new Person("Tom",12));
set.add(new Person("Jerry",13));
set.add(new Person("Mike",5));
set.add(new Person("Jim",34));
set.add(new Person("Peter",20));
set.add(new Person("Peter",29));
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
class Person implements Comparable{
private String name;
private int age;
public Person(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;
Person person = (Person) o;
if (age != person.age) return false;
return name != null ? name.equals(person.name) : person.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
//按照姓名字母从小到大排序
@Override
public int compareTo(Object o) {
if (o instanceof Person){
Person person = (Person)o;
return this.name.compareTo(person.name);
}else{
throw new RuntimeException("输入的类型不匹配");
}
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
>>> Person{name='Jerry', age=13}
Person{name='Jim', age=34}
Person{name='Mike', age=5}
Person{name='Peter', age=20} //只出现了一个 Peter 是因为我们没有重写年龄的 compareTo 方法,比较姓名发现相同,则只存储一个 Peter 数据
Person{name='Tom', age=12}
TreeSet定制排序
定制排序中,比较两个对象是否相同的标准为:compare()返回 0,不再是 equals()
import org.junit.Test;
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
public class SetTest {
@Test
public void test(){
Comparator com = new Comparator() {
//按照年龄从小到大排序
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof Person && o2 instanceof Person){
Person p1 = (Person)o1;
Person p2 = (Person)o2;
return Integer.compare(p1.getAge(), p2.getAge());
}else{
throw new RuntimeException("输入的数据格式不匹配");
}
}
};
TreeSet set = new TreeSet(com);
set.add(new Person("Tom",12));
set.add(new Person("Jerry",13));
set.add(new Person("Mike",5));
set.add(new Person("Jim",34));
set.add(new Person("Peter",20));
set.add(new Person("Peter",29));
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
class Person {
private String name;
private int age;
public Person(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;
Person person = (Person) o;
if (age != person.age) return false;
return name != null ? name.equals(person.name) : person.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
>>> Person{name='Mike', age=5}
Person{name='Tom', age=12}
Person{name='Jerry', age=13}
Person{name='Peter', age=20}
Person{name='Peter', age=29}
Person{name='Jim', age=34}
有关Set接口的面试题
// Person 类中重写了 hashCode() 和 equals()
import org.junit.Test;
import java.util.HashSet;
public class MyTest {
@Test
public void test() {
HashSet set = new HashSet();
Person p1 = new Person(1001,"AA");
Person p2 = new Person(1002,"BB");
set.add(p1);
set.add(p2);
p1.name = "CC";
set.remove(p1);
System.out.println(set);
set.add(new Person(1001,"CC"));
System.out.println(set);
set.add(new Person(1001,"AA"));
System.out.println(set);
}
}
>>> [Person{name='CC', age=1001}, Person{name='BB', age=1002}]
[Person{name='CC', age=1001}, Person{name='CC', age=1001}, Person{name='BB', age=1002}]
[Person{name='CC', age=1001}, Person{name='CC', age=1001}, Person{name='AA', age=1001}, Person{name='BB', age=1002}]
p1 . name = "CC" 之后,remove p1,寻找的是名为 CC 的哈希值索引数据,并没有对原来 AA 哈希值位置的数据操作,所以没有删除 p1
再添加 Person(1001,"CC"),寻找的是 CC 哈希值位置的数据,发现为 null,则添加数据
最后 Person(1001,"AA"),寻找哈希值为 AA 的哈希值数据,发现有元素存在,则调用 equals()方法,发现 AA 与 CC 不同,添加成功