深入理解-HashMap

一、HashMap概述

  HashMap 在家族中位置:实现了Map接口,继承AbstractMap类。HashMap 允许key/value 都为null.

二、HashMap存储结构

  

  HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。在其内部维护一个Entry类型数组,初始大小为16。

 1 /**
 2  * The table, resized as necessary. Length MUST Always be a power of two.
 3  */
 4 transient Entry[] table;
 5
 6 static class Entry<K,V> implements Map.Entry<K,V> {
 7     final K key;
 8     V value;
 9     Entry<K,V> next;
10     final int hash;
11     ……
12 }

三、HashMap 的存取遍历

  1、存储 

 1 public V put(K key, V value) {
 2     // HashMap允许存放null键和null值。
 3     // 当key为null时,调用putForNullKey方法,将value放置在数组第一个位置。
 4     if (key == null)
 5         return putForNullKey(value);
 6     // 根据key的keyCode重新计算hash值。
 7     int hash = hash(key.hashCode());
 8     // 搜索指定hash值在对应table中的索引。
 9     int i = indexFor(hash, table.length);
10     // 如果 i 索引处的 Entry 不为 null,通过循环不断遍历 e 元素的下一个元素。
11     for (Entry<K,V> e = table[i]; e != null; e = e.next) {
12         Object k;
13         if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
14             V oldValue = e.value;
15             e.value = value;
16             e.recordAccess(this);
17             return oldValue;
18         }
19     }
20     // 如果i索引处的Entry为null,表明此处还没有Entry。
21     modCount++;
22     // 将key、value添加到i索引处。
23     addEntry(hash, key, value, i);
24     return null;
25 }

当我们往HashMap中put元素的时候,先根据key的hashCode重新计算hash值,根据hash值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。

 1     final int hash(Object k) {
 2         int h = hashSeed;
 3         if (0 != h && k instanceof String) {
 4             return sun.misc.Hashing.stringHash32((String) k);
 5         }
 6
 7         h ^= k.hashCode();
 8
 9         // This function ensures that hashCodes that differ only by
10         // constant multiples at each bit position have a bounded
11         // number of collisions (approximately 8 at default load factor).
12         h ^= (h >>> 20) ^ (h >>> 12);
13         return h ^ (h >>> 7) ^ (h >>> 4);
14     }
1     static int indexFor(int h, int length) {
2         // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
3         return h & (length-1);
4     }

  获取的hash码之后对数组大小取模。

addEntry(hash, key, value, i)方法根据计算出的hash值,将key-value对放在数组table的i索引处。addEntry 是 HashMap 提供的一个包访问权限的方法,代码如下:

1     void addEntry(int hash, K key, V value, int bucketIndex) {
2         if ((size >= threshold) && (null != table[bucketIndex])) {
3             resize(2 * table.length);
4             hash = (null != key) ? hash(key) : 0;
5             bucketIndex = indexFor(hash, table.length);
6         }
7
8         createEntry(hash, key, value, bucketIndex);
9     }

如果经过hash之后,数组的下标不在数组大小范围之内而且当前数组大小超过指定阈值,那么将会把hashmap 2倍扩容。

2、读取数据  

1     public V get(Object key) {
2         if (key == null)
3             return getForNullKey();
4         Entry<K,V> entry = getEntry(key);
5
6         return null == entry ? null : entry.getValue();
7     }
 1     private V getForNullKey() {
 2         if (size == 0) {
 3             return null;
 4         }
 5         for (Entry<K,V> e = table[0]; e != null; e = e.next) {
 6             if (e.key == null)
 7                 return e.value;
 8         }
 9         return null;
10     }
 1 final Entry<K,V> getEntry(Object key) {
 2         if (size == 0) {
 3             return null;
 4         }
 5
 6         int hash = (key == null) ? 0 : hash(key);
 7         for (Entry<K,V> e = table[indexFor(hash, table.length)];
 8              e != null;
 9              e = e.next) {
10             Object k;
11             if (e.hash == hash &&
12                 ((k = e.key) == key || (key != null && key.equals(k))))
13                 return e;
14         }
15         return null;
16     }

  首先,如果要获取的key==null,那么直接调用getForNullKey()从table[0]中,获取相应的value;如果key != null,那么将会获得该key的hashcode,之后获取table的下标,从中遍历出来相应的value。

3、遍历  

1         Set<String> keys = map.keySet();
2         Iterator<String> iterator = keys.iterator();
3         while (iterator.hasNext()) {
4             String key = iterator.next();
5             System.out.println("[" + key + "," + map.get(key) + "]");
6         }

4、resize() 再哈希

  当哈希表的容量超过默认容量时(在hashmap中,如果第一次hash之后,找到的数组下标不在合理的范围之内而且当前的大小超过规定阈值),必须调整table的大小。当容量已经达到最大可能值时,那么该方法就将容量调整到Integer.MAX_VALUE返回,这时,需要创建一张新表,将原表的映射到新表中。

 1     void resize(int newCapacity) {
 2         Entry[] oldTable = table;
 3         int oldCapacity = oldTable.length;
 4         if (oldCapacity == MAXIMUM_CAPACITY) {
 5             threshold = Integer.MAX_VALUE;
 6             return;
 7         }
 8
 9         Entry[] newTable = new Entry[newCapacity];
10         transfer(newTable, initHashSeedAsNeeded(newCapacity));
11         table = newTable;
12         threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
13     }

loadFactor的默认值为0.75,这是一个折中的取值。也就是说,默认情况下,数组大小为16,那么当HashMap中元素个数超过16*0.75=12的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。

四、性能

  Fail-Fast机制

  我们知道java.util.HashMap不是线程安全的,因此如果在使用迭代器的过程中有其他线程修改了map,那么将抛出ConcurrentModificationException,这就是所谓fail-fast策略

这一策略在源码中的实现是通过modCount域,modCount顾名思义就是修改次数,对HashMap内容的修改都将增加这个值,那么在迭代器初始化过程中会将这个值赋给迭代器的expectedModCount。其中modCount是声明为volatile,保证线程可见。

感谢:http://zhangshixi.iteye.com/blog/672697

时间: 2024-10-11 01:57:20

深入理解-HashMap的相关文章

十分钟深入理解HashMap源码

十分钟就要深入理解HashMap源码,看完你能懂?我觉得得再多看一分钟,才能完全掌握! 终于来到比较复杂的HashMap,由于内部的变量,内部类,方法都比较多,没法像ArrayList那样直接平铺开来说,因此准备从几个具体的角度来切入. 桶结构 HashMap的每个存储位置,又叫做一个桶,当一个Key&Value进入map的时候,依据它的hash值分配一个桶来存储. 看一下桶的定义:table就是所谓的桶结构,说白了就是一个节点数组. transient Node<K,V>[] tab

深入理解HashMap(及hash函数的真正巧妙之处)

原文地址:http://www.iteye.com/topic/539465 Hashmap是一种非常常用的.应用广泛的数据类型,最近研究到相关的内容,就正好复习一下.网上关于hashmap的文章很多,但到底是自己学习的总结,就发出来跟大家一起分享,一起讨论. 1.hashmap的数据结构 要知道hashmap是什么,首先要搞清楚它的数据结构,在java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,hashmap也不例外.

深入理解HashMap

转自: http://annegu.iteye.com/blog/539465  Hashmap是一种非常常用的.应用广泛的数据类型,最近研究到相关的内容,就正好复习一下.网上关于hashmap的文章很多,但到底是自己学习的总结,就发出来跟大家一起分享,一起讨论. 1.hashmap的数据结构 要知道hashmap是什么,首先要搞清楚它的数据结构,在java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,hashmap也不例

深入理解HashMap、ConcurrentHashMap

前言 Map 这样的 Key Value 在软件开发中是非常经典的结构,常用于在内存中存放数据. 本篇主要想讨论 ConcurrentHashMap 这样一个并发容器,在正式开始之前我觉得有必要谈谈 HashMap,没有它就不会有后面的 ConcurrentHashMap. HashMap 众所周知 HashMap 底层是基于 数组 + 链表 组成的,不过在 jdk1.7 和 1.8 中具体实现稍有不同. Base 1.7 1.7 中的数据结构图: 先来看看 1.7 中的实现. 这是 HashM

深入理解HashMap和CurrentHashMap

原文链接:https://segmentfault.com/a/1190000015726870 前言 Map 这样的 Key Value 在软件开发中是非常经典的结构,常用于在内存中存放数据. 本篇主要想讨论 ConcurrentHashMap 这样一个并发容器,在正式开始之前我觉得有必要谈谈 HashMap,没有它就不会有后面的 ConcurrentHashMap. HashMap 众所周知 HashMap 底层是基于 数组 + 链表 组成的,不过在 jdk1.7 和 1.8 中具体实现稍有

从源码来理解HashMap和HashSet

HashMap类 HashMap 内有一个table数组存放<K,V>,用关键字transient,则说明HashMap的table数组值是存放在内存中,不作为序列化数据保存. put函数 如果key==null, 注意:table是一个数组,而这个数组下每个元素的下面其实是个链表,都是通过hash(key)得到相同k位置(table[k]) 空值统一放在table的0位置,先遍历table[0]下的所有元素,如果插入的key==null,则将原本Entry的value替换,返回之前的值(ol

从源码理解HashMap

package java.util; import java.io.IOException; import java.io.InvalidObjectException; import java.io.Serializable; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.function.BiConsumer; import java.util.funct

理解HashMap底层原理,一个简单的HashMap例子

package com.jl.testmap; /** * 自定义一个HashMap * @author JiangLai * */ public class MyHashMap<K,V> { Node<K,V>[] table;//位桶数组 int size;//存放键值对的个数 public MyHashMap() { table = new Node[16];//长度一般定义为2的整数次幂 } public void put(K key,V value) { //定义新的节点

如果你这么去理解HashMap就会发现它真的很简单

Java中的HashMap相信大家都不陌生,也是大家编程时最常用的数据结构之一,各种面试题更是恨不得掘地三尺的去问HashMap.HashTable.ConcurrentHashMap,无论面试题多么刁钻的问,只要我们真正的掌握了它的设计思想,便可以不变应万变,hold住所有的面试题了. 本文主要包含以下内容,力求深入浅出一步一步彻底明白HashMap的设计思想: 数组的优势 数组是特殊的键值对 Hash函数 Hash冲突 此时再看HashMap源码 文章干货内容较多,建议大家“收藏”后持续阅读