大多时候使用Set集合时就是使用HashSet实现类。HashSet按Hash算法来存储集合中的元素,所以具备很好的存取和查找性能java
HashSet具备如下特色算法
不能保证元素的排列顺序,顺序可能与添加顺序不一样,顺序也有可能发生变化数组
HashSet不是同步的,若是多个线程同时访问一个HashSet,假设有两个或两个以上线程同时修改了HashSet集合时,则必须经过代码来保证其同步安全
集合元素值能够是null数据结构
HashSet会调用该对象的hashCode()方法来获得该对象的hashCode()值,而后根据该hashCode()值决定该对象在HashSet中的存储位置;HashSet集合判断两个元素相等的标准是两个对象经过equals()方法比较相等,而且两个对象的hashCode()方法返回值也相等工具
hash(也被翻译为哈希、散列)算法的功能:
它能保证快速查找被检索的对象,hash算法的价值在于速度。当须要查询集合中某个元素时,hash算法能够根据该元素的hashCode值计算出该元素的存储位置,从而快速定位该元素。性能
为何不直接使用数组、还须要使用HashSet?
由于数组元素的索引是连续的,并且数组的长度是固定的、没法自由增长数组的长度。而HashSet采用每一个元素的hashCode值来计算其存储位置,从而能够自由增长HashSet的长度,并能够根据元素的HashCode值来访问元素。所以,当从HashSet中访问元素时,HashSet先计算该元素的hashCode值(调用该对象的hashCode()方法的返回值),而后直接到该hashCode值对应的位置去取出该元素——这就是HashSet速度很快的缘由this
HashSet中每一个能存储元素的“槽位”(slot)一般称为“桶”(bucket)es5
hashCode()方法的基本重写规则spa
在程序运行过程当中,同一个对象屡次调用hashCode()方法应该返回相同的值
当两个对象经过equals()方法比较返回true时,这两个对象的hashCode()方法应该返回相等的值
对象中用做equals()方法比较标准的实例变量,都应该用于计算hashCode()值
hashCode()方法的基本重写步骤
把对象内每一个有意义的实例变量(即每一个参与equals()方法比较标准的实例变量)计算出一个Int类型的hashCode值
用第一步计算出来的多个hashCode值组合计算出一个hashCode值返回
LinkedHashSet集合根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序,这样使得元素看起来是以插入的顺序保存的。当遍历LinkedHashSet集合里的元素时,LinkedHashSet将会按元素的添加顺序来访问集合里的元素
LinkedHashSet须要维护元素的插入顺序,所以性能略低于HashSet的性能,但在迭代访问Set里的所有元素时将有很好的性能,由于它以链表来维护内部顺序
虽然LinkedHashSet使用了链表记录集合元素的添加顺序,但LinkedHashSet依然是HashSet,所以不容许集合元素重复
TreeSet是SortedSet接口的实现类,能够确保集合元素处于排序状态。根据元素实际值的大小进行排序
TreeSet的额外方法
Comparator comparator():若是TreeSet采用了定制排序,则该方法返回定制排序所使用的Comparator;若是TreeSet采用了天然排序,则返回null
Object first():返回集合中的第一个元素
Object last():返回集合中的最后一个元素
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的元素组成
HashSet采用hash算法来决定元素的存储位置,TreeSet采用红黑树的数据结构来存储集合元素。
TreeSet支持两种排序方法。在默认状况下,TreeSet采用天然排序
import java.util.*; public class TreeSetTest { public static void main(String[] args) { TreeSet nums = new TreeSet(); // 向TreeSet中添加四个Integer对象 nums.add(5); nums.add(2); nums.add(10); nums.add(-9); // 输出集合元素,看到集合元素已经处于排序状态 System.out.println(nums); // 输出集合里的第一个元素 System.out.println(nums.first()); // 输出-9 // 输出集合里的最后一个元素 System.out.println(nums.last()); // 输出10 // 返回小于4的子集,不包含4 System.out.println(nums.headSet(4)); // 输出[-9, 2] // 返回大于5的子集,若是Set中包含5,子集中还包含5 System.out.println(nums.tailSet(5)); // 输出 [5, 10] // 返回大于等于-3,小于4的子集。 System.out.println(nums.subSet(-3 , 4)); // 输出[2] } }
TreeSet会调用集合元素的compareTo(Object obj)方法来比较元素之间的大小关系,而后将集合元素按升序排列,这种方式就是天然排列
compareTo(Object obj)方法返回一个整数值,实现该接口的类必须实现该方法,实现了该接口的类的对象就能够比较大小。当一个对象调用该方法与另外一个对象进行比较时,例如obj1.compareTo(obj2),若是该方法返回0,则代表这两个对象相等;若是该方法返回一个正整数,则代表obj1大于obj2;若是该方法返回一个负整数,则代表obj1小于obj2
实现了Comparable接口的经常使用类
BigDecimal、BigInteger以及全部的数组型对应的包装类:按它们对应的数组大小进行比较
Character:按字符的UNICODE值进行比较
Boolean:true对应的包装类实例大于false对应的包装类实例
String:按字符串中字符的UNICODE值进行比较
Date、Time:后面的时间、日期比前面的时间、日期大
一个对象添加到TreeSet时,则该对象的类必须实现Comparable接口,不然程序将会抛出异常
import java.util.TreeSet; class Error{ } public class TreeSetErrorTest { public static void main(String[] args) { TreeSet treeSet = new TreeSet<>(); treeSet.add(new Error()); treeSet.add(new Error()); //① } }
添加第一个对象时,TreeSet里没有任何元素,因此不会出现任何问题;当添加第二个Error对象时,TreeSet就会调用该对象的compareTo(Object obj)方法与集合中的其余元素进行比较——若是其对应的类没有实现Comparable接口,则会引起ClassCastException异常
向TreeSet集合中添加元素时,只有第一个元素无须实现Comparable接口,后面添加的全部元素都必须实现Comparable接口
把一个对象添加到TreeSet集合时,TreeSet会调用该对象的compareTo(Object obj)方法与集合中的其余元素进行比较。向TreeSet中添加的应该是同一个类的对象,不然也会引起ClassCastException异常
若是但愿TreeSet能正常运行,TreeSet只能添加同一种类型的对象
当把一个对象加入TreeSet集合中时,TreeSet调用该对象的compareTo(Object obj)方法与容器中的其余对象比较大小,而后根据红黑树结构找到它的存储位置。若是两个对象经过compareTo(Object obj)方法比较相等,新对象将没法添加到TreeSet集合中
TreeSet的天然排序是根据集合元素的大小,TreeSet将它们以升序排列。若是须要实现定制排序,例如以降序排列,则能够经过Comparator接口的帮助。
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 TreeSet ts = new TreeSet((o1 , o2) -> { M m1 = (M)o1; M 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)); System.out.println(ts); } }
EnumSet是一个专为枚举类设计的集合类,EnumSet中的全部元素都必须是指定枚举类型的枚举值,该枚举类型在建立EnumSet时显式或隐式地指定。EnumSet的集合元素也是有序的,EnumSet以枚举值在Enum类内的定义顺序来决定集合元素的顺序
EnumSet在内部以位向量的形式存储,EnumSet对象占用内存很小,运行效率很好。尤为是进行批量操做(如调用containsAll()和retainAll()方法)时,若是其参数也是EnumSet集合,则该批量操做的执行速度也很是快
EnumSet集合不容许加入null元素,不然抛出NullPointException异常
EnumSet没有暴露任何构造器来建立该类的实例,应经过其提供的类方法来建立EnumSet对象
EnumSet allOf(Class elementType): 建立一个包含指定枚举类里全部枚举值的EnumSet集合
EnumSet complementOf(EnumSet e): 建立一个其元素类型与指定EnumSet里元素类型相同的EnumSet集合,新EnumSet集合包含原EnumSet集合所不包含的、此类枚举类剩下的枚举值(即新EnumSet集合和原EnumSet集合的集合元素加起来是该枚举类的全部枚举值)
EnumSet copyOf(Collection c): 使用一个普通集合来建立EnumSet集合
EnumSet copyOf(EnumSet e): 建立一个指定EnumSet具备相同元素类型、相同集合元素的EnumSet集合
EnumSet noneOf(Class elementType): 建立一个元素类型为指定枚举类型的空EnumSet
EnumSet of(E first,E…rest): 建立一个包含一个或多个枚举值的EnumSet集合,传入的多个枚举值必须属于同一个枚举类
EnumSet range(E from,E to): 建立一个包含从from枚举值到to枚举值范围内全部枚举值的EnumSet集合
enum Season { SPRING,SUMMER,FALL,WINTER } public class EnumSetTest { public static void main(String[] args) { // 建立一个EnumSet集合,集合元素就是Season枚举类的所有枚举值 EnumSet es1 = EnumSet.allOf(Season.class); System.out.println(es1); // 输出[SPRING,SUMMER,FALL,WINTER] // 建立一个EnumSet空集合,指定其集合元素是Season类的枚举值。 EnumSet es2 = EnumSet.noneOf(Season.class); System.out.println(es2); // 输出[] // 手动添加两个元素 es2.add(Season.WINTER); es2.add(Season.SPRING); System.out.println(es2); // 输出[SPRING,WINTER] // 以指定枚举值建立EnumSet集合 EnumSet es3 = EnumSet.of(Season.SUMMER , Season.WINTER); System.out.println(es3); // 输出[SUMMER,WINTER] EnumSet es4 = EnumSet.range(Season.SUMMER , Season.WINTER); System.out.println(es4); // 输出[SUMMER,FALL,WINTER] // 新建立的EnumSet集合的元素和es4集合的元素有相同类型, // es5的集合元素 + es4集合元素 = Season枚举类的所有枚举值 EnumSet es5 = EnumSet.complementOf(es4); System.out.println(es5); // 输出[SPRING] } }
EnumSet能够复制另外一个EnumSet集合中的全部元素来建立新的EnumSet集合,或者复制另外一个Collection集合中的全部元素来建立新的EnumSet集合。当复制Collection集合中的全部元素来建立新的EnumSet集合时,要求Collection集合中的全部元素必须是同一个枚举类的枚举值
HashSet的性能总比TreeSet好,特别是最经常使用的添加、查询元素等操做。由于TreeSet须要额外的红黑树算法来维护集合元素的次序。只有当须要保持排序的Set时,才应该使用TreeSet,不然都应该使用HashSet
LinkedHashSet是HashSet的一个子类,对于普通的插入、删除操做,LinkedHashSet比HashSet要略微满意的,这是由维护链表所带来的额外开销所形成的,但因为有了链表,遍历LinkedHashSet会更快
EnumSet是全部Set实现类中性能最好的,但它只能保存同一个枚举的枚举值做为集合元素
HashSet、TreeSet、EnumSet都是线程不安全的,若是有多个线程同时访问一个Set集合,而且有超过一个线程修改了该Set集合,则必须手动保证该Set集合的同步性。一般能够经过Collections工具类的synchronizedSortedSet方法来“包装”该Set集合。在建立时进行,以防对Set集合的意外非同步访问
SortedSet s = Collections.synchronizedSortedSet(new TreeSet(...));