集合(七) Set—HashSet,TreeSet和LinkedHashSet

四、Set

Set和List一样,也是继承Collection的接口,但Set是不包含重复元素的集合。由于先啃下Map,Set的难度将会大幅减小。因为Set基本上都是以Map为基础实现的,例如两个主要集合HashSet以HashMap为基础实现,是无序的;而TreeSet以TreeMap为基础实现,是有序的。

1.HashSet

与HashMap相同,HashSet同样允许元素为null,且是线程不安全的。构造函数就不再介绍了,倒是想展示一下它的成员域:

      // HashSet是通过map(HashMap对象)保存内容的
      private transient HashMap<E,Object> map;
  
      // PRESENT是向map中插入key-value对应的value
      // 因为HashSet中只需要用到key,而HashMap是key-value键值对;
      // 所以,向map中添加键值对时,键值对的值固定是PRESENT
      private static final Object PRESENT = new Object();
    

可以说简单粗暴,就一个HashMap和一个Object对象。至于Object类对象就只负责填充HashMap的值,也就是说HashSet就是HashMap的键!

HashSet的不重复性:(添加不是put,而是add)

        HashSet<String> hs = new HashSet();
        hs.add("wu");
        hs.add("Wu");
        String s = new String ("wu");
        hs.add(s);
        StringBuffer sb= new StringBuffer("wu");
        hs.add(sb.toString());
        System.out.println(hs);

结果如下,其实非常好理解,HashMap的键怎么会重复呢?只会不断地覆盖,正是如此。

[wu, Wu]

 

 HashSet的其他用法:

        hs.add("yi");
        hs.add("ming");

        HashSet hs2 = (HashSet) hs.clone();
        System.out.println(hs2);

        hs2.remove("Wu");
        hs.retainAll(hs2);

        Iterator it = hs.iterator();
        while (it.hasNext())
            System.out.print(it.next()+" ");
            System.out.println();
        String [] ss = (String [])hs2.toArray(new String[0]);
        for (String str:ss)
            System.out.print(str+" ");

其实这些方法大部分还是Collection得方法,值得一提的是可以用foreach遍历,不过首先要用toArray()方法转为数组,但是toArray必须要有参数new String [0],因为没有参数转化的是Object []类型,而Object []类型不能强制转换为String [] 类型,因此无参数方法可能不太友好,如果非常想使用,只能对于每个数组元素取出后单独转化,即Object是可以直接强制转为String类型的,然而数组不行。

结果如下:

[wu, Wu]
[yi, ming, wu, Wu]
yi ming wu 
yi ming wu 

2.TreeSet

显然HashSet是无序集合,如果想要一个有序集合,就需要使用TreeSet了。TreeSet是基于TreeMap来实现的,这样一来就简单很多了。

      // NavigableMap对象
      private transient NavigableMap<E,Object> m;
  
      // TreeSet是通过TreeMap实现的,
      // PRESENT是键-值对中的值。
      private static final Object PRESENT = new Object();

与TreeMap同样的特色是可以以Comparator对象作为参数。比如我们将上面的字符串按照wu yi ming 的顺序排列:

class COM implements Comparator<String>
{
    @Override
    public int compare(String o1, String o2) {
        if(o1.length()>o2.length())
            return 1;
        else if (o1.length()<o2.length())
            return -1;
        else
        {
            if(o1.compareTo(o2)>0)
                return 1;
            else
                return -1;
        }

    }
}

public class Treeset {
    public static void main(String [] args)
    {
        TreeSet<String> ts = new TreeSet(new COM());
        ts.add("wu");
        ts.add("yi");
        ts.add("ming");
        System.out.println(ts);
    }
}

首先排字符串长度,然后排首字母即可,当然这个返回值我也记不太住,看运气吧😂

 

3.LinkedHashSet

 依据前面两个例子来说,想必这个也很明朗了。同LinkedHashMap继承HashMap一样,LinkedHashSet继承了HashSet,实现了有顺序的HashSet。构造函数也是全部继承于HashSet父类:

 

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

    private static final long serialVersionUID = -2851667679971038690L;

    /**
     * Constructs a new, empty linked hash set with the specified initial
     * capacity and load factor.
     *
     * @param      initialCapacity the initial capacity of the linked hash set
     * @param      loadFactor      the load factor of the linked hash set
     * @throws     IllegalArgumentException  if the initial capacity is less
     *               than zero, or if the load factor is nonpositive
     */
    public LinkedHashSet(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor, true);
    }

    /**
     * Constructs a new, empty linked hash set with the specified initial
     * capacity and the default load factor (0.75).
     *
     * @param   initialCapacity   the initial capacity of the LinkedHashSet
     * @throws  IllegalArgumentException if the initial capacity is less
     *              than zero
     */
    public LinkedHashSet(int initialCapacity) {
        super(initialCapacity, .75f, true);
    }

    /**
     * Constructs a new, empty linked hash set with the default initial
     * capacity (16) and load factor (0.75).
     */
    public LinkedHashSet() {
        super(16, .75f, true);
    }

    /**
     * Constructs a new linked hash set with the same elements as the
     * specified collection.  The linked hash set is created with an initial
     * capacity sufficient to hold the elements in the specified collection
     * and the default load factor (0.75).
     *
     * @param c  the collection whose elements are to be placed into
     *           this set
     * @throws NullPointerException if the specified collection is null
     */
    public LinkedHashSet(Collection<? extends E> c) {
        super(Math.max(2*c.size(), 11), .75f, true);
        addAll(c);
    }

    /**
     * Creates a <em><a href="Spliterator.html#binding">late-binding</a></em>
     * and <em>fail-fast</em> {@code Spliterator} over the elements in this set.
     *
     * <p>The {@code Spliterator} reports {@link Spliterator#SIZED},
     * {@link Spliterator#DISTINCT}, and {@code ORDERED}.  Implementations
     * should document the reporting of additional characteristic values.
     *
     * @implNote
     * The implementation creates a
     * <em><a href="Spliterator.html#binding">late-binding</a></em> spliterator
     * from the set's {@code Iterator}.  The spliterator inherits the
     * <em>fail-fast</em> properties of the set's iterator.
     * The created {@code Spliterator} additionally reports
     * {@link Spliterator#SUBSIZED}.
     *
     * @return a {@code Spliterator} over the elements in this set
     * @since 1.8
     */
    @Override
    public Spliterator<E> spliterator() {
        return Spliterators.spliterator(this, Spliterator.DISTINCT | Spliterator.ORDERED);
    }
}

这次好像把注释都copy上了,这不是我的风格,但是我想说的是这就是LinkedHashMap的全部代码,简单的有点出人意料,正因如此,我索性就把所有代码全部都粘贴上来了。最后一个实现了spliterator方法,是一个可分割迭代器,是JDK8中为实现并行遍历操作的方法,这里暂不对其进行详细讲解,只简单的了解。后面有机会详细讲解。

可以看出构造函数多出了一个boolean参数,正式这个参数是的LinkedHashSet和HashSet区分开来:

    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }

可以看出,这个boolean参数实际上没啥用,但是区分了构造函数,使得域map申请的是一个LinkedHashMap对象,而非HashMap对象。那么既然LinkedHashSet没有什么成员方法,那么调用的应该是父类HashSet的方法,而HashSet中的操作也多是以map为基础实现的,因此map对象所属类不同时(LinkedHashMap和HashMap)自然执行相同的方法也会有覆盖的现象出现。

 

从ArrayList以来,集合这一部分总共花了14个月有余,震惊吧!坚持一件事情总是很难,但如果是自己喜欢的事情也很快乐,很有感触,由于客观原因和我本身的原因我的计划总是严重拖延,但这条路上我不会放弃,其实我是很讨厌鸡汤的,不过还是有感而发。

posted @ 2019-07-15 23:02  LeftBody  阅读(276)  评论(0编辑  收藏  举报