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吗?" ...
随机推荐
- mysql 查询某一主键在那些表中中被设置为外键了
use information_schema; show tables; select * from KEY_COLUMN_USAGE where COLUMN_NAME='areaid';
- [C#]C#彩色扭曲验证码
该验证码生成类集合了网上大部分的验证码生成类的精华,博采众长并多次改进,现在已经形成了可在生产环节中使用的验证码. 该验证码加入了背景噪点,背景噪点曲线和直线,背景噪点文字以及扭曲,调暗,模糊等.完全 ...
- 跟我一起学习ASP.NET 4.5 MVC4.0(一)
跟我一起学习ASP.NET 4.5 MVC4.0(一) 由于上面一个项目使用的是ASP.NET4.0 MVC3.0,在招人的时候发现很多人有听说过MVC,但是却是没用过,对MVC也只是一知半解,最 ...
- sql 的理解
sql的作用有: 1.筛选数据,连接表 2.数据的补充,连接表 3.数据的加减乘除的运算,+ - * / 4.数据的逻辑运输,比如case..when...,decode,nvl,ifnull.... ...
- css 让div 置于最顶层而不被其他东西挡住
今天遇到自己写的div被其他东西给挡住了,需要设置一个属性就成功了 设置:z-index:值:比如 z-index:999. 若值设置为为-1,代表为最底层. div的图层由div的style中的z- ...
- struts2 实现rest
参考链接https://www.ibm.com/developerworks/cn/java/j-lo-struts2rest/
- java修饰符的作用范围
访问修饰符: private 缺省 protected public 作用范围: private 被private修饰的属性和方法,不能被其他类访问,子类不能继承也不能访问.只能在所在类内部访问.缺省 ...
- gitignore中常见需要被无视的文件
gitignore中常见的需要被忽略的文件:例如各个系统.一些软件会自动生成的文件,主要适用于web项目. 复制后,保存进.gitignore文件中即可. # Project node_modules ...
- centos6:一个网卡上显示多个ip地址的错误
网卡显示如下: 上面显示em1有两个ip地址,其中36是设置的固定IP,212这个ip不清楚怎么搞出来的,但是通过212地址也能够正常连接主机.重启网卡之后,还是没有变化. 网卡em1配置如下,/e ...
- Hibernate和Spring整合出现懒加载异常:org.hibernate.LazyInitializationException: could not initialize proxy - no Session
出现问题: SSH整合项目里,项目目录结构如下: 在EmployeeAction.java的list()方法里将employees的list放入到request的Map中. EmployeeActi ...