Java中的集合框架大類可分爲Collection和Map;二者的區別:html
一、Collection是單列集合;Map是雙列集合java
二、Collection中只有Set系列要求元素惟一;Map中鍵須要惟一,值能夠重複c++
三、Collection的數據結構是針對元素的;Map的數據結構是針對鍵的。程序員
在說兩大集合體系以前先說說泛型,由於在後面的集合中都會用到;
所謂的泛型就是:類型的參數化算法
泛型是類型的一部分,類名+泛型是一個總體編程
若是有泛型,不使用時,參數的類型會自動提高成Object類型,若是再取出來的話就須要向下強轉,就可能發生類型轉化異常(ClassCaseException);不加泛型就不能在編譯期限定向集合中添加元素的類型,致使後期的處理麻煩。數組
下面就來對比加了泛型和不加泛型的區別:安全
package 好好學java; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class Test { public static void main(String[] args) { // 不加泛型,添加和遍歷 List list = new ArrayList<>(); list.add(1); list.add("123"); list.add("hello"); Iterator it = list.iterator(); while(it.hasNext()){ // 沒有添加泛型,這裏只能使用Object接收 Object obj = it.next(); System.out.println(obj); } } } package 好好學java; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class Test { public static void main(String[] args) { // 加泛型,添加和遍歷 List<String> list = new ArrayList<String>(); list.add("123"); list.add("hello"); Iterator<String> it = list.iterator(); while(it.hasNext()){ // 由於添加了泛型,就說明集合中裝的所有都是String類型的數據 // 因此這裏用String類型接收,就不會發生異常,而且能夠使用String的方法 String str = it.next(); System.out.println(str.length()); } } }
自定義帶泛型的類:微信
package 好好學java; public class Test { // 自定義一個帶有一個參數的泛型類,能夠向傳入什麼類型就傳入什麼類型 public static void main(String[] args) { // 進行測試, 傳入一個String對象 Person<String> perStr = new Person<String>(); perStr.setT("我是字符串"); String str = perStr.getT(); System.out.println(str); // 進行測試,傳入一個Integer對象 Person<Integer> perInt = new Person<Integer>(); perInt.setT(100); Integer intVal = perInt.getT(); System.out.println(intVal); } } //自定義一個帶有一個參數的泛型類 class Person<T>{ private T t; void setT(T t){ this.t = t; } T getT(){ return t; } }
實現帶有泛型的接口類型:數據結構
實現接口的同時, 指定了接口中的泛型類型. (定義類時肯定);
public class GenericImpl1 implements GenericInter<String> {}
實現接口時, 沒有指定接口中的泛型類型.此時, 須要將該接口的實現類定義爲泛型類.接口的類型須要在建立實現類對象時才能真正肯定其類型. (始終不肯定類型, 直到建立對象時肯定類型);
public class GenericImpl2<T> implements GenericInter<T> {}
泛型的通配符(?):
上限限定:好比定義方法的時候出現,public void getFunc(List<? extends Animal> an),
那麼表示這裏的參數能夠傳入Animal,或者 Animal的子類
下限限定: 好比定義方法的時候出現,public void getFunc(Set<? super Animal> an ),
那麼表示這裏的參數能夠傳入Animal,或者Animal的父類
使用泛型的注意點:
一、泛型不支持基本數據類型
二、泛型不支持繼承,必須保持先後一致(好比這樣是錯誤的:List<Object> list = new ArrayList<String>();
ollection包括兩大致系,List和Set
List的特色:
存取有序,有索引,能夠根據索引來進行取值,元素能夠重複
Set的特色:
存取無序,元素不能夠重複
下面有ArrayList,LinkedList,Vector
(已過期)
集合的的最大目的就是爲了存取;List集合的特色就是存取有序,能夠存儲重複的元素,能夠用下標進行元素的操做
ArrayList: 底層是使用數組實現,因此查詢速度快,增刪速度慢
package 好好學java; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class Test { // 使用ArrayList進行添加和遍歷 public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("接口1"); list.add("接口2"); list.add("接口3"); // 第一種遍歷方式,使用迭代器 Iterator<String> it = list.iterator(); while(it.hasNext()){ String next = it.next(); System.out.println(next); } System.out.println("-------------------"); // 第二種遍歷方式,使用foreach for (String str : list){ System.err.println(str); } } }
LinkedList:是基於鏈表結構實現的,因此查詢速度慢,增刪速度快,提供了特殊的方法,對頭尾的元素操做(進行增刪查)。
使用LinkedList來實現棧和隊列;棧是先進後出,而隊列是先進先出
package com.xiaoshitou.classtest; import java.util.LinkedList; /** * 利用LinkedList來模擬棧 * 棧的特色:先進後出 * @author Beck * */ public class MyStack { private LinkedList<String> linkList = new LinkedList<String>(); // 壓棧 public void push(String str){ linkList.addFirst(str); } // 出棧 public String pop(){ return linkList.removeFirst(); } // 查看 public String peek(){ return linkList.peek(); } // 判斷是否爲空 public boolean isEmpty(){ return linkList.isEmpty(); } } package 好好學java; public class Test { public static void main(String[] args) { // 測試棧 StackTest stack = new StackTest(); stack.push("我是第1個進去的"); stack.push("我是第2個進去的"); stack.push("我是第3個進去的"); stack.push("我是第4個進去的"); stack.push("我是第5個進去的"); // 取出 while (!stack.isEmpty()){ String pop = stack.pop(); System.out.println(pop); } // 打印結果 /*我是第5個進去的 我是第4個進去的 我是第3個進去的 我是第2個進去的 我是第1個進去的*/ } }
LinkedList實現Queue:
package 好好學java; import java.util.LinkedList; /** * 利用linkedList來實現隊列 * 隊列: 先進先出 * @author Beck * */ public class QueueTest { private LinkedList<String> link = new LinkedList<String>(); // 放入 public void put(String str){ link.addFirst(str); } // 獲取 public String get(){ return link.removeLast(); } // 判斷是否爲空 public boolean isEmpty(){ return link.isEmpty(); } } package 好好學java; public class Test { public static void main(String[] args) { // 測試隊列 QueueTest queue = new QueueTest(); queue.put("我是第1個進入隊列的"); queue.put("我是第2個進入隊列的"); queue.put("我是第3個進入隊列的"); queue.put("我是第4個進入隊列的"); // 遍歷隊列 while (!queue.isEmpty()){ String str = queue.get(); System.out.println(str); } // 打印結果 /*我是第1個進入隊列的 我是第2個進入隊列的 我是第3個進入隊列的 我是第4個進入隊列的*/ } }
Vector:由於已通過時,被ArrayList取代了;它還有一種迭代器經過vector.elements()獲取,判斷是否有元素和取元素的方法爲:hasMoreElements(),nextElement()
。
package 好好學java; import java.util.Enumeration; import java.util.Vector; public class Test { public static void main(String[] args) { Vector<String> vector = new Vector<String>(); vector.add("搜索"); vector.add("vector"); vector.add("list"); Enumeration<String> elements = vector.elements(); while (elements.hasMoreElements()){ String nextElement = elements.nextElement(); System.out.println(nextElement); } } }
Set集合的特色:元素不重複,存取無序,無下標
Set集合下面有:HashSet,LinkedHashSet,TreeSet
HashSet存儲字符串:
package 好好學java; import java.util.HashSet; import java.util.Iterator; import java.util.Set; public class Test { public static void main(String[] args) { // 利用HashSet來存取 Set<String> set = new HashSet<String>(); set.add("個人天"); set.add("我是重複的"); set.add("我是重複的"); set.add("welcome"); // 遍歷 第一種方式 迭代器 Iterator<String> it = set.iterator(); while(it.hasNext()){ String str = it.next(); System.out.println(str); } System.out.println("--------------"); for (String str : set){ System.out.println(str); } // 打印結果,重複的已經去掉了 /*個人天 welcome 我是重複的 -------------- 個人天 welcome 我是重複的*/ }
那哈希表是怎麼來保證元素的惟一性的呢,哈希表是經過hashCode和equals方法來共同保證的。
哈希表的存儲數據過程(哈希表底層也維護了一個數組):
根據存儲的元素計算出hashCode值,而後根據計算得出的hashCode值和數組的長度進行計算出存儲的下標;若是下標的位置無元素,那麼直接存儲;若是有元素,那麼使用要存入的元素和該元素進行equals方法,若是結果爲真,則已經有相同的元素了,因此直接不存;若是結果假,那麼進行存儲,以鏈表的形式存儲。
演示HashSet來存儲自定義對象:
package 好好學java; public class Person { // 屬性 private String name; private int age; // 構造方法 public Person() { super(); } public Person(String name, int age) { super(); this.name = name; this.age = age; } // 要讓哈希表存儲不重複的元素,就必須重寫hasCode和equals方法 @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + age; result = prime * result + ((name == null) ? 0 : name.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Person other = (Person) obj; if (age != other.age) return false; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; return true; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } // getter & setter ... } package 好好學java; import java.util.HashSet; import java.util.Set; public class Test { public static void main(String[] args) { // 利用HashSet來存取自定義對象 Person Set<Person> set = new HashSet<Person>(); set.add(new Person("張三", 12)); set.add(new Person("李四", 13)); set.add(new Person("王五", 22)); set.add(new Person("張三", 12)); // 遍歷 for (Person p : set){ System.out.println(p); } // 結果:向集合中存儲兩個張三對象,可是集合中就成功存儲了一個 /*Person [name=王五, age=22] Person [name=李四, age=13] Person [name=張三, age=12]*/ } }
因此在向HashSet集合中存儲自定義對象時,爲了保證set集合的惟一性,那麼必須重寫hashCode和equals方法。
是基於鏈表和哈希表共同實現的,因此具備存取有序,元素惟一
package 好好學java; import java.util.LinkedHashSet; public class Test { public static void main(String[] args) { // 利用LinkedHashSet來存取自定義對象 Person LinkedHashSet<Person> set = new LinkedHashSet<Person>(); set.add(new Person("張三", 12)); set.add(new Person("李四", 13)); set.add(new Person("王五", 22)); set.add(new Person("張三", 12)); // 遍歷 for (Person p : set){ System.out.println(p); } // 結果:向集合中存儲兩個張三對象,可是集合中就成功存儲了一個, // 而且存進的順序,和取出來的順序是一致的 /*Person [name=張三, age=12] Person [name=李四, age=13] Person [name=王五, age=22]*/ } }
特色:存取無序,元素惟一,能夠進行排序(排序是在添加的時候進行排序)。
TreeSet是基於二叉樹的數據結構,二叉樹的:一個節點下不能多餘兩個節點。
二叉樹的存儲過程:
若是是第一個元素,那麼直接存入,做爲根節點,下一個元素進來是會跟節點比較,若是大於節點放右邊的,小於節點放左邊;等於節點就不存儲。後面的元素進來會依次比較,直到有位置存儲爲止
TreeSet集合存儲String對象
package 好好學java; import java.util.TreeSet; public class Test { public static void main(String[] args) { TreeSet<String> treeSet = new TreeSet<String>(); treeSet.add("abc"); treeSet.add("zbc"); treeSet.add("cbc"); treeSet.add("xbc"); for (String str : treeSet){ System.out.println(str); } // 結果:取出來的結果是通過排序的 /* abc cbc xbc zbc*/ } }
TreeSet保證元素的惟一性是有兩種方式:
一、自定義對象實現Comparable接口,重寫comparaTo方法,該方法返回0表示相等,小於0表示準備存入的元素比被比較的元素小,不然大於0;
二、在建立TreeSet的時候向構造器中傳入比較器Comparator接口實現類對象,實現Comparator接口重寫compara方法。
若是向TreeSet存入自定義對象時,自定義類沒有實現Comparable接口,或者沒有傳入Comparator比較器時,會出現ClassCastException異常
下面就是演示用兩種方式來存儲自定義對象
package 好好學java; public class Person implements Comparable<Person>{ // 屬性 private String name; private int age; // 構造方法 public Person() { super(); } public Person(String name, int age) { super(); this.name = name; this.age = age; } // 要讓哈希表存儲不重複的元素,就必須重寫hasCode和equals方法 @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + age; result = prime * result + ((name == null) ? 0 : name.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Person other = (Person) obj; if (age != other.age) return false; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; return true; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } // getter & setter ... @Override public int compareTo(Person o) { int result = this.age - o.age; if (result == 0){ return this.name.compareTo(o.name); } return result; } } package 好好學java; import java.util.TreeSet; public class Test { public static void main(String[] args) { // 利用TreeSet來存儲自定義類Person對象 TreeSet<Person> treeSet = new TreeSet<Person>(); // Person類實現了Comparable接口,而且重寫comparaTo方法 // 比較規則是先按照 年齡排序,年齡相等的狀況按照年齡排序 treeSet.add(new Person("張山1", 20)); treeSet.add(new Person("張山2", 16)); treeSet.add(new Person("張山3", 13)); treeSet.add(new Person("張山4", 17)); treeSet.add(new Person("張山5", 20)); for (Person p : treeSet){ System.out.println(p); } // 結果:按照comparaTo方法內的邏輯來排序的 /* Person [name=張山3, age=13] Person [name=張山2, age=16] Person [name=張山4, age=17] Person [name=張山1, age=20] Person [name=張山5, age=20] */ } }
另外一種方式:使用比較器Comparator
package 好好學java; public class Person{ // 屬性 private String name; private int age; // 構造方法 public Person() { super(); } public Person(String name, int age) { super(); this.name = name; this.age = age; } // 要讓哈希表存儲不重複的元素,就必須重寫hasCode和equals方法 @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + age; result = prime * result + ((name == null) ? 0 : name.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Person other = (Person) obj; if (age != other.age) return false; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; return true; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } // getter & setter ... } package 好好學java; import java.util.Comparator; import java.util.TreeSet; public class Test { public static void main(String[] args) { // 利用TreeSet來存儲自定義類Person對象 // 建立TreeSet對象的時候傳入Comparator比較器,使用匿名內部類的方式 // 比較規則是先按照 年齡排序,年齡相等的狀況按照年齡排序 TreeSet<Person> treeSet = new TreeSet<Person>(new Comparator<Person>() { @Override public int compare(Person o1, Person o2) { if (o1 == o2){ return 0; } int result = o1.getAge() - o2.getAge(); if (result == 0){ return o1.getName().compareTo(o2.getName()); } return result; } }); treeSet.add(new Person("張山1", 20)); treeSet.add(new Person("張山2", 16)); treeSet.add(new Person("張山3", 13)); treeSet.add(new Person("張山4", 17)); treeSet.add(new Person("張山5", 20)); for (Person p : treeSet){ System.out.println(p); } // 結果:按照compara方法內的邏輯來排序的 /* Person [name=張山3, age=13] Person [name=張山2, age=16] Person [name=張山4, age=17] Person [name=張山1, age=20] Person [name=張山5, age=20] */ } }
List : "特色 :" 存取有序,元素有索引,元素能夠重複.
ArrayList : 數組結構,查詢快,增刪慢,線程不安全,所以效率高.
Vector : 數組結構,查詢快,增刪慢,線程安全,所以效率低.
addFirst() removeFirst() getFirst()
Set :"特色 :" 存取無序,元素無索引,元素不能夠重複.
請問 : 哈希表如何保證元素惟一呢 ? 底層是依賴 hashCode 和 equals 方法.
當存儲元素的時候,先根據 hashCode + 數組長度 計算出一個索引,判斷索引位置是否有元素.
若是沒有元素,直接存儲,若是有元素,先判斷 equals 方法,比較兩個元素是否相同,不一樣則存儲,相同則捨棄.
咱們自定義對象存儲的元素必定要實現 hashCode 和 equals.
LinkedHashSet : 存儲有序,元素不能夠重複.
有兩種排序方式 :
咱們的元素必須實現 Comparable 接口.可比較的.實現 CompareTo 方法.
咱們須要自定義類,實現Comparetor接口,這個類就是比較器實現 compare 方法.
而後在建立 TreeSet 的時候,把比較器對象做爲參數傳遞給 TreeSet.
Map是一個雙列集合,其中保存的是鍵值對,鍵要求保持惟一性,值能夠重複
鍵值是一一對應的,一個鍵只能對應一個值
Map的特色:是存取無序,鍵不可重複
Map在存儲的時候,將鍵值傳入Entry,而後存儲Entry對象
其中下面有HashMap,LinkedHashMap和TreeMap
是基於哈希表結構實現的,因此存儲自定義對象做爲鍵時,必須重寫hasCode和equals方法。存取無序的
下面演示HashMap以自定義對象做爲鍵:
package 好好學java; import java.util.HashMap; import java.util.Iterator; import java.util.Map.Entry; import java.util.Set; public class Test { public static void main(String[] args) { // 利用HashMap存儲,自定義對象Person做爲鍵 // 爲了保證鍵的惟一性,必須重寫hashCode和equals方法 HashMap<Person,String> map = new HashMap<Person,String>(); map.put(new Person("張三", 12), "JAVA"); map.put(new Person("李四", 13), "IOS"); map.put(new Person("小花", 22), "JS"); map.put(new Person("小黑", 32), "PHP"); map.put(new Person("張三", 12), "C++"); Set<Entry<Person, String>> entrySet = map.entrySet(); Iterator<Entry<Person, String>> it = entrySet.iterator(); while (it.hasNext()){ Entry<Person, String> entry = it.next(); System.out.println(entry.getKey() + "---" + entry.getValue()); } // 結果:存入的時候添加了兩個張三,若是Map中鍵相同的時候,當後面的值會覆蓋掉前面的值 /* Person [name=李四, age=13]---IOS Person [name=張三, age=12]---C++ Person [name=小黑, age=32]---PHP Person [name=小花, age=22]---JS */ } }
用法跟HashMap基本一致,它是基於鏈表和哈希表結構的因此具備存取有序,鍵不重複的特性
下面演示利用LinkedHashMap存儲,注意存的順序和遍歷出來的順序是一致的:
package 好好學java; import java.util.LinkedHashMap; import java.util.Map.Entry; public class Test { public static void main(String[] args) { // 利用LinkedHashMap存儲,自定義對象Person做爲鍵 // 爲了保證鍵的惟一性,必須重寫hashCode和equals方法 LinkedHashMap<Person,String> map = new LinkedHashMap<Person,String>(); map.put(new Person("張三", 12), "JAVA"); map.put(new Person("李四", 13), "IOS"); map.put(new Person("小花", 22), "JS"); map.put(new Person("小黑", 32), "PHP"); map.put(new Person("張三", 12), "C++"); // foreach遍歷 for (Entry<Person,String> entry : map.entrySet()){ System.out.println(entry.getKey()+"==="+entry.getValue()); } // 結果:存入的時候添加了兩個張三,若是Map中鍵相同的時候,當後面的值會覆蓋掉前面的值 // 注意:LinkedHashMap的特色就是存取有序,取出來的順序就是和存入的順序保持一致 /* Person [name=張三, age=12]===C++ Person [name=李四, age=13]===IOS Person [name=小花, age=22]===JS Person [name=小黑, age=32]===PHP */ } }
給TreeMap集合中保存自定義對象,自定義對象做爲TreeMap集合的key值。因爲TreeMap底層使用的二叉樹,其中存放進去的全部數據都須要排序,要排序,就要求對象具有比較功能。對象所屬的類須要實現Comparable接口。或者給TreeMap集合傳遞一個Comparator接口對象。
利用TreeMap存入自定義對象做爲鍵:
package 好好學java; import java.util.Comparator; import java.util.Map.Entry; import java.util.TreeMap; public class Test { public static void main(String[] args) { // 利用TreeMap存儲,自定義對象Person做爲鍵 // 自定義對象實現Comparable接口或者傳入Comparator比較器 TreeMap<Person,String> map = new TreeMap<Person,String>(new Comparator<Person>() { @Override public int compare(Person o1, Person o2) { if (o1 == o2){ return 0; } int result = o1.getAge() - o2.getAge(); if (result == 0){ return o1.getName().compareTo(o2.getName()); } return result; } }); map.put(new Person("張三", 12), "JAVA"); map.put(new Person("李四", 50), "IOS"); map.put(new Person("小花", 32), "JS"); map.put(new Person("小黑", 32), "PHP"); map.put(new Person("張三", 12), "C++"); // foreach遍歷 for (Entry<Person,String> entry : map.entrySet()){ System.out.println(entry.getKey()+"==="+entry.getValue()); } // 結果:存入的時候添加了兩個張三,若是Map中鍵相同的時候,當後面的值會覆蓋掉前面的值 // 注意:TreeMap 取出來的順序是通過排序的,是根據compara方法排序的 /* Person [name=張三, age=12]===C++ Person [name=小花, age=32]===JS Person [name=小黑, age=32]===PHP Person [name=李四, age=50]===IOS */ } }
不管使用的數組屬於什麼類型,數組標識符實際都是指向真實對象的一個句柄。那些對象自己是在內存
「堆」裏建立的。堆對象既可「隱式」建立(即默認產生),亦可「顯式」建立(即明確指定,用一個 new
表達式)。堆對象的一部分(實際是咱們能訪問的惟一字段或方法)是隻讀的length(長度)成員,它告訴
咱們那個數組對象裏最多能容納多少元素。對於數組對象,「 []」語法是咱們能採用的惟一另類訪問方法。
對象數組和基本數據類型數組在使用方法上幾乎是徹底一致的。惟一的差異在於對象數組容納的是句柄,而基本數據類型數組容納的是具體的數值
public class ArraySize { public static void main(String[] args) { // Arrays of objects: Weeble[] a; // Null handle Weeble[] b = new Weeble[5]; // Null handles Weeble[] c = new Weeble[4]; for (int i = 0; i < c.length; i++) c[i] = new Weeble(); Weeble[] d = { new Weeble(), new Weeble(), new Weeble() }; // Compile error: variable a not initialized: // !System.out.println("a.length=" + a.length); System.out.println("b.length = " + b.length); // The handles inside the array are // automatically initialized to null: for (int i = 0; i < b.length; i++) System.out.println("b[" + i + "]=" + b[i]); System.out.println("c.length = " + c.length); System.out.println("d.length = " + d.length); a = d; System.out.println("a.length = " + a.length); // Java 1.1 initialization syntax: a = new Weeble[] { new Weeble(), new Weeble() }; System.out.println("a.length = " + a.length); // Arrays of primitives: int[] e; // Null handle int[] f = new int[5]; int[] g = new int[4]; for (int i = 0; i < g.length; i++) g[i] = i * i; int[] h = { 11, 47, 93 }; // Compile error: variable e not initialized: // !System.out.println("e.length=" + e.length); System.out.println("f.length = " + f.length); // The primitives inside the array are // automatically initialized to zero: for (int i = 0; i < f.length; i++) System.out.println("f[" + i + "]=" + f[i]); System.out.println("g.length = " + g.length); System.out.println("h.length = " + h.length); e = h; System.out.println("e.length = " + e.length); // Java 1.1 initialization syntax: e = new int[] { 1, 2 }; System.out.println("e.length = " + e.length); } }
輸出以下:
b.length = 5
b[0]=null
b[1]=null
b[2]=null
b[3]=null
b[4]=null
c.length = 4
d.length = 3
a.length = 3
a.length = 2
f.length = 5
f[0]=0
f[1]=0
f[2]=0
f[3]=0
f[4]=0
g.length = 4
h.length = 3
e.length = 3
e.length = 2
其中,數組 a 只是初始化成一個 null 句柄。此時,編譯器會禁止咱們對這個句柄做任何實際操做,除非已正
確地初始化了它。數組 b 被初始化成指向由 Weeble 句柄構成的一個數組,但那個數組裏實際並未放置任何
Weeble 對象。然而,咱們仍然能夠查詢那個數組的大小,由於 b 指向的是一個合法對象。
換言之,咱們只知道數組對象的大小或容量,不知其實際容納了多少個元素。
儘管如此,因爲數組對象在建立之初會自動初始化成 null,因此可檢查它是否爲 null,判斷一個特定的數組「空位」是否容納一個對象。相似地,由基本數據類型構成的數組會自動初始化成零(針對數值類型)、 null(字符類型)或者false(布爾類型)
數組 c 顯示出咱們首先建立一個數組對象,再將 Weeble 對象賦給那個數組的全部「空位」。數組 d 揭示出
「集合初始化」語法,從而建立數組對象(用 new 命令明確進行,相似於數組 c),而後用 Weeble 對象進行
初始化,所有工做在一條語句裏完成。
下面這個表達式:
a = d;
向咱們展現瞭如何取得同一個數組對象鏈接的句柄,而後將其賦給另外一個數組對象,向咱們展現瞭如何取得同一個數組對象鏈接的句柄,而後將其賦給另外一個數組對象
1.基本數據類型集合
集合類只能容納對象句柄。但對一個數組,卻既可令其直接容納基本類型的數據,亦可容納指向對象的句
柄。利用象 Integer、 Double 之類的「 封裝器」類,可將基本數據類型的值置入一個集合裏。
不管將基本類型的數據置入數組,仍是將其封裝進入位於集合的一個類內,都涉及到執行效率的問題。顯
然,若能建立和訪問一個基本數據類型數組,那麼比起訪問一個封裝數據的集合,前者的效率會高出許多。
假定咱們如今想寫一個方法,同時不但願它僅僅返回同樣東西,而是想返回一系列東西。此時,象C 和 C++這樣的語言會使問題複雜化,由於咱們不能返回一個數組,只能返回指向數組的一個指針。這樣就很是麻煩,由於很難控制數組的「存在時間」,它很容易形成內存「漏洞」的出現。
Java 採用的是相似的方法,但咱們能「返回一個數組」。固然,此時返回的實際還是指向數組的指針。但在Java 裏,咱們永遠沒必要擔憂那個數組的是否可用—— 只要須要,它就會自動存在。並且垃圾收集器會在咱們完成後自動將其清除
public class IceCream { static String[] flav = { "Chocolate", "Strawberry", "Vanilla Fudge Swirl", "Mint Chip", "Mocha Almond Fudge", "Rum Raisin", "Praline Cream", "Mud Pie" }; static String[] flavorSet(int n) { // Force it to be positive & within bounds: n = Math.abs(n) % (flav.length + 1); String[] results = new String[n]; int[] picks = new int[n]; for(int i = 0; i < picks.length; i++) picks[i] = -1; for(int i = 0; i < picks.length; i++) { retry: while(true) { int t =(int)(Math.random() * flav.length); for(int j = 0; j < i; j++)213 if(picks[j] == t) continue retry; picks[i] = t; results[i] = flav[t]; break; } } return results; } public static void main(String[] args) { for (int i = 0; i < 20; i++) { System.out.println("flavorSet(" + i + ") = "); String[] fl = flavorSet(flav.length); for (int j = 0; j < fl.length; j++) System.out.println("\t" + fl[j]); } } }
flavorSet()方法建立了一個名爲 results 的 String 數組。該數組的大小爲 n—— 具體數值取決於咱們傳遞給方法的自變量。隨後,它從數組 flav 裏隨機挑選一些「香料」( Flavor),並將它們置入 results 裏,並最終返回 results。返回數組與返回其餘任何對象沒什麼區別—— 最終返回的都是一個句柄。
另外一方面,注意當 flavorSet()隨機挑選香料的時候,它須要保證之前出現過的一次隨機選擇不會再次出現。爲達到這個目的,它使用了一個無限 while 循環,不斷地做出隨機選擇,直到發現未在 picks 數組裏出現過的一個元素爲止(固然,也能夠進行字串比較,檢查隨機選擇是否在 results 數組裏出現過,但字串比較的效率比較低)。若成功,就添加這個元素,並中斷循環( break),再查找下一個( i 值會遞增)。但倘若 t 是一個已在 picks 裏出現過的數組,就用標籤式的 continue 往回跳兩級,強制選擇一個新 t。 用一個調試程序能夠很清楚地看到這個過程。
爲容納一組對象,最適宜的選擇應當是數組。並且假如容納的是一系列基本數據類型,更是必須採用數組。
缺點:類型未知
使用 Java 集合的「缺點」是在將對象置入一個集合時丟失了類型信息。之因此會發生這種狀況,是因爲當初編寫集合時,那個集合的程序員根本不知道用戶到底想把什麼類型置入集合。若指示某個集合只容許特定的類型,會妨礙它成爲一個「常規用途」的工具,爲用戶帶來麻煩。爲解決這個問題,集合實際容納的是類型爲 Object 的一些對象的句柄。
固然,也要注意集合並不包括基本數據類型,由於它們並非從「任何東西」繼承來的。
Java 不容許人們濫用置入集合的對象。假如將一條狗扔進一個貓的集合,那麼仍會將集合內的全部東西都看做貓,因此在使用那條狗時會獲得一個「違例」錯誤。在一樣的意義上,倘若試圖將一條狗的句柄「造型」到一隻貓,那麼運行期間仍會獲得一個「違例」錯誤
class Cat { private int catNumber; Cat(int i) { catNumber = i; } void print() { System.out.println("Cat #" + catNumber); } } class Dog { private int dogNumber; Dog(int i) { dogNumber = i; } void print() { System.out.println("Dog #" + dogNumber); } } public class CatsAndDogs { public static void main(String[] args) { Vector cats = new Vector(); for (int i = 0; i < 7; i++) cats.addElement(new Cat(i)); // Not a problem to add a dog to cats: cats.addElement(new Dog(7)); for (int i = 0; i < cats.size(); i++) ((Cat) cats.elementAt(i)).print(); // Dog is detected only at run-time } }
class Mouse { private int mouseNumber; Mouse(int i) { mouseNumber = i; } // Magic method: public String toString() { return "This is Mouse #" + mouseNumber; } void print(String msg) { if (msg != null) System.out.println(msg); System.out.println("Mouse number " + mouseNumber); } } class MouseTrap { static void caughtYa(Object m) { Mouse mouse = (Mouse) m; // Cast from Object mouse.print("Caught one!"); } } public class WorksAnyway { public static void main(String[] args) { Vector mice = new Vector(); for(int i = 0; i < 3; i++) mice.addElement(new Mouse(i)); for(int i = 0; i < mice.size(); i++) { // No cast necessary, automatic call // to Object.toString(): System.out.println( "Free mouse: " + mice.elementAt(i)); MouseTrap.caughtYa(mice.elementAt(i)); } } }
可在 Mouse 裏看到對 toString()的重定義代碼。在 main()的第二個 for 循環中,可發現下述語句:
System.out.println("Free mouse: " + mice.elementAt(i));
在「 +」後,編譯器預期看到的是一個 String 對象。 elementAt()生成了一個 Object,因此爲得到但願的String,編譯器會默認調用 toString()。但不幸的是,只有針對 String 才能獲得象這樣的結果;其餘任何類型都不會進行這樣的轉換。
隱藏造型的第二種方法已在 Mousetrap 裏獲得了應用。 caughtYa()方法接收的不是一個 Mouse,而是一個Object。隨後再將其造型爲一個 Mouse。固然,這樣作是很是冒失的,由於經過接收一個 Object,任何東西均可以傳遞給方法。然而,倘若造型不正確—— 若是咱們傳遞了錯誤的類型—— 就會在運行期間獲得一個違例錯誤。這固然沒有在編譯期進行檢查好,但仍然能防止問題的發生。注意在使用這個方法時毋需進行造型:
MouseTrap.caughtYa(mice.elementAt(i));
class Gopher { private int gopherNumber; Gopher(int i) { gopherNumber = i; } void print(String msg) { if (msg != null) System.out.println(msg); System.out.println("Gopher number " + gopherNumber); } } class GopherTrap { static void caughtYa(Gopher g) { g.print("Caught one!"); } } class GopherVector { private Vector v = new Vector(); public void addElement(Gopher m) { v.addElement(m); } public Gopher elementAt(int index) { return (Gopher) v.elementAt(index); } public int size() { return v.size(); } public static void main(String[] args) { GopherVector gophers = new GopherVector(); for (int i = 0; i < 3; i++) gophers.addElement(new Gopher(i)); for (int i = 0; i < gophers.size(); i++) GopherTrap.caughtYa(gophers.elementAt(i)); } }
新的 GopherVector 類有一個類型爲 Vector 的 private 成員(從 Vector 繼承有些麻煩,理由稍後便知),並且方法也和 Vector 相似。然而,它不會接收和產生普通 Object,只對 Gopher 對象
感興趣。
因爲 GopherVector 只接收一個 Gopher(地鼠),因此假如咱們使用:
gophers.addElement(new Pigeon());
就會在編譯期間得到一條出錯消息。採用這種方式,儘管從編碼的角度看顯得更使人沉悶,但能夠當即判斷出是否使用了正確的類型。注意在使用 elementAt()時沒必要進行造型—— 它確定是一個 Gopher
容納各類各樣的對象正是集合的首要任務。在 Vector 中, addElement()即是咱們插入對象採用的方法,而 elementAt()是
提取對象的惟一方法。 Vector 很是靈活,咱們可在任什麼時候候選擇任何東西,並可以使用不一樣的索引選擇多個元素。
若從更高的角度看這個問題,就會發現它的一個缺陷:須要事先知道集合的準確類型,不然沒法使用。乍看來,這一點彷佛沒什麼關係。但倘若最開始決定使用Vector,後來在程序中又決定(考慮執行效率的緣由)改變成一個 List(屬於 Java1.2 集合庫的一部分),這時又該如何作呢?
咱們一般認爲反覆器是一種「輕量級」對象;也就是說,建立它只需付出極少的代價。但也正是因爲這個緣由,咱們常發現反覆器存在一些彷佛很奇怪的限制。例如,有些反覆器只能朝一個方向移動。
Java 的 Enumeration(枚舉,註釋②)即是具備這些限制的一個反覆器的例子。除下面這些外,不可再用它
作其餘任何事情:
(1) 用一個名爲 elements()的方法要求集合爲咱們提供一個 Enumeration。咱們首次調用它的 nextElement()
時,這個 Enumeration 會返回序列中的第一個元素。
(2) 用 nextElement() 得到下一個對象。
(3) 用 hasMoreElements()檢查序列中是否還有更多的對象
class Hamster { private int hamsterNumber; Hamster(int i) { hamsterNumber = i; } public String toString() { return "This is Hamster #" + hamsterNumber; } } class Printer { static void printAll(Enumeration e) { while (e.hasMoreElements()) System.out.println(e.nextElement().toString()); } } public class HamsterMaze { public static void main(String[] args) { Vector v = new Vector(); for (int i = 0; i < 3; i++) v.addElement(new Hamster(i)); Printer.printAll(v.elements()); } }
仔細研究一下打印方法:
static void printAll(Enumeration e) { while(e.hasMoreElements()) System.out.println( e.nextElement().toString()); }
注意其中沒有與序列類型有關的信息。咱們擁有的所有東西即是Enumeration。爲了解有關序列的狀況,一個 Enumeration 便足夠了:可取得下一個對象,亦可知道是否已抵達了末尾。取得一系列對象,而後在其中遍歷,從而執行一個特定的操做—— 這是一個很有價值的編程概念
崩潰 Java
Java 標準集合裏包含了 toString()方法,因此它們能生成本身的 String 表達方式,包括它們容納的對象。
例如在 Vector 中, toString()會在 Vector 的各個元素中步進和遍歷,併爲每一個元素調用 toString()。假定咱們如今想打印出本身類的地址。看起來彷佛簡單地引用 this 便可(特別是 C++程序員有這樣作的傾向):
public class CrashJava { public String toString() { return "CrashJava address: " + this + "\n"; } public static void main(String[] args) { Vector v = new Vector(); for (int i = 0; i < 10; i++) v.addElement(new CrashJava()); System.out.println(v); } }
此時發生的是字串的自動類型轉換。當咱們使用下述語句時:
「CrashJava address: 」 + this
編譯器就在一個字串後面發現了一個「 +」以及好象並不是字串的其餘東西,因此它會試圖將 this 轉換成一個字串。轉換時調用的是 toString(),後者會產生一個遞歸調用。若在一個 Vector 內出現這種事情,看起來堆棧就會溢出,同時違例控制機制根本沒有機會做出響應。
若確實想在這種狀況下打印出對象的地址,解決方案就是調用 Object 的 toString 方法。此時就沒必要加入this,只需使用 super.toString()。固然,採起這種作法也有一個前提:咱們必須從 Object 直接繼承,或者沒有一個父類覆蓋了 toString 方法。
BitSet 實際是由「 二進制位」構成的一個 Vector。若是但願高效率地保存大量「開-關」信息,就應使用BitSet。它只有從尺寸的角度看纔有意義;若是但願的高效率的訪問,那麼它的速度會比使用一些固有類型的數組慢一些。
BitSet 的最小長度是一個長整數( Long)的長度: 64 位。這意味着假如咱們準備保存比這更小的數據,如 8 位數據,那麼 BitSet 就顯得浪費了。因此最好建立本身的類,用它容納本身的標誌位。
Stack 有時也能夠稱爲「後入先出」( LIFO)集合。換言之,咱們在堆棧裏最後「壓入」的東西將是之後第
一個「彈出」的。和其餘全部 Java 集合同樣,咱們壓入和彈出的都是「對象」,因此必須對本身彈出的東西
進行「造型」。
下面是一個簡單的堆棧示例,它能讀入數組的每一行,同時將其做爲字串壓入堆棧。
public class Stacks { static String[] months = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; public static void main(String[] args) { Stack stk = new Stack(); for (int i = 0; i < months.length; i++) stk.push(months[i] + " "); System.out.println("stk = " + stk); // Treating a stack as a Vector: stk.addElement("The last line"); System.out.println("element 5 = " + stk.elementAt(5)); System.out.println("popping elements:"); while (!stk.empty()) System.out.println(stk.pop()); } }
months 數組的每一行都經過 push()繼承進入堆棧,稍後用 pop()從堆棧的頂部將其取出。要聲明的一點是,Vector 操做亦可針對 Stack 對象進行。這多是由繼承的特質決定的—— Stack「屬於」一種 Vector。所以,能對 Vector 進行的操做亦可針對 Stack 進行,例如 elementAt()方法
Vector 容許咱們用一個數字從一系列對象中做出選擇,因此它實際是將數字同對象關聯起來了。
但假如咱們想根據其餘標準選擇一系列對象呢?堆棧就是這樣的一個例子:它的選擇標準是「最後壓入堆棧的東西」。
這種「從一系列對象中選擇」的概念亦可叫做一個「映射」、「字典」或者「關聯數組」。從概念上講,它看起來象一個 Vector,但卻不是經過數字來查找對象,而是用另外一個對象來查找它們!這一般都屬於一個程序中的重要進程。
在 Java 中,這個概念具體反映到抽象類 Dictionary 身上。該類的接口是很是直觀的 size()告訴咱們其中包含了多少元素; isEmpty()判斷是否包含了元素(是則爲 true); put(Object key, Object value)添加一個值(咱們但願的東西),並將其同一個鍵關聯起來(想用於搜索它的東西); get(Object key)得到與某個鍵對應的值;而 remove(Object Key)用於從列表中刪除「鍵-值」對。還能夠使用枚舉技術: keys()產生對鍵的一個枚舉( Enumeration);而 elements()產生對全部值的一個枚舉。這即是一個 Dict ionary(字典)的所有。
public class AssocArray extends Dictionary { private Vector keys = new Vector(); private Vector values = new Vector(); public int size() { return keys.size(); } public boolean isEmpty() { return keys.isEmpty(); } public Object put(Object key, Object value) { keys.addElement(key); values.addElement(value); return key; } public Object get(Object key) { int index = keys.indexOf(key); // indexOf() Returns -1 if key not found: if (index == -1) return null; return values.elementAt(index); } public Object remove(Object key) { int index = keys.indexOf(key); if (index == -1) return null; keys.removeElementAt(index); Object returnval = values.elementAt(index); values.removeElementAt(index); return returnval; } public Enumeration keys() { return keys.elements(); } public Enumeration elements() { return values.elements(); } // Test it: public static void main(String[] args) { AssocArray aa = new AssocArray(); for (char c = 'a'; c <= 'z'; c++) aa.put(String.valueOf(c), String.valueOf(c).toUpperCase()); char[] ca = { 'a', 'e', 'i', 'o', 'u' }; for (int i = 0; i < ca.length; i++) System.out.println("Uppercase: " + aa.get(String.valueOf(ca[i]))); } }
在對 AssocArray 的定義中,咱們注意到的第一個問題是它「擴展」了字典。這意味着 AssocArray 屬於Dictionary 的一種類型,因此可對其發出與 Dictionary 同樣的請求。若是想生成本身的 Dictionary,並且就在這裏進行,那麼要作的所有事情只是填充位於 Dictionary 內的全部方法(並且必須覆蓋全部方法,由於
它們—— 除構建器外—— 都是抽象的)。
標準 Java 庫只包含 Dictionary 的一個變種,名爲 Hashtable(散列表,註釋③)。 Java 的散列表具備與AssocArray 相同的接口(由於二者都是從 Dictionary 繼承來的)。但有一個方面卻反映出了差異:執行效率。若仔細想一想必須爲一個 get()作的事情,就會發如今一個 Vector 裏搜索鍵的速度要慢得多。但此時用散列表卻能夠加快很多速度。沒必要用冗長的線性搜索技術來查找一個鍵,而是用一個特殊的值,名爲「散列碼」。散列碼能夠獲取對象中的信息,而後將其轉換成那個對象「相對惟一」的整數( int)。全部對象都有一個散列碼,而 hashCode()是根類 Object 的一個方法。 Hashtable 獲取對象的 hashCode(),而後用它快速查找鍵。
class Counter { int i = 1; public String toString() { return Integer.toString(i); } } class Statistics { public static void main(String[] args) { Hashtable ht = new Hashtable(); for (int i = 0; i < 10000; i++) { // Produce a number between 0 and 20: Integer r = new Integer((int) (Math.random() * 20)); if (ht.containsKey(r)) ((Counter) ht.get(r)).i++; else ht.put(r, new Counter()); } System.out.println(ht); } }
class Groundhog { int ghNumber; Groundhog(int n) { ghNumber = n; } } class Prediction { boolean shadow = Math.random() > 0.5; public String toString() { if (shadow) return "Six more weeks of Winter!"; else return "Early Spring!"; } } public class SpringDetector { public static void main(String[] args) { Hashtable ht = new Hashtable(); for (int i = 0; i < 10; i++) ht.put(new Groundhog(i), new Prediction()); System.out.println("ht = " + ht + "\n"); System.out.println("Looking up prediction for groundhog #3:"); Groundhog gh = new Groundhog(3); if (ht.containsKey(gh)) System.out.println((Prediction) ht.get(gh)); } }
問題在於Groundhog 是從通用的 Object 根類繼承的(若當初未指
定基礎類,則全部類最終都是從 Object 繼承的)。事實上是用 Object 的 hashCode()方法生成每一個對象的散列碼,並且默認狀況下只使用它的對象的地址。因此, Groundhog(3)的第一個實例並不會產生與Groundhog(3)第二個實例相等的散列碼,而咱們用第二個實例進行檢索
或許認爲此時要作的所有事情就是正確地覆蓋 hashCode()。但這樣作依然行不能,除非再作另外一件事情:覆蓋也屬於 Object 一部分的 equals()。當散列表試圖判斷咱們的鍵是否等於表內的某個鍵時,就會用到這個方法。一樣地,默認的 Object.equals()只是簡單地比較對象地址,因此一個 Groundhog(3)並不等於
另外一個 Groundhog(3)。
所以,爲了在散列表中將本身的類做爲鍵使用,必須同時覆蓋 hashCode()和 equals(),就象下面展現的那樣:
class Groundhog { int ghNumber; Groundhog(int n) { ghNumber = n; } } class Prediction { boolean shadow = Math.random() > 0.5; public String toString() { if (shadow) return "Six more weeks of Winter!"; else return "Early Spring!"; } } public class SpringDetector { public static void main(String[] args) { Hashtable ht = new Hashtable(); for (int i = 0; i < 10; i++) ht.put(new Groundhog(i), new Prediction()); System.out.println("ht = " + ht + "\n"); System.out.println("Looking up prediction for groundhog #3:"); Groundhog gh = new Groundhog(3); if (ht.containsKey(gh)) System.out.println((Prediction) ht.get(gh)); } }
Groundhog2.hashCode()將土拔鼠號碼做爲一個標識符返回(在這個例子中,程序員須要保證沒有兩個土拔鼠用一樣的 ID 號碼並存)。爲了返回一個獨一無二的標識符,並不須要 hashCode(), equals()方法必須可以嚴格判斷兩個對象是否相等。
equals()方法要進行兩種檢查:檢查對象是否爲 null;若不爲 null ,則繼續檢查是否爲 Groundhog2 的一個實例(要用到 instanceof 關鍵字)。即便爲了繼續執行 equals(),它也應該是一個Groundhog2。正如你們看到的那樣,這種比較創建在實際 ghNumber 的基礎上。這一次一旦咱們運行程序,就會看到它終於產生了正確的輸出(許多 Java 庫的類都覆蓋了 hashcode() 和 equals()方法,以便與本身提供的內容適應)。
將穿越一個序列的操做與那個序列的基礎結構分隔開。在下面的例子裏, PrintData 類用一個 Enumeration 在一個序列中移動,併爲每一個對象都調用toString()方法。此時建立了兩個不一樣類型的集合:一個 Vector 和一個 Hashtable。而且在它們裏面分別填
充 Mouse 和 Hamster 對象,因爲 Enumeration 隱藏了基層集合的結構,因此PrintData 不知道或者不關心 Enumeration 來自於什麼類型的集合:
class PrintData { static void print(Enumeration e) { while (e.hasMoreElements()) System.out.println(e.nextElement().toString()); } } class Enumerators2 { public static void main(String[] args) { Vector v = new Vector(); for (int i = 0; i < 5; i++) v.addElement(new Mouse(i)); Hashtable h = new Hashtable(); for (int i = 0; i < 5; i++) h.put(new Integer(i), new Hamster(i)); System.out.println("Vector"); PrintData.print(v.elements()); System.out.println("Hashtable"); PrintData.print(h.elements()); } }
注意 PrintData.print()利用了這些集合中的對象屬於 Object 類這一事實,因此它調用了 toString()。但在
解決本身的實際問題時,常常都要保證本身的 Enumeration 穿越某種特定類型的集合。例如,可能要求集合
中的全部元素都是一個 Shape(幾何形狀),並含有 draw()方法。若出現這種狀況,必須從
Enumeration.nextElement()返回的 Object 進行下溯造型,以便產生一個 Shape。
編寫通用的排序代碼時,面臨的一個問題是必須根據對象的實際類型來執行比較運算,從而實現正確的排序。固然,一個辦法是爲每種不一樣的類型都寫一個不一樣的排序方法。然而,應認識到倘若這樣作,之後增長新類型時便不易實現代碼的重複利用。
程序設計一個主要的目標就是「將發生變化的東西同保持不變的東西分隔開」。在這裏,保持不變的代碼是通用的排序算法,而每次使用時都要變化的是對象的實際比較方法。所以,咱們不可將比較代碼「硬編碼」到多個不一樣的排序例程內,而是採用「回調」技術。
利用回調,常常發生變化的那部分代碼會封裝到它本身的類內,而老是保持相同的代碼則「回調」發生變化的代碼。這樣一來,不一樣的對象就能夠表達不一樣的比較方式,同時向它們傳遞相同的排序代碼。
下面這個「接口」( Interface)展現瞭如何比較兩個對象,它將那些「要發生變化的東西」封裝在內:
interface Compare { boolean lessThan(Object lhs, Object rhs); boolean lessThanOrEqual(Object lhs, Object rhs); }
對這兩種方法來講, lhs 表明本次比較中的「左手」對象,而 rhs 表明「右手」對象。
可建立 Vector 的一個子類,經過 Compare 實現「快速排序」。對於這種算法,包括它的速度以及原理等等
public class SortVector extends Vector { private Compare compare; // To hold the callback public SortVector(Compare comp) { compare = comp; } public void sort() { quickSort(0, size() - 1); } private void quickSort(int left, int right) { if (right > left) { Object o1 = elementAt(right); int i = left - 1; int j = right; while (true) { while (compare.lessThan(elementAt(++i), o1)) ; while (j > 0) if (compare.lessThanOrEqual(elementAt(--j), o1)) break; // out of while if (i >= j) break; swap(i, j); } swap(i, right); quickSort(left, i - 1); quickSort(i + 1, right); } } private void swap(int loc1, int loc2) { Object tmp = elementAt(loc1); setElementAt(elementAt(loc2), loc1); setElementAt(tmp, loc2); } }
爲使用 SortVector,必須建立一個類,令其爲咱們準備排序的對象實現 Compare。此時內部類並不顯得特別重要,但對於代碼的組織倒是有益的。下面是針對 String 對象的一個例子
public class StringSortTest { static class StringCompare implements Compare { public boolean lessThan(Object l, Object r) { return ((String) l).toLowerCase().compareTo( ((String) r).toLowerCase()) < 0; } public boolean lessThanOrEqual(Object l, Object r) { return ((String) l).toLowerCase().compareTo( ((String) r).toLowerCase()) <= 0; } } public static void main(String[] args) { SortVector sv = new SortVector(new StringCompare()); sv.addElement("d"); sv.addElement("A"); sv.addElement("C"); sv.addElement("c"); sv.addElement("b"); sv.addElement("B"); sv.addElement("D"); sv.addElement("a"); sv.sort(); Enumeration e = sv.elements(); while (e.hasMoreElements()) System.out.println(e.nextElement()); } }
一旦設置好框架,就能夠很是方便地重複使用象這樣的一個設計—— 只需簡單地寫一個類,將「須要發生變化」的東西封裝進去,而後將一個對象傳給SortVector 便可
繼承( extends)在這兒用於建立一種新類型的 Vector—— 也就是說, SortVector 屬於一種 Vector,並帶有一些附加的功能。繼承在這裏可發揮很大的做用,但了帶來了問題。它使一些方法具備了final 屬性,因此不能覆蓋它們。若是想建立一個排好序的 Vector,令其只接收和生成 String 對象,就會遇到麻煩。由於 addElement()和 elementAt()都具備 final 屬性,並且它們都是咱們必須覆蓋的方法,不然便沒法實現只能接收和產生 String 對象。
但在另外一方面,請考慮採用「合成」方法:將一個對象置入一個新類的內部。此時,不是改寫上述代碼來達到這個目的,而是在新類裏簡單地使用一個 SortVector。在這種狀況下,用於實現 Compare 接口的內部類就能夠「匿名」地建立
import java.util.*; public class StrSortVector { private SortVector v = new SortVector( // Anonymous inner class: new Compare() { public boolean lessThan(Object l, Object r) { return ((String) l).toLowerCase().compareTo( ((String) r).toLowerCase()) < 0; } public boolean lessThanOrEqual(Object l, Object r) { return ((String) l).toLowerCase().compareTo( ((String) r).toLowerCase()) <= 0; } }); private boolean sorted = false; public void addElement(String s) { v.addElement(s); sorted = false; } public String elementAt(int index) { if(!sorted) { v.sort();232 sorted = true; } return (String)v.elementAt(index); } public Enumeration elements() { if (!sorted) { v.sort(); sorted = true; } return v.elements(); } // Test it: public static void main(String[] args) { StrSortVector sv = new StrSortVector(); sv.addElement("d"); sv.addElement("A"); sv.addElement("C"); sv.addElement("c"); sv.addElement("b"); sv.addElement("B"); sv.addElement("D"); sv.addElement("a"); Enumeration e = sv.elements(); while (e.hasMoreElements()) System.out.println(e.nextElement()); } }
這張圖剛開始的時候可能讓人有點兒摸不着頭腦,相信你們會真正理解它實際只有三個集合組件: Map, List 和 Set。並且每一個組件實際只有兩、三種實現方式
虛線框表明「接口」,點線框表明「抽象」類,而實線框表明普通(實際)類。點線箭頭表示一個特定的類準備實現一個接口(在抽象類的狀況下,則是「部分」實現一個接口)。雙線箭頭表示一個類可生成箭頭指向的那個類的對象。
致力於容納對象的接口是 Collection, List, Set 和 Map。在傳統狀況下,咱們須要寫大量代碼才能同這些接口打交道。並且爲了指定本身想使用的準確類型,必須在建立之初進行設置。因此可能建立下面這樣的一
個 List:
List x = new LinkedList();
固然,也能夠決定將 x 做爲一個 LinkedList 使用(而不是一個普通的 List),並用 x 負載準確的類型信息。使用接口的好處就是一旦決定改變本身的實施細節,要作的所有事情就是在建立的時候改變它,就象下面這樣:
List x = new ArrayList();
在類的分級結構中,可看到大量以「 Abstract 」(抽象)開頭的類,這剛開始可能會令人感受迷惑。它們其實是一些工具,用於「部分」實現一個特定的接口。舉個例子來講,假如想生成本身的Set,就不是從 Set接口開始,而後自行實現全部方法。相反,咱們能夠從 AbstractSet 繼承,只需極少的工做便可獲得本身的新類。儘管如此,新集合庫仍然包含了足夠的功能,可知足咱們的幾乎全部需求。因此考慮到咱們的目的,可忽略全部以「 Abstract」開頭的類。
所以,在觀看這張示意圖時,真正須要關心的只有位於最頂部的「接口」以及普通(實際)類—— 均用實線方框包圍。一般須要生成實際類的一個對象,將其上溯造型爲對應的接口。之後便可在代碼的任何地方使用那個接口。下面是一個簡單的例子,它用 String 對象填充一個集合,而後打印出集合內的每個元素:
public class SimpleCollection { public static void main(String[] args) { Collection c = new ArrayList(); for (int i = 0; i < 10; i++) c.add(Integer.toString(i)); Iterator it = c.iterator(); while (it.hasNext()) System.out.println(it.next()); } }
main()的第一行建立了一個 ArrayList 對象,而後將其上溯造型成爲一個集合。因爲這個例子只使用了Collection 方法,因此從 Collection 繼承的一個類的任何對象均可以正常工做。但 ArrayList 是一個典型的 Collection,它代替了 Vector 的位置。
add()方法的做用是將一個新元素置入集合裏。然而,用戶文檔謹慎地指出 add()「保證這個集合包含了指定的元素」。這一點是爲 Set 做鋪墊的,後者只有在元素不存在的前提下才會真的加入那個元素。對於ArrayList 以及其餘任何形式的 List, add()確定意味着「直接加入」。
利用 iterator()方法,全部集合都能生成一個「反覆器」( Iterator)。反覆器其實就象一個「枚舉」( Enumeration),是後者的一個替代物,只是:
(1) 它採用了一個歷史上默認、並且早在 OOP 中獲得普遍採納的名字(反覆器)。
(2) 採用了比 Enumeration 更短的名字: hasNext()代替了 hasMoreElement(),而 next()代替了nextElement()。
(3) 添加了一個名爲 remove()的新方法,可刪除由 Iterator 生成的上一個元素。因此每次調用 next()的時候,只需調用 remove()一次
下面這張表格總結了用一個集合能作的全部事情(亦可對 Set 和 List 作一樣的事情,儘管 List 還提供了一
些額外的功能)。 Map 不是從 Collection 繼承的,因此要單獨對待
boolean add(Object) *保證集合內包含了自變量。若是它沒有添加自變量,就返回 false(假)
boolean addAll(Collection) *添加自變量內的全部元素。若是沒有添加元素,則返回 true(真)
void clear() *刪除集合內的全部元素
boolean contains(Object) 若集合包含自變量,就返回「真」
boolean containsAll(Collection) 若集合包含了自變量內的全部元素,就返回「真」
boolean isEmpty() 若集合內沒有元素,就返回「真」
Iterator iterator() 返回一個反覆器,以用它遍歷集合的各元素
boolean remove(Object) *如自變量在集合裏,就刪除那個元素的一個實例。若是已進行了刪除,就返回
「真」
boolean removeAll(Collection) *刪除自變量裏的全部元素。若是已進行了任何刪除,就返回「真」
boolean retainAll(Collection) *只保留包含在一個自變量裏的元素(一個理論的「交集」)。若是已進
行了任何改變,就返回「真」
int size() 返回集合內的元素數量
Object[] toArray() 返回包含了集合內全部元素的一個數組
*這是一個「可選的」方法,有的集合可能並未實現它。若確實如此,該方法就會遇到一個
UnsupportedOperatiionException,即一個「操做不支持」違例。
下面這個例子向你們演示了全部方法。一樣地,它們只對從集合繼承的東西有效,一個ArrayList 做爲一種「不經常使用的分母」使用
public class Collection1 { // Fill with 'size' elements, start // counting at 'start': public static Collection fill(Collection c, int start, int size) { for (int i = start; i < start + size; i++) c.add(Integer.toString(i)); return c; } // Default to a "start" of 0: public static Collection fill(Collection c, int size) { return fill(c, 0, size); } // Default to 10 elements: public static Collection fill(Collection c) { return fill(c, 0, 10); } // Create & upcast to Collection: public static Collection newCollection() { return fill(new ArrayList()); // ArrayList is used for simplicity, but it's // only seen as a generic Collection // everywhere else in the program. } // Fill a Collection with a range of values: public static Collection newCollection(int start, int size) { return fill(new ArrayList(), start, size); } // Moving through a List with an iterator: public static void print(Collection c) { for (Iterator x = c.iterator(); x.hasNext();) System.out.print(x.next() + " "); System.out.println(); } public static void main(String[] args) { Collection c = newCollection(); c.add("ten"); c.add("eleven"); print(c); // Make an array from the List: Object[] array = c.toArray(); // Make a String array from the List: String[] str = (String[]) c.toArray(new String[1]); // Find max and min elements; this means // different things depending on the way // the Comparable interface is implemented: System.out.println("Collections.max(c) = " + Collections.max(c)); System.out.println("Collections.min(c) = " + Collections.min(c)); // Add a Collection to another Collection c.addAll(newCollection()); print(c); c.remove("3"); // Removes the first one print(c); c.remove("3"); // Removes the second one print(c); // Remove all components that are in the // argument collection: c.removeAll(newCollection()); print(c); c.addAll(newCollection()); print(c); // Is an element in this Collection? System.out.println("c.contains(\"4\") = " + c.contains("4")); // Is a Collection in this Collection? System.out.println("c.containsAll(newCollection()) = " + c.containsAll(newCollection())); Collection c2 = newCollection(5, 3); // Keep all the elements that are in both // c and c2 (an intersection of sets): c.retainAll(c2); print(c); // Throw away all the elements in c that // also appear in c2: c.removeAll(c2); System.out.println("c.isEmpty() = " + c.isEmpty()); c = newCollection(); print(c); c.clear(); // Remove all elements System.out.println("after c.clear():"); print(c); } }
newCollection()的兩個版本都建立了 ArrayList,用於包含不一樣的數據集,並將它們做爲集合對象返回。因此很明顯,除了 Collection 接口以外,不會再用到其餘什麼。
List(接口) 順序是 List 最重要的特性;它可保證元素按照規定的順序排列。 List 爲 Collection 添加了大量方法,以便咱們在 List 中部插入和刪除元素(只推薦對 LinkedList 這樣作)。 List 也會生成一個ListIterator(列表反覆器),利用它可在一個列表裏朝兩個方向遍歷,同時插入和刪除位於列表中部的元素(一樣地,只建議對 LinkedList 這樣作)
ArrayList 由一個數組後推獲得的 List。做爲一個常規用途的對象容器使用,用於替換原先的 Vector。容許咱們快速訪問元素,但在從列表中部插入和刪除元素時,速度卻嫌稍慢。通常只應該用ListIterator 對一個 ArrayList 進行向前和向後遍歷,不要用它刪除和插入元素;與 LinkedList 相比,它的效率要低許多LinkedList 提供優化的順序訪問性能,同時能夠高效率地在列表中部進行插入和刪除操做。但在進行隨機訪問時,速度卻至關慢,此時應換用 ArrayList。
也提供了 addFirst(), addLast(), getFirst(),getLast(), removeFirst() 以及 removeLast()
(未在任何接口或基礎類中定義),以便將其做爲一個規格、隊列以及一個雙向隊列使用
public class List1 { // Wrap Collection1.fill() for convenience: public static List fill(List a) { return (List) Collection1.fill(a); } // You can use an Iterator, just as with a // Collection, but you can also use random // access with get(): public static void print(List a) { for (int i = 0; i < a.size(); i++) System.out.print(a.get(i) + " "); System.out.println(); } static boolean b; static Object o; static int i; static Iterator it; static ListIterator lit; public static void basicTest(List a) { a.add(1, "x"); // Add at location 1 a.add("x"); // Add at end // Add a collection: a.addAll(fill(new ArrayList())); // Add a collection starting at location 3: a.addAll(3, fill(new ArrayList())); b = a.contains("1"); // Is it in there? // Is the entire collection in there? b = a.containsAll(fill(new ArrayList())); // Lists allow random access, which is cheap // for ArrayList, expensive for LinkedList: o = a.get(1); // Get object at location 1 i = a.indexOf("1"); // Tell index of object // indexOf, starting search at location 2: i = a.indexOf("1", 2); b = a.isEmpty(); // Any elements inside? it = a.iterator(); // Ordinary Iterator lit = a.listIterator(); // ListIterator lit = a.listIterator(3); // Start at loc 3 i = a.lastIndexOf("1"); // Last match i = a.lastIndexOf("1", 2); // ...after loc 2 a.remove(1); // Remove location 1 a.remove("3"); // Remove this object a.set(1, "y"); // Set location 1 to "y" // Keep everything that's in the argument // (the intersection of the two sets): a.retainAll(fill(new ArrayList())); // Remove elements in this range: a.removeRange(0, 2); // Remove everything that's in the argument: a.removeAll(fill(new ArrayList())); i = a.size(); // How big is it? a.clear(); // Remove all elements } public static void iterMotion(List a) { ListIterator it = a.listIterator(); b = it.hasNext(); b = it.hasPrevious(); o = it.next(); i = it.nextIndex(); o = it.previous(); i = it.previousIndex(); } public static void iterManipulation(List a) { ListIterator it = a.listIterator(); it.add("47"); // Must move to an element after add(): it.next(); // Remove the element that was just produced: it.remove(); // Must move to an element after remove(): it.next(); // Change the element that was just produced: it.set("47"); } public static void testVisual(List a) { print(a); List b = new ArrayList(); fill(b); System.out.print("b = "); print(b); a.addAll(b); a.addAll(fill(new ArrayList())); print(a); // Shrink the list by removing all the // elements beyond the first 1/2 of the list System.out.println(a.size()); System.out.println(a.size() / 2); a.removeRange(a.size() / 2, a.size() / 2 + 2); print(a); // Insert, remove, and replace elements // using a ListIterator: ListIterator x = a.listIterator(a.size() / 2); x.add("one"); print(a); System.out.println(x.next()); x.remove(); System.out.println(x.next()); x.set("47"); print(a); // Traverse the list backwards: x = a.listIterator(a.size()); while (x.hasPrevious()) System.out.print(x.previous() + " "); System.out.println(); System.out.println("testVisual finished"); } // There are some things that only // LinkedLists can do: public static void testLinkedList() { LinkedList ll = new LinkedList(); Collection1.fill(ll, 5); print(ll); // Treat it like a stack, pushing: ll.addFirst("one"); ll.addFirst("two"); print(ll); // Like "peeking" at the top of a stack: System.out.println(ll.getFirst()); // Like popping a stack: System.out.println(ll.removeFirst()); System.out.println(ll.removeFirst()); // Treat it like a queue, pulling elements // off the tail end: System.out.println(ll.removeLast()); // With the above operations, it's a dequeue! print(ll); } public static void main(String args[]) { // Make and fill a new list each time: basicTest(fill(new LinkedList())); basicTest(fill(new ArrayList())); iterMotion(fill(new LinkedList())); iterMotion(fill(new ArrayList())); iterManipulation(fill(new LinkedList())); iterManipulation(fill(new ArrayList())); testVisual(fill(new LinkedList())); testLinkedList(); } }
在 basicTest()和 iterMotiion() 中,只是簡單地發出調用,以便揭示出正確的語法。並且儘管捕獲了返回
值,可是並未使用它。在某些狀況下,之因此不捕獲返回值,是因爲它們沒有什麼特別的用處。在正式使用
它們前,應仔細研究一下本身的聯機文檔,掌握這些方法完整、正確的用法。
import java.awt.List; import java.util.ArrayList; import java.util.Iterator; /** * @author sihai * @time 2018/4/19 * ArrayList用法示例說明 * */ public class Main { public static void main(String[] args) { //ArrayList用法示例 ArrayList<String> m_ArrayList=new ArrayList<String>(); m_ArrayList.add("Evankaka"); m_ArrayList.add("sihai"); m_ArrayList.add("德德"); m_ArrayList.add("Evankaka"); m_ArrayList.add("小紅"); m_ArrayList.set(2,"sihai2");// 將索引位置爲2的對象修改 m_ArrayList.add(3,"好好學java");// 將對象添加到索引位置爲3的位置 //ArrayList遍歷方法1 Iterator<String> it_ArrayList = m_ArrayList.iterator(); System.out.println("ArrayList遍歷方法1"); while (it_ArrayList.hasNext()) { System.out.println(it_ArrayList.next()); } //ArrayList遍歷方法2 System.out.println("ArrayList遍歷方法2"); for(Object o:m_ArrayList){ System.out.println(o); } //ArrayList遍歷方法2 System.out.println("ArrayList遍歷方法3"); for(int i = 0; i<m_ArrayList.size(); i++){ System.out.println(m_ArrayList.get(i)); } //刪除元素 m_ArrayList.remove("Evankaka"); it_ArrayList = m_ArrayList.iterator(); System.out.println("ArrayList刪除元素後的遍歷"); while (it_ArrayList.hasNext()) { String m_String=it_ArrayList.next(); if(m_String.equals("好好學java")){ it_ArrayList.remove(); }else{ System.out.println(m_String); } } } }
輸出結果:
ArrayList遍歷方法1
Evankaka
sihai
sihai2
好好學java
Evankaka
小紅
ArrayList遍歷方法2
Evankaka
sihai
sihai2
好好學java
Evankaka
小紅
ArrayList遍歷方法3
Evankaka
sihai
sihai2
好好學java
Evankaka
小紅
ArrayList刪除元素後的遍歷
sihai
sihai2
Evankaka
小紅
(1)使用Iterator迭代集合過程當中,不可修改集合元素,不然會引起異常。而且Iterator只能向後迭代
(2)若是你想在循環過程當中去掉某個元素,只能調用it.remove方法, 不能使用list.remove方法, 不然必定出併發訪問的錯誤.
Set徹底就是一個 Collection,只是具備不一樣的行爲(這是實例和多形性最理想的應用:用於表達不一樣的行爲)。在這裏,一個 Set 只容許每一個對象存在一個實例(正如你們之後會看到的那樣,一個對象的「值」的構成是至關複雜的)
Set(接口) 添加到 Set 的每一個元素都必須是獨一無二的;不然 Set 就不會添加劇復的元素。添加到 Set 裏的對象必須定義 equals(),從而創建對象的惟一性。 Set 擁有與 Collection 徹底相同的接口。一個 Set 不能保證本身可按任何特定的順序維持本身的元素
HashSet 用於除很是小的之外的全部 Set。對象也必須定義 hashCode()
ArraySet 由一個數組後推獲得的 Set。面向很是小的 Set 設計,特別是那些須要頻繁建立和刪除的。對於小
Set,與 HashSet 相比, ArraySet 建立和反覆所需付出的代價都要小得多。但隨着 Set 的增大,它的性能也
會大打折扣。不須要 HashCode()
TreeSet 由一個「紅黑樹」後推獲得的順序 Set(註釋⑦)。這樣一來,咱們就能夠從一個 Set 裏提到一個
順序集合
public class Set1 { public static void testVisual(Set a) { Collection1.fill(a); Collection1.fill(a); Collection1.fill(a); Collection1.print(a); // No duplicates! // Add another set to this one: a.addAll(a); a.add("one"); a.add("one"); a.add("one"); Collection1.print(a); // Look something up: System.out.println("a.contains(\"one\"): " + a.contains("one")); } public static void main(String[] args) { testVisual(new HashSet()); testVisual(new TreeSet()); } }
重複的值被添加到 Set,但在打印的時候,咱們會發現 Set 只接受每一個值的一個實例。運行這個程序時,會注意到由 HashSet 維持的順序與 ArraySet 是不一樣的。這是因爲它們採用了不一樣的方法來保存元素,以便它們之後的定位。 ArraySet 保持着它們的順序狀態,而 HashSet 使用一個散列函數,這是特別爲快速檢索設計的)。
class MyType implements Comparable { private int i; public MyType(int n) { i = n; } public boolean equals(Object o) { return (o instanceof MyType) && (i == ((MyType) o).i); } public int hashCode() { return i; } public String toString() { return i + " "; } public int compareTo(Object o) { int i2 = ((MyType) o).i; return (i2 < i ? -1 : (i2 == i ? 0 : 1)); } } public class Set2 { public static Set fill(Set a, int size) { for (int i = 0; i < size; i++) a.add(new MyType(i)); return a; } public static Set fill(Set a) { return fill(a, 10); } public static void test(Set a) { fill(a); fill(a); // Try to add duplicates fill(a); a.addAll(fill(new TreeSet())); System.out.println(a); } public static void main(String[] args) { test(new HashSet()); test(new TreeSet()); } }
但只有要把類置入一個 HashSet 的前提下,纔有必要使用 hashCode()—— 這種狀況是徹底有可能的,由於一般應先選擇做爲一個 Set 實現。
Map(接口) 維持「鍵-值」對應關係(對),以便經過一個鍵查找相應的值
HashMap 基於一個散列表實現(用它代替 Hashtable)。針對「鍵-值」對的插入和檢索,這種形式具備最穩定的性能。可經過構建器對這一性能進行調整,以便設置散列表的「能力」和「裝載因子」
ArrayMap 由一個 ArrayList 後推獲得的 Map。對反覆的順序提供了精確的控制。面向很是小的 Map 設計,特別是那些須要常常建立和刪除的。對於很是小的Map,建立和反覆所付出的代價要比
HashMap 低得多。但在Map 變大之後,性能也會相應地大幅度下降
TreeMap 在一個「紅-黑」樹的基礎上實現。查看鍵或者「鍵-值」對時,它們會按固定的順序排列(取決於 Comparable 或 Comparator,稍後即會講到)。 TreeMap 最大的好處就是咱們獲得的是已排好序的結果。TreeMap 是含有 subMap()方法的惟一一種 Map,利用它能夠返回樹的一部分
public class Map1 { public final static String[][] testData1 = { { "Happy", "Cheerful disposition" }, { "Sleepy", "Prefers dark, quiet places" }, { "Grumpy", "Needs to work on attitude" }, { "Doc", "Fantasizes about advanced degree" }, { "Dopey", "'A' for effort" }, { "Sneezy", "Struggles with allergies" }, { "Bashful", "Needs self-esteem workshop" }, }; public final static String[][] testData2 = { { "Belligerent", "Disruptive influence" }, { "Lazy", "Motivational problems" }, { "Comatose", "Excellent behavior" } }; public static Map fill(Map m, Object[][] o) { for (int i = 0; i < o.length; i++) m.put(o[i][0], o[i][1]); return m; } // Producing a Set of the keys: public static void printKeys(Map m) { System.out.print("Size = " + m.size() + ", "); System.out.print("Keys: "); Collection1.print(m.keySet()); } // Producing a Collection of the values: public static void printValues(Map m) { System.out.print("Values: "); Collection1.print(m.values()); } // Iterating through Map.Entry objects (pairs): public static void print(Map m) { Collection entries = m.entries(); Iterator it = entries.iterator(); while (it.hasNext()) { Map.Entry e = (Map.Entry) it.next(); System.out.println("Key = " + e.getKey() + ", Value = " + e.getValue()); } } public static void test(Map m) { fill(m, testData1); // Map has 'Set' behavior for keys: fill(m, testData1); printKeys(m); printValues(m); print(m); String key = testData1[4][0]; String value = testData1[4][1]; System.out.println("m.containsKey(\"" + key + "\"): " + m.containsKey(key)); System.out.println("m.get(\"" + key + "\"): " + m.get(key)); System.out.println("m.containsValue(\"" + value + "\"): " + m.containsValue(value)); Map m2 = fill(new TreeMap(), testData2); m.putAll(m2); printKeys(m); m.remove(testData2[0][0]); printKeys(m); m.clear(); System.out.println("m.isEmpty(): " + m.isEmpty()); fill(m, testData1); // Operations on the Set change the Map: m.keySet().removeAll(m.keySet()); System.out.println("m.isEmpty(): " + m.isEmpty()); } public static void main(String args[]) { System.out.println("Testing HashMap"); test(new HashMap()); System.out.println("Testing TreeMap"); test(new TreeMap()); } }
package com.test; import java.util.HashMap; import java.util.Iterator; import java.util.Map; public class Test { public static void main(String[] args) { Map<String, String> map = new HashMap<String, String>(); map.put("first", "linlin"); map.put("second", "好好學java"); map.put("third", "sihai"); map.put("first", "sihai2"); // 第一種:經過Map.keySet遍歷key和value System.out.println("===================經過Map.keySet遍歷key和value:==================="); for (String key : map.keySet()) { System.out.println("key= " + key + " and value= " + map.get(key)); } // 第二種:經過Map.entrySet使用iterator遍歷key和value System.out.println("===================經過Map.entrySet使用iterator遍歷key和value:==================="); Iterator<Map.Entry<String, String>> it = map.entrySet().iterator(); while (it.hasNext()) { Map.Entry<String, String> entry = it.next(); System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue()); } // 第三種:經過Map.entrySet遍歷key和value System.out.println("===================經過Map.entrySet遍歷key和value:==================="); for (Map.Entry<String, String> entry : map.entrySet()) { System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue()); } // 第四種:經過Map.values()遍歷全部的value,可是不能遍歷鍵key System.out.println("===================經過Map.values()遍歷全部的value:==================="); for (String v : map.values()) { System.out.println("value= " + v); } } }
輸出結果以下:
===================經過Map.keySet遍歷key和value:===================
key= third and value= sihai
key= first and value= sihai2
key= second and value= 好好學java
===================經過Map.entrySet使用iterator遍歷key和value:===================
key= third and value= sihai
key= first and value= sihai2
key= second and value= 好好學java
===================經過Map.entrySet遍歷key和value:===================
key= third and value= sihai
key= first and value= sihai2
key= second and value= 好好學java
===================經過Map.values()遍歷全部的value:===================
value= sihai
value= sihai2
value= 好好學java
ArrayList, LinkedList 以及 Vector(大體等價於 ArrayList)都實現了List 接口,因此不管選用哪個,咱們的程序都會獲得相似的結果。然而, ArrayList(以及 Vector)是由一個數組後推獲得的;而 LinkedList 是根據常規的雙重連接列表方式實現的,由於每一個單獨的對象都包含了數據以及指向列表內先後元素的句柄。正是因爲這個緣由,假如想在一個列表中部進行大量插入和刪除操做,那麼 LinkedList 無疑是最恰當的選擇( LinkedList 還有一些額外的功能,創建於AbstractSequentialList 中)。若非如此,就情願選擇 ArrayList,它的速度可能要快一些。
做爲另外一個例子, Set 既可做爲一個 ArraySet 實現,亦可做爲 HashSet 實現。 ArraySet 是由一個 ArrayList
後推獲得的,設計成只支持少許元素,特別適合要求建立和刪除大量 Set 對象的場合使用。然而,一旦須要在本身的 Set 中容納大量元素, ArraySet 的性能就會大打折扣。寫一個須要 Set 的程序時,應默認選擇HashSet。並且只有在某些特殊狀況下(對性能的提高有迫切的需求),才應切換到 ArraySet。
爲體會各類 List 實施方案間的差別,最簡便的方法就是進行一次性能測驗。
public class ListPerformance { private static final int REPS = 100; private abstract static class Tester { String name; int size; // Test quantity Tester(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 不遲。
可在 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
選擇不一樣的 Map 實施方案時,注意 Map 的大小對於性能的影響是最大的,下面這個測試程序清楚地闡示了這
一點:
public class MapPerformance { private static final int REPS = 200; public static Map fill(Map m, int size) { for (int i = 0; i < size; i++) { String x = Integer.toString(i); m.put(x, x); } return m; } private abstract static class Tester { String name; Tester(String name) { this.name = name; } abstract void test(Map m, int size); } private static Tester[] tests = { new Tester("put") { void test(Map m, int size) { for (int i = 0; i < REPS; i++) { m.clear(); fill(m, size); } } }, new Tester("get") { void test(Map m, int size) { for (int i = 0; i < REPS; i++) for (int j = 0; j < size; j++) m.get(Integer.toString(j)); } }, new Tester("iteration") { void test(Map m, int size) { for (int i = 0; i < REPS * 10; i++) { Iterator it = m.entries().iterator(); while (it.hasNext()) it.next(); } } }, }; public static void test(Map m, int size) { // A trick to print out the class name: System.out.println("Testing " + m.getClass().getName() + " size " + size); fill(m, size); for (int i = 0; i < tests.length; i++) { System.out.print(tests[i].name); long t1 = System.currentTimeMillis(); tests[i].test(m, size); long t2 = System.currentTimeMillis(); System.out.println(": " + ((double) (t2 - t1) / (double) size)); } } public static void main(String[] args) { // Small: test(new Hashtable(), 10); test(new HashMap(), 10); test(new TreeMap(), 10); // Medium: test(new Hashtable(), 100); test(new HashMap(), 100); test(new TreeMap(), 100); // Large: test(new HashMap(), 1000); test(new Hashtable(), 1000); test(new TreeMap(), 1000); } }
因爲 Map 的大小是最嚴重的問題,因此程序的計時測試按Map 的大小(或容量)來分割時間,以便獲得使人
信服的測試結果。下面列出一系列結果(在你的機器上可能不一樣):
即便大小爲 10, ArrayMap 的性能也要比 HashMap 差—— 除反覆循環時之外。而在使用 Map 時,反覆的做用一般並不重要( get()一般是咱們時間花得最多的地方)。 TreeMap 提供了出色的 put()以及反覆時間,但 get()的性能並不佳。可是,咱們爲何仍然須要使用TreeMap 呢?這樣一來,咱們能夠不把它做爲 Map 使用,而做爲建立順序列表的一種途徑。一旦填充了一個 TreeMap,就能夠調用 keySet()來得到鍵的一個 Set「景象」。而後用 toArray()產生包含了那些鍵的一個數組。隨後,可用 static 方法 Array.binarySearch()快速查找排好序的數組中的內容。固然,也許只有在 HashMap 的行爲不可接受的時候,才須要採用這種作法。由於HashMap 的設計宗旨就是進行快速的檢索操做。最後,當咱們使用 Map 時,首要的選擇應該是 HashMap。只有在極少數狀況下才須要考慮其餘方法
public class MapCreation { public static void main(String[] args) { final long REPS = 100000; long t1 = System.currentTimeMillis(); System.out.print("Hashtable"); for (long i = 0; i < REPS; i++) new Hashtable(); long t2 = System.currentTimeMillis(); System.out.println(": " + (t2 - t1)); t1 = System.currentTimeMillis(); System.out.print("TreeMap"); for (long i = 0; i < REPS; i++) new TreeMap(); t2 = System.currentTimeMillis(); System.out.println(": " + (t2 - t1)); t1 = System.currentTimeMillis(); System.out.print("HashMap"); for (long i = 0; i < REPS; i++) new HashMap(); t2 = System.currentTimeMillis(); System.out.println(": " + (t2 - t1)); } }
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); // Iteration System.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 operation a.clear(); // Unsupported a.add("eleven"); // Unsupported a.addAll(a2); // Unsupported a.retainAll(a2); // Unsupported a.remove(s[0]); // Unsupported a.removeAll(a2); // Unsupported } }
從中能夠看出,實際只實現了 Collection 和 List 接口的一部分。剩餘的方法致使了不受歡迎的一種狀況,名爲UnsupportedOperationException。
在實現那些接口的集合類中,或者提供、或者沒有提供對那些方法的支持。若調用一個未獲支持的方法,就會致使一個 UnsupportedOperationException(操做未支持違例),這代表出現了一個編程錯誤。
Arrays.toList()產生了一個 List(列表),該列表是由一個固定長度的數組後推出來的。所以惟一可以支持的就是那些不改變數組長度的操做。在另外一方面,若請求一個新接口表達不一樣種類的行爲(可能叫做「 FixedSizeList」 —— 固定長度列表),就有遭遇更大的複雜程度的危險。這樣一來,之後試圖使用庫的時候,很快就會發現本身不知從何處下手。
對那些採用 Collection, List, Set 或者 Map 做爲參數的方法,它們的文檔應當指出哪些可選的方法是必須實現的。舉個例子來講,排序要求實現 set()和 Iterator.set()方法,但不包括 add()和 remove()。
Arrays類爲全部基本數據類型的數組提供了一個過載的 sort()和 binarySearch()
,它們亦可用於 String 和Object。
public class Array1 { static Random r = new Random(); static String ssource = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; static char[] src = ssource.toCharArray(); // Create a random String public static String randString(int length) { char[] buf = new char[length]; int rnd; for (int i = 0; i < length; i++) { rnd = Math.abs(r.nextInt()) % src.length; buf[i] = src[rnd]; } return new String(buf); } // Create a random array of Strings: public static String[] randStrings(int length, int size) { String[] s = new String[size]; for (int i = 0; i < size; i++) s[i] = randString(length); return s; } public static void print(byte[] b) { for (int i = 0; i < b.length; i++) System.out.print(b[i] + " "); System.out.println(); } public static void print(String[] s) { for (int i = 0; i < s.length; i++) System.out.print(s[i] + " "); System.out.println(); } public static void main(String[] args) { byte[] b = new byte[15]; r.nextBytes(b); // Fill with random bytes print(b); Arrays.sort(b); print(b); int loc = Arrays.binarySearch(b, b[10]); System.out.println("Location of " + b[10] + " = " + loc); // Test String sort & search: String[] s = randStrings(4, 10); print(s); Arrays.sort(s); print(s); loc = Arrays.binarySearch(s, s[4]); System.out.println("Location of " + s[4] + " = " + loc); } }
在 main()中, Random.nextBytes()
用隨機選擇的字節填充數組自變量(沒有對應的Random 方法用於建立其餘基本數據類型的數組)。得到一個數組後,即可發現爲了執行 sort()或者 binarySearch(),只需發出一次方法調用便可。與 binarySearch()有關的還有一個重要的警告:若在執行一次 binarySearch()以前不調用 sort(),便會發生不可預測的行爲,其中甚至包括無限循環。
對 String 的排序以及搜索是類似的,但在運行程序的時候,咱們會注意到一個有趣的現象:排序遵照的是字典順序,亦即大寫字母在字符集中位於小寫字母的前面。所以,全部大寫字母都位於列表的最前面,後面再跟上小寫字母—— Z 竟然位於 a 的前面。彷佛連電話簿也是這樣排序的。
public class CompClass implements Comparable { private int i; public CompClass(int ii) { i = ii; } public int compareTo(Object o) { // Implicitly tests for correct type:258 int argi = ((CompClass) o).i; if (i == argi) return 0; if (i < argi) return -1; return 1; } public static void print(Object[] a) { for (int i = 0; i < a.length; i++) System.out.print(a[i] + " "); System.out.println(); } public String toString() { return i + ""; } public static void main(String[] args) { CompClass[] a = new CompClass[20]; for (int i = 0; i < a.length; i++) a[i] = new CompClass((int) (Math.random() * 100)); print(a); Arrays.sort(a); print(a); int loc = Arrays.binarySearch(a, a[3]); System.out.println("Location of " + a[3] + " = " + loc); } }
public class ListSort { public static void main(String[] args) { final int SZ = 20; // Using "natural comparison method": List a = new ArrayList(); for(int i = 0; i < SZ; i++) a.add(new CompClass( (int)(Math.random() *100))); Collection1.print(a); Collections.sort(a); Collection1.print(a); Object find = a.get(SZ/2);259 int loc = Collections.binarySearch(a, find); System.out.println("Location of " + find + " = " + loc); // Using a Comparator: List b = new ArrayList(); for(int i = 0; i < SZ; i++) b.add(Array1.randString(4)); Collection1.print(b); AlphaComp ac = new AlphaComp(); Collections.sort(b, ac); Collection1.print(b); find = b.get(SZ/2); // Must use the Comparator to search, also: loc = Collections.binarySearch(b, find, ac); System.out.println("Location of " + find + " = " + loc); } }
這些方法的用法與在 Arrays 中的用法是徹底一致的,只是用一個列表代替了數組。
TreeMap 也必須根據 Comparable 或者 Comparator 對本身的對象進行排序
Collections 類中的實用工具:
enumeration(Collection) 爲自變量產生原始風格的 Enumeration(枚舉)
max(Collection), min(Collection) 在自變量中用集合內對象的天然比較方法產生最大或最小元素
max(Collection,Comparator), min(Collection,Comparator) 在集合內用比較器產生最大或最小元素
nCopies(int n, Object o) 返回長度爲 n 的一個不可變列表,它的全部句柄均指向 o
subList(List,int min,int max) 返回由指定參數列表後推獲得的一個新列表。可將這個列表想象成一個
「窗口」,它自索引爲 min 的地方開始,正好結束於 max 的前面
注意 min()和 max()都是隨同 Collection 對象工做的,而非隨同 List,因此沒必要擔憂 Collection 是否須要排序(就象早先指出的那樣,在執行一次 binarySearch()—— 即二進制搜索—— 以前,必須對一個 List 或者一個數組執行 sort())
一般,建立 Collection 或 Map 的一個「只讀」版本顯得更有利一些。 Collections 類容許咱們達到這個目標,方法是將原始容器傳遞進入一個方法,並令其傳回一個只讀版本。這個方法共有四種變化形式,分別用於 Collection(若是不想把集合看成一種更特殊的類型對待)、 List、 Set 以及 Map。
public class ReadOnly { public static void main(String[] args) { Collection c = new ArrayList(); Collection1.fill(c); // Insert useful data c = Collections.unmodifiableCollection(c); Collection1.print(c); // Reading is OK // ! c.add("one"); // Can't change it List a = new ArrayList(); Collection1.fill(a); a = Collections.unmodifiableList(a); ListIterator lit = a.listIterator(); System.out.println(lit.next()); // Reading OK // ! lit.add("one"); // Can't change it Set s = new HashSet(); Collection1.fill(s); s = Collections.unmodifiableSet(s); Collection1.print(s); // Reading OK // ! s.add("one"); // Can't change it Map m = new HashMap(); Map1.fill(m, Map1.testData1); m = Collections.unmodifiableMap(m); Map1.print(m); // Reading OK // ! m.put("Ralph", "Howdy!"); } }
對於每種狀況,在將其正式變爲只讀之前,都必須用有有效的數據填充容器。一旦載入成功,最佳的作法就是用「不可修改」調用產生的句柄替換現有的句柄。這樣作可有效避免將其變成不可修改後不慎改變其中的內容。
在另外一方面,該工具也容許咱們在一個類中將可以修改的容器保持爲private 狀態,並可從一個方法調用中返回指向那個容器的一個只讀句柄。這樣一來,雖然咱們可在類裏修改它,但其餘任何人都只能讀。
爲特定類型調用「不可修改」的方法不會形成編譯期間的檢查,但一旦發生任何變化,對修改特定容器的方法的調用便會產生一個 UnsupportedOperationException 違例。
在這兒,你們只需注意到 Collections 類提供了對整個容器進行自動同步的一種途徑。它的語法與「不可修改」的方法是相似的:
public class Synchronization { public static void main(String[] args) { Collection c = Collections.synchronizedCollection(new ArrayList()); List list = Collections.synchronizedList(new ArrayList()); Set s = Collections.synchronizedSet(new HashSet()); Map m = Collections.synchronizedMap(new HashMap()); } }
(1) 數組包含了對象的數字化索引。它容納的是一種已知類型的對象,因此在查找一個對象時,沒必要對結果進行造型處理。數組能夠是多維的,並且可以容納基本數據類型。可是,一旦把它建立好之後,大小便不能變化了。
(2) Vector(矢量)也包含了對象的數字索引—— 可將數組和 Vector 想象成隨機訪問集合。當咱們加入更多的元素時, Vector 可以自動改變自身的大小。但 Vector 只能容納對象的句柄,因此它不可包含基本數據類型;並且將一個對象句柄從集合中取出來的時候,必須對結果進行造型處理。
(3) Hashtable(散列表)屬於 Dictionary(字典)的一種類型,是一種將對象(而不是數字)同其餘對象關聯到一塊兒的方式。散列表也支持對對象的隨機訪問,事實上,它的整個設計方案都在突出訪問的「高速度」。
(4) Stack(堆棧)是一種「後入先出」( LIFO)的隊列
對於 Hashtable,可將任何東西置入其中,並以很是快的速度檢索;對於 Enumeration(枚舉),可遍歷一個序列,並對其中的每一個元素都採起一個特定的操做。那是一種功能足夠強勁的工具。
但 Hashtable 沒有「順序」的概念。 Vector 和數組爲咱們提供了一種線性順序,但若要把一個元素插入它們任何一個的中部,通常都要付出「慘重」的代價。除此之外,隊列、拆散隊列、優先級隊列以及樹都涉及到元素的「排序」 —— 並不是僅僅將它們置入,以便之後能按線性順序查找或移動它們。
集(Set):集裏的對象不按任何特定的方式排列,按索引值來操做數據,不能有重複的元素
列表(List):序列中的對象以線性方式存儲,按索引值來操做數據,能夠有重複的元素
映射(Map):映射的每一項爲「名稱—數值」對,名稱不能夠重複,值能夠重複,一個名稱對應一個惟一的值
迭代器是按次序一個一個地獲取集合中全部的對象,是訪問集合中每一個元素的標準機制。
迭代器的建立:Collection接口的iterator()方法返回一個Iterator
Iterator it=test.iterator(); //將test集合對象轉爲迭代器
迭代器的經常使用方法:
hasNext() //判斷迭代器中是否有下一個元素
next() //返回迭代的下一個元素
Remove() //將迭代器新返回的元素刪除
public interface Iterator { boolean hasNext(); Object next(); void remove(); // Optional }
在調用remove()方法的時候, 必須調用一次next()方法.
remove()方法其實是刪除上一個返回的元素.
void add(int index, Object element) :添加對象element到位置index上
boolean addAll(int index, Collection collection) :在index位置後添加容器collection中全部的元素
Object get(int index) :取出下標爲index的位置的元素
int indexOf(Object element) :查找對象element 在List中第一次出現的位置
int lastIndexOf(Object element) :查找對象element 在List中最後出現的位置
Object remove(int index) :刪除index位置上的元素
ListIterator listIterator(int startIndex) :返回一個ListIterator 跌代器,開始位置爲startIndex
List subList(int fromIndex, int toIndex) :返回一個子列表List ,元素存放爲從 fromIndex 到toIndex以前的一個元素
能夠將其看做是可以自動增加容量的數組。
利用ArrayList的toArray()返回一個數組。
Arrays.asList()返回一個列表。
迭代器(Iterator) 給咱們提供了一種通用的方式來訪問集合中的元素。
ArrayList能夠自動擴展容量
ArrayList.ensureCapacity(int minCapacity)
首先獲得當前elementData 屬性的長度oldCapacity。
而後經過判斷oldCapacity和minCapacity參數誰大來決定是否須要擴容, 若是minCapacity大於 oldCapacity,那麼咱們就對當前的List對象進行擴容。
擴容的的策略爲:取(oldCapacity * 3)/2 + 1和minCapacity之間更大的那個。而後使用數組拷 貝的方法,把之前存放的數據轉移到新的數組對象中若是minCapacity不大於oldCapacity那麼就不進行擴容。
LinkedList是採用雙向循環鏈表實現的。
利用LinkedList能夠實現棧(stack)、隊列(queue)、雙向隊列(double-ended queue )。
它具備方法addFirst()、addLast()、getFirst()、getLast()、removeFirst()、removeLast()等。
1.ArrayList是實現了基於動態數組的數據結構,LinkedList基於鏈表的數據結構。
2.對於隨機訪問get和set,ArrayList以爲優於LinkedList,由於LinkedList要移動指針。
3.對於新增和刪除操做add和remove,LinedList比較佔優點,由於ArrayList要移動數據。
儘可能避免同時遍歷和刪除集合。由於這會改變集合的大小;
for( Iterator<ComType> iter = ComList.iterator(); iter.hasNext();){ ComType com = iter.next(); if ( !com.getName().contains("abc")){ ComList.remove(com);} }
推薦:
for( Iterator<ComType> iter = ComList.iterator(); iter.hasNext();){ ComType com = iter.next(); if ( !com.getName().contains("abc")){ iter.remove(com); }
無限制的在lst中add element,勢必會形成lst佔用內存太高
經常使用方法:
Object put(Object key,Object value):用來存放一個鍵-值對Map中
Object remove(Object key):根據key(鍵),移除鍵-值對,並將值返回
void putAll(Map mapping) :將另一個Map中的元素存入當前的Map中
void clear() :清空當前Map中的元素
Object get(Object key) :根據key(鍵)取得對應的值
boolean containsKey(Object key) :判斷Map中是否存在某鍵(key)
boolean containsValue(Object value):判斷Map中是否存在某值(value)
public Set keySet() :返回全部的鍵(key),並使用Set容器存放
public Collection values() :返回全部的值(Value),並使用Collection存放
public Set entrySet() :返回一個實現 Map.Entry 接口的元素 Set
Map 主要用於存儲鍵(key)值(value)對,根據鍵獲得值,所以鍵不容許重複,但容許值重複。
HashMap 是一個最經常使用的Map,它根據鍵的HashCode 值存儲數據,根據鍵能夠直接獲取它的值,具備很快的訪問速度。
HashMap最多隻容許一條記錄的鍵爲Null;容許多條記錄的值爲 Null;
HashMap不支持線程的同步,即任一時刻能夠有多個線程同時寫HashMap;可能會致使數據的不一致。若是須要同步,能夠用 Collections的synchronizedMap方法使HashMap具備同步的能力,或者使用ConcurrentHashMap
使用HashMap ,當一個對象被看成鍵值須要對equals()和hashCode()同時覆寫
Hashtable與 HashMap相似,它繼承自Dictionary類,不一樣的是:它不容許記錄的鍵或者值爲空;它支持線程的同步,即任一時刻只有一個線程能寫Hashtable,所以也致使了 Hashtable在寫入時會比較慢。
Hashmap 是一個最經常使用的Map,它根據鍵的HashCode 值存儲數據,根據鍵能夠直接獲取它的值,具備很快的訪問速度,遍歷時,取得數據的順序是徹底隨機的。
LinkedHashMap保存了記錄的插入順序,在用Iterator遍歷LinkedHashMap時,先獲得的記錄確定是先插入的.也能夠在構造時用帶參數,按照應用次數排序。在遍歷的時候會比HashMap慢,不過有種狀況例外,當HashMap容量很大,實際數據較少時,遍歷起來可能會比LinkedHashMap慢,由於LinkedHashMap的遍歷速度只和實際數據有關,和容量無關,而HashMap的遍歷速度和他的容量有關。
TreeMap實現SortMap接口,可以把它保存的記錄根據鍵排序,默認是按鍵值的升序排序,也能夠指定排序的比較器,當用Iterator 遍歷TreeMap時,獲得的記錄是排過序的。
咱們用的最多的是HashMap,HashMap裏面存入的鍵值對在取出的時候是隨機的,在Map 中插入、刪除和定位元素,HashMap 是最好的選擇。
TreeMap取出來的是排序後的鍵值對。但若是您要按天然順序或自定義順序遍歷鍵,那麼TreeMap會更好。
LinkedHashMap 是HashMap的一個子類,若是須要輸出的順序和輸入的相同,那麼用LinkedHashMap能夠實現,它還能夠按讀取順序來排列,像鏈接池中能夠應用。
不容許重複元素
對 add()、equals() 和 hashCode() 方法添加了限制
HashSet和TreeSet是Set的實現
Set—》HashSet LinkedHashSet
SortedSet —》 TreeSet
public boolean contains(Object o) :若是set包含指定元素,返回true
public Iterator iterator()返回set中元素的迭代器
public Object[] toArray() :返回包含set中全部元素的數組public Object[] toArray(Object[] a) :返回包含set中全部元素的數組,返回數組的運行時類型是指定數組的運行時類型
public boolean add(Object o) :若是set中不存在指定元素,則向set加入
public boolean remove(Object o) :若是set中存在指定元素,則從set中刪除
public boolean removeAll(Collection c) :若是set包含指定集合,則從set中刪除指定集合的全部元素
public boolean containsAll(Collection c) :若是set包含指定集合的全部元素,返回true。若是指定集合也是一個set,只有是當前set的子集時,方法返回true
實現Set接口的HashSet,依靠HashMap來實現的。
咱們應該爲要存放到散列表的各個對象定義hashCode()和equals()。
HashSet如何過濾重複元素
調用元素HashCode得到哈希碼–》判斷哈希碼是否相等,不相等則錄入—》相等則判斷equals()後是否相等,不相等在進行hashcode錄入,相等不錄入
TreeSet是依靠TreeMap來實現的。
TreeSet是一個有序集合,TreeSet中元素將按照升序排列,缺省是按照天然順序進行排列,意味着TreeSet中元素要實現Comparable接口,咱們能夠在構造TreeSet對象時,傳遞實現了Comparator接口的比較器對象。
HashSet不能保證元素的排列順序,順序有可能發生變化,不是同步的,集合元素能夠是null,但只能放入一個null
TreeSet是SortedSet接口的惟一實現類,TreeSet能夠確保集合元素處於排序狀態。TreeSet支持兩種排序方式,天然排序 和定製排序,其中天然排序爲默認的排序方式。向 TreeSet中加入的應該是同一個類的對象。
TreeSet判斷兩個對象不相等的方式是兩個對象經過equals方法返回false,或者經過CompareTo方法比較沒有返回0
天然排序
天然排序使用要排序元素的CompareTo(Object obj)方法來比較元素之間大小關係,而後將元素按照升序排列。
定製排序
天然排序是根據集合元素的大小,以升序排列,若是要定製排序,應該使用Comparator接口,實現 int compare(To1,To2)方法
LinkedHashSet集合一樣是根據元素的hashCode值來決定元素的存儲位置,可是它同時使用鏈表維護元素的次序。這樣使得元素看起 來像是以插入順 序保存的,也就是說,當遍歷該集合時候,LinkedHashSet將會以元素的添加順序訪問集合的元素。
LinkedHashSet在迭代訪問Set中的所有元素時,性能比HashSet好,可是插入時性能稍微遜色於HashSet。
參考資料:
http://www.cnblogs.com/xiaoshitoutest/p/6963798.html
文章有不當之處,歡迎指正,你也能夠關注個人微信公衆號:
好好學java
,獲取優質資源。