文档章节

HashMap源码理解

清尘V
 清尘V
发布于 2016/04/16 17:34
字数 1634
阅读 285
收藏 3
点赞 1
评论 0
private static int roundUpToPowerOf2(int number) {
    return number >= MAXIMUM_CAPACITY
            ? MAXIMUM_CAPACITY
            : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
}
  • Integer.highestOneBit方法是获取不大于本身的2^n的值,那该方法具体含义是:

  • 获取新的数组容量值,如果给定值大于等于最大的容量,则返回最大容量,否则:如果容量小于等于1,则返回1,否则返回大于等于给定值的最接近的2^n的值

  • 容量为什么要是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);
    }

    • 给方法是查找h(ash)在数组的索引位置;那现在看length为什么要是2^n次方呢?点此查看

    • 保证&之后的数据不大于length

    • length-1之后,最低的n位都是1,那与h的&运算之后的值即是h的最低n位

    • 采用length-1而不是直接length是因为2^n最低一位是0,那&运算之后数据都分布在偶数位,不是随机的

private void inflateTable(int toSize) {
    // Find a power of 2 >= toSize
    int capacity = roundUpToPowerOf2(toSize);

    threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
    table = new Entry[capacity];
    initHashSeedAsNeeded(capacity);
}

扩容:先获取最接近并且>=指定容量的的数值作为扩容后的容量;扩容界限就是取容量*加载因子和最大容量+1的最小值;重新初始化table;扩容之后的table没有任何数据,所以在相关调用操作之后会有数据重新分配操作,比较耗时,所以在初始化一个hashmap的时候最好指定容量避免扩容操作发生

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

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

get方法:

  •     如果key==null,则单独获取

private V getForNullKey() {
    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;
}


  • 如果size==0,则直接返回null;

  • 遍历table,判断key,返回value

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


根据key获取Entry:

  •     获取hash值

  • 获取数组索引

  • 获取数组中索引的第一个entry

  • 遍历entry,如果hash值相等并且(key相等或者equal),则返回当前entry即可

public boolean containsKey(Object key) {
    return getEntry(key) != null;
}

判断key是否存在只是调用getEntry方法判断是否为null即可

public V put(K key, V value) {
    if (table == EMPTY_TABLE) {
        inflateTable(threshold);
    }
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key);
    int i = indexFor(hash, table.length);
    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;
}

put方法:

  •     如果table为空数组,则先扩容到扩容界限(threshold)的数组,如果是默认的初始化方法,则threshold=容量;执行该方法之后。threshold=容量*加载因子;

  • 如果key==null,单独存入该值,putForNullKey

  • 根据hash和length获取table索引,找出第一个entry

  • 遍历entry,如果找到则重新设置新值,返回旧值,否则新添加一个entry(addEntry)

private V putForNullKey(V value) {
    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;
}
  • 存入key==null的value,直接查找table的第一个位置(index=0),如果找到则重新设置新值,返回旧值,否则添加新的entry;

  • key==null的值全部在table【0】的entry上

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

    createEntry(hash, key, value, bucketIndex);
}


创建新的entry:

  • 先检查是否满足扩容条件:size>=threshold&&null!=table[bucketIndex];

  • 如果满足扩容,计算新的hash和数组索引(bucketIndex)

  • 创建新的entry(createEntry)

  • 注意事项:size是针对hash表里的所有数据的容量,而扩容是指数组扩容

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));
    table = newTable;
    threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
  • 先获取oldTable的length,如果已是最大长度,则无需扩容,并且将threshold设为Integer.MAX_VALUE,不可扩容!

  • 创建新的Entry数组,将旧的table数据添加到newTable中(transfer)

  • 设置新的table和threshold

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

简单理解为:数据拷贝

  • 获取新的数组长度,遍历table数组;

  • 遍历每个链表:重定向到新的数组和链表中

  • 现在遍历到[1,0]即table[1]下的第一个entry,计算得出在新的i=5,则该entry的下一个是newTable[5],newTable[5]=entry

  • 从以上可以看出,新进入newTable的数据在后进入的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++;
}

创建新的Entry:

  • 先根据索引(bucketIndex)获取数组中的元素(e)

  • 创建新的entry,位于链表第一个entry,而e是当前新的entry的next

public void putAll(Map<? extends K, ? extends V> m) {
    int numKeysToBeAdded = m.size();
    if (numKeysToBeAdded == 0)
        return;

    if (table == EMPTY_TABLE) {
        inflateTable((int) Math.max(numKeysToBeAdded * loadFactor, threshold));
    }

    /*
     * Expand the map if the map if the number of mappings to be added
     * is greater than or equal to threshold.  This is conservative; the
     * obvious condition is (m.size() + size) >= threshold, but this
     * condition could result in a map with twice the appropriate capacity,
     * if the keys to be added overlap with the keys already in this map.
     * By using the conservative calculation, we subject ourself
     * to at most one extra resize.
     */
    if (numKeysToBeAdded > threshold) {
        int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1);
        if (targetCapacity > MAXIMUM_CAPACITY)
            targetCapacity = MAXIMUM_CAPACITY;
        int newCapacity = table.length;
        while (newCapacity < targetCapacity)
            newCapacity <<= 1;
        if (newCapacity > table.length)
            resize(newCapacity);
    }

    for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
        put(e.getKey(), e.getValue());
}

将一个集合添加到现有集合中:

  • 先获取要添加结合的元素数量

  • 如果现有集合是空集合,扩容:当前threshold和根据添加元素容量计算的新容量的最大值

  • 假如添加的集合元素数量>threshold,则判断当前table是否需要扩容

  • 此处是保守估计新的集合添加之后的容量:但是可以保证最多只有一次调用resize方法!

  • 如果numKeysToBeAdded <=threshold,即使在put方法导致扩容也至多有一次:扩容至两倍,那threshold也会变为两倍

  • 如果numKeysToBeAdded >threshold,如果:targetCapacity>table.length,则在put方法可能会导致resize,否则newCapacity必定大于table.length,在此处resize,put方法就不会resize了

    


参考文章:http://blog.csdn.net/eson_15/article/details/51158865

个人博客:http://www.whereta.com

© 著作权归作者所有

共有 人打赏支持
清尘V
粉丝 43
博文 107
码字总数 47780
作品 0
青岛
程序员
Java8之HashMap源码分析

高级Java架构交流群:283943715 java高级交流群 一、前言   在分析jdk1.8后的HashMap源码时,发现网上好多分析都是基于之前的jdk,而Java8的HashMap对之前做了较大的优化,其中最重要的一个...

小炎哥 ⋅ 2017/06/19 ⋅ 1

集合源码学习(十一):LinkedHashMap

何为LinkedHashMap LinkedHashMap是一个,具有顺序的HashMap,也就是使用Iterator进行迭代时,顺序与put进来的顺序是一致的。 先看LinkedHashMap的定义: 如上,LinkedHashMap继承自HashMap...

anLA_ ⋅ 2017/10/20 ⋅ 0

浅谈HashMap

HashMap结构图 image.png HashMap主要方法 final int hash(Object k) static int indexFor(int h, int length) void resize(int newCapacity) public V put(K key, V value) public V get(O......

小鱼嘻嘻 ⋅ 2017/10/28 ⋅ 0

java容器源码分析(六)——HashSet

本文内容 HashSet概述 HashSet源码分析 HashSet概述 HashSet是Set的一种实现,其底层是用HashMap实现的,整个HashSet看起来就像一个包装类! HashSet的继承图如下: HashSet继承了Set、Abstr...

风水书生 ⋅ 2015/12/17 ⋅ 0

HashMap理解

大家好,查看资料我了解到HashMap是一个 数组跟链表 的结合,但是我理解能力有限,想象不到,我的理解是: 初始化HashMap默认为16个长度,也就是数组长度,每个数组单元称为‘桶‘,每个桶里面...

樱木花道VS康 ⋅ 2017/07/05 ⋅ 3

手把手带你源码分析 HashMap 1.7

前言 HashMap 在 Java 和 Android 开发中非常常见 今天,我将带来 HashMap 的全部源码分析,希望你们会喜欢。 目录 1. 简介 类定义 3. 具体使用 3.1 主要使用API(方法、函数) 3.2 使用流程...

烂猪皮 ⋅ 04/21 ⋅ 0

java集合介绍

对于高级语言来说,集合(容器)是非常的重要的知识点,也是非常基础的,相信很多刚毕业的同学包括初级程序员和求职的过程中,经常会被问到集合相关的知识。我觉得该文只是对集合的一个简单的...

The_flying_pig ⋅ 2017/09/18 ⋅ 0

Java源码--JDK 1.8 HashMap 源码分析

注:感谢 美团点评技术团队 的分享~~,博客部分内容摘抄自其中。侵删! 今天我们来探究一下 HashMap 的内部实现机制。 明确 JDK 1.8 中的 HashMap 使用数组 + 链表 + 红黑树的结构进行实现。...

championhengyi ⋅ 04/20 ⋅ 0

hashTable 和 hashMap 作缓存,实现的两种单例的区别

看过不同的源码,可还是不太懂下面两种数据结构实现的单例有什么区别: // 第一种使用 hashtableprivate static Hashtable managers = new Hashtable();// 第二种,使用 hashmap// private ...

静心天涯 ⋅ 2014/11/18 ⋅ 8

HashMap中傻傻分不清楚的那些概念

很多人在通过阅读源码的方式学习Java,这是个很好的方式。而JDK的源码自然是首选。在JDK的众多类中,我觉得HashMap及其相关的类是设计的比较好的。很多人读过HashMap的代码,不知道你们有没有...

⋅ 05/20 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

JavaScript零基础入门——(八)JavaScript的数组

JavaScript零基础入门——(八)JavaScript的数组 欢迎大家回到我们的JavaScript零基础入门,上一节课我们讲了有关JavaScript正则表达式的相关知识点,便于大家更好的对字符串进行处理。这一...

JandenMa ⋅ 今天 ⋅ 0

sbt网络问题解决方案

转自:http://dblab.xmu.edu.cn/blog/maven-network-problem/ cd ~/.sbt/launchers/0.13.9unzip -q ./sbt-launch.jar 修改 vi sbt/sbt.boot.properties 增加一个oschina库地址: [reposit......

狐狸老侠 ⋅ 今天 ⋅ 0

大数据,必须掌握的10项顶级安全技术

我们看到越来越多的数据泄漏事故、勒索软件和其他类型的网络攻击,这使得安全成为一个热门话题。 去年,企业IT面临的威胁仍然处于非常高的水平,每天都会看到媒体报道大量数据泄漏事故和攻击...

p柯西 ⋅ 今天 ⋅ 0

Linux下安装配置Hadoop2.7.6

前提 安装jdk 下载 wget http://mirrors.hust.edu.cn/apache/hadoop/common/hadoop-2.7.6/hadoop-2.7.6.tar.gz 解压 配置 vim /etc/profile # 配置java环境变量 export JAVA_HOME=/opt/jdk1......

晨猫 ⋅ 今天 ⋅ 0

crontab工具介绍

crontab crontab 是一个用于设置周期性被执行的任务工具。 周期性执行的任务列表称为Cron Table crontab(选项)(参数) -e:编辑该用户的计时器设置; -l:列出该用户的计时器设置; -r:删除该...

Linux学习笔记 ⋅ 今天 ⋅ 0

深入Java多线程——Java内存模型深入(2)

5. final域的内存语义 5.1 final域的重排序规则 1.对于final域,编译器和处理器要遵守两个重排序规则: (1)在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用...

江左煤郎 ⋅ 今天 ⋅ 0

面试-正向代理和反向代理

面试-正向代理和反向代理 Nginx 是一个高性能的反向代理服务器,但同时也支持正向代理方式的配置。

秋日芒草 ⋅ 今天 ⋅ 0

Spring 依赖注入(DI)

1、Setter方法注入: 通过设置方法注入依赖。这种方法既简单又常用。 类中定义set()方法: public class HelloWorldOutput{ HelloWorld helloWorld; public void setHelloWorld...

霍淇滨 ⋅ 昨天 ⋅ 0

马氏距离与欧氏距离

马氏距离 马氏距离也可以定义为两个服从同一分布并且其协方差矩阵为Σ的随机变量之间的差异程度。 如果协方差矩阵为单位矩阵,那么马氏距离就简化为欧氏距离,如果协方差矩阵为对角阵,则其也...

漫步当下 ⋅ 昨天 ⋅ 0

聊聊spring cloud的RequestRateLimiterGatewayFilter

序 本文主要研究一下spring cloud的RequestRateLimiterGatewayFilter GatewayAutoConfiguration @Configuration@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMi......

go4it ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部