Java開發筆記(六十六)映射:HashMap和TreeMap

前面介紹了兩種集合的用法,它們的共性爲每一個元素都是惟一的,區別在於一個無序一個有序。雖然說往集合裏面保存數據還算容易,但要從集合中取出數據就沒那麼方便了,由於集合竟然不提供get方法,沒有get方法怎麼從一堆數據之中挑出你想要的東西呢?難道只能從頭遍歷集合的全部元素,再逐個加以辨別嗎?顯然這個缺陷是集合的硬傷,比如去銀行開帳戶,存錢的時候你們都開開心心,但是等到取錢的時候,卻發現櫃員拿出一疊存單一張一張找過去,等找到你的存單之時,黃花菜兒都涼了。所以,實際開發中通常不多直接使用集合,而是使用集合的升級版本——映射。
映射指的是兩個實體之間存在一對一的關係,例如一個身份證號碼對應某個公民,一個書名對應某本書籍等等。有了映射關係以後,從一堆數據中尋找目標對象就好辦了,只要給定目標對應的號碼或者名稱,根據映射關係可以馬上找到號碼或名稱表明的對象。這樣下次去銀行取錢,就沒必要等櫃員兀自地翻存單,只要在電腦上輸入身份證號,便可自動找到當初的存款記錄。
Java編程經過「鍵值對」的概念來表達映射關係,它包含「鍵名」和「鍵值」兩個實體,且鍵名與鍵值是一一對應的,相同的鍵名指向的鍵值也必然是相同的。如此一來,映射裏面的每一個元素都是一組鍵值對,即「Key→Value」,在代碼中採起形如「Map<Key, Value>」的格式來表達,其中Key表示鍵名的數據類型,Value表示鍵值的數據類型。往映射裏面保存數據的時候,須要填寫完整的鍵值對信息;而從映射中取出數據,只需提供鍵名便可得到相應的鍵值信息。
因爲Map屬於接口,所以開發過程一般調用它的兩個實現類,包括哈希圖HashMap和紅黑樹TreeMap。映射與集合密切相關,它們的存儲原理也相似,好比HashMap和HashSet同樣採起哈希表結構,而TreeMap和TreeSet同樣採起二叉樹結構;不一樣的是,映射元素的惟一性和有序性是由各元素的鍵名決定的。由於HashMap和TreeMap僅僅是內部存儲結構存在差別,外部的代碼調用仍然保持一致,因此接下來就以HashMap爲例闡述映射的具體用法。
與HashSet相比,HashMap在編碼上主要有三處改動,分別說明以下。
1、建立映射實例必須同時指定鍵名和鍵值的數據類型,即HashMap後面的那對尖括號內部要有兩個類型名稱。下面是建立一個手機映射的代碼例子:html

		// 建立一個哈希映射,該映射的鍵名爲String類型,鍵值爲MobilePhone類型
		HashMap<String, MobilePhone> map = new HashMap<String, MobilePhone>();

 

2、往映射中添加新的鍵值對,調用的是put方法而非add方法,而且put方法的第一個參數爲新元素的鍵名,第二個參數爲新元素的鍵值。若是映射內部不存在該鍵名,則映射會直接增長一組鍵值對;若是映射已經存在該鍵名,則映射會自動將新的鍵值覆蓋舊的鍵值。給手機映射添加若干組手機信息的代碼示例以下:java

		map.put("米8", new MobilePhone("小米", 3000));
		map.put("Mate20", new MobilePhone("華爲", 6000));
		map.put("榮耀10", new MobilePhone("榮耀", 2000));
		map.put("紅米6", new MobilePhone("紅米", 1000));
		map.put("OPPO R17", new MobilePhone("OPPO", 2800));

 

3、遍歷映射內部的全部元素,也有好幾種方式,依次說明以下。
一、經過迭代器遍歷。首先調用映射實例的entrySet得到該映射的集合入口,再調用入口對象的iterator方法得到映射的迭代器,而後使用迭代器遍歷整個映射。在遍歷過程當中,每次調用next方法獲得的是下個位置的鍵值對記錄,此時還需調用該記錄的getKey方法才能獲取鍵值對中的鍵名,調用getValue方法獲取鍵值對中的鍵值。詳細的迭代器遍歷代碼以下所示:面試

		// 第一種遍歷方式:顯式指針,即便用迭代器
		Set<Map.Entry<String, MobilePhone>> entry_set = map.entrySet();
		Iterator<Map.Entry<String, MobilePhone>> iterator = entry_set.iterator();
		while (iterator.hasNext()) {
			// 注意這裏要先把入口取出來,這樣才能分別getKey和getValue
			Map.Entry<String, MobilePhone> iterator_item = iterator.next();
			// 獲取該鍵值對的鍵名
			String key = iterator_item.getKey();
			// 獲取該鍵值對的鍵值
			MobilePhone value = iterator_item.getValue();
			System.out.println(String.format("iterator_item key=%s, value=%s %d", 
					key, value.getBrand(), value.getPrice()));
		}

 

二、經過for循環遍歷。第一種遍歷方式能夠看到明確的迭代器對象,故而又被稱做顯式指針。其實迭代器僅僅起到了指示的做用,它徹底能夠被簡化的for循環所取代。儘管在for循環中看不到迭代器對象,但編譯器知道這裏有個隱含着的迭代器,所以for循環遍歷也被稱做隱式指針。下面是採起for循環遍歷手機映射的代碼例子:編程

		// 第二種遍歷方式:隱式指針,即便用for循環
		for (Map.Entry<String, MobilePhone> for_item : map.entrySet()) {
			// 獲取該鍵值對的鍵名
			String key = for_item.getKey();
			// 獲取該鍵值對的鍵值
			MobilePhone value = for_item.getValue();
			System.out.println(String.format("for_item key=%s, value=%s %d", 
					key, value.getBrand(), value.getPrice()));
		}

 

三、經過鍵名集合遍歷。有別於上述兩種依次遍歷鍵值對的方式,第三種方式先調用映射的keySet方法得到只包含鍵名的集合,再經過遍歷鍵名集合來獲取每一個鍵名對應的鍵值。該方式的映射遍歷代碼示例以下:性能

		// 第三種遍歷方式:先得到鍵名的集合,再經過鍵名集合遍歷整個映射
		// 注意:HashMap的keySet方法返回的是無序集合
		Set<String> key_set = map.keySet();
		for (String key : key_set) {
			// 經過鍵名獲取該鍵值對的鍵值
			MobilePhone value = map.get(key);
			System.out.println(String.format("set_item key=%s, value=%s %d", 
					key, value.getBrand(), value.getPrice()));
		}

 

四、經過forEach方法遍歷。顯然前面的幾種方式都很囉嗦,自從Java8引入了Lambda表達式,遍歷映射的全部元素也變得異常簡潔了,單單下面一行代碼就所有搞定:編碼

		// 第四種遍歷方式:使用forEach方法夾帶Lambda表達式進行遍歷
		map.forEach((key, value) -> 
				System.out.println(String.format("each_item key=%s, value=%s %d", 
						key, value.getBrand(), value.getPrice())) );

  

最後簡要描述一下TreeMap背後的紅黑樹概念,這是各家公司面試Java開發人員的常見知識點。紅黑樹是一種自平衡二叉查找樹,所謂平衡指的是像天平那樣左右兩邊的重量相等,從而使天平保持不偏不倚的水平狀態。固然對於二叉樹來講,絕對的平衡是難以達到的,只能作到相對平衡,即左右兩棵子樹的高度差不大於1,同時左右兩棵子樹自己也是平衡二叉樹。鑑於平衡二叉樹的平衡特性,從根節點出發到達每一個葉子節點的距離比較平均,使得整棵樹的查找性能相對優越。高度平衡的二叉樹在進行查找操做時表現近乎完美,可是給它添加新節點或者刪除原節點的時候,二叉樹爲了在節點增刪以後仍然保持高度平衡,可能不得很少次左旋右旋,一旦這棵樹遇到頻繁的節點添加和刪除操做,它的總體性能將會急劇降低,真可謂魚與熊掌不可兼得。
爲了兼顧平衡二叉樹的查找性能和節點增刪後的旋轉性能,另外一種折中的自平衡二叉樹(即紅黑樹)應運而生,紅黑樹並不是高度平衡的,而是一種相對平衡,它的每次插入操做最多隻要兩次旋轉,刪除操做最多隻要三次旋轉。平衡二叉樹要求左右子樹的高度差不大於1,而紅黑樹只要求左右子樹的高度差不大於根節點到葉子節點的最短距離,也就是說,根節點到葉子節點的最長距離不大於最短距離的兩倍。紅黑樹不處於最理想的平衡狀態,而是處於大體平衡的狀態,那麼對它的葉子節點進行增刪之時,這樣無論左旋仍是右旋,只需少數幾回旋轉就能讓左右字書的高度差保持在合理的範圍內了。指針

 

更多Java技術文章參見《Java開發筆記(序)章節目錄orm

相關文章
相關標籤/搜索