java之Map原始碼淺析
Map是鍵值對,也是常用的資料結構。Map介面定義了map的基本行為,包括最核心的get和put操作,此介面的定義的方法見下圖:
JDK中有不同的的map實現,分別適用於不同的應用場景,如執行緒安全的hashTable和非執行緒安全的hashMap.
如下圖是JDK中map介面的子類UML類圖,其中有個特例Dictionary已經不建議使用:
Map介面中的方法我們需要關注的就是get、put 和迭代器相關的方法如entrySet()、keySet()、values()方法。
Entry
在開始分析map之前,首先了解map中元素的儲存,我們知道map可以認為是鍵值對的集合,java中map使用Entry儲存鍵值對,這是一個介面,其定義如下,簡單明瞭,介面方法主要是對鍵和值進行操作。
interface Entry<K,V> {
K getKey();
V getValue();
V setValue(V value);
boolean equals(Object o);
int hashCode();
}
AbstractMap
Map介面的抽象實現,見以下示例實現程式碼:
Map<String,String> a = /** * *抽象map實現示意,根據文件說,和list介面及其類似。 * *map分為可變和不可變兩種,不可變只需實現 entrySet方法即可,且返回的 set的迭代器不能支援修改操作。 * *可變map,需要實現put方法,然後 entrySet的迭代器也需要支援修改操作 * * *AbstractMap 裡面實現了map的梗概,但是其效率難說,比如其get方法中時採用方法entrySet實現的。 * *通常子類用更有效率的方法覆蓋之。如hashMap中覆蓋了keySet 、values 、get方法等 */ new AbstractMap<String,String>(){ /* * 返回map中的元素集合,返回的集合通常繼承AbstractSet 即可。 */ @Override public Set<Map.Entry<String, String>> entrySet() { return new AbstractSet<Map.Entry<String,String>>() { @Override public Iterator<java.util.Map.Entry<String, String>> iterator() { return null; } @Override public int size() { return 0; } }; } /* * 預設實現丟擲異常,可變map需要實現此方法 */ @Override public String put(String key, String value) { return null; } };
HashMap
hashMap繼承abstractMap,是相當常用的資料結構,採用hash雜湊的思想,可以在O(1)的時間複雜度內插入和獲取資料。其基本實現可以分析上個小節中的抽象方法,文章
淺析HashMap的實現和效能分析 已經對hashMap的實現、put和get操作進行了較詳細的說明。這裡不再贅述,關鍵看他的迭代器實現,這裡只分析下entrySet()方法,而keySet()和values()方法實現與之一脈相承。
關於迭代器,見下面摘出的部分原始碼和相關注釋:
/** * 返回map中所有的鍵值對集合,用於遍歷 */ public Set<Map.Entry<K,V>> entrySet() { return entrySet0(); } /** * 延遲初始化,只有使用的時候才構建。 * * values()和 keySet()方法也使用了類似的機制 */ private Set<Map.Entry<K,V>> entrySet0() { Set<Map.Entry<K,V>> es = entrySet; return es != null ? es : (entrySet = new EntrySet()); } /** * 真正的 enterySet,是一個內部類,其關鍵實現是迭代器實現。 * * values()和 keySet()方法也對應了相應的內部類。 * 對應的自己的迭代器實現。關鍵在於這個迭代器 * */ private final class EntrySet extends AbstractSet<Map.Entry<K,V>> { public Iterator<Map.Entry<K,V>> iterator() { return newEntryIterator(); } public boolean contains(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry<K,V> e = (Map.Entry<K,V>) o; Entry<K,V> candidate = getEntry(e.getKey()); return candidate != null && candidate.equals(e); } public boolean remove(Object o) { return removeMapping(o) != null; } public int size() { return size; } public void clear() { HashMap.this.clear(); } } /** * entrySet迭代器,繼承HashIterator,實現next方法。 * values()和 keySet()方法,也是繼承HashIterator,只是實現next 的方法不同, * * 可以對比下。 * * 關鍵在於HashIterator * * */ private final class EntryIterator extends HashIterator<Map.Entry<K,V>> { public Map.Entry<K,V> next() { return nextEntry(); } } /** * *keySet()對應的迭代器 */ private final class KeyIterator extends HashIterator<K> { public K next() { return nextEntry().getKey(); } } /** * * hashmap entrySet() keySet() values()的通用迭代器 */ private abstract class HashIterator<E> implements Iterator<E> { Entry<K,V> next; // next entry to return int expectedModCount; // For fast-fail int index; // current slot Entry<K,V> current; // current entry HashIterator() { expectedModCount = modCount; if (size > 0) { // advance to first entry Entry[] t = table; //構造時候,在陣列中查詢第一個不為null的陣列元素,即Entry連結串列,關於hashmap的實現請看 //本人前面的博文 while (index < t.length && (next = t[index++]) == null) ; } } public final boolean hasNext() { return next != null; } /** * 關鍵實現,很容易看懂,查詢next的時候,和構造迭代器的時候一樣 */ final Entry<K,V> nextEntry() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); Entry<K,V> e = next; if (e == null) throw new NoSuchElementException(); if ((next = e.next) == null) { Entry[] t = table; while (index < t.length && (next = t[index++]) == null) ; } current = e; return e; } public void remove() { if (current == null) throw new IllegalStateException(); if (modCount != expectedModCount) throw new ConcurrentModificationException(); Object k = current.key; current = null; HashMap.this.removeEntryForKey(k); expectedModCount = modCount; } }
HashTable
實現和hashMap基本一致,只是在方法上加上了同步操作。多執行緒環境可以使用它。不過現在有ConcurrentHashMap了,在高併發的時候,可以用它替換hashtable.
LinkedHashMap
hashMap可能在某些場景下不符合要求,因為放入到其中的元素是無序的。而LinkedHashMap則在一定程度上解決這個問題。
其在實現上繼承了HashMap,在儲存上擴充套件haspMap.enteySet,加入了before、after欄位,把hashMap的元素用雙向連結串列連線了起來。這個雙向連結串列決定了它的遍歷順序。其順序通常是插入map中的順序,但是它有一個欄位accessOrder當為true時,遍歷順序將是LRU的效果。
研究它的有序性,我們可以從put方法、get方法和遍歷的方法入手,首先看get方法:
/**
* 直接呼叫父類的getEntry方法。關鍵在於
* e.recordAccess(this) 這句程式碼
*/
public V get(Object key) {
Entry<K,V> e = (Entry<K,V>)getEntry(key);
if (e == null)
return null;
e.recordAccess(this);
return e.value;
}
/**
* Entry.recordAccess 方法
*
* 如果是訪問順序(accessOrder=true),那麼就把它放到頭結點的下一個位置
* 否則什麼也不做,
* 這樣就可以根據初始 accessOrder 屬性,來決定遍歷的順序。
*/
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
if (lm.accessOrder) {
lm.modCount++;
remove();
addBefore(lm.header);
}
}
Put方法:
/**
* put方法呼叫此方法,覆蓋了父類中的實現,
*/
void addEntry(int hash,K key, V value, int bucketIndex) {
createEntry(hash, key, value, bucketIndex);
// Remove eldest entry if instructed, else grow capacity if appropriate
Entry<K,V> eldest = header.after;
//回撥。如果有必要移除在老的元素,最新的元素在連結串列尾部。
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
} else {
if (size >= threshold)
resize(2 * table.length);
}
}
/**
*
*/
void createEntry(int hash,K key, V value, int bucketIndex) {
HashMap.Entry<K,V> old = table[bucketIndex];
Entry<K,V> e = new Entry<K,V>(hash, key, value, old);
table[bucketIndex] = e;
//本質是插入雙向連結串列的末尾
e.addBefore(header);
size++;
}
/**
* 插入到 existingEntry的前面,因為是雙向連結串列。當existingEntry是 header時,
* 相當於插入到連結串列最後。
*
*/
private void addBefore(Entry<K,V> existingEntry) {
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}
遍歷迭代直接使用雙向連結串列進行迭代介面,這裡不贅述,可以看原始碼很容易理解。注意的是實現上市覆蓋了父類中相關的生成迭代器的方法。
TreeMap和CurrentHashMap都可以單獨開一篇文章來分析了。這裡簡單說下。TreeMap是基於b樹map,根據key排序。CurrentHashMap是併發包中的一個強大的類,適合多執行緒高併發時資料讀寫。
相關推薦
java之Map原始碼淺析
Map是鍵值對,也是常用的資料結構。Map介面定義了map的基本行為,包括最核心的get和put操作,此介面的定義的方法見下圖: JDK中有不同的的map實現,分別適用於不同的應用場景,如執行緒安全的hashTable和非執行緒安全的hashMap. 如下圖是JDK中m
java之Set原始碼淺析
Set的介面和實現類是最簡單的,說它簡單原因是因為它的實現都是基於實際的map實現的。如 hashSet 基於hashMap,TreeSet 基於TreeMap,CopyOnWriteArraySet 基於 CopyOnWriteArrayList 。 故對其實現簡要分析。
java之list原始碼淺析
三大資料結構連結串列、樹和圖,順序表作為其中的一種,可以說是平時程式設計中最長使用到的。List介面是順序表在java中的實現,它有很多子介面和實現類,平時的程式設計中使用起來非常方便。但是更進一步,我們有必要對其實現和原理進行理解,並和資料結構中所學比較,並應用於平時的程
第一章 JAVA集合之HashMap原始碼淺析
屌絲程式設計師的奮鬥之路現在開始 java集合這一塊無論在面試或在寫程式碼中,我們都會接觸到,所以java集合是特別重要的,其中HashMap更是被我們經常用到。 一.概括 HashM
Java之map使用方法
str basic 添加元素 修改 添加 col pack mov 遍歷 1 package basic; 2 3 import java.util.HashMap; 4 import java.util.Map; 5 6 //map使用方法 7 p
java之Map集合遍歷幾種方法
package cn.com.javatest.collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; /** * java之Map集合遍歷幾種方法 * * @author:
Java之Map實現升級版
package learn.java.cn.collection; import java.util.LinkedList; /* * 思路:陣列中放list,list中放DefMap,即陣列加連結串列; * 存放時,key的hashcode值 對陣列長度求餘後作為索引值 存放到陣列中,
Java之Map實現初級版
package learn.java.cn.collection; public class MyMap { int size=0; DefMap [] defMap=new DefMap[999]; public void put(Object key,Object value) { /
Java基礎—ArrayList原始碼淺析
注:以下原始碼均為JDK8的原始碼 一、 核心屬性 基本屬性如下: 核心的屬性其實是紅框中的兩個: //從註釋也容易看出,一個是集合元素,一個是集合長度(注意是邏輯長度,即元素的個數,而非陣列長度) 其中:transient指明序列化時請忽略。 二、構造
java 之 通訊錄原始碼
1️⃣聯絡人類 package com.kll.LinkMan; public class LinkMan { //聯絡人:姓名 年齡 性別 地址 電話 private String name = null; private int age = 0
Java中HashMap原始碼淺析
在Java編碼中可以說HashMap的使用是可以說是無處不在的,對於HashMap的實現原理沒有去過多深入學習,一直停留在使用階段。現在想來還是要一探HashMap的實現原理,不要一味的只是停留在使用階段。而且HashMap的原理在很多面試中都會問到哦,所以弄清
Java 集合框架 原始碼淺析 與理解
最近在研究java原始碼,就是看一看別人寫好的東西,也不算是研究。知根知底的對以後的學習會有很大的幫助,我先去了解一下java集合框架,從總體上對這個組織和操作資料的資料結構有個淺顯得的瞭解。 從網上看了很多資料,發現這一張圖總結的還算不錯就引用過來了。但是最
[JDK1.6] JAVA集合 Hashtable 原始碼淺析
文章目錄 (一) 簡介: Hashtable 體系結構: Hashtable 欄位屬性: Hashtabl 儲存 (鍵-值) 的節點 Entry: 構造方法: 儲存 鍵-值 put(K, V) 擴容 rehash: 獲取資料
重拾Java之LinkedList原始碼閱讀
上文我們檢視ArrayList的原始碼(重拾Java之ArrayList原始碼閱讀),接著我們來瞅瞅LinkedList有什麼神奇之處。ArrayList的資料儲存方式是陣列,LinkedList裡面儲存資料的方式是連結串列,什麼是連結串列了?你可以將其理解為一列火
常用集合之LinkedHashMap原始碼淺析
眾所周知 HashMap 是一個無序的 Map,因為每次根據 key 的 hashcode對映到Entry陣列上,所以遍歷出來的順序並不是寫入的順序。 因此 JDK 推出一個基於 HashMap 但具有順序的 LinkedHashMap 來解決有排序需求的場景
Java之LinkedList原始碼解讀(JDK 1.8)
java.util.LinkedList 雙向連結串列實現的List。 基於JDK 1.8。 沒有使用標準的註釋,並適當調整了程式碼的縮排以方便介紹。 裡面很多方法的實現是一樣的,不過可以讓外界感覺其提供了更多的行為。 需要花比Array
Java之HashMap原始碼解讀
HashMap一直是陣列加連結串列的資料結構,在陣列的某個下標位置,有多次碰撞,則使用連結串列資料結果儲存。在jdk1.8中,引入了紅黑二叉查詢樹的資料結構。剛開始產生碰撞時,碰撞處仍然是連結串列結構,當連結串列的長度超過原始碼設定值8以後,該處的連結串列將轉為
Android之日曆原始碼淺析
前言:本文在整理過程中由於水平有限,若有不當之處,請指正! 1 常見介面及佈局的實現 1.1 日曆主介面: 日曆主介面是由AllInOneActivity實現,對應四種檢視型別動態載入相應的Fragment實現。各檢視如下: (1) 日檢視:在AllInOneActivi
常用集合之HashSet原始碼淺析
類圖 原始碼 構造方法 public class HashSet<E> extends AbstractSet<E> implements
JAVA之map
算法 一個數 就是 idt span .com mov bubuko 占用 1,hashmap,hashtable,ConcurrentHashMap 考問點1,hashmap的數據結構,其數據結構為數組+鏈表,把key進過hash算法得到hashcode, 然後has