package com.jshedu.Set_;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* @author 韩顺平
* @version 1.0
*/
@SuppressWarnings({"all"})
public class SetMethod {
public static void main(String[] args) {
//老韩解读
//1. 以Set 接口的实现类 HashSet 来讲解Set 接口的方法
//2. set 接口的实现类的对象(Set接口对象), 不能存放重复的元素, 可以添加一个null
//3. set 接口对象存放数据是无序(即添加的顺序和取出的顺序不一致)
//4. 注意:取出的顺序的顺序虽然不是添加的顺序,但是他是固定的.
Set set = new HashSet();
set.add("john");
set.add("lucy");
set.add("john");//重复
set.add("jack");
set.add("hsp");
set.add("mary");
set.add(null);//
set.add(null);//再次添加null
for(int i = 0; i <10;i ++) {
System.out.println("set=" + set);
}
//遍历
//方式1: 使用迭代器
System.out.println("=====使用迭代器====");
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println("obj=" + obj);
}
set.remove(null);
//方式2: 增强for
System.out.println("=====增强for====");
for (Object o : set) {
System.out.println("o=" + o);
}
//set 接口对象,不能通过索引来获取
}
}
遍历只有两种方法
package com.jshedu.Set_;
import java.util.HashSet;
import java.util.Set;
/**
* @author 韩顺平
* @version 1.0
*/
@SuppressWarnings({"all"})
public class HashSet_ {
public static void main(String[] args) {
//老韩解读
//1. 构造器走的源码
/*
public HashSet() {
map = new HashMap<>();//实际上是HashMap
}
2. HashSet 可以存放null ,但是只能有一个null,即元素或对象不能重复
3.HashSet不保证元素是有序的,取决于hash后,在确定索引的结果
(不保证存放顺序和取出顺序一样)
*/
Set hashSet = new HashSet();
hashSet.add(null);
hashSet.add(null);
System.out.println("hashSet=" + hashSet);
}
}
HashSet讲解
HashSet底层原理
链表在指定位置添加结点
package com.jshedu.Set_;
/**
* @author 韩顺平
* @version 1.0
*/
@SuppressWarnings({"all"})
public class HashSetStructure {
public static void main(String[] args) {
//模拟一个HashSet的底层 (HashMap 的底层结构)
//1. 创建一个数组,数组的类型是 Node[]
//2. 有些人,直接把 Node[] 数组称为 表
Node[] table = new Node[16];
//3. 创建结点
Node john = new Node("john", null);
table[2] = john;
//在链表中,分两步
//1.先创建要加入的结点
//2.用属性next,把结点的地址赋给它。就把结点连起来了
Node jack = new Node("jack", null);
john.next = jack;// 将jack 结点挂载到john
Node rose = new Node("Rose", null);
jack.next = rose;// 将rose 结点挂载到jack
Node lucy = new Node("lucy", null);
table[3] = lucy; // 把lucy 放到 table表的索引为3的位置.
System.out.println("table=" + table);
}
}
class Node { //结点, 存储数据, 可以指向下一个结点,从而形成链表
Object item; //存放数据
Node next; // 指向下一个结点
public Node(Object item, Node next) {
this.item = item;
this.next = next;
}
}
在数组的指定位置添加链表形式的数据。
HashSet添加元素底层是如何实现的
HashSet底层是HashMap
添加一个元素时,先得到hash值-->会转成--->索引值
找到存储数据表table,看这个索引位置是否已经存放的有元素
如果没有,直接加入
如果有,调用equals比较,如果相同,就放弃添加,如果不相同,则添加到最后
在java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)。【如果table的大小还没有到64,但链表的个数超过8,那么table就会扩容,2倍扩容】
package com.jshedu.Set_;
import java.util.HashSet;
/**
* @author 韩顺平
* @version 1.0
*/
@SuppressWarnings({"all"})
public class HashSetSource {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
hashSet.add("java");//到此位置,第1次add分析完毕.
hashSet.add("php");//到此位置,第2次add分析完毕
hashSet.add("java");
System.out.println("set=" + hashSet);
/*
老韩对HashSet 的源码解读
1. 执行 HashSet()
public HashSet() {
map = new HashMap<>();
}
2. 执行 add()
public boolean add(E e) {//e = "java"
return map.put(e, PRESENT)==null;//(static) PRESENT = new Object();
}
3.执行 put() , 该方法会执行 hash(key) 得到key对应的hash值 算法h = key.hashCode()) ^ (h >>> 16)
hash值不是hashCode,是经过处理的hashCode。右移16位
public V put(K key, V value) {//key = "java" value = PRESENT 共享
return putVal(hash(key), key, value, false, true);
}
4.执行 putVal
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node[] tab; Node p; int n, i; //定义了辅助变量
//table 就是 HashMap 的一个数组,类型是 Node[]
//if 语句表示如果当前table 是null, 或者 大小=0
//就是第一次扩容,到16个空间.
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//(1)根据key,得到hash 去计算该key应该存放到table表的哪个索引位置
//并把这个位置的对象,赋给 p
//(2)判断p 是否为null
//(2.1) 如果p 为null, 表示还没有存放元素, 就创建一个Node (key="java",value=PRESENT)
//(2.2) 就放在该位置 tab[i] = newNode(hash, key, value, null)
//i = (n - 1) & hash,决定元素放到那个位置
if ((p = tab[i = (n - 1) & hash]) == null)//p指向表的一个结点
tab[i] = newNode(hash, key, value, null);
else {
//一个开发技巧提示: 在需要局部变量(辅助变量)时候,在创建
Node e; K k; //
//如果当前索引位置对应的链表的第一个元素和准备添加的key的hash值一样
//并且满足 下面两个条件之一:
//(1) 准备加入的key 和 p 指向的Node 结点的 key 是同一个对象,这个就是地址把
//(2) p 指向的Node 结点的 key 的equals() 和准备加入的key比较后相同,这个就是内容吧
//就不能加入
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//再判断 p 是不是一颗红黑树,
//如果是一颗红黑树,就调用 putTreeVal , 来进行添加
else if (p instanceof TreeNode)
e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);
else {//如果table对应索引位置,已经是一个链表, 就使用for循环比较
//(1) 依次和该链表的每一个元素比较后,都不相同, 则加入到该链表的最后
// 注意在把元素添加到链表后,立即判断 该链表是否已经达到8个结点
// , 就调用 treeifyBin() 对当前这个链表进行树化(转成红黑树)
// 注意,在转成红黑树时,要进行判断, 判断条件
// if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64))
// resize();
// 如果上面条件成立,先table扩容。(就是虽然你的链表有8个元素,但数组还没有到64个)
// 只有上面条件不成立时,才进行转成红黑树
//(2) 依次和该链表的每一个元素比较过程中,如果有相同情况,就直接break
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD(8) - 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;
//size 就是我们每加入一个结点Node(k,v,h,next), size++
if (++size > threshold)
resize();//扩容
afterNodeInsertion(evict);
return null;
}
*/
}
}
add
HashSet的扩容和转成红黑树机制
当一个链表上元素个数是8之后再在该链表上添加元素,会导致table扩容,按扩容机制来扩容,而且链表的hash值决定的table数组下标也会发生改变。链表上在添加,table在扩容,当table大于64的时候,链表会被树化,不在扩容。
package com.jshedu.Set_;
import java.util.HashSet;
import java.util.Objects;
/**
* @author 韩顺平
* @version 1.0
*/
@SuppressWarnings({"all"})
public class HashSetIncrement {
public static void main(String[] args) {
/*
HashSet底层是HashMap, 第一次添加时,table 数组扩容到 16,
临界值(threshold)是 16*加载因子(loadFactor)是0.75 = 12
如果table 数组使用到了临界值 12,就会扩容到 16 * 2 = 32,
新的临界值就是 32*0.75 = 24, 依次类推
*/
HashSet hashSet = new HashSet();
// for(int i = 1; i <= 100; i++) {
// hashSet.add(i);//1,2,3,4,5...100
// }
/*
在Java8中, 如果一条链表的元素个数到达 TREEIFY_THRESHOLD(默认是 8 ),
并且table的大小 >= MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树),
否则仍然采用数组扩容机制
*/
// for(int i = 1; i <= 12; i++) {
// hashSet.add(new A(i));//
// }
/*
当我们向hashset增加一个元素,-> Node -> 加入table , 就算是增加了一个size++
size和扩容有关(不是table达到0.75才扩容,只要链表元素的个数达到0.75也扩容
不管table上有几个链表)
*/
for(int i = 1; i <= 7; i++) {//在table的某一条链表上添加了 7个A对象
hashSet.add(new A(i));//
}
for(int i = 1; i <= 7; i++) {//在table的另外一条链表上添加了 7个B对象
hashSet.add(new B(i));//
}
}
}
class B {
private int n;
public B(int n) {
this.n = n;
}
@Override
public int hashCode() {
return 200;
}
}
class A {
private int n;
public A(int n) {
this.n = n;
}
@Override
public int hashCode() {
return 100;
}
}
hashCode值是怎么算出来的不用管,内部有一个数学算法,尽量不同的Object数组得到的hashCode不同。
例题
package com.jshedu.Set_;
import java.util.HashSet;
import java.util.Objects;
/**
* @author Mr.jia
* @version 1.0
*/
public class Homework01 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
HashSet obj = new HashSet();
obj.add(new Employee("jack",20));
//在add过程中就用hashCode()方法进行比较name,age
obj.add(new Employee("tom",22));
obj.add(new Employee("tom",32));
obj.add(new Employee("jack",20));//加不进去
//重写hashCode(),equals()前
//这里加入四个对象,为什么是四个,第四个和第一个不是一样吗
//答:创建四个对象,返回的hash值不一样,所以在table表中
// 存储的位置也就不同
//重写后会对name,age进行比较
System.out.println(obj.toString());
}
}
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 +
'}';
}
@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相同就返回相同的hash值
@Override
public int hashCode() {
return Objects.hash(name, age);
//先比较返回hash值是否相同,相同在比较内容,
//内容不同就直接放链表尾部
//相同就不加入
}
}
重写hashCode(),和equals()
package com.jshedu.Set_;
import java.util.HashSet;
import java.util.Objects;
/**
* @author Mr.jia
* @version 1.0
*/
public class Homework02 {
public static void main(String[] args) {
HashSet
参数是引用类型怎么处理myDate