`
javantsky
  • 浏览: 84278 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

白话HashMap

阅读更多

通过这篇文章我想回答下列几个问题:

1、HashMap的数据存储结构?

2、存储数据的逻辑?

3、key为null是怎么存储的?

4、怎么根据key取数据的?

5、为什么初始容量必须是2的n次方?

 

第一个问题:hashmap的数据存储结构



 如上图,HashMap由一个数组和一系列的链表组成,存储的数据类型为Entry,一个HashMap的内部类

Entry{hash,key,value,next}。

数组的长度为2的n次方,默认值为16,如果自己设置的值不为2的n次方,则由hashmap自己处理为2的n次方,比如说:

HashMap<String,String> hsm = new HashMap<String,String>(13);

源码如下:

public HashMap(int initialCapacity, float loadFactor) {
        
。。。。
        // Find a power of 2 >= initialCapacity
        int capacity = 1;
        while (capacity < initialCapacity) 
            capacity <<= 1;                           //实际效果就是2的n次方,当大于initialCapacity后停止循环    
        this.loadFactor = loadFactor;
        threshold = (int)(capacity * loadFactor); //数组扩容临界值
        table = new Entry[capacity];             //用计算后的容量初始化数组大小
。。。。
    }

 传入13,实际数组大小为16。

 

第二个问题:存储数据的逻辑

以刚才new的HashMap为例,存入 hsm.put("abc", "奥运、亚运都是浮云,有能耐整好春运");

看源码:

public V put(K key, V value) {
	if (key == null)     //如果key为null,执行特殊存储操作,在问题3中详细回答
	    return putForNullKey(value);
        int hash = hash(key.hashCode());       //再次hash
        int i = indexFor(hash, table.length);    //查找在数组中的存储位置
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {    //如果数组当前位置不为null,循环数组对应位置的链表
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {   //如果存在符合hash和key的条件的entry
               V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);              //啥也没干
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);     //如果数组当前位置为null,则插入当前entry
        return null;
    }

 查找在数组中的存储位置,源码:

static int indexFor(int h, int length) {
        return h & (length-1);
    }

   如果length为2的n次方,则length-1的二进制为1111...,比如现在(16-1)为1111,经过计算后返回值一定在数组长度范围内。

新增Entry到数组中,源码:

void addEntry(int hash, K key, V value, int bucketIndex) {
	Entry<K,V> e = table[bucketIndex];
        //new一个新的Entry,并存储在数组的bucketIndex位置上
        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
        //判断数组是否需要扩容
        if (size++ >= threshold)
            resize(2 * table.length);
    }

 第三个问题:key为null是怎么存储的

源码:

private V putForNullKey(V value) {
        //static final Object NULL_KEY = new Object();
        //如果key为null,则用默认的NULL_KEY的hashcode来查找数组中位置
        //我还看过不用indexFor(),直接 i=0 的实现,个人认为这种实现更好
        int hash = hash(NULL_KEY.hashCode());
        int i = indexFor(hash, table.length);

        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            if (e.key == NULL_KEY) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
  

第四个问题:怎么根据key取数据的

源码:

 

public V get(Object key) {
	if (key == null)//key为null则用特殊方法取value
	    return getForNullKey();
        int hash = hash(key.hashCode());
        //根据key的hash值取数组位置
        //如果不为null,则循环挂靠在该位置上的链表
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            //如果找到则返回value值
            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
                return e.value;
        }
        return null;
    }

    private V getForNullKey() {
        //取默认NULL_KEY的hash值,查找数组位置
        int hash = hash(NULL_KEY.hashCode());
        int i = indexFor(hash, table.length);

        Entry<K,V> e = table[i];
        while (true) {
            if (e == null)
                return null;
            if (e.key == NULL_KEY)
                return e.value;
            e = e.next;
        } 
        modCount++;
        addEntry(hash, (K) NULL_KEY, value, i);
        return null;
    }

 第五个问题:为什么初始容量必须是2的n次方

看源码:

static int indexFor(int h, int length) {
        return h & (length-1);
    }

所有的存储和查找都是通过这个方法查找数组中的位置的。

举个不是2的n次方的例子,如果长度为13会发生什么情况?

13-1的二进制为: 1100

也就是说hash值以...1100、...1101、 ...1110、 ...1111为结尾的一系列的key都会落到数组[12]的位置上,换句话说这个位置的链表会很长,通过hash散列的效果差,查找效率会低。

 

 

 

 

  • 大小: 29.7 KB
2
1
分享到:
评论

相关推荐

    hashmap面试题_hashmap_

    《HashMap面试题详解》 HashMap作为Java集合框架中的重要成员,是面试中常见的知识点,尤其在数据结构与算法、并发编程以及JVM内存管理等领域,HashMap的深入理解至关重要。本篇将围绕HashMap的相关面试题,从基础...

    hashmap实现原理

    哈希映射(HashMap)是Java编程语言中广泛使用的数据结构之一,主要提供键值对的存储和查找功能。HashMap的实现基于哈希表的概念,它通过计算对象的哈希码来快速定位数据,从而实现了O(1)的平均时间复杂度。在深入...

    HashMap介绍和使用

    ### HashMap介绍和使用详解 #### 一、HashMap的数据结构 HashMap是Java集合框架的一个重要组成部分,它实现了Map接口,能够存储键值对映射。在Java编程语言中,最基本的数据结构有两种:数组和引用(模拟指针)。...

    HashMap之resize()方法源码解读.docx

    HashMap之resize()方法源码解读 HashMap的resize()方法是HashMap中最核心的方法之一,该方法负责扩容HashMap的容量,以便存储更多的键值对。下面我们将对HashMap的resize()方法进行源码解读,了解其扩容机制和原理...

    HashMap和HashTable的区别和不同

    ### HashMap与HashTable的区别详解 #### 引言 在Java编程中,`HashMap`与`HashTable`作为两种常用的数据结构,经常被用来存储键值对数据。尽管它们在功能上相似,但在实现细节、性能表现以及使用场景方面存在显著...

    HashMap的数据结构

    HashMap是Java编程语言中一个非常重要的数据结构,它属于集合框架的一部分,主要用于存储键值对(Key-Value)数据。HashMap在内部实现上基于哈希表,也称为散列表,它提供了一种快速查找、插入和删除数据的方法,...

    Java HashMap类详解

    Java HashMap 类详解 本资源详细介绍了 Java 中的 HashMap 类,包括其实现机制、Hash 存储机制、集合存储机制等方面的知识点。 1. HashMap 和 HashSet 的关系 HashMap 和 HashSet 是 Java Collection Framework ...

    hashMap和hashTable的区别

    ### hashMap和hashTable的区别 #### 一、简介与基本概念 `HashMap` 和 `HashTable` 都是 Java 集合框架中非常重要的数据结构,它们都实现了 `Map` 接口,用于存储键值对。尽管它们在功能上有很多相似之处,但在...

    hashmap 实例

    《HashMap 实例解析与关联数据结构对比》 HashMap 是 Java 中常用的一种数据结构,属于 Java.util 包下的类,它是基于哈希表实现的。在本文中,我们将深入理解 HashMap 的实例及其工作原理,并与其他数据结构如 ...

    关于如何解决HashMap线程安全问题的介绍

    在Java编程中,HashMap是一个非常常用的集合类,用于存储键值对数据。然而,它存在一个重要的特性,那就是线程不安全。理解这个问题并找到解决方案是每个Java开发者必须掌握的知识。 HashMap线程不安全的原因主要...

    HASHMAP排序功能描述

    HashMap是Java编程语言中常用的集合类之一,它属于哈希表数据结构,提供key-value的存储方式,并且具有快速查询的特性。然而,HashMap本身并不保证元素的顺序,特别是当涉及到遍历或输出HashMap的内容时,顺序可能会...

    HashMap总结

    HashMap 总结 HashMap 是 Java 中的一种常用的数据结构,用于存储键值对(Key-Value)数据。下面是 HashMap 的一些特性和使用方法总结。 键(Key)的特性 1. 键可以为 null:HashMap 中的键可以为 null,这意味着...

    HashMap类.rar

    HashMap类在Java编程语言中是集合框架的一部分,它是一个基于哈希表的Map接口实现。HashMap提供了快速的插入、删除和查找操作,平均时间复杂度为O(1)。在这个压缩包“HashMap类.rar”中,包含的是易语言版本的...

    易语言HashMap类

    易语言HashMap类是一种在易语言编程环境中实现的高效数据结构,它主要用于存储键值对(key-value pairs),提供快速的数据存取。HashMap类基于哈希表(Hash Table)原理,通过计算键的散列值来确定数据在内存中的...

    基于JavaScript的HashMap实现

    在JavaScript中,HashMap是一种常用的键值对存储结构,它提供了快速的插入、删除和查找操作。JavaScript本身并不直接支持HashMap,但我们可以利用对象(Object)的特性来模拟HashMap的实现。这篇博客“基于...

    Java中HashMap详解(通俗易懂).doc

    Java中的HashMap是一个非常重要的数据结构,它实现了Map接口,提供了键值对的高效存储和访问。HashMap基于哈希表(也称为散列表)原理工作,它允许用户通过键(Key)快速查找对应的值(Value)。在HashMap中,键和值...

    HashMap与HashTable区别

    ### HashMap与HashTable的区别 在Java编程语言中,`HashMap`和`HashTable`是两种非常重要的数据结构,它们都实现了`Map`接口,并提供了键值对的存储方式。这两种数据结构虽然相似,但在实现细节和使用场景上存在...

    java HashMap原理分析

    Java HashMap原理分析 Java HashMap是一种基于哈希表的数据结构,它的存储原理是通过将Key-Value对存储在一个数组中,每个数组元素是一个链表,链表中的每个元素是一个Entry对象,Entry对象包含了Key、Value和指向...

    HASHMAP缓存.txt

    在深入探讨《HASHMAP缓存.txt》所提及的知识点前,我们先来解析一下文档的标题、描述和部分内容,以确保我们对所讨论的主题有全面的理解。标题“HASHMAP缓存.txt”暗示了文档主要关注的是Java编程语言中HashMap作为...

Global site tag (gtag.js) - Google Analytics