`
chinakite
  • 浏览: 125822 次
  • 性别: Icon_minigender_1
社区版块
存档分类
最新评论

JDK中HashMap的分析

JDK 
阅读更多
J2SDK中提供了大量的通用类,供我们在开发的时候使用。其中,HashMap类是我们经常使用的。下面简单的分析了HashMap类的一些主要代码。

概述
HashMap类位于java.util包中。主要作用是提供了一个Map的实现,可以比较方便的进行 关键字(key) – 值(value)的存取。其主要方法为:

public Object put(Object key, Object value) 加入一个对象。
public Object get(Object key) 获得一个对象。如果没找到对应于key的对象,返回null.

下面着重分析这两个方法。

put方法
put方法的作用是将一个对象(value)加入到map中,它的key为(key)。如果key重复了,将替换掉旧的值,并将旧值返回。如果key没有重复,将新的值插入。该部分代码如下(有删节):

Object k = maskNull(key);
int hash = hash(k);
int i = indexFor(hash, table.length);
for (Entry e = table[i]; e != null; e = e.next) {
   if (e.hash == hash && eq(k, e.key)) {
      Object oldValue = e.value;
      e.value = value;
      e.recordAccess(this);
      return oldValue;
   }
}
addEntry(hash, k, value, i);
return null;首先,程序执行了maskNull函数。该函数很简单,仅仅判断如果key非空,直接返回,否则返回一个new Object(). 接下来,使用HashMap类中函数hash(k)来计算key的hash值(这应该就是这个类被命名为HashMap的原因)。比较有意思的是,hash函数中,并没有直接返回k.hashCode(),而是在hashCode()之上进行了额外的计算。按照该函数的javadoc,这是为了抵御"某些不良设计的hashCode函数"。

再往下,是计算hash值在列表中的起始位置。table就是实际保存值的列表,indexFor这个函数返回的是hash值的在table中的位置。具体含义为,一些key的hash值很可能是相同的(冲突),这些key冲突的key--value所在的项组成一个链表,indexFor函数返回的就是这个链表的首项。而这个indexFor函数极其简单:

static int indexFor(int h, int length) {
   return h & (length-1);
}很明显,因为传入的hash值(h)可能为很大的数,所以通过与table的长度做AND运算,保证得到的位置在[0, table.length-1]的区间内。

这就带来一个问题:如果table的长度增加了,那么indexFor函数返回的位置可能就变化了,这就产生了错误。实际上,HashMap是能够避免这个问题的,后文分析。

继续查看put函数的代码。在得到了起始的位置之后,是一个while循环。该循环很简单,依次遍历相同hash值的Entry,如果key也相等,那么将value赋入,同时返回旧的值。否则的话,while执行完毕,还没有找到key,那么,执行addEntry函数,将key-value加入到table中。在加入后,进行判断:

if (size++ >= threshold)
   resize(2 * table.length);即如果新的容量大于限制值,那么会进行容量扩充的操作,也就是扩充table的大小。新容量是旧容量的2倍。在resize函数中,要生成新的table并设置成新的容量,同时,进行比较重要的操作,即将旧的table中的内容拷贝到新的table中去。在拷贝的过程中,Entry在新的table中的位置是经过重新计算的,所以上文提到的问题不会出现。

get方法
get方法相对put方法很简单,代码如下:

public Object get(Object key) {
   Object k = maskNull(key);
   int hash = hash(k);
   int i = indexFor(hash, table.length);
   Entry e = table[i];
   while (true) {
      if (e == null)
         return e;
      if (e.hash == hash && eq(k, e.key))
         return e.value;
      e = e.next;
   }
}首先计算hash值,然后调用indexFor函数得到起始的位置,获取该位置上的项,改项即为具有相同hash值的链表的首项。最后使用while循环来遍历这个链表。在循环中,逐个比较如果key也相等了,说明找到了,返回,否则继续找下去。如果没有hash值相等的项了,可以判断为没找到,返回空。

table详细内容
见下图.


每个key--value对放在Entry对象中。由key计算出的hash值相等的Entry组成一个链表,链表的元素之间使用Entry.next相连,链表的首位置由indexOf计算得到。遍历这个链表即可以得到某个key可能存在的Entry,HashMap也通过这种方式来解决冲突。

一些值得注意的问题
在上文中,我们注意到,如果table容量大于某个限制,那么将要进行扩充。扩充的操作,实际上是新建table,进行拷贝,并且重建Entry链表的过程。这个过程自然是极其耗时的,我们也应该尽量避免这个操作。避免的方法也很简单,就是初始化HashMap的时候,指定一个比较大的容量即可(默认是16). HashMap提供了构造函数 public HashMap(int initialCapacity) 来解决这个问题。
可以看出,无论是在添加的put函数,和获取的get函数,均要遍历冲突的链表,也就是说,如果在HashMap中存在大量的冲突(因为某种原因,key计算出来的hash值重复),那么,HashMap将近似退化为链表的线性查找。这就会比较大的影响性能。这就要求我们仔细设计key对象的hashCode()函数,尽量少的减少不同的key得多相同的hash值的情况(自然,完全不同的hashCode的值是不可能的)
分享到:
评论

相关推荐

    JDK7HashMap源码

    精确的版本号是jdk-7u80。想不通,竟然很多人都收费,这个明明可以在安装JDK的目录中找到啊!自己下一个JDK就可以得到。

    JDK8HashMap源码

    精确的版本号是jdk-8u181。想不通,竟然很多人都收费,这个明明可以在安装JDK的目录中找到啊!自己下一个JDK就可以得到。

    changdy#codingdoc#JDK8 HashMap解析1

    JDK8 HashMap解析参考

    JDK6HashMap源码

    精确的版本号是jdk-6u45。想不通,竟然很多人都收费,这个明明可以在安装JDK的目录中找到啊!自己下一个JDK就可以得到。

    jdk1.7 HashMap中的致命错误:循环链表

    总的来说,JDK1.7 HashMap的循环链表问题源自其设计中的并发缺陷,而在JDK1.8中,通过改进插入策略和引入红黑树,大大提升了HashMap在多线程环境下的性能和安全性。对于开发者而言,理解这些内部机制有助于我们在...

    JDK5HashMap源码

    想不通,竟然很多人都收费,这个明明可以在安装JDK的目录中找到啊!自己下一个JDK就可以得到。 想不通,竟然很多人都收费,这个明明可以在安装JDK的目录中找到啊!自己下一个JDK就可以得到。

    jdk8-HashMap,效果演示

    jdk8-HashMap,效果演示

    02-JDK1.7中的HashMap底层实现分析.mp4

    02-JDK1.7中的HashMap底层实现分析.mp4

    03-JDK1.8中的HashMap底层实现分析.mp4

    03-JDK1.8中的HashMap底层实现分析.mp4

    Jdk1.8 HashMap实现原理详细介绍

    在HashMap中,哈希桶数组`table`的长度必须是2的幂,这是因为哈希算法计算出来的索引是通过与运算(`&`)和位移运算来完成的,这样的设计使得哈希值的计算更高效,同时减少了冲突的可能性。与素数相比,虽然合数可能...

    Jdk1.8中的HashMap实现原理.docx

    此外,HashMap中的size字段记录了当前存储的键值对数量,而modCount则用于跟踪HashMap结构的变化,用于实现迭代器的快速失败机制,当HashMap在迭代过程中被修改时,迭代器能够立即抛出异常,避免不一致的结果。...

    源码解析jdk8.0集合:HashMap的底层实现原理.pdf

    HashMap中几个关键字段的含义如下: - DEFAULT_INITIAL_CAPACITY:默认初始化容量,为16,即2的4次方。 - MAXIMUM_CAPACITY:最大容量,为2的30次方。 - DEFAULT_LOAD_FACTOR:默认负载因子,为0.75。 - TREEIFY_...

    Jdk1.8中的HashMap实现原理.pdf

    当HashMap中的键值对数量达到`threshold`时,HashMap会进行扩容,将数组长度翻倍,并重新分布所有的键值对。这种设计保证了在大多数情况下,哈希碰撞的概率较低,提高了查找效率。负载因子(loadFactor)和数组长度...

    jdk17中文说明文档

    "方便已义中文方式浏览jdk中的说明"强调了这个文档是中文翻译版,使得开发者能够更轻松地理解JDK中的各种类、接口、方法和概念,降低了学习和使用的难度。 **标签解析:** "范文/模板/素材" 这个标签可能是指这个...

    hashmap_use_in_JDK7.zip

    《HashMap在JDK7中的实现解析》 HashMap是Java编程语言中最常用的集合类之一,它在JDK7中有着广泛的应用。本篇文章将深入探讨HashMap的内部实现原理,通过源码分析,帮助开发者更好地理解和使用这个高效的数据结构...

    jdk1.7和jdk1.8中hashmap区别

    jdk1.7和jdk1.8中hashmap区别: Hashmap解决冲突是采用链表,性能上就抱有一定疑问,如果说成百上千个节点在Hash时发生碰撞。存储在一个链表中,那么如果要查找其中的一个节点,就不可避免的花费 O(n) 的查找时间,...

    马士兵老师HashMap学习笔记

    本文将结合马士兵老师的讲解,详细阐述HashMap在不同JDK版本中的put操作流程,以及可能遇到的死循环问题。 首先,我们来看JDK 8中HashMap的put操作。在JDK 8中,HashMap进行了重大的优化,引入了红黑树(Red-Black ...

    java面试 集合中知识点 HashMap(JDK1.8)源码+底层数据结构分析 整理.pdf

    2. **JDK1.8之后**:在JDK1.8中,当链表长度超过8且数组长度超过64时,HashMap会将链表转换为红黑树。红黑树是一种自平衡二叉查找树,它的插入、删除和查找操作的时间复杂度可以达到O(logn),这显著提升了在高冲突率...

    java面试 集合中知识点 HashMap(JDK1.8)源码+底层数据结构分析 整理.docx

    `put`方法是HashMap中用于添加键值对的关键方法。它首先计算键的哈希值,然后根据哈希值确定元素在数组中的位置。如果位置已存在元素,会进行链表或红黑树的查找,判断是否为相同的键,如果是则更新值,否则在链表或...

Global site tag (gtag.js) - Google Analytics