Java源码角度分析HashMap用法

 更新时间:2018年01月04日 10:07:11   作者:Leesire  
这篇文章主要介绍了Java源码角度分析HashMap用法,具有一定借鉴价值,需要的朋友可以参考下

—HashMap—

优点:超级快速的查询速度,时间复杂度可以达到O(1)的数据结构非HashMap莫属。动态的可变长存储数据(相对于数组而言)。

缺点:需要额外计算一次hash值,如果处理不当会占用额外的空间。

—HashMap如何使用—

平时我们使用hashmap如下

Map<Integer,String> maps=new HashMap<Integer,String>();   
maps.put(1, "a");   
maps.put(2, "b");

上面代码新建了一个HashMap并且插入了两个数据,这里不接受基本数据类型来做K,V

如果这么写的话,就会出问题了:

Map<int,double> maps=new HashMap<int,double>();

我们为什么要这样使用呢?请看源码:

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

这是HashMap实现类的定义。

—HashMap是一个动态变长的数据结构—

在使用HashMap的时候,为了提高执行效率,我们往往会设置HashMap初始化容量:

Map<String,String> rm=new HashMap<String,String>(2)

或者使用guava的工具类Maps,可以很方便的创建一个集合,并且,带上合适的大小初始化值。

Map<String, Object> map = Maps.newHashMapWithExpectedSize(7);

那么为什么要这样使用呢?我们来看他们的源码构造函数。

未带参的构造函数:

public HashMap() {   
    this.loadFactor = DEFAULT_LOAD_FACTOR;   
    threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);   
    table = new Entry[DEFAULT_INITIAL_CAPACITY];   
    init();   
  } 

public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
table = new Entry[DEFAULT_INITIAL_CAPACITY];
init();
}

/** 
   * Constructs an empty <tt>HashMap</tt> with the specified initial 
   * capacity and the default load factor (0.75). 
   * 
   * @param initialCapacity the initial capacity. 
   * @throws IllegalArgumentException if the initial capacity is negative. 
   */ 
  public HashMap(int initialCapacity) { 
    this(initialCapacity, DEFAULT_LOAD_FACTOR); 
  }

名词解释:

DEFAULT_LOAD_FACTOR  //默认加载因子,如果不制定的话是0.75  
DEFAULT_INITIAL_CAPACITY //默认初始化容量,默认是16  
threshold //阈(yu)值 根据加载因子和初始化容量计算得出 ,<span style="color: rgb(54, 46, 43); font-family: "microsoft yahei";">threshold表示当HashMap的size大于threshold时会执行resize操作。

因此我们知道了,如果我们调用无参数的构造方法的话,我们将得到一个16容量的数组。

所以问题就来了:如果初始容量不够怎么办?

数组是定长的,如何用一个定长的数据来表示一个不定长的数据呢,答案就是找一个更长的,但是在resize的时候是很降低效率的。所以我们建议HashMap的初始化的时候要给一个靠谱的容量大小。

—HashMap的Put方法—

public V put(K key, V value) {  
    if (key == null) //键为空的情况,HashMap和HashTable的一个区别  
      return putForNullKey(value);  
    int hash = hash(key.hashCode()); //根据键的hashCode算出hash值  
    int i = indexFor(hash, table.length); //根据hash值算出究竟该放入哪个数组下标中  
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {//整个for循环实现了如果存在K那么就替换V  
      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;  
  }

如果插入的数据超过现有容量就会执行

addEntry(hash, key, value, i);
void addEntry(int hash, K key, V value, int bucketIndex) {   
Entry<K,V> e = table[bucketIndex];   
    table[bucketIndex] = new Entry<K,V>(hash, key, value, e);   
    if (size++ >= threshold)   
     <span style="color:#ff0000;"><strong> resize(2 * table.length);
}

这里显示了如果当前 size++ >threshold 的话那么就会扩展当前的size的两倍,执行resize(2*table.length),那么他们是如何扩展的呢?

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]; <span style="color: rgb(51, 51, 51); font-family: Arial;">new 一个新的数组,</span> 
    <strong> <span style="color:#ff0000;">transfer(newTable);</span> </strong> //将就数组转移到新的数组中 
    table = newTable;   
    threshold = (int)(newCapacity * loadFactor);  //重新计算容量 
  }

对于转移数组transfer是如何转移的呢?

void transfer(Entry[] newTable) {   
    Entry[] src = table;   
    int newCapacity = newTable.length;   
    for (int j = 0; j < src.length; j++) {   
      Entry<K,V> e = src[j];   
      if (e != null) {   
        src[j] = null;   
        do {   
          Entry<K,V> next = e.next;   
          int i = <strong><span style="color:#ff0000;">indexFor(e.hash, newCapacity);  //根据hash值个容量重新计算下标</span></strong> 
          e.next = newTable[i];   
          newTable[i] = e;   
          e = next;   
        } while (e != null);   
      }   
    }   
  } 

—hashmap扩容额外执行次数—

因此如果我们要添加一个1000个元素的hashMap,如果我们用默认值那么我么需要额外的计算多少次呢

当大于16*0.75=12的时候,需要从新计算12次

当大于16*2*0.75=24的时候,需要额外计算24次

……

当大于16*n*0.75=768的时候,需要额外计算768次

所以我们总共在扩充过程中额外计算12+24+48+……+768次

因此强力建议我们在项目中如果知道范围的情况下,我们应该手动指定初始大小像这样:

Map<Integer,String> maps=new HashMap<Integer,String>(1000);

总结:这就是为什么当hashmap使用过程中如果超出 初始容量后他的执行效率严重下降的原因。

以上就是本文关于Java源码角度分析HashMap用法的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!

相关文章

  • Java C++刷题leetcode1106解析布尔表达式

    Java C++刷题leetcode1106解析布尔表达式

    这篇文章主要为大家介绍了Java C++刷题leetcode1106解析布尔表达式示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-01-01
  • Java中多态性的实现方式

    Java中多态性的实现方式

    这篇文章主要介绍了Java中多态性的实现方式,什么是多态?通过简单的一道题目帮大家理解java多态性,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-09-09
  • Spring Boot支持HTTPS步骤详解

    Spring Boot支持HTTPS步骤详解

    这篇文章主要介绍了Spring Boot支持HTTPS步骤详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-09-09
  • Spring用AspectJ开发AOP(基于Annotation)

    Spring用AspectJ开发AOP(基于Annotation)

    这篇文章主要介绍了Spring用AspectJ开发AOP(基于Annotation),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-10-10
  • Jpa 如何使用@EntityListeners 实现实体对象的自动赋值

    Jpa 如何使用@EntityListeners 实现实体对象的自动赋值

    这篇文章主要介绍了Jpa 如何使用@EntityListeners 实现实体对象的自动赋值,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • Java动态规划篇之线性DP的示例详解

    Java动态规划篇之线性DP的示例详解

    这篇文章主要通过几个例题为大家详细介绍一些Java动态规划中的线性DP,文中的示例代码讲解详细,对我们学习Java有一定的帮助,需要的可以参考一下
    2022-11-11
  • MybatisPlus更新为null的字段及自定义sql注入

    MybatisPlus更新为null的字段及自定义sql注入

    mybatis-plus在执行更新操作,当更新字段为空字符串或者null的则不会执行更新,本文主要介绍了MybatisPlus更新为null的字段及自定义sql注入,感兴趣的可以了解一下
    2024-05-05
  • Java 多线程并发AbstractQueuedSynchronizer详情

    Java 多线程并发AbstractQueuedSynchronizer详情

    这篇文章主要介绍了Java 多线程并发AbstractQueuedSynchronizer详情,文章围绕主题展开想象的内容介绍,具有一定的参考价值,感兴趣的小伙伴可以参考一下
    2022-06-06
  • IDEA启动Tomcat报Unrecognized option: --add-opens=java.base/java.lang=ALL-UNNAMED的解决方法

    IDEA启动Tomcat报Unrecognized option: --add-opens=java

    这篇文章主要为大家介绍了解决IDEA启动Tomcat报Unrecognized option: --add-opens=java.base/java.lang=ALL-UNNAMED的方法,文中通过图文介绍的非常详细,需要的朋友可以参考下
    2023-08-08
  • idea hibernate jpa 生成实体类的实现

    idea hibernate jpa 生成实体类的实现

    这篇文章主要介绍了idea hibernate jpa 生成实体类的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-11-11

最新评论