Set 接口
Set 集合的通用知识
-
Set 它类似一个罐子,程序可以把多个对象“丢进”Set 集合,Set 集合不允许包含相同的元素,如果试把两个相同的元素加入同一个 Set 集合中,则添加操作失败(add()方法会返回 false,且新元素不会被加入)。
-
Set接口是Collection的子接口,set接口没有提供额外的方法
-
Set 判断两个对象是否相同不是使用 == 运算符,而是根据 equals 方法
-
无序性:无序性 != 随机性(教室座位)。
真正的无序性,指的是元素在底层存储的位置是无序的。 -
说明:要求添加进Set中的元素所在的类,一定要重写equals()和hashCode()方法。
Person1 p1 = new Person1("GG", 23);
Person1 p2 = new Person1("GG", 23);
//若没重写,则上面两者都能添加进去
HashSet(典型实现,主要使用类)
主要特点
- HashSet 具有以下特点:
- 不能保证元素的排列顺序
- HashSet 不是线程安全的
- 集合元素可以是 null
- HashSet 集合判断两个元素相等的标准:两个对象通过 hashCode() 方法比较相等,并且两个对象的 equals() 方法返回值也相等。
注意:
如果需要把某个类的对象保存到 HashSet 集合中,重写这个类的 equals() 方法和 hashCode() 方法时,应该尽量保证两个对象通过 equals() 方法比较返回 true 时,它们的 hashCode() 方法的返回值也相等。
下面程序分别提供三个类A、B和C,它们分别重写了 equals()、hashCode() 两个方法的一个或全部,通过此程序可以让读者看到 HashSet 判断集合元素相同的标准:
package www.xq;
import java.util.HashSet;
/**
* @author xqstart
* @date 2018/10/23 - 7:55
*/
//类A的 equals() 方法总是返回 true,但没有重写其 hashCode() 方法
class A{
public boolean equals(Object obj){
return true;
}
}
//类 B 的 hashCode() 方法总是返回 1,但是没有重写其 equals() 方法
class B{
public int hashCode() {
return 1;
}
}
//类 C 的hashCode() 方法总是返回 2,且重写其 equals() 方法总是返回 true
class C{
public int hashCode(){
return 2;
}
public boolean equals(Object obj){
return true;
}
}
public class TestHashSet {
public static void main(String[] args) {
HashSet books = new HashSet();
//分别向 books 集合中添加两个 A 对象、两个 B 对象、两个 C 对象
books.add(new A());
books.add(new A());
books.add(new B());
books.add(new B());
books.add(new C());
books.add(new C());
System.out.println(books);
}
}
分析如下:
注意:
当向 HashSet 中添加可变对象时,必须十分小心。如果修改 HashSet 集合中的对象,有可能导致该对象与集合中的其他对象相等,从而导致 HashSet 无法准确访问该对象。
哈希算法
-
Set中的元素是如何存储的呢?使用了哈希算法。(学长讲解)
-
当向Set中添加对象时,首先调用此对象所在类的hashCode()方法,计算此对象的哈希值
,此哈希值决定了此对象在Set中的存储位置。若此位置之前没有对象存储,则这个对象直接存储到此位置。 -
若此位置已有对象存储,再通过equals()比较这两个对象是否相同。如果相同,后一个对象就不能再添加进来。
-
LinkedHashSet
-
LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,但它同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的。
-
LinkedHashSet插入性能略低于 HashSet,但在迭代访问 Set 里的全部元素时有很好的性能。
-
图解
TreeSet
-
向TreeSet中添加的元素必须是同一个类的,否则会引发 ClassCastException
-
可以按照添加进集合中的元素的指定的顺序遍历。像String,包装类等默认按照从小到大的顺序遍历
-
当自定义类没有实现Comparable接口时,当向TreeSet中添加自定义类对象时,报 ClassCastException。
-
当向TreeSet中添加自定义类的对象时,有两种排序方法:
①自然排序(可操作类用)②定制排序(优先) -
自然排序:要求自定义类实现(java.lang.Comparable)接口并重compareTo(Object obj)
在此方法中,指明按照自定义类的哪个属性进行排序。 -
向TreeSet中添加元素时,首先按照compareTo(),一旦返回0,虽然仅是两个对象此
属性值相同,但是程序会认为这两个对象是相同的,进而后一个对象就不能添加进来。
compareTo()与hashCode()以及equals()三者保持一致!???
各 Set 实现类的性能分析
-
HashSet 和 TreeSet 是 Set 的两个典型体现,到底该如何选择呢?
- HashSet 的性能是比 TreeSet 好(特别是最常用的添加、查询元素等操作),因为 TreeSet 需要额外的红黑树算法来维护集合的次序。只有当需要一个保持排序的 Set 时,才应该使用 TreeSet,否则都应该使用 HashSet。
-
HashSet 还有一个子类:LinkedHashSet,对于普通的插入,删除操作,LinkedHashSet 比 HashSet 要略微慢一点,这是由维护链表所带来的额外开销造成的,但由于有了链表,遍历 LinkedHashSet 会更快。
-
EnumSet 是所有 Set 实现类中性能最好的,但它只能保存同一个枚举类的枚举值作为集合元素。
-
必须指出,Set 的三个实现类 HashSet、TreeSet、EnumSet 都是线程不安全的。如果有多个线程同时访问一个 Set 集合,并且有超过一个线程修改了该 Set 集合,则必须手动保持该 Set 集合的同步性。通常可以通过 Collections 工具类的 synchronizedSortedSet 方法来“包装”该 Set 集合,此操作最好在创建时进行,以防止对 Set 集合的意外非同步访问。例如:
SortedSet s = Collections.synchronizedSortedSet(new TreeSet(...));