Set集合
HashSet类
import java.util.HashSet;
/*
Set集合:元素唯一且元素无序(存储和取出顺序不一致)的集合
HashSet类概述
不保证 set 的迭代顺序
特别是它不保证该顺序恒久不变。
HashSet如何保证元素唯一性
底层数据结构是哈希表(元素是链表的数组)
哈希表依赖于哈希值存储
添加功能底层依赖两个方法:
int hashCode()
boolean equals(Object obj)
Set集合中的元素为什么不会重复?看添加add()方法的源码
*/
public class SetDemo1 {
public static void main(String[] args) {
//用Set的子类去实例化
HashSet<String> arr = new HashSet<>();
//添加元素到集合
arr.add("hello");
arr.add("world");
arr.add("java");
arr.add("bigdata");
arr.add("hadoop");
arr.add("hello");
arr.add("hello");
arr.add("java");
arr.add("spark");
arr.add("flink");
arr.add("world");
arr.add("hadoop");
//增强for循环遍历
for(String s : arr){
System.out.println(s);
}
}
}
//----------HashSet<>()中add()的源码分析-------
public interface Set<E> extends Collection<E>{
}
public class HashSet<E> extends AbstractSet<E> implements Set<E>{
private static final Object PRESENT = new Object();
private transient HashMap<E,Object> map;
public boolean add(E e) {
//E -- String
//e -- "hello"
return map.put(e, PRESENT)==null;
}
}
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>{
public V put(K key, V value) {
//key -- "hello"
//value -- new Object()
return putVal(hash(key), key, value, false, true);
}
//简单理解为调用元素类的hashCode()方法计算哈希值
static final int hash(Object key) { //"hello"
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
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;
//根据元素对象计算好的哈希值再进行一次与运算,计算出的是该元素存储在哈希表中的位置
//如果该元素的位置是null,说明该位置没有元素,可以进行存储
//就创建新的结点,存储元素
//分析到这一步我们得出第一个结论,存储的位置与元素类中的hashCode()有关。
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
//如果该元素的位置不是null,说明这个位置上已经有元素了,可以确定是哈希值是一样的
//但是呢,我们并不能确定两个值是不是一样的
Node<K,V> e; K k;
//先将存入元素的哈希值与该位置中元素的哈希值做比较
//如果哈希值都不一样,继续走判断instanceof()
//如果哈希值都一样,会调用元素的equals(k)方法进行比较
//如果equals(k)方法进行比较结果是false,继续向下执行,最终会将元素插入到集合中或者不插入
//如果equals(k)方法进行比较结果是true,表示元素的哈希值和内容都一样,表示元素重复了
//就覆盖,从现象上来看,其实就是不赋值
//说到这里我们已经知道了add()方法和hashCode()以及equals()方法有关
//会不会去重取决于元素类型有没有重写hashCode()以及equals()方法
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;
}
}
import java.util.Objects;
public class Student2 {
private String name;
private int age;
public Student2() {
}
public Student2(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;
}
//重写equals()和hashCode()方法
@Override
public String toString() {
return "Student2{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student2 student2 = (Student2) o;
return age == student2.age &&
Objects.equals(name, student2.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
import java.util.HashSet;
/*
存储自定义对象并遍历
*/
public class HashSetDemo2 {
public static void main(String[] args) {
//创建集合对象
HashSet<Student2> hashSet = new HashSet<>();
//创建学生对象
Student2 s1 = new Student2("xiaowang", 18);
Student2 s2 = new Student2("xiaowang", 18);
Student2 s3 = new Student2("xiaoli", 19);
Student2 s4 = new Student2("xiaoliu", 20);
hashSet.add(s1);
hashSet.add(s2);
hashSet.add(s3);
hashSet.add(s4);
for(Student2 s:hashSet){
System.out.println(s);
}
//输出结果发现没有去重,这是为什么呢?
//根据刚才add()的源码分析,如果没有在Student2类中重写equals()和hashCode()
//比较的就是对象的地址值,而s1到s4的地址值都不一样,所以都添加。没有达到去重的目的。
//想要去重,就需要在Student2类中重写equals()和hashCode()方法
}
}
LinkedHashSet类
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.TreeSet;
/*
public class HashSet<E> implements Set<E>{}
public class LinkedHashSet<E> extends HashSet<E>{}
LinkedHashSet:底层数据结构是哈希表和双向链表
哈希表保证了元素唯一
链表保证了元素的有序(存储和取出顺序一致)
*/
public class LinkedHashSetDemo {
public static void main(String[] args) {
//创建LinkedHashSet集合对象
LinkedHashSet<String> arr = new LinkedHashSet<>();
//添加元素到集合
arr.add("hello");
arr.add("world");
arr.add("java");
arr.add("bigdata");
arr.add("hadoop");
arr.add("hello");
arr.add("hello");
arr.add("java");
arr.add("spark");
arr.add("flink");
arr.add("world");
arr.add("hadoop");
for (String s : arr){
System.out.println(s);
}
}
}
TreeSet类及其自然排序
import java.util.TreeSet;
/*
TreeSet:元素唯一,元素的顺序可以按照某种规则进行排序
两种排序方式:
自然排序 :无参构造--实现comparable接口
比较器排序 :有参构造--实现Comparator接口
A NavigableSet实现基于TreeMap 。
的元件使用其有序natural ordering ,或由Comparator集合创建时提供,这取决于所使用的构造方法。
要想知道如何去重以及排序,就去看源码。
因为Integer类实现了comparable接口,所以它可以做自然排序
*/
public class TreeSetDemo1 {
public static void main(String[] args) {
//创建一个集合对象
TreeSet<Integer> ts = new TreeSet<>();
//添加元素到集合中
ts.add(20);
ts.add(18);
ts.add(23);
ts.add(24);
ts.add(66);
ts.add(12);
ts.add(18);
ts.add(20);
ts.add(23);
ts.add(2);
//遍历
for(Integer i : ts){
System.out.println(i);
}
}
}
//---------------TreeSet集合的add()方法的源码--------------
---------------------------------------
interface Collection {
...
}
interface Set extends Collection {
...
}
---------------------------------------
class TreeSet implements Set {
...
private static final Object PRESENT = new Object();
private transient NavigableMap<E,Object> m;
public TreeSet() {
this(new TreeMap<E,Object>());
}
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
...
}
---------------------------------------
class TreeMap implements NavigableMap {
...
public V put(K key, V value) {
Entry<K,V> t = root; // 先造根,TreeSet集合底层数据结构是红黑树(是一个自平衡的二叉树)
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator; // 因为用的是TreeSet的无参构造方法,是自然排序,没有用到comparator比较器
if (cpr != null) { // 所以此时的comparator = null,则程序执行else里面的代码
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
} else {
if (key == null)
throw new NullPointerException();
Comparable<? super K> k = (Comparable<? super K>) key; // 此接口Comparable强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的 compareTo 方法被称为它的自然比较方法。。
do { // 举例中我们使用的是包装类Intrger,而Integer类实现了Comparable接口。此例子是向上转型。
parent = t;
cmp = k.compareTo(t.key); // 类的 compareTo 方法被称为它的自然比较方法。
if (cmp < 0) // int compareTo(T o) 比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
...
}
---------------------------------------
由上可知:真正的比较是依赖于元素的compareTo()方法,而这个方法compareTo()是定义在 Comparable接口里面的(抽象方法)。
所以,你要想重写该方法,就必须是先实现 Comparable接口。这个接口表示的就是自然排序。
public class Student3 implements Comparable<Student3> {//为了解决这个问题,让Student3实现Comparable接口
private String name; //尖括号中放当前元素类型--Student3,并且重写Comparable接口中的抽象方法
private int age;
public Student3() {
}
public Student3(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 "Student2{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Student3 o) {
// return 0;--只插入第一个元素
// return 1;--永远都可以插入,没有去重
// return -1;--也不行,也没去重
//这里返回什么,其实应该根据我们的规则来排序
//比如我想在去重的前提下,按照年龄进行排序
// return this.age - o.age;
//年龄一样,姓名不一定一样
//主要条件(题目要求的条件)
int i = this.age - o.age;
//隐含条件(需要自己挖掘)
int i2 = i == 0 ? this.name.compareTo(o.name) : i;
return i2;
}
}
import java.util.TreeSet;
/*
TreeSet存储学生对象并遍历
按照正常的写法,我们一运行就报错了
java.lang.ClassCastException:类型转换异常
由于我这里创建TreeSet对象调用的是无参构造方法,所以走的是自然排序
而底层源码有一步向下转型
Comparable<? super K> k = (Comparable<? super K>) key;
报错了
原因是我们Student3类没有实现Comparable接口,无法向下转型,所以报错了。
*/
public class TreeSetDemo2 {
public static void main(String[] args) {
//创建集合对象
TreeSet<Student3> ts = new TreeSet<>();
//创建学生对象
Student3 s1 = new Student3("周姐",24);
Student3 s2 = new Student3("李元浩",25);
Student3 s3 = new Student3("李湘赫",22);
Student3 s4 = new Student3("汉子哥",26);
Student3 s5 = new Student3("硬币哥",21);
Student3 s6 = new Student3("乌兹",20);
Student3 s7 = new Student3("李元浩",25);
Student3 s8 = new Student3("厂长",25);
//将学生对象插入到集合中
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
ts.add(s6);
ts.add(s7);
ts.add(s8);
for (Student3 s:ts){
System.out.println(s);
}
}
}