各自努力,最高处见!加油!
集合(三)
一、Set接口
1.set接口介绍
- 无序(添加和取出的顺序不一致),没有索引
- 不允许重复元素,最多包含一个null
2.遍历方式
- 迭代器
- 增强for循环(底层也是迭代器)
- 不能使用索引方式获取(即没有get方法)
3.常用方法使用
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class Set01 {
public static void main(String[] args) {
//用HashSet子类来演示Set类的使用方法
Set set=new HashSet();
set.add("john");
set.add("lucy");
set.add("john");
set.add("jack");
set.add(null);
set.add(null);
System.out.println(set);//[null, john, lucy, jack]
// Set接口实现类的对象不能存放重复的元素
// 存放的数据是无序的
// 添加的元素与取出的元素顺序不一致(取出的顺序是一定的,底层有算法来进行排序)
//迭代器遍历
Iterator iterator=set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
//增强for循环遍历
for (Object o :set) {
System.out.println(o);
}
//删除元素
set.remove(null);
System.out.println(set);
}
}
二、HashSet接口实现类
HashSet的底层代码比较重要,复习韩顺平老师的522~525课
https://www.bilibili.com/video/BV1fh411y7R8?p=521
hash()+equals()
- HashSet底层是HashMap
- 添加一个元素时,先得到一个hash值–>转变为索引值。HashSet首先比较的就是hash值,不同对象的hash值不同,即使里面存放的数据相同,也会添加到数组中。(默认条件下,重写了hashCode方法的情况除外)。
- 找到存储数据表table,看这个索引位置是否已经存放元素
- 如果没有,直接加入
- 如果有,调用equals比较,如果相同,就放弃添加,如果不相同,则添加到最后
- 在JDK8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认为8),并且table大小>=MIN_TREEIFY_CAPACITY(默认为64)就会转化为红黑树。
- 哈希值不是HashCode
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
HashMap.Node[] tab;//用的是HashMap
int n;
if ((tab = this.table) == null || (n = tab.length) == 0) {//table是HashMap的一个成员变量
n = (tab = this.resize()).length;
}
Object p;
int i;
if ((p = tab[i = n - 1 & hash]) == null) {
tab[i] = this.newNode(hash, key, value, (HashMap.Node)null);
} else {
Object e;
Object k;
if (((HashMap.Node)p).hash == hash && ((k = ((HashMap.Node)p).key) == key || key != null && key.equals(k))) {
e = p;
} else if (p instanceof HashMap.TreeNode) {
e = ((HashMap.TreeNode)p).putTreeVal(this, tab, hash, key, value);
} else {
int binCount = 0;
while(true) {
if ((e = ((HashMap.Node)p).next) == null) {
((HashMap.Node)p).next = this.newNode(hash, key, value, (HashMap.Node)null);
if (binCount >= 7) {
this.treeifyBin(tab, hash);
}
break;
}
if (((HashMap.Node)e).hash == hash && ((k = ((HashMap.Node)e).key) == key || key != null && key.equals(k))) {
break;
}
p = e;
++binCount;
}
}
if (e != null) {
V oldValue = ((HashMap.Node)e).value;
if (!onlyIfAbsent || oldValue == null) {
((HashMap.Node)e).value = value;
}
this.afterNodeAccess((HashMap.Node)e);
return oldValue;
}
}
++this.modCount;
if (++this.size > this.threshold) {
this.resize();
}
this.afterNodeInsertion(evict);
return null;
}
HashSet的扩容机制
- HashSet底层是HashMap,第一次添加时,table数组扩容到16,临界值(threshold)是16,加载因子loaFactor是0.75.
- 如果table数组使用到了临界值12,就会扩容到162=32,新的临界值就是320.75=24,以此类推。
举例说明:
第一次添加table数组扩容到16,继续添加。
当添加了12个元素后,数组再次扩容,扩容到16的两倍32 。
当添加了32×0.75=24个元素后,数组再次扩容,扩容到32的两倍64 。
当添加了64×0.75=48个元素后,数组再次扩容,扩容到64的两倍128 。
如此类推
注:只要是往数组中添加了元素,不管添加在那个链表,都是增加了元素。 - 在JDK8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认为8),并且table大小>=MIN_TREEIFY_CAPACITY(默认为64)就会转化为红黑树,否则仍然采用数组扩容机制(元素个数大于8,且table大小大于64==>红黑树)。
调试程序:
import java.util.HashSet;
public class HashSetlncrement {
public static void main(String[] args) {
HashSet hashSet=new HashSet();
for (int i = 0; i < 100; i++) {
hashSet.add(new A(i));
}
}
}
class A{
private int n;
public A(int n){
this.n=n;
}
@Override
public int hashCode() {
return 100;
}
}
三、HashSet练习
1.习题一
定义一个类,该类包括:private成员属性name,age。要求:
- 创建3个Employee对象放入HashSet中
- 当name和age的值相同时,认为是相同员工,不能添加到HashSet中。
import java.util.HashSet;
import java.util.Objects;
public class HashSetHomework {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
hashSet.add(new Employee("jack",18));
hashSet.add(new Employee("tom",20));
hashSet.add(new Employee("jack",18));
System.out.println(hashSet);
}
}
class Employee{
String name;
int age;
public Employee(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Employee)) return false;
Employee employee = (Employee) o;
return age == employee.age &&
Objects.equals(name, employee.name);
}
@Override
public int hashCode() {//name和age都相同,在计算hashCode时返回相同的结果
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
注意:要同时重写hashCode和equals方法才能实现不重复添加的功能。
2.习题二
定义一个Employee类,该类包含:privvate成员属性name,money,birthday(MyDate类型),其中MyDate的类型包括year,month,day。要求:
- 创建3个Employee放入HashSet中。
- 当name和birthday的值相同时,认为是相同员工,不能添加到HashSet集合中。
import java.util.HashSet;
import java.util.Objects;
public class HashSetHomework {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
hashSet.add(new Employee("jack",18));
hashSet.add(new Employee("tom",20));
hashSet.add(new Employee("jack",18));
System.out.println(hashSet);
}
}
class Employee{
String name;
int age;
public Employee(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Employee)) return false;
Employee employee = (Employee) o;
return age == employee.age &&
Objects.equals(name, employee.name);
}
@Override
public int hashCode() {//name和age都相同,在计算hashCode时返回相同的结果
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}