Map基本概念html
數據結構中Map是一種重要的形式。Map接口定義的是查詢表,或稱查找表,其用於儲存所謂的鍵/值對(key-value pair),其中key是映射表的索引。java
JDK結構中還存在實現Map相似功能的遺留集合:安全
Hashtable(線程安全的散列映射表)數據結構
Properties(屬性映射表),經常使用於配置文件(如db.properties)。框架
Map接口源代碼ide
package java.util; /* 映射表(查詢表)泛型接口 */
public interface Map<K,V> { /* 基本與Collection相同,返回映射表中key-value映射對數量(內部屬性size) */ int size(); /* 基本與Collection相同,返回映射表是否包含映射對 */ boolean isEmpty(); /* 返回映射表是否包含指定的鍵 */ boolean containsKey(Object key); /* 返回映射表是否包含指定的值,或者說指定的值是否有大於等於1個映射的鍵 */ boolean containsValue(Object value); /* 若是映射表中存在指定key,返回此key的值;不然,返回null */ V get(Object key); /* 將鍵值對放入映射表中。
* 若是鍵存在,則用如今的值替換原有值,返回原有值對象;
* 若是鍵不存在則存入鍵值對,返回null */
*/ V put(K key, V value); /* 移除指定鍵對應的值。若是存在鍵,則移除,並返回移除值對象;反之,則返回null */ V remove(Object key); /* 複製另外一張映射表元素到本映射表中 */ void putAll(Map<? extends K, ? extends V> m); /* 基本同Collection,清空映射表 */ void clear(); /* 得到鍵的Set集合 */ Set<K> keySet(); /* 得到值的Collection集合 */ Collection<V> values(); /* 得到鍵值對的Set集合 */ Set<Map.Entry<K, V>> entrySet(); /* 內部接口 Entry<K,V> */ interface Entry<K,V> { /* 獲取鍵值對的鍵 */ K getKey(); /* 獲取鍵值對的值 */ V getValue(); /* 設置鍵值對的值 */ V setValue(V value); /* 比較entry(鍵值對) */ boolean equals(Object o); /* 生成entry對象的hash值 */ int hashCode(); } /* 比較映射表 */ boolean equals(Object o); /* 生成映射表對象的hash碼*/ int hashCode(); }
深刻理解碼源測試
對象比較好基友:
equals(Object obj)
、hashcode()
this
Map<K, V>
接口及其內部接口Entry<K, V>
都有這倆方法。如此設計,目的就是規範其實現子類,要求子類必須重寫Object
類的這倆方法,從而完成映射表這種數據結構的既定思想。spa
boolean equals(Object o); // 對象比較 int hashCode(); // 哈希碼
集合框架通用方法:
size()
、isEmpty()
線程集合框架(包括
Collection
接口及其子接口List
、Set
,Map
接口)內部維護一個size
屬性,其描述用戶提供可操縱元素數量,經過size()
方法向用戶提供可見性,經過isEmpty()
方法向用戶說明是否集合中仍存在其可操縱的元素。
int size(); // 元素保有量 boolean isEmpty(); // 元素數量是否爲0
鍵、值存在性判斷:
containsKey(Object key)
、containsValue(Object value)
boolean containsKey(Object key); // 映射表是否包含指定鍵的元素 boolean containsValue(Object value); // 映射表是否包含指定值得元素
映射表增刪查改:
增、改
V put(K key, V value) V putAll(Map<? extends K, ? value V> m)
刪
V remove(Object key) void clear()
查
V get(Object key)
V put(K key, V value) // 放入或替換指定key的鍵值對 V putAll(Map<? extends K, ? value V> m) // 將另外一映射表全部元素放入本映射表 V remove(Object key) // 移除指定key的鍵值對 V get(Object key) // 獲取指定key的鍵值對 void clear() // 清除全部映射表元素
package com.forget406.study; import java.util.HashMap; import java.util.Map; import java.util.TreeMap; public class MapStudy { @SuppressWarnings({ "unchecked", "rawtypes" }) public static void main(String[] args) { /* JDK 1.7容許後面的尖括號內不寫泛型變量 */ Map<String, Coordinate> rect = new HashMap<>(); rect.put("point A", new Coordinate(0, 0)); rect.put("point B", new Coordinate(1, 0)); rect.put("point C", new Coordinate(1, 1)); rect.put("point D", new Coordinate(0, 1)); Map<String, Coordinate> line = new TreeMap<String, Coordinate>(); line.put("point A", new Coordinate(0, 0)); line.put("point B", new Coordinate(3, 3)); /***** 實驗測試部分 *****/ System.out.println(rect); // output rectangle System.out.println(line); // output line System.out.println(rect.put("point D", new Coordinate(2, 2))); // (0, 1) System.out.println(rect.get("point C")); // (1,1) System.out.println(rect.get("point E")); // null rect.putAll(line); System.out.println(rect); System.out.println(line.remove("point C")); // null System.out.println(line.remove("point A")); // (0, 0) } } /** * 座標類 * * @author forget406 * @since 09/08/2016 * @param <T> 泛型參數 */ class Coordinate<T> { private T x; private T y; public Coordinate(T x, T y) { this.x = x; this.y = y; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((x == null) ? 0 : x.hashCode()); result = prime * result + ((y == null) ? 0 : y.hashCode()); return result; } @SuppressWarnings("unchecked") @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Coordinate<T> other = (Coordinate<T>) obj; if (x == null) { if (other.x != null) return false; } else if (!x.equals(other.x)) return false; if (y == null) { if (other.y != null) return false; } else if (!y.equals(other.y)) return false; return true; } @Override public String toString() { return "(" + x + "," + y + ")"; } }
{point C=(1,1), point B=(1,0), point A=(0,0), point D=(0,1)} {point A=(0,0), point B=(3,3)} (0,1) (1,1) null {point C=(1,1), point B=(3,3), point A=(0,0), point D=(2,2)} null (0,0)
映射表元素遍歷三種方式(或稱 映射表集合視圖(*)):
- 按
key
遍歷Set<K> keySet() 返回映射表中全部鍵的視圖。能夠從這個Set中刪除元素,同時也從映射表中刪除了它們。
- 按
value
遍歷Collection<V> values() 返回映射表中全部值的視圖。能夠從這個Collection中刪除元素,同時也從映射表中刪除了它們。
- 按
Entry(key-value對)
遍歷Set<Map.Entry<K, V>> entrySet()
返回Map.Entry對象Set集的視圖,即映射表中的鍵/值對。能夠從這個集合中刪除元素,同時也從映射表中刪除了它們。
注意:雖然能夠從這三種遍歷方式遍歷得到的視圖集合中刪除元素,可是均不可以在其中添加元素。
Set<K> keySet(); // 元素鍵視圖集 Collection<V> values(); // 元素值視圖集 Set<Map.Entry<K, V>> entrySet(); // 元素鍵/值對視圖集
Map
接口的內部接口,即Entry<K, V>
接口,起封裝鍵值對的做用。經過鍵值對遍歷Map
時,能夠經過Entry
接口提供的方法獲取相應的鍵、值,以及設置鍵值對的值。
K getKey()
得到鍵值對的鍵
V getValue()
得到鍵值對的值
V setValue(V value)
設置鍵值對的值,同時映射表的內容也同時更新(*)
但沒有提供設置鍵值對鍵的方法,why? 鍵值對的鍵是鍵值對的索引,改變鍵不具備實際意義,並且改變以後會引發一系列諸如hash值改變等問題。K setKey(K key)
/* Entry<K, V>接口,屬於內部接口 */
interface Entry<K,V> { K getKey(); // 得到鍵值對的鍵 V getValue(); // 得到鍵值對的值 V setValue(V value); // 設置鍵值對的值 /* 前面已經論述過 */ boolean equals(Object o); int hashCode(); }
import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Set; public class MapStudy { @SuppressWarnings({ "unchecked", "rawtypes" }) public static void main(String[] args) { Map<String, Coordinate> rect = new HashMap<String, Coordinate>(); rect.put("point A", new Coordinate(0, 0)); rect.put("point B", new Coordinate(1, 0)); rect.put("point C", new Coordinate(1, 1)); rect.put("point D", new Coordinate(0, 1)); /***** 實驗測試代碼 *****/ Set<Entry<String, Coordinate>> pos = rect.entrySet();
/* 最好判斷一下是否爲null */ if (pos != null) { for(Entry<String, Coordinate> p : pos) { System.out.println(p.getKey()+ "=" +p.getValue()); // 得到鍵、值 p.setValue(new Coordinate("?", "?")); // 設置對應Entry的值 } System.out.println(rect); // 更新Entry後,原來映射表內容也隨之更新 } } } /** * 座標類 * * @author forget406 * @since 09/08/2016 * @param <T> 泛型參數 */ class Coordinate<T> { private T x; private T y; public Coordinate(T x, T y) { this.x = x; this.y = y; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((x == null) ? 0 : x.hashCode()); result = prime * result + ((y == null) ? 0 : y.hashCode()); return result; } @SuppressWarnings("unchecked") @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Coordinate<T> other = (Coordinate<T>) obj; if (x == null) { if (other.x != null) return false; } else if (!x.equals(other.x)) return false; if (y == null) { if (other.y != null) return false; } else if (!y.equals(other.y)) return false; return true; } @Override public String toString() { return "(" + x + "," + y + ")"; } }
point C=(1,1) point B=(1,0) point A=(0,0) point D=(0,1) {point C=(?,?), point B=(?,?), point A=(?,?), point D=(?,?)}
若是讀者須要進一步瞭解Java的內部接口機制,能夠閱讀另外一篇文章:《Java高級特性(二)內部接口》。
Map遍歷方式
Map
遍歷做爲文章體系中極爲重要的一塊內容,從上面模塊中抽出來單獨總結。
Iterator<E>
接口實現
這種實現方式相對來講比較雞肋,反正遍歷Map
我不多用到的。
import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; public class MapStudy { @SuppressWarnings({ "unchecked", "rawtypes" }) public static void main(String[] args) { Map<String, Coordinate> rect = new HashMap<String, Coordinate>(); rect.put("point A", new Coordinate(0, 0)); rect.put("point B", new Coordinate(1, 0)); rect.put("point C", new Coordinate(1, 1)); rect.put("point D", new Coordinate(0, 1)); /***** Iterator<E>方式遍歷代碼 *****/ // 遍歷key,獲取key Set<String> keys = rect.keySet(); Iterator<String> it1 = keys.iterator(); while(it1.hasNext()) { // 問 String key = it1.next(); // 取 System.out.println(key); // it1.remove(); 刪 } // 遍歷value,獲取value Collection<Coordinate> values = rect.values(); Iterator<Coordinate> it2 = values.iterator(); while(it2.hasNext()) { Coordinate value = it2.next(); System.out.println(value); } // 遍歷key-value對,獲取key、value,設置value Set<Entry<String, Coordinate>> entries = rect.entrySet(); Iterator<Entry<String, Coordinate>> it3 = entries.iterator(); while(it3.hasNext()) { Entry<String, Coordinate> entry = it3.next(); String key = entry.getKey(); System.out.println(key); Coordinate value = entry.getValue(); System.out.println(value); entry.setValue(new Coordinate("?", "?")); } System.out.println(rect); } } /** * 座標類 * * @author forget406 * @since 09/08/2016 * @param <T> 泛型參數 */ class Coordinate<T> { private T x; private T y; public Coordinate(T x, T y) { this.x = x; this.y = y; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((x == null) ? 0 : x.hashCode()); result = prime * result + ((y == null) ? 0 : y.hashCode()); return result; } @SuppressWarnings("unchecked") @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Coordinate<T> other = (Coordinate<T>) obj; if (x == null) { if (other.x != null) return false; } else if (!x.equals(other.x)) return false; if (y == null) { if (other.y != null) return false; } else if (!y.equals(other.y)) return false; return true; } @Override public String toString() { return "(" + x + "," + y + ")"; } }
point C point B point A point D (1,1) (1,0) (0,0) (0,1) point C (1,1) point B (1,0) point A (0,0) point D (0,1) {point C=(?,?), point B=(?,?), point A=(?,?), point D=(?,?)}
for-each實現
在JDK1.5版本引入for-each(由JVM維護,內部實現也是經過迭代器實現)後,上面那種有點原始社會的感受。因此通常我都是使用for-each遍歷Map
。
import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; public class MapStudy { @SuppressWarnings({ "unchecked", "rawtypes" }) public static void main(String[] args) { Map<String, Coordinate> rect = new HashMap<String, Coordinate>(); rect.put("point A", new Coordinate(0, 0)); rect.put("point B", new Coordinate(1, 0)); rect.put("point C", new Coordinate(1, 1)); rect.put("point D", new Coordinate(0, 1)); /***** for-each方式遍歷代碼 *****/ // 遍歷key,獲取key Set<String> keys = rect.keySet(); for(String key : keys) { System.out.println(key); } // 遍歷value,獲取value Collection<Coordinate> values = rect.values(); for(Coordinate value : values) { System.out.println(value); } // 遍歷key-value對,獲取key、value,設置value Set<Entry<String, Coordinate>> entries = rect.entrySet(); for(Entry<String, Coordinate> entry : entries) { String key = entry.getKey(); System.out.println(key); Coordinate value = entry.getValue(); System.out.println(value); entry.setValue(new Coordinate("?", "?")); } System.out.println(rect); } } /** * 座標類 * * @author forget406 * @since 09/08/2016 * @param <T> 泛型參數 */ class Coordinate<T> { private T x; private T y; public Coordinate(T x, T y) { this.x = x; this.y = y; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((x == null) ? 0 : x.hashCode()); result = prime * result + ((y == null) ? 0 : y.hashCode()); return result; } @SuppressWarnings("unchecked") @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Coordinate<T> other = (Coordinate<T>) obj; if (x == null) { if (other.x != null) return false; } else if (!x.equals(other.x)) return false; if (y == null) { if (other.y != null) return false; } else if (!y.equals(other.y)) return false; return true; } @Override public String toString() { return "(" + x + "," + y + ")"; } }
Map體系結構
Map接口的實現類大體分爲兩大類:
通用映射表類
HashMap(散列映射表)、TreeMap(二叉樹映射表)
專用映射表類
IdentityHashMap(標識散列映射表)、WeakHashMap(弱散列映射表)、LinkedHashMap(連接散列映射表)、EnumMap(枚舉映射表)等。
固然,上面只是比較粗略地分類,後續文章將進一步深刻分析Map相關接口、抽象類、實現類的內部機制,以及其於Set集的對應關係。
Map相關問題思考
思考1:標識符命名單複數
這個問題不光光是Map
有,整個Java語言在設計時都是採用這種思路。這種寫法歸根結底仍是因爲英語國家語言語法習慣,值得咱們在平時命名的時候注意。
boolean containsKey(Object key);
若是換成我習慣的命名法則,讓我來設計底層代碼,可能就寫成
boolean containKey(Object key);
boolean isContainKey(Object key);
真的是一個大寫的囧 o(╯□╰)o 和大牛的差距就體現出來了。
對於設計方法標識符時大體應該遵循:
一、若是方法用於判斷,即返回值是boolean
boolean isAb(Ab是名詞) // 判斷對象是Ab嗎?
boolean AbsCd(Abs是動詞單數,Cd是名詞) // 判斷對象Abs了Cd?好比上面的ContainsKey
二、通常的方法,規則是動詞開頭(具體判斷是否加單數),後能夠加名詞或者連詞(Of、As等)
思考2:Map視圖
視圖(views)實際上是集合框架(collection frame)所共有的語言特性。
關於視圖的內容,會單獨整理總結成文,這裏就是做爲一塊須要重點理解的內容提出來。
Map
涉及到視圖的內容:
Set<K> keySet(); // 元素鍵視圖集 Collection<V> values(); // 元素值視圖集 Set<Map.Entry<K, V>> entrySet(); // 元素鍵/值對視圖集
/** Entry<K, V>接口,屬於內部接口 */
interface Entry<K,V> { K getKey(); // 得到鍵值對的鍵 V getValue(); // 得到鍵值對的值 V setValue(V value); // 設置鍵值對的值 }
思考3:Map遍歷須要用Collection(集合)和Set(集)的緣由分析
回頭看前面Map
接口源代碼,你會發現它並無寫成
public interface Map<K, V> extends Iterable<E>
而是簡單地寫成
public interface Map<K, V>
也就是說,Map
接口其實是一個頂層接口。
相比較Collection<T>
接口繼承更頂層的Iterable<T>
(擁有iterator()
方法,以及for-each使用必須繼承的接口),Map
接口顯得較爲特殊。
爲何Map遍歷必須藉助Collection或Set呢?
假設如今Map接口源代碼寫成
public interface Map<K, V> extends Iterable<E>
那麼問題來了,該怎麼遍歷Map呢?如今有key、value兩個泛型參數,固然咱們能夠將這兩個泛型參數當作一對Entry
,不過若是我須要單獨遍歷key或者value呢?沒法單獨經過Map
實現。這就會顯得功能太單調。
與其這樣,不如一不作二不休,將Map<K, V>
接口的遍歷查詢結果作成三種Set
集或Collection
集合視圖。
Map<K, V>
的K不可以重複,選擇合適的數據結構Set
儲存。
Map<K, V>
的V能夠重複,選擇合適的數據結構Collection
儲存。固然我我的認爲List
儲存也是能夠的,只不過設計者選擇更加穩妥、保守的方式而已。
思考4:增刪查改方法都有泛型返回值V(映射表值)
這一點其實也沒琢磨太清楚,多是設計者認爲這麼定義方法更加人性化。好比:
V remove(Object o) 刪除映射表中指定鍵元素,若是刪除則返回刪除元素的值對象,反正則返回null。這樣咱們還可以看看到底刪除值對不對。