java基础系列:集合总结(5)
決定使用哪種集合
ArrayList, LinkedList 以及 Vector(大致等價于 ArrayList)都實現了List 接口,所以無論選用哪一個,我們的程序都會得到類似的結果。然而, ArrayList(以及 Vector)是由一個數組后推得到的;而 LinkedList 是根據常規的雙重鏈接列表方式實現的,因為每個單獨的對象都包含了數據以及指向列表內前后元素的句柄。正是由于這個原因,假如想在一個列表中部進行大量插入和刪除操作,那么 LinkedList 無疑是最恰當的選擇( LinkedList 還有一些額外的功能,建立于AbstractSequentialList 中)。若非如此,就情愿選擇 ArrayList,它的速度可能要快一些。
作為另一個例子, Set 既可作為一個 ArraySet 實現,亦可作為 HashSet 實現。 ArraySet 是由一個 ArrayList
后推得到的,設計成只支持少量元素,特別適合要求創建和刪除大量 Set 對象的場合使用。然而,一旦需要在自己的 Set 中容納大量元素, ArraySet 的性能就會大打折扣。寫一個需要 Set 的程序時,應默認選擇HashSet。而且只有在某些特殊情況下(對性能的提升有迫切的需求),才應切換到 ArraySet。
1. 決定使用何種 List
為體會各種 List 實施方案間的差異,最簡便的方法就是進行一次性能測驗。
public class ListPerformance {private static final int REPS = 100;private abstract static class Tester {String name;int size; // Test quantityTester(String name, int size) {this.name = name;this.size = size;}abstract void test(List a);}private static Tester[] tests = { new Tester("get", 300) {void test(List a) {for (int i = 0; i < REPS; i++) {for (int j = 0; j < a.size(); j++)a.get(j);}}}, new Tester("iteration", 300) {void test(List a) {for (int i = 0; i < REPS; i++) {Iterator it = a.iterator();while (it.hasNext())it.next();}}}, new Tester("insert", 1000) {void test(List a) {int half = a.size() / 2;String s = "test";ListIterator it = a.listIterator(half);for (int i = 0; i < size * 10; i++)it.add(s);}}, new Tester("remove", 5000) {void test(List a) {ListIterator it = a.listIterator(3);while (it.hasNext()) {it.next();it.remove();}}}, };public static void test(List a) {// A trick to print out the class name:System.out.println("Testing " + a.getClass().getName());for (int i = 0; i < tests.length; i++) {Collection1.fill(a, tests[i].size);System.out.print(tests[i].name);long t1 = System.currentTimeMillis();tests[i].test(a);long t2 = System.currentTimeMillis();System.out.println(": " + (t2 - t1));}}public static void main(String[] args) {test(new ArrayList());test(new LinkedList());} }內部類 Tester 是一個抽象類,用于為特定的測試提供一個基礎類。它包含了一個要在測試開始時打印的字串、一個用于計算測試次數或元素數量的 size 參數、用于初始化字段的一個構建器以及一個抽象方法test()。 test()做的是最實際的測試工作。各種類型的測試都集中到一個地方: tests 數組。我們用繼承于Tester 的不同匿名內部類來初始化該數組。為添加或刪除一個測試項目,只需在數組里簡單地添加或移去一個內部類定義即可,其他所有工作都是自動進行的。
Type Get Iteration Insert Remove A r r a y L i s t 110 490 3790 8730 LinkedList 1980 220 110 110在 ArrayList 中進行隨機訪問(即 get())以及循環反復是最劃得來的;但對于 LinkedList 卻是一個不小的開銷。但另一方面,在列表中部進行插入和刪除操作對于 LinkedList 來說卻比 ArrayList 劃算得多。我們最好的做法也許是先選擇一個 ArrayList 作為自己的默認起點。以后若發現由于大量的插入和刪除造成了性能的降低,再考慮換成 LinkedList 不遲。
2. 決定使用何種 Set
可在 ArraySet 以及 HashSet 間作出選擇,具體取決于 Set 的大小(如果需要從一個 Set 中獲得一個順序列表,請用 TreeSet;)
public class SetPerformance {private static final int REPS = 200;private abstract static class Tester {String name;Tester(String name) {this.name = name;}abstract void test(Set s, int size);}private static Tester[] tests = { new Tester("add") {void test(Set s, int size) {for (int i = 0; i < REPS; i++) {s.clear();Collection1.fill(s, size);}}}, new Tester("contains") {void test(Set s, int size) {for (int i = 0; i < REPS; i++)for (int j = 0; j < size; j++)s.contains(Integer.toString(j));}}, new Tester("iteration") {void test(Set s, int size) {for (int i = 0; i < REPS * 10; i++) {Iterator it = s.iterator();while (it.hasNext())it.next();}}}, };public static void test(Set s, int size) {// A trick to print out the class name:System.out.println("Testing " + s.getClass().getName() + " size "+ size);Collection1.fill(s, size);for (int i = 0; i < tests.length; i++) {System.out.print(tests[i].name);long t1 = System.currentTimeMillis();tests[i].test(s, size);long t2 = System.currentTimeMillis();System.out.println(": " + ((double) (t2 - t1) / (double) size));}}public static void main(String[] args) {// Small:test(new TreeSet(), 10);test(new HashSet(), 10);// Medium:test(new TreeSet(), 100);test(new HashSet(), 100);// Large:test(new HashSet(), 1000);test(new TreeSet(), 1000);} }進行 add()以及 contains()操作時, HashSet 顯然要比 ArraySet 出色得多,而且性能明顯與元素的多寡關系不大。一般編寫程序的時候,幾乎永遠用不著使用 ArraySet
3.決定使用何種 Map
選擇不同的 Map 實施方案時,注意 Map 的大小對于性能的影響是最大的,下面這個測試程序清楚地闡示了這
一點:
由于 Map 的大小是最嚴重的問題,所以程序的計時測試按Map 的大小(或容量)來分割時間,以便得到令人
信服的測試結果。下面列出一系列結果(在你的機器上可能不同):
即使大小為 10, ArrayMap 的性能也要比 HashMap 差—— 除反復循環時以外。而在使用 Map 時,反復的作用通常并不重要( get()通常是我們時間花得最多的地方)。 TreeMap 提供了出色的 put()以及反復時間,但 get()的性能并不佳。但是,我們為什么仍然需要使用TreeMap 呢?這樣一來,我們可以不把它作為 Map 使用,而作為創建順序列表的一種途徑。一旦填充了一個 TreeMap,就可以調用 keySet()來獲得鍵的一個 Set“景象”。然后用 toArray()產生包含了那些鍵的一個數組。隨后,可用 static 方法 Array.binarySearch()快速查找排好序的數組中的內容。當然,也許只有在 HashMap 的行為不可接受的時候,才需要采用這種做法。因為HashMap 的設計宗旨就是進行快速的檢索操作。最后,當我們使用 Map 時,首要的選擇應該是 HashMap。只有在極少數情況下才需要考慮其他方法
TreeMap 的創建速度比其他兩種類型明顯快得多(但你應親自嘗試一下,因為據說新版本可能會改善 ArrayMap 的性能)。考慮到這方面的原因,同時由于前述 TreeMap 出色的 put()性能,所以如
果需要創建大量 Map,而且只有在以后才需要涉及大量檢索操作,那么最佳的策略就是:創建和填充TreeMap;以后檢索量增大的時候,再將重要的 TreeMap 轉換成 HashMap—— 使用 HashMap(Map)構建器。
未支持的操作
利用 static(靜態)數組 Arrays.toList(),也許能將一個數組轉換成 List
public class Unsupported {private static String[] s = { "one", "two", "three", "four", "five", "six","seven", "eight", "nine", "ten", };static List a = Arrays.toList(s);static List a2 = Arrays.toList(new String[] { s[3], s[4], s[5] });public static void main(String[] args) {Collection1.print(a); // IterationSystem.out.println("a.contains(" + s[0] + ") = " + a.contains(s[0]));System.out.println("a.containsAll(a2) = " + a.containsAll(a2));System.out.println("a.isEmpty() = " + a.isEmpty());System.out.println("a.indexOf(" + s[5] + ") = " + a.indexOf(s[5]));// Traverse backwards:ListIterator lit = a.listIterator(a.size());while (lit.hasPrevious())System.out.print(lit.previous());System.out.println();// Set the elements to different values:for (int i = 0; i < a.size(); i++)a.set(i, "47");Collection1.print(a);// Compiles, but won't run:lit.add("X"); // Unsupported operationa.clear(); // Unsupporteda.add("eleven"); // Unsupporteda.addAll(a2); // Unsupporteda.retainAll(a2); // Unsupporteda.remove(s[0]); // Unsupporteda.removeAll(a2); // Unsupported} }從中可以看出,實際只實現了 Collection 和 List 接口的一部分。剩余的方法導致了不受歡迎的一種情況,名為UnsupportedOperationException。
在實現那些接口的集合類中,或者提供、或者沒有提供對那些方法的支持。若調用一個未獲支持的方法,就會導致一個 UnsupportedOperationException(操作未支持違例),這表明出現了一個編程錯誤。
Arrays.toList()產生了一個 List(列表),該列表是由一個固定長度的數組后推出來的。因此唯一能夠支持的就是那些不改變數組長度的操作。在另一方面,若請求一個新接口表達不同種類的行為(可能叫作“ FixedSizeList” —— 固定長度列表),就有遭遇更大的復雜程度的危險。這樣一來,以后試圖使用庫的時候,很快就會發現自己不知從何處下手。
對那些采用 Collection, List, Set 或者 Map 作為參數的方法,它們的文檔應當指出哪些可選的方法是必須實現的。舉個例子來說,排序要求實現 set()和 Iterator.set()方法,但不包括 add()和 remove()。
總結
以上是生活随笔為你收集整理的java基础系列:集合总结(5)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java基础系列:集合总结(4)
- 下一篇: java基础系列:集合总结(6)