1. Set集合通常不能记住元素的添加顺序。Set不允许包含重复的元素。
  2. Set集合不允许包含相同的元素,如果试图把两个相同的元素加入同一个Set集合中,则添加操作失败,add()方法返回false,且新元素不会被加入。

HashSet类

  1. HashSet按照Hash算法来存储集合中的元素,因此具有很好的存取和查询性能。
  2. HashSet特点

    • 不能保证元素的排列顺序,顺序可能与添加顺序不同,顺序也有可能发生变化。
    • HashSet不是同步的,如果多个线程同时访问一个HashSet,假设有两个或者两个以上线程同时修改了HashSet集合时,则必须通过代码来保证其同步。
    • 集合元素值可以是null;
  3. 当HashSet集合中存入一个元素时,HashSet会调用该对象的HashCode()方法来得到该对象的hashCode值,然后根据该hashCode值决定该对象在HashSet中的存储位置,如果有两个元素通过equals()方法比较返回true,但他们的hashCode()方法返回值不相等,HashSet将会把他们存在不同位置,依然可以添加成功。
  4. HashSet集合判断两个元素的标准是两个对象通过equals()方法比较相等,并且两个对象的hashCode()方法返回值也相等。
  5. 即使两个A对象通过equals()方法比较返回true,但HashSet依然把他们当成两个对象,即使两个B对象的hashCode()方法返回相同值,但HashSet依然把他们当成两个对象。
  6. 当把一个对象放入HashSet中时,如果需要重写该对象对应类的equals()方法,则也应该重写其hashCode()方法,规则是:如果两个对象通过equals()方法比较返回true,这两个对象的hashCode值也应该相同。
  7. 如果两个对象通过equals()比较返回true,但这两个对象的hashCode()方法返回不同的hashCode值时,这将导致HashSet会把这两个对象保存在Hash表的不同位置,从而使两个对象都可以添加成功,这就与Set集合规则冲突了。
  8. 如果两个对象的hashCode()值返回的值相同,但他们通过equals()方法比较返回false时更麻烦。因为两个对象的hashCode值相同,HashSet将试图把他们保存在同一个位置,但又不行(否则将只剩下一个对象),所以实际上回在这个位置用链式结构来保存多个对象;而HashSet访问集合元素时也是根据元素的hashCode值来快速定位的,如果HashSet中两个以上的元素具有相同的hashCode值,将会导致性能下降。
  9. Hash算法可以直接根据该元素的hashCode值计算出该元素的存储位置,从而快速定位该元素。
  10. 数组是所有能存储一组元素里最快的数据结构。
  11. 当从HashSet中访问元素时,HashSet先计算该元素的hashCode值,也就是调用该元素的hashCode()方法的返回值,然后直接到该hashCode值对应的位置去取出该元素。这就是HashSet速度很快的原因。
  12. HashSet中每个能存储元素的“槽位slot”通常称为桶bucket,如果有多个元素的hashCode值相同,但他们通过equals方法比较返回false,就需要在一个桶里存放多个元素,就会导致性能下降。
  13. 重写hashCode方法的基本原则。

    • 在程序运行过程中,同一个对象多次调用hashCode()方法应该返回相同的值。
    • 当两个对象通过equals()方法比较返回true时,hashCode()应该也返回相同值。
    • 对象中用作equals()方法比较标准的实例变量,都应该用于计算hashCode值。
    • 当程序把可变对象添加到HashSet中之后,尽量不要去修改该集合元素中参与计算hashCode()’equals()的实例变量,否则将会导致HashSet无法争取操作这些集合。

LinkedHashSet类

  1. LinkedHashSet集合也是根据元素的hashCode值来决定元素的存储位置,但它同时使用链表维护元素的次序,LinkedHashSet将会按元素的添加顺序来访问集合里的元素。
  2. 同样不能允许集合元素重复,
public class LinkedHashSetTest {
    public static void main(String[] args) {
        Collection<String> collection = new LinkedHashSet<>();
        collection.add("java");
        collection.add("python");
        //[java, python]
        System.out.println(collection);
        collection.remove("java");
        collection.add("java");
        //[python, java]
        System.out.println(collection);
    }
}

TreeSet类

  1. TreeSet是StortedSet接口的实现类。TreeSet可以保证集合元素处于排序状态。
public class TreeSetTest {
    public static void main(String[] args) {
        TreeSet<String> treeSet = new TreeSet<>();
        treeSet.add("5");
        treeSet.add("4");
        treeSet.add("3");
        treeSet.add("2");
        treeSet.add("1");
        System.out.println(treeSet);
        System.out.println(treeSet.first());//1
        System.out.println(treeSet.last());//5
        //返回集合中位于指定元素之前的元素
        System.out.println(treeSet.lower("4"));//3
        //返回集合中位于指定元素之后的元素
        System.out.println(treeSet.higher("4"));//5
        //返回此set的子集,由小于指定元素的元素组成
        SortedSet<String> headSet = treeSet.headSet("4");
        System.out.println(headSet);//[1, 2, 3]
        //返回set的子集。由大于或者等于指定元素的元素组成
        SortedSet<String> tailSet = treeSet.tailSet("4");
        System.out.println(tailSet);//[4, 5]
    }
}
  1. Tree并不是根据元素的插入顺序进行排序的,而是根据元素实际的大小来进行排序的。
  2. 与HashSet集合采用hash算法来决定元素的存储位置不同,TreeSet是采用红黑树的数据结构来存储集合元素,TreeSet支持两种排序方法,自然排序和定制排序。默认下TreeSet采用自然排序。
  3. 自然排序

    • TreeSet会调用集合元素的compareTo(Object o)方法来比较元素之间的大小关系,然后将集合元素按升序排序,这种方式就是自然排序。
    • 如果试图把一个对象添加到TreeSet时,则该对象的类必须实现comparable接口。否则报错
public class TreeSetErrorTest {
    public static void main(String[] args) {
        USER user = new USER("", 2);
        TreeSet<USER> treeSet = new TreeSet<>();
        //Exception in thread "main" java.lang.ClassCastException: setTest.USER cannot be cast to java.base/java.lang.Comparable
        treeSet.add(user);
    }
}
  • 当试图把一个对象添加到TreeSet集合时,TreeSet会调用该对象的comparaTo(Object o)方法与集合中的其他元素进行比较,这就要求集合中的其他元素与该元素时同一类的实例,也就是说,向TreeSet中添加的应该是同一个类的对象,否则也会引发ClassCastException异常。
public class TreeSetErrorTest2 {
    public static void main(String[] args) {
        TreeSet set = new TreeSet<>();
        set.add(new String());
        //Exception in thread "main" java.lang.ClassCastException: java.base/java.lang.String cannot be cast to java.base/java.util.Date
        set.add(new Date());
        /*
         * 在添加String时,是没有错误的,当添加Date对象时,TreeSet就会调用该对象的comparaTo方法与集合中的其他元素进行比较--
         * Date对象的comparaTo方法无法与字符串对象比较大小,所以引发异常
         * */
    }
}
  • TreeSet只能添加同一种类型的对象,
  • 当把一个对象加入TreeSet集合中时,TreeSet调用该对象的compareTo(Obejct o)方法与容器中的其他对象比较大小,然后根据红黑树结构找到他的存储位置,如果两个对象通过compareTo(Object o)方法比较相等,新对象将无法添加到TreeSet集合中。
  • TreeSet集合判断两个对象是够相等的唯一标准是:如果通过compareTo方法比较返回0,TreeSet则会认为他们相等,否则就认为他们不相等。
  • 当需要把一个对象放入TreeSet中,重写该对象对应类的equals方法时,应保证该方法与compareTo方法有一致的结果,其规则是,如果两个对象通过equals方法比较返回true时,这两个对象通过compareTo方法比较应返回0;
  • TreeSet可以删除没有被修改实例变量,且不与其他被修改实例变量的对象重复的对象。P309
  • 推荐不要修改放入HashSet和TreeSet集合中元素的关键实例变量。

定制排序

public class MSort {
    public static void main(String[] args) {
        TreeSet<M> treeSet = new TreeSet<>((o1,o2)->{
            M m1 = o1;
            M m2 = o2;
            return Integer.compare(m1.a, m2.a);
        });
        treeSet.add(new M(12));
        treeSet.add(new M(1232));
        treeSet.add(new M(121));
        //[12, 121, 1232]
        System.out.println(treeSet);
    }
}
  • 当通过Comparator对象或Lambda表达式来实现TreeSet的定制排序时,依然不可以向TreeSet中添加类型不同的对象,否则会引发ClassCastException异常,使用定制排序时,TreeSet对集合元素排序不管集合元素本身的大小,而是由Comparator对象或Lambda表达式。负责集合元素的排序规则,TreeSet判断两个集合元素相等的标准是:通过Comparator比较两个元素返回了0,这样TreeSet不会把第二个元素添加到集合中。

EnumSet类

  1. EnumSet是一个转为枚举类设计的集合类,EnumSet中的所有元素都必须是指定枚举类型的枚举值,该枚举值在创建EnumSet时显示或隐式地指定。EnumSet的集合元素也是有序的,EnumSet以枚举值在Enum类内的定义顺序来决定集合元素的顺序。
  2. EnumSet在内部以位向量的形式存储,这种存储形式非常紧凑高效,因此EnumSet对象占用内存很小,运行效率好,尤其是在进行批量操作的时候。
  3. EnumSet集合不允许加入NULL;
public class SeasonEnumSetTest {
    public static void main(String[] args) {
        //创建一个EnumSet集合,集合元素是Season的全部枚举
        EnumSet<Season> enumSet = EnumSet.allOf(Season.class);
        System.out.println(enumSet);//[SPTING, SUMMER, FALL, WINTER]
        //创建一个空集合,指定其集合元素是Season类的枚举类
        EnumSet<Season> noneOf = EnumSet.noneOf(Season.class);
        System.out.println(noneOf);//[]
        noneOf.add(Season.FALL);
        noneOf.add(Season.WINTER);
        System.out.println(noneOf);//[FALL, WINTER]
        //利用现有枚举进行创建EnumSet集合
        EnumSet<Season> of = EnumSet.of(Season.SPTING,Season.SUMMER);
        System.out.println(of);//[SPTING, SUMMER]
        //创建几个从begin到end之间的枚举作为新集合的元素
        EnumSet<Season> range = EnumSet.range(Season.SUMMER, Season.WINTER);
        System.out.println(range);//[SUMMER, FALL, WINTER]
        //range与complementof枚举值和是Season的全部枚举
        EnumSet<Season> complementOf = EnumSet.complementOf(range);
        System.out.println(complementOf);//[SPTING]
    }
}
  1. 当试图复制一个Collection集合里的元素来创建EnumSet集合时,必须保证Collection集合里的所有元素都是同一个枚举类的枚举值。

Set实现类的性能分析

  1. HashSet的性能总是比TreeSet好,特别是最常用的添加,查询元素等操作,因为TreeSet需要额外的红黑树算法来维护集合元素的次序,只有当需要一个保持排序的Set时,才应该使用TreeSet,否则都应该使用HashSet。
  2. LinkedHashSet对于普通的插入,删除操作,LinkedHashSet比HashSet要略慢一点,这是由维护链表所带来的额外开销造成的,但由于有了链表,遍历LinkedHashSet会更快。
  3. EnumSet是所有Set实现类中性能最好的,但它只能保存同一个枚举类的枚举值作为集合元素。
  4. Set的三个实现类HashSet,TreeSet和EnumSet都是线程不安全的。
  5. 如果有多个线程同时访问Set集合那么需要手动保证该Set集合的同步性。
  6. Collections.synchronizedSortedSet(new TreeSet(...));

期待
3 声望1 粉丝

在校的一枚技术小新,欢迎大佬指正缺点


« 上一篇
java集合-Map