Java HashMap原理
HashMap存储结构
HashMap中数据的存储是由数组与链表一起实现的

数组寻址非常容易,其时间复杂度为O(1),但是当要插入或删除数据时,时间复杂度就会变为O(n)。链表插入和删除操作的内存复杂度为O(1),但是寻址操作的复杂度却是O(n)。HashMap结合两者的优点,即寻址,插入删除都快。
HashMap中定义了一个Entry类的数组table, Entry<K,V>[] table; 数组table中存储的是Entry对象(也是Entry链表的头结点)。
Entry对象中保存的是Key-Value对。
其中数组table被称作buckets,每个数组节点则是一个bucket(可以构成一个链表)
capacity指的是buckets的容量,也即数组的大小,默认capacity大小如下
/**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 16;
load factor是衡量buckets填满程度的比例,默认load factor大小如下
/**
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
当buckets中entry数量大于capacity*loadfactor时就要把capacity扩充为原来的两倍。
HashMap中方法
添加键值对put(K key, V value)方法
实现流程如下:
进行key是否为null的判断,如果key==null ,放置在数组table的0号位置,即table[0]。
若key不为null,由key的hashCode计算相应的hash值,进而得到该key对应的数组table的下标i。
判断table[i] 是否为null(不包含任何键值对),若是则创建该键值对的Entry对象,添加至table[i]。
若table[i]不为null,遍历table[i]链表中的每个Entry对象,若该链表中已包含key,则覆盖其value。若该链表中不包含key,则创建该键值对的Entry对象并添加至链表头部。
实现代码如下:
public V put(K key, V value) {
// HashMap允许存放null键和null值。
// 当key为null时,调用putForNullKey方法,将value放置在数组table第一个位置。
if (key == null)
return putForNullKey(value);
// 根据key的hashCode计算hash值。
int hash = hash(key.hashCode());
// 由hash值求得key对应table的下标。
int i = indexFor(hash, table.length);
// 如果 i 索引处的 Entry 不为 null,遍历table[i]链表的每一个Entry对象
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
// 如果链表中已有键值key,则新的value覆盖原来的,并返回原来的value
// 判断为同一个键值的方法是,hash值相同,且key相同
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
// 若table[i]为null,则创建key,value的Entry对象,并添加至table[i]处
// 若table[i]链表中没有键值key,则创建key,value的Entry对象,并添加至table[i]处
addEntry(hash, key, value, i);
return null;
}
addEntry()方法
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length); //扩容操作,将数据元素重新计算位置后放入newTable中,链表的顺序与之前的顺序相反
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
void createEntry(int hash, K key, V value, int bucketIndex) {
// 若table[i]不为null,则e指向table[i]链表的第一个元素
Entry<K,V> e = table[bucketIndex];
// 新的Entry对象添加至table[i]的表头
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
get()方法
计算hash值,然后调用indexFor()方法得到该key在table中的存储位置,得到该位置的单链表,遍历列表找到key和指定key内容相等的Entry,返回entry.value值
实现代码如下:
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
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的问题。
Java HashMap原理的更多相关文章
- Java:HashMap原理与设计缘由
前言 Java中使用最多的数据结构基本就是ArrayList和HashMap,HashMap的原理也常常出现在各种面试题中,本文就HashMap的设计与设计缘由作出一一讲解,并解答面试常见的一些问题. ...
- java - HashMap原理及实现 (转)
众所周知,HashMap是一个用于存储Key-Value键值对的集合,每一个键值对也叫做Entry.这些个键值对(Entry)分散存储在一个数组当中,这个数组就是HashMap的主干. HashMap ...
- HashMap的原理与实 无锁队列的实现Java HashMap的死循环 red black tree
http://www.cnblogs.com/fornever/archive/2011/12/02/2270692.html https://zh.wikipedia.org/wiki/%E7%BA ...
- Java HashMap工作原理及实现
Java HashMap工作原理及实现 2016/03/20 | 分类: 基础技术 | 0 条评论 | 标签: HASHMAP 分享到:3 原文出处: Yikun 1. 概述 从本文你可以学习到: 什 ...
- Java基础-hashMap原理剖析
Java基础-hashMap原理剖析 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.什么是哈希(Hash) 答:Hash就是散列,即把对象打散.举个例子,有100000条数 ...
- Hash算法及java HashMap底层实现原理理解(含jdk 1.7以及jdk 1.8)
现在很多公司面试都喜欢问java的HashMap原理,特在此整理相关原理及实现,主要还是因为很多开发集合框架都不甚理解,更不要说各种其他数据结构了,所以造成面子造飞机,进去拧螺丝. 1.哈希表结构的优 ...
- Java HashMap实现原理分析
参考链接:https://www.cnblogs.com/xiarongjin/p/8310011.html 1. HashMap的数据结构 数据结构中有数组和链表来实现对数据的存储,但这两者基本上是 ...
- java中HashMap原理?
参考:https://www.cnblogs.com/yuanblog/p/4441017.html(推荐) https://blog.csdn.net/a745233700/article/deta ...
- Java HashMap工作原理:不仅仅是HashMap
前言: 几乎所有java程序员都用过hashMap,但会用不一定会说. 近年来hashMap是非常常见的面试题,如何为自己的回答加分?需要从理解开始. "你用过hashMap吗?" ...
随机推荐
- 最小生成树 - 普里姆 - 边稠密 - O(N ^ 2)
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<string.h> #define N 1005 #def ...
- 067——VUE中vue-router之使用transition设置酷炫的路由组件过渡动画效果
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- linux physical and virtual addressing modes
example 1: 特理地址和虚拟地址一致 Physical addressing mode requires no page tables and the CPU does not attempt ...
- linux shard virtual memory
- Android开发——1轻松战胜开发环境
写在前头的话:鄙人乃2016年本科毕业的程序yuan一枚,大学阶段从未学过安卓,java也是一知半解,回想这一年半的开发生涯真的是相当悲壮.你要是问我喜欢开发吗,当然确定一定以及肯定地告诉你不喜欢啊! ...
- QRCodeHelper 二维码生成
QRCodeHelper 二维码生成 using System; using System.Drawing; using ThoughtWorks.QRCode.Codec; using System ...
- apscheduler -定时任务
https://apscheduler.readthedocs.io/en/latest/userguide.html 简单的使用方式为: from apscheduler.schedulers.bl ...
- http指定状态码
Http状态代码 1.指定状态码: setStatus HttpServletResponse的setStatus方法.如果响应的状态代码比较特殊,并且伴有相关的文档内容,那么一定要在用PrintWr ...
- Final阶段第1周/共1周 Scrum立会报告+燃尽图 03
作业要求[https://edu.cnblogs.com/campus/nenu/2018fall/homework/2482] 版本控制:https://git.coding.net/liuyy08 ...
- 解决在转发一条内容为满的彩信,删除主题FWD,发送的时候提示转化为短信。
问题描述: 1.长按一条输入内容为满的彩信,选择转发 2.输入联系人-删除主题FWD-发送 测试结果为:提示正转化为短信(见附件) 预期结果为:不应该有提示,应该还是彩信 测试结果图为: 根据提示的T ...