<h3>寫在開始之前:</h3>
Java為數(shù)據(jù)結(jié)構(gòu)中的映射定義了一個接口java.util.Map,此接口主要有四個常用的實(shí)現(xiàn)類,分別是HashMap、Hashtable、LinkedHashMap和TreeMap。
(1) HashMap:它根據(jù)鍵的hashCode值存儲數(shù)據(jù),大多數(shù)情況下可以直接定位到它的值,因而具有很快的訪問速度,但遍歷順序卻是不確定的。 HashMap最多只允許一條記錄的鍵為null,允許多條記錄的值為null。HashMap非線程安全,即任一時刻可以有多個線程同時寫HashMap,可能會導(dǎo)致數(shù)據(jù)的不一致。如果需要滿足線程安全,可以用 Collections的synchronizedMap方法使HashMap具有線程安全的能力,或者使用ConcurrentHashMap。
(2) Hashtable:Hashtable是遺留類,很多映射的常用功能與HashMap類似,不同的是它承自Dictionary類,并且是線程安全的,任一時間只有一個線程能寫Hashtable,并發(fā)性不如ConcurrentHashMap,因?yàn)镃oncurrentHashMap引入了分段鎖。Hashtable不建議在新代碼中使用,不需要線程安全的場合可以用HashMap替換,需要線程安全的場合可以用ConcurrentHashMap替換。
(3) LinkedHashMap:LinkedHashMap是HashMap的一個子類,保存了記錄的插入順序,在用Iterator遍歷LinkedHashMap時,先得到的記錄肯定是先插入的,也可以在構(gòu)造時帶參數(shù),按照訪問次序排序。
(4) TreeMap:TreeMap實(shí)現(xiàn)SortedMap接口,能夠把它保存的記錄根據(jù)鍵排序,默認(rèn)是按鍵值的升序排序,也可以指定排序的比較器,當(dāng)用Iterator遍歷TreeMap時,得到的記錄是排過序的。如果使用排序的映射,建議使用TreeMap。在使用TreeMap時,key必須實(shí)現(xiàn)Comparable接口或者在構(gòu)造TreeMap傳入自定義的Comparator,否則會在運(yùn)行時拋出java.lang.ClassCastException類型的異常。
本文介紹的HashMap主要基于JDK 1.7 嘍,JDK 1.8 里對HashMap做了一些優(yōu)化,比如采用使用紅黑樹結(jié)構(gòu)、優(yōu)化擴(kuò)容算法等在本文不做介紹。
<h3>背景介紹</h3>
在做改版過程中,有個地方是5個并排的按鈕,分別跳轉(zhuǎn)到不同的頁面。機(jī)智如我寫下了如下代碼來初始化5個按鈕(Key是按鈕對應(yīng)資源文件ID,Value是按鈕點(diǎn)擊事件):
private HashMap<Integer, View.OnClickListener> resources = new HashMap<>();
結(jié)果發(fā)現(xiàn)5個按鈕的顯示順序與添加順序不一致。才突然間記起HashMap是不能保證順序的。那么為什么它不能保證順序呢?它是如何存儲數(shù)據(jù)的呢?
<h4>簡介</h4>
Java 中HashMap是Map的最常用的實(shí)現(xiàn)類。HashMap 是一個散列表,它存儲的內(nèi)容是鍵值對(key-value)映射。HashMap 繼承于AbstractMap,實(shí)現(xiàn)了Map、Cloneable、java.io.Serializable接口。HashMap 的實(shí)現(xiàn)不是同步的,這意味著它不是線程安全的。它的key\value都可以為null。
<h4>數(shù)據(jù)存取</h4>
HashMap是基于Hashing的原理,我們一般使用put(key, value)存儲對象到HashMap中,使用get(key)從HashMap中獲取對象。
當(dāng)我們給put()方法傳遞鍵和值時,我們先對鍵調(diào)用hashCode()方法,返回的hashCode用于找到Bucket位置來儲存Entry對象。
如果存儲的時候遇到不同的對象有相同的hashCode呢?
需要注意的是,Bucket是一個LinkedList結(jié)構(gòu)。如果多個元素的Key對象的HasCode相同,則存儲于同一個LinkedList。而LinkedList中包含了每個元素的鍵值信息,所以當(dāng)取數(shù)據(jù)遇到Key對應(yīng)的HashCode相同的情況也可以通過Key值的比較來查找到想要的元素。
<h4>加載因子</h4>
加載因子是表示Hsah表中元素的填滿的程度.加載因子越大,填滿的元素越多,空間利用率高了,但沖突的機(jī)會加大了。反之,加載因子越小填滿的元素越少,好處是沖突的機(jī)會減小了,但空間浪費(fèi)多了。沖突的機(jī)會越大,則查找的成本越高.反之,查找的成本越小.因而,查找時間就越小.因此,必須在 "沖突的機(jī)會"與"空間利用率"之間尋找一種平衡與折衷. 這種平衡與折衷本質(zhì)上是數(shù)據(jù)結(jié)構(gòu)中有名的"時-空"矛盾的平衡與折衷.
默認(rèn)情況下HashMap是自增的,它的初始長度是16,加載因子是 0.75 。當(dāng)HashMap中的條目數(shù)超出了加載因子與當(dāng)前容量的乘積時,通過調(diào)用 rehash 方法將容量 <strong> 翻倍 </strong>。
<h4>Android中的HashMap</h4>
因?yàn)镠ashMap內(nèi)存使用不充足。在內(nèi)存相對吃緊的移動客戶端簡直是浪費(fèi)嘛。
所以Google提供了兩個新的數(shù)據(jù)結(jié)構(gòu) SparseArray和ArrayMap來供大家使用。但是這兩個數(shù)據(jù)結(jié)構(gòu)由于各自的局限性,并不能完全取代HashMap。SparseArray只能適用于key為int的數(shù)據(jù)結(jié)構(gòu)。而ArrayMap的問題是在大數(shù)據(jù)量下性能較低。所以,三者之中具體選用哪個還得看情況而定哦。
說了這么多,那5個按鈕到底如何保證順序呢?
答:使用LinkedHashMap!