面试的时候经常会遇见诸如:“java中的HashMap是怎么工作的”,“HashMap的get和put内部的工作原理”这样的问题。本文将用一个简单的例子来解释下HashMap内部的工作原理。每当hashmap扩容的时候需要重新去add Entry对象,需要重新hash,然后放入我们新的entry table数组里面。如果在工作中,已经知道hashmap需要存多少值,几千或者几万的时候,最好新指定题目的扩容大小,防止在put的时候进行再次扩容很多次。

一、源码分析

1、初始化参数

在hashmap中我们必须要知道的参数有:初始化容量大小,默认是1 << 4,也就是16,

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

加载因子0.75f,

static final float DEFAULT_LOAD_FACTOR = 0.75f;

hash在什么时候扩容?

put的时候会去做扩容,当大于3/4的时候就会。偶数扩容  ,  2*16    2*32   2*64 
2、put方法
 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。

对于put的值,我们可以来看一个例子:
public class demo {
    
    public static void main(String[] args) {
         HashMap hashMap=new HashMap<String, String>();
         Object put1 = hashMap.put("aa", "30");
         Object put2 = hashMap.put("aa", "31");
         System.out.println(put1+":"+put2);
    }
}
当put两个相同的key的时候,这个put返回的值是oldValue,也就是说我这里打印出来的 结果就是  null:30
put返回的值是oldValue。key一样value会覆盖
如果hash key重复,value是不会覆盖的(经过hash算法返回的值如果重复了)。

对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);}
3、get方法

当你传递一个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数组的同一个桶里面。

---------------------------------------------------------------------------------------------------

文章出处:http://blog.csdn.net/sdksdk0/article/details/79299286
作者:朱培 ID:sdksdk0
---------------------------------------------------------------------------------------------------

HashMap源码分析与实现相关推荐

  1. Java类集框架 —— HashMap源码分析

    HashMap是基于Map的键值对映射表,底层是通过数组.链表.红黑树(JDK1.8加入)来实现的. HashMap结构 HashMap中存储元素,是将key和value封装成了一个Node,先以一个 ...

  2. 查询已有链表的hashmap_源码分析系列1:HashMap源码分析(基于JDK1.8)

    1.HashMap的底层实现图示 如上图所示: HashMap底层是由  数组+(链表)=(红黑树) 组成,每个存储在HashMap中的键值对都存放在一个Node节点之中,其中包含了Key-Value ...

  3. 源码分析系列1:HashMap源码分析(基于JDK1.8)

    1.HashMap的底层实现图示 如上图所示: HashMap底层是由  数组+(链表)+(红黑树) 组成,每个存储在HashMap中的键值对都存放在一个Node节点之中,其中包含了Key-Value ...

  4. HashMap源码分析(转载)

    一.HashMap概述 HashMap基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了不同步和允许使用 null 之外,HashMap  ...

  5. Map接口总结与HashMap源码分析

    Map接口 1.Map,用于保存K-V(双列元素) 2.Map中的Key Value可以是任意引用分类型的数据,会封装到HashMap的Node对象中 3.Map的key不允许重复.原因和HashSe ...

  6. 在参考了众多博客之后,我写出了多达三万字的HashMap源码分析,比我本科毕业论文都要精彩

    HashMap源码分析 以下代码都是基于java8的版本 HashMap简介 源码: public class HashMap<K,V> extends AbstractMap<K, ...

  7. hashmap源码分析及常用方法测试_一点课堂(多岸学院)

    HashMap 简介 底层数据结构分析 JDK1.8之前 JDK1.8之后 HashMap源码分析 构造方法 put方法 get方法 resize方法 HashMap常用方法测试 感谢 changfu ...

  8. HashMap 源码分析与常见面试题

    文章目录 HashMap 源码分析 jdk 1.7 内部常量 静态内部类 Holder 类 构造方法 put 过程 put 整体流程图 jdk 1.8 增加的常量 Node 类 Hash 值计算的变化 ...

  9. HashMap源码分析

    文章目录 简介 继承关系 存储结构 源码分析 属性 Node节点 TreeNode HashMap 构造方法 put 添加方法 待更新 简介 在我们使用数据存储的时候都会有数据结构这种东西,但是传统的 ...

  10. JDK7中HashMap源码分析

    文章目录 JDK7中的HashMap 一.JDK7中HashMap源码中重要的参数 二.JDK7中HashMap的构造方法 三.JDK7中创建一个HashMap的步骤 四.JDK7中HashMap的p ...

最新文章

  1. python谁的课比较好-【年度系列】2018年学习Python最好的5门课程
  2. Android --- java.lang.RuntimeException: Can‘t create handler inside thread that has not called Loop
  3. 存储过程中执行DDL
  4. android alsa,android下alsa驱动alsa_arecord录音问题
  5. javamail发送邮件,解决被垃圾邮件问题
  6. 零基础学python难吗-零基础学Python难吗,或者有什么其他数据加工软件推荐?
  7. show processlist中kill锁表语句与慢sql
  8. C#操作XML的完整例子——XmlDocument篇(转载,仅做学习之用)
  9. 网络操作系统发展历程
  10. 将网页转换成pdf文档的方法
  11. fatal error C1083: 无法打开包括文件: “SDKDDKVer.h”: No such file or directory
  12. 安卓手机上最好的3个小说阅读器
  13. 手机怎么识别图中文字?这两个方法靠谱
  14. 软件编程语言培训师张孝祥
  15. java2048ai,2048小游戏ai设计
  16. 应届毕业生不想应聘上班,一心想独自创业,有什么好的项目推荐?
  17. 2017年BEC剑桥商务英语考试指南
  18. 20170909深度学习solar测试日志
  19. 《硝烟中的scrum和xp》读书笔记
  20. 我的世界整合包内自带服务器,我的世界服务器整合包怎么用

热门文章

  1. 阿辉DirectX 11学习笔记一
  2. 大数据管理平台-数据处理与数据集市
  3. html仿excel冻结 css,css实例:实现gridview仿excel冻结列
  4. JavaWeb项目间隔刷新出现412
  5. C/C++ FFmepeg Qt音视频开发录屏摄像机
  6. 华为设备MSDP配置命令
  7. 项目中的“里程碑”就是我们常说的里程碑吗?
  8. Android SQLite Google 官方用法
  9. 分析了获得家庭摇号新能源指标的数据后,我发现了一个秘密
  10. 单播、广播、组播(多播)