HashMap源码分析
最近一直特别忙,好不容易闲下来了。准备把HashMap的知识总结一下,很久以前看过HashMap源码。一直想把集合类的知识都总结一下,加深自己的基础。我觉的java的集合类特别重要,能够深刻理解和应用这些集合类能够让自己写的程序上一步台阶。
本文主要根据自己学习与使用HashMap来解析HashMap的源码,深入到HashMap的内部结构和实现,增强自己的基础知识。同时会借鉴网上的相关资料,深入理解HashMap.
HashMap的内部存储结构
一提到HashMap我们就知道键值对,即一个键对应一个值。可能我们经常会使用HashMap,但是并不关注里面的内部实现。今天我们就来学习一下HashMap的内部实现。
HashMap是一种以键值对存储数据的容器,每个对象在java中都会有一个hashCode,HashMap正是借每个对象的HashCode来组织键值对的存储,因为HashCode的特性,使得HashMap以非常快速和高效地地根据键值key进行数据的存取。键值对的具体实现在HashMap内部会封装成一个Entry[] table,Entry[] table是键值对的表现形式。下图为HashMap的存储结构。HashMap中Value可以相同,但是键不可以相同.
上图可以看出来HashMap是数组+链表的存储结构,数组的每一个元素是一个链表的表头。这样的结构能够综合2个经常使用的数据结构的特点,数组查找、遍历快,链表增加、删除快。
HashMap的属性
static final int DEFAULT_INITIAL_CAPACITY = 16;//默认初始化加载容量,即table数组的长度。
static final int MAXIMUM_CAPACITY = 1 << 30;//最大的容量。1左移30位,2的30次方:1073741824
static final float DEFAULT_LOAD_FACTOR = 0.75f;//默认加载因子为0.75
transient Entry[] table;//table数组,上图的黄色部分。Entry为蓝色部分
transient int size;//HashMap存储元素的数量。
int threshold;//阀值 table的长度*加载因子(默认wei0.75)
final float loadFactor;//加载因子
transient volatile int modCount;//修改次数
注:如果用transient声明一个实例变量,当对象存储时,它的值不需要维持。换句话来说就是,用transient关键字标记的成员变量不参与序列化过程。
volatile为同步变量,保证每次都读取的值是最新的。
HashMap的构造方法
public HashMap() {//最常用的改造方法
this.loadFactor = DEFAULT_LOAD_FACTOR;//默认加载因子,0.75
threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);//阀值 默认的容量*加载英子。12
table = new Entry[DEFAULT_INITIAL_CAPACITY];//table数组的默认为16,容量为16,切记容量不等于HashMap存储的元素数量,容易混淆。
init();//初始化方法,里面是个空
}
默认的构造方法开辟16个大小的空间。还有另外一个构造方法我们使用的比较少,当你觉的默认的HashMap的存储空间浪费时(容量达到3/4时HashMap就会调用resize扩大为原来的2倍),可以使用下面一个。
public HashMap(int initialCapacity, float loadFactor) {//初始化容量,加载因子
if (initialCapacity < 0)//容量不能小于0
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)//大于允许最大的容量时,设置未最大值
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))//加载因子小于大于0或者不是数字的时候,报非法加载因子异常
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
// Find a power of 2 >= initialCapacity
int capacity = 1;
while (capacity < initialCapacity)//实际的开辟的空间要大于传入的第一个参数的值。
capacity <<= 1;//这是一个重点,capacity才是容量。
this.loadFactor = loadFactor;//加载因子设置为传入的值
threshold = (int)(capacity * loadFactor);//阈值
table = new Entry[capacity];//数组
init();
}
此外还有2个构造方法基本上很少用到,感兴趣的可以自己看看。HashMap最重要的方法是put和get方法,下面我们重点看看这2个方法。
HashMap的put方法分析
public V put(K key, V value) {//很熟悉的方法
if (key == null)//如果key==null,直接设置空的
return putForNullKey(value);
int hash = hash(key.hashCode());//获得hash值
int i = indexFor(hash, table.length);//根据hash值找到此键值对应该放在数组的第几个位置。
for (Entry<K,V> e = table[i]; e != null; e = e.next) {//拿到放置该键值对的链表,
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {//如果原来的存在,直接替换值
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);//不存在就新增
return null;
}
put方法会先判断键是不是空,如果为空就调putForNullKey方法。方法如下:
private V putForNullKey(V value) {
for (Entry<K,V> e = table[0]; e != null; e = e.next) {//获得第一个链表,遍历查找是否原来就存储为null的键值对
if (e.key == null) {//如果存在
V oldValue = e.value;//记录下老的值
e.value = value;//把新值设置进去
e.recordAccess(this);
return oldValue;//返回老值
}
}
modCount++;
addEntry(0, null, value, 0);// Key 为null,则将Entry<null,Value>放置到第一桶table[0]中
return null;
}
计算Hash值的方法如下:
static int hash(int h) {//根据特定的hashcode 重新计算hash值
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {//匹配到具体的桶当中,
return h & (length-1);//相当于int i = hash % Entry[].length;得到i后,就是在Entry数组中的位置
}
整个put方法的流程如下:
- 首先判断键是否为空,如果为空在判断是否已经存在键为空的键值对,存在就更新值,不存在就新增;
- 如果键不为空,则获取这个Key的hashcode值,根据此值确定键值对的存储位置;
- 遍历所在桶中的Entry<Key,Value>链表,查找其中是否已经有了以Key值为Key存储的Entry<Key,Value>对象,若已存在,定位到对应的Entry<Key,Value>,其中的Value值更新为新的Value值;返回旧值;若不存在,则根据键值对<Key,Value> 创建一个新的Entry<Key,Value>对象,然后添加到这个桶的Entry<Key,Value>链表的头部。
- 当前的HashMap的大小(即Entry<key,Value>节点的数目)是否超过了阀值,若超过了阀值(threshold),则增大
HashMap的容量(即Entry[] table 的大小),并且重新组织内部各个Entry<Key,Value>排列。
HashMap的get方法分析
public V get(Object key) {
if (key == null)//如果键等于null,调用getForNullKey
return getForNullKey();
int hash = hash(key.hashCode());//获得hash值,
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {//indexfor计算存储位置,根据位置遍历链表
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;//拿到对应的值
}
return null;
}
如果键等于null,调用getForNullKey
private V getForNullKey() {
for (Entry<K,V> e = table[0]; e != null; e = e.next) {//键为null的键值对存储在数组的第一个元素,
if (e.key == null)//如果存在则返回值
return e.value;
}
return null;//不存在返回null
}
get方法比较简单,比较容易理解。主要流程如下:
- 首先判断键是否为空,如果为空在table[0]中取键为空的键值对,如果不存在为空的则返回null。
- 如果键不为空,则获取这个Key的hashcode值,根据此值确定该键值对的存储位置;
- 遍历所在桶中的Entry<Key,Value>链表,查找其中是否已经有了以Key值为Key存储的Entry<Key,Value>对象,若已存在,定位到对应的Entry<Key,Value>,获得Value值;返回旧值;若不存在,则返回空。
HashMap的总结
本文主要讲了HashMap的存储结构以及基本属性、构造方法,同时分析了比较常用的put和get方法。其他方法请读者自行查看。在看HashMap的源码时,我认为以下几个方面比较重要,能够理解到以下的几点,搞定HashMap基本不成问题。
- HashMap的存储结构,以及为什么要这样进行存储。
- HashMap的各个属性的含义,以及其扩容的策略(扩容算法)。
- Hash值的计算.确定桶的位置。
- HashMap中的value可以相同,键不可以相同。
- HashMap是非线程安全的。
HashMap源码分析的更多相关文章
- 【JAVA集合】HashMap源码分析(转载)
原文出处:http://www.cnblogs.com/chenpi/p/5280304.html 以下内容基于jdk1.7.0_79源码: 什么是HashMap 基于哈希表的一个Map接口实现,存储 ...
- Java中HashMap源码分析
一.HashMap概述 HashMap基于哈希表的Map接口的实现.此实现提供所有可选的映射操作,并允许使用null值和null键.(除了不同步和允许使用null之外,HashMap类与Hashtab ...
- JDK1.8 HashMap源码分析
一.HashMap概述 在JDK1.8之前,HashMap采用数组+链表实现,即使用链表处理冲突,同一hash值的节点都存储在一个链表里.但是当位于一个桶中的元素较多,即hash值相等的元素较多时 ...
- HashMap源码分析和应用实例的介绍
1.HashMap介绍 HashMap 是一个散列表,它存储的内容是键值对(key-value)映射.HashMap 继承于AbstractMap,实现了Map.Cloneable.java.io.S ...
- 【Java】HashMap源码分析——常用方法详解
上一篇介绍了HashMap的基本概念,这一篇着重介绍HasHMap中的一些常用方法:put()get()**resize()** 首先介绍resize()这个方法,在我看来这是HashMap中一个非常 ...
- 【Java】HashMap源码分析——基本概念
在JDK1.8后,对HashMap源码进行了更改,引入了红黑树.在这之前,HashMap实际上就是就是数组+链表的结构,由于HashMap是一张哈希表,其会产生哈希冲突,为了解决哈希冲突,HashMa ...
- Java BAT大型公司面试必考技能视频-1.HashMap源码分析与实现
视频通过以下四个方面介绍了HASHMAP的内容 一. 什么是HashMap Hash散列将一个任意的长度通过某种算法(Hash函数算法)转换成一个固定的值. MAP:地图 x,y 存储 总结:通过HA ...
- Java源码解析——集合框架(五)——HashMap源码分析
HashMap源码分析 HashMap的底层实现是面试中问到最多的,其原理也更加复杂,涉及的知识也越多,在项目中的使用也最多.因此清晰分析出其底层源码对于深刻理解其实现有重要的意义,jdk1.8之后其 ...
- HashMap源码分析(史上最详细的源码分析)
HashMap简介 HashMap是开发中使用频率最高的用于映射(键值对 key value)处理的数据结构,我们经常把hashMap数据结构叫做散列链表: ObjectI entry<Key, ...
- HashMap 源码分析 基于jdk1.8分析
HashMap 源码分析 基于jdk1.8分析 1:数据结构: transient Node<K,V>[] table; //这里维护了一个 Node的数组结构: 下面看看Node的数 ...
随机推荐
- 【Win 10 应用开发】Web授权示例:获取新浪微博的授权码
在使用类似微博的开放API的时候,会涉及到授权的问题,就拿微博来说,当用户在你的应用中需要调用微博API来处理一些事情时,你首先要让用户登录微博,得到用户授权后,才能调用微博API. 授权通常通过一个 ...
- MySql 管理操作常用命令
登陆mysql mysql -u username -p 创建用户名配置权限,这里为该用户配置tablename表的全部权限,也可以指定 GRANT ALL PRIVILEGES ON databas ...
- 前端学PHP之PHP操作memcache
× 目录 [1]安装 [2]连接 [3]增删改查[4]分布式[5]状态[6]安全[7]应用 前面的话 和访问mysql服务器类似,PHP也是作为客户端API访问memcached服务器的,所以同样需要 ...
- 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(13)-系统日志和异常的处理③
系列目录 上一节我们讲了如何捕获异常和记录日志,这一节我们讲,没有捕获的或者忘记捕获的异常包括404错误等,我们统一处理这个异常. 这一讲是利用 Application_Error 捕获所有异常,全局 ...
- c 进程间的通信
在上篇讲解了如何创建和调用进程 c 进程和系统调用 这篇文章就专门讲讲进程通信的问题 先来看一段下边的代码,这段代码的作用是根据关键字调用一个Python程序来检索RSS源,然后打开那个URL #in ...
- 2.C#WinForm基础Email分析器
功能:输入Email地址,输出用户名和域名 string[] String.split(params char[] separator)(+5重载)) 返回的字符串数组包含此实例的字符串(由指定Uni ...
- 如何在虚拟机安装桌面Ubuntu
本篇仅为作业... 实验课程:Linux 实验机器:联想y410p 指导老师:刘臣奇 实验时间:2016年10月12日 学生学号:140815 姓名:杨文乾 一.新建一个虚拟机,按照之前建立虚拟机的步 ...
- hdu-2444-二分图判定+最大分配
The Accomodation of Students Time Limit: 5000/1000 MS (Java/Others) Memory Limit: 32768/32768 K ( ...
- Jsp语法简介
1.JSP指令 jsp指令用来设置整个JSP网页想关闭的属性,如网页的编码和脚本语言等.常用的3种指令为page,include和taglib. 2.JSP声明 jsp声明用于声明JSP代表的Serv ...
- Android 手机卫士3--设置中心
1.要点击九宫格中的条目,需要注册点击事件 // 注册九宫格单个条目的点击事件 gv_home.setOnItemClickListener(new OnItemClickListener() { / ...