總結一下Java中常用容器之Map

今天又有點時間了,所以還是抽出點時間寫點小東西吧。

其實關於Java中常用容器的知識點,我很早之前就有總結過,只不過在自己手寫的筆記本上(忽然發現很久沒手寫筆記了啊奮鬥)趁着今天的機會,自己再整理一波,順便給大家貼上來。
今天暫時只整理一下Map的東西,其他的容器,像List啊 Set啊 這些等有時間也會整理一下貼出來。
好了,首先先給大家貼一張圖上來,這圖是之前在網上看到的一張挺經典的圖,很好的描述了Java中各容器之間的關係,我感覺還是比較清晰的,所以放上來給大家看一下。



可以看到這個圖很清晰描述了各個容器之間的關係,標紅色的部分是大家常用到的

圖上的東西還是挺多,像Iterator迭代器啊,Comparable接口啊, Comparator接口啊,這些有時間再講。
先大概提一下,基本上只要涉及到和Tree相關的容器,也就是底層實現是二叉樹的容器,基本上都會用到Comparable接口和Comparator接口,這兩個接口一個是讓放入容器中的元素具有比較性,另一個是讓容器
剛一初始化容器本身就帶有可比較元素的功能,好了,這個先不多說了,有時間具體講。

看一個東西,要先看頂層的接口,再看其具體的實現類,因爲頂層的接口裏邊都是這一類事物共同具有的共性的東西,所以大家先看下Map接口中的東西


因爲是容器嘛,所以裏邊的操作必然是添加、刪除、判斷、加獲取。
把上邊的方法大概歸一下類
1.添加
put(K key,V value)
putAll(Map<? extendsK,? extends V> m)
2.刪除
 remove(Object key)
3.判斷
4.獲取
get(Object key)


上邊歸類列出的是常用的方法,看字面意思也能知道都是什麼意思,不多說了。

接口中共性的方法看完後,接下來就該看Map的小弟了


可以看到實現了Map接口的小弟有這麼多,咱們常用的基本就三個
|--Hashtable:底層實現是哈希表數據結構,不可以存入null鍵和null值,JDK1.0出現的,是線程同步的
|--HashMap:底層實現是哈希表數據結構,允許存入null鍵和null值,JDK1.2出現的,是線程不同步的
|-- TreeMap:底層實現是二叉樹數據結構,JDK1.2出現的,線程不同步,因爲是二叉樹數據結構,
所以要想往該容器裏存東西必須保證你存入的key是具有比較性的,或者是該容器自身
具備比較功能,所以大家可以看到下圖裏TreeMap的構造方法裏都要求了鍵的比較性排序
如果你的鍵具有比較性,就可以放入該容器,這個稱之爲按照自然順序放入;如果你的鍵
你沒有讓它具備比較性(也就是你沒讓你的鍵這個類實現Comparable接口),則你就得
讓你的容器一構造出來就具備比較的功能,就是傳入一個Comparator比較器,就是下圖的
第二種構造方式,這種稱爲按照指定的比較順序放入,而不是元素自身具體的自然排序


因爲Map中的key是唯一的,所以必然會想到那各個實現的Map接口的實現類都是怎麼保證key的唯一性呢?

Hashtable和HashMap從名字就能看出,因爲他們底層結構是哈希表實現的,所以他們判斷唯一性的
標準是首先根據hashCode方法判斷,如果返回的hashCode值不一樣它就直接判斷爲不重複;
如果返回的hashCode一樣,它就會繼續根據equals方法進行判斷,如果equals返回true則認爲重複,
如果返回false則認爲不重複。

TreeMap從名字看出來底層是二叉樹,二叉樹判斷元素的唯一性是根據元素的比較性,
實現了Comparable接口以後,實現裏邊的compareTo方法,這個方法會返回 1、0、-1
如果返回0則認爲兩個元素相等,就是重複,返回1,則會把比較的元素放到被比較元素的右邊,
如果返回-1,則會把比較的元素放到被比較元素的左邊,因爲放入的時候是有規律的,
所以取出的時候也按照規律取出的話,這樣放入二叉樹結構容器裏的元素就相當於是排了序的。
因爲你寫的實體類在應用中很有可能就會被放到各種不同的容器裏去,
所以最最正規的做法就是你寫的實體類最好
去自己重寫hashCode方法和equals方法,其次就是讓你的實體實現Comparable接口,
就是讓你的實體放到二叉樹的,容器中後可以自然排序。
大家可以隨便看一個Java JDK裏的類,基本上都實現了Comparable接口
隨便看個String和Integer的吧


可以看到String和Integer都實現了Comparable接口。

關於放入二叉樹結構容器必須具有比較性啊,排序啊,這些問題到時候有時間我會整理一篇出來
這裏就先不多說了。

接下來看看一個Map中常用,也比較重要的一個,就是怎麼循環取出map中的多個元素
有兩種方法
1.Map中有個方法 keySet()
這個方法是把map中的key轉成set集合返回返回值就是set<K>,然後通過key找到value
舉個生活中的例子,這個方式就相當於生活中通過老公(key)能找到妻子(value)似的,
舉的這個例子會和下邊的進行對比

下面代碼寫一下,實驗一下
package com.cj.map;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * 
 * @author caoju
 *
 */
public class Test {
	public static void main(String[] args) {
		Map<String, String> map = new HashMap<String, String>();
		map.put("001", "zhangsan1");
		map.put("002", "lisi");
		map.put("003", "wangwu");
		//得到map的key的集合
		Set<String> keySet = map.keySet();
		//得到map的key的集合後,循環的根據key去取value就好了
		for (String key : keySet) {
			String value = map.get(key);
			System.out.println(key+":"+value);
		}
	}
}
運行結果:





2.Map中還有個方法entrySet() 
返回此映射中包含的映射關係的set視圖。這個是JDK API官方的解釋
它的返回值是Set<Map.Entry<K,V>>看起來很詭異。但是Set是個集合那它泛型裏邊一定是個類型
所以Map.Entry<K,V>肯定是個類型,只不過寫法很特殊。

看文檔可以知道,Map接口中有個內部接口叫Entry
看Map接口的源碼也能出來

但是源碼上是直接在Map接口中定義了一個interface Entry<K,V>,
爲什麼API文檔上卻寫成了Map.Entry<K,V>呢?
對內部類比較熟悉的的話,對這個寫法應該不會覺得懵逼。
咱們平常要想直接訪問內部類中的東西,那肯定得new一個內部類出來,才能訪問其內容。
所以咱們會這麼寫,Outer.Inner inner = new Outer().new Inner();
所以上邊的Outer.Inner就是一種類型,這個類型就是指內部類類型,
Map.Entry<K,V>和這個不是同樣的道理嗎
也就是說,如果interface Entry<K,V> 這個接口不是在Map內部,那寫它的類型的時候直接
就可以寫成Entry<K,V>,但是它現在是Map的內部接口,所以就得寫成Map.Entry<K,V>。
估計小夥伴麼會有疑問?爲什麼這麼寫呢,這麼麻煩。。。
其實這麼寫也是有原因的,因爲如果其他接口中也有個Entry<K,V>的內部接口,
那你直接寫Entry<K,V>,你到底是指誰裏邊的Entry<K,V>呢,是不是就有歧義了,
所以寫內部類或者內部接口的類型時需要在前邊帶上它的外部類的類名。

但是Map.Entry<K,V>中玩意是個接口,Map中的entrySet()的返回值Set<Map.Entry<K,V>>
裏邊的Map.Entry<K,V>肯定不是一個接口了,肯定是個實現了Map.Entry<K,V>這個接口
的具體實現類,具體怎麼返回的,不需要咱們管,不同的具體Map他們自己裏邊
entrySet()方法肯定會有具體的實現的。

但是Map中爲什麼要定義interface Entry<K,V>這個接口呢?
我覺得是因爲Map裏不是存的key value關係的數據嗎?
Entry<K,V>這個接口就是描述key value之間的這個關係。
Java認爲一切皆對象嘛,所以它就把這個關係也封裝成個接口了。
上邊第一種方法舉了個生活中老公妻子的例子
現在再舉個生活中的例子,還是剛纔的老公和妻子,Entry<K,V>就相當於生活中的結婚證書,
這個證書描述了老公和妻子之間的關係。
entrySet()的返回值Set<Map.Entry<K,V>>就相當於返回的這個Set集合裏
裝了一堆結婚證書(也就是一堆Entry<K,V>
第一個例子是通過老公(key)找到妻子(value),現在這種就是通過結婚證書能找到結婚證上
的老公和老婆。大家可以看下圖,Entry<K,V>類裏邊getKey()和getValue()都有
(這栗子不知道恰當不恰當。。。)
但是具體不同的Map他們各自對這個關係描述的實現也不一樣,
就是說實現了Map接口的這些小弟們,像Hashtable、HashMap、TreeMap他們內部
也都具體的實現了interface Entry<K,V>這個接口。舉個HashMap例子的截圖,
大家可以看下邊的圖是HashMap中對這個內部接口的具體實現



但是爲什麼這麼做呢,我的理解是,因爲得容器裏先有元素(也就是key,value)纔有關係,
而且具體的到底怎麼取出這個關係,具體的容器它自己最清楚,所以就定義在了容器的內部,我覺得這個和Collection接口裏的Iterator迭代器原理差不多,迭代器估計從JDK1.5以後出現了高級for以後大家用的少了,有時間大家可以看看,我覺得剛纔說的那個和Collection接口和它內部的Iterator迭代器很類似。看下邊的圖,去ArrayList源碼看一下,裏邊也是有一個內部類實現了Iterator接口的。
我理解的就是Map和Map.Entry<K,V>的關係就和LIst和Iterator的關係一樣。我是這麼理解的。



好啦,多的不說了,下面代碼演示一下
package com.cj.map;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * 
 * @author caoju
 *
 */
public class Test {
	public static void main(String[] args) {
		
		Map<String, String> map = new HashMap<String, String>();
		map.put("001", "zhangsan1");
		map.put("002", "lisi");
		map.put("003", "wangwu");
		Set<Map.Entry<String, String>> es = map.entrySet();
		for (Map.Entry<String, String> e : es) {
			String key = e.getKey();
			String value = e.getValue();
			System.out.println(key+":"+value);
		}
		
	}
}
運行結果:



可以看到兩種方式結果一樣的,但是在Map的內部它具體的實現是不一樣的。
好了,今天就到這兒吧

以上寫的僅僅是自己的理解,供大家參考,若有錯誤的地方希望大家包涵並及時指出,3Q

晚安








發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章