集合Set
确定性:对任意对象都能判定其是否属于某一个集合。
互异性:集合内每个元素都是互不相同的,注意是内容互异。
无序性:集合内的顺序无关。
Java中的集合接口Set
HashSet:基于散列函数的集合,无序,不支持同步。
TreeSet:基于树结构的集合,可排序的,不支持同步。
LinkedHashSet:基于散列函数和双向链表的集合,可排序的,不支持同步。
1. HashSet HashSet
下面通过示例代码和其输出结果,来演示HashSet的使用。
下面的代码中还测试了HashSet使用迭代器和使用for-each遍历的性能,for-each性能更好。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 import java.util.ArrayList;import java.util.HashSet;import java.util.Iterator;public class HashSetTest { public static void main (String[] args) { HashSet<Integer> hs = new HashSet <Integer>(); hs.add(null ); hs.add(1000 ); hs.add(20 ); hs.add(3 ); hs.add(4 ); hs.add(5000000 ); hs.add(3 ); hs.add(null ); System.out.println(hs.size()); if (!hs.contains(6 )) { hs.add(6 ); } System.out.println(hs.size()); hs.remove(4 ); System.out.println(hs.size()); System.out.println("============for循环遍历==============" ); for (Integer item : hs) { System.out.println(item); } System.out.println("============测试集合交集==============" ); HashSet<String> set1 = new HashSet <String>(); HashSet<String> set2 = new HashSet <String>(); set1.add("a" ); set1.add("b" ); set1.add("c" ); set2.add("c" ); set2.add("d" ); set2.add("e" ); set1.retainAll(set2); System.out.println("交集是 " + set1); System.out.println("============测试多种遍历方法速度==============" ); HashSet<Integer> hs2 = new HashSet <Integer>(); for (int i = 0 ; i < 100000 ; i++) { hs2.add(i); } traverseByIterator(hs2); traverseByFor(hs2); } public static void traverseByIterator (HashSet<Integer> hs) { long startTime = System.nanoTime(); System.out.println("============迭代器遍历==============" ); Iterator<Integer> iter1 = hs.iterator(); while (iter1.hasNext()) { iter1.next(); } long endTime = System.nanoTime(); long duration = endTime - startTime; System.out.println(duration + "纳秒" ); } public static void traverseByFor (HashSet<Integer> hs) { long startTime = System.nanoTime(); System.out.println("============for循环遍历==============" ); for (Integer item : hs) { ; } long endTime = System.nanoTime(); long duration = endTime - startTime; System.out.println(duration + "纳秒" ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 6 7 7 ============for循环遍历============== null 40000 3 20 6 1000 5000000 ============测试集合交集============== 交集是 [c] ============测试多种遍历方法速度============== ============迭代器遍历============== 7311791纳秒 ============for循环遍历============== 6130979纳秒
2. LinkedHashSet LinkedHashSet
继承HashSet,也是基于HashMap实现的,可以容纳null元素。
不支持同步
同样可以通过Set s = Collections.synchronizedSet(new LinkedHashSet(...))
获取一个支持同步的LinkedHashSet。
方法和HashSet基本一致。
add(),clear(),contains(),remove(),size()等。
通过一个双向链表 维护插入顺序。
下面通过示例代码和其输出结果,来演示LinkedHashSet的使用。
LinkedHashSet用法与HashSet几乎完全一致,仅仅只是保留了插入的顺序。
下面的代码中还测试了LinkedHashSet使用迭代器和使用for-each遍历的性能,for-each性能更好。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 import java.util.ArrayList;import java.util.HashSet;import java.util.Iterator;import java.util.LinkedHashSet;public class LinkedHashSetTest { public static void main (String[] args) { LinkedHashSet<Integer> lhs = new LinkedHashSet <Integer>(); lhs.add(null ); lhs.add(1000 ); lhs.add(20 ); lhs.add(3 ); lhs.add(4 ); lhs.add(5000000 ); lhs.add(3 ); lhs.add(null ); System.out.println(lhs.size()); if (!lhs.contains(6 )) { lhs.add(6 ); } System.out.println(lhs.size()); lhs.remove(4 ); System.out.println(lhs.size()); System.out.println("============for循环遍历==============" ); for (Integer item : lhs) { System.out.println(item); } LinkedHashSet<Integer> lhs2 = new LinkedHashSet <Integer>(); for (int i = 0 ; i < 100000 ; i++) { lhs2.add(i); } traverseByIterator(lhs2); traverseByFor(lhs2); } public static void traverseByIterator (LinkedHashSet<Integer> hs) { long startTime = System.nanoTime(); System.out.println("============迭代器遍历==============" ); Iterator<Integer> iter1 = hs.iterator(); while (iter1.hasNext()) { iter1.next(); } long endTime = System.nanoTime(); long duration = endTime - startTime; System.out.println(duration + "纳秒" ); } public static void traverseByFor (LinkedHashSet<Integer> hs) { long startTime = System.nanoTime(); System.out.println("============for循环遍历==============" ); for (Integer item : hs) { ; } long endTime = System.nanoTime(); long duration = endTime - startTime; System.out.println(duration + "纳秒" ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 6 7 6 ============for循环遍历============== null 1000 20 3 5000000 6 ============迭代器遍历============== 7966941纳秒 ============for循环遍历============== 4119412纳秒
3. TreeSet TreeSet
基于TreeMap实现的,不可以容纳null元素 ,不支持同步。
可以通过SortedSet s = Collectons.synchronizedSortedSet(new TreeSet(...))
得到一个支持同步的TreeSet。
add() 添加一个元素。
clear() 清空整个TreeSet。
contains() 判定是否包含一个元素。
remove() 删除一个元素。
size() 返回集合中元素的个数。
根据compareTo方法或指定Comparator排序。
下面通过示例代码和其输出结果,来演示TreeSet的使用。
下面的代码中还测试了TreeSet使用迭代器和使用for-each遍历的性能,for-each性能更好。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 import java.util.ArrayList;import java.util.HashSet;import java.util.Iterator;import java.util.LinkedHashSet;import java.util.TreeSet;public class TreeSetTest { public static void main (String[] args) { TreeSet<Integer> ts = new TreeSet <Integer>(); ts.add(1000 ); ts.add(20 ); ts.add(3 ); ts.add(4 ); ts.add(5000000 ); ts.add(3 ); System.out.println(ts.size()); if (!ts.contains(6 )) { ts.add(6 ); } System.out.println(ts.size()); ts.remove(4 ); System.out.println(ts.size()); System.out.println("============for循环遍历==============" ); for (Integer item : ts) { System.out.println(item); } TreeSet<Integer> ts2 = new TreeSet <Integer>(); for (int i = 0 ; i < 100000 ; i++) { ts2.add(i); } traverseByIterator(ts2); traverseByFor(ts2); } public static void traverseByIterator (TreeSet<Integer> hs) { long startTime = System.nanoTime(); System.out.println("============迭代器遍历==============" ); Iterator<Integer> iter1 = hs.iterator(); while (iter1.hasNext()) { iter1.next(); } long endTime = System.nanoTime(); long duration = endTime - startTime; System.out.println(duration + "纳秒" ); } public static void traverseByFor (TreeSet<Integer> hs) { long startTime = System.nanoTime(); System.out.println("============for循环遍历==============" ); for (Integer item : hs) { ; } long endTime = System.nanoTime(); long duration = endTime - startTime; System.out.println(duration + "纳秒" ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 5 6 5 ============for循环遍历============== 3 6 20 1000 40000 5000000 ============迭代器遍历============== 8215442纳秒 ============for循环遍历============== 6403116纳秒
4. HashSet/LinkedHashSet/TreeSet的异同 可以存放的内容
判定元素重复的原则
下面通过代码示例,来理解三种集合的重复判定。
我们先定义3个简单的类,Cat、Dog和Tiger,用于做HashSet和LinkedHashSet,还有TreeSet的判重实验。
1 2 3 4 5 6 7 8 9 class Cat { private int size; public Cat (int size) { this .size = size; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Dog { private int size; public Dog (int s) { size = s; } public int getSize () { return size; } public boolean equals (Object obj2) { System.out.println("Dog equals()~~~~~~~~~~~" ); if (0 == size - ((Dog) obj2).getSize()) { return true ; } else { return false ; } } public int hashCode () { System.out.println("Dog hashCode()~~~~~~~~~~~" ); return size; } public String toString () { System.out.print("Dog toString()~~~~~~~~~~~" ); return size + "" ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Tiger implements Comparable { private int size; public Tiger (int s) { size = s; } public int getSize () { return size; } public int compareTo (Object o) { System.out.println("Tiger compareTo()~~~~~~~~~~~" ); return size - ((Tiger) o).getSize(); } }
下面通过在集合中存放Cat类或Dog类或Tiger类的对象,来观察判重的结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 import java.util.HashSet;import java.util.Iterator;import java.util.LinkedHashSet;import java.util.TreeSet;public class ObjectHashSetTest { public static void main (String[] args) { System.out.println("==========Cat HashSet ==============" ); HashSet<Cat> hs = new HashSet <Cat>(); hs.add(new Cat (2 )); hs.add(new Cat (1 )); hs.add(new Cat (3 )); hs.add(new Cat (5 )); hs.add(new Cat (4 )); hs.add(new Cat (4 )); System.out.println(hs.size()); System.out.println("========================" ); LinkedHashSet<Cat> lhs = new LinkedHashSet <Cat>(); lhs.add(new Cat (2 )); lhs.add(new Cat (1 )); lhs.add(new Cat (3 )); lhs.add(new Cat (5 )); lhs.add(new Cat (4 )); lhs.add(new Cat (4 )); System.out.println(lhs.size()); System.out.println("==========Dog HashSet ==============" ); HashSet<Dog> hs2 = new HashSet <Dog>(); hs2.add(new Dog (2 )); hs2.add(new Dog (1 )); hs2.add(new Dog (3 )); hs2.add(new Dog (5 )); hs2.add(new Dog (4 )); hs2.add(new Dog (4 )); System.out.println(hs2.size()); System.out.println("========================" ); LinkedHashSet<Dog> lhs2 = new LinkedHashSet <Dog>(); lhs2.add(new Dog (2 )); lhs2.add(new Dog (1 )); lhs2.add(new Dog (3 )); lhs2.add(new Dog (5 )); lhs2.add(new Dog (4 )); lhs2.add(new Dog (4 )); System.out.println(lhs2.size()); System.out.println("==========Tiger HashSet ==============" ); HashSet<Tiger> hs3 = new HashSet <Tiger>(); hs3.add(new Tiger (2 )); hs3.add(new Tiger (1 )); hs3.add(new Tiger (3 )); hs3.add(new Tiger (5 )); hs3.add(new Tiger (4 )); hs3.add(new Tiger (4 )); System.out.println(hs3.size()); System.out.println("========================" ); LinkedHashSet<Tiger> lhs3 = new LinkedHashSet <Tiger>(); lhs3.add(new Tiger (2 )); lhs3.add(new Tiger (1 )); lhs3.add(new Tiger (3 )); lhs3.add(new Tiger (5 )); lhs3.add(new Tiger (4 )); lhs3.add(new Tiger (4 )); System.out.println(lhs3.size()); System.out.println("==========Tiger TreeSet ==============" ); TreeSet<Tiger> ts3 = new TreeSet <Tiger>(); ts3.add(new Tiger (2 )); ts3.add(new Tiger (1 )); ts3.add(new Tiger (3 )); ts3.add(new Tiger (5 )); ts3.add(new Tiger (4 )); ts3.add(new Tiger (4 )); System.out.println(ts3.size()); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 ==========Cat HashSet ============== 6 ======================== 6 ==========Dog HashSet ============== Dog hashCode()~~~~~~~~~~~ Dog hashCode()~~~~~~~~~~~ Dog hashCode()~~~~~~~~~~~ Dog hashCode()~~~~~~~~~~~ Dog hashCode()~~~~~~~~~~~ Dog hashCode()~~~~~~~~~~~ Dog equals()~~~~~~~~~~~ 5 ======================== Dog hashCode()~~~~~~~~~~~ Dog hashCode()~~~~~~~~~~~ Dog hashCode()~~~~~~~~~~~ Dog hashCode()~~~~~~~~~~~ Dog hashCode()~~~~~~~~~~~ Dog hashCode()~~~~~~~~~~~ Dog equals()~~~~~~~~~~~ 5 ==========Tiger HashSet ============== 6 ======================== 6 ==========Tiger TreeSet ============== Tiger compareTo()~~~~~~~~~~~ Tiger compareTo()~~~~~~~~~~~ Tiger compareTo()~~~~~~~~~~~ Tiger compareTo()~~~~~~~~~~~ Tiger compareTo()~~~~~~~~~~~ Tiger compareTo()~~~~~~~~~~~ Tiger compareTo()~~~~~~~~~~~ Tiger compareTo()~~~~~~~~~~~ Tiger compareTo()~~~~~~~~~~~ Tiger compareTo()~~~~~~~~~~~ 5
观察上面代码及输出结果可以发现。
在HashSet/LinkedHashSet中,Cat类虽然size相同,但却被认为没有重复,因为两个Cat对象的hashCode是不同的。
在HashSet/LinkedHashSet中,因为Dog类重写了hashCode()函数,两个size相同的Dog对象的hashCode相等,所以再判断equals(),因为equals也重写了,判定相等。所以最终这两个相同size的Dog对象被判重。通常来说我们重写了hashCode()和equals(),也应当重写toString()。hashCode()、equals()和toString()应该都是一样的。
Tiger实现Comparable接口,所以必须实现compareTo()方法来比较大小。
CompareTo()方法具体规则如下:
int a = obj1.compareTo(obj2);
如果 a > 0,则 obj1 > obj2;
如果 a == 0,则 obj1 == obj2;
如果 a < 0,则 obj1 < obj2。
HashSet/LinkedHashSet只关注hashCode和equals(),不关注compareTo();
TreeSet只关注compareTo(),不关注hashCode和equals()。