容器--HashMap
一、前言
看了下上一篇博客已经是半个月前,将近20天前了,很惭愧没有坚持下来,这期间主要是受奥运会和王宝强事件的影响,另外加上HashMap中关于rehash的实现比较不好理解,所以就一拖再拖。如果能坚持,也许容器这一部分已经写完了。
不管怎么样,奥运总算是结束了,到年底来说,也没有太多可以关注的内容了,坚持下来,做总比不做好。
回到正题,本文要分析的是Map中的经典实现,HashMap. 顾名思义是通过Hash来查找和存储元素的Map, 这种Map定位方便,支持自动扩容,是一种效率比较高的非线程安全的Map。下面做详细的介绍。
二、存储介绍
HashMap的存储相对于之前我们介绍的List要复杂一些,它使用了数组和链表相结合的方式,一个示例结构如下:

上图大致描述了HashMap的存储结构,即其内部首先是通过对key进行hash, 根据其hash值映射到内部的数组中的某一个位置,但考虑到hash冲突的可能性,所以对于相同hash值而key不同的元素,再通过一个链表进行存储。
也就是说,实际上这个数组中存储的是链表的头元素,每个节点都是一个Entry类型的对象,其包括了hash值,key, value及指向下一个节点的指针。
三、实现分析
根据上一节的介绍,对于map来说,需要实现一些常用而重要的功能,如put,get,delete,search等,本节就来分析一下对于这些功能, hashMap是怎么实现的。
3.1 添加节点, put.
根据map的特点,其key是惟一的,而且允许key为null, 所以,在向map中添加一个元素,需要考虑这样几个问题。
1)key==null的情况下,其hash值如何计算?
如果key==null,这种情况下,直接将key映射到下标为0的位置
2)key已经存在的情况下,如何处理?
将key的value设置为当前值,原来的值直接返回。
3)以什么样的机制扩容?
HashMap内部有一个叫threshold的值,这个值被称之为阈值,初始时,是容量*负载因子,如果当前的map中的元素个数达到这个阈值,且当前映射的位置已经有元素的话,则会对内部的table进行扩容,将其扩大一倍,并对原来的元素重新映射。
这样做是很有必要的,否则当map中的元素过多的话,就容易造成大量的key映射之后的值是同一个下标,这样就影响了hash的存取效率。
4)如何查找key是否存在
如果key为null的话,直接定位到数组中下标为0的位置,否则要先计算hash值,计算完了之后再根据hash值映射到对应的下标。
找到下标后,要看该下标中如果没有链表,则创建一个,如果有的话,则需要依次遍历链表,比较节点的key是否和当前的key相同,比较的逻辑如下:
int hash = hash(key);
int i = indexFor(hash, table.length);//table中的位置
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k; //entry相同的条件 , hash相同 , key的引用相同,或者equals()
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
可以看到,首先要保证hash值一样,再判断key是否相同或equals
如果没有找到匹配的确实需要添加的话,HashMap采用了个非常巧妙的方式,将元素添加到链表中的头节点,这样省去了遍历。
3.2 查找
查找的逻辑其实和添加时对key的查找是类似的,没有找到的话,直接返回null.
HashMap中对于containsValue的实现比较简单,直接遍历数组的每个下标,再遍历每个下标中的节点,这是一个双重循环,效率相对比较低下,所以这个方法一般情况下不要使用。
3.3 删除
删除的查找过程和前面类似,找到后的删除逻辑如下:
final Entry<K,V> removeEntryForKey(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : hash(key);
int i = indexFor(hash, table.length);
Entry<K,V> prev = table[i];
Entry<K,V> e = prev;
while (e != null) {
Entry<K,V> next = e.next;
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
modCount++;
size--;
if (prev == e)
table[i] = next;
else
prev.next = next;
e.recordRemoval(this);
return e;
}
prev = e;
e = next;
}
return e;
}
这里主要是对于链表的修改,如果是头节点则指向下一个,否则指向头节点。
3.4 遍历
常见的是获取keySet()以及entrySet(),当然也可以获取值集合values(), 这在内部是通过迭代器来实现的。
如果我们要遍历整个map, 最理想的方式是获取entrySet(),这样直接取其key和value即可,而不是获取keySet。
四、总结
理解了HashMap的存储结构之后,那么对于其各种操作的实现也就不难理解了,hashMap中,如何生成hash值,以及是否需要rehash,这一部分是不太好理解的,个人也没理解得太透彻,所以本文没做介绍,有时间的话再做研究。
容器--HashMap的更多相关文章
- java容器HashMap原理
1.为什么需要HashMap 前面我们说了ArrayList和LinkedList,它们对容器内的对象都能实现增.删.改.查.遍历等操作, 并且对应不同的情况,我们可以选择不同的List,用以提高效率 ...
- Java修炼——容器HashMap用法
直接上代码,容器集合之间的关系在后面我会继续详细分析,这次先看HashMap用法 HashMap的方法都在代码中有解释.有需要的可以仔细看看 package com.bjsxt.map; import ...
- Map容器——HashMap及常用API,及put,get方法解析,哈希码的产生和使用
Map接口 ① 映射(map)是一个存储键/值对的对象.给定一个键,可以查询到它的值,键和值都是对象; ② 键必须是唯一的,值可以重复; ③ 有些映射可以接收null键和null值,而有的 ...
- 容器HashMap原理(学习)
一.概述 基于哈希表的 Map 接口的非同步实现,允许使用 null 值和 null 键,不保证映射的顺序 二.数据结构 HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体:Has ...
- 深入理解Java容器——HashMap
目录 存储结构 初始化 put resize 树化 get 为什么equals和hashCode要同时重写? 为何HashMap的数组长度一定是2的次幂? 线程安全 参考 存储结构 JDK1.8前是数 ...
- JAVA编程思想(第四版)学习笔记----11.4 容器的打印
import static java.lang.System.out; import java.util.ArrayList; import java.util.Collection; import ...
- Java基础学习总结--对象容器
目录: ArrayList 顺序泛型容器 HashSet 集合容器 HashMap<Key,Value>容器 要用Java实现记事本的功能.首先列出记事本所需功能: 可以添加记录(字符串) ...
- HashMap:从源码分析到面试题
1 HashMap简介 HashMap是实现map接口的一个重要实现类,在我们无论是日常还是面试,以及工作中都是一个经常用到角色.它的结构如下: 它的底层是用我们的哈希表和红黑树组成的.所以我们在学习 ...
- 【读书笔记《Android游戏编程之从零开始》】6.Android 游戏开发常用的系统控件(TabHost、ListView)
3.9 TabSpec与TabHost TabHost类官方文档地址:http://developer.android.com/reference/android/widget/TabHost.htm ...
随机推荐
- Android笔记——Android五大布局
一.五大布局 Android的界面是有布局和组件协同完成的,布局好比是建筑里的框架,而组件则相当于建筑里的砖瓦.组件按照布局的要求依次排列,就组成了用户所看见的界面.Android的五大布局分别是Li ...
- Jenkins的系统设置
1.登录Jenkins进入以下界面: 2.点击 系统管理 3.点击 系统设置 (下图的系统设置是已经设置好的) 4.这里设置主要设置 Jenkins URL (这里我是测试的所以使用默认的)与 邮件的 ...
- Struts2第一个例子Hello World!
1.首先用eclipse新建一个动态web项目Struts2Demo1: (把Default output floder由build\classes改成WebContent\WEB-INF\class ...
- CSS弹性盒模型flex在布局中的应用
× 目录 [1]元素居中 [2]两端对齐 [3]底端对齐[4]输入框按钮[5]等分布局[6]自适应布局[7]悬挂布局[8]全屏布局 前面的话 前面已经详细介绍过flex弹性盒模型的基本语法和兼容写法, ...
- Android属性动画之第一重修炼总结
经过这两天对ObjectAnimator属性动画的学习,基本对Android提供的属性动画有了一定的认识,现在就为大家以一个类似扇形打开的效果做总结. 效果图: 下面就让我们用刚刚学到的属性动画效果, ...
- POJ 2312Battle City(BFS-priority_queue 或者是建图spfa)
/* bfs搜索!要注意的是点与点的权值是不一样的哦! 空地到空地的步数是1, 空地到墙的步数是2(轰一炮+移过去) 所以用到优先队列进行对当前节点步数的更新! */ #include<iost ...
- sqlserver -- 学习笔记(八)体验charindex、stuff 和 for xml path在实际问题中的应用及几个问题的探讨
写在前面 之前做了个微信端顾客扫码评价员工的功能,除了打分数,还可以打标签. 需要统计分数和统计各个员工每种标签被点击的次数. 后来加了个要求,需要查看客户对某个员工一次服务所打出的标签组合. 在不 ...
- Ext.grid.CheckboxSelectionModel状态设置
直接上代码: var model = grid.getSelectionModel(); model.selectAll();//选择所有行 model.selectFirstRow();//选择第一 ...
- 如何调试PHP程序
一.PHP自带的调试功能 1.修改php.ini )开发环境 需要打开报错输出显示,方便开发者调试. display_errors = On )生产环境 不能直接将错误输出,而是记入日志,以免透露路径 ...
- SQLServer学习笔记系列7
一.写在前面的话 转眼又是周一,回想双休的日子,短暂而幸福,在阳光明媚的下午,可以自己做自己想做的任何事,惬意舒适,或读书,或运动,或音乐,当我们静下心来慢慢感受这些的时候,会突然发觉,原来生活是这么 ...