Java自学笔记—HashSet
Set接口特点
Set接口为Collection接口的一个子类
无序,没有索引
不允许重复,最多一个null
常用方法和Collection一样,但不能用索引获取元素
/**
* @author Lspero
* @version 1.0
*/
@SuppressWarnings("all")
public class SetMethod {
public static void main(String[] args) {
/*
* 无序,没有索引
* 不允许重复,最多一个null
* 常用方法和Collection一样,但不能用索引获取元素
* */
Set set = new HashSet();
//set接口实现类对象不能存放重复对象,可以添加一个null
//数据存放时是无序的,但取出的顺序是固定的
set.add("jhon");
set.add("lucy");
set.add("jhon");
set.add("jack");
set.add(null);
set.add(null);
System.out.println("Set = " + set);
//遍历 迭代器和增强For循环
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println(next);
}
System.out.println("=========");
for (Object o :set) {
System.out.println(o);
}
//Set接口对象不能通过索引获取
}
}
HashSet底层
HashSet底层是HashMap
/**
* @author Lspero
* @version 1.0
*/
public class HashSet_ {
public static void main(String[] args) {
Set hashSet = new HashSet();
/* 1.HashSet实际上是HashMap,数组 + 链表
public HashSet() {
map = new HashMap<>();
}
2.可以存放一个null,不能有重复元素
3.不保证元素是有序的,取决于hash后,再确定索引结果
* */
hashSet.add(null);
//添加失败返回flase
System.out.println(hashSet.add(null));
System.out.println(hashSet);
}
}
自定义一个类,数组 + 链表,模拟HashMap
数据结构示意如下图
/**
* @author Lspero
* @version 1.0
*/
public class HashSetStructure {
public static void main(String[] args) {
// 模拟HashMap底层(HashSet),数组 + 链表,为了提高效率
//1.创建一个Node[]数组
Node[] table = new Node[16];
Node jhon = new Node("jhon", null);
table[2] = jhon;
Node jack = new Node("jack", null);
jhon.next = jack;//将jack结点挂载到jhon
Node rose = new Node("rose", null);
jack.next = rose;//将rose结点挂载到jack
Node lucy = new Node("lucy", null);
table[3] = lucy;
System.out.println("table = " + table);
}
}
//结点,可以指向下一个结点,从而形成链表
class Node{
Object item;
Node next;
public Node(Object item, Node next) {
this.item = item;
this.next = next;
}
}
HashSet元素添加过程
添加元素过程
1. HashSet底层是HashMap
2. 添加一个元素,先得到hash值,然后转成索引值
3. 找到存储数据表table,看这个索引是否有已经存放的元素
4. 如果没有,则直接加入
5. 如果有,则调equals比较,如果相同,就放弃添加,如果不同,就添加到该条链表最后,equals可以重写
6. 在jdk8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认为8)
并且table大小>=MIN_TREEIFY_CAPACITY(默认为64),就会进行树化(红黑树)
如果链表到8,table没到64,会扩table
1. HsahSet底层为HashMap,第一次添加时,table数组扩容到16,临界值(threshold)为16加载因子(loadFactor = 0.75) = 12
2. 如果table使用到临界值12,就会扩容到162=32,新的临界值就是32*0.75=24,以此类推
3. 每加入一个Node(hash, key, vaule ,null)结点,size就会加一,不一定要同一链表,一共有12个后就开始扩容
equals()和hashCode()重写
重写equals()
和hashCode()
方法可以指定类的具体实例是否能够加入Set
hashCode()
返回该实例Hash值,经过add()方法确定该实例在table表中的索引
equals()
用于判断两个实例是否为同一实例,即相同时是否加入链表
import java.util.HashSet;
import java.util.Objects;
/**
* @author Lspero
* @version 1.0
*/
public class HashSetExercise {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
//三个哈希值不同,一定可以添加
//重写equals()和hashCode()后,第二个就添加不了
hashSet.add(new Employee("milan", 18));
hashSet.add(new Employee("smith", 18));
hashSet.add(new Employee("milan", 18));
System.out.println(hashSet);
}
}
class Employee{
private String name;
private int age;
public Employee(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 "Employee{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
//如果name和age相同,则确定为相同对象,返回true
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return age == employee.age && Objects.equals(name, employee.name);
}
//如果name和age相同,则返回相同哈希值
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}