??之前對(duì)Collection接口以及其對(duì)應(yīng)的子接口已經(jīng)有所了解玲昧,可以發(fā)現(xiàn)在Collection接口之中所保存的數(shù)據(jù)全部都只是單個(gè)對(duì)象摩瞎,而在數(shù)據(jù)結(jié)構(gòu)中除了可以進(jìn)行單個(gè)對(duì)象的保存外,也可以進(jìn)行二元偶對(duì)象的保存(key=value)的形式來存儲(chǔ)纹份,而存儲(chǔ)二元偶對(duì)象的核心意義在于需要通過key獲取對(duì)應(yīng)的value苟跪。
在開發(fā)中,Collection集合保存數(shù)據(jù)的目的是為了輸出蔓涧,Map集合保存數(shù)據(jù)的目的是為了進(jìn)行key的查找件已。
Map接口簡(jiǎn)介
??Map接口是進(jìn)行二元偶對(duì)象保存的最大父接口。該接口定義如下:
public interface Map<K,V>
??該接口為一個(gè)獨(dú)立的父接口蠢笋,并且在進(jìn)行接口對(duì)象實(shí)例化的時(shí)候需要設(shè)置K與V的類型拨齐,也就是在整體操作的時(shí)候需要保存兩個(gè)內(nèi)容鳞陨,在Map接口中定義有許多操作方法昨寞,但是需要記住以下的核心操作方法:
-
向集合中保存數(shù)據(jù):
V put?(K key,V value)
-
根據(jù)key查詢數(shù)據(jù):
V get?(Object key)
-
將Map集合轉(zhuǎn)為Set集合:
Set<Map.Entry<K,V>> entrySet()
- 指定key是否存在:
boolean containsKey?(Object key)
- 將Map集合中的key轉(zhuǎn)為Set集合:
Set<K> keySet()
- 根據(jù)key刪除指定的數(shù)據(jù):
V remove?(Object key)
??從JDK1.9后Map接口中也擴(kuò)充了一些靜態(tài)方法提供用戶使用。
范例:觀察Map集合的特點(diǎn)
import java.util.Map;
public class JavaAPIDemo {
public static void main(String[] args) throws Exception {
// Map<String,Integer> map=Map.of("one",1,"two",2);
// System.out.println(map);//{two=2, one=1}
//
// Map<String,Integer> map=Map.of("one",1,"two",2,"one",101);
// System.out.println(map);//Exception in thread "main" java.lang.IllegalArgumentException: duplicate key: one
Map<String,Integer> map=Map.of("one",1,"two",2,null,0);
System.out.println(map);//Exception in thread "main" java.lang.NullPointerException
}
}
在Map集合中數(shù)據(jù)的保存就是按照“key=value”的形式存儲(chǔ)的厦滤,并且使用of()方法時(shí)里面的key是不允許重復(fù)援岩,如果重復(fù)則會(huì)出現(xiàn)
java.lang.IllegalArgumentException: duplicate key: one
,如果設(shè)置的key或value為null,則會(huì)出現(xiàn)java.lang.NullPointerException
??對(duì)于現(xiàn)在使用的of()方法嚴(yán)格意義上來說并不是Map集合的標(biāo)準(zhǔn)用法掏导,因?yàn)檎5拈_發(fā)中需要通過Map集合的子類來進(jìn)行接口對(duì)象的實(shí)例化享怀,而常用的子類:HashMap、HashTable趟咆、TreeMap添瓷、LinkedHashMap。
HashMap子類
??HashMap是Map接口中最為常見的一個(gè)子類,該類的主要特點(diǎn)是無序存儲(chǔ)值纱,通過Java文檔首先來觀察一下HashMap子類的定義:
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
public abstract class AbstractMap<K,V> extends Object implements Map<K,V>
該類的定義繼承形式符合之前的集合定義形式鳞贷,依然提供有抽象類并且依然需要重復(fù)實(shí)現(xiàn)Map接口。
范例:觀察Map集合的使用
import java.util.HashMap;
import java.util.Map;
public class JavaAPIDemo {
public static void main(String[] args) throws Exception {
Map<String,Integer> map=new HashMap();
map.put("1",1);
map.put("2",2);
map.put("1",101);//key重復(fù)
map.put(null,0);//key為null
map.put("0",null);//value為null
System.out.println(map.get("1"));//key存在:101
System.out.println(map.get(null));//key存在:0
System.out.println(map.get("3"));//key不存在:null
}
}
以上的操作形式為Map集合使用的最標(biāo)準(zhǔn)的處理形式虐唠,通過代碼可以發(fā)現(xiàn)搀愧,通過HashMap實(shí)例化的Map接口可以針對(duì)key或者value保存null的數(shù)據(jù),同時(shí)也可以發(fā)現(xiàn)即便保存數(shù)據(jù)的key重復(fù)疆偿,那么也不會(huì)出現(xiàn)錯(cuò)誤咱筛,而是內(nèi)容的覆蓋。
??對(duì)于Map接口中提供的put()方法本身是提供有返回值的杆故,那么這個(gè)返回值指的是在重復(fù)key的情況下返回舊的value迅箩。
范例:觀察put()方法
import java.util.HashMap;
import java.util.Map;
public class JavaAPIDemo {
public static void main(String[] args) throws Exception {
Map<String, Integer> map = new HashMap();
Integer oldValue = map.put("1", 1);
System.out.println(oldValue);//key不重復(fù),返回null:null
oldValue = map.put("1", 101);
System.out.println(oldValue);//key重復(fù)处铛,返回舊數(shù)據(jù):1
}
}
在設(shè)置了相同key的內(nèi)容時(shí)饲趋,put()方法會(huì)返回原始的數(shù)據(jù)內(nèi)容叉钥。
??清楚了HashMap的基本功能后下面就需要來研究一下HashMap中給出的源代碼。
final float loadFactor;
static final float DEFAULT_LOAD_FACTOR = 0.75f;
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
當(dāng)使用無參構(gòu)造的時(shí)候篙贸,會(huì)出現(xiàn)有一個(gè)loadFactor屬性投队,并且該屬性默認(rèn)的內(nèi)容為“0.75”
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
在使用put()方法進(jìn)行數(shù)據(jù)保存時(shí)會(huì)調(diào)用一個(gè)putVal()方法,同時(shí)會(huì)將key進(jìn)行hash處理(生成一個(gè)hash編碼)爵川,而對(duì)于putVal()方法中會(huì)發(fā)現(xiàn)會(huì)提供一個(gè)Node節(jié)點(diǎn)類進(jìn)行數(shù)據(jù)的保存敷鸦,而在使用putVal()方法操作的過程中,會(huì)調(diào)用一個(gè)resize()方法可以進(jìn)行容量的擴(kuò)充寝贡。
LinkedHashMap
??HashMap雖然是Map集合中最為常用的子類扒披,但是其本身保存的數(shù)據(jù)都是無序的(有序與否對(duì)Map沒有影響),如果現(xiàn)在希望Map集合中的保存的數(shù)據(jù)的順序?yàn)槠湓黾拥捻樞蚱耘荩瑒t就可以更換子類為LinkedHashMap(基于鏈表實(shí)現(xiàn)的)碟案,觀察LinkedHashMap的定義:
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
??既然是鏈表保存,所以一般在使用LinkedHashMap類時(shí)數(shù)據(jù)量不要特別大颇蜡,因?yàn)闀?huì)造成時(shí)間復(fù)雜度攀升价说,通過繼承的結(jié)構(gòu)可以發(fā)現(xiàn)LinkedHashMap是HashMap的子類,繼承關(guān)系如下:
范例:使用LinkedHashMap
import java.util.LinkedHashMap;
import java.util.Map;
public class JavaAPIDemo {
public static void main(String[] args) throws Exception {
Map<String, Integer> map = new LinkedHashMap<String, Integer>();
map.put("1", 1);
map.put("2", 2);
map.put("5", 5);
map.put("4", 4);
map.put("3", 3);
System.out.println(map);//{1=1, 2=2, 5=5, 4=4, 3=3}
}
}
通過此時(shí)的程序執(zhí)行可以發(fā)現(xiàn)當(dāng)使用LinkedHashMap進(jìn)行存儲(chǔ)之后所有數(shù)據(jù)的保存順序?yàn)樘砑禹樞颉?/p>
HashTable
??HashTable類是從JDK1.0時(shí)提供的风秤,與Vector鳖目、Enumeration屬于最早的一批動(dòng)態(tài)數(shù)組的實(shí)現(xiàn)類,后來為了將其繼續(xù)保留下來缤弦,所以讓其多實(shí)現(xiàn)了一個(gè)Map接口领迈,HashTable的定義如下:
public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, Serializable
public abstract class Dictionary<K,V> extends Object
??HashTable的繼承結(jié)構(gòu)如下:
范例:觀察HashTable類的使用
import java.util.Hashtable;
import java.util.Map;
public class JavaAPIDemo {
public static void main(String[] args) throws Exception {
Map<String, Integer> map = new Hashtable();
map.put("1", 1);
map.put("2", 2);
map.put("5", 5);
map.put(null, 4);//不能為空
map.put("3",null);//不能為空
System.out.println(map);
//Exception in thread "main" java.lang.NullPointerException
}
}
通過觀察可以發(fā)現(xiàn)在HashTable中進(jìn)行數(shù)據(jù)存儲(chǔ)時(shí)設(shè)置的key和value都不允許為null,否則會(huì)出現(xiàn)NullPointerException異常碍沐。
Map.Entry接口
??雖然清楚了整個(gè)Map集合的基本操作形式狸捅,但是依然有一個(gè)核心問題要解決,Map集合中是如何進(jìn)行數(shù)據(jù)存儲(chǔ)的累提?對(duì)于List而言(LinkedList)依靠的是鏈表的形式實(shí)現(xiàn)的數(shù)據(jù)存儲(chǔ)尘喝,那么在進(jìn)行數(shù)據(jù)存儲(chǔ)的時(shí)一定要將數(shù)據(jù)保存在Node節(jié)點(diǎn)中,雖然HashMap中也可以見到Node類型定義刻恭,通過源代碼定義可以發(fā)現(xiàn)瞧省,HashMap類中Node內(nèi)部類本身實(shí)現(xiàn)Map.Entry接口。
static class Node<K,V> implements Map.Entry<K,V>
??所以可以得出結(jié)論:所有的key和value的數(shù)據(jù)都被封裝在Map.Entry接口之中鳍贾,而此接口定義如下:
public static interface Map.Entry<K,V>
??在這個(gè)內(nèi)部接口中提供了兩個(gè)重要的操作方法:
- 獲取key:
K getKey()
- 獲取value:
V getValue()
??在JDK1.9以前的開發(fā)版本中鞍匾,開發(fā)者基本上都不會(huì)去考慮創(chuàng)建Map.Entry的對(duì)象,實(shí)際上在正常的開發(fā)過程中開發(fā)者也不需要關(guān)心Map.Entry對(duì)象骑科,但是從JDK1.9后橡淑,Map接口追加有一個(gè)新的方法:
- 創(chuàng)建Map.Entry對(duì)象:
static <K,V> Map.Entry<K,V> entry?(K k, V v)
范例:創(chuàng)建Map.Entry對(duì)象
import java.util.Map;
public class JavaAPIDemo {
public static void main(String[] args) throws Exception {
Map.Entry<String, Integer> entry = Map.entry("one",1);
System.out.println(entry.getKey());//one
System.out.println(entry.getValue());//1
System.out.println(entry.getClass().getName());//java.util.KeyValueHolder
}
}
通過分析可以發(fā)現(xiàn)在整個(gè)Map集合中,Map.Entry的主要作用就是作為一個(gè)Key和Value的包裝類型使用咆爽,而大部分情況下在進(jìn)行數(shù)據(jù)存儲(chǔ)的時(shí)候都會(huì)將Key和Value包裝為一個(gè)Map.Entry對(duì)象進(jìn)行使用梁棠。
使用Iterator輸出Map集合
??對(duì)于集合的輸出而言置森,最標(biāo)準(zhǔn)的做法就是利用Iterator接口來完成,但是需要明確一點(diǎn)就是在Map集合中并沒有方法可以直接返回Iterator接口對(duì)象符糊,所以這種情況下就必須分析不直接提供Iterator接口實(shí)例化的方法的原因,下面對(duì)Collection和Map集合的存儲(chǔ)結(jié)構(gòu)進(jìn)行一個(gè)比較男娄。
??發(fā)現(xiàn)在Map集合中保存的實(shí)際上是一組Map.Entry接口對(duì)象(里面包裝的是Key與Value),所以整個(gè)來說Map依然實(shí)現(xiàn)的是單值的保存模闲,查看文檔可以發(fā)現(xiàn)Map集合中提供了一個(gè)方法“Set<Map.Entry<K,V>> entrySet()
”,將全部的Map集合轉(zhuǎn)為Set集合尸折。
??經(jīng)過分析可以發(fā)現(xiàn)啰脚,如果想要使用Iterator實(shí)現(xiàn)Map集合的輸出則必須按照如下步驟處理:
- 利用Map接口中提供的entrySet()方法見Map集合轉(zhuǎn)為Set集合;
- 利用Set接口中的iterator()方法將Set集合轉(zhuǎn)為Iterator接口實(shí)例实夹;
- 利用Iterator進(jìn)行迭代輸出獲取每一組的Map.Entry對(duì)象橄浓,隨后通過getKey()和getValue()獲取數(shù)據(jù)。
范例:利用Iterator輸出Map集合
import java.util.Iterator;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class JavaAPIDemo {
public static void main(String[] args) throws Exception {
Map<String, Integer> map = new HashMap();
map.put("one", 1);
map.put("two", 2);
Set<Map.Entry<String, Integer>> set = map.entrySet();
Iterator<Map.Entry<String, Integer>> iterator = set.iterator();
while (iterator.hasNext()) {
Map.Entry<String, Integer> entry = iterator.next();
System.out.println(entry.getKey() + ":" + entry.getValue());
}
}
}
雖然Map集合可以支持迭代輸出收擦,但是從實(shí)際開發(fā)來說贮配,Map集合最主要的用法在于實(shí)現(xiàn)數(shù)據(jù)的Key查找操作谍倦,另外需要注意的是塞赂,如果現(xiàn)在不適用Iterator而使用foreach語法輸出則也需要將Map集合轉(zhuǎn)為Set集合。
范例:利用foreach輸出Map集合
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class JavaAPIDemo {
public static void main(String[] args) throws Exception {
Map<String, Integer> map = new HashMap();
map.put("one", 1);
map.put("two", 2);
Set<Map.Entry<String, Integer>> set = map.entrySet();
for (Map.Entry<String, Integer> entry:set) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
}
}
由于Map迭代輸出的情況相對(duì)較少昼蛀,所以對(duì)于此類的語法應(yīng)該深入理解一下宴猾,并且一定要靈活掌握。
關(guān)于KEY的定義
??在使用Map集合時(shí)可以發(fā)現(xiàn)對(duì)于Key和Value的類型實(shí)際上都可以由開發(fā)者任意決定叼旋,那么意味著現(xiàn)在依然可以使用自定義類來進(jìn)行Key類型的設(shè)置仇哆。對(duì)于自定義Key類型所在類中一定要覆寫hashCode()和equals()方法,否則無法查找到夫植。
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
在進(jìn)行數(shù)據(jù)保存的時(shí)候發(fā)現(xiàn)會(huì)自動(dòng)使用傳入的key的數(shù)據(jù)數(shù)據(jù)生成一個(gè)hash碼讹剔,也就是說存儲(chǔ)的時(shí)候是有這個(gè)Hash數(shù)值。
在根據(jù)key獲取數(shù)據(jù)的時(shí)候依然要將傳入的key通過hash()方法來獲取其對(duì)應(yīng)的hash碼详民,也就是說查詢的過程中首先要利用hashCode()來進(jìn)行數(shù)據(jù)查詢延欠,當(dāng)使用getNode()方法查詢時(shí)還需要使用到equals()方法。
范例:使用自定義類作為Key類型
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
@Data
@AllArgsConstructor
class Person{
private String name;
private int age;
}
public class JavaAPIDemo {
public static void main(String[] args) throws Exception {
Map<Person, String> map = new HashMap();
map.put(new Person("張三",20), "HELLO");
map.put(new Person("李四",31), "WORLD");
System.out.println(map.get(new Person("張三",20)));//null
}
}
雖然運(yùn)行你使用自定義的類作為Key的類型沈跨,但是而需要注意一點(diǎn)由捎,在實(shí)際的開發(fā)之中對(duì)于Map集合的Key常用的三種類型:String、Long饿凛、Integer狞玛。