Java集合類框架圖java
| 類型 | 數據結構 | 查詢速度 | 插入速度 | 是否線程安全 | | ArrayList | 數組 | 快 | 慢 | 否 | | LinkList | 雙向鏈表,| 慢 | 快 | 否 | | Vector | 數組 | 慢 | 慢 | 是 | 對於它們三種類型的List的查詢、刪除、插入的性能(時間複雜度和空間複雜度)能夠根據它們底層所依賴的數據結構和算法具體分析,上面的圖表只是簡單的總結。在實際開發中,存在大量的隨機訪問和少許的刪除、插入時推薦使用ArrayList,反之使用LinkList。在須要保證線程安全的狀況下使用Vetor,一樣Collections.synchronizedList(new ArrayList())和Collections.synchronizedList(new LinkList())也能得到線程安全的List。
| 類型 | 是否有序 | 元素是否容許重複 | | List | 是 | 否 | | Set | 否 | 是 |
實現Set接口,基於HashMap實現,容器內元素不能重複。
程序員
HashMap中的數據結構是數組+鏈表實現,如圖: 算法
HashMap的查找:首先根據hashcode在table數組中查找,而後根據key在其後的鏈表中得到key。編程
equals()數組
指示其餘某個對象是否與此對象「相等」。安全
equals 方法在非空對象引用上實現相等關係: 數據結構
(1)自反性:對於任何非空引用值 x,x.equals(x) 都應返回 true。 併發
(2)對稱性:對於任何非空引用值 x 和 y,當且僅當 y.equals(x) 返回 true 時,x.equals(y) 才應返回 true。 (3)傳遞性:對於任何非空引用值 x、y 和 z,若是 x.equals(y) 返回 true,而且 y.equals(z) 返回 true,那麼 x.equals(z) 應返回 true。 框架
(4)一致性:對於任何非空引用值 x 和 y,屢次調用 x.equals(y) 始終返回 true 或始終返回 false,前提是對象上 equals 比較中所用的信息沒有被修改。 數據結構和算法
(5)對於任何非空引用值 x,x.equals(null) 都應返回 false。
(6)Object 類的 equals 方法實現對象上差異可能性最大的相等關係;即,對於任何非空引用值 x 和 y,當且僅當 x 和 y 引用同一個對象時,此方法才返回 true(x == y 具備值 true)。(實際JDK中對equals()方法進行了重寫)
注意:當此方法被重寫時,一般有必要重寫 hashCode 方法,以維護 hashCode 方法的常規協定,該協定聲明相等對象必須具備相等的哈希碼。
//重寫equals()方法Demo public class Student { protected String name; public Student(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public boolean equals(Object obj){ if(obj instanceof Student){ Student student = (Student) obj; if(student.getName() == null || name == null){ return false; }else { return name.equalsIgnoreCase(student.getName()); } } return false; } } public class SeniorStudent extends Student { //增長一個id屬性 private int id; public int getId() { return id; } public void setId(int id) { this.id = id; } public SeniorStudent(String name, int id) { super(name); this.id = id; } @Override public boolean equals(Object obj) { if (obj instanceof SeniorStudent) { SeniorStudent ss = (SeniorStudent) obj; return super.equals(obj) && ss.getId() == id; } return false; } } //測試 public class Test { public static void main(String[] args) { SeniorStudent ss1 = new SeniorStudent("zhangsan", 10); SeniorStudent ss2 = new SeniorStudent("zhangsan", 20); Student s = new Student("zhangsan"); System.out.println(s.equals(ss1)); System.out.println(s.equals(ss2)); System.out.println(ss1.equals(ss2)); } } 預期: false false false 結果: true true false 緣由分析: 咱們使用instanceof關鍵字來檢查ss否爲Student類,而instanceof是判斷其左邊對象是否爲其右邊類的實例,也能夠用來判斷繼承中的子類的實例是否爲父類的實現,因此當有繼承的時便會出現問題。 //因此在equals()中使用getClass進行類型判斷
hashcode()
返回該對象的哈希碼值。支持此方法是爲了提升哈希表(例如 java.util.Hashtable 提供的哈希表)的性能。 hashCode 的常規協定是:
(1)在 Java 應用程序執行期間,在對同一對象屢次調用 hashCode 方法時,必須一致地返回相同的整數,前提是將對象進行 equals 比較時所用的信息沒有被修改。
(2)從某一應用程序的一次執行到同一應用程序的另外一次執行,該整數無需保持一致。 若是根據 equals(Object) 方法,兩個對象是相等的,那麼對這兩個對象中的每一個對象調用 hashCode 方法都必須生成相同的整數結果。
(3)若是根據 equals(java.lang.Object) 方法,兩個對象不相等,那麼對這兩個對象中的任一對象上調用 hashCode 方法不要求必定生成不一樣的整數結果。可是,程序員應該意識到,爲不相等的對象生成不一樣整數結果能夠提升哈希表的性能。
實際上,由 Object 類定義的 hashCode 方法確實會針對不一樣的對象返回不一樣的整數。(這通常是經過將該對象的內部地址轉換成一個整數來實現的,可是 JavaTM 編程語言不須要這種實現技巧。)
hashCode()和equals配合使用
//重寫hashCode()和equals()的Demo public class Student1 { private int age; private String name; public Student1(int age, String name) { this.age = age; this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public boolean equals(Object o) { System.out.println("調用equals()方法"); if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Student1 student1 = (Student1) o; if (age != student1.age) return false; return !(name != null ? !name.equals(student1.name) : student1.name != null); } @Override public int hashCode() { System.out.println("調用hashCode()方法"); int result = age; result = 31 * result + (name != null ? name.hashCode() : 0); System.out.println("name: " + name + " age: " + age + " hashCode "+result); return result; } public static void main(String[] args) { Student1 s1 = new Student1(1,"張三"); Student1 s2 = new Student1(2,"張三"); Student1 s3 = new Student1(1,"李四"); Student1 s4 = new Student1(1,"張三"); System.out.println("s1 == s4 " + (s1==s4 )); System.out.println("s1.equals(s4)" + s1.equals(s4)); HashSet<Student1> hashSet = new HashSet(); hashSet.add(s1); hashSet.add(s2); hashSet.add(s3); hashSet.add(s4); System.out.println(hashSet.size()); } } //結果 s1 == s4 false 調用equals()方法 s1.equals(s4)true 調用hashCode()方法 name: 張三 age: 1 hashCode 774920 調用hashCode()方法 name: 張三 age: 2 hashCode 774951 調用hashCode()方法 name: 李四 age: 1 hashCode 842092 調用hashCode()方法 name: 張三 age: 1 hashCode 774920 調用equals()方法 //注意調用equals()方法 s1.equals(s4)==true時 3
//(方法1)同時獲取key-value Map<String,String> map = new HashMap(); for (Map.Entry<String,String> entry : map.entrySet()) { entry.getKey(); entry.getValue(); } //(方法2)獲取key Map<String, String> map = new HashMap(); for (String key : map.keySet()) { System.out.println(key); } //(方法3)獲取value Map<String, String> map = new HashMap(); for (String key : map.keySet()) { String value = map.get(key); } //(方法4)獲取value Map<String, String> map = new HashMap(); for (String value : map.values()) { System.out.println(value); } //上面的四個例子也可使用迭代器,方法(1)能夠以下,可是不推薦 Map<String, String> map = new HashMap(); Iterator<Map.Entry<String, String>> it = map.entrySet().iterator(); while (it.hasNext()) { Map.Entry<String, String> entry = it.next(); } /** *同時獲取key-value是推薦使用方法(1) *只是獲取key推薦方法(2) *獲取方法value(4) **/
Hashtable和HashMap採用一樣的數據結構,實現基本相同。
| 類型 | key爲是否容許爲null | key爲是否容許爲null | 是否線程安全 | 效率 | 父類 |
| Hashtable | 是 | 是 | 否 | 高 | Dictionary |
| HashMap | 否 | 否 | 是 | 低 | AbstractMap |
//案例以下:運行下面代碼,使用top命令,CPU會飆升趨於100% public class HashMapInfLoop { private HashMap map = new HashMap(); public HashMapInfLoop() { for(int i =0; i<100000; i++) { Thread thread = new Thread(new Runnable() { public void run() { for (int i = 0; i < 50000000; i++) { map.put(new Integer(i), Integer.valueOf(i)); } } }); thread.start(); } } public static void main(String[] args) { new HashMapInfLoop(); } }
問題出在擴容時,移動oldTable裏的數據到newTable裏時,在併發條件下會出現環路。源碼以下:
void transfer(Entry[] newTable) { Entry[] src = table; int newCapacity = newTable.length; for (int j = 0; j < src.length; j++) { Entry<K,V> e = src[j]; if (e != null) { src[j] = null; do { Entry<K,V> next = e.next; int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } while (e != null); } } }
圖解分析能夠參考:http://ifeve.com/hashmap-infinite-loop/
上面的例子能夠說明併發下不能使用HashMap,那咱們可使用線程安全的Hashtable嗎?
HashTable中是使用synchronized實現同步,synchronized鎖的HashTable的實例對象,其餘線程訪問HashTable的同步方法時,可能會進入阻塞或輪詢狀態。如線程A使用put進行添加元素,線程B不但不能使用put方法添加元素,而且也不能使用get方法來獲取元素,因此競爭越激烈效率越低。因此就得使用ConcurrentHashMap。
ConcurrentHashMap結構圖:
根據ConcurrentHashMap的結構圖能夠明顯看出,ConcurrentHashMap採用的是二次hash的方式,
第一次hash將key映射到對應的segment(Segment是一種可重入鎖ReentrantLock),而第二次hash則是映射到segment的不一樣桶中。爲何要用二次hash,主要緣由是爲了構造分離鎖,使得對於map的修改不會鎖住整個容器,提升併發能力。可是二次hash帶來的問題是整個hash的過程比hashmap單次hash要長,因此不是併發狀況下不要使用ConcurrentHashMap。
WeakHashMap 使用 WeakReference 做爲 key, 一旦沒有指向 key 的強引用, WeakHashMap 在 GC 後將自動刪除相關的 entry。
WeakHashMap<String,String> map = new WeakHashMap<String, String>(); map.put("wjk","hello"); map.put("snail","world"); map.put(new String("wjk"),"hello"); map.put(new String("snail"),"world"); System.gc(); System.out.println(map.size()); } //輸出 2
Java引用類型能夠參考:http://my.oschina.net/u/2361475/blog/603125
使用Google Guava框架和Apache Commons框架的工具類集合工具類,編寫優雅的代碼。