集合(七) 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个月有余,震惊吧!坚持一件事情总是很难,但如果是自己喜欢的事情也很快乐,很有感触,由于客观原因和我本身的原因我的计划总是严重拖延,但这条路上我不会放弃,其实我是很讨厌鸡汤的,不过还是有感而发。