IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> PHP知识库 -> 集合——Set接口及其实现类【HashSet、TreeSet】 -> 正文阅读

[PHP知识库]集合——Set接口及其实现类【HashSet、TreeSet】

Set 接口基本介绍

  1. 无序(添加和取出的顺序不一致),没有索引
  2. 不允许重复元素,所以最多包含一个null


package collection_;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

/**
 * @Author: Gin
 * @Description: 以 Set 接口的实现类 HashSet 来讲解 Set 接口
 * @Modified By: Gin
 * @Date: Created in 14:42 2021/9/17
 */
public class SetMethod {
    public static void main(String[] args) {

        // 1. set 接口的实现类的对象,不能存放相同的元素,可以存放一个 null
        // 2. set 接口对象存放数据是无序的(即添加的数据和取出的数据的顺序不一致)
        // 3. 注意:去除的数据虽然是无序的,但是顺序是固定的
        Set set = new HashSet();
        set.add(null);
        set.add("Gin");
        set.add("Vodka");
        set.add("Sherry");
        set.add("Vermouth");
        set.add("Vodka"); // 再次添加 Vodka
        set.add(null); // 再次添加 null

        for(int i = 0; i < 5; i++) {
            System.out.println(set);
        }


        // 迭代器遍历 Set 对象
        System.out.println("==迭代器==");
        Iterator iterator = set.iterator();
        while (iterator.hasNext()) {
            Object next = iterator.next();
            System.out.println(next);
        }

        // 增强 fro 遍历 Set 对象
        System.out.println("==增强 for==");
        for (Object o : set) {
            System.out.println(o);
        }


    }
}


在这里插入图片描述

HashSet

  1. HashSet实现了Set接口
  2. HashSet实际上是HashMap
  3. 可以存放null值,但是只能有一个null
  4. HashSet不保证元素是有序的,取决于hash后,再确定索引的结果(即,不保证存放元素的)
  5. 不能有重复元素/对象
  6. String字符出存放位置(常量池)

package collection_;

import java.util.HashSet;
import java.util.Set;

/**
 * @Author: Gin
 * @Description:
 * @Modified By: Gin
 * @Date: Created in 15:07 2021/9/17
 */
public class HashSet_ {
    public static void main(String[] args) {
        // new HashSet() 底层就是 HashMap
        /*
            public HashSet() {
                map = new HashMap<>();
            }
         */
        Set set = new HashSet();
        set.add(null);
        set.add(null);
        System.out.println(set);

        // 1. 在执行 add 方法后,会返回一个 boolean 值
        // 2. 添加成功返回 true,失败返回 false
        System.out.println(set.add("Gin"));
        System.out.println(set.add("Gin"));
        System.out.println(set.add("Vodka"));
        System.out.println(set.add("Sherry"));
        System.out.println(set.add("Vermouth"));
        System.out.println(set);

        // 3. 通过 remove 方法执行删除元素,此方法也返回一个 boolean 值
        System.out.println(set.remove("Vodka"));
        System.out.println(set);


        set = new HashSet();
        // 4. HashSet 不能添加相同元素
        set.add("lucy"); // True
        set.add("lucy"); // False
        set.add(new Tiger("Gin")); // True
        set.add(new Tiger("Gin")); // True
        System.out.println(set);


        // 5. 此处源码解析
        set = new HashSet();
        set.add(new String("Gin")); // True
        set.add(new String("Gin")); // False
        System.out.println(set);

    }
}
class Tiger{
    private String name;

    public Tiger(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "name='" + name ;
    }
}



数组链表模拟

HashSet 底层就是 HashMap,HashMap 底层是(数组 + 链表 + 红黑树)
在这里插入图片描述



package collection_;

/**
 * @Author: Gin
 * @Description:
 * @Modified By: Gin
 * @Date: Created in 16:18 2021/9/17
 */
public class HashSetStructure {
    public static void main(String[] args) {
        // 模拟一个 HashSet 底层(即 HashMap 的底层结构)

        // 1. 创建一个数组,数组类型是 Nod[]
        // 2. 也可以成 Nod[] 数组为 表
        Nod[] table = new Nod[16];
        System.out.println("table = " + table);

        // 3. 创建 Gin 结点,在 table 数组索引为 2 的位置插入此结点
        Nod Gin = new Nod("Gin", null);
        table[2] = Gin;
        // 4. 在 Gin 结点后挂载一个新结点 Vodka
        Nod Vodka = new Nod("Vodka", null);
        Gin.next = Vodka;
        // 5. 在 Vodka 结点后再挂载一个新结点 Vermouth
        Nod Vermouth = new Nod("Vermouth", null);
        Vodka.next = Vermouth;
        System.out.println("table = " + table);

    }
}
class Nod{ // 结点,用于存储数据,可以指向下一个结点,从而形成链表
    Object item; // 存放数据
    Nod next; // 指向下一个结点

    public Nod(Object item, Nod next) {
        this.item = item;
        this.next = next;
    }
}


在这里插入图片描述

在这里插入图片描述

HashSet 源码解读(1)

向空的 HashSet 中加入第一个元素

在这里插入图片描述



package collection_;

import java.util.HashSet;
import java.util.Set;

/**
 * @Author: Gin
 * @Description:
 * @Modified By: Gin
 * @Date: Created in 17:12 2021/9/17
 */
public class HashSetSource {
    public static void main(String[] args) {
        Set set = new HashSet();
        set.add("java");

        /*
            1. 执行 HashSet()
            public HashSet() {
                map = new HashMap<>();
            }
            2. 执行 add(E e)
            public boolean add(E e) { // e: "java"
                // PRESENT 是 HashSet 的一个静态属性:private static final Object PRESENT = new Object();
                return map.put(e, PRESENT)==null;
            }
            3. 执行 put(K key, V value),该方法会执行 hash(key),得到 key 对应的 hash 值,算法为 4.
            public V put(K key, V value) { // key: "java"      value: PRESENT
                return putVal(hash(key), key, value, false, true);
            }
            4. 执行 hash(Object key),key 的 hash 值: h = key.hashCode() ^ (h >>> 16)
            static final int hash(Object key) {
                int h;
                return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
            }
            5. 执行 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; // 定义了辅助变量

                // if 语句表示:如果当前 table 是 null,或者大小 = 0,就执行第一次扩容,到 16 个空间
                // resize() 方法 6.
                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(hash, "java", PRESENT, null)
                // (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;
                    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 {
                        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) // 判断大小是否大于 临界值,如果大于则对 table 数组进行扩容
                    resize();
                afterNodeInsertion(evict);
                return null; // 返回 null 表示 add() 操作成功
            }
            6. 执行 resize() ff
            final Node<K,V>[] resize() {
                Node<K,V>[] oldTab = table; // 将 table 对象赋给 Node 数组 oldTab
                int oldCap = (oldTab == null) ? 0 : oldTab.length; // 如果 oldTab 为 null,则定义旧数组容量 oldCap = 0
                int oldThr = threshold;
                int newCap, newThr = 0;
                if (oldCap > 0) { // 因为第一次执行 add() 方法,table 为 null,所以 oldCap == 0,进入 else
                    if (oldCap >= MAXIMUM_CAPACITY) {
                        threshold = Integer.MAX_VALUE;
                        return oldTab;
                    }
                    else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                             oldCap >= DEFAULT_INITIAL_CAPACITY)
                        newThr = oldThr << 1; // double threshold
                }
                else if (oldThr > 0)
                    newCap = oldThr;
                else {                      // table 为 null, 给新容量 newCap 赋初值:DEFAULT_INITIAL_CAPACITY(默认值 16)
                    newCap = DEFAULT_INITIAL_CAPACITY;
                    newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); // 给新临界值赋值:
                }                           // DEFAULT_LOAD_FACTOR = 0.75,新临界值 newThr = 0.75 * 16 = 12
                if (newThr == 0) {
                    float ft = (float)newCap * loadFactor;
                    newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                              (int)ft : Integer.MAX_VALUE);
                }
                threshold = newThr;         // 将得到的 临界值 newThr 赋值给 threshold
                @SuppressWarnings({"rawtypes","unchecked"})
                Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
                table = newTab;
                if (oldTab != null) {
                    for (int j = 0; j < oldCap; ++j) {
                        Node<K,V> e;
                        if ((e = oldTab[j]) != null) {
                            oldTab[j] = null;
                            if (e.next == null)
                                newTab[e.hash & (newCap - 1)] = e;
                            else if (e instanceof TreeNode)
                                ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                            else { // preserve order
                                Node<K,V> loHead = null, loTail = null;
                                Node<K,V> hiHead = null, hiTail = null;
                                Node<K,V> next;
                                do {
                                    next = e.next;
                                    if ((e.hash & oldCap) == 0) {
                                        if (loTail == null)
                                            loHead = e;
                                        else
                                            loTail.next = e;
                                        loTail = e;
                                    }
                                    else {
                                        if (hiTail == null)
                                            hiHead = e;
                                        else
                                            hiTail.next = e;
                                        hiTail = e;
                                    }
                                } while ((e = next) != null);
                                if (loTail != null) {
                                    loTail.next = null;
                                    newTab[j] = loHead;
                                }
                                if (hiTail != null) {
                                    hiTail.next = null;
                                    newTab[j + oldCap] = hiHead;
                                }
                            }
                        }
                    }
                }
                return newTab; // 返回数组的 容量大小
            }
         */

        set.add("php");
        set.add("java");
        System.out.println(set);


    }
}



如图表示第一次添加成功后,tab数组(即table数组)存放数据的情况
在这里插入图片描述

HashSet 源码解读(2)

如何判断添加的元素与链表的元素是否相同



package collection_;

import java.util.HashSet;
import java.util.Set;

/**
 * @Author: Gin
 * @Description:
 * @Modified By: Gin
 * @Date: Created in 17:12 2021/9/17
 */
public class HashSetSource {
    public static void main(String[] args) {
        Set set = new HashSet();
        set.add("java");

        /*
            1. 执行 HashSet()
            public HashSet() {
                map = new HashMap<>();
            }
            2. 执行 add(E e)
            public boolean add(E e) { // e: "java"
                // PRESENT 是 HashSet 的一个静态属性:private static final Object PRESENT = new Object();
                return map.put(e, PRESENT)==null;
            }
            3. 执行 put(K key, V value),该方法会执行 hash(key),得到 key 对应的 hash 值,算法为 4.
            public V put(K key, V value) { // key: "java"      value: PRESENT
                return putVal(hash(key), key, value, false, true);
            }
            4. 执行 hash(Object key),key 的 hash 值: h = key.hashCode() ^ (h >>> 16)
            static final int hash(Object key) {
                int h;
                return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
            }
            5. 执行 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; // 定义了辅助变量

                // if 语句表示:如果当前 table 是 null,或者大小 = 0,就执行第一次扩容,到 16 个空间
                // resize() 方法 6.
                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(hash, "java", PRESENT, null)
                // (2.2) 就存放到该位置 tab[i] = newNode(hash, key, value, null);
                if ((p = tab[i = (n - 1) & hash]) == null)
                    tab[i] = newNode(hash, key, value, null);
                // ============================如果 p 不为 null,分为以下三种情况:============================
                else { // (p表示:当前索引位置对应的链表的第一个元素,此处 else 分支 p 已经有元素)
                    Node<K,V> e; K k;
                    // 一:
                    // 如果 p 的 hash 与 要添加的元素的 hash 值一样  &&
                    // 并且,以下条件满足一个即可:
                    //      (1) p 的 key 与 要添加的元素的 key 是同一个对象
                    //    或 (2) p 的 key 与 要添加的元素的 key 通过 equals() 比较后相同 且不为空
                    //                             (不同对象的 equals 方法可能不同,根据动态绑定机制判断)
                    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 循环进行遍历比较
                    else {
                        // 此处 for 循环为死循环
                        for (int binCount = 0; ; ++binCount) {
                            // (3.1) 将 p.next 赋给 e(辅助变量)
                            //       如果 p 的 next 结点为 null,则表示都不相同,则加入到该链表最后,完成后 break 跳出 for 循环
                            //
                            //  注意:再把元素添加到链表后,立即判断该链表是否已达 8 个结点(TREEIFY_THRESHOLD 默认值为 8)
                            //      if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            //          treeifyBin(tab, hash);
                            //  如果已达 8 个结点,就调用 treeifyBin() 方法对该链表进行树化(红黑树)
                            //  在转成红黑树时,要进行判断,判断条件:
                            //      if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
                            //          resize();
                            //  如果上面判断条件成立,则先扩容 table
                            //  如果上面判断条件不成立,则转为红黑树
                            if ((e = p.next) == null) {
                                p.next = newNode(hash, key, value, null);
                                if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                                    treeifyBin(tab, hash);
                                break;
                            }
                            // (3.2) 如果与该链表某个结点有相同的情况,则直接 break 跳出 for 循环
                            if (e.hash == hash &&
                                ((k = e.key) == key || (key != null && key.equals(k))))
                                break;
                            // (3.3) 此操作确保遍历的执行,当以上两个判断都不符合时 p 的指向会后移,从而对下一个结点进行比较
                            p = e;
                        }
                    }
                    // 此处 e != null 表示该链表中存在相同的元素
                    // 如果 e != null,则 if 分支返回 oldValue ,表示插入失败
                    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) // 判断大小是否大于 临界值,如果大于则对 table 数组进行扩容
                    resize();
                afterNodeInsertion(evict);
                return null; // 返回 null 表示 add() 操作成功
            }
            6. 执行 resize() 方法
            final Node<K,V>[] resize() {
                Node<K,V>[] oldTab = table; // 将 table 对象赋给 Node 数组 oldTab
                int oldCap = (oldTab == null) ? 0 : oldTab.length; // 如果 oldTab 为 null,则定义旧数组容量 oldCap = 0
                int oldThr = threshold;
                int newCap, newThr = 0;
                if (oldCap > 0) { // 因为第一次执行 add() 方法,table 为 null,所以 oldCap == 0,进入 else
                    if (oldCap >= MAXIMUM_CAPACITY) {
                        threshold = Integer.MAX_VALUE;
                        return oldTab;
                    }
                    else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                             oldCap >= DEFAULT_INITIAL_CAPACITY)
                        newThr = oldThr << 1; // double threshold
                }
                else if (oldThr > 0)
                    newCap = oldThr;
                else {                      // table 为 null, 给新容量 newCap 赋初值:DEFAULT_INITIAL_CAPACITY(默认值 16)
                    newCap = DEFAULT_INITIAL_CAPACITY;
                    newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); // 给新临界值赋值:
                }                           // DEFAULT_LOAD_FACTOR = 0.75,新临界值 newThr = 0.75 * 16 = 12
                if (newThr == 0) {
                    float ft = (float)newCap * loadFactor;
                    newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                              (int)ft : Integer.MAX_VALUE);
                }
                threshold = newThr;         // 将得到的 临界值 newThr 赋值给 threshold
                @SuppressWarnings({"rawtypes","unchecked"})
                Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
                table = newTab;
                if (oldTab != null) {
                    for (int j = 0; j < oldCap; ++j) {
                        Node<K,V> e;
                        if ((e = oldTab[j]) != null) {
                            oldTab[j] = null;
                            if (e.next == null)
                                newTab[e.hash & (newCap - 1)] = e;
                            else if (e instanceof TreeNode)
                                ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                            else { // preserve order
                                Node<K,V> loHead = null, loTail = null;
                                Node<K,V> hiHead = null, hiTail = null;
                                Node<K,V> next;
                                do {
                                    next = e.next;
                                    if ((e.hash & oldCap) == 0) {
                                        if (loTail == null)
                                            loHead = e;
                                        else
                                            loTail.next = e;
                                        loTail = e;
                                    }
                                    else {
                                        if (hiTail == null)
                                            hiHead = e;
                                        else
                                            hiTail.next = e;
                                        hiTail = e;
                                    }
                                } while ((e = next) != null);
                                if (loTail != null) {
                                    loTail.next = null;
                                    newTab[j] = loHead;
                                }
                                if (hiTail != null) {
                                    hiTail.next = null;
                                    newTab[j + oldCap] = hiHead;
                                }
                            }
                        }
                    }
                }
                return newTab; // 返回数组的 容量大小
            }
         */

        set.add("php");
        set.add("java");
        System.out.println(set);


    }
}



  PHP知识库 最新文章
Laravel 下实现 Google 2fa 验证
UUCTF WP
DASCTF10月 web
XAMPP任意命令执行提升权限漏洞(CVE-2020-
[GYCTF2020]Easyphp
iwebsec靶场 代码执行关卡通关笔记
多个线程同步执行,多个线程依次执行,多个
php 没事记录下常用方法 (TP5.1)
php之jwt
2021-09-18
上一篇文章      下一篇文章      查看所有文章
加:2021-09-18 09:51:10  更:2021-09-18 09:52:25 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/17 12:37:23-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码