Map集合

??之前對(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。

image.png

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

范例:使用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

范例:觀察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è)比較男娄。

Collection與Map

??發(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狞玛。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末软驰,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子心肪,更是在濱河造成了極大的恐慌锭亏,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件硬鞍,死亡現(xiàn)場(chǎng)離奇詭異贰镣,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)碑隆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門上煤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來劫狠,“玉大人独泞,你說我怎么就攤上這事苔埋。” “怎么了荞膘?”我有些...
    開封第一講書人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵羽资,是天一觀的道長屠升。 經(jīng)常有香客問我狭郑,道長,這世上最難降的妖魔是什么微服? 我笑而不...
    開封第一講書人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任以蕴,我火速辦了婚禮丛肮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘焚廊。我一直安慰自己习劫,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開白布袒餐。 她就那樣靜靜地躺著灸眼,像睡著了一般焰宣。 火紅的嫁衣襯著肌膚如雪捕仔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,737評(píng)論 1 305
  • 那天闸天,我揣著相機(jī)與錄音,去河邊找鬼瓤逼。 笑死库物,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的诱告。 我是一名探鬼主播民晒,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼沃但!你這毒婦竟也來了佛吓?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤淤刃,失蹤者是張志新(化名)和其女友劉穎钝凶,沒想到半個(gè)月后唁影,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡哟沫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年嗜诀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了隆敢。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拂蝎。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡温自,死狀恐怖皇钞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情馆里,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布丙者,位于F島的核電站蔓钟,受9級(jí)特大地震影響卵贱,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜兰绣,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一缀辩、第九天 我趴在偏房一處隱蔽的房頂上張望踪央。 院中可真熱鬧,春花似錦健无、人聲如沸液斜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽渗磅。三九已至屎媳,卻和暖如春夺溢,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背丹禀。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留持搜,地道東北人葫盼。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓村斟,卻偏偏與公主長得像,于是被迫代替她去往敵國和親孩灯。 傳聞我的和親對(duì)象是個(gè)殘疾皇子逾滥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 12.1 Map集合 Map集合用于保存具有映射關(guān)系的數(shù)據(jù)讥巡,key和value都可以是任意類型的數(shù)據(jù)舔哪,key不允許...
    王毅巽閱讀 830評(píng)論 0 3
  • Map 今天的主要內(nèi)容 Map接口概述 Map集合的兩種遍歷方式通過鍵找值keySet通過鍵值對(duì)對(duì)象獲取鍵和值涉及...
    須臾之北閱讀 255評(píng)論 0 0
  • HashMap數(shù)據(jù)結(jié)構(gòu) HashMap是基于hashing的原理吱涉; value>插到table[i]中外里,如果有兩個(gè)...
    魏樹鑫閱讀 526評(píng)論 0 1
  • Map接口不屬于Collection的繼承或?qū)崿F(xiàn),Map接口是維護(hù)鍵值對(duì)的鳖链,并且不能有重復(fù)的鍵墩莫,但是在Map的底層...
    螺絲釘25閱讀 297評(píng)論 0 0
  • 正在趕來戰(zhàn)場(chǎng)的路上 你聽 DurDurDur!!! 明天進(jìn)入狀態(tài)喔狂秦! 今天沒有Dur! 任性!
    Echo也可以閱讀 366評(píng)論 0 0