- 浏览: 449120 次
- 性别:
- 来自: 深圳
文章分类
最新评论
-
g_man1990:
update config 不成功啊
build-helper-maven-plugin 配置多 source resource 文件 -
netwelfare:
文章很详细,就是太长了,读起来有点困难,倒不如写精练点,像这篇 ...
Java 基本类型 -
huyuancai1010:
function commitForm() {
var ...
加时间戳或者随机数去除js缓存 -
Smile__:
不过这个东西以前还真没研究过 。
hibernate.jdbc.fetch_size 和 hibernate.jdbc.batch_size -
Smile__:
想不到你也是北大青鸟的 。哈
hibernate.jdbc.fetch_size 和 hibernate.jdbc.batch_size
Hashmap是一种非常常用的、应用广泛的数据类型,最近研究到相关的内容,就正好复习一下。网上关于hashmap的文章很多,但到底是自己学习的总结,就发出来跟大家一起分享,一起讨论。
1、hashmap的数据结构
要知道hashmap是什么,首先要搞清楚它的数据结构,在java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,hashmap也不例外。Hashmap实际上是一个数组和链表的结合体(在数据结构中,一般称之为“链表散列“),请看下图(横排表示数组,纵排表示数组元素【实际上是一个链表】)。
从图中我们可以看到一个hashmap就是一个数组结构,当新建一个hashmap的时候,就会初始化一个数组。我们来看看java代码:
- /**
- * The table, resized as necessary. Length MUST Always be a power of two.
- * FIXME 这里需要注意这句话,至于原因后面会讲到
- */
- transient Entry[] table;
/** * The table, resized as necessary. Length MUST Always be a power of two. * FIXME 这里需要注意这句话,至于原因后面会讲到 */ transient Entry[] table;
- static class Entry<K,V> implements Map.Entry<K,V> {
- final K key;
- V value;
- final int hash;
- Entry<K,V> next;
- ..........
- }
static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; final int hash; Entry<K,V> next; .......... }
上面的Entry就是数组中的元素,它持有一个指向下一个元素的引用,这就构成了链表。
当我们往hashmap中put元素的时候,先根据key的hash值得到这个元素在数组中的位置(即下标),然后就可以把这个元素放到对应的位置中了。如果这个元素所在的位子上已经存放有其他元素了,那么在同一个位子上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。从hashmap中get元素时,首先计算key的hashcode,找到数组中对应位置的某一元素,然后通过key的equals方法在对应位置的链表中找到需要的元素。从这里我们可以想象得到,如果每个位置上的链表只有一个元素,那么hashmap的get效率将是最高的,但是理想总是美好的,现实总是有困难需要我们去克服,哈哈~
2、hash算法
我们可以看到在hashmap中要找到某个元素,需要根据key的hash值来求得对应数组中的位置。如何计算这个位置就是hash算法。前面说过hashmap的数据结构是数组和链表的结合,所以我们当然希望这个hashmap里面的元素位置尽量的分布均匀些,尽量使得每个位置上的元素数量只有一个,那么当我们用hash算法求得这个位置的时候,马上就可以知道对应位置的元素就是我们要的,而不用再去遍历链表。
所以我们首先想到的就是把hashcode对数组长度取模运算,这样一来,元素的分布相对来说是比较均匀的。但是,“模”运算的消耗还是比较大的,能不能找一种更快速,消耗更小的方式那?java中时这样做的,
static int indexFor(int h, int length) { return h & (length-1); }
首先算得key得hashcode值,然后跟数组的长度-1做一次“与”运算(&)。看上去很简单,其实比较有玄机。比如数组的长度是2的4次方,那么hashcode就会和2的4次方-1做“与”运算。很多人都有这个疑问,为什么hashmap的数组初始化大小都是2的次方大小时,hashmap的效率最高,我以2的4次方举例,来解释一下为什么数组大小为2的幂时hashmap访问的性能最高。
看下图,左边两组是数组长度为16(2的4次方),右边两组是数组长度为15。两组的hashcode均为8和9,但是很明显,当它们和1110“与”的时候,产生了相同的结果,也就是说它们会定位到数组中的同一个位置上去,这就产生了碰撞,8和9会被放到同一个链表上,那么查询的时候就需要遍历这个链表,得到8或者9,这样就降低了查询的效率。同时,我们也可以发现,当数组长度为15的时候,hashcode的值会与14(1110)进行“与”,那么最后一位永远是0,而0001,0011,0101,1001,1011,0111,1101这几个位置永远都不能存放元素了,空间浪费相当大,更糟的是这种情况中,数组可以使用的位置比数组长度小了很多,这意味着进一步增加了碰撞的几率,减慢了查询的效率!
所以说,当数组长度为2的n次幂的时候,不同的key算得得index相同的几率较小,那么数据在数组上分布就比较均匀,也就是说碰撞的几率小,相对的,查询的时候就不用遍历某个位置上的链表,这样查询效率也就较高了。
说到这里,我们再回头看一下hashmap中默认的数组大小是多少,查看源代码可以得知是16,为什么是16,而不是15,也不是20呢,看到上面annegu的解释之后我们就清楚了吧,显然是因为16是2的整数次幂的原因,在小数据量的情况下16比15和20更能减少key之间的碰撞,而加快查询的效率。
所以,在存储大容量数据的时候,最好预先指定hashmap的size为2的整数次幂次方。就算不指定的话,也会以大于且最接近指定值大小的2次幂来初始化的,代码如下(HashMap的构造方法中):
- // Find a power of 2 >= initialCapacity
- int capacity = 1;
- while (capacity < initialCapacity)
- capacity <<= 1;
// Find a power of 2 >= initialCapacity int capacity = 1; while (capacity < initialCapacity) capacity <<= 1;
3、hashmap的resize
当hashmap中的元素越来越多的时候,碰撞的几率也就越来越高(因为数组的长度是固定的),所以为了提高查询的效率,就要对hashmap的数组进行扩容,数组扩容这个操作也会出现在ArrayList中,所以这是一个通用的操作,很多人对它的性能表示过怀疑,不过想想我们的“均摊”原理,就释然了,而在hashmap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize。
那么hashmap什么时候进行扩容呢?当hashmap中的元素个数超过数组大小*loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,也就是说,默认情况下,数组大小为16,那么当hashmap中元素个数超过16*0.75=12的时候,就把数组的大小扩展为2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知hashmap中元素的个数,那么预设元素的个数能够有效的提高hashmap的性能。比如说,我们有1000个元素new HashMap(1000), 但是理论上来讲new HashMap(1024)更合适,不过上面annegu已经说过,即使是1000,hashmap也自动会将其设置为1024。 但是new HashMap(1024)还不是更合适的,因为0.75*1000 < 1000, 也就是说为了让0.75 * size > 1000, 我们必须这样new HashMap(2048)才最合适,既考虑了&的问题,也避免了resize的问题。
4、key的hashcode与equals方法改写
在第一部分hashmap的数据结构中,annegu就写了get方法的过程:首先计算key的hashcode,找到数组中对应位置的某一元素,然后通过key的equals方法在对应位置的链表中找到需要的元素。所以,hashcode与equals方法对于找到对应元素是两个关键方法。
Hashmap的key可以是任何类型的对象,例如User这种对象,为了保证两个具有相同属性的user的hashcode相同,我们就需要改写hashcode方法,比方把hashcode值的计算与User对象的id关联起来,那么只要user对象拥有相同id,那么他们的hashcode也能保持一致了,这样就可以找到在hashmap数组中的位置了。如果这个位置上有多个元素,还需要用key的equals方法在对应位置的链表中找到需要的元素,所以只改写了hashcode方法是不够的,equals方法也是需要改写滴~当然啦,按正常思维逻辑,equals方法一般都会根据实际的业务内容来定义,例如根据user对象的id来判断两个user是否相等。
在改写equals方法的时候,需要满足以下三点:
(1) 自反性:就是说a.equals(a)必须为true。
(2) 对称性:就是说a.equals(b)=true的话,b.equals(a)也必须为true。
(3) 传递性:就是说a.equals(b)=true,并且b.equals(c)=true的话,a.equals(c)也必须为true。
通过改写key对象的equals和hashcode方法,我们可以将任意的业务对象作为map的key(前提是你确实有这样的需要)。
总结:
本文主要描述了HashMap的结构,和hashmap中hash函数的实现,以及该实现的特性,同时描述了hashmap中resize带来性能消耗的根本原因,以及将普通的域模型对象作为key的基本要求。尤其是hash函数的实现,可以说是整个HashMap的精髓所在,只有真正理解了这个hash函数,才可以说对HashMap有了一定的理解。
发表评论
-
StringBuStringffer 和 StringBuilder 区别
2010-02-05 17:37 1423String 字符串常量 StringBuffer 字符串变量 ... -
Java timezone
2010-01-13 20:57 1717转自:行者买刀 获得java时间时,突然获得的时间不是当前 ... -
Javap
2010-01-04 10:07 3767javap是jdk自带的一个工具,可以反编译,也可以查看jav ... -
JAVA 主动调用与被动调用
2009-12-31 13:54 2325public class Test { stati ... -
Java解析XML文件的方法比较
2009-12-24 17:14 13691)DOM(JAXP Crimson解 ... -
Java 遍历二叉树
2009-12-18 17:13 2013Binary.java import java.util.S ... -
Java protected
2009-12-16 15:17 4777(1)除了在(2)中表 ... -
ConcurrentHashMap
2009-12-15 11:40 1879关键字: concurrenthashmap ... -
SimpleDateFormat 参数 Z
2009-12-11 17:20 1716import java.text.SimpleDate ... -
JAVA自动装箱与拆箱
2009-11-24 17:15 1662自动装箱与拆箱的功能事实上是编译器来帮您的忙,编译器在编译时期 ... -
String java
2009-11-24 16:29 1468众所周知,String是由字符组成的串,在程序中使用频率很高。 ... -
Java 动态绑定
2009-09-08 11:45 1526代码: public class Par ... -
JAVA虚拟机命令行参数
2009-08-18 15:51 2013一、运行class文件执行带main方法的class文件,命令 ... -
Collections.sort
2009-07-03 16:59 1718第一种方法:容器内要排序的类必须时下Comparable接口: ... -
java 多态 例子
2009-06-30 21:58 2707其实也就是两种绑定状态:动态绑定(也称后期绑定),静态绑定(也 ... -
Java 字节 字符 字符串
2009-06-23 10:07 6036String的length()方法和数组的length属性 S ... -
Java 运算符 详解
2009-06-23 09:59 2171有些运算符在JAVA语言中存在着,但是在实际开发中我们或许很少 ... -
Java 面试题 经典
2009-06-22 16:42 1699请下载见附件~~~ -
Java 基本类型
2009-06-22 16:39 32385基本类型,或者叫做内置类型,是JAVA中不同于类的特殊类型。它 ... -
java 传值 传引用
2009-06-22 16:18 1799JAVA中的传递都是值传递吗?有没有引用传递呢? 在回答这两个 ...
相关推荐
这个文档“ HashMap详解(通俗易懂)”很好的阐述了hashmap的底层数据结构示意,希望对学习java的人有帮助
HashMap 详解 HashMap 是一种常用的数据结构,在 Java 中,它是一个数组和链表的结合体。下面我们将深入探讨 HashMap 的数据结构、 put 方法的实现细节和 Hash 码的计算过程。 HashMap 的数据结构 HashMap 的数据...
Java中的HashMap是一个非常重要的数据结构,它实现了Map接口,提供了键值对的高效存储和访问。HashMap基于哈希表(也称为散列表)原理工作,它允许用户通过键(Key)快速查找对应的值(Value)。在HashMap中,键和值...
Java中的HashMap是一种基于散列机制的Map接口的实现,它允许我们存储键值对。键是唯一的,而值可以重复。HashMap在处理数据时非常高效,因为其操作的时间复杂度接近于O(1)。这是通过使用散列函数将键映射到相应的...
HashMap是Java编程语言中一种非常重要的数据结构,它实现了Map接口,主要用于存储键值对。HashMap内部基于哈希表(Hash Table)实现,利用哈希函数将键映射到一个特定的位置,以便快速查找和插入元素。以下是HashMap...
HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。 HashMap 继承于AbstractMap,实现了Map、Cloneable、java.io.Serializable接口。 HashMap 的实现不是同步的,这意味着它不是线程安全的。它的key、...
HashMap是Java编程语言中常用的集合类之一,它提供了一种以键值对形式存储数据的方式。HashMap基于哈希表的数据结构实现,具有快速查找、插入和删除的特点。本文将详细介绍HashMap的基本概念、构造函数、数据结构...
Java数据结构-HashMap详解 Java数据结构中的HashMap是一种基于哈希表的数据结构,它提供了高效的存储和检索机制。HashMap的实现基于数组和链表(或红黑树)的结合,根据哈希冲突的长度不同,进行不同的存储和查找...
Java中的HashMap是基于哈希表实现的Map接口的一个实现,它允许将键映射到相应的值。HashMap在Java集合框架中扮演着重要角色,提供快速的查找、插入和删除操作,平均时间复杂度为O(1)。下面将详细介绍HashMap的特点、...
Java HashMap 类详解 本资源详细介绍了 Java 中的 HashMap 类,包括其实现机制、Hash 存储机制、集合存储机制等方面的知识点。 1. HashMap 和 HashSet 的关系 HashMap 和 HashSet 是 Java Collection Framework ...
Java实现简易HashMap详解 Java实现简易HashMap功能详解是指使用Java语言实现一个简易的HashMap功能,通过结合实例形式详细分析了Java实现HashMap功能相关原理、操作步骤与注意事项。 HashMap是Java中一种常用的...
哈希映射(HashMap)是Java编程语言中一个非常重要的数据结构,主要用于存储键值对。HashMap类位于java.util包中,它实现了Map接口,提供了快速的存取速度,平均时间复杂度为O(1)。这个数据结构的核心原理是哈希函数...
"基于Java中最常用的集合类框架之HashMap详解" Java中的HashMap是一种常用的集合类框架,它基于哈希表的Map接口实现,提供了所有可选的映射操作。HashMap存储的是对的映射,允许多个null值和一个null键,但它不保证...
### Rust 集合类型String, Vector, HashMap 使用详解 #### 一、String 类型详解 **String** 是 Rust 中非常重要的数据结构之一,用于表示可变长度的 UTF-8 编码的文本字符串。Rust 语言设计时充分考虑了 Unicode ...
**HashMap详解与手写实现** HashMap是Java编程中常用的数据结构之一,它是基于哈希表实现的,提供了高效的键值对存储和查找功能。在面试中,深入理解HashMap的内部工作原理是衡量开发者基础扎实与否的重要标准。...
《HashMap面试题详解》 HashMap作为Java集合框架中的重要成员,是面试中常见的知识点,尤其在数据结构与算法、并发编程以及JVM内存管理等领域,HashMap的深入理解至关重要。本篇将围绕HashMap的相关面试题,从基础...