Java面向对象系列[v1.0.0][Set]

Set集合与Collection基本相同,没有提供任何额外的方法,实际上Set就是Collection,只是Set不允许包含重复元素

HashSet类

HashSet是Set接口的典型实现,它是按Hash算法来存储集合中的元素,也因此具备了很好的存取和查找性能,其特点如下:

  • 不能保证元素的排列sh顺序,顺序可能与添加顺序不同,而且顺序还可能发生变化
  • HashSet不是同步的,如果多个线程同时访问一个HashSet,假设有两个或者两个以上线程同时修改了HashSet集合,那么必须通过代码来保证其同步
  • 集合元素值可以是null

当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据该hashCode值决定该对象在HashSet中的存储位置。如果有两个元素通过equals()方法比较返回true,但他们的hashCode()方法返回值不相等,那么HashSet将会把他们存储在不同的位置,添加可以成功,也就是说HashSet判断两个元素相等的标准是两个对象通过equals()方法比较相等并且两个对象的hashCode()方法返回值也相等。

package demo;

import java.util.*;
import static java.lang.System.*;
/**
 * Description:
 * 网站: <a href="https://davieyang.blog.csdn.net/">全栈软件测试</a><br>
 * Copyright (C), 2001-2020, Davieyang.D.Y<br>
 * This program is protected by copyright laws.<br>
 * Program Name:<br>
 * Date:<br>
 * @author Davieyang.D.Y
 */

// 类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 HashSetTest
{
    public static void main(String[] args)
    {
        var 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());
        out.println(books);
    }
}

添加了两个A的对象,两个B的对象和两个C的对象,其中C类重写了equals()方法总是返回ture,hashCode()方法总是返回2,这将导致HashSet把两个C对象当成同一个对象执行结果为:
[demo.A@49e4cb85, demo.B@1, demo.B@1, demo.A@7c30a502, demo.C@2]

  • 两个A的对象通过equals()方法比较返回true,但HashSet依然把他们当成两个对象
  • 两个B的对象的hashCode()返回相同的值1,但HashSet依然把它们当成两个对象
  • 两个C的对象的equals()和hashCode()返回相同的值,因此只有一个对象被添加到了集合中,结果里只有一个C的对象

因为这两个类都只是重写了一个方法,一个是equals()另一个是hashCode(),并没有同时重写,就出现了equals()方法返回值相同但hashCode()返回值不同,或者hashCode()返回值相同但equals()方法返回值不同的情况,因此如果要重写类的equals()方法和hashCode()方法的话,应该尽量保证这两个方法的返回值相同

  • 如果两个对象通过equals()方法比较返回true,但这两个对象的hashCode()方法返回不同的hashCode()值时,这将导致HashSet会把这两个对象保存在Hash表的不同位置,从而使两个对象都可以添加成功,这就与Set集合的规则冲突了
  • 如果两个对象的hashSet()方法返回的ha’shCode值相同,但他们通过equals()方法比较返回false时就更麻烦,两个对象的hashCode值相同,HashSet将视图把他们保存在相同的位置,实际上会在这个位置用链式结构来保存多个对象,当HashSet访问集合元素时也是根据元素的hasCode来快速定位,当两个以上的元素具备相同的hashCode值,将会导致性能下降

数组和HashSet区别

数组是所有能存储一组元素里最块的数据结构,它可以包含多个元素,每个元素都有索引,如果要访问某个数组元素,只需要提供元素的索引,即可根据索引计算该元素在内存里的存储位置;表面上看HashSet没有索引,但实际上当程序向HashSet添加元素时,HashSet会根据该元素的hashCode值来计算他存储位置,从而进行快速定位;然而数组元素的索引是连续的,而且数组的长度是固定的,无法自由增加数组长度,而HashSet就不一样了,它根据元素的hashCode来计算存储位置,,从而可以自由增加HashSet的长度,并且当访问元素的时候也是根据元素的ha’shCode值来访问元素。

HashSet中每个能存储元素的槽位(slot)通常称为桶(bucket),如果有多个元素的hashCode值相同,但t他们的通过equals()方法比较返回false,就需要在一个桶里放多个元素,这样就会导致性能下降。

重写hashCode()方法的基本原则

在程序运行过程中,同一个对象多次调用hashCode()方法应该返回相同的值
当两个对象通过equals()方法比较返回true时,这两个对象的hashCode()方法应返回相等的值
对象中用作equals()方法比较标准的实例变量,都应该用于计算hashCode值

重写hashCode()方法的基本步骤

把对象内每个有意义实例变量(即每个参与equals()方法比较标准的实例变量)计算出一个int类型的hashCode值,计算方式如下
在这里插入图片描述
用上一步计算出来的多个hashCode值组合计算出一个hashCode值返回,例如
return f1.hashCode() + (int)f2;
为了避免直接相加产生偶然相等,可以通过各实例变量的hashCode值乘以任意一个质数后再相加,例如
return f1.hashCode() * 19 + (int)f2 * 31;
如果想HashSet中添加一个可变对象后,后续程序修改了该可变对象的实例变量,则可能导致它与集合中的其他元素相同(即两个对象通过equals()方法比较返回true,两个对象的hashCode值也相等),这就可能导致HashSet中包含两个相同的对象

package demo;
import java.util.*;
import static java.lang.System.*;
/**
 * Description:
 * 网站: <a href="https://davieyang.blog.csdn.net/">全栈软件测试</a><br>
 * Copyright (C), 2001-2020, Davieyang.D.Y<br>
 * This program is protected by copyright laws.<br>
 * Program Name:<br>
 * Date:<br>
 * @author Davieyang.D.Y
 */
class R
{
    int count;
    public R(int count)
    {
        this.count = 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)
        {
            var r = (R) obj;
            return this.count == r.count;
        }
        return false;
    }
    public int hashCode()
    {
        return this.count;
    }
}
public class HashSetTest2
{
    public static void main(String[] args)
    {
        var hs = new HashSet();
        hs.add(new R(5));
        hs.add(new R(-3));
        hs.add(new R(9));
        hs.add(new R(-2));
        // 打印HashSet集合,集合元素没有重复
        System.out.println(hs);
        // 取出第一个元素
        var it = hs.iterator();
        var first = (R) it.next();
        // 为第一个元素的count实例变量赋值
        // 改变了Set集合中第一个R对象的count实例变量的值,这将导致该R对象与集合中的其他对象相同
        first.count = -3;
        // 再次输出HashSet集合,集合元素有重复元素
        out.println(hs);
        // 删除count为-3的R对象
        // HashSet会计算出该对象的hashCode值,从而找出该对象在集合中的位置
        // 然后把此处的对象与count为-3的R对象通过equals()方法进行比较
        // 如果相等则删除该对象,这里只有第二个元素才满足条件
        // 第一个元素实际上保存在count为-2的R对象对应的位置,因此第二个元素被删除
        hs.remove(new R(-3));
        // 可以看到被删除了一个R元素
        out.println(hs);
        // 输出false
        out.println("hs是否包含count为-3的R对象?" + hs.contains(new R(-3)));
        // 输出false
        out.println("hs是否包含count为-2的R对象?" + hs.contains(new R(-2)));
    }
}

执行结果为:

[R[count:-2], R[count:-3], R[count:5], R[count:9]]
[R[count:-3], R[count:-3], R[count:5], R[count:9]]
[R[count:-3], R[count:5], R[count:9]]
hs是否包含count为-3的R对象?false
hs是否包含count为-2的R对象?false

LinkedHashSet类

HashSet还有一个子类LinkedHashSet,LinkedHashSet集合也是根据元素的hashCode值来决定元素的存储位置,但他同时使用链表维护元素的次序,这样使得元素看起来以插入的顺序保存的,当遍历LinkedHashSet集合里的元素时,LinkedHashSet将会按元素的添加顺序来访问集合里的元素。
同时也因为LinkedHashSet需要维护元素的插入顺序,性能也就略低于HashSet的性能,但在迭代访问Set里的全部元素时将有很好的性能,因为它以链表来维护内部顺序。
虽然LinkedHashSet使用了链表记录集合元素的添加顺序,但LinkedHashSet依然是HashSet,依然不允许集合元素重复

package demo;
import java.util.*;
import static java.lang.System.*;

public class LinkedHashSetTest
{
    public static void main(String[] args)
    {
        var books = new LinkedHashSet();
        books.add("what is going on?");
        books.add("How are you doing today!");
        // 输出LinkedHashSet集合的元素时,元素的顺序总是与添加顺序一致
        out.println(books);
        books.remove("what is going on?");
        // 重新添加 
        books.add("what is going on?");
        // 输出LinkedHashSet集合的元素时,元素的顺序总是与添加顺序一致
        out.println(books);
    }
}

TreeSet类

TreeSet是SortedSet接口的实现类,它可以确保集合元素处于排序状态,并提供了几个额外的方法:

  • Comparator comparator():如果TreeSet采用了订制排序,则该方法返回订制排序所使用的Comparator;如果采用了自然排序则返回null
  • Object first():返回集合中的第一个元素
  • Object last(0:返回集合中的最后一个元素
  • Object lower(Object e):返回集合中位于指定元素之前的元素(即小于指定元素的最大元素,参考元素不需要是TreeSet集合里的元素)
  • Object higher(Object e):返回集合中位于指定元素之后的元素(即大于指定元素的最小元素,参考元素不需要时TreeSet集合里的元素)
  • SortedSet subSet(Object fromElement, Object toElement):返回此Set的子集合,范围从fromElement(包含)到toElement(不包含)
  • SortedSet headSet(Object toElement):返回此Set的子集,由小于toElement的元素组成
  • SortedSet tailSet(Object fromElement):返回此Set的子集,由大于或等于fromElement的元素组成
package demo;
import java.util.*;
import static java.lang.System.*;

public class TreeSetTest
{
    public static void main(String[] args)
    {
        var nums = new TreeSet();
        // 向TreeSet中添加四个Integer对象
        nums.add(5);
        nums.add(2);
        nums.add(10);
        nums.add(-9);
        // 输出集合元素,看到集合元素已经处于排序状态
        out.println(nums);
        // 输出集合里的第一个元素
        // 输出-9
        out.println(nums.first());
        // 输出集合里的最后一个元素
        // 输出10
        out.println(nums.last());
        // 返回小于4的子集,不包含4
        // 输出[-9, 2]
        out.println(nums.headSet(4));
        // 返回大于5的子集,如果Set中包含5,子集中还包含5
        // 输出 [5, 10]
        out.println(nums.tailSet(5));
        // 返回大于等于-3,小于4的子集。
        // 输出[2]
        out.println(nums.subSet(-3, 4));
    }
}

TreeSet并不是根据元素的插入顺序进行排序的,而是根据元素实际值大小来进行排序的,并且与HashSet集合采用hash算法来决定元素的存储位置不同,TreeSet采用红黑树的数据结构来存储集合元素。
TreeSet支持两种排序方法,分别是自然排序和定制排序

自然排序

TreeSet会调用集合元素的compareTo(Object obj)方法来比较元素之间的大小关系,然后将集合元素按升序排列,这种方式就是自然排序。
Java提供了一个Comparable接口,该接口里定义了一个compareTo(Object obj)方法,该方法返回一个整数值,实现该接口的类必须实现该方法,实现了该接口的类的对象就可以比较大小,当调用该方法与另一个对象进行比较时,例如obj1.compareTo(obj2),如果返回0,则表明这两个对象相等,如果返回一个正整数,则表明obj1大于obj2;如果该方法返回一个负整数,则表明obj1小于obj2.

Java的一些常用类已经实现了Comparable接口,并提供了比较大小的标准:

  • BigBigDecimal、BigInteger以及所有的数值类型对应的包装类:可以按数值大小进行比较
  • Character:按字符的UNICODE值进行比较
  • Boolean:true对应的包装类实例大于false对应的包装类实例
  • String:按字符串中字符的UNICODE值进行比较
  • Date、Time:后面的时间、日期比前面的时间、日期大
    如果试图把一个对象添加到TreeSet时,则该对象的类必须实现Comparable接口,否则程序将会抛出异常
package demo;
import java.util.*;

class Err{}
public class TreeSetErrorTest
{
    public static void main(String[] args)
    {
        var ts = new TreeSet();
        // 向TreeSet集合中添加Err对象
        // 自然排序时,Err没实现Comparable接口将会引发错误
        ts.add(new Err());
    }
}

执行结果会报如下异常

Exception in thread "main" java.lang.ClassCastException: class demo.Err cannot be cast to class java.lang.Comparable

大部分类实现compareTo(Object obj)方法时,都需要将被比较对象objqiang’zh向强制类型转换成相同的类型,因为只有相同类的两个实例才能比较大小,当把一个对象添加到TreeSet集合时,TreeSet会调用该对象的compareTo(Object obj)方法与集合中其他的对象进行比较,这就要求集合中的其他元素与该元素是同一个类的实例,也就是说向TreeSet中添加的应该是同一个类的对象,否则也会引发ClassCastException异常

package demo;
import java.util.*;

public class TreeSetErrorTest2
{
    public static void main(String[] args)
    {
        var ts = new TreeSet();
        // 向TreeSet集合中添加两个对象
        ts.add(new String("blog.csdn.com.davieyang"));
        // 抛异常
        ts.add(new Date());   
    }
}

当把一个对象加入TreeSet集合中时,TreeSet调用该对象的compareTo(Object obj)方法与容器中的其他对象比较大小,然后根据红黑树结构找到它的存储位置,如果两个对象通过compareTo(Object obj)方法比较相等,新对象将无法添加到TreeSet集合中,TreeSet判断两个对象是否相等唯一的标准就是两个对象通过compareTo(Object obj)方法比较返回0

package demo;
import java.util.*;
import static java.lang.System.*;

class Z implements Comparable
{
    int age;
    public Z(int age)
    {
        this.age = age;
    }
    // 重写equals()方法,总是返回true
    public boolean equals(Object obj)
    {
        return true;
    }
    // 重写了compareTo(Object obj)方法,总是返回1
    public int compareTo(Object obj)
    {
        return 1;
    }
}
public class TreeSetTest2
{
    public static void main(String[] args)
    {
        var set = new TreeSet();
        var z1 = new Z(6);
        set.add(z1);
        // 第二次添加同一个对象,输出true,表明添加成功
        out.println(set.add(z1));
        // 下面输出set集合,将看到有两个元素
        out.println(set);
        // 修改set集合的第一个元素的age变量
        ((Z)(set.first())).age = 9;
        // 输出set集合的最后一个元素的age变量,将看到也变成了9
        out.println(((Z)(set.last())).age);
    }
}

在这里插入图片描述
当需要把一个对象放入TreeSet中时,重写该对象对应类的equals()方法时,应保证该方法与compareTo(Object obj)方法有一致的结果,比如如果两个对象通过equals()方法比较返回true,那么这两个对象通过compareTo(Object obj)方法比较应该返回0

如果向TreeSet中添加一个可变对象后,并且后面程序修改了该可变对象的实例变量,这将导致它与其他对象大小顺序发生了改变,但TreeSet不会在此调整他们的顺序,甚至可能导致TreeSet中保存的两个对象通过compareTo(Object obj)方法比较返回0

package demo;

import java.util.*;
import static java.lang.System.*;

class R implements Comparable
{
    int count;
    public R(int count)
    {
        this.count = count;
    }
    public String toString()
    {
        return "R[count:" + count + "]";
    }
    // 重写equals方法,根据count来判断是否相等
    public boolean equals(Object obj)
    {
        if (this == obj)
        {
            return true;
        }
        if (obj != null && obj.getClass() == R.class)
        {
            var r = (R) obj;
            return r.count == this.count;
        }
        return false;
    }
    // 重写compareTo方法,根据count来比较大小
    public int compareTo(Object obj)
    {
        var r = (R) obj;
        return count > r.count ? 1 :
                count < r.count ? -1 : 0;
    }
}
public class TreeSetTest3
{
    public static void main(String[] args)
    {
        var ts = new TreeSet();
        ts.add(new R(5));
        ts.add(new R(-3));
        ts.add(new R(9));
        ts.add(new R(-2));
        // 打印TreeSet集合,集合元素是有序排列的
        System.out.println(ts);    
        // 取出第一个元素
        var first = (R) ts.first();
        // 对第一个元素的count赋值
        first.count = 20;
        // 取出最后一个元素
        var last = (R) ts.last();
        // 对最后一个元素的count赋值,与第二个元素的count相同
        last.count = -2;
        // 再次输出将看到TreeSet里的元素处于无序状态,且有重复元素
        out.println(ts);   
        // 删除实例变量被改变的元素,删除失败
        out.println(ts.remove(new R(-2)));  
        out.println(ts);
        // 删除实例变量没有被改变的元素,删除成功
        out.println(ts.remove(new R(5)));    
        out.println(ts);
    }
}

R对象对应的类重写了equals()方法,compareTo()方法,这两个方法都以R对象的count实例变量作为判断的依据
改变了count的值后,集合处于无序状态,且包含了重复元素,如下第一行和第二行结果所示是改变了count值前后的结果

D:\BaiduNetdiskDownload\CrazyJava\codes\08\8.3>java TreeSetTest3
[R[count:-3], R[count:-2], R[count:5], R[count:9]]
[R[count:20], R[count:-2], R[count:5], R[count:-2]]
false
[R[count:20], R[count:-2], R[count:5], R[count:-2]]
true
[R[count:20], R[count:-2], R[count:-2]]

改变了TreeSet集合里可变元素的实例变量,当再删除该对象时,TreeSet也会删除失败,只能删除TreeSet中没有被修改的实例变量,并且该变量是不与其他被修改的实例变量的对象重复的才可以删除
ts.remove(new R(5))删除成功后,TreeSet会对集合中的重新索引(不是重新排序),然后就可以删除TreeSet中的所有元素了
对于HashSet和TreeSet而言,为了程序的健壮性,不建议修改已经放入集合中的元素的关键实例变量

定制排序

TreeSet的自然排序是根据集合元素的大小,将他们升序排列,如果需要实现订制排序,例如降序排列,则可以通过Comparator接口帮助,该接口包含一个int compare(T ol1, T ol2)方法,用于比较ol1和ol2的大小,返回正整数表示ol1大于ol2,返回0表示相等,返回负整数表示ol1小于ol2

如果要实现订制排序,则需要在创建TreeSet集合对象时,提供一个Comparator对象与该TreeSet集合关联,由该Comparator对象负责集合元素的排序逻辑,Comparator是一个函数式接口,可以使用Lambda表达式来代替Comparator对象

package demo;
import java.util.*;
import static java.lang.System.*;

class M
{
    int age;
    public M(int age)
    {
        this.age = age;
    }
    public String toString()
    {
        return "M [age:" + age + "]";
    }
}
public class TreeSetTest4
{
    public static void main(String[] args)
    {
        // 此处Lambda表达式的目标类型是Comparator
        var ts = new TreeSet((o1, o2) ->
        {
            var m1 = (M) o1;
            var m2 = (M) o2;
            // 根据M对象的age属性来决定大小,age越大,M对象反而越小
            return m1.age > m2.age ? -1 : m1.age < m2.age ? 1 : 0;
        });
        ts.add(new M(5));
        ts.add(new M(-3));
        ts.add(new M(9));
        out.println(ts);
    }
}
  • 当通过Comparator对象或者Lambda表达式来实现TreeSet的订制排序时,依然不能向TreeSet中添加类型不同的对象
  • 定制排序无视元素本身的大小,完全由Comparator对象或Lambda表达式负责集合元素的排序规则
  • TreeSet判断两个集合元素相等的标准是:通过Comparator或Lambda表达式比较两个元素返回0,则不会把第二个元素添加进去

EnumSet类

EnumSet是一个专门为枚举类设计的集合类,EnumSet中的所有元素都必须是指定枚举类型的枚举值,该枚举类型在创建EnumSet时显示或隐式的指定,并且EnumSet的集合元素也是有序的,EnumSet以枚举值在Enum类内的定义顺序来决定集合元素的顺序
EnumSet在内部以位向量的形式存储,这种存储形式非常紧凑、高效,因此EnumSet对象占用内存很小,而且运行效率很好,尤其是进行批量操作(例如调用containsAll()和retainAll())时,如果其参数也是EnumSet集合,则该批量操作的执行速度也非常快
EnumSet集合不允许加入null元素,如果视图插入null元素,EnumSet将抛出NullPointerException异常
EnumSet类没有暴露任何构造器来创建该类的实例,程序应该通过它提供的类方法来创建EnumSet对象,常用创建对象的方法如下:

  • EnumSet allOf(Class elementType):创建一个包含指定枚举类里所有枚举值的EnumSet集合
  • EnumSet complementOf(EnumSet s):创建一个其元素类型与指定EnumSet里元素类型相同的EnumSet集合,新EnumSet集合包含原EnumSet集合所不包含的、此枚举类剩下的枚举值(即新EnumSet集合和- 原EnumSet集合的集合元素加起来就是该枚举类的所有枚举值)
  • EnumSet copyOf(Collection c):使用一个普通集合来创建EnumSet集合
  • EnumSet copyOf(EnumSet s):创建一个与指定EnumSet具有相同元素类型、相同集合元素的EnumSet集合
  • EnumSet noneOf(Class elementType):创建一个元素类型为指定枚举类型空EnumSet
  • EnumSet of(E first, E…rest):创建一个包含一个或多个枚举值的EnumSet集合,传入的多个枚举值必须属于同一个枚举类
  • EnumSet range(E from, E to):创建一个包含从from枚举值到to枚举值范围内所有枚举值的EnumSet集合
package demo;
import java.util.*;
import static java.lang.System.*;
enum Season
{
    SPRING,SUMMER,FALL,WINTER
}
public class EnumSetTest
{
    public static void main(String[] args)
    {
        // 创建一个EnumSet集合,集合元素就是Season枚举类的全部枚举值
        var es1 = EnumSet.allOf(Season.class);
        // 输出[SPRING, SUMMER, FALL, WINTER]
        out.println(es1); 
        // 创建一个EnumSet空集合,指定其集合元素是Season类的枚举值。
        var es2 = EnumSet.noneOf(Season.class);
        // 输出[]
        out.println(es2); 
        // 手动添加两个元素
        es2.add(Season.WINTER);
        es2.add(Season.SPRING);
        // 输出[SPRING, WINTER]
        out.println(es2);
        // 以指定枚举值创建EnumSet集合
        var es3 = EnumSet.of(Season.SUMMER, Season.WINTER);
        // 输出[SUMMER, WINTER]
        out.println(es3); 
        var es4 = EnumSet.range(Season.SUMMER, Season.WINTER);
        // 输出[SUMMER, FALL, WINTER]
        out.println(es4); 
        // 新创建的EnumSet集合的元素和es4集合的元素有相同类型,
        // es5的集合元素 + es4集合元素 = Season枚举类的全部枚举值
        var es5 = EnumSet.complementOf(es4);
        // 输出[SPRING]
        out.println(es5); 
    }
}

当试图复制一个Collection集合里的元素来创建EnumSet集合时,必须保证Collection集合里的所有元素都是同一个枚举类的枚举值

package demo;
import java.util.*;
import static java.lang.System.*;
public class EnumSetTest2
{
    public static void main(String[] args)
    {
        var c = new HashSet();
        c.clear();
        c.add(Season.FALL);
        c.add(Season.SPRING);
        // 复制Collection集合中所有元素来创建EnumSet集合
        var enumSet = EnumSet.copyOf(c); 
        // 输出[SPRING, FALL]
        out.println(enumSet);
        c.add("我醉欲眠卿且去");
        c.add("锄禾日当午汗滴禾下土谁知盘中餐粒粒皆辛苦");
        // 下面代码出现异常:因为c集合里的元素不是全部都为枚举值
        enumSet = EnumSet.copyOf(c); 
    }
}

各Set实现类的性能分析

  • HashSet的性能总是比TreeSet好,TreeSet需要额外的红黑树算法来维护集合元素的次序,只有当需要保持排序的Set时,才应该使用TreeSet,否则都应该使用HashSet
  • HashSet还有个子类LinkedHashSet,对于普通的插入、删除操作LinkedHashSet比HashSet稍差,因为LinkedHashSet需要维护链表,但由于有了链表,遍历LinkedHashSet会更快
  • EnumSet是所有Set实现类里性能最好的,但它只能保存一个枚举类的枚举值作为集合元素

Set的三个实现类HashSet、TreeSet和EnumSet都是线程不安全的,多个线程访问同一个Set集合,并且有超过一个线程修改了Set集合,则必须手动保证该Set集合的同步性,通常可以通过Collections工具类的synchronizedSortedSet方法来“包装”该Set集合,此操作最好在创建时进行,以防止对Set集合的以外非同步访问
SortedSet s = Collections.synchronizedSortedSet(new TreeSet(...));

发布了207 篇原创文章 · 获赞 124 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/dawei_yang000000/article/details/105148913
今日推荐