- Set集合通常不能记住元素的添加顺序。Set不允许包含重复的元素。
- Set集合不允许包含相同的元素,如果试图把两个相同的元素加入同一个Set集合中,则添加操作失败,add()方法返回false,且新元素不会被加入。
HashSet类
- HashSet按照Hash算法来存储集合中的元素,因此具有很好的存取和查询性能。
-
HashSet特点
- 不能保证元素的排列顺序,顺序可能与添加顺序不同,顺序也有可能发生变化。
- HashSet不是同步的,如果多个线程同时访问一个HashSet,假设有两个或者两个以上线程同时修改了HashSet集合时,则必须通过代码来保证其同步。
- 集合元素值可以是null;
- 当HashSet集合中存入一个元素时,HashSet会调用该对象的HashCode()方法来得到该对象的hashCode值,然后根据该hashCode值决定该对象在HashSet中的存储位置,如果有两个元素通过equals()方法比较返回true,但他们的hashCode()方法返回值不相等,HashSet将会把他们存在不同位置,依然可以添加成功。
- HashSet集合判断两个元素的标准是两个对象通过equals()方法比较相等,并且两个对象的hashCode()方法返回值也相等。
- 即使两个A对象通过equals()方法比较返回true,但HashSet依然把他们当成两个对象,即使两个B对象的hashCode()方法返回相同值,但HashSet依然把他们当成两个对象。
- 当把一个对象放入HashSet中时,如果需要重写该对象对应类的equals()方法,则也应该重写其hashCode()方法,规则是:如果两个对象通过equals()方法比较返回true,这两个对象的hashCode值也应该相同。
- 如果两个对象通过equals()比较返回true,但这两个对象的hashCode()方法返回不同的hashCode值时,这将导致HashSet会把这两个对象保存在Hash表的不同位置,从而使两个对象都可以添加成功,这就与Set集合规则冲突了。
- 如果两个对象的hashCode()值返回的值相同,但他们通过equals()方法比较返回false时更麻烦。因为两个对象的hashCode值相同,HashSet将试图把他们保存在同一个位置,但又不行(否则将只剩下一个对象),所以实际上回在这个位置用链式结构来保存多个对象;而HashSet访问集合元素时也是根据元素的hashCode值来快速定位的,如果HashSet中两个以上的元素具有相同的hashCode值,将会导致性能下降。
- Hash算法可以直接根据该元素的hashCode值计算出该元素的存储位置,从而快速定位该元素。
- 数组是所有能存储一组元素里最快的数据结构。
- 当从HashSet中访问元素时,HashSet先计算该元素的hashCode值,也就是调用该元素的hashCode()方法的返回值,然后直接到该hashCode值对应的位置去取出该元素。这就是HashSet速度很快的原因。
- HashSet中每个能存储元素的“槽位slot”通常称为桶bucket,如果有多个元素的hashCode值相同,但他们通过equals方法比较返回false,就需要在一个桶里存放多个元素,就会导致性能下降。
-
重写hashCode方法的基本原则。
- 在程序运行过程中,同一个对象多次调用hashCode()方法应该返回相同的值。
- 当两个对象通过equals()方法比较返回true时,hashCode()应该也返回相同值。
- 对象中用作equals()方法比较标准的实例变量,都应该用于计算hashCode值。
- 当程序把可变对象添加到HashSet中之后,尽量不要去修改该集合元素中参与计算hashCode()’equals()的实例变量,否则将会导致HashSet无法争取操作这些集合。
LinkedHashSet类
- LinkedHashSet集合也是根据元素的hashCode值来决定元素的存储位置,但它同时使用链表维护元素的次序,LinkedHashSet将会按元素的添加顺序来访问集合里的元素。
- 同样不能允许集合元素重复,
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类
- 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]
}
}
- Tree并不是根据元素的插入顺序进行排序的,而是根据元素实际的大小来进行排序的。
- 与HashSet集合采用hash算法来决定元素的存储位置不同,TreeSet是采用红黑树的数据结构来存储集合元素,TreeSet支持两种排序方法,自然排序和定制排序。默认下TreeSet采用自然排序。
-
自然排序
- 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类
- EnumSet是一个转为枚举类设计的集合类,EnumSet中的所有元素都必须是指定枚举类型的枚举值,该枚举值在创建EnumSet时显示或隐式地指定。EnumSet的集合元素也是有序的,EnumSet以枚举值在Enum类内的定义顺序来决定集合元素的顺序。
- EnumSet在内部以位向量的形式存储,这种存储形式非常紧凑高效,因此EnumSet对象占用内存很小,运行效率好,尤其是在进行批量操作的时候。
- 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]
}
}
- 当试图复制一个Collection集合里的元素来创建EnumSet集合时,必须保证Collection集合里的所有元素都是同一个枚举类的枚举值。
Set实现类的性能分析
- HashSet的性能总是比TreeSet好,特别是最常用的添加,查询元素等操作,因为TreeSet需要额外的红黑树算法来维护集合元素的次序,只有当需要一个保持排序的Set时,才应该使用TreeSet,否则都应该使用HashSet。
- LinkedHashSet对于普通的插入,删除操作,LinkedHashSet比HashSet要略慢一点,这是由维护链表所带来的额外开销造成的,但由于有了链表,遍历LinkedHashSet会更快。
- EnumSet是所有Set实现类中性能最好的,但它只能保存同一个枚举类的枚举值作为集合元素。
- Set的三个实现类HashSet,TreeSet和EnumSet都是线程不安全的。
- 如果有多个线程同时访问Set集合那么需要手动保证该Set集合的同步性。
- Collections.synchronizedSortedSet(new TreeSet(...));
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。