首页 > 其他分享 >3、Set接口

3、Set接口

时间:2023-01-25 22:56:13浏览次数:49  
标签:Set hash name 接口 add set key null

1、Set接口基本介绍

  1. 无序(添加和取出的顺序不一致),没有索引
  2. 不允许重复元素,所以最多包含一个null
  3. JDK API中Set接口的实现类有AbstractSet , ConcurrentHashMap.KeySetView , ConcurrentSkipListSet , CopyOnWriteArraySet , EnumSet , HashSet , JobStateReasons , LinkedHashSet , TreeSet

2、Set接口的常用方法

  • 和List接口一样,Set接口也是Collection的子接口,因此,常用方法和Collection接口一样

3、Set接口的遍历方式

  • 同Collection的遍历方式一样,因为Set接口是Collection接口的子接口
    1. 可以使用迭代器
    2. 增强for循环
    3. 不能使用索引的方式来获取
import java.util.Iterator;
import java.util.Set;
import java.util.HashSet;

@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(null);
        set.add(null);
        System.out.println("set=" + set);

        //遍历
        //方式1:使用迭代器
        Iterator iterator= set.iterator();
        while (iterator.hasNext()) {
            Object obj = iterator.next();
            System.out.println("obj=" + obj);
        }

        System.out.println("===============");
        //方式2:增强for循环
        for (Object o : set) {
            System.out.println("0=" + o);
        }
    }
}
/*
	运行结果:
        set=[null, hsp, john, lucy, jack]
        obj=null
        obj=hsp
        obj=john
        obj=lucy
        obj=jack
        ===============
        0=null
        0=hsp
        0=john
        0=lucy
        0=jack
*/

4、HashSet的全面说明

  1. HashSet实现了Set接口

  2. HashSet实际上是HashMap,

    public HashSet() {
        map = new HashMap<>();
    }
    
  3. 可以存放null值,但是只能有一个null

  4. HashSet不保证元素是有序的,取决于hash后,再确定索引的结果

  5. 不能有重复元素/对象

  6. 分析HashSet底层是HashMap,HashMap底层是(数组+链表+红黑树)

  7. HashSet的添加元素底层时如何实现

    • HashSet底层是 HashMap
    • 添加一个元素时看,先得到hash值 -会转成 - > 索引值
    • 找到存储数据表table,看到这个索引位置是否已经存放有元素
    • 如果没有,直接加入
    • 如果有,调用 equals 比较,如果相同,就放弃添加,如果不相同,则添加到最后
    • 在Java8中,如果一条链表的元素个数到达TEEIFY_THRESHOLD(默认是8),并且table的大小 >= MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)
    package com.hspedu.set_;
    
    import java.util.HashSet;
    
    @SuppressWarnings({"all"})
    public class HashSetSource {
        public static void main(String[] args) {
    
            HashSet hashSet = new HashSet();
            hashSet.add("java");
            hashSet.add("php");
            hashSet.add("java");
            System.out.println("set=" + 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)
                    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<K,V>[] tab; Node<K,V> 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)
                        //
    
                        if ((p = tab[i = (n - 1) & hash]) == null)
                            tab[i] = newNode(hash, key, value, null);
                        else {
    
                            //一个开发技巧提示:在需要局部变量(辅助变量)时候,再创建
    
                            Node<K,V> 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<K,V>)p).putTreeVal(this, tab, hash, key, value);
                                
                                //如果table对应索引位置,已经有一个链表,就使用for循环比较
                                //(1) 依次和该链表的每一个元素比较后,都不相同,则加入到该链表的最后
                                //    注意在把元素添加到链表后,立即判断,该链表是否已经达到8个结点
                                //    , 如果达到,就调用 treeifyBin() 对当前这个链表进行树化(转成红黑树)
                                //    注意,在转成红黑树时,要进行判断,判断条件
                                //    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
                                //            resize();
                                //    如果上面条件成立,先table扩容,
                                //    只有上面条件不成立时,才进行转成红黑树
                                //(2) 依次和该链表的每一个元素比较过程中,如果有相同情况,就直接break
                                
                            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;
                    }
             */
    
        }
    }
    
    /*
    	运行结果:
    	set=[java, php]
    */
    
  8. HashSet的扩容和转成红黑树机制

    • HashSet底层是 HashMap,第一次添加时,table数组扩容到16,临界值(threshold)是16*加载因子,

      (loadFactor)是0.75 = 12

    • 如果table 数组使用到了临界值12,就会扩容到16 * 2 = 32,新的临界值就是32 * 0.75 = 24,依此类推

    • 在Java8中,如果一条链表的元素个数到达TEEIFY_THRESHOLD(默认是8),并且table的大小 >= MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树),否则仍然采用数组扩容机制

5、LinkedHashSet的全面说明

  1. LinkedHashSet 是 HashSet 的子类

  2. LinkedHashSet 底层是一个 LinkedHashMap,底层维护了一个 数组 + 双向链表

  3. LinkedHashSet 根据元素的hashCode 值来决定元素的存储位置,同时使用链表维护元素的次序(图),这使得元素看起来是以插入顺序保存的。

  4. LinkedHashSet 不允许添加重复元素

  5. 底层机制

    • 在LinkedHashSet 中维护了一个hash表二号双向链表(LinkedHashSet 有 head 和 tail)
    • 每一个结点有before 和after 属性,这样可以形成双向链表
    • 在添加一个元素时,先求hash值,在求索引,确定该元素在table 的位置,然后将添加的元素加入到双向链表(如果已经存在,不添加 【原则和hashset一样】)
    • 这样的话,我们遍历LinkedHashSet 也能确保插入顺序和遍历顺序一致
    package com.hspedu.set_;
    
    import java.util.LinkedHashSet;
    import java.util.Set;
    
    @SuppressWarnings({"all"})
    public class LinkedHashSetSource {
        public static void main(String[] args) {
    
            //分析LinkeHashSet的底层机制
    
            Set set = new LinkedHashSet();
            set.add(new String("AA"));
            set.add(456);
            set.add(456);
            set.add(new Customer("刘",1001));
            set.add(123);
            set.add("HSP");
    
            System.out.println("set = " + set);
    
            //1. LinkedHashSet  加入顺序和取出元素/数据的顺序一致
            //2. LinkedHashSet 底层维护的是一个LinkedHashMap(是HashMap的子类)
            //3. LinkedHashSet 底层结构 (数组+双向链表)
            //4. 添加第一次时,直接将 table 扩容到 16 ,存放的结点类型是 LinkedHashMap$Entry
            //5. 数组是 HashMap$Node[] 存放的元素/数据是 LinkedHashMap$Entry类型
            /*
                     //继承关系是在内部类完成
                     static class Entry<K,V> extends HashMap.Node<K,V> {
                        Entry<K,V> before, after;
                        Entry(int hash, K key, V value, Node<K,V> next) {
                            super(hash, key, value, next);
                        }
                    }
             */
    
        }
    }
    
    class Customer {
        private String name;
        private int no;
    
        public Customer(String name, int no) {
            this.name = name;
            this.no = no;
        }
    }
    
    /*
       运行结果:
       set = [AA, 456, com.hspedu.set_.Customer@154617c, 123, HSP]
    */
    
  6. 练习

    package com.hspedu.set_;
    
    import java.util.LinkedHashSet;
    import java.util.Objects;
    
    @SuppressWarnings({"all"})
    public class LinkedHashSetExercise {
        public static void main(String[] args) {
    
            LinkedHashSet linkedHashSet = new LinkedHashSet();
            linkedHashSet.add(new Car("奥拓",1000));
            linkedHashSet.add(new Car("奥迪",300000));
            linkedHashSet.add(new Car("法拉利",100000000));
            linkedHashSet.add(new Car("奥迪",300000));
            linkedHashSet.add(new Car("保时捷",700000000));
            linkedHashSet.add(new Car("奥迪",300000));
    
            System.out.println("linkedHashSet = " + linkedHashSet);
    
        }
    }
    
    class Car {
        private String name;
        private double price;
    
        public Car(String name, double price) {
            this.name = name;
            this.price = price;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public double getPrice() {
            return price;
        }
    
        public void setPrice(double price) {
            this.price = price;
        }
    
        @Override
        public String toString() {
            return "\n" + "Car{" +
                    "name='" + name + '\'' +
                    ", price=" + price +
                    '}';
        }
    
        //重写equals 方法 和 hashCode
        //当 name 和 price 相同时,就返回相同的 hashCode 值,equas返回t
        
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Car car = (Car) o;
            return Double.compare(car.price, price) == 0 && Objects.equals(name, car.name);
        }
    
        @Override
        public int hashCode() {
            return Objects.hash(name, price);
        }
    }
    
    /*
    	运行结果:
    	linkedHashSet = [
        Car{name='奥拓', price=1000.0}, 
        Car{name='奥迪', price=300000.0}, 
        Car{name='法拉利', price=1.0E8}, 
        Car{name='保时捷', price=7.0E8}]
    
    */
    

标签:Set,hash,name,接口,add,set,key,null
From: https://www.cnblogs.com/muzhe777/p/17067398.html

相关文章

  • PHP获取微信JS-SDK接口设置(access_token、jsapi_ticket、signature)
    前端引入JS文件<scriptsrc="http://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>或<scriptsrc="http://res2.wx.qq.com/open/js/jweixin-1.6.0.js"></script......
  • VC下这样创建实例m_pRecordset.CreateInstance(_uuidof(Connection))不一定成功!...
    //昨天在打开数据库的记录集时,出现了一个错误,错误提示为无效的指针类型,上网查了一下,原来是我的//的程序在创建实例时,出现了错误,呵呵,以前我都是这样创建实例的:m_pRecordset.......
  • 注意:Java的ResultSet对象,默认是不可更新的!...
     注意啊!Java的ResultSet对象,默认是不可更新的,仅有一个向前移动的指针,因此,只能遍历它一次,并且只能按从第一行到最后一行的顺序进行。如果需要ResultSet指针具有移动和可......
  • Vue3 setup 如何添加name
    Vue3中name有什么用呢?1.在递归组件的时候需要定义name2.配合keep-aliveincludeexclude可以缓存组件3.在Vue有报错或者调试的时候可以看到组件的nameVue3定义name1.自动......
  • Vue $set 源码解析(保证你也能看懂)
    首先我们看文档有这个例子下面是vue$set部分源码if(process.env.NODE_ENV!=='production'&&(isUndef(target)||isPrimitive(target))){warn('Cannotset......
  • v8 setup
    记录下笔者本人搭建v8环境的过程,记得挂好代理环境:处于一些原因笔者选择在kali2023上搭建v8gitclonehttps://chromium.googlesource.com/chromium/tools/depot_tools.g......
  • 56、商品上架---远程上架接口
    @Overridepublicvoidup(LongspuId){List<SkuEsModel>upProducts=newArrayList<>();//查询所有该spu的规格属性List<ProductAtt......
  • Rust 中 HashSet 的基础用法示例
    代码:usestd::collections::{HashSet,HashMap};fnmain(){letmutset:HashSet<i32>=HashSet::new();set.insert(1);set.insert(2);//插入元素......
  • Python实现头条项目接口自动化测试
    目标1.熟悉接口自动化测试的流程2.能够对一个项目的接口实现自动化测试接口自动化测试流程1.需求分析2.挑选需要做自动化测试的功能3.设计测试用例4.搭建自动化测试环......
  • 看懂了这篇,你就懂了函数式接口
    看懂了这篇-你就懂了函数式接口函数式编程是一种编程规范或一种编程思想,简单可以理解问将运算或实现过程看做是函数的计算。Java8为了实现函数式编程,提出了3个重要的概念:L......