Set接口
HashSet、LinkedHashSet、TreeSet
- HashSet是Set接口的典型实现,大多数时候Set集合都使用这个实现类。HashSet是按Hash算法来存储集合中的元素,因此具有很好的存取、查找、删除性能。底层就是HashMap;HashSet具有以下特点:不能保证元素的排列顺序;HashSet不是线程安全的;集合元素可以是null;HashSet集合判断两个元素相等的标准:两个对象通过hashCode()方法比较相等,并且两个对象的equals()方法返回值也相等。对于存放在Set容器中的对象,对应的类一定要重写equals()和hashCode()方法,以实现对象相等规则。既“相等的对象必须具有相同的hashCode”
- LinkedHashSet是HaseSet的子类LinkedHashSet根据元素的hashCode值来决定元素的存储位置,但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的。LinkedHashSet插入性能略低于HashSet,但在迭代访问Set里的全部元素时有很好的性能。LinkedHashSet不允许集合元素重复。
- TreeSet是SortedSet接口的实现类,TreeSet可以确保集合元素处于排序状态。TreeSet底层就是TreeMap,而TreeMap底层使用红黑树结构(JDK1.8以后)存储key,TreeSet只是使用TreeMap的keyTreeSet两种排序方法:自然排序和Comparetor比较器排序,默认使用自然排序。
HashSet
HashSet的底层初始化:
private transient HashMap<E,Object> map;
public HashSet() {
map = new HashMap<>();
}
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);
}
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
可以看到HashSet底层就是一个HashMap,那这里就不多关于HashMap的东西,主要看下对HashSet的测试:
@Test
public void test6(){
Set<Employee> set = new HashSet<>(10);
System.out.println(set.size());
for(int i = 0; i < 5; i++){
set.add(new Employee("test",(i%2 == 0) ? 2 : 1,(i%2 == 0) ? 2 : 1));
}
Iterator<Employee> iterator = set.iterator();
while (iterator.hasNext()){
Employee employee = iterator.next();
System.out.println(employee);
}
/**
* 打印结果:
* 0
* run hashCode ...
* run hashCode ...
* run hashCode ...
* run equals()....
* run hashCode ...
* run equals()....
* run hashCode ...
* run equals()....
* Employee{name='test', age=2, salary=2}
* Employee{name='test', age=1, salary=1}
* 2
*/
/**
* Employee的equals()重写时加了一句:
* System.out.println("run equals()....");
* Employee的hashCode()重写时加了一句:
* System.out.println("run hashCode ... ");
* 上面对List进行测试的时候也加了。。。
*/
}
从上面的打印结果可以看到,初始化后set的size为0,后面每次添加一个元素size都会增长1,而再添加过程中如果遇到hashCode相同情况下会进行equals判断,元素内的值相同会导致添加失败。所以最终set中只有2个元素。
LinkedHashSet
LinkedHashSet的底层初始化:
public class LinkedHashSet<E>
extends HashSet<E>
implements Set<E>, Cloneable, java.io.Serializable {
public LinkedHashSet(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor, true);
}
public LinkedHashSet(int initialCapacity) {
super(initialCapacity, .75f, true);
}
public LinkedHashSet() {
super(16, .75f, true);
}
public LinkedHashSet(Collection<? extends E> c) {
super(Math.max(2*c.size(), 11), .75f, true);
addAll(c);
}
}
父类HashSet下的带有三个参数的构造器:
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
显然,LinkedHashSet底层是使用LinkedHashMap
实现的,也就是说它带有LinkedHashMap
的特点——可以找到添加过程中的前后元素。具体可以看LinkedHashMap
TreeSet
TreeSet的底层初始化:
public TreeSet() {
this(new TreeMap<E,Object>());
}
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
public TreeSet(Collection<? extends E> c) {
this();
addAll(c);
}
public TreeSet(SortedSet<E> s) {
this(s.comparator());
addAll(s);
}
使用TreeSet:
@Test
public void test7(){
Set<Employee> set = new TreeSet<>();
for(int i = 0; i < 5; i++){
set.add(new Employee("test",i,i));
}
Iterator<Employee> iterator = set.iterator();
while (iterator.hasNext()){
Employee employee = iterator.next();
System.out.println(employee);
}
/**
* 报错:
* java.lang.ClassCastException: com.chapter12_collection.Employee cannot be cast to java.lang.Comparable
*
* at java.util.TreeMap.compare(TreeMap.java:1294)
* at java.util.TreeMap.put(TreeMap.java:538)
* at java.util.TreeSet.add(TreeSet.java:255)
* at com.chapter12_collection.CollectionTest.test7(CollectionTest.java:115)
* at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
* ...
*/
}
发现必须得把Employee转成Comparable,那么就让Employee实现Comparable接口呗。于是乎重写里面的compareTo()方法:
// 先按name排序,再按age排序
@Override
public int compareTo(Object o) {
if(o instanceof Employee){
Employee employee = (Employee) o;
if(this.name == employee.getName()){
return Integer.compare(this.age, employee.getAge());
}else {
return this.name.compareTo(employee.getName());
}
}else {
throw new RuntimeException("输入的类型不是Employee");
}
}
再来跑测试代码:
@Test
public void test7(){
Set<Employee> set = new TreeSet<>();
for(int i = 5; i > 0; i--){
set.add(new Employee("test"+i,i,i));
}
Iterator<Employee> iterator = set.iterator();
while (iterator.hasNext()){
Employee employee = iterator.next();
System.out.println(employee);
}
/**
* 打印结果:
* Employee{name='test1', age=1, salary=1}
* Employee{name='test2', age=2, salary=2}
* Employee{name='test3', age=3, salary=3}
* Employee{name='test4', age=4, salary=4}
* Employee{name='test5', age=5, salary=5}
*/
}
显然,此时是按照name从小到大排序的,而我们插入的时候是从大到小的,也就是说TreeSet是可以实现Comparable比较器排序的,关于自然排序就看下下面这段代码吧:
@Test
public void test8(){
Set<Integer> set = new TreeSet<>();
set.add(11);
set.add(-1);
set.add(33);
set.add(22);
set.add(44);
Iterator<Integer> iterator = set.iterator();
while (iterator.hasNext()){
System.out.print(iterator.next() + " ");
}
/**
* 打印结果:
* -1 11 22 33 44
*/
}