HashMap源码分析与实现
面试的时候经常会遇见诸如:“java中的HashMap是怎么工作的”,“HashMap的get和put内部的工作原理”这样的问题。本文将用一个简单的例子来解释下HashMap内部的工作原理。每当hashmap扩容的时候需要重新去add Entry对象,需要重新hash,然后放入我们新的entry table数组里面。如果在工作中,已经知道hashmap需要存多少值,几千或者几万的时候,最好新指定题目的扩容大小,防止在put的时候进行再次扩容很多次。
一、源码分析
在hashmap中我们必须要知道的参数有:初始化容量大小,默认是1 << 4,也就是16,
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
加载因子0.75f,
static final float DEFAULT_LOAD_FACTOR = 0.75f;
hash在什么时候扩容?
public V put(K key, V value) {if (table == EMPTY_TABLE) {inflateTable(threshold);}if (key == null)return putForNullKey(value);int hash = hash(key);int i = indexFor(hash, table.length);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;}
对key做null检查。如果key是null,会被存储到table[0],因为null的hash值总是0。
key的hashcode()方法会被调用,然后计算hash值。hash值用来找到存储Entry对象的数组的索引。有时候hash函数可能写的很不好,所以JDK的设计者添加了另一个叫做hash()的方法,它接收刚才计算的hash值作为参数。如果你想了解更多关于hash()函数的东西,可以参考:hashmap中的hash和indexFor方法
indexFor(hash,table.length)用来计算在table数组中存储Entry对象的精确的索引。
如果两个key有相同的hash值(也叫冲突),他们会以链表的形式来存储。所以,这里我们就迭代链表。
如果在刚才计算出来的索引位置没有元素,直接把Entry对象放在那个索引上。
如果索引上有元素,然后会进行迭代,一直到Entry->next是null。当前的Entry对象变成链表的下一个节点。
如果我们再次放入同样的key会怎样呢?逻辑上,它应该替换老的value。事实上,它确实是这么做的。在迭代的过程中,会调用equals()方法来检查key的相等性(key.equals(k)),如果这个方法返回true,它就会用当前Entry的value来替换之前的value。
对key进行hash处理的时候其实也是进行的位移处理,我们来看到hash的源码。
final int hash(Object k) {int h = hashSeed;if (0 != h && k instanceof String) {return sun.misc.Hashing.stringHash32((String) k);}h ^= k.hashCode();// This function ensures that hashCodes that differ only by// constant multiples at each bit position have a bounded// number of collisions (approximately 8 at default load factor).h ^= (h >>> 20) ^ (h >>> 12);return h ^ (h >>> 7) ^ (h >>> 4);}
当你传递一个key从hashmap总获取value的时候:对key进行null检查。如果key是null,table[0]这个位置的元素将被返回。
key的hashcode()方法被调用,然后计算hash值。
indexFor(hash,table.length)用来计算要获取的Entry对象在table数组中的精确的位置,使用刚才计算的hash值。
在获取了table数组的索引之后,会迭代链表,调用equals()方法检查key的相等性,如果equals()方法返回true,get方法返回Entry对象的value,否则,返回null。
public V get(Object key) {if (key == null)return getForNullKey();Entry<K,V> entry = getEntry(key);return null == entry ? null : entry.getValue();}
final Entry<K,V> getEntry(Object key) {if (size == 0) {return null;}int hash = (key == null) ? 0 : hash(key);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 != null && key.equals(k))))return e;}return null;}
4、Entry
hashmap table :数组+链接 的数据结构
void resize(int newCapacity) {Entry[] oldTable = table;int oldCapacity = oldTable.length;if (oldCapacity == MAXIMUM_CAPACITY) {threshold = Integer.MAX_VALUE;return;}
//创建Entry对象的数组Entry[] newTable = new Entry[newCapacity];
//赋值transfer(newTable, initHashSeedAsNeeded(newCapacity));table = newTable;threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);}
二、手写hashmap
通过前面的内容,我们已经知道了hashmap的原理了,我们现在来自己写一个hashmap。
1、map接口
public interface Map<K,V> {public V put(K k,V v);public V get(K k);public int size();public interface Entry<K,V>{public K getKey();public V getValue();}}
2、实现类
public class HashMap<K,V> implements Map<K, V> {//初始容量private static int defaultLength=16; //加载因子private static double defaultLoader=0.75;private Entry[] table=null;private int size=0;public HashMap() {this(defaultLength, defaultLoader);}public HashMap(int length,double loader){defaultLength=length;defaultLoader=loader;table=new Entry[defaultLength];}public HashMap(Entry[] table, int size) {super();this.table = table;this.size = size;}public V put(K k, V v) {size++;int index=hash(k);Entry<K, V> entry=table[index];if(entry==null){table[index]=newEntry(k,v,null);}else{table[index]=newEntry(k,v,entry);}return (V)table[index].getValue();}private Entry<K,V> newEntry(K k, V v, Entry<K, V> next) {return new Entry(k, v, next);}public int hash(K k){int m=defaultLength;int i=k.hashCode()%m;return i>=0?i:-i;}public V get(K k) {int index=hash(k);if(table[index]==null){return null;}//在数组中查找return find(k,table[index]) ;}public V find(K k, Entry entry) {if(k==entry.getKey() || k.equals(entry.getKey())){if(entry.next!=null){//System.out.println("1oldValue:"+entry.next.getValue()); }return (V) entry.getValue();}else{if(entry.next!=null){//System.out.println("2oldValue:"+entry.next.getValue());return find(k, entry.next);}}return null;}public int size() {return size;}class Entry<K,V> implements Map.Entry<K, V>{K k;V v;Entry<K,V> next;public Entry(K k, V v, Entry<K, V> next) {super();this.k = k;this.v = v;this.next = next;}public K getKey() {return k;}public V getValue() {return v;}public K getK() {return k;}public void setK(K k) {this.k = k;}public V getV() {return v;}public void setV(V v) {this.v = v;}public Entry<K, V> getNext() {return next;}public void setNext(Entry<K, V> next) {this.next = next;}}}
3、通过一个main函数,来测试一下,我们写的这个hashmap是否正确。
public static void main(String[] args) {Map<String, Integer> map=new HashMap<String,Integer>();map.put("sss", 30);map.put("sss", 31);System.out.println(map.get("sss"));}
注意,引入包的时候要注意,不要引入jdk框架的map和hash包,要引入我们自己写的这个包。
HashMap有一个叫做Entry的内部类,它用来存储key-value对。
上面的Entry对象是存储在一个叫做table的Entry数组中。
table的索引在逻辑上叫做“桶”(bucket),它存储了链表的第一个元素。
key的hashcode()方法用来找到Entry对象所在的桶。
如果两个key有相同的hash值,他们会被放在table数组的同一个桶里面。
---------------------------------------------------------------------------------------------------
作者:朱培 ID:sdksdk0
HashMap源码分析与实现相关推荐
- Java类集框架 —— HashMap源码分析
HashMap是基于Map的键值对映射表,底层是通过数组.链表.红黑树(JDK1.8加入)来实现的. HashMap结构 HashMap中存储元素,是将key和value封装成了一个Node,先以一个 ...
- 查询已有链表的hashmap_源码分析系列1:HashMap源码分析(基于JDK1.8)
1.HashMap的底层实现图示 如上图所示: HashMap底层是由 数组+(链表)=(红黑树) 组成,每个存储在HashMap中的键值对都存放在一个Node节点之中,其中包含了Key-Value ...
- 源码分析系列1:HashMap源码分析(基于JDK1.8)
1.HashMap的底层实现图示 如上图所示: HashMap底层是由 数组+(链表)+(红黑树) 组成,每个存储在HashMap中的键值对都存放在一个Node节点之中,其中包含了Key-Value ...
- HashMap源码分析(转载)
一.HashMap概述 HashMap基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了不同步和允许使用 null 之外,HashMap ...
- Map接口总结与HashMap源码分析
Map接口 1.Map,用于保存K-V(双列元素) 2.Map中的Key Value可以是任意引用分类型的数据,会封装到HashMap的Node对象中 3.Map的key不允许重复.原因和HashSe ...
- 在参考了众多博客之后,我写出了多达三万字的HashMap源码分析,比我本科毕业论文都要精彩
HashMap源码分析 以下代码都是基于java8的版本 HashMap简介 源码: public class HashMap<K,V> extends AbstractMap<K, ...
- hashmap源码分析及常用方法测试_一点课堂(多岸学院)
HashMap 简介 底层数据结构分析 JDK1.8之前 JDK1.8之后 HashMap源码分析 构造方法 put方法 get方法 resize方法 HashMap常用方法测试 感谢 changfu ...
- HashMap 源码分析与常见面试题
文章目录 HashMap 源码分析 jdk 1.7 内部常量 静态内部类 Holder 类 构造方法 put 过程 put 整体流程图 jdk 1.8 增加的常量 Node 类 Hash 值计算的变化 ...
- HashMap源码分析
文章目录 简介 继承关系 存储结构 源码分析 属性 Node节点 TreeNode HashMap 构造方法 put 添加方法 待更新 简介 在我们使用数据存储的时候都会有数据结构这种东西,但是传统的 ...
- JDK7中HashMap源码分析
文章目录 JDK7中的HashMap 一.JDK7中HashMap源码中重要的参数 二.JDK7中HashMap的构造方法 三.JDK7中创建一个HashMap的步骤 四.JDK7中HashMap的p ...
最新文章
- python谁的课比较好-【年度系列】2018年学习Python最好的5门课程
- Android --- java.lang.RuntimeException: Can‘t create handler inside thread that has not called Loop
- 存储过程中执行DDL
- android alsa,android下alsa驱动alsa_arecord录音问题
- javamail发送邮件,解决被垃圾邮件问题
- 零基础学python难吗-零基础学Python难吗,或者有什么其他数据加工软件推荐?
- show processlist中kill锁表语句与慢sql
- C#操作XML的完整例子——XmlDocument篇(转载,仅做学习之用)
- 网络操作系统发展历程
- 将网页转换成pdf文档的方法
- fatal error C1083: 无法打开包括文件: “SDKDDKVer.h”: No such file or directory
- 安卓手机上最好的3个小说阅读器
- 手机怎么识别图中文字?这两个方法靠谱
- 软件编程语言培训师张孝祥
- java2048ai,2048小游戏ai设计
- 应届毕业生不想应聘上班,一心想独自创业,有什么好的项目推荐?
- 2017年BEC剑桥商务英语考试指南
- 20170909深度学习solar测试日志
- 《硝烟中的scrum和xp》读书笔记
- 我的世界整合包内自带服务器,我的世界服务器整合包怎么用