Java集合之HashSet和LinkedHashSet

阅读这篇文章只需5-10分钟。

HashSet

HashSetHash 算法来存储集合中的元素,因此具有很好的存取和查找性能。

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.
得到即是完结,快乐的精髓在于过程

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值