Set 总结

目录

HashSet

总结:

彩蛋

 LinkedHashSet

总结

彩蛋

TreeSet

总结

彩蛋

问题


 

java里面的Set对应于数学概念上的集合,里面的元素是不可重复的,通常使用Map或者List来实现。

 

HashSet

参考博客:

【死磕 Java 集合】— HashSet源码分析

HashSet是Set的一种实现方式,底层主要使用HashMap来确保元素不重复。

HashSet底层用的是HashMap value值为

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

 

总结:

(1)HashSet内部使用HashMap的key存储元素,以此来保证元素不重复;

(2)HashSet是无序的,因为HashMap的key是无序的;

(3)HashSet中允许有一个null元素,因为HashMap允许key为null;

(4)HashSet是非线程安全的;

(5)HashSet是没有get()方法的;

彩蛋

(1)阿里手册上有说,使用java中的集合时要自己指定集合的大小,通过这篇源码的分析,你知道初始化HashMap的时候初始容量怎么传吗?

我们发现有下面这个构造方法,很清楚明白地告诉了我们怎么指定容量。

假如,我们预估HashMap要存储n个元素,那么,它的容量就应该指定为((n/0.75f) + 1),如果这个值小于16,那就直接使用16得了。

初始化时指定容量是为了减少扩容的次数,提高效率。

public HashSet(Collection<? extends E> c) {
    map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
    addAll(c);
}

(2)什么是fail-fast?

fail-fast机制是java集合中的一种错误机制。

当使用迭代器迭代时,如果发现集合有修改,则快速失败做出响应,抛出ConcurrentModificationException异常。

这种修改有可能是其它线程的修改,也有可能是当前线程自己的修改导致的,比如迭代的过程中直接调用remove()删除元素等。

另外,并不是java中所有的集合都有fail-fast的机制。比如,像最终一致性的ConcurrentHashMap、CopyOnWriterArrayList等都是没有fast-fail的。

那么,fail-fast是怎么实现的呢?

细心的同学可能会发现,像ArrayList、HashMap中都有一个属性叫modCount,每次对集合的修改这个值都会加1,在遍历前记录这个值到expectedModCount中,遍历中检查两者是否一致,如果出现不一致就说明有修改,则抛出ConcurrentModificationException异常。

这也是在高并发非线程安全集合操作时 常出现的错误。

 LinkedHashSet

参考博客:【死磕 Java 集合】— LinkedHashSet源码分析

 

上一节我们说HashSet中的元素是无序的,那么有没有什么办法保证Set中的元素是有序的呢?

答案是当然可以。

我们今天的主角LinkedHashSet就有这个功能,它是怎么实现有序的呢?让我们来一起学习吧。

 

  LinkedHashSet中一共提供了5个方法,其中4个是构造方法,还有一个是迭代器。

4个构造方法都是调用父类的super(initialCapacity, loadFactor, true);这个方法。

    // HashSet的构造方法
    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }

构造方法里面使用了LinkedHashMap来初始化HashSet中的map。

现在这个逻辑应该很清晰了,LinkedHashSet继承自HashSet,它的添加、删除、查询等方法都是直接用的HashSet的,唯一的不同就是它使用LinkedHashMap存储元素。

总结

(1)LinkedHashSet的底层使用LinkedHashMap存储元素。

(2)LinkedHashSet是有序的,它是按照插入的顺序排序的。

彩蛋

LinkedHashSet支持按元素访问顺序排序吗?

让我们一起来分析下。

首先,LinkedHashSet所有的构造方法都是调用HashSet的同一个构造方法,如下:

    // HashSet的构造方法
    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }

然后,通过调用LinkedHashMap的构造方法初始化map,如下所示:

    public LinkedHashMap(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
        accessOrder = false;
    }

可以看到,这里把accessOrder写死为false了。

所以,LinkedHashSet是不支持按访问顺序对元素排序的,只能按插入顺序排序。

TreeSet

参考博客:【死磕 Java 集合】— TreeSet源码分析

  TreeSet底层是采用TreeMap实现的一种Set,所以它是有序的,同样也是非线程安全的。

总结

(1)TreeSet底层使用NavigableMap存储元素;

(2)TreeSet是有序的;

(3)TreeSet是非线程安全的;

(4)TreeSet实现了NavigableSet接口,而NavigableSet继承自SortedSet接口;

(5)TreeSet实现了SortedSet接口;

彩蛋

(1)通过之前的学习,我们知道TreeSet和LinkedHashSet都是有序的,那它们有何不同?

LinkedHashSet并没有实现SortedSet接口,它的有序性主要依赖于LinkedHashMap的有序性,所以它的有序性是指按照插入顺序保证的有序性;

而TreeSet实现了SortedSet接口,它的有序性主要依赖于NavigableMap的有序性,而NavigableMap又继承自SortedMap,这个接口的有序性是指按照key的自然排序保证的有序性,而key的自然排序又有两种实现方式,一种是key实现Comparable接口,一种是构造方法传入Comparator比较器。

(2)TreeSet里面真的是使用TreeMap来存储元素的吗?

通过源码分析我们知道TreeSet里面实际上是使用的NavigableMap来存储元素,虽然大部分时候这个map确实是TreeMap,但不是所有时候都是TreeMap。

因为有一个构造方法是TreeSet(NavigableMap<E,Object> m),而且这是一个非public方法,通过调用关系我们可以发现这个构造方法都是在自己类中使用的,比如下面这个:

    public NavigableSet<E> tailSet(E fromElement, boolean inclusive) {
        return new TreeSet<>(m.tailMap(fromElement, inclusive));
    }

而这个m我们姑且认为它是TreeMap,也就是调用TreeMap的tailMap()方法:

    public NavigableMap<K,V> tailMap(K fromKey, boolean inclusive) {
        return new AscendingSubMap<>(this,
                                     false, fromKey, inclusive,
                                     true,  null,    true);
    }

可以看到,返回的是AscendingSubMap对象,这个类的继承链是怎么样的呢?

AscendingSubMap

可以看到,这个类并没有继承TreeMap,不过通过源码分析也可以看出来这个类是组合了TreeMap,也算和TreeMap有点关系,只是不是继承关系。

所以,TreeSet的底层不完全是使用TreeMap来实现的,更准确地说,应该是NavigableMap。

问题

(1)HashSet怎么保证添加元素不重复?  

     在向hashSet中add()元素时,判断元素是否存在的依据,不仅仅是hash码值就能够确定的,同时还要结合equles方法。

    https://blog.csdn.net/u010698072/article/details/52802179 

(2)HashSet是有序的吗?

       hashset继承的是set接口,set是无序集合。

(3)HashSet是否允许null元素?

      允许

(4)Set是否有get()方法?

   没有,set是无序的。

(5)LinkedHashSet是有序的吗?怎么个有序法?

    LinkedHashSet底层是采用的是LinkedHashMap实现双向链表。能够保证插入顺序和迭代顺序一样。但是不能像TreeSet那样排序。

(6)LinkedHashSet支持按元素访问顺序排序吗? LinkedHashSet所有的构造方法都是调用HashSet的同一个构造方法,如下:

// HashSet的构造方法

HashSet(int initialCapacity, float loadFactor, boolean dummy) {

map = new LinkedHashMap<>(initialCapacity, loadFactor);

}
然后,通过调用LinkedHashMap的构造方法初始化map,如下所示:
public LinkedHashMap(int initialCapacity, float loadFactor) {

super(initialCapacity, loadFactor);

accessOrder = false;

}

可以看到,这里把accessOrder写死为false了。

所以,LinkedHashSet是不支持按访问顺序对元素排序的,只能按插入顺序排序。

(8)TreeSet真的是使用TreeMap来存储元素的吗?

   TreeSet的底层不完全是使用TreeMap来实现的,更准确地说,应该是NavigableMap。 具体参考文章前面TreeSet讲解的彩蛋部分。

(9)TreeSet是有序的吗?怎么个有序法?

  LinkedHashSet并没有实现SortedSet接口,它的有序性主要依赖于LinkedHashMap的有序性,所以它的有序性是指按照插入顺  序保证的有序性;

  而TreeSet实现了SortedSet接口,它的有序性主要依赖于NavigableMap的有序性,而NavigableMap又继承自SortedMap,这个   接口的有序性是指按照key的自然排序保证的有序性,而key的自然排序又有两种实现方式,一种是key实现Comparable接口,      一种是构造方法传入Comparator比较器。

(10)TreeSet和LinkedHashSet有何不同?

如果你需要一个访问快速的Set,你应该使用HashSet;当你需要一个排序的Set,你应该使用TreeSet;当你需要记录下插入时的顺序时,你应该使用LinedHashSet。

Set接口:

      1.Set不允许包含相同的元素,如果试图把两个相同元素加入同一个集合中,add方法返回false。

      2.Set判断两个对象相同不是使用==运算符,而是根据equals方法。也就是说,只要两个对象用equals方法比较返回true,Set就不会接受这两个对象。

                 |——SortedSet接口——TreeSet实现类

Set接口——|——HashSet实现类                

                   |——LinkedHashSet实现类

(11)TreeSet和SortedSet有什么区别和联系?

     SortedSet是个接口,它里面的(只有TreeSet这一个实现可用)中的元素一定是有序的。

(12)CopyOnWriteArraySet是用Map实现的吗?

(13)CopyOnWriteArraySet是有序的吗?怎么个有序法?

(14)CopyOnWriteArraySet怎么保证并发安全?

(15)CopyOnWriteArraySet以何种方式保证元素不重复?

(16)如何比较两个Set中的元素是否完全一致?

(17)ConcurrentSkipListSet的底层是ConcurrentSkipListMap吗?

(18)ConcurrentSkipListSet是有序的吗?怎么个有序法? 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值