HashMap的學習以及原始碼分析
Hashmap
HashMap繼承AbstractMap類,實現了Map介面(由下圖可見),在java集合中,它是一個基本的儲存資料的結構。他的底層是由 陣列+連結串列 構成,通過特定的雜湊函式從 鍵(key)來定位值。
HashMap的結構形式大概如圖所示:
構造雜湊函式
1.直接定址法
f(key) = key
f(key) = a*key+y
2.除留餘數法
f(x) = x%m
hash碰撞 m n f(m) = f(n) (m不等於n)
解決方式:
1.鏈地址法
2.探測發
hashmap的特點:
1、資料以key-value鍵值對形式存在
2、HashMap中鍵不能重複,即同一個key只能出現一次、若key相同,value會被覆蓋
3、可以存在key為null的情況,value可以存在多個為null的情況
4、資料不能保證順序性
hashmap的原始碼分析:
1.方法、屬性、繼承關係
HashMap的主幹是一個Entry陣列。Entry是HashMap的基本組成單元,每一個Entry包含一個key-value鍵值對。
繼承關係:
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
繼承了AbstractMap抽象類,主要實現了map介面,當前HashMap也能實現克隆序列化Cloneable。
屬性:
//HashMap的初始容量為16; static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 //初始容量既可以傳參給定,也可以給預設值,主要作用是對資料大小進行初始化,容量最大值 1<<30 static final int MAXIMUM_CAPACITY = 1 << 30; //載入因子:在擴容時使用(使用方法put),預設大小是0.75(loadFactor:用來計算threshold) static final float DEFAULT_LOAD_FACTOR = 0.75f; //底層儲存資料,陣列的資料型別是Entry static final Entry<?,?>[] EMPTY_TABLE = {}; //threshold擴容閾值:計算方式如上:容量*載入因子得到, 決定map是否需要擴容,threshold = capacity * loadFactor(hashmap的size的最大值) threshold = (int) Math.min(capacity * loadFactor) //Entry的資料型別包含儲存的key——value資料,hash,還有entry型別的next屬性,還可以看出entry資料是通過連結串列來組織連線 底層資料結構是:陣列+連結串列 static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; Entry<K,V> next; int hash;
2.CRUD
put新增元素的過程:
1.判斷table是否為空陣列,是則建立陣列(注意:threshold值得改變)
2.key為null的新增操作做特殊處理,將該key對應的entry實體放入table[0]的位置(存在該key為null的實體進行覆蓋操作)
3.過程通過key進行hash
4.通過indexfor方法來定位資料儲存的索引位置
5.遍歷該位置的連結串列,判斷是否存在該key的實體(k.hashcode == equals),有則將value替換
6.將該元素插入到對應的索引的連結串列 size
擴容(2*table.length)方式擴容(擴容時機)
插入位置(第一個位置,頭插)
public V put(K key, V value) {
//如果table陣列為空陣列{},進行陣列填充(為table分配實際記憶體空間),入參為threshold,此時threshold為initialCapacity 預設是1<<4(24=16)
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
//如果key為null,儲存位置為table[0]或table[0]的衝突鏈上
if (key == null)
return putForNullKey(value);
int hash = hash(key);//對key的hashcode進一步計算,確保雜湊均勻
int i = indexFor(hash, table.length);//獲取在table中的實際位置
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
//如果該對應資料已存在,執行覆蓋操作。用新value替換舊value,並返回舊value
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;//保證併發訪問時,若HashMap內部結構發生變化,快速響應失敗
addEntry(hash, key, value, i);//具體的插入方法(擴容),頭插法
return null;
}
remove刪除元素的過程:
1、陣列元素size為0,即不存在元素,直接返回
2、對key進行雜湊,找到在陣列table中的索引位置
3、對該位置的連結串列進行從頭開始遍歷
4、prev,e,next定義變數,找出key的位置
分兩種情況:
key對應的entry實體在連結串列頭節點,直接將該節點的next的置為頭結點
entry實體在連結串列中,該判斷結點的後一個節點的next直接指向該節點的next節點
public V remove(Object key) {
Entry<K,V> e = removeEntryForKey(key);
return (e == null ? null : e.value);
}
通過key刪除資料並且返回以前的值
final Entry<K,V> removeEntryForKey(Object key) {
if (size == 0) {
return null;
}
//先找到節點的位置,key == null(table[0]) 否則(hash(key))
int hash = (key == null) ? 0 : hash(key);
int i = indexFor(hash, table.length);
Entry<K,V> prev = table[i];
Entry<K,V> e = prev;
while (e != null) {
Entry<K,V> next = e.next;
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
modCount++;
size--;
//如果要刪除的節點是第一個只需要將第二個放入陣列中就可以
if (prev == e)
table[i] = next;
else
//如果不是就需要將前一個節點的next置於next
prev.next = next;
e.recordRemoval(this);
return e;
}
prev = e;
e = next;
}
return e;
}
get獲取元素的過程:
get的過程是先計算hash然後通過hash與table.length取摸計算index值,然後遍歷table[index]上的連結串列,直到找到key,然後返回
注意點:
什麼情況返回null
1.先對key進行雜湊,並找到key存在的陣列table的索引位置
2.對索引位置的連結串列進行遍歷,判斷key是否相等(key的hashcode,==,equals)
public V get(Object key) {
//如果key為null呼叫一個單獨的方法,說明HashMap支援鍵為null
if (key == null)
return getForNullKey();
//先獲取到Entry實體
Entry<K,V> entry = getEntry(key);
//返回值
return null == entry ? null : entry.getValue();
}
通過鍵取值
getForNullKey
private V getForNullKey() {
if (size == 0) {
return null;
}
//注意:null鍵所對應的實體在table[0]儲存
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null)
return e.value;
}
return null;
}
containsValue判斷key是否存在方法
public boolean containsKey(Object key) {
//直接呼叫getEntry看能不能找到對應的Entry實體即可
return getEntry(key) != null;
}
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : hash(key);
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
containsKey判斷value是否存在方法
public boolean containsValue(Object value) {
//特殊情況
if (value == null)
return containsNullValue(); //查詢是否為值為null的value
//直接兩層for迴圈遍歷
Entry[] tab = table;
for (int i = 0; i < tab.length ; i++)
for (Entry e = tab[i] ; e != null ; e = e.next)
if (value.equals(e.value))
return true;
return false;
}
兩者區別點:
1、判斷相等方式不同:containsKey中key需要比較Hashcode、== equals
containsValue中value直接equals
2、遍歷陣列大小不同:containsKey中通過key找到該key存在的陣列索引位置,遍歷該索引位置的連結串列即可
containsValue中需要對該連結串列所有陣列的所有連結串列全部遍歷
下面將根據HashMap的基本特性和屬性實現一個簡單的MyHashMap
/**
* 描述:自己實現一個hashmap
* @Author administrator{GINO ZHANG}
* @Date2018/10/28
*/
class myhashmap {
private static int limit; //閾值,表示擴容時機,即當前陣列連結串列長度是否到達閾值,到達即擴容
private int size; //元素個數
private int tableNum[]; //表示table上的每一個索引位置當前連結串列的數量個數
private Entry[] table; //entry型別的陣列
class Entry { //entry型別的陣列
private final Object key;
private String value;
private Entry next;
public Entry(){ //無參建構函式
this.key = 0;
this.value = null;
this.next = next;
}
public Entry(Object key, String value, Entry next) { //帶參建構函式
this.key = key;
this.value = value;
this.next = next;
}
/**
* get,set方法
* @return
*/
public Object getKey() {
return key;
}
public Object getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public Entry getNext() {
return next;
}
public void setNext(Entry next) {
this.next = next;
}
}
public myhashmap(){ // myhashmap類的無參建構函式
this.table = new Entry[4];
this.size = 0;
this.tableNum = tableNum;
this.limit = table.length;
}
/**
* 根據key的hashcode和table長度取模計算key在table中的位置
* @param key
* @return
*/
public int hashCode(Object key) {
return key.hashCode() % table.length;
}
public void put(Object key, String value) {
int index = hashCode(key); //通過index方法計算key的hashCode,確定在陣列中索引的位置
Entry entry = table[index]; //遍歷index位置的entry,若找到重複key則覆蓋對應entry的值,然後返回
while (entry != null) {
//三種判斷是否存在key實體的方法
if (entry.getKey().hashCode() == key.hashCode() && (entry.getKey() == key || entry.getKey().equals(key))) {
entry.setValue(value);
}
entry = entry.getNext(); //在entry節點建立前驅next的聯絡
}
//若index位置沒有entry或者未找到重複的key,則將新key新增到table的index位置
add(index, key, value);
size++;
}
private void add(int index, Object key, String value) {
//將新的entry放到table的index位置第一個,若原來有值則以連結串列形式存放
Entry entry = new Entry(key, value, table[index]);
table[index] = entry;
//判斷size是否達到擴容閾值,若達到則進行擴容
if (key.hashCode() >= limit) {
resize();
}
}
/**
* 擴容
* @param
*/
private void resize() {
Entry[] newTable = new Entry[2*table.length];
//遍歷原table,將每個entry都重新計算hash放入newTable中
for (int i = 0; i < table.length; i++) {
Entry old = table[i];
while (old != null) {
Entry next = old.getNext();
int index = hashCode(old.getKey());
old.setNext(newTable[index]);
newTable[index] = old;
old = next;
}
}
//用newTable替table
table = newTable;
//修改臨界值
limit = newTable.length;
}
/**
* remove方法
* @param key
*/
public void remove(Object key) {
int index = hashCode(key); //通過index方法計算key的hashCode,確定在陣列中索引的位置
Entry prev = null;
Entry entry = table[index];
while (entry != null) {
if (entry.getKey().hashCode() == key.hashCode() && (entry.getKey() == key || entry.getKey().equals(key))) {
if (prev == null) {
table[index] = entry.getNext(); //
} else{
prev.setNext(entry.getNext());
}
//如果成功找到並刪除,修改size
size--;
}
prev = entry;
entry = entry.getNext();
}
}
/**@Override
public String toString() {
return "myhashmap{" +
"table=" + Arrays.toString(table) +
'}';
}*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (Entry entry : table) {
while (entry != null) {
sb.append(entry.getKey() + ":" + entry.getValue() + "\n");
entry = entry.getNext();
}
}
return sb.toString();
}
public static void main(String[] args) {
myhashmap hashMap = new myhashmap();
hashMap.put(0,"jiaruo");
hashMap.put(1,"Jenny");
hashMap.put(2,"Danny");
hashMap.put(3,"Corleone Z");
hashMap.put(4,"xiaoming");
hashMap.put(19,"xinan");
hashMap.remove(4);
System.out.println(hashMap);
System.out.println(hashMap.size);
}
}
列印結果
C:\java\java7\jdk1.7.0_80\bin\java.exe -javaagent:D:\ideaIU-2018.1.5.win\lib\idea_rt.jar=5940:D:\ideaIU-2018.1.5.win\bin -Dfile.encoding=UTF-8 -classpath
0:jiaruo
1:Jenny
2:Danny
3:Corleone Z
19:xinan
5
Process finished with exit code 0