HashMap源码分析

  • 源码阅读
  • 多线程下形成死循环的原因
  • key为什么一般用字符串比较多,能用其他对象,或者自定义的对象吗?为什么

源码阅读

基础

1.扩容基础系数: loadFactor=0.75f
2.存储的基本数据结构:transient Entry<K,V>[] table
3.默认初始化table大小:initialCapacity =1 << 4
4.当前table元素个数:size
5.threshold=loadFactor*table大小

构造函数

public HashMap(int initialCapacity, float loadFactor) {
     //各种判断,省略...
    //这里只是做了容量和扩容系数的初始化
    this.loadFactor = loadFactor;
    threshold = initialCapacity;
    init();
}

put操作

public V put(K key, V value) {
    if (table == EMPTY_TABLE) {
        inflateTable(threshold);//如果table为空就初始化table
    }
    if (key == null)
        return putForNullKey(value);//对key==null的情况做个特殊处理,允许key=null
    int hash = hash(key);
    int i = indexFor(hash, table.length);//根据key的hash值和当前table的长度计算该Entry位于数组中的位置
   //循环判断该Entry是否已经存在于数组第i个位置上的链表里
    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))) {
            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) {
    //如果当前容量>=table.length*0.75f &&table的第i个位置!=null就扩容
    if ((size >= threshold) && (null != table[bucketIndex])) {
        resize(2 * table.length);//扩容和元素重排
        hash = (null != key) ? hash(key) : 0;
        bucketIndex = indexFor(hash, table.length);
    }
    createEntry(hash, key, value, bucketIndex);
}

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];//创建一个新的Entry数组
    transfer(newTable, initHashSeedAsNeeded(newCapacity));//重排
    table = newTable;
    threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}

void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    //遍历old table中的所有元素
    for (Entry<K,V> e : table) {
        //每个元素遍历自身的链表
        while(null != e) {
            Entry<K,V> next = e.next;
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            //计算在新table中的位置,并插入
            int i = indexFor(e.hash, newCapacity);
            e.next = newTable[i];
            newTable[i] = e;
            e = next;
        }
    }
}

get操作

get操作很简单,就是根据key的hash值定位到该key对应table中的位置,如果该key对应的hash有重复(即在table[i]上有链表结构),就挨个比较该链表上的每个节点(Entry)中key的值

public V get(Object key) {
    if (key == null)
        return getForNullKey();
    Entry<K,V> entry = getEntry(key);
    return null == entry ? null : entry.getValue();
}

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;
}

图解

内部结构图
  • 初始化和put操作

      HashMap map = new HashMap(2)
      map.put("k1","v1");
      map.put("k2","v2");
    
初始化
put操作(1)

put操作(2)

  • 正常resize操作

      map.put("k3","v3");
    
初始状态
resize(1)
resize(2)
resize(3)

  • 多线程线resize造成死循环

      HashMap map = new HashMap(2);
      map.put("k1","v1");
      map.put("k2","v2");
      
      
      map.put("k3","v3");//A线程操作
      map.put("k4","v4");//B线程操作
    

假设A线程和B线程同时进入resize方法的transfer方法的
Entry<K,V> next = e.next; 这一行
这时B线程被挂起,A线程执行完之后,B线程再执行,我们看看这时会发生什么?

初始状态
B线程被刮起前状态
A线程执行完毕
void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e : table) {
        while(null != e) {
            Entry<K,V> next = e.next;
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            int i = indexFor(e.hash, newCapacity);
            e.next = newTable[i];
            newTable[i] = e;
            e = next;
        }
    }
}
}
B-resize(1)
B-resize(2)

多线程下形成死循环的原因

多线程情况下,当多个线程同时对同一个hashmap进行resize操作时,就有可能造成链表的循环调用,具体原因如图解所示。

key一般用字符串比较多,能用其他对象,或者自定义的对象吗?为什么

 public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
  • HashMap支持传入泛型,所以肯定支持对象,也肯定支持自定义对象(不支持基本数据类型哦),但是在用自定义对象的时候一定要注意要重写hashCode和equals方法,因为Object的equals默认是比较两个对象的地址是否相等,所以即使两个对象的属相一摸一样,在HashMap中依旧被当作是两个不同的key。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,914评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,935评论 2 383
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,531评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,309评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,381评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,730评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,882评论 3 404
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,643评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,095评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,448评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,566评论 1 339
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,253评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,829评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,715评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,945评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,248评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,440评论 2 348

推荐阅读更多精彩内容