Android內(nèi)存優(yōu)化-----使用ArrayMap/ArraySet代替HashMap/HashSet

[TOC]

1. 為什么要用ArrayMap/ArraySet

在Android開發(fā)中虎敦,經(jīng)常會(huì)用到HashMap/HashSet等集合類,但是Java在設(shè)計(jì)集合類的時(shí)候并沒有考慮到內(nèi)存寶貴場(chǎng)景下優(yōu)化身隐。而對(duì)Android系統(tǒng)來說內(nèi)存是非常寶貴的資源,所以Google針對(duì)Android系統(tǒng)的特性提供了 HashMap/HashSet的代替品唯灵,即ArrayMap/ArraySet贾铝。這幾個(gè)類位于android.util.*包下面。

2. 簡單回憶下HashMap<K, V>的實(shí)現(xiàn)

2.1 數(shù)據(jù)接口

在Java中埠帕,HashMap是通過數(shù)組加引用組合實(shí)現(xiàn)的數(shù)據(jù)結(jié)構(gòu)垢揩,結(jié)構(gòu)圖如下:

注:圖片來自網(wǎng)絡(luò)

)

從圖中我們看到,數(shù)組的每個(gè)元素之后會(huì)跟著一條鏈表敛瓷。我們應(yīng)該很容易想到這是為了解決HashCode沖突而設(shè)計(jì)的叁巨,使用鏈地址法解決沖突。

順便提一下解決沖突的常用四個(gè)方法:

  1. 鏈地址法
  2. 再Hash法
  3. 開放地址發(fā)
  4. 建立公共溢出區(qū)

2.2 HashMap的數(shù)據(jù)結(jié)構(gòu)

HashMap有三個(gè)構(gòu)造方法:

HaspMap()
HaspMap(int initialCapacity)
HashMap(int initialCapacity, float loadFactor)
  • 其中 initialCapacity是一個(gè)初始化容量大小呐籽,指的是table數(shù)組的初始大小锋勺。
  • loadFactor是負(fù)載因子,當(dāng)table數(shù)組的實(shí)際容量超過(initialCapacity*loadFactor)的時(shí)候绝淡,table數(shù)組就會(huì)擴(kuò)容宙刘。
  • initialCapacity默認(rèn)值是15 而loadFactor的默認(rèn)值是0.75 他倆都是影響HashMap性能的重要因素。

首先看下構(gòu)造函數(shù)的源碼:

public HashMap(int initialCapacity, float loadFactor) {
        // 首先檢查各個(gè)參數(shù)的合法性
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: "
                    + initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: "
                    + loadFactor);

        // 計(jì)算出大于 initialCapacity 的最小的 2 的 n 次方值牢酵。
        // HashMap的實(shí)際數(shù)組大小都是2^n,是為了優(yōu)化而設(shè)計(jì)
        int capacity = 1;
        while (capacity < initialCapacity)
            capacity <<= 1;
       
        this.loadFactor = loadFactor;
        // 計(jì)算極限容量 超過后將會(huì)進(jìn)行擴(kuò)容
        threshold = (int) (capacity * loadFactor);
        //初始化table數(shù)組
        table = new Entry[capacity];
        init();
    }

其中有一個(gè)EntryHashMap的內(nèi)部類:

Entry() {
     K key;                           
     V value;
     Entry<K, V> next;
     int hash;
}

它是實(shí)際儲(chǔ)存Key和Value的實(shí)體衙猪。next是指向下一個(gè)元素的引用馍乙,hash是key的hash值布近。

2.3 數(shù)據(jù)的存放

再來看下put方法的源碼:

public V put(K key, V value) {
        // 如果key的值是null,直接將其放在null位置上丝格,也就是第一個(gè)位置上
        if (key == null)
            return putForNullKey(value);
        //計(jì)算key的hash值
        int hash = hash(key.hashCode());
        //計(jì)算key hash 值在 table 數(shù)組中的位置
        int i = indexFor(hash, table.length);
        //從i出開始迭代 e,找到 key 保存的位置
        for (Entry<K, V> e = table[i]; e != null; e = e.next) {
            Object k;
            //判斷該條鏈上是否有hash值相同的(key相同)
            //若存在相同撑瞧,則直接覆蓋value,返回舊value
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;    //舊值 = 新值
                e.value = value;
                e.recordAccess(this);
                return oldValue;     //返回舊值
            }
        }
        //修改次數(shù)增加1
        modCount++;
        //將key显蝌、value添加至i位置處
        addEntry(hash, key, value, i);
        return null;
    }
  • 首先判斷key值是否為null预伺,如果是null,則將hash值當(dāng)成0處理 否則
  • 根據(jù)key對(duì)象的hashCode()方法計(jì)算出hash值曼尊,然后再將hash值轉(zhuǎn)化成在table中的索引 然后
  • 查找該索引處的鏈表中是否存在key相同的對(duì)象酬诀,如果有則將其覆蓋,并且返回舊值 否則
  • 將key骆撇、value存放于該數(shù)組索引處鏈表的第一個(gè)位置上瞒御,其中這一步是在addEntry()方法中完成的
void addEntry(int hash, K key, V value, int bucketIndex) {
        //獲取bucketIndex處的Entry
        Entry<K, V> e = table[bucketIndex];
        //將新創(chuàng)建的 Entry 放入 bucketIndex 索引處,并讓新的 Entry 指向原來的 Entry 
        table[bucketIndex] = new Entry<K, V>(hash, key, value, e);
        //若HashMap中元素的個(gè)數(shù)超過極限了神郊,則容量擴(kuò)大兩倍
        if (size++ >= threshold)
            resize(2 * table.length);
    }

2.4 數(shù)據(jù)的取出

對(duì)于HashMap肴裙,取出值是相對(duì)簡單的:

public V get(Object key) {
        // 若為null,調(diào)用getForNullKey方法返回相對(duì)應(yīng)的value
        if (key == null)
            return getForNullKey();
        // 根據(jù)該 key 的 hashCode 值計(jì)算它的 hash 碼  
        int hash = hash(key.hashCode());
        // 取出 table 數(shù)組中指定索引處的值
        for (Entry<K, V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) {
            Object k;
            //若搜索的key與查找的key相同涌乳,則返回相對(duì)應(yīng)的value
            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
                return e.value;
        }
        return null;
    }

2.5 HashMap的小結(jié)

從上面的分析中可以看出蜻懦,HashMap的存取速度都是相對(duì)比較快的,在一般情況下都能實(shí)現(xiàn)O(1)的速度夕晓,但是從初始容量和負(fù)載因子都可以看出阻肩,這種快速的讀取都是通過內(nèi)存來換取,而對(duì)于移動(dòng)設(shè)備來講运授,內(nèi)存又是很重要的烤惊,所以,Google為我們提供了Arraymap來代替它吁朦。

3. ArrayMap的簡單分析

在ArrayMap的內(nèi)部柒室,使用一個(gè)hash數(shù)組加一個(gè)kay,value數(shù)組來儲(chǔ)存逗宜。

Google官方圖

當(dāng)你想獲取某個(gè)value的時(shí)候雄右,ArrayMap會(huì)計(jì)算輸入key轉(zhuǎn)換過后的hash值,然后對(duì)hash數(shù)組使用二分查找法尋找到對(duì)應(yīng)的index纺讲,然后我們可以通過這個(gè)index在另外一個(gè)數(shù)組中直接訪問到需要的鍵值對(duì)擂仍。如果在第二個(gè)數(shù)組鍵值對(duì)中的key和前面輸入的查詢key不一致,那么就認(rèn)為是發(fā)生了碰撞沖突熬甚。為了解決這個(gè)問題逢渔,我們會(huì)以該key為中心點(diǎn),分別上下展開乡括,逐個(gè)去對(duì)比查找肃廓,直到找到匹配的值(開放地址法)智厌。如下圖所示:

Google官網(wǎng)圖

可以看出,ArrayMap將內(nèi)存的使用率提高了很多盲赊,但是讀取的復(fù)雜度卻是O(lgN)(因?yàn)槎址ú檎遥┫撑簟K栽跀?shù)據(jù)量不是很大(千級(jí)以內(nèi))的時(shí)候,我們使用ArrapMap可以優(yōu)化內(nèi)存哀蘑,并且存取速度幾乎是不受什么影響的诚卸。

其實(shí)這里還有一點(diǎn),就是自動(dòng)裝箱問題绘迁,假設(shè)我們把一個(gè)key是int類型的數(shù)據(jù)存儲(chǔ)時(shí)合溺,HashMap會(huì)將int值自動(dòng)裝箱成Integer對(duì)象,一個(gè)對(duì)象和一個(gè)基本類型所占的內(nèi)存大小是差別很大的脊髓,所以為了避免這樣情況辫愉,Google提供了SparseIntArray,SparseBoolArray等一系列大禮包供我們使用。

4. 總結(jié)

總結(jié)一句話将硝,在數(shù)據(jù)量千級(jí)以內(nèi)恭朗,使用ArrayMap ArraySet或者SparseIntMap等來代替HashMap,以此節(jié)約寶貴的內(nèi)存資源依疼。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末痰腮,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子律罢,更是在濱河造成了極大的恐慌膀值,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件误辑,死亡現(xiàn)場(chǎng)離奇詭異沧踏,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)巾钉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門翘狱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人砰苍,你說我怎么就攤上這事潦匈。” “怎么了赚导?”我有些...
    開封第一講書人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵茬缩,是天一觀的道長。 經(jīng)常有香客問我吼旧,道長凰锡,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮寡夹,結(jié)果婚禮上处面,老公的妹妹穿的比我還像新娘厂置。我一直安慰自己菩掏,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開白布昵济。 她就那樣靜靜地躺著智绸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪访忿。 梳的紋絲不亂的頭發(fā)上瞧栗,一...
    開封第一講書人閱讀 49,764評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音海铆,去河邊找鬼迹恐。 笑死,一個(gè)胖子當(dāng)著我的面吹牛卧斟,可吹牛的內(nèi)容都是我干的殴边。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼珍语,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼锤岸!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起板乙,我...
    開封第一講書人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤是偷,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后募逞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蛋铆,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年放接,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了刺啦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡透乾,死狀恐怖洪燥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情乳乌,我是刑警寧澤捧韵,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站汉操,受9級(jí)特大地震影響再来,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一芒篷、第九天 我趴在偏房一處隱蔽的房頂上張望搜变。 院中可真熱鬧,春花似錦针炉、人聲如沸挠他。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽殖侵。三九已至,卻和暖如春镰烧,著一層夾襖步出監(jiān)牢的瞬間拢军,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來泰國打工怔鳖, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留茉唉,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓结执,卻偏偏與公主長得像度陆,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子昌犹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

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

  • 實(shí)際上坚芜,HashSet 和 HashMap 之間有很多相似之處,對(duì)于 HashSet 而言斜姥,系統(tǒng)采用 Hash 算...
    曹振華閱讀 2,510評(píng)論 1 37
  • HashMap 是 Java 面試必考的知識(shí)點(diǎn)鸿竖,面試官從這個(gè)小知識(shí)點(diǎn)就可以了解我們對(duì) Java 基礎(chǔ)的掌握程度。網(wǎng)...
    野狗子嗷嗷嗷閱讀 6,661評(píng)論 9 107
  • 65d1a170ef1e閱讀 336評(píng)論 0 0
  • 有你铸敏,花也燦爛缚忧, 如清風(fēng)拂面, 悠悠杈笔,然然闪水; 想你,斜月彎彎蒙具, 無夢(mèng)的夜里球榆, 你的誓約在唇邊, 悄悄禁筏,纏綿… 你是...
    因?yàn)閻鬯詧?zhí)著閱讀 360評(píng)論 0 2