1. 程式人生 > >HashMap的學習以及原始碼分析

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