深入研究集合-Map

目錄

7.1.HashMap

7.2.LinkedHashMap有序的map

7.3.TreeMap排序的map


本章研究MAP之類

7.1.HashMap

簡單的說,HashMap內部結構使用的是數組。HashMap就是就key做Hash算法,然後將hash後的值映射到數組下表,這樣就能快速操作數組。

Hashmap必須保證幾個特點:

1).hash算法必須是高效的;

2).hash值映射數組下標算法是高效的;

3).根據數組下標可以直接獲取對應的值.

原文:https://blog.csdn.net/lan861698789/article/details/81323643

1.內部組成

public class HashMap{

    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 默認容量24次方 = 16

    static final int MAXIMUM_CAPACITY = 1 << 30;//最大容量:230次方

    static final Entry<?,?>[] EMPTY_TABLE = {};//默認空

    transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;//數組

    transient int size;//大小

}

原文:https://blog.csdn.net/lan861698789/article/details/81323643

static class Entry<K,V> {

    final K key;

    V value;

    Entry<K,V> next;//下個entry

    int hash;

 

    Entry(int h, K k, V v, Entry<K,V> n) {

        value = v;

        next = n;

        key = k;

        hash = h;

    }

}

最主要的結構:table鏈表數組、size大小。

 

這裏使用了entry來做存儲. Entry裏面結構:key,value,next,hash

默認容量16

 

2.HASH算法

算出key的hash值

final static int hash(Object k) {

    h ^= k.hashCode();

 

    h ^= (h >>> 20) ^ (h >>> 12);

    return h ^ (h >>> 7) ^ (h >>> 4);

}

都是高效的位運算。

3.HASH值映射數組下標算法

static int indexFor(int h, int length) {

    return h & (length-1);//與運算

}

4.添加put

原文:https://blog.csdn.net/lan861698789/article/details/81323643

代碼:以test.put("01", "01-value");來舉例。

private static void test01() {

    HashMap<String, String> test = new HashMap<String, String>();

    //indexFor(hash("01"), 16);

    test.put("01", "01-value");

    //indexFor(hash("02"), 16);

    test.put("02", "02-value");

    //indexFor(hash("99"), 16);

    test.put("99", "99-value");

    test.get("02");

   

    test.remove("02");

}

public V put(K key, V value) {

    if (key == null)

        return putForNullKey(value);//可以放空值

    int hash = hash(key);//HASH算法,求出keyhash值。這裏key:01hash1645

    int i = indexFor(hash, table.length);//通過hash值,計算出數組中的下標。 這裏爲13

    //舊值查找替換,直接通過數組下標,找到key對應的數組table[i]

    for (Entry<K,V> e = table[i]; e != null; e = e.next) { //循環table[i],查找是否有next元素

        Object k;

        //對比hashkey是否相等。 如果相等,直接用新值替換舊值。並且返回舊值。

        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {

            V oldValue = e.value;

            e.value = value;

            e.recordAccess(this);

            return oldValue;

        }

    }

 

    modCount++;

    //新增

    addEntry(hash, key, value, i);

    return null;

}

原文:https://blog.csdn.net/lan861698789/article/details/81323643

void addEntry(int hash, K key, V value, int bucketIndex) {

    if ((size >= threshold) && (null != table[bucketIndex])) {//擴容

        resize(2 * table.length);

        hash = (null != key) ? hash(key) : 0;

        bucketIndex = indexFor(hash, table.length);

    }

    //將新增的元素放到i位置,並將它的next指向舊的元素

    Entry<K,V> e = table[bucketIndex];

    table[bucketIndex] = new Entry<>(hash, key, value, e);//代碼1

    size++;

}

原理:

1).hash算法得出key的hash值

2).通過hash值得出該key在table數組的下標

3).判斷該key在table數組裏面是否存在,存在則替換爲新值。 這裏要注意next,後面會講到hash衝突就會存到next裏面。

4).如果不存在,則新增。

5).先做擴容2倍

6).新增,取出舊元素,將新元素賦值給table[i],在將table[i].next賦值爲舊元素。

這裏的舊元素,如果第一次新增就是null。如果新增的元素hash值一樣,key不一樣,那舊元素就不爲空了。後面會講到。

截圖:

代碼1執行後圖

分析:

通過新增,我們可以知道,雖然hashmap是以數組來存儲的,但是每次新增數組下標卻不一定是自增,所以他是無序的。

原文:https://blog.csdn.net/lan861698789/article/details/81323643

5.獲取get

代碼:

public V get(Object key) {

    if (key == null)

        return getForNullKey();

    Entry<K,V> entry = getEntry(key);

 

    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;

}

原理:

1).hash算法得出key的hash值

2).通過hash值得出該key在table數組的下標

3).循環next,找出hash和key都一樣的元素

6.hash值衝突解決

雖然通過計算hash來獲取數組下標很高效,但是還是會存在兩個不同的key,計算得到的hash值一樣的情況。那麼hashmap會如何處理呢?

再看下hashmap的組成結構,發現entry裏面有一個next屬性,它會指向另外一個entry。

再次回顧添加的源碼,裏面兩處使用了next。

第一次,查找舊值時候,會判斷next是否存在

第二次,沒有查到舊值,新增新值時候,每次新增的值都放在table[i],把舊值放到新值的next處,也就是table[i].next,這個值可能是null.

public V put(K key, V value) {

    if (key == null)

        return putForNullKey(value);//可以放空值

    int hash = hash(key);//HASH算法,求出keyhash值。這裏key:01hash1645

    int i = indexFor(hash, table.length);//通過hash值,計算出數組中的下標。 這裏爲13

//舊值查找替換,直接通過數組下標,找到key對應的數組table[i]

//第一次

    for (Entry<K,V> e = table[i]; e != null; e = e.next) { //循環table[i],查找是否有next元素

        Object k;

        //對比hashkey是否相等。 如果相等,直接用新值替換舊值。並且返回舊值。

        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {

            V oldValue = e.value;

            e.value = value;

            e.recordAccess(this);

            return oldValue;

        }

    }

 

    modCount++;

//新增

//第二次

    addEntry(hash, key, value, i);

    return null;

}

 

void addEntry(int hash, K key, V value, int bucketIndex) {

    if ((size >= threshold) && (null != table[bucketIndex])) {//擴容

        resize(2 * table.length);

        hash = (null != key) ? hash(key) : 0;

        bucketIndex = indexFor(hash, table.length);

    }

    //將新增的元素放到i位置,並將它的next指向舊的元素

    Entry<K,V> e = table[bucketIndex];

    table[bucketIndex] = new Entry<>(hash, key, value, e);//代碼1

size++;

}

原文:https://blog.csdn.net/lan861698789/article/details/81323643

static class Entry<K,V> {

    final K key;

    V value;

    Entry<K,V> next;//下個entry

    int hash;

 

    Entry(int h, K k, V v, Entry<K,V> n) {

        value = v;

        next = n;

        key = k;

        hash = h;

    }

}

7.hashMap輸出順序

雖然hashmap是以數組來存儲的,但是每次新增數組下標卻不一定是自增,所以他是無序的。

那麼他是怎麼排序的呢?

private static void test01() {

    HashMap<String, String> test = new HashMap<String, String>();

    System.out.println("01->"+indexFor(hash("01"), 16));

    test.put("01", "01-value");

    System.out.println("02->"+indexFor(hash("02"), 16));

    test.put("02", "02-value");

    System.out.println("99->"+indexFor(hash("99"), 16));

    test.put("99", "99-value");

    test.put("99", "99-value2");

    //循環輸出

    Iterator<Entry<String, String>> iterator = test.entrySet().iterator();

    while (iterator.hasNext()) {

        System.out.println(iterator.next().getKey());

    }

    test.get("02");

   

    test.remove("02");

}  

輸出:

每次輸出都是一樣。

分析:

可以看出,hashmap排序是通過通過數組排序的,也就是按照table裏面的數組順序輸出的。但是因爲每次存入的數組下標不一樣。所以看起來不是先進先出這種排序了。

7.2.LinkedHashMap有序的map

1.內部組成

public class HashMap{

    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 默認容量24次方 = 16

    static final int MAXIMUM_CAPACITY = 1 << 30;//最大容量:230次方

    static final Entry<?,?>[] EMPTY_TABLE = {};//默認空

    transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;//數組

    transient int size;//大小

}

 

static class Entry<K,V> {

    final K key;

    V value;

    int hash;

    Entry<K,V> next;//下個entry  

    Entry<K,V> before, after;//linkedHashMap特有

 

    Entry(int h, K k, V v, Entry<K,V> n) {

        value = v;

        next = n;

        key = k;

        hash = h;

    }

}

黃色部分爲linkedHashMap特有的結構。

原文:https://blog.csdn.net/lan861698789/article/details/81323643

7.3.TreeMap排序的map

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章