`
微Smile
  • 浏览: 34902 次
  • 性别: Icon_minigender_2
  • 来自: 湖南
社区版块
存档分类
最新评论

HashMap的内部实现机制之篇一

    博客分类:
  • java
 
阅读更多

        HashMap的内部实现是采用的是hash表这种数据结构。

 

   什么是hash表?

   答曰:hash表又叫散列表。hash表是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做hash函数,存放记录的数组叫做hash表。

    简单的说:hash表就是一个数组与链表的集合。集成了数组遍历快和链表方便插入删除的优点。

   

     HashMap是如何内部实现的?

     大概说来,主要有以下几点:

1 底层是用一个Entry<k,v>数组实现的,每个Entry对象的内部又含有指向下一个Entry类型对象的引用。

 

看Entry类型,内部拥有加入Map中的K,V,hash值,和自身的一个引用

  static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next; ///Entry类型内部有一个自己类型的引用,指向下一个Entry
        
        final int hash; 
     ………………………………………………………………}

 
实例化数组(默认大小为16)后,通过hash()函数得到在数组中的存储位置index,笔者把这一步就理解为散列。

    且看源代码:

  

//源码中一个不带参数的构造方法,new了一个Entry数组
  public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
        table = new Entry[DEFAULT_INITIAL_CAPACITY];
        init();
    }

 注:默认的default_init_capicity为16。

public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        ...........
}

     注意此处与hashTable的对比,hashTable是直接用key.hashCoe()当hash值的,因此少了hash()函数,散列分布没那么均匀,这也是hashMap代替HashTable的一定优化。

  static int hash(int h) {
        // 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);
    }
/**
     * Returns index for hash code h.
     */
    static int indexFor(int h, int length) {
        return h & (length-1);
    }

  暂且不看key值为空的情况。当它不为空时,通过一个hash()函数得到hash值,函数参数为key.hashCode()(暂且知道hashCode是一种散列码,在此我们可先理解为对象的内存地址,具体请看下篇),然后再调用一个indexFor()函数来得到数组中的存储位置。

   内部实现毕竟要精益求精一些。为什么在这里要绕两个函数hash()函数和indexFor()函数才得到真正需要的index?

  其实这样做是为了让散列更加均匀,减少冲突的概率。当每个数组上都有值时,链表中需存放的元素就少了,因此只是尽可能的求得一种时间和空间上的平衡。然而这两个函数为什么是这样,笔者真心不知。再次还请看官不吝赐教!!!!

 

  

    附录:此处还有key为空的情况,key值重复的情况。当key值重复时,我们自己测试易发现是用新的value值代替了旧的value值,对此有兴趣的看官不妨深究下把。

 

 

2 解决冲突:因为即使键key不同,通过hash函数得到的index值也可能相同(当然,也正是存在这种冲突,才存在hash表结构,不然就直接是数组存储了)。这时我们可以把得到相同index值的元素连接到数组中对应index值位置的后面,即把该位置作为链表的头结点,在后面添加节点。

 

3 数组扩容。通过api可以发现,hashMap的构造方法可以有两个参数,一个为初始容量initCapicity,另外一个为装载因子loadFactor.初始容量很好理解,即为数组的初始大小;那何谓装载因子。据api注解:。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。 据笔者本人的理解:不管什么加载因子等书面名词,它只是对可以放进hash表的数目的一个限制,当超过这个限制时就需扩大数组容量,重新散列所有元素。我们也可以自定义一个阀值来代替这个加载因子,例如:当数组中的元素元素过少,而后面跟的链表过长,那么整体就偏向于链表存储而缺失了数组存储的优点了,因此我们可以设定一个阀值为500,即当某个链表长度超过500,则需要扩大数组容量,进行rehash操作。

  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)
            resize(2 * table.length);
    }

 注:此处threshold也可以理解为阀值。此处的resize()扩容方法和后面的rehash,源码为transfer()方法就需大家一起探究了。笔者有点力不从心啊。

 

4 rehash():在数组扩容之后执行该操作。即把原来已经散列过的元素取出重新散列一遍,因为数组长度扩大了,这得到的散列位置可能和原来不同。因为要全部取出原来已经散列过的元素,因此这步操作是非常耗时的。因此我们在预定数组初始长度时,最好选取一个合适的长度和加载因子,尽量避免rehash()出现。

 

 至此,hashMap的内部实现机制大体框架就是这样了。 

 

分享到:
评论
3 楼 Mr_lee_2012 2012-03-12  
精辟,同为大三,深表惭愧!
2 楼 微Smile 2012-03-09  
coolxing 写道
不错
楼主的叙述虽然有很多不清晰的地方, 但至少给对HashMap内部实现比较感兴趣的同学一个切入点

恩 谢谢 我会继续努力的!
1 楼 coolxing 2012-03-09  
不错
楼主的叙述虽然有很多不清晰的地方, 但至少给对HashMap内部实现比较感兴趣的同学一个切入点

相关推荐

    基于JavaScript的HashMap实现

    总之,"基于JavaScript的HashMap实现"这篇博客深入讲解了如何在JavaScript环境中构建一个高效、灵活的HashMap数据结构,这对于理解JavaScript的数据存储机制和提升编程技巧是非常有价值的。通过阅读和理解HashMap.js...

    HashMap底层实现原理共6页.pdf.zip

    这篇文档将深入探讨HashMap的内部机制,包括其设计理念、数据结构、哈希函数、冲突解决策略以及扩容机制。 1. **设计理念** HashMap的设计目标是实现快速的插入、删除和查找操作。它通过使用哈希表,将键(Key)...

    Javascript实现和操作HashMap

    在JavaScript中,我们可以利用对象(object)作为HashMap的基础,因为JavaScript的对象本质上就是一个键值对的集合,其内部已经实现了哈希表的机制。 ### 自定义HashMap ```javascript class HashMap { ...

    电话本管理系统HashMap实现

    Map接口存储键值对(key-value pairs),而HashMap则使用哈希表数据结构来实现,提供平均时间复杂度为O(1)的插入、删除和查找操作。哈希表通过计算对象的哈希码来定位数据,这使得访问速度非常快。 首先,我们需要...

    HashMap类

    通过阅读和理解HashMap的源码,我们可以更好地掌握其内部实现细节,例如扩容机制、桶的分配策略等,这对于优化代码性能和解决潜在问题非常有帮助。在阅读《HashMap1.js》和《HashMap.js》这两个文件时,可以深入分析...

    hashMap工具类

    在本篇文章中,我们将详细介绍一个名为`hashMap`的工具类,该类被设计用于Adobe Flex应用程序中,旨在提供一种简单且高效的方法来处理键值对数据结构。通过深入分析该类的实现细节,我们能够更好地理解其内部机制,...

    java提高篇(二三)-----HashMap.pdf

    HashMap内部使用的Entry类实现了Map.Entry接口,每个Entry对象包含一个键和一个值,以及指向下一个相同哈希桶中Entry的引用。这样,即使有哈希冲突,也能通过链表顺序遍历找到正确的键值对。 总结: HashMap是Java...

    手写HashMap源码.rar

    本篇文章将通过分析一个名为"MyHashMap"的手写HashMap源码,来探讨HashMap的内部机制,帮助提升编程能力。 首先,HashMap的核心是基于哈希表的数据结构,它利用键(Key)的哈希函数将键映射到数组的特定位置,从而...

    hashmap_use_in_JDK7.zip

    本篇文章将深入探讨HashMap的内部实现原理,通过源码分析,帮助开发者更好地理解和使用这个高效的数据结构。 HashMap是一个基于哈希表的Map接口实现,它提供了快速的插入、删除和查找操作。在JDK7中,HashMap的主要...

    在Java8与Java7中HashMap源码实现的对比

    在Java的集合框架中,HashMap是一个非常重要的数据结构,它提供了高效的键值对存储和查找功能。本篇文章将深入对比Java 7和Java 8中HashMap...对于开发者来说,理解HashMap的内部机制有助于写出更高效、更稳定的代码。

    HashMap源码分析

    这篇源码分析将深入探讨HashMap的工作原理和内部实现。 在HashMap中,每个键值对被封装在一个Entry对象中,这些Entry对象存储在一个数组中。当插入新的键值对时,HashMap会根据键的哈希值计算出数组的索引位置。...

    JavaHashSet和HashMap源码剖析编程开发技术

    本篇技术文档将深入剖析这两类数据结构的源码,帮助开发者理解其内部实现原理,提升在实际开发中的应用能力。 HashSet类是基于HashMap实现的,它不包含重复元素,并且不保证集合中元素的顺序。在HashSet中,元素的...

    HashMap与CorruntHashMap性能对比

    它们在很多场景下都能提供高效的查找、插入和删除操作,但两者的内部实现和线程安全性有着显著的区别。这篇文章将深入探讨这两个类的性能对比及其背后的原理。 `HashMap`是Java集合框架中的基础类,首次出现在JDK ...

    Java集合系列之HashMap源码分析

    Java集合系列之HashMap源码分析是Java集合系列中的一篇非常重要的文章,它详细介绍了Java集合系列之HashMap源码,具有很高的参考价值。下面我们将对HashMap源码进行详细的分析。 HashMap是基于哈希表实现的,它的...

    java集合类HashMap总结共7页.pdf.zip

    HashMap作为其中的一员,是基于哈希表实现的,提供了键值对(key-value pair)的存储方式,具有快速查找和插入的特点。这篇7页的PDF文档“java集合类HashMap总结”可能是对HashMap类的深入解析,包括其原理、常用...

    HashMap源码粗略解读(面试必问)

    HashMap是Java中最常用的集合类之一,它提供了高效的存储和检索对象键值对的能力。这篇文章将对HashMap的一些核心知识点进行深入解读,特别关注于面试中常见的问题。 1. **HashMap的默认容量** HashMap的默认容量...

Global site tag (gtag.js) - Google Analytics