Set接口和常用方法
Set接口得基本介绍
1)无序(添加和去除得顺序不一致),没有索引
2)不允许重复元素,所以最多包含一个null
3)JDK API中set接口的实现类由
//1、以hashset为接口Set的实现类讲解
//2、set接口的对象(Set接口对象)不能存放重复的元素 可以添加一个null
//3、set 接口对象存放对象数据是无序的 即添加的顺序和取出的顺序是不一致的
//4、注意:取出的顺序的顺序虽然不是添加的顺序,但是是固定的
set遍历
package com.company.Collection.Set_;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class SetMethod {
public static void main(String[] args) {
//1、以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(null);
set.add(null);
System.out.println(set);
//遍历
//1、使用迭代器
System.out.println("====Iterator迭代器====");
Iterator iterator = set.iterator();
while (iterator.hasNext()){
Object object = iterator.next();
System.out.println(object);
}
//方式二、增强for
System.out.println("=====增强for=====");
for (Object object:set) {
System.out.println(object);
}
}
}
[null, john, lucy, jack]
2、Set接口的实现类——HashSet
HashSet的全面说明
1)HashSet实现了Set接口
2)HashSet实际上是HashMap
public HashSet() {
map = new HashMap<>();
}
3)可以存放null值,但是智能由一个null
4)HashSet不保证元素是有序的 取决于hash后再确定索引的结果()
5)不能有重复的元素/对象,在前面set接口使用
package com.company.Collection.Set_;
import java.util.HashSet;
public class HashSet01 {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
//说明
//1、在执行add方法后 会返回一个boolean值
//2、如果添加成功返回true 否则返回false
//3、可以通过remove 指定删除的哪一个值
System.out.println(hashSet.add("z"));
System.out.println(hashSet.add("j"));
System.out.println(hashSet.add("q"));
System.out.println(hashSet.add("l"));
System.out.println(hashSet.add("m"));
hashSet.remove("m");
//5、hashset不能添加相同用的元素/数据(在java JVM中放入常量池中)
System.out.println("hashset="+hashSet);
hashSet = new HashSet();
hashSet.add("ll");
hashSet.add("ll");
hashSet.add(new Dog("zjq")); //可以加入到hashset中
hashSet.add(new Dog("zjq")); //可以加入到hashset中
System.out.println(hashSet);
//加深理解 经典面试题
//
}
}
class Dog{
private String name;
public Dog(String name){
this.name = name;
}
}
其中new Dog可以添加 因为是new 了两个新类 他们俩的hashcode不同
静态调用的String 不能使用,两者的 String 直接进入到常量池中 指向同一哥常量池
HashSet底层机制
分析HashSet地城是HashMap HashMap底层是(数组+链表+红黑树)
Set接口类:
1、HashSet底层HashMap
2、添加一个元素时先得到hash值 会转成 索引值
3、找到存储数据表table这个索引位置是否已经存放的元素
4、如果没有直接加入
5、如果有调用equals比较 如果相同就放弃添加 如果不相同就添加到最后
6、在java8中 如果一条链表元素个数超过默认的8而且每个table>=64 就转化为红黑树
开发技巧补充:
在什么地方需要变量就在哪里添加
1、先获取元素的hash值
2、对hash值进行运算 得出一个索引值 即为所要存放的hash表总的位置号
如果该位置上有已经有其他元素则需要对equals判断是否相同
补充英语单词
threshold 门槛
1、执行HashSet
public HashSet(){
map = new HashMap<>();
}
2、执行
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
3、执行put
public V put(K key, V value) { 得到key对应hash值 算法h= key.hashCode()^(h>>16)
return putVal(hash(key), key, value, false, true);
}
4、执行
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i; //定义辅助变量
//使用map里面的table变量 数组是null
//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
//(2.2) 就放在该位置tab[i] = new Node(hash, key, value, null)
如果当前索引位置对应的链表的第一个元素和准备添加的key的hash值一样
并且满足 (1)准备加入的key和p指向的node节点的key是同一个对象
(2)p指向的node节点的key的equals 和准备加入的key比较后相同
//再判断P是不是一颗红黑树
//如果是一颗红黑树 就调用putTreeVal 来进行添加
package com.company.Collection.Set_;
import java.util.HashSet;
public class HashSetSource {
public static void main(String[] args) {
//分析hashset 的添加元素是如何hash()+equals()
HashSet hashSet = new HashSet();
hashSet.add("java");
hashSet.add("php");
hashSet.add("java");
System.out.println("set="+hashSet);
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
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 {//如果table对应的索引位置 已经是一个链表就用for循环来进行比较
//(1) 依次和该链表的每一个元素进行表后 都不相同则进行 则加入到该链表中
//把元素添加到链表后,立即判断 该链表是否达有8个节点 即node =7 就会树化
//,就调用该链表treeifyBin(),
注意在进行树化时
注意,在转成红黑树 要进行判断 如果该table数组的大小《
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
如果上面条件成立 先对table进行扩容
只有上面条件不成立时才转换成红黑树
//(2)依次和该链表的每一个元素进行比较过程中 如果有相同的情况 直接break
static final float DEFAULT_LOAD_FACTOR = 0.75f;
hash临界值就是0.75 每次达到临界值的 四分之三 就进行扩容
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;
//每次加入一个节点 Node(k,v,h node)
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
* */
}
}
16-》24-》48
1620.75+24临界值
3220.75=48临界值
package com.company.Collection.Set_;
import java.util.HashSet;
public class HashSetIncreatment {
public static void main(String[] args) {
/**
* HashSet 底层HashMap 第一次添加时table 数组扩容到16
* 临界值(threadHold)是16*加载银子loadFactor是0。75*16 = 12
* 如果table 数组用到临界值 12 就会扩容到 16*2 32
* 新的临界值 就是32*0.75 = 24 一次类推
*
* */
HashSet hashSet = new HashSet();
// for (int i = 0; i <100 ; i++) {
// hashSet.add(i);
// }
for (int i = 0; i < 7 ; i++) {
hashSet.add(new A(i));
}
for (int i = 0 ; i <=7 ; i++) {//在table 某一条链表上添加一个值 7个对象
hashSet.add(new B(i));
}
}
}
class A{
private int n;
public A(int n){
this.n = n;
}
@Override
public int hashCode(){
return 100;
}
}
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;
}
}
class B{
private int n;
public B(int n){
this.n = n;
}
@Override
public int hashCode(){
return 200;
}
}