集合类源码Collection之Set(HashSet, LinkedHashSet, TreeSet)

Posted lua123

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了集合类源码Collection之Set(HashSet, LinkedHashSet, TreeSet)相关的知识,希望对你有一定的参考价值。

HashSet

先看一下成员变量

// 由此可见内部存储是一个HashMap
private transient HashMap<E,Object> map;

private static final Object PRESENT = new Object();

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

add方法

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

仅仅是把新元素作为key,一个事先初始化好的空Object对象作为value,存入HashMap。

同理,contains方法和remove方法都是调用HashMap的方法

public boolean contains(Object o) {
    return map.containsKey(o);
}

public boolean remove(Object o) {
    return map.remove(o)==PRESENT;
}

HashSet实现很简单,完全基于HashMap。

LinkedHashSet

先来看继承关系,继承了HashSet

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

本身也没有提供相关CRUD方法

技术图片

 

 来看下构造方法

public LinkedHashSet() {
    // 调用的父类的方法
    super(16, .75f, true);
}

// 父类(HashSet)构造方法
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
    // 和HashSet区别在于new了一个LinkedHashMap
    map = new LinkedHashMap<>(initialCapacity, loadFactor);
}

这个构造方法是一个包私有的,仅仅提供给LinkedHashSet使用。

LinkedHashSet的实现基于LinkedHashMap

TreeSet

到这里,不用看也能知道,TreeSet的实现基于TreeMap....

来看成员变量

private transient NavigableMap<E,Object> m;

// 人家官方管这个Map中value的默认值叫做“哑值”
private static final Object PRESENT = new Object();

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

TreeSet(NavigableMap<E,Object> m) {
    this.m = m;
}

而add、contains、remove等方法也不必解释了

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

public boolean contains(Object o) {
    return m.containsKey(o);
}

public boolean remove(Object o) {
    return m.remove(o)==PRESENT;
}

ConcurrentSkipListSet

虽然这个名字很长,人家也是线程安全的Set,但是,依然依托于Map的具体实现类

// 内部存储结构
private final ConcurrentNavigableMap<E,Object> m;

// 构造方法
public ConcurrentSkipListSet() {
    m = new ConcurrentSkipListMap<E,Object>();
}

// add
public boolean add(E e) {
    return m.putIfAbsent(e, Boolean.TRUE) == null;
}

// contains
public boolean contains(Object o) {
    return m.containsKey(o);
}

// remove
public boolean remove(Object o) {
    return m.remove(o, Boolean.TRUE);
}

与前面不同的是,ConcurrentSkipListSet内部Map的value值永远是一个  Boolean.TRUE

优点:

1. 基于跳表实现,查找效率高

2. 线程安全

CopyOnWriteArraySet

这里呢,就不是基于Map的了,毕竟没有 CopyOnWriteArrayMap 这个集合类。虽然没有相关Map实现,但是是不是有一个 CopyOnWriteArrayList

private final CopyOnWriteArrayList<E> al;

/**
 * Creates an empty set.
 */
public CopyOnWriteArraySet() {
    al = new CopyOnWriteArrayList<E>();
}

那是怎样去重的呢?

public boolean add(E e) {
    // addIfAbsent:如果没有则添加,就是这样来保证无重复元素的
    return al.addIfAbsent(e);
}

其它都是直接调用其内部数据结构的方法

public boolean contains(Object o) {
    return al.contains(o);
}

public boolean remove(Object o) {
    return al.remove(o);
}

总结:

Set旗下的集合一般都是站在Map集合的肩膀上的,懂了Map,就懂了Set

关于有序和无序:

0. 无序:HashSet

1. 按照自然顺序排序的:TreeSet、ConcurrentSkipListSet

2. 按照添加顺序排序的:LinkedHashSet、CopyOnWriteArraySet

测试程序

public static void main(String[] args) throws Exception {
     deal(new HashSet<>());
     deal(new LinkedHashSet<>());
     deal(new TreeSet<>());
     deal(new ConcurrentSkipListSet<>());
     deal(new CopyOnWriteArraySet<>());
}
private static void deal(Set<String> set){
    set.add("s1");
    set.add("s9");
    set.add("s3");
    set.add("s5");
    set.add("s7");
    set.add("s2");
    set.add("s8");
    System.out.println(set.getClass().getName() + ":" + set);
}

结果:

java.util.HashSet:[s3, s5, s7, s8, s9, s1, s2]
java.util.LinkedHashSet:[s1, s9, s3, s5, s7, s2, s8]
java.util.TreeSet:[s1, s2, s3, s5, s7, s8, s9]
java.util.concurrent.ConcurrentSkipListSet:[s1, s2, s3, s5, s7, s8, s9]
java.util.concurrent.CopyOnWriteArraySet:[s1, s9, s3, s5, s7, s2, s8]

 

以上是关于集合类源码Collection之Set(HashSet, LinkedHashSet, TreeSet)的主要内容,如果未能解决你的问题,请参考以下文章

Java之set源码分析

死磕 java集合之HashSet源码分析

Java集合进阶之Collection单列集合(Set)

jdk源码分析之List--常用实现类分析与对比

Java 集合系列02之 Collection架构

Java集合框架源码阅读之AbstractCollection