HashMap源码剖析~

包路径:package java.util;

                import java.io.*;

一、基本属性

1、static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;  默认初始容量16

2、static final int MAXIMUM_CAPACITY = 1 << 30;  限制数组最大值

3、static final float DEFAULT_LOAD_FACTOR = 0.75f;  默认加载因子,0.75f是根据经验所得

4、static final Entry<?,?>[] EMPTY_TABLE = {};  初始默认空数组

5、transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;  数组,用来存储entry数据

6、transient int size;  存储数据量

7、int threshold;   阈值

8、final float loadFactor;  加载因子

9、transient int modCount;  版本号

10、static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;
     默认哈希阈值

11、transient int hashSeed = 0;  哈希种子

二、继承关系

三、构造方法(共四种构造方法)

构造方法一、传入两个参数:初始容量,加载因子。首先进行传入参数的合法性检验

public HashMap(int initialCapacity, float loadFactor) {
        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);

        this.loadFactor = loadFactor;
        threshold = initialCapacity;
        init();
    }

构造方法二、传入一个参数:初始容量

 public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

构造方法三、无参构造

public HashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }

构造方法四、传入集合对象m,通过集合构造新的集合

public HashMap(Map<? extends K, ? extends V> m) {
        this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
                      DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
        inflateTable(threshold);

        putAllForCreate(m);
    }

四、核心方法

1、添加元素:put()

public V put(K key, V value) { 传入的参数是键值对:key和value
        if (table == EMPTY_TABLE) { 首先检查table数组是否为空,为空的话进行初始化
            inflateTable(threshold);
        }
        if (key == null)  如果key为空,调用特殊处理的方法
            return putForNullKey(value);
        int hash = hash(key);   根据键值key进行哈希计算得到对应的哈希码
        int i = indexFor(hash, table.length); 根据哈希码得到映射到table数组的索引下标

   遍历第i个链表查找是否存在key的entry,存在则替换到entry下的value值并返回旧的value值
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
        在这里比较key和获取元素比较key思想是一样的,需要比较三者!!!
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this); 替换存在的entry的value值需要调用
                return oldValue;
            }
        }

     若不存在key相同的entry,则添加新的entry到HashMap中
        modCount++;  添加元素,HashMap的结构发生变化,版本号modCount++
        addEntry(hash, key, value, i);
        return null;
    }
添加元素,当Key为null时,调用此方法
private V putForNullKey(V value) {  直接在table的第0号索引下标的链表中查找替换或者进行添加
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
        addEntry(0, null, value, 0);
        return null;
    }
在table的第bucketIndex个链表上添加哈希码为hash,键值为key,映射为value的entry
void addEntry(int hash, K key, V value, int bucketIndex) {
首先进行合法性检查,当HashMap的size大于阈值,并且table数组的第bucketIndex个链表为null的时候,进行2倍的扩容操作。
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0; 重现计算key的哈希码
            bucketIndex = indexFor(hash, table.length); 
          重新计算哈希码应该对应放到table数组的哪个链表上
        }

        createEntry(hash, key, value, bucketIndex);  
     创建entry对象,并插入到第bucketIndex个索引链表的第一个位置上
    }
创建entry对象,并插入到第bucketIndex个索引链表的第一个位置上,相当于头插法
void createEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
    }
final int hash(Object k) {
        int h = hashSeed;
        if (0 != h && k instanceof String) { 当哈希种子不为0,且k是string时
            return sun.misc.Hashing.stringHash32((String) k);  调用特殊的哈希算法
        }

        h ^= k.hashCode();
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }


static int indexFor(int h, int length) {
        return h & (length-1);
    }

2、获取元素:get()

public V get(Object key) { 获取键值为key的entry的value值
        if (key == null)  键值key为null,特殊处理,调用特殊的方法
            return getForNullKey();
        Entry<K,V> entry = getEntry(key);

        return null == entry ? null : entry.getValue();
    }
private V getForNullKey() {  key为null时
        if (size == 0) {  首先检查数组大小
            return null;
        }
        for (Entry<K,V> e = table[0]; e != null; e = e.next) { 
           在table数组的第0号索引链表下查找
            if (e.key == null)
                return e.value;
        }
        return null;
    }
final Entry<K,V> getEntry(Object key) {
        if (size == 0) {
            return null;
        }

        int hash = (key == null) ? 0 : hash(key); 
 根据Key计算哈希码,根据哈希码找到table数组下的索引链表,进行查找
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
 这里查找的时候,需要使用“==”比较找到的entey的hash是否相等,然后使用“==”比较entry的key和已知的key是否相等,或者使用“.equals()”方法比较两者的key是否相等且key不能为null
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }

3、删除元素:

public V remove(Object key) {  根据键值key进行删除
        Entry<K,V> e = removeEntryForKey(key);
        return (e == null ? null : e.value);
    }
 final Entry<K,V> removeEntryForKey(Object key) {
        if (size == 0) {
            return null;
        }
        int hash = (key == null) ? 0 : hash(key); 首先根据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;
        找到key对应的entry
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k)))) { 比较!!!
                modCount++; HashMap数据结构发生改变,版本号modCunt++
                size--;
                if (prev == e) 如果移除的是table索引的第一个entry,直接修改table[i]
                    table[i] = next;
                else
                    prev.next = next; 
    移除的不是第一个entry,将移除entry的前一个entry的next指向移除entry的next
                e.recordRemoval(this);  记录移除的entry
                return e;
            }
            prev = e; 顺序后移
            e = next;  再次循环
        }

        return e;  返回移除的entry
    }

五、遍历方式(共用三种遍历方式)

这里直接进行示例代码演示如下:

HashMap<Integer,Integer> hashMap=new HashMap<Integer,Integer>();
        hashMap.put(1,11);
        hashMap.put(2,22);
        hashMap.put(3,33);
        hashMap.put(4,44);
        System.out.print("通过键值对进行遍历:");
        Iterator<Map.Entry<Integer,Integer>> iterator1=hashMap.entrySet().iterator();
        while (iterator1.hasNext()){
            Map.Entry<Integer,Integer> entry=iterator1.next();
            Integer key1=entry.getKey();
            Integer value1=entry.getValue();
            System.out.print(key1+"==>"+value1+"     ");
        }
        System.out.println();

        System.out.print("通过键进行遍历:");
        Iterator<Integer> key=hashMap.keySet().iterator();
        while (key.hasNext()){
            Integer key2=key.next();
            System.out.print(key2+"   ");
        }
        System.out.println();

        System.out.print("通过值进行遍历:");
        Iterator<Integer> value=hashMap.values().iterator();
        while(value.hasNext()){
            Integer value2=value.next();
            System.out.print(value2+"    ");
        }
        System.out.println();
    }

运行结果:

六、总结

1、底层数据结构:数组+链表,是以键值对的形式进行存储。

2、key可以为null,且最多只能有一个为null,且不可重复,value可以有多个为null,可以存储重复的数据。

3、数组是按照2倍指数进行扩容增长。

4、存储的数据不能保证有序性。

5、非线程安全的。

猜你喜欢

转载自blog.csdn.net/qq_40303781/article/details/84248940
今日推荐