Hashset
插入无序,不可指定位置访问,不能重复
1.如果没有其他的限制,这就是默认的选择,比较常用
private transient HashMap<E,Object> map; //默认构造器 public HashSet() { map = new HashMap<>(); } //将传入的集合添加到HashSet的构造器 public HashSet(Collection<? extends E> c) { map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16)); addAll(c); } //明确初始容量和装载因子的构造器 public HashSet(int initialCapacity, float loadFactor) { map = new HashMap<>(initialCapacity, loadFactor); } //仅明确初始容量的构造器(装载因子默认0.75) public HashSet(int initialCapacity) { map = new HashMap<>(initialCapacity); }
通过上面的源码,我们发现了HashSet就是一个皮包公司,它就对外接活儿,活儿接到了就直接扔给HashMap处理了。因为底层是通过HashMap实现的,这里简单提一下:
HashMap的数据存储是通过数组+链表/红黑树实现的,存储大概流程是通过hash函数计算在数组中存储的位置,如果该位置已经有值了,判断key是否相同,相同则覆盖,不相同则放到元素对应的链表中,如果链表长度大于8,就转化为红黑树,如果容量不够,则需扩容(注:这只是大致流程)。
2.add
HashSet的add方法时通过HashMap的put方法实现的,不过HashMap是key-value键值对,而HashSet是集合,那么是怎么存储的呢,我们看一下源码
private static final Object PRESENT = new Object(); public boolean add(E e) { return map.put(e, PRESENT)==null; }
看源码我们知道,HashSet添加的元素是存放在HashMap的key位置上,而value取了默认常量PRESENT,是一个空对象,至于map的put方法,大家可以看HashMap原理(二) 扩容机制及存取原理。
之后的remove方法也是通过hashmap进行的
TreeSet
1.底层用treemap实现,实现了sortedset接口。红黑树实现,插入无序内部有序。可以自然和定制排序
2.TreeSet中的元素必须实现Comparable接口并重写compareTo()方法,TreeSet判断元素是否重复 、以及确定元素的顺序 靠的都是这个方法;
①对于java类库中定义的类,TreeSet可以直接对其进行存储,如String,Integer等,因为这些类已经实现了Comparable接口;
②对于自定义类,如果不做适当的处理,TreeSet中只能存储一个该类型的对象实例,否则无法判断是否重复。
3.相对HashSet,TreeSet的优势是有序,劣势是相对读取慢,所以要根据不同的场景选择不同的集合。
LinkedHashSet
1.具有可预知迭代顺序的 Set 接口的哈希表和链接列表实现。
* 此实现与 HashSet 的不同之外在于,后者维护着一个运行于所有条目的双重链接列表。
* 父类:HashSet
* 父接口:Set
* 注意,此实现不是同步的
* 此实现与 HashSet 的不同之外在于,后者维护着一个运行于所有条目的双重链接列表。
* 父类:HashSet
* 父接口:Set
* 注意,此实现不是同步的