谈一谈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 ...
随机推荐
- centos远程访问
centos远程访问即windows下的mysql和linux下的mysql能连接,即windows下的navicat能连接到(访问)centos下的mysql中的库表 (centos是linux的一 ...
- cumtoj 一起来选课
一起来选课 题目地址:http://192.168.173.163/JudgeOnline/problem.php?cid=1019&pid=7 题目 明泽私立大学有n门课程提供给大一的同学来 ...
- Trim Galore用法及参数考量
Trim Galore是一个非常流行的用于「去接头序列」的软件,用于处理高通量测序得到的原始数据.通常我们从测序公司拿到数据后,第一步就是评估数据的质量以及对raw data去接头处理.公司拿来的数据 ...
- (转) Learning Deep Learning with Keras
Learning Deep Learning with Keras Piotr Migdał - blog Projects Articles Publications Resume About Ph ...
- C#Winform工具箱简介
BindingSource:指定支持事务处理初始化Button:[按钮]用户单击它时引发事件 CheckBox:[复选框]允许用户选择或清除关联选项 CheckedListBox:[复选列表框]显示一 ...
- AtomicReference实现单例模式
CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试. 乐观锁的一种实 ...
- 说明Heap与stack的差别。
Heap 是堆,Stack 是栈. 栈与堆都是Java用来在Ram中存放数据的地方,与C++不同,Java会自动管理栈与堆,程序员不能直接设置栈与堆. Java的堆是一个运行时的数据区,类的对象从中分 ...
- _spellmod_leech_aura
comment 备注 aura 光环ID,玩家有这个光环时候造成的伤害会转化成吸血效果 chance 每次伤害转化成吸血效果的几率 type 吸血的类型,数据库枚举类型,可以直接选取 base ...
- CentOS6.5下搭建SVN服务器
1.检查是否已安装 rpm -qa | grep subversion 如果要卸载旧版本: yum remove subversion 2.安装 yum install subversion PS:y ...
- 学Hadoop还是Spark好?
JS 相信看这篇文章的你们,都和我一样对Hadoop和Apache Spark的选择有一定的疑惑,今天查了不少资料,我们就来谈谈这两种 平台的比较与选择吧,看看对于工作和发展,到底哪个更好. 一.Ha ...