JAVA中自带的各种Set(基于JDK11)

1、HashSet

HashSet底层为HashMap实现,里面的方法也基本是调用HashMap的方法。内部用了一个固定的Object对象作为每个Key对应的Value:

// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();

public HashSet() {
    map = new HashMap<>(); //构造方法也是调用HashMap的
}

2、LinkedHashSet

该类继承了HashSet并调用了父类的构造方法,把父类中的map初始化为LinkedHashMap,所以LinkedHashSet是有序的(遍历按照插入顺序)

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


//HashSet中的构造方法
HashSet(int initialCapacity, float loadFactor, boolean dummy) { //dummy只是为了区分其他构造方法
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }

3、TreeSet

底层依赖于TreeMap,内部也是用了一个固定的Object对象作为每个Key对应的Value,其他的操作也都是依赖于TreeMap

/**
 * The backing map.
 */
private transient NavigableMap<E,Object> m;

// 固定对象
private static final Object PRESENT = new Object();

/**
 * Constructs a set backed by the specified navigable map.
 */
TreeSet(NavigableMap<E,Object> m) {
    this.m = m;
}

//All elements inserted into the set must implement the {@link Comparable} interface
public TreeSet() {
    this(new TreeMap<>()); //构建一个TreeMap
}

4、EnumSet

一个通过位操作存储枚举的Set,无法通过new构造,需要通过静态方法实例化。

public static <E extends Enum<E>> EnumSet<E> of(E e) {
    EnumSet<E> result = noneOf(e.getDeclaringClass()); //创建实例
    result.add(e);
    return result;
}

public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2) {
    EnumSet<E> result = noneOf(e1.getDeclaringClass());
    result.add(e1);
    result.add(e2);
    return result;
}

...

public static <E extends Enum<E>> EnumSet<E> of(E first, E... rest) {
    EnumSet<E> result = noneOf(first.getDeclaringClass());
    result.add(first);
    for (E e : rest)
        result.add(e);
    return result;
}

这里我们可以看到,上述这些方法都调用了noneOf这个方法,这个方法是干嘛用的呢?我们来看看:

public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
    Enum<?>[] universe = getUniverse(elementType); //获取该枚举类型所有元素
    if (universe == null)
        throw new ClassCastException(elementType + " not an enum");

    if (universe.length <= 64) //为什么规定阈值为64呢? 提示:long
        return new RegularEnumSet<>(elementType, universe);
    else
        return new JumboEnumSet<>(elementType, universe);
}

这里我们可以发现,noneOf并没有返回EnumSet实例本身,而是返回了它的子类,而且当枚举个数<=64时返回RegularEnumSet,反之返回JumboEnumSet。

而EnumSet正是依赖于子类内部的位操作来实现的,我们先来看看RegularEnumSet是如何放入枚举的

class RegularEnumSet<E extends Enum<E>> extends EnumSet<E> {
    private static final long serialVersionUID = 3411599620347842686L;
    /**
     * Bit vector representation of this set.  The 2^k bit indicates the
     * presence of universe[k] in this set.
     */
    private long elements = 0L; //定义了long类型变量记录

    RegularEnumSet(Class<E>elementType, Enum<?>[] universe) {
        super(elementType, universe);
    }


//添加
public boolean add(E e) {
    typeCheck(e);

    long oldElements = elements;
    elements |= (1L << ((Enum<?>)e).ordinal()); //逻辑或上1左移枚举的顺序数位
    return elements != oldElements;
}

//移除
public boolean remove(Object e) {
    if (e == null)
        return false;
    Class<?> eClass = e.getClass();
    if (eClass != elementType && eClass.getSuperclass() != elementType)
        return false;

    long oldElements = elements;
    elements &= ~(1L << ((Enum<?>)e).ordinal()); //&~ 等同于 ^ ,和添加反向操作
    return elements != oldElements;
}

EnumSet中还有一些有趣的构造方法,比如:

//添加该枚举类型的所有元素
public static <E extends Enum<E>> EnumSet<E> allOf(Class<E> elementType) {
    EnumSet<E> result = noneOf(elementType);
    result.addAll();
    return result;
}

//添加该枚举类型的范围元素X (from<=X<=to)
public static <E extends Enum<E>> EnumSet<E> range(E from, E to) {
    if (from.compareTo(to) > 0)
        throw new IllegalArgumentException(from + " > " + to);
    EnumSet<E> result = noneOf(from.getDeclaringClass());
    result.addRange(from, to);
    return result;
}

//RegularEnumSet中的方法
void addAll() {
    if (universe.length != 0)
        elements = -1L >>> -universe.length; //-1的原码位1111...1
        //无符号右移64-universe.length位,就把低universe.length位的1补上了,就添加了全部的数
}

//RegularEnumSet中的方法
void addRange(E from, E to) {
    elements = (-1L >>>  (from.ordinal() - to.ordinal() - 1)) << from.ordinal();
    //先用-1无符号右移(from 到 to 之前元素个数)的长度位数,得到全部的元素值
    //再左移起点顺序位,得到中间的值,就是结果
}

来看看他遍历迭代的时候是如何寻找元素的:

private class EnumSetIterator<E extends Enum<E>> implements Iterator<E> {
	 long unseen;
     long lastReturned = 0;
     EnumSetIterator() {
         unseen = elements; //
     }
     public boolean hasNext() {
         return unseen != 0;
     }
     @SuppressWarnings("unchecked")
     public E next() {
         if (unseen == 0)
             throw new NoSuchElementException();
         //n&-n  取得n的二进制最右面的1,所以可以用 n&-n == n 来判断n是不是2的整数次幂
         lastReturned = unseen & -unseen; 
         //下一位
         unseen -= lastReturned;
         return (E) universe[Long.numberOfTrailingZeros(lastReturned)];
     }
 }

而JumboEnumSet则是使用Long[]来保存枚举顺序数

class JumboEnumSet<E extends Enum<E>> extends EnumSet<E> {
    private long elements[];
    private int size = 0;
    JumboEnumSet(Class<E>elementType, Enum<?>[] universe) {
        super(elementType, universe);
        elements = new long[(universe.length + 63) >>> 6];
    }
}

其他的操作也都是根据位操作来实现的,本质上和RegularEnumSet一致,有兴趣的同学可以自己看源代码。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值