Java Set讲解
Set集合用于存放无序的,不重复的元素。
HashSet
HashSet类是Set接口的实现类,使用Hash值来决定集合内部存放的位置。
HashSet使用equals()对比元素,当equals()返回true,HashCode也相同,则不接受新元素,反之则允许其放入集合中。
//类A的equals方法总是返回true,但没有重写其hashCode()方法。
public class A {
public boolean equals(Object obj) {
return true;
}
}
HashSet books = new HashSet();
books.add(new A());
books.add(new A());
// 调用
while (aa.hasNext())
{
Object bb = aa.next();
System.out.println(bb.getClass() + "====" + bb.hashCode());
}
/** 运行结果:
class main.A====723074861
class main.A====895328852
*/
由上可知,HashSet在添加前对比两个对象时,equals返回了true,但因为HashCode值不同,所以新添加的元素被判定为2个不同的元素。
//类B的hashCode()方法总是返回1,但没有重写其equals()方法。
public class B {
public int hashCode() {
return 1;
}
}
books.add(new B());
books.add(new B());
/** 运行结果:
class main.B====1
class main.B====1
*/
HashSet在添加两个HashCode不同的元素时,由于equals返回了false,因此新添加的元素被判定为2个不同的元素。
//类C的hashCode()方法总是返回2,且有重写其equals()方法
public class C {
public int hashCode() {
return 2;
}
public boolean equals(Object obj) {
return true;
}
}
books.add(new C());
books.add(new C());
/** 运行结果:
class main.C====2
*/
当equals和hashCode都相同时,HashSet才会将元素判定为同一个对象。
总结:
-
hashCode()决定存放的位置,它和equals必须同时满足才允许新元素加入集合。
-
如果两个对象的hashCode相同,但是它们的equlas返回值不同,HashSet会在这个位置用链式结构来保存多个对象。而HashSet访问集合元素时也是根据元素的HashCode值来快速定位的,这种链式结构会导致性能下降。
-
使用HashSet集合,在重新元素内部equals和hashCode方法时,一定要知悉HashSet内部原理。
LinkedHashSet
LinkedHashSet继承自HashSet,顾名思义,LinkedHashSet在HashSet的基础上使用了链表的存储形式。基于此,我们大致应该猜想到,这种集合保持着顺序。
基于HashSet上的代码进行修改:
Set books = new LinkedHashSet();
books.add(new A());
books.add(new A());
books.add(new B());
books.add(new B());
books.add(new C());
books.add(new C());
books.add("aaa");
books.add("bbb");
books.add("aaa");
Iterator aa = books.iterator();
while (aa.hasNext()) {
Object bb = aa.next();
System.out.println(bb.getClass() + "====" + bb.hashCode());
}
/** 运行结果:
class main.A====723074861
class main.A====895328852
class main.B====1
class main.B====1
class main.C====2
class java.lang.String====96321
class java.lang.String====97314
*/
基于HashSet的知识分析Log可得出,A对象、B对象都被添加了两次,C对象由于被判定为同一个对象,因此不可重复插入。后面新增的 aaa 、bbb 、 aaa 中,前两个都被添加入了集合,最后一个元素又是 aaa ,因此没有存入到集合中。和HashSet()比较得知,LinkedHashSet是保持插入顺序的。
SortedSet
SortedSet是一个接口,该接口在Set接口上又实现了排序接口。
TreeSet
TreeSet nums = new TreeSet();
nums.add(5);
nums.add(2);
nums.add(10);
nums.add(-9);
System.out.println("默认进行了排序:" + nums);
System.out.println("输出集合里的第一个元素:"+nums.first());
System.out.println("输出集合里的最后一个元素:"+nums.last());
System.out.println("返回小于4的子集,不包含4:"+nums.headSet(4));
System.out.println("返回大于等于5的子集:"+nums.tailSet(5));
System.out.println("返回大于等于-3,小于4的子集:"+nums.subSet(-3, 4));
/** 运行结果:
默认进行了排序:[-9, 2, 5, 10]
输出集合里的第一个元素:-9
输出集合里的最后一个元素:10
返回小于4的子集,不包含4:[-9, 2]
返回大于等于5的子集:[5, 10]
返回大于等于-3,小于4的子集:[2]
*/
TeeeSet不再使用HashCode值来决定元素的存储位置了,它采用红黑树的数据结构来存储集合元素。
TreeSet支持两种排序方式: 自然排序、定制排序
自然排序
-
TreeSet会调用集合元素的compareTo(Object obj)方法来比较元素之间的大小关系,然后将集合元素按升序排序,即自然排序。如果试图把一个对象添加到TreeSet时,则该对象的类必须实现Comparable接口,否则程序会抛出异常。
-
当把一个对象加入TreeSet集合中时,TreeSet会调用该对象的compareTo(Object obj)方法与容器中的其他对象比较大小,然后根据红黑树结构找到它的存储位置。如果两个对象通过compareTo(Object obj)方法比较相等,新对象将无法添加到TreeSet集合中(牢记Set是不允许重复的概念)。
-
当需要把一个对象放入TreeSet中,重写该对象对应类的equals()方法时,应该保证该方法与compareTo(Object obj)方法有一致的结果,即如果两个对象通过equals()方法比较返回true时,这两个对象通过compareTo(Object obj)方法比较结果应该也为0(即相等)
定制排序
若要实现自定义排序的效果,可以通过Comparator接口的帮助。该接口里包含一个int compare(T o1, T o2)方法,该方法用于比较大小
TreeSet ts = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
D d1 = (D)o1;
D d2 = (D)o2;
return d1.age > d2.age ? -1
: d1.age < d2.age ? 1 : 0;
}
});
ts.add(new D(5));
ts.add(new D(-3));
ts.add(new D(9));
ts.add(new D(11));
ts.add(new D(2));
System.out.println(ts);
/** 运行结果:
[D[age:11], D[age:9], D[age:5], D[age:2], D[age:-3]]
*/
自然排序、定制排序、Comparator决定的是谁大的问题,即按什么顺序(升序、降序)进行排序
它们的关注点是不同的,一定要注意区分