文档章节

HashMap源码分析

 慕哲
发布于 2017/04/10 20:34
字数 2560
阅读 12
收藏 0

一、类的定义

public class HashMap<K,V>  extends AbstractMap<K,V>  implements Map<K,V>, Cloneable, Serializable

HashMap的依赖图。

 

二、类的属性

/**
 * 存储了所有的键值对,其中长度必须是  2的n次方。
 */
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
/**
 * 键值对的数量
 */
transient int size;

/**
*进行扩容的下一个容量
*/
int threshold;
//加载因子
final float loadFactor;
//当前参数被修改的次数,在迭代器中用于发现快速失败。这个参数是不会被序列化的。
transient int modCount;

 

 

 

三、类的内部类

Entry<K,V>这个对象主要用来存放键值对。

static class Entry<K,V> implements Map.Entry<K,V> {
    final K key;
    V value;
    Entry<K,V> next;//解决冲突的指针
    int hash;//当前对象的Hash值。

    /**
     * Creates new entry.
     */
    Entry(int h, K k, V v, Entry<K,V> n) {
        value = v;
        next = n;
        key = k;
        hash = h;
    }

    public final K getKey() {
        return key;
    }

    public final V getValue() {
        return value;
    }

    public final V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }

    public final boolean equals(Object o) {
        if (!(o instanceof Map.Entry))//首先校验类型。
            return false;
        Map.Entry e = (Map.Entry)o;
        Object k1 = getKey();//当前的Entry的key。
        Object k2 = e.getKey();//这个被比较的对象的key。
        if (k1 == k2 || (k1 != null && k1.equals(k2))) {//如果两个key相等。
            Object v1 = getValue();
            Object v2 = e.getValue();
            if (v1 == v2 || (v1 != null && v1.equals(v2)))//比较value。
                return true;
        }
        return false;
    }

    public final int hashCode() {
        return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());//hahs值是根据key和value对应的hashCode获取到的
    }

    public final String toString() {
        return getKey() + "=" + getValue();
    }

    /**
     * This method is invoked whenever the value in an entry is
     * overwritten by an invocation of put(k,v) for a key k that's already
     * in the HashMap.
     */
    void recordAccess(HashMap<K,V> m) {
    }

    /**
     * This method is invoked whenever the entry is
     * removed from the table.
     */
    void recordRemoval(HashMap<K,V> m) {
    }
}

四、构造函数

/**
*根据初始化的大小和 加载因子来创建一个HashMap
*HashMap在最开始的时候是不初始化对应的 Entry[]的,只会将 capacity和loadFactor进行设置。
*/
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))//如果当前的float的值是非法的、
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);

    this.loadFactor = loadFactor;
    threshold = initialCapacity;
    init();//初始化HashMap。什么都么有做。这个时候Table默认的是空。
}
/**
*默认的加载因子 =  0.75
*/
public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

五、重要的方法

public  V put(K key, V value);

/**
*向当前的map中添加一些参数
*/
public V put(K key, V value) {
    if (table == EMPTY_TABLE) {//如果当前的对象是初始化,开始初始化一下map
        inflateTable(threshold);
    }
    if (key == null)//如果key == null需要单独处理对应的对象。
        return putForNullKey(value);//将null值写入到对应的对象中。
    int hash = hash(key);//获取到key的hash值
    int i = indexFor(hash, table.length);//找出当前hash在系统中的位置
    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值相等的,就替换掉。
            V oldValue = e.value;//将当前的信息的对象写入到当中。
            e.value = value;//设置新值
            e.recordAccess(this);
            return oldValue;
        }
    }

    modCount++;
    addEntry(hash, key, value, i);//将当前值写入到当前Map中。
    return null;
}

/**
 * Inflates the table.初始化table
 */
private void inflateTable(int toSize) {
    // Find a power of 2 >= toSize
    int capacity = roundUpToPowerOf2(toSize);//使用最近的2的n次方来作为一个  容量。

    threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);//开发出容量,容量是数量 * 加载因子。
    table = new Entry[capacity];//创建一个加载因子的  table的大小。
    initHashSeedAsNeeded(capacity);
}

/**
 * 初始化一下HashNeed。这个需要看下为啥?
 */
final boolean initHashSeedAsNeeded(int capacity) {
    boolean currentAltHashing = hashSeed != 0;
    boolean useAltHashing = sun.misc.VM.isBooted() &&
            (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
    boolean switching = currentAltHashing ^ useAltHashing;
    if (switching) {
        hashSeed = useAltHashing
            ? sun.misc.Hashing.randomHashSeed(this)
            : 0;
    }
    return switching;
}

//将null值写入到当前系统中去:null值和其他的值逻辑是一样的,但是过程是有些差别的。

private V putForNullKey(V value) {//将null值写入到map中。
    for (Entry<K,V> e = table[0]; e != null; e = e.next) {
        if (e.key == null) {//如果key值 == null ,这个时候需要
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
    modCount++;
    addEntry(0, null, value, 0);
    return null;
}

//计算当前对象的Hash值。
final int hash(Object k) {//根据一个Hash值来基数按一个Hash值。其中String的Hash值是单独计算的。
    int h = hashSeed;
    if (0 != h && k instanceof String) {
        return sun.misc.Hashing.stringHash32((String) k);
    }

    h ^= k.hashCode();//在计算当前的Key的Hash值的时候是用到了当前对象的hashCode的。

    // This function ensures that hashCodes that differ only by
    // constant multiples at each bit position have a bounded
    // number of collisions (approximately 8 at default load factor).
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}

/**
 * 看下当前的Hash值当前数组中的下标。其中的关键是 length 是  2 的n次方。
 */
static int indexFor(int h, int length) {
    // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
    return h & (length-1);
}

//其中length是 2的n次方后, (length -1)的值的2进制的值  全是1,这个时候 和h进行  位与的时候,效果是  将高位的值进行截断。值剩下了当前空间中的下表的值。如果使用其他的可能会损失掉一些位,例如 如果 如果最低位 = 0.那么底层求交集的时候,,最低位一定 = 0.位 & 后的值的结果将是 偶数位。

/**
* 使用指定的键、值和哈希代码将新项添加到指定桶。
* 如果适当,调整此表的大小是该方法的责任。
* 子类重写此方法以改变放置方法的行为。
* 其中扩容的工作是在 添加了数据之前做的。
*/
void addEntry(int hash, K key, V value, int bucketIndex) {
   
 if ((size >= threshold) && (null != table[bucketIndex])) {//如果超过了当前容量的上限
        resize(2 * table.length);//这个时候就需要扩充到2倍。
        hash = (null != key) ? hash(key) : 0;
        bucketIndex = indexFor(hash, table.length);//然后重新进行index确定对应的  位置。
    }

    createEntry(hash, key, value, bucketIndex);//新建一个Entry。
}

/**
*当触发了容量上相的时候,这个时候需要对当期那的 空间进行扩容。
*如果当前的空间的容量已经到和 Max_Capacity的时候,这个时候,就把空间的大小扩展为Integer.MAX_NUM;
* 创建更大空间的 Entry[],然后将当前的数据写到新的空间中去。
* 然后设置新的 table和新的  threshold对象。
*/
void resize(int newCapacity) {
   
    Entry[] oldTable = table;
    int oldCapacity = oldTable.length;
    if (oldCapacity == MAXIMUM_CAPACITY) {
        threshold = Integer.MAX_VALUE;
        return;
    }

    Entry[] newTable = new Entry[newCapacity];
    transfer(newTable, initHashSeedAsNeeded(newCapacity));//将就的Enrty中的数据转移到新的Entry中去。
    table = newTable;//切换为新的Table
    threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);//设置新的触发的门槛
}

/**
 * 将当前HashMap中的值转发到新的Map中去
 */
void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e : table) {//每一个Entry都可能是一个链表。
        while(null != e) {
            Entry<K,V> next = e.next;//先记录下一个的位置。
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);//计算出当前e的hash值。
            }
            int i = indexFor(e.hash, newCapacity);//重新看一下新Entry数组中的位置、
            e.next = newTable[i];//将当前的Entry写入entry[i]中的第一个。
            newTable[i] = e;
            e = next;//然后开始处理当前列表的下一个。
        }
    }
}

//将新的值添加大对应的值当中,其中新添加的在链表的前面。
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++;
}












总结:对于HashMap中的put(K k,V v) 方法的实现。其中对于Entry[]的长度的设置总是  2的n次方。

其中首先判断一下当前的Hash是否是空的,如果为空,这个时候需要初始化一下当前的Map ,主要是创建Entry数组,和设置ThreasHold。

然后计算出Hash值。

接着根据Hash值计算出在当前Entry数组中的位置。

首先判断当前的key值是否已经存在了,如果存在了 这个时候,将对应的值替换掉。返回就的值。

如果不是替换,那么将当前的值写入到系统中去:

首先判断当前的空间是否已经触发了  扩容上限。如果到了,这个时候就将当前的对象扩容为两倍。如果新的空间比Max_LENGTH大的话,只将扩容上限转变为Integer.MAX,接着返回.然后将当前的所有Entry转到新的Map中来。

然后为当前的值创建一个新的Entry,然后将 Entry插入到对应的Map中,然后修改size的变量。

 

查询当前系统中的值:
public V get(Object key) {
    if (key == null)//如果key == null,就获取到 null值对应的  value值。
        return getForNullKey();
    Entry<K,V> entry = getEntry(key);

    return null == entry ? null : entry.getValue();
}

private V getForNullKey() {//获取  key=null值的value值,返回对应的值。其中 key = null是固定的存在  table[0]。
    if (size == 0) {
        return null;
    }
    for (Entry<K,V> e = table[0]; e != null; e = e.next) {
        if (e.key == null)
            return e.value;
    }
    return null;
}
/**
*获取当前对象的value值。
**/
final Entry<K,V> getEntry(Object key) {
    if (size == 0) {
        return null;
    }

    int hash = (key == null) ? 0 : hash(key);//其中key值是固定的存放在0这个位置上的。
    for (Entry<K,V> e = table[indexFor(hash, table.length)];
         e != null;
         e = e.next) {//从对应的key 的队列的队头开始遍历,找出相等的实现。
        Object k;
        if (e.hash == hash &&
            ((k = e.key) == key || (key != null && key.equals(k))))
            return e;
    }
    return null;
}

 

 

/**
* 删除Key =  key的键值对
**/
public V remove(Object key) {
    Entry<K,V> e = removeEntryForKey(key);//删除对应的key值
    return (e == null ? null : e.value);
}

/**
 * 移除key对应的键值对,如果当前HashMap中不包括对应的key,那么就返回 null值
 * 
 */
final Entry<K,V> removeEntryForKey(Object key) {
    if (size == 0) {//如果当期那Map为空
        return null;
    }
    int hash = (key == null) ? 0 : hash(key);//为key确定hash值
    int i = indexFor(hash, table.length);//计算出当前的 hash对应的  index的值。
    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
                prev.next = next;//从链表中删除对应的节点。
            e.recordRemoval(this);
            return e;//返回对应的节点。
        }
        prev = e;//开始访问下边的。
        e = next;
    }

    return e;//e一直都没有赋值,就需要将当前的值返回。
}

 

todo

© 著作权归作者所有

粉丝 0
博文 7
码字总数 19795
作品 0
私信 提问

暂无文章

PostgreSQL 11.3 locking

rudi
今天
5
0
Mybatis Plus sql注入器

一、继承AbstractMethod /** * @author beth * @data 2019-10-23 20:39 */public class DeleteAllMethod extends AbstractMethod { @Override public MappedStatement injectMap......

一个yuanbeth
今天
13
1
一次写shell脚本的经历记录——特殊字符惹的祸

本文首发于微信公众号“我的小碗汤”,扫码文末二维码即可关注,欢迎一起交流! redis在容器化的过程中,涉及到纵向扩pod实例cpu、内存以及redis实例的maxmemory值,statefulset管理的pod需要...

码农实战
今天
4
0
为什么阿里巴巴Java开发手册中不建议在循环体中使用+进行字符串拼接?

之前在阅读《阿里巴巴Java开发手册》时,发现有一条是关于循环体中字符串拼接的建议,具体内容如下: 那么我们首先来用例子来看看在循环体中用 + 或者用 StringBuilder 进行字符串拼接的效率...

武培轩
今天
9
0
队列-链式(c/c++实现)

队列是在线性表功能稍作修改形成的,在生活中排队是不能插队的吧,先排队先得到对待,慢来得排在最后面,这样来就形成了”先进先出“的队列。作用就是通过伟大的程序员来实现算法解决现实生活...

白客C
今天
87
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部