阅读这篇文章只需5-10分钟。
HashSet
HashSet 按Hash 算法来存储集合中的元素,因此具有很好的存取和查找性能。
Hash算法:
(也被翻译成哈希、散列)算法,它能保证快速查找被检索的对象,hash算法的价值在于速度。
当需要查询集合中某个元素时,hash算法可以直接根据该元素的hashCode值计算出该元素的存储位置,从未快速定位该元素。
为什不直接使用数组,还需要使用HashSet呢?
因为数组元素的索引是连续的,而且数组的长度是固定的,无法自由的增加数组的长度。而HashSet不一样,HashSet采用每个元素的hashCode值来计算其存储的位置,从而可以自由增加HashSet的长度,并可以根据元素的hashCode值来访问元素。
因此,当从HashSet中访问元素时,HashSet先计算该元素的hashCode值(也就是调用该对象的hashCode()方法返回的值),然后直接到该hashCode值对应的位置取出该元素,这就是HashSet速度很快的原因。
特点:
1 不保证元素的排列顺序,顺序可能与添加顺序不同,顺序也有可能发生变化;
2 HashSet不是同步的,如果多个线程同时访问HashSet,则必须通过代码来保证同步;
3 集合元素值允许为null;
注意:
当向HashSet中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据该hashCode值来决定该对象在HashSet中的存储位置。如果有两个元素通过equals()方法比较返回true,但它们的hashCode不相等,HashSet会将它们存储在不同的位置,依然可以添加成功。也就是说,HashSet判断两个元素相等的标准是两个对象通过equals()方法比较相等,并且两个对象的hashCode()方法返回的值也相等。
示例:
package com.collection;
import java.util.HashSet;
public class HashSetTest {
public static void main(String[] args) {
HashSet set = new HashSet();
set.add(new A());
set.add(new A());
set.add(new B());
set.add(new B());
set.add(new C());
set.add(new C());
System.out.println(set);
//[com.collection.A@1, com.collection.A@1,
//com.collection.C@2,//当hashCode 与equals()都满足条件时,HashSet是不允许有重复数据出现的
//com.collection.B@15db9742, com.collection.B@6d06d69c]
}
}
class A {
public int hashCode() {
return 1;
}
}
class B {
public boolean equals(Object o) {
return true;
}
}
class C {
public boolean equals(Object o) {
return true;
}
public int hashCode() {
return 2;
}
}
HashSet注意事项
当向HashSet中添加可变对象时,必须十分小心,如果修改HashSet集合中的对象,有可能导致该对象与集合中的其他对象相等,从而导致HashSet无法准确访问该对象。
示例:
package com.collection;
import java.util.HashSet;
import java.util.Iterator;
public class ChangeHashSetTest {
public static void main(String[] args) {
HashSet set = new HashSet();
set.add(new R(5));
set.add(new R(9));
set.add(new R(-3));
set.add(new R(-2));
System.out.println("----原");
System.out.println(set);
System.out.println("set.contains(new R(-2)) = "+set.contains(new R(-2)));
System.out.println("set.contains(new R(-3)) = "+set.contains(new R(-3)));
Iterator it = set.iterator();
R r = (R) it.next();
r.count = -3;
System.out.println("----修改对象");
System.out.println(set);
set.remove(new R(-3));
System.out.println("----删除对象 -3 ");
System.out.println(set);
System.out.println("set.contains(new R(-2)) = "+set.contains(new R(-2)));
System.out.println("set.contains(new R(-3)) = "+set.contains(new R(-3)));
输出内容:
//----原
//[R[count-2], R[count-3], R[count5], R[count9]]
//set.contains(new R(-2)) = true
//set.contains(new R(-3)) = true
//----修改对象
//[R[count-3], R[count-3], R[count5], R[count9]]
//----删除对象 -3
//[R[count-3], R[count5], R[count9]]
//set.contains(new R(-2)) = false
//set.contains(new R(-3)) = false
}
}
class R {
int count;
public R(int count) {
this.count = count;
}
public int hashCode() {
return this.count;
}
public String toString() {
return "R[count" + count + "]";
}
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj != null && obj.getClass() == R.class) {
R r = (R) obj;
return this.count == r.count;
}
return false;
}
}
问题:
R类重写了hsshCode()与equals()方法,这两个方法都是根据R对象的count示例变量来判断的。当Set集合中第一个对象的count实例变量被改变时,这将导致该R对象与集合中其他对象相同。
HashSet集合中的第一个元素和第二个元素完全相同,这表明两个元素已经重复。此时HashSet会比较混乱:当试图删除count 为-3的R 对象时,HashSet会计算出该对象hashCode值,从而找到该对象在集合中的保存位置,然后把此处的对象与count为-3的R 对象通过equals()方法进行比较,如果相等则删除该对象–HashSet只有第二个元素才满足该条件(第一个元素实际上保存在count 为-2的R 对象相应的位置),所以第二个元素被删除。至于第一个count为 -3的R对象,是保存在count 为 -2的R 对象对应的位置,但使用equals()方法拿它和count为 -2 的R对象比较时又返回false,这将导致HashSet不可能准确的访问该元素。
LinkedHashSet
HashSet的子类,LinkedHashSet也是根据元素的hashCode值来决定元素的存储位置,但它同时使用链表维护元素的次序,这样使得元素看起来是以插入的顺序保存的。
当遍历LinkedHashSet集合时,LinkedHashSet将会按元素的添加顺序来访问集合里的元素。
缺点:
LinkedHashSet需要维护元素的插入顺序,因此性能略低于HashSet的性能;
优点:
但在迭代访问Set里的全部元素时将会有很好性能,因为它以链表来维护内部顺序。
示例:
package com.collection;
import java.util.LinkedHashSet;
public class LinkedHashSetTest {
public static void main(String[] args) {
LinkedHashSet set = new LinkedHashSet();
set.add("Java课堂");
set.add("Java doc");
set.add("Java doc");
set.add("Java 精简版");
System.out.println(set);
//输出 [Java课堂, Java doc, Java 精简版]
}
}
注意:
LinkedHashSet依然是HashSet,因此,它不允许有重复的元素。
LinkedHashSet, 对于普通的插入,删除操作,LinkedHashSet比HashSet要略微慢一点,这是由维护链表所带来的额外开销造成的,但由于有了链表,遍历LinkedHashSet会更快。
Things won are done; joy’s soul lies in the doing.
得到即是完结,快乐的精髓在于过程