Collection集合体系全景图(四)

897 阅读6分钟

Collection中Set家族:

image.png

### TreeSet关于他的一些详细特性在Collection集合体系全景图(三)已经详细讲过, > TreeSet底层是采用TreeMap实现的一种Set,所以它是有序的,同样也是非线程安全的。TreeMap 底层数据结构是红黑树

TreeSet特点

  1. 有序性:TreeSet中的元素是有序的,它们按照自然顺序或者指定的比较器顺序进行排序。
  2. 唯一性:TreeSet中的元素是唯一的,它们不会重复。
  3. 可排序性:TreeSet中的元素可以按照自然顺序或者指定的比较器顺序进行排序。
  4. 底层数据结构:TreeSet底层采用红黑树实现,因此插入、删除、查找等操作的时间复杂度为O(log n)。
  5. 线程不安全:TreeSet不是线程安全的,如果多个线程同时访问一个TreeSet对象,可能会出现并发问题。
  6. 不允许空元素:TreeSet不允许插入空元素,否则会抛出NullPointerException异常。

使用案例

import java.util.TreeSet;

public class TreeSetExample {
    public static void main(String[] args) {
        // 创建一个TreeSet对象
        TreeSet<Integer> set = new TreeSet<>();

        // 添加元素
        set.add(5);
        set.add(2);
        set.add(8);
        set.add(1);
        set.add(10);

        // 打印集合元素
        System.out.println("TreeSet集合元素:" + set);

        // 获取第一个元素
        int first = set.first();
        System.out.println("第一个元素:" + first);

        // 获取最后一个元素
        int last = set.last();
        System.out.println("最后一个元素:" + last);

        // 获取小于等于指定元素的最大元素
        int floor = set.floor(6);
        System.out.println("小于等于6的最大元素:" + floor);

        // 获取大于等于指定元素的最小元素
        int ceiling = set.ceiling(6);
        System.out.println("大于等于6的最小元素:" + ceiling);

        // 获取小于指定元素的最大元素
        int lower = set.lower(6);
        System.out.println("小于6的最大元素:" + lower);

        // 获取大于指定元素的最小元素
        int higher = set.higher(6);
        System.out.println("大于6的最小元素:" + higher);

        // 删除元素
        set.remove(5);
        System.out.println("删除元素5后的集合元素:" + set);

        // 获取集合大小
        int size = set.size();
        System.out.println("集合大小:" + size);

        // 判断集合是否为空
        boolean empty = set.isEmpty();
        System.out.println("集合是否为空:" + empty);

        // 清空集合
        set.clear();
        System.out.println("清空集合后的元素:" + set);
    }
}

TreeSet源码分析

public class TreeSet<E> extends AbstractSet<E>  
implements NavigableSet<E>, Cloneable, java.io.Serializable

它继承了 AbstractSet 类,因此它具有 Set 接口的所有基本功能,如添加、删除、查找等操作。同时,它还实现了 NavigableSet 接口,这意味着它支持一些高级操作,如获取子集、查找最小值和最大值、查找前驱和后继元素等。此外,它还实现了 Cloneable 和 Serializable 接口,这意味着它可以被克隆和序列化。

//用于存储 TreeSet 中的元素。其中,NavigableMap 是一个支持按照排序顺序访问键值对的 Map 接口,
//它扩展了 SortedMap 接口
//transient 关键字表示该字段不会被序列化
private transient NavigableMap<E,Object> m;  
//表示一个静态常量,用于作为 NavigableMap中的 value,因为 NavigableMap 的实现是基于 Map 的
//而 Map 中的 value 是可以为 null 的,但是 `TreeSet` 中的元素是不能重复的,
//因此需要一个非 null 的 value 来占位。
private static final Object PRESENT = new Object();
//构造方法,接受一个 NavigableMap类型的参数 m,将其赋值给成员变量 m
TreeSet(NavigableMap<E,Object> m) {  
this.m = m;  
}
//无参构造方法,创建一个默认的空的 TreeSet,底层使用 TreeMap 实现
public TreeSet() {  
this(new TreeMap<E,Object>());  
}
//创建一个新的TreeSet实例,使用指定的Comparator来比较元素,并使用一个新的TreeMap实例来存储元素
//TreeSet是一个有序的集合,它根据元素的自然顺序或者指定的Comparator来排序元素
public TreeSet(Comparator<? super E> comparator) {  
this(new TreeMap<>(comparator));  
}

案例教学

    // 创建一个按照字符串长度从小到大排序的 TreeSet
    TreeSet<String> set = new TreeSet<>(new Comparator<String>() {
        @Override
        public int compare(String o1, String o2) {
            return o1.length() - o2.length();
        }
    });

    // 添加元素
    set.add("hello");
    set.add("world");
    set.add("java");

    // 输出结果
    System.out.println(set); // [java, hello, world]

继续源码分析TreeSet

    //这个构造函数是用来创建一个 TreeSet 对象,并将指定集合中的所有元素添加到 TreeSet 中。其中,参数 c 是一个集合,它包含了要添加到 TreeSet 中的元素
    public TreeSet(Collection<? extends E> c) {  
    this();  
    addAll(c);  
    }
    //用于将一个集合中的所有元素添加到另一个集合中。
    //它还检查源集合是否是一个有序集合,并且目标集合是否是一个TreeMap。如果是,则将源集合中的元素添加到目标集合中
    //并返回true。如果不是,则调用父类的addAll方法
    public boolean addAll(Collection<? extends E> c) {    
    //Map对象m的大小为0;
    // 集合c的大小大于0,且c是SortedSet类型,m是TreeMap类型
    //instanceof 是 Java 中的一个关键字,用于判断一个对象是否属于某个类或其子类的实例
    //c instanceof SortedSet 判断集合 c是否是 SortedSet类或其子类的实例,
    //m instanceof TreeMap 判断映射 m 是否是 TreeMap 类或其子类的实例
    //说明集合 c 是有序集合,映射 m 是基于红黑树实现的有序映射。
    //SortedSet是Java集合框架中的一个接口,它继承自Set接口,表示一个有序的集合
    if (m.size()==0 && c.size() > 0 &&  c instanceof SortedSet &&  m instanceof TreeMap) {  
    SortedSet<? extends E> set = (SortedSet<? extends E>) c;  
    TreeMap<E,Object> map = (TreeMap<E, Object>) m;  
    Comparator<?> cc = set.comparator();  
    Comparator<? super E> mc = map.comparator();  
    if (cc==mc || (cc != null && cc.equals(mc))) {  
    map.addAllForTreeSet(set, PRESENT);  
    return true;  
    }  
    }  
    return super.addAll(c);  
    }
    //用于将一个集合中的所有元素添加到当前集合中
    public boolean addAll(Collection<? extends E> c) {  
    boolean modified = false;  
    for (E e : c)  
    if (add(e))  
    modified = true;  
    return modified;  
    }
    }

TreeSet(Collection<? extends E> c) 的使用案例:

List<Integer> list = new ArrayList<>();
list.add(3);
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(2);
TreeSet<Integer> set = new TreeSet<>(list);
System.out.println(set);//输出结果为:[1, 2, 3, 4]

继续源码分析TreeSet

//实现了 SortedSet接口中的 iterator() 方法,返回一个迭代器,
//用于遍历集合中的元素。具体实现是通过调用 TreeMap 的 navigableKeySet() 方法获取一个有序的键集合,然后返回该键集合的迭代器
public Iterator<E> iterator() {  
return m.navigableKeySet().iterator();  
}
//逆序迭代
public Iterator<E> descendingIterator() {  
return m.descendingKeySet().iterator();  
}
//return m.put(e, PRESENT)==e不存在于 m 中,则将其添加到 m 中,并返回 true,否则不做任何操作并返回 false
public boolean add(E e) {  
return m.put(e, PRESENT)==null;  
}
//该方法返回一个NavigableSet,该集合包含从fromElement到toElement范围内的元素。
//fromInclusive和toInclusive参数指定是否包括fromElement和toElement本身。
//该方法使用TreeMap的subMap方法来实现子集的创建。
//范围取值
public NavigableSet<E> subSet(E fromElement, boolean fromInclusive,  
E toElement, boolean toInclusive) {  
return new TreeSet<>(m.subMap(fromElement, fromInclusive,  
toElement, toInclusive));  
}
//该方法返回一个NavigableSet,其中包含小于(或等于,如果包含toElement)给定元素的所有元素
public NavigableSet<E> headSet(E toElement, boolean inclusive) {  
return new TreeSet<>(m.headMap(toElement, inclusive));  
}
//该方法返回一个NavigableSet,其中包含大于(或等于,fromElement)给定元素的所有元素
public NavigableSet<E> tailSet(E fromElement, boolean inclusive) {  
return new TreeSet<>(m.tailMap(fromElement, inclusive));  
}

subSet案例实现

import java.util.NavigableSet;
import java.util.TreeSet;

public class TreeSetExample {
    public static void main(String[] args) {
        // 创建一个TreeSet
        TreeSet<Integer> treeSet = new TreeSet<>();

        // 添加元素
        treeSet.add(10);
        treeSet.add(20);
        treeSet.add(30);
        treeSet.add(40);
        treeSet.add(50);

        // 获取子集
        NavigableSet<Integer> subSet = treeSet.subSet(20, true, 40, true);

        // 输出子集
        System.out.println("子集元素:");
        for (Integer i : subSet) {
            System.out.println(i); //20,30,40
        }
    }
}

以上就是TreeSet 源码中比较重要的点,其他的一些方法大家可以自行学习探讨

LinkedHashSet特点

  1. 有序性:LinkedHashSet中的元素是按照插入顺序进行排序的,因此可以保证元素的顺序性。
  2. 可以避免重复元素:LinkedHashSet中不允许存储重复元素,如果尝试添加重复元素,将会被忽略。
  3. 性能较好:LinkedHashSet的性能与HashSet相当,但是由于需要维护元素的插入顺序,因此在插入和删除元素时可能会稍微慢一些。
  4. 底层实现是HashMap:LinkedHashSet的底层实现是一个HashMap,因此具有HashMap的所有特点,如快速查找、高效存储等。

LinkedHashSet源码分析

    public class LinkedHashSet<E>  
    extends HashSet<E>  
    implements Set<E>, Cloneable, java.io.Serializable {

LinkedHashSet 是一个有序的集合,它保留了元素插入的顺序。它继承了 HashSet 的所有方法,并且添加了一些额外的方法来支持有序集合。

//LinkedHashSet类的构造方法,它接受两个参数:initialCapacity
//和loadFactor。initialCapacity表示集合的初始容量,loadFactor表示负载因子
//相当创建一个指定参数HashSet集合
public LinkedHashSet(int initialCapacity, float loadFactor) {  
super(initialCapacity, loadFactor, true);  
}
//给一个初始容量,负载因子默认扩容参数,0.75,可以容纳75%的元素,然后就需要扩展容量。
//相当创建一个初始值
HashSet集合
public LinkedHashSet(int initialCapacity) {  
super(initialCapacity, .75f, true);  
}
//给一个初始容量16,负载因子为0.75
public LinkedHashSet() {  
super(16, .75f, true);  
}
//,它包含从指定集合中获取的元素。它首先调用父类HashSet的构造函数,
//该构造函数将初始容量设置为指定集合大小的两倍或11(取两者中的较大值),负载因子为0.75,
//以及指定LinkedHashSet实例的访问顺序为插入顺序。
//它调用addAll方法将指定集合中的所有元素添加到新的LinkedHashSet中
public LinkedHashSet(Collection<? extends E> c) {  
super(Math.max(2*c.size(), 11), .75f, true);  
addAll(c);  
}

LinkedHashSet案例演示

import java.util.LinkedHashSet;

public class LinkedHashSetExample {
    public static void main(String[] args) {
        LinkedHashSet<String> set = new LinkedHashSet<>();

        // 添加元素
        set.add("apple");
        set.add("banana");
        set.add("orange");
        set.add("apple"); // 重复元素不会被添加

        // 打印元素
        System.out.println(set); // 输出 [apple, banana, orange]

        // 删除元素
        set.remove("banana");

        // 判断元素是否存在
        System.out.println(set.contains("apple")); // 输出 true
        System.out.println(set.contains("banana")); // 输出 false

        // 获取元素数量
        System.out.println(set.size()); // 输出 2

        // 清空集合
        set.clear();
        System.out.println(set); // 输出 []
    }
}