【Java学习笔记】Set接口

Ubuntu20.04系统笔记本网卡AX211无法找到wifi问题 之前电脑的系统是18.04基本上安装好了系统,可以直接连上wifi,在买了拯救者y9000p2022版本之后,因为网卡太新,安装ubuntu18.04或者20.04之后会没有wifi连接,非常不方便。于是上网找了各种帖子去解决这个问题。具体实现有所不同。 主要参考了这个帖子来解决的:http://t.csdn.cn/7807R 首先: sudo apt install flex bison 然后: git clone https://github.com/intel/backpor. 阅读详情

Set 接口

1. 基本介绍

(1)无序(添加和取出的顺序不一致),没有索引

  • 具体详述

    • 添加的顺序和取出的顺序不一致(强调二者顺序不一致)

    • 但是取出的顺序是固定的(强调存储后,位置是固定的,不会变化)

(2)不允许有重复元素,最多包含一个null

(3)两个实现类(不是指全部)

  • HashSet

  • TreeSet

2. 常用方法

由于是 Collection 的子接口,方法和它一样

3. Set 接口的遍历

  • 迭代器:Iterator

  • 增强 for 循环

  • 不可以用索引来取值

关于顺序的说明(以HashSet举例)

import java.util.HashSet;
import java.util.Set;
@SuppressWarnings("all")
public class pra {
    public static void main(String[] args) {

        Set set = new HashSet();

        // jack
        set.add("jack");
        set.add("jack");
        // null
        set.add(null);
        // java
        set.add("java");
        // tom
        set.add("tom");

        System.out.println(set + "\n");

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

// 输出结果
[null, java, tom, jack]

[null, java, tom, jack]
[null, java, tom, jack]
[null, java, tom, jack]
[null, java, tom, jack]
[null, java, tom, jack]

代码分析

  • 添加顺序和取出的顺序是不一致的

  • 但是取出的顺序是固定的

  • 元素是不能重复的

  • 如果添加的是对象(即使内容相同),也可以添加进去(具体分析需要看底层源码的实现原理)

面试经典易错题

import java.util.HashSet;

@SuppressWarnings("all")
public class pra {
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        hashSet.add(new dog(8));
        hashSet.add(new dog(8));

        hashSet.add("dog:8");
        hashSet.add("dog:8");

        System.out.println(hashSet);
    }
}

class dog{
    int age;

    public dog(int age) {
        this.age = age;
    }

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

// 输出结果
[dog{age=8}, dog{age=8}, dog:8]

代码分析

(1)对于创建的对象(内容相同),可以添加(不够成重复元素的判断条件)

(2)但是对于字符串对象,只能添加一个

为什么?引出 HashSet 的底层添加原理


HashSet(理解底层源码很重要!)

1. 基本介绍

  • HashSet 底层是 HashMap

  • 实现了 Set 接口,拥有 Set 接口和 Collection 接口的方法

  • 可以存放空值,且只能有一个 null

  • HashSet 不能保证元素是有序的,取决于 hash 后的索引值(即无法保证存储元素顺序和取出元素顺序一致)

  • 不能有重复元素

  • HashSet、HashMap 都是线程不安全的

2. 透彻理解 HashSet 的添加原理(剖析底层源码)

说明:HashSet 底层是 HashMap

HashMap 的基本介绍

底层结构:数组 + 单链表 + 红黑树

请添加图片描述

>> 先说结论

一、HashSet 底层添加机制

(1) HashSet 底层是 HashMap

(2) 添加一个元素时,先得到 hash 值(hashCode 方法),会转换 -> 索引值

(3) 找到存储数据的哈希表 table(即数组),看这个索引位置是否已经存放的元素

(4) 如果没有,直接加入

(5) 如果有,调用 equals 方法(由程序员决定,可以被重写) 比较,如果相同,就放弃添加,如果不同,则添加到最后

(6) 在 Java8 中

  • 如果一条链表的元素个数达到 TREEIFY_THRESHOLD(临界值:默认是 8)

  • 并且 table 的大小 >= MIN_TREEIFY_CAPACITY(执行树化需要 table 数组的大小:默认 64)

  • 就会进行树化(红黑树)

二、树化(红黑树)和扩容机制

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

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

(3) 在 Java8 中

  • 如果一条链表的元素个数达到 TREEIFY_THRESHOLD(默认是 8)

  • 并且 table 的大小 >= MIN_TREEIFY_CAPACITY(默认 64),就会进行树化(红黑树)

  • 否则仍然采用数组扩容机制(即如果节点数达到八,但是数组空间小于 64,就会先扩容到 64,之后进行树化)

(4)容易误解的地方!:只要添加了新节点到 HashSet(HashMap)中,就算数组空间大小自增一,不是说添加到第一个位置才算空间自增,当值达到某些条件就会进行相应的操作

>> 源码剖析

示例代码

import java.util.HashSet;

@SuppressWarnings("all")
public class pra {
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();

        for (int i = 1; i <= 100; i++) {
            hashSet.add(i);
        }

        System.out.println(hashSet);
    }
}

阶段一:第一次插入元素的过程

第一步

调用构造器,底层是 HashMap

public HashSet() {
    map = new HashMap<>();
}

第二步

首先进行装箱操作(valueOf()方法),这部分不看,进入add()方法

public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

代码说明

PRESENT:private static final Object PRESENT = new Object(),这里起到的占位作用,在后续在put()方法中传值给value

第三步

进入put()方法

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

代码说明

key:add()方法中添加的内容

第四步

(1)首先调用hash()方法,计算哈希值

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

(2)进入putVal()方法(重点!)

补充:resize()方法

final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    int newCap, newThr = 0;
    if (oldCap > 0) {
        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) // initial capacity was placed in threshold
        newCap = oldThr;
    else {               // zero initial threshold signifies using defaults
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                    (int)ft : Integer.MAX_VALUE);
    }
    threshold = newThr;
    @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;
}

代码解析

static final float DEFAULT_LOAD_FACTOR = 0.75f;

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

1. 初始化空间大小(table 数组):16

2. 临界因子:0.75

  • 解释:16 * 0.75 = 12,当数组大小达到 12 时候,数组就会进行扩容(扩容 2 倍),变为16 * 2 = 32

  • 扩容:newCap = oldCap << 1; // double threshold

最后返回一个新的数组

总结:resize()方法的本质就是初始化table数组,初始化空间为 16,同时设置临界因子为0.75 * size

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 ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    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)
        resize();
    afterNodeInsertion(evict);
    return null;
}

代码解析

  • table:是HashMap的一个属性(数组),类型是 Node[ ](即结构示意图中的数组)

if ((tab = table) == null || (n = tab.length) == 0)
    n = (tab = resize()).length;

代码解析

  • 1. if 语句:如果table数组当前为空或者大小为 0,就进行第一次初始化

  • 2. 执行完resize()方法后,table数组的空间大小初始化为 16,里面元素为 null

if ((p = tab[i = (n - 1) & hash]) == null)
    tab[i] = newNode(hash, key, value, null);

代码解析

  • (1)根据 key (add() 方法中添加的内容),得到 hash 去计算该 key 应该存放到 table 表的哪个索引位置 , 并把这个位置的对象,赋给 p

  • (2)判断 p 是否为 null

    • 如果 p 为 null, 表示还没有存放元素, 就创建一个 Node (key = (传入的值), value = PRESENT)

    • 放在该位置 tab[i] = newNode(hash, key, value, null),传入 hash的目的是:下一次添加节点时,判断该位置是否已经存放了元素(避免冲突)

由于是元素第一次添加,节点处还没有值(结果为空),进入 if 语句,跳过 else 语句,接下来进入 if 判断

if (e != null) { // existing mapping for key
    V oldValue = e.value;
    if (!onlyIfAbsent || oldValue == null)
        e.value = value;
    afterNodeAccess(e);
    return oldValue;
}

代码解析

  • e 是add()方法中的添加的元素,此时不为空,跳过 if 判断,进入到下面的代码

++modCount;
if (++size > threshold)
    resize();
afterNodeInsertion(evict);
return null;

代码解析

  • 1. 记录修改次数,自增一次

  • 2. 如果当前空间大小大于了临界值,数组就需要进行扩容(2 倍大小)

  • 3. 在 HashMap 中afterNodeInsertion()方法为空,留给子类(例如:LinkedHashMap)实现,涉及插入后的有序问题( void afterNodeInsertion(boolean evict) { })

  • 4. 返回 null

    • putval()方法返回空,传递给put()方法

    • put()方法也返回空,传递给add()方法

    • 接下来在add()方法中判断是否为空,返回add()方法的值

    • 如果不返回空,则返回oldValue,oldValue == null返回 false ,即插入失败

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true); // 返回空,传递给 put() 方法
}
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
} // 返回空, 传递给 add() 方法

结论:结果为 true,返回真,add()方法执行成功,第一次插入元素执行结束

预备工作:显示控制 null 值

请添加图片描述

调试结果如下

1. 内存视图

请添加图片描述

2. table 数组

请添加图片描述


阶段二:添加相同的元素的处理

代码示例

public class pra {
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();

        hashSet.add("java");
        hashSet.add("java");
        System.out.println(hashSet);
    }
}

由于添加的是同一个对象,计算的 hash 值相同,此时 p 的指向不为空,进入 else 分支

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;
    }
}

代码解析

1. 第一个分支判断(首先判断第一个节点(存放在 table 数组中))

if (p.hash == hash &&
    ((k = p.key) == key || (key != null && key.equals(k))))
    e = p;

首先判断 p 指向的第一个节点的哈希值和需要插入节点的哈希值是否相同,并且

  • 是否是同一个对象,或者

  • 内容(key 值)相同(equals() 方法由程序员决定,可以被重写)

结论:如果满足这个条件,就不能添加

2. 第二个分支判断

else if (p instanceof TreeNode){
    e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
}

如果是红黑树,就调用putTreeVal()方法来添加

3. 第三个分支判断(从第二个元素开始判断(table 数组中存放元素链接的第一个节点))

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;
    }
}

在第一个分支判断中完成了和第一个元素的比较,如果加入的值不是第一个元素(存放在 table 数组中),则可能是后面连接的元素,这个时候通过死循环遍历判断

(1)情况一:比较过程中发现相同的,就是第一个分支判断的情况,直接跳出循环,不执行添加

if (e.hash == hash &&
    ((k = e.key) == key || (key != null && key.equals(k))))
    break;

(2)情况二:没有找到相同的

 if ((e = p.next) == null) {
    p.next = newNode(hash, key, value, null);
    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
        treeifyBin(tab, hash);
    break;
}

通过两个指针遍历,如果为空,直接生成新的节点链接起来

注意!:链接之后立马判断是否达到 8 个节点,然后进行树化(有前提条件)

  • 调用treeifyBin()方法

  • 首先判断数组大小是否 >= 64(默认值),如果不满足,会调用resize()方法进行扩容,否则执行树化操作

treeifyBin() 源码

final void treeifyBin(Node<K,V>[] tab, int hash) {
    int n, index; Node<K,V> e;
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        resize();
    else if ((e = tab[index = (n - 1) & hash]) != null) {
        TreeNode<K,V> hd = null, tl = null;
        do {
            TreeNode<K,V> p = replacementTreeNode(e, null);
            if (tl == null)
                hd = p;
            else {
                p.prev = tl;
                tl.next = p;
            }
            tl = p;
        } while ((e = e.next) != null);
        if ((tab[index] = hd) != null)
            hd.treeify(tab);
    }
}

4. 第四个分支判断

if (e != null) { // existing mapping for key
    V oldValue = e.value;
    if (!onlyIfAbsent || oldValue == null)
        e.value = value;
    afterNodeAccess(e);
    return oldValue;
}

补充:第一个分支判断如下

if (p.hash == hash &&
    ((k = p.key) == key || (key != null && key.equals(k))))
    e = p;

第二次添加的元素和第一次添加的的元素相同,则进入 else 分支,执行完第一个分支判断后,调到第四个分支判断

  • e 不等于空,在第一个分支判断时被赋值为 p

  • e.value 的值(返回的 oldValue):PRESENT(在之前通过函数的传值得到)

  • 返回的 oldValue 不为空,返回到上一层函数,执行到 add() 函数,返回值为 false,即添加失败

返回到add()函数

public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

结论:当添加元素为可能的重复元素时,通过判断,执行结束


阶段三:树化和扩容

1. 当 table 数组到达临界值 12 时(初始化为 16),就会扩容为 2 倍,通过计算新的临界值,达到新的临界值后,又扩容为数组大小的两倍,依次类推

2. 当数组中链接的节点个数达到 8 时,首先判断数组大小是否达到 64(默认值)

  • 如果没有达到:执行扩容机制(结合临界值)

  • 达到了 64,进行树化


LinkedHashSet(补充)

1. 基本介绍

(1)关系:LinkedHashSet 是 HashSet 的子类,底层是LinkedHashMap

(2)底层结构:数组 + 双向链表

(3)特点:通过链表维护元素的次序(是 HashSet 的升级版),即添加顺序和取出顺序是一致

(4)LinkedHashSet 不允许添加重复元

2. 关于结构的说明

  • 在 LinkedHashSet 中维护一个 hash 表和双向链表(LinkedHashSet 有 head 和 tail)

  • 每个节点有 before 和 after 属性,这样可以形成双向链表

  • 在添加一个元素时,先计算 hash 值,在查找索引,确定该元素在 table 的位置,然后将添加的元素加入到双向链表(如果已经存在,不添加原则和 hashset 一样)

结构示意图

在这里插入图片描述


练习:定义员工类,如果名字和年龄相同就不添到 HashSet 中

方法:快捷键(alt + insert)选择重写 hashCode() 和 equals() 方法

import java.util.HashSet;
import java.util.Iterator;
import java.util.Objects;

@SuppressWarnings("all")
public class pra {
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();

        hashSet.add(new employee("jack",18));
        hashSet.add(new employee("tom",23));
        hashSet.add(new employee("jack",18));

        Iterator iterator = hashSet.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
}

class employee{
    String name;
    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 +
                '}';
    }

    // 重写 hashCode() 和 equals() 方法
    @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);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

TreeSet

结构:TreeSet 的底层是 TreeMap

调用无参构造器:添加顺序和取出顺序不一致,底层还是计算 hash 值

特点:提供了一个构造器可以传入 Comparator 接口,通过实现 compare()方法来指定添加顺序

示例代码

public class pra {
    public static void main(String[] args) {
        TreeSet treeSet = new TreeSet(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                return ((String)o1).compareTo((String)o2);
            }
        });

        treeSet.add("jack");
        treeSet.add("tom");
        treeSet.add("hi");
        treeSet.add("jack");

        System.out.println("treeSet = " + treeSet);
    }
}

// 输出结果
treeSet = [hi, jack, tom]

代码分析

1. 调用构造器传入实现 Comparator 接口的实例对象(匿名内部类),通过实现 compare()方法来指定添加顺序

2. 调用字符串的 compareTo()方法,由于添加的字符串都不相同,可见添加的顺序是按照字典序排列的

>>补充:compareTo()方法回顾

  • 1. 如果长度相同,并且每个个字符也相同,就返回 0

  • 2. 如果长度相同或者不相同,但是在比较时,可以区分大小

  • 3. 如果前面的部分都相同,就返回长度之差(str1.len - str2.len)

底层源码如下

public int compareTo(String anotherString) {
    int len1 = value.length;
    int len2 = anotherString.value.length;
    int lim = Math.min(len1, len2);
    char v1[] = value;
    char v2[] = anotherString.value;

    int k = 0;
    while (k < lim) {
        char c1 = v1[k];
        char c2 = v2[k];
        if (c1 != c2) {
            return c1 - c2;
        }
        k++;
    }
    return len1 - len2;
}

添加元素有序的实现原理

1. 关于构造器调用的底层源码分析

在调用构造器之前会进行类加载的操作,这部分忽略

进入无参构造器,这里调用了同类中的另一个构造器

public TreeSet() {
    this(new TreeMap<E,Object>());
}

接着进入另一个构造器

public TreeSet(Comparator<? super E> comparator) {
    this(new TreeMap<>(comparator));
}

发现本质:底层还是 TreeMap

public TreeMap(Comparator<? super K> comparator) {
    this.comparator = comparator;
}

代码分析

构造器把传入的比较器对象赋给了 TreeSet 的底层的 TreeMap 的属性 this.comparator

2. 关于 add() 方法的添加机制和构造器的关系的底层源码分析

进入 add() 方法

public boolean add(E e) {
    return m.put(e, PRESENT)==null;
}

进入 put() 方法

public V put(K key, V value) {
    Entry<K,V> t = root;
    if (t == null) {
        compare(key, key); // type (and possibly null) check

        root = new Entry<>(key, value, null);
        size = 1;
        modCount++;
        return null;
    }
    int cmp;
    Entry<K,V> parent;
    // split comparator and comparable paths
    Comparator<? super K> cpr = comparator;
    if (cpr != null) {
        do {
            parent = t;
            cmp = cpr.compare(key, t.key);
            if (cmp < 0)
                t = t.left;
            else if (cmp > 0)
                t = t.right;
            else
                return t.setValue(value);
        } while (t != null);
    }
    else {
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
            Comparable<? super K> k = (Comparable<? super K>) key;
        do {
            parent = t;
            cmp = k.compareTo(t.key);
            if (cmp < 0)
                t = t.left;
            else if (cmp > 0)
                t = t.right;
            else
                return t.setValue(value);
        } while (t != null);
    }
    Entry<K,V> e = new Entry<>(key, value, parent);
    if (cmp < 0)
        parent.left = e;
    else
        parent.right = e;
    fixAfterInsertion(e);
    size++;
    modCount++;
    return null;
}

核心部分

do {
    parent = t;
    cmp = k.compareTo(t.key);
    if (cmp < 0)
        t = t.left;
    else if (cmp > 0)
        t = t.right;
    else
        return t.setValue(value);
} while (t != null);

代码分析

底层其实是调用了传入的匿名内部类中实现的 compare 方法,即实现了可以自定义添加顺序

易错点

问题:在下面的代码中,java 能否被添加到 TreeSet 中

public class pra {
    public static void main(String[] args) {

        TreeSet treeSet = new TreeSet(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                return ((String)o1).length() - ((String)o2).length();
            }
        });

        treeSet.add("jack");
        treeSet.add("tom");
        treeSet.add("hi");
        treeSet.add("java");

        System.out.println("treeSet = " + treeSet);
    }
}

// 运行结果
treeSet = [hi, tom, jack]

答案:不能

根据传入的比较器,这里是根据字符串的长度指定排序顺序

源码分析:此时java和jack的字符串长度相同,调用compareTo()方法返回 0,走到 else 分支,该元素不会被添加进入 treeSet

总结:调用带有比较器的构造器时,可以通过实现compareTo()方法来指定添加顺序,如果compareTo()方法=返回值为 0 则不进行添加

3D Slicer学习记录(4)-PLUSapp连接双目相机Intel Realsense plusapp是基于openIGTLink协议的免费的开源库和应用程序API接口,用于数据采集、预处理、校准和实时流成像、位置跟踪和其他传感器数据。这里以intel realsense双目相机为例详细的演示整个图像流导入到3D Slicer的流程。 阅读详情

相关推荐

利用Skipper跨层追踪技术高效定位LVS Short问题

本文详细介绍了如何利用Skipper跨层追踪技术高效定位LVS Short问题。通过解析Skipper的跨层trace net原理和实战操作步骤,帮助芯片设计工程师快速识别版图中的短路问题,显著提升验证效率。文章还提供了处理大规模设计的高级技巧和常见问题排查方法。

weixin_29239661的博客 509

Set接口详解

Set 接口是 Java 集合框架中的一种数据结构,它继承自 Collection 接口,用于存储一组唯一的对象,即不允许重复的元素。Set 接口中的元素不按特定的方式排序,但它提供了基于对象的相等性来确定元素是否重复的方法。即如果集合中已经存在一个等于新加入元素的对象,则新元素不会被添加到集合中。Set 接口在 Java 集合框架中有多种实现,其中最常用的是 HashSet 和 TreeSet。HashSet 实现是基于哈希表的,具有高效的添加、删除、查找操作;

上班摸鱼,下班躺平 1104

AI驱动Spine骨骼动画生成:从图像拆分到动画自动化的全流程解析

计算机视觉与生成式AI技术正深度赋能传统数字内容生产流程。在2D动画制作领域,骨骼动画技术通过模拟真实生物关节运动,实现了角色动画的高效制作与灵活控制。其核心原理在于将角色拆分为独立部件,通过骨骼层级关系驱动部件变换,结合关键帧插值算法生成流畅动作。这项技术的工程价值在于大幅降低动画制作门槛,提升内容生产效率,尤其适用于游戏开发、影视预演、广告设计等需要快速原型迭代的场景。本文聚焦于如何利用SIFT特征匹配、RANSAC几何验证等CV算法实现部件自动对位,并结合动画12原则的程序化实现,构建从单张角色立绘到

weixin_28696185的博客 1519

Set接口介绍

文章目录前言一、Set接口:1.Set接口特点:2.Set的实现类:Set接口提供的方法API:二、HashSet1.HashSet的特点:2.HashSet的属性:3.HashSet的构造函数:4.HashSet方法:三、LinkedHashSet:1.LinkedHashSet的特点:2.LinkedHashSet的继承关系和构造器:3、LinkedHastSet的应用场景:四、TreeSet1.TreeSet的特点:2.属性:3.构造器方法总结 前言 Set接口是Collection接口的子类,..

liangcheng0213的博客 3983

RoCE Overlay

1. RoCE over vxlan 配置vxlan封装的RoCE流量,可以在PF switchdev模式下利用ovs bridge,或直接设置tc rules。下面将基于一对NIC互联的服务器介绍两种方式的使用,并分析封装后的数据包结构。 1.1. 基本配置 - VF与switchdev 首先创建VF,并为两个host设置PF的switchdev模式。使用ovs bridge或tc rules,...

sunshuying1010的博客 3063

模电学习笔记(十二)——跨阻放大器

模电学习笔记(十二) 跨阻放大器 跨组运算放大器是将电流信号转换成为电压信号,电流到电压增益基于反馈电阻。 设计目标: 输入 输出 带宽 电源 0A 100uA 0V 7V 20kHz 15V -15V 1.计算R1的值(增益电阻器) 2.计算C1的值满足电路带宽 ...

tang11081023的博客 2万+

第十四章 集合(Set)

一、Set 接口 | 二、HashSet 接口 | 三、LinkedHashSet 接口 | 四、Map 接口和常用方法 | 五、Map 接口实现类 | 六、如何选择集合实现类(TreeSet、TreeMap) | 七、Collections 工具类 |

yirenyuan的博客 1028

uni-app - 解决 H5 微信公众号授权登录后,无法回退到 “上一页“ 的问题(微信登录后如何返回到触发它的那一页?无法通过 uni.navigateBack() 返回到前一页的问题?)详细教程

uniappH5微信登录重定向后无法返回到原来进入前的页面了,uni-appH5微信浏览器中授权微信登录后,无法正常回退问题,uniappH5微信登录后怎么返回上一页,uniapp公众号登陆后使用uni.navigateBack()回不去上一页,uniapp微信登录成功后返回页面后一直自动刷新页面,uniapph5授权成功后如何返回上一页,uniappH5微信登录后怎么返回到触发它的那一页去,公众号h5在授权后如何跳转回到刚进入时的页面,微信公众号H5网页返回上一页疑问,解决微信H5静默授

🏆 前端领域优质创作者 7715

Java集合(六)Set接口和常用方法

(1)无序(添加和取出的顺序不一致),没有索引(2)不允许重复元素,所以最多包含一个null(3)JDK API中Set接口的实现类有:EnumSetHashSetTreeSetSet接口的常用方法:和List接口一样,Set接口也是Collection的子接口,因此,常用方法和Collection接口一样Set接口的遍历方式:同Collection的遍历方式一样,因为Set接口是Collection接口的子接口1.可以使用迭代器2.增强for3.不能使用索引的方式来获取。

超级程序诞生的博客 1618

使用GitLab限制开发者使用CI/CD的权限,三种方案

使用三种方案来限制开发人员的CI/CD权限。

知无涯 5097

全志A133的AndroidSDK

本小节来学习如何下载全志安卓SDK,SDK的目录,编译和烧录提示:以下是本篇文章正文内容,下面案例可供参考以上就是整个SDK的目录和编译烧录,目的在于让初学者快速了解整个源码的目录和编译出镜像喜欢就订阅吧。

qq_24093081的博客 4054

深入解析Java中的Set接口

在数据结构和算法的世界里,确保集合中元素的唯一性是一个常见的需求。Java 提供了 Set 接口来满足这一需求,它继承自 Collection 接口,并进一步限制了不能包含重复元素。本文将深入探讨 Set 接口的核心概念、常见实现类及其应用案例,帮助开发者更好地理解和利用这一强大的工具。

2301_77163982的博客 1349

SAP SEGW 事物码里的 Function Import

开放数据协议(OData)包括标准的 CRUD (创建、检索、更新和删除)操作,这些操作映射到 HTTP 方法的 POST、GET、PUT/MERGE 和 Delete 几类。此外,OData还支持进一步的服务操作:,这些操作可以由HTTP方法GET或POST调用,用于处理不能映射到标准 CRUD 操作的任何内容。通过在数据模型中创建 ,可以在 service Builder 中实现此类额外的服务操作。例如,您可以为以下自定义操作创建函数导入:Work Item confirmation检查航班的可用性虽然

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 995

Java学习笔记——Set接口和Map接口

Set接口和Map接口 1.了解Set集合的特点 2.掌握Set集合下常用实现类的使用 3.了解Map集合的特点 4.掌握Map集合下常用实现类的使用 5.掌握Map集合的遍历方式 6.掌握Collections工具类 第一节 Set接口 1.1 Set接口常用方法 方法名 描述 ...

冷漠的小猿 4944

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

JAVA学习笔记32——hashCode和equals方法+set接口

最近在看JAVA教学的视频,觉得老师讲的很好,同时借用源代码还有笔记来撰写本系列博客,记录自己的学习内容,同时也供看到的人学习。 本篇介绍两个内容,首先来说一下hashCode和equals方法: 一般情况下不需要重写hashCode和equals方法。 示例代码: import java.util.Date; public class Student extends Objec

向计算机殿堂奔跑的跑道 690

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

【Java学习笔记】Set接口及其实现类

目录 1.Set接口概述 2.HashSet集合 2.1 概述 2.2 HashSet如何保证元素唯一性 2.3 HashSet案例 ​​​​​​​3.LinkedHashSet集合 4.TreeSet集合 4.1TreeSet存储Integer类型的元素并遍历 4.2 TreeSet存储自定义对象并遍历 1.Set接口概述 一个不包含重复元素的Collection。 2...

我们仨 576

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

java学习笔记23(Set接口)

Set接口:   1.Set接口是不包含重复元素的Collection;   2.set集合没有索引,只能通过增强型for循环或迭代器来遍历;   3.Set接口只包含从collection接口继承的方法,并且增加了禁止重复元素这个限制。   Set接口有HashSet类和LinkedHashSet类实现; Set接口的实现类:HashSet类    HashSet类特点:1.无序集合...

weixin_30244889的博客 86

Java学习笔记之集合:Set接口

1.Set接口是Collection接口的子接口,用于存储无序且不可重复的数据。其主要实现类是HashSet

SunnyFall的博客 221

【java学习笔记】Set接口和常用方法

set接口的基本介绍,遍历方式

2401_88623584的博客 330

Java学习笔记 --- Set接口方法

1、无序(添加和取出的顺序不一致),没有索引2、不允许重复元素,所以最多包含一个null3、JDK API 由 Set 接口的实现类有。

乌云的博客 462

Java学习笔记--Set接口

Set接口简介Set是不保存重复元素的Collection,Set接口只包含从Collection接口继承的方法,并且增加禁止重复元素的限制。实现Set接口的通用类是HashSet,LInkedHashSet,TreeSet类。 HashSet类。HashSet采用hashCode算法存放元素,元素的存放顺序与插入顺序无关。HashSet是为了快速查找而实现的Set。由hashCode()和equa

loveqRc的博客 451

java set实现类_Java学习笔记_22_Set接口的实现类

22.Set接口的实现类:Set接口存放的元素是无序的且不包括反复元素。1>实现类HashSet:HashSet类依据元素的哈希码进行存放,取出时也能够依据哈希码高速找到。HashSet不保存元素的加入的顺序。样例:import java.util.HashSet;import java.util.Iterator;public class Student {public static vo...

weixin_39755712的博客 176

Java容器学习笔记(二) Set接口及其实现类的相关知识总结

在Java容器学习笔记(一)中概述了Collection的基本概念及接口实现,并且总结了它的一个重要子接口List及其子类的实现和用法。 本篇主要总结Set接口及其实现类的用法,包括HashSet(无序不重复),LinkedHashSet(按放入顺序有序不重复),TreeSet(按红黑树方式有序不重复),EnumSet,ConcurrentSkipListSet(来自于java.util.con

不积跬步无以至千里,不积小流无以成江海。 1万+

【Java学习笔记】Set 接口实现类-HashSet

HashSet的全面说明、执行过程.......

2401_88623584的博客 600

(Java学习笔记)JavaSE 集合:Collection接口(Liist、Set接口)

Collection接口 Collection 接口是 List、Set 和 Queue 接口的父接口,该接口里定义的方法 既可用于操作 Set 集合,也可用于操作 List 和 Queue 集合。 目录:Collection接口Collection接口1、Collection接口方法2、子接口:List接口2.1、List接口方法2.11遍历的三种方式2.12、总结:常用方法2.2、Array...

陌然白(小白)的博客 791

Java基础学习笔记二十七 容器(Set接口)

Set接口 Set接口继承自Collection,Set接口没有新增方法,方法和Collection完全一致。 Set容器特点:无序、不可重复。无序指Set中的元素没有索引,只能遍历查找;不可重复指不允许加入重复元素。 Set常用的实现类有:HashSet、TreeSet等。 /** * HashSet的基本用法 */ import java.util.HashSet; import java...

怪咖的博客 144
上一篇: 【Java学习笔记】List接口
下一篇: 【Java学习笔记】Map接口
jackson凌
博客等级 码龄2年 476粉丝 183原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jackson凌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值