谈一谈HashMap类
一、Java中的hashCode()和equals()
1、 hashCode()的存在主要是用于查找的快捷性,如Hashtable,HashMap等,hashCode()是用来在散列存储结构中确定对象的存储地址的;
2、如果两个对象相同,就是指对象调用equals()方法返回true,那么这两个对象的hashCode()方法的返回值一定要相同;
3、如果对象的equals()方法被重写,那么对象的hashCode()方法也尽量重写,并且hashCode()方法使用的对象的变量信息,一定要和equals方法中使用的一致,否则就会违反上面提到的第2点;
4、 两个对象的hashCode()相同,并不一定表示两个对象就相同,也就是不一定适用于equals(java.lang.Object) 方法,只能够说明这两个对象在散列存储结构中,如Hashtable,他们“存放在同一个篮子里“。
再归纳一下就是hashCode()是用于查找使用的,而equals()是用于比较两个对象是否相等的。
二、HashMap类的实现原理
1、HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
2、在java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。
3、HashMap的存取:
1)利用key的hashCode重新hash计算出当前对象的元素在数组中的下标(索引计算公式:(length-1)& hash);
2)存储时,如果出现hash值相同的key,此时有两种情况。(1)如果key相同,则覆盖原始值;(2)如果key不同(出现冲突),则将当前的key-value放入链表中
3)获取时,直接找到hash值对应的下标,在进一步判断key是否相同,从而找到对应值。
4)理解了以上过程就不难明白HashMap是如何解决hash冲突的问题,核心就是使用了数组的存储方式,然后将冲突的key的对象放入链表中,一旦发现冲突就在链表中做进一步的对比。
三、HashMap的扩容机制
当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)更合适,不过即使是1000,hashmap也自动会将其设置为1024。 但是new HashMap(1024)还不是更合适的,因为0.75*1000 < 1000, 也就是说为了让0.75 * size > 1000, 我们必须这样new HashMap(2048)才最合适,既考虑了&的问题,也避免了resize的问题。
/**
* jdk版本:1.8.0_111。
* 使用HashMap的空参构造创建一个HashMap集合,初始容量为16;负载因子loadFactor为0.75;
* 当集合的元素个数超过16*0.75=12时集合容量扩大一倍。*/
public class Demo01 {
public static void main(String[] args) throws Exception {
HashMap<String, Object> map = new HashMap<String, Object>();
System.out.println(map.size()); //
System.out.println(getCapacity(map)); // map.put("abc0", 1);
map.put("abc16", 1);
map.put("abc27", 1);
map.put("abc49", 1);
System.out.println(map.size()); //
System.out.println(getCapacity(map)); // String key = null;
for (int i = 0; i < 8; i++) {
key = "a" + i;
map.put(key, key);
}
System.out.println(map.size()); //
System.out.println(getCapacity(map)); //
Object obj = map; map.put("a8", "a8");
System.out.println(map.size()); //
System.out.println(getCapacity(map)); //
System.out.println(obj == map); // true,扩容后引用地址没变
} public static Integer getCapacity(Map<?, ?> map) throws Exception {
Method method;
method = map.getClass().getDeclaredMethod("capacity");
method.setAccessible(true);
return (int) method.invoke(map);
}
}
再看下面的一个例子:
/**
* jdk版本:jdk1.7.0_60。
* 创建一个HashMap集合,初始容量为4,加载因子loadFactor为0.75;
* 我们理应认为:当集合的元素个数超过4*0.75=3时集合容量扩大一倍。
* 但是当使用jdk1.7.0_60时,结果并非如此。
*/
public class Demo2 {
public static void main(String[] args) throws Exception {
HashMap<Integer, Object> map = new HashMap<>(4, 0.75f);
System.out.println(map.size()); //
System.out.println(getCapacity(map)); // map.put(0, 1);
map.put(4, 1);
map.put(8, 1);
System.out.println(map.size()); //
System.out.println(getCapacity(map)); //
map.put(1, 1);
System.out.println(map.size()); //
System.out.println(getCapacity(map)); //
map.put(2, 1);
System.out.println(map.size()); //
System.out.println(getCapacity(map)); //
map.put(3, 1);
System.out.println(map.size()); //
System.out.println(getCapacity(map)); //
map.put(5, 1);
System.out.println(map.size()); //
System.out.println(getCapacity(map)); // } public static Integer getCapacity(Map<?, ?> map) throws Exception {
Method method;
method = map.getClass().getDeclaredMethod("capacity");
method.setAccessible(true);
return (int) method.invoke(map);
}
}
查看jdk1.7.0_60HashMap源码:问题的关键在于标红的代码。
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
} createEntry(hash, key, value, bucketIndex);
}
四、为什么HashMap容量为2的次幂?
看了一些资料,以下是自己的理解:
HashMap底层实现为数组+单链表,元素的存取是根据数组索引来操作的。那么HashMap是如何计算元素在数组中的索引呢?
首先,HashMap的元素是包含key、value的键值对,HashMap是根据key值,加上一些算法计算得到该元素在数组中的索引。具体算法如下:
// 传入key,得到对应的hash值
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
} /**
* @param key 键值对的key
* @param capicity HashMap集合的容量
* @return
*/
static int getIndex(Object key, int capicity) {
int hash = hash(key); // 传入key,得到对应的hash值
int index = (capicity - 1) & hash; // 得到元素在数组中的索引
return index;
}
综上可知,HashMap集合是根据 (capicity - 1) & key的hash值 来计算元素在数组中的索引。
假设HashMap集合的容量capacity=15,则元素在数组中的索引 index = (capacity-1) & hash = 14 & hash = 0b1110 & hash,则index只有8种结果:0、2、4、6、8、10、12、14,意思就是数组的某些位置不能存值,浪费空间,也加大了hash冲突的可能。
如果HashMap集合的容量capacity=2的n次方,则元素在数组中的索引 index = (2n-1) & hash = 0b11...11(n个1) & hash,则index有2n种结果,即是0、1、2 ... 2n-1,所以数组的每个位置都会存值,基本上均匀地分布,有效利用空间,也减少了hash冲突,提高HashMap集合的性能。
事实上,只有当HashMap集合的容量capacity=2n时,(capacity-1) & hash 与 hash % capacity 的结果是等效的。
写个小案例体会一下:
public class Demo1 {
public static void main(String[] args) {
String key = null; int capicity = 15; // 容量
int[] array = new int[capicity]; for (int i = 0; i < 10000; i++) {
key = "abc1" + i;
int index = getIndex(key, capicity);
if (index < capicity) {
array[index] += 1;
}
}
//capicity=16: [642, 643, 600, 590, 556, 538, 572, 554, 609, 607, 655, 665, 692, 711, 674, 692]
//capicity=8: [1251, 1250, 1255, 1255, 1248, 1249, 1246, 1246]
//capicity=4: [2499, 2499, 2501, 2501] //capicity=15: [1285, 0, 1190, 0, 1094, 0, 1126, 0, 1216, 0, 1320, 0, 1403, 0, 1366]
//capicity=14: [1242, 1233, 0, 0, 1128, 1092, 0, 0, 1264, 1272, 0, 0, 1366, 1403]
System.out.println(Arrays.toString(array));
} // 传入key,得到对应的hash值
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
} /**
* 得到元素在数组中的索引
* @param key 键值对的key
* @param capicity HashMap集合的容量
* @return
*/
static int getIndex(Object key, int capicity) {
int hash = hash(key); // 传入key,得到对应的hash值
int index = (capicity - 1) & hash; // 得到元素在数组中的索引
return index;
}
}
参考资料:
2、HashMap中的为什么hash的长度为2的幂而&位必须为奇数
谈一谈HashMap类的更多相关文章
- 谈一谈HashMap类2
1.由一个小案例引出本博文的讨论 public class Demo1 { public static void main(String[] args) throws Exception { Stud ...
- 谈一谈Java8的函数式编程(二) --Java8中的流
流与集合 众所周知,日常开发与操作中涉及到集合的操作相当频繁,而java中对于集合的操作又是相当麻烦.这里你可能就有疑问了,我感觉平常开发的时候操作集合时不麻烦呀?那下面我们从一个例子说起. 计 ...
- 谈一谈泛型(Generic)
谈一谈泛型 首先,泛型是C#2出现的.这也是C#2一个重要的新特性.泛型的好处之一就是在编译时执行更多的检查. 泛型类型和类型参数 泛型的两种形式:泛型类型( 包括类.接口.委托和结构 没有泛型枚 ...
- 谈一谈深度学习之semantic Segmentation
上一次发博客已经是9月份的事了....这段时间公司的事实在是多,有写博客的时间都拿去看paper了..正好春节回来写点东西,也正好对这段时间做一个总结. 首先当然还是好好说点这段时间的主要工作:语义分 ...
- 谈一谈并查集QAQ(上)
最近几日理了理学过的很多oi知识...发现不知不觉就有很多的知识忘记了... 在聊聊并查集的时候顺便当作巩固吧.... 什么是并查集呢? ( Union Find Set ) 是一种用于处理分离集合的 ...
- 谈一谈C#的事件
谈一谈C#的事件 C#中事件基于委托,要理解事件要先理解委托,如果觉得自己关于委托不是很了解可以看看我前面写委托的文章 事件基于委托,是一种功能受限的委托,为委托提供了一种发布/订阅机制 使用委托时, ...
- Hashtable和HashMap类的区别
Hashtable和HashMap类有三个重要的不同之处.第一个不同主要是历史原因.Hashtable是基于陈旧的Dictionary类的,HashMap是Java 1.2引进的Map接口的一个实现. ...
- Java API —— HashMap类 & LinkedHashMap类
1.HashMap类 1)HashMap类概述 键是哈希表结构,可以保证键的唯一性 2)HashMap案例 HashMap<String,String> ...
- JAVA中的数据结构——集合类(线性表:Vector、Stack、LinkedList、set接口;键值对:Hashtable、Map接口<HashMap类、TreeMap类>)
Java的集合可以分为两种,第一种是以数组为代表的线性表,基类是Collection:第二种是以Hashtable为代表的键值对. ... 线性表,基类是Collection: 数组类: person ...
随机推荐
- win32 汇编学习(2):消息框
这一次,我们将用汇编语言写一个 Windows 程序,程序运行时将弹出一个消息框并显示"你好,我的第一个Win32汇编程序". 理论知识 Windows 为编写应用程序提供了大量的 ...
- sql -- 移除数据中的换行符和回车符
https://blog.csdn.net/jcx5083761/article/details/40185795 --移除回车符 update master_location SET street_ ...
- P2153 [SDOI2009]晨跑
思路 典型的最小费用最大流问题,拆点,每个点对应的入点和出点之间连一条cap=1的边表示只能经过一次的限制条件 然后其他边从u的出点连向v的入点即可 代码 #include <cstdio> ...
- cmd设置环境变量
方法,仅本次生效 set path=%path%;[新路径]方法,永久生效 setx path "%path%;[新路径]"方法,永久生效 wmic ENVIRONMENT cre ...
- 8、nginx和tengine简介
练习: 使用nginx反向代理(rr调度)用户请求至两个以上的后端LAMP(按标准路径部署的有pma,wd),不管用户请求是什么内容都反向代理至后端服务器去,但是如果用户请求的是图片或者是html,就 ...
- 消息队列之ActiveMQ简单环境搭建
准备: 环境:win7,Eclipse,jdk1.8 ActiveMQ版本:ActiveMQ 5.9.0 Release下载地址:http://activemq.apache.org/download ...
- 搭建springboot环境
1.前戏准备: SpringBoot核心jar包:这里直接从Spring官网下载了1.5.9版本. jdk:jdk1.8.0_45. maven项目管理工具:3.5版本. tomcat:8.5版本. ...
- 【译】第9节---EF Code First中数据注解
原文:http://www.entityframeworktutorial.net/code-first/dataannotation-in-code-first.aspx EF Code-First ...
- HDU 4825 Xor Sum(01字典树入门题)
http://acm.hdu.edu.cn/showproblem.php?pid=4825 题意: 给出一些数,然后给出多个询问,每个询问要从之前给出的数中选择异或起来后值最大的数. 思路:将给出的 ...
- Java问题解决:Java compiler level does not match the version of the installed Java project facet.
问题原因:Java编译器级别与Facted Project 中的Java 版本设定不匹配. 解决办法:将两者设置一致 1.查看Java compiler level : 选中项目右键propertie ...