HashMap在java编程中,算使用频率top10中的类了。这里是关于HashMap的源码的分析。一个类的源码分析,要看他的来龙去脉,他的历史迭代。一来从以前的版本开始分析,由易到难;二来可以看到他的迭代优化过程。HashMap的源码分析,就从很老以前的一个版本开始分析。
 
   简要说明,HashMap内部是一个数组,Object key 通过hash得到数组的index,如果数组index的位置有碰撞,则通过链表的形式接到那个位置上。取值是先hash到某个位置,然后在比较链表中每个值,获取到要取的值。
 
   这里先重点分析java1.2中HashMap的源码。

private transient Entry table[];

    private transient int count;

    private int threshold;

    private float loadFactor;

    private transient int modCount = 0;

 // 构造方法一
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Initial Capacity: "+
initialCapacity);
if (loadFactor <= 0)
throw new IllegalArgumentException("Illegal Load factor: "+
loadFactor);
if (initialCapacity==0)
initialCapacity = 1;
this.loadFactor = loadFactor;
table = new Entry[initialCapacity];
threshold = (int)(initialCapacity * loadFactor);
}
// 构造方法二
public HashMap(int initialCapacity) {
this(initialCapacity, 0.75f);
}
// 构造方法三
public HashMap() {
this(101, 0.75f);
}
// 构造方法四
public HashMap(Map t) {
this(Math.max(2*t.size(), 11), 0.75f);
putAll(t);
}
     属性数组table就是HashMap内部存放各种实体的数组。
     count是当前存放实体的数量。
     modCount是HashMap修改的次数,这里不是做什么统计用,在后面的HashIterator里要用到,HashIterator计算中,不希望HashMap被修改的。
     initialCapacity是HashMap内数组的初始长度。构造方法三中,默认初始长度为101,不太清楚这个101是怎么定义出来的。在构造方法四中,他的初始长度是11和入参map长度的2倍之间的大值。这里的11和101比较诡异。
     capacity是HashMap里数组的最大长度,随着HashMap里数组长度的变化而变化。
     threshold是用来提醒数组table该增大了的一个阀值。
     loadFactor是threshold值计算的一个系数,默认0.75,  threshold = capacity * loadFactor 。
 
基本的方法:
 
 // 查看HashMap存放数,count是HashMap的存放数属性
public int size() {
return count;
} // 查看HashMap存放数是否为空,count==0表示存放数为空
public boolean isEmpty() {
return count == 0;
}
 
对于调用方存入的一个个key和value,HashMap内部存放的是一个个内部实体对象,这里分析他的内部实体类。

//  HashMap内部实体类
private static class Entry implements Map.Entry {
int hash; // 存入HashMap key值的hashCode值
Object key; // 存入HashMap的key值
Object value; // 存入HashMap的value值
Entry next; // 存入HashMap中下一个值,这里是为产生Hash碰撞后,链表结构准备的,单向链表 // 构造方法
Entry(int hash, Object key, Object value, Entry next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
} // 对象克隆方法
protected Object clone() {
return new Entry(hash, key, value,
(next==null ? null : (Entry)next.clone()));
} public Object getKey() {
return key;
} public Object getValue() {
return value;
} // 设置value值,并返回老的value值
public Object setValue(Object value) {
Object oldValue = this.value;
this.value = value;
return oldValue;
} // 判断实体对象的相等方法;只有在实体的key和value都相等时,才相等
public boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry)o; return (key==null ? e.getKey()==null : key.equals(e.getKey())) &&
(value==null ? e.getValue()==null : value.equals(e.getValue()));
} // hashCode值,实体的hashCode值为key的hashCode和value的hashCode的异或值
public int hashCode() {
return hash ^ (value==null ? 0 : value.hashCode());
} public String toString() {
return key+"="+value;
}
}
 
HashMap同时也实现了Iterator接口,这里分析他的实现逻辑

// Iterator的类型
private static final int KEYS = 0; // Iterator对key进行操作
private static final int VALUES = 1;// Iterator对value进行操作
private static final int ENTRIES = 2;// iterator对entry进行操作 // HashMap对Iterator接口的实现
private class HashIterator implements Iterator {
Entry[] table = HashMap.this.table;
int index = table.length;
Entry entry = null; // 临时变量,存放hasNext方法时,遍历到的数组位链表对象
Entry lastReturned = null; // 临时变量,存放next方法时,遍历到的对象;为remove方法中准备
int type; // iterator的类型 // 存放HashMap修改的次数
private int expectedModCount = modCount; // 构造方法,入参为设定Iterator的类型
HashIterator(int type) {
this.type = type;
} // 判断容器内是否有值
public boolean hasNext() {
while (entry==null && index>0)
entry = table[--index]; return entry != null;
} // 获取下一个值
public Object next() {
// 遍历过程中,HashMap是不允许有改动,否则抛出异常ConcurrentModificationException
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
// 遍历过程中,处理那种没有先调用hasNext方法的情况
while (entry==null && index>0)
entry = table[--index];
// 如果entry不为null,处理entry
if (entry != null) {
// entry存放在临时变量lastReturned
Entry e = lastReturned = entry;
// entry存放链表的下一个节点,为下一次调用next方法做准备
entry = e.next;
// 根据type的类型,返回不同的值
return type == KEYS ? e.key : (type == VALUES ? e.value : e);
}
throw new NoSuchElementException();
} // 移除实体对象
public void remove() {
// remove调用之前,需要先调用hasNext方法,否则lastReturned=null,抛出异常
if (lastReturned == null)
throw new IllegalStateException();
// 移除操作,HashMap也不希望在其他地方有修改
if (modCount != expectedModCount)
throw new ConcurrentModificationException(); Entry[] tab = HashMap.this.table;
// 找到lastReturned对应数组的位置
int index = (lastReturned.hash & 0x7FFFFFFF) % tab.length; // 找到对应数组的位置后,删除对应链表中的lastReturned值,下面是链表的操作
for (Entry e = tab[index], prev = null; e != null;
prev = e, e = e.next) {
if (e == lastReturned) {
modCount++;
expectedModCount++;
if (prev == null)
tab[index] = e.next;
else
prev.next = e.next;
count--;
lastReturned = null;
return;
}
}
throw new ConcurrentModificationException();
}
}
 
下面主要分析HashMap的添删查

// 判断HashMap是否包含key值
public boolean containsKey(Object key) {
Entry tab[] = table; // key值不为null
if (key != null) {
// 通过key计算出key对应数组table的下标,其实这里是可以封装成一个方法的,多个地方都要用到,后面jdk的版本都有改进
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
// 查找链表,判断key是否存在
for (Entry e = tab[index]; e != null; e = e.next)
if (e.hash==hash && key.equals(e.key))
return true;
} else { // key为null
// 查找链表,判断key是否存在
for (Entry e = tab[0]; e != null; e = e.next)
if (e.key==null)
return true;
} return false;
} // 判断HashMap是否包含value值
// 这个方法实际效率是很低的,需要先遍历数组,在一个个遍历链表,没有用到hash特性
public boolean containsValue(Object value) {
Entry tab[] = table;
// value值为null
if (value==null) {
// 遍历数组
for (int i = tab.length ; i-- > 0 ;)
// 遍历链表
for (Entry e = tab[i] ; e != null ; e = e.next)
if (e.value==null)
return true;
} else {// value值不为null
// 遍历数组
for (int i = tab.length ; i-- > 0 ;)
// 遍历链表
for (Entry e = tab[i] ; e != null ; e = e.next)
if (value.equals(e.value))
return true;
} return false;
} // 获取key对应的value值
public Object get(Object key) {
Entry tab[] = table;
// key不为null
if (key != null) {
// key的hashCode值
int hash = key.hashCode();
// key的hashCode计算出的hash值
int index = (hash & 0x7FFFFFFF) % tab.length;
// 通过index定位到数组,然后遍历对应的链表结构
for (Entry e = tab[index]; e != null; e = e.next)
if ((e.hash == hash) && key.equals(e.key))
return e.value;
} else {
// 如果key为null,直接遍历对应的链表结构
for (Entry e = tab[0]; e != null; e = e.next)
if (e.key==null)
return e.value;
} return null;
} // 往HashMap中存放key和value
public Object put(Object key, Object value) { Entry tab[] = table;
int hash = 0;
int index = 0; // 这部分操作是当数组下标的对应位置已经有值,并且
// key不为null
if (key != null) {
// 获得key对应数组的下标
hash = key.hashCode();
index = (hash & 0x7FFFFFFF) % tab.length;
// 在数组对应的下标有值的情况下
// key已经存在,把新的value代替老的value,并返回老的value
for (Entry e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && key.equals(e.key)) {
Object old = e.value;
e.value = value;
return old;
}
}
} else { // key为null,数组的下标是确定的,为0
// 在数组对应的下标有值的情况下
// key已经存在,把新的value代替老的value,并返回老的value
for (Entry e = tab[0] ; e != null ; e = e.next) {
if (e.key == null) {
Object old = e.value;
e.value = value;
return old;
}
}
} // HashMap的修改次数加一
modCount++;
// 如果数组的使用长度大于或等于阀值threshold后
if (count >= threshold) {
// 扩展数组
rehash(); // 重新赋值tab
tab = table;
// 重新计算index
index = (hash & 0x7FFFFFFF) % tab.length;
} // 在原HashMap中,key没找到对应的value时
// 这里先构造一个实体,把实体的next属性指向数组下标index对应的位置
Entry e = new Entry(hash, key, value, tab[index]);
// 数组下标对应的位置赋值实体e
tab[index] = e;
// 实体数量加一
count++;
return null;
} // 推展数组
private void rehash() {
// 原数组长度
int oldCapacity = table.length;
// 原数组
Entry oldMap[] = table; // 新数组长度,这里数组的增长策略是2倍加一的增
int newCapacity = oldCapacity * 2 + 1;
// 重新创建数组
Entry newMap[] = new Entry[newCapacity]; // HashMap的修改次数加一
modCount++;
// 更新HashMap的扩展数组的阀值
threshold = (int)(newCapacity * loadFactor);
// 赋值新数组到HashMap的数组
// 这个地方存在并发问题,因为此时数组虽然创建了,但是实体值还是没有迁移过来的。
// 如果此时有人读取value值,可能会返回为空
table = newMap; // 把老数组里的值迁移到新数组里
// 遍历数组
for (int i = oldCapacity ; i-- > 0 ;) {
// 遍历链表
for (Entry old = oldMap[i] ; old != null ; ) {
Entry e = old;
old = old.next; int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = newMap[index];
newMap[index] = e;
}
}
} // 移除实体
public Object remove(Object key) {
Entry tab[] = table;
// key不为null
if (key != null) {
// 获得key对应的hash值和对应的数组下标
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length; // 根据对应的数组下标,找到链表对象
// 遍历链表
for (Entry e = tab[index], prev = null; e != null;
prev = e, e = e.next) {
if ((e.hash == hash) && key.equals(e.key)) {
modCount++;
if (prev != null)
prev.next = e.next;
else
tab[index] = e.next; count--;
Object oldValue = e.value;
e.value = null;
return oldValue;
}
}
} else {// key为null,对应的数组下标为0
// 根据对应的数组下标,找到链表对象
// 遍历链表
for (Entry e = tab[0], prev = null; e != null;
prev = e, e = e.next) {
if (e.key == null) {
modCount++;
if (prev != null)
prev.next = e.next;
else
tab[0] = e.next; count--;
Object oldValue = e.value;
e.value = null;
return oldValue;
}
}
} return null;
} // 批量添加
public void putAll(Map t) {
Iterator i = t.entrySet().iterator();
while (i.hasNext()) {
Map.Entry e = (Map.Entry) i.next();
put(e.getKey(), e.getValue());
}
} // 清除数组内容
public void clear() {
Entry tab[] = table;
modCount++;
for (int index = tab.length; --index >= 0; )
tab[index] = null;
count = 0;
}

HashMap源码分析一的更多相关文章

  1. 【JAVA集合】HashMap源码分析(转载)

    原文出处:http://www.cnblogs.com/chenpi/p/5280304.html 以下内容基于jdk1.7.0_79源码: 什么是HashMap 基于哈希表的一个Map接口实现,存储 ...

  2. Java中HashMap源码分析

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

  3. JDK1.8 HashMap源码分析

      一.HashMap概述 在JDK1.8之前,HashMap采用数组+链表实现,即使用链表处理冲突,同一hash值的节点都存储在一个链表里.但是当位于一个桶中的元素较多,即hash值相等的元素较多时 ...

  4. HashMap源码分析和应用实例的介绍

    1.HashMap介绍 HashMap 是一个散列表,它存储的内容是键值对(key-value)映射.HashMap 继承于AbstractMap,实现了Map.Cloneable.java.io.S ...

  5. 【Java】HashMap源码分析——常用方法详解

    上一篇介绍了HashMap的基本概念,这一篇着重介绍HasHMap中的一些常用方法:put()get()**resize()** 首先介绍resize()这个方法,在我看来这是HashMap中一个非常 ...

  6. 【Java】HashMap源码分析——基本概念

    在JDK1.8后,对HashMap源码进行了更改,引入了红黑树.在这之前,HashMap实际上就是就是数组+链表的结构,由于HashMap是一张哈希表,其会产生哈希冲突,为了解决哈希冲突,HashMa ...

  7. Java BAT大型公司面试必考技能视频-1.HashMap源码分析与实现

    视频通过以下四个方面介绍了HASHMAP的内容 一. 什么是HashMap Hash散列将一个任意的长度通过某种算法(Hash函数算法)转换成一个固定的值. MAP:地图 x,y 存储 总结:通过HA ...

  8. Java源码解析——集合框架(五)——HashMap源码分析

    HashMap源码分析 HashMap的底层实现是面试中问到最多的,其原理也更加复杂,涉及的知识也越多,在项目中的使用也最多.因此清晰分析出其底层源码对于深刻理解其实现有重要的意义,jdk1.8之后其 ...

  9. HashMap源码分析(史上最详细的源码分析)

    HashMap简介 HashMap是开发中使用频率最高的用于映射(键值对 key value)处理的数据结构,我们经常把hashMap数据结构叫做散列链表: ObjectI entry<Key, ...

  10. HashMap 源码分析 基于jdk1.8分析

    HashMap 源码分析  基于jdk1.8分析 1:数据结构: transient Node<K,V>[] table;  //这里维护了一个 Node的数组结构: 下面看看Node的数 ...

随机推荐

  1. Java中的一些关键字:static,final,和abstract,interface,以及访问修饰符说明

    1.关键字可以修饰的说明: 关键字 属性(是否可修饰) 方法(是否可修饰) 类(是否可修饰) static 是 是 是 final 是 是 是 abstract 否 是 是 2.关键字的意义: 关键字 ...

  2. 通过js获取本机的IP地址

    参考链接:https://blog.csdn.net/qq_39327418/article/details/90052668

  3. 磁盘冗余列阵Raid技术知识与实践

    Raid介绍 什么是Raid?  Raid级别介绍  Raid 技术分类 RAID分为两类: 软RAID, 系统层面实现的,性能差. 硬RAID, 硬件层面实现的,性能好. 主板板载RAID: 功能弱 ...

  4. docker镜像内没有vim

    问题: 我们在容器中找不到vim等命令   原因: 镜像制作的时候没把这些东西加进去   解决: 用apt update更新源之后再安装vim apt update apt-get install - ...

  5. BIO和NIO实现文件复制

    普通文件复制 public void copyFile() throws Exception{ FileInputStream fis=new FileInputStream("C:\\Us ...

  6. UWP笔记-消息弹窗自动淡出

    为了让用户有个更好的UI交互,可以增加自动淡出的消息弹窗,例如:网易云音乐UWP,切换播放模式时,出现的类似消息提示. 右键项目,添加用户控件 UserControlDemo.xaml: <Us ...

  7. 《MIT 6.828 Lab 1 Exercise 11》实验报告

    本实验的网站链接:MIT 6.828 Lab 1 Exercise 11. 题目 The above exercise should give you the information you need ...

  8. 初识RedisCluster集群

    为什么需要Redis集群 需要提高更大的并发量 Redis官方提出拥有10万QPS的请求量 如果业务需要Redis拥有100万的QPS 可以通过集群来提升并发量. 需要存储更大的数据量 一般服务器的机 ...

  9. Sumitomo Mitsui Trust Bank Programming Contest 2019 Task F. Interval Running

    Link. There is a nice approach to this problem that involves some physical insight. In the following ...

  10. EffectiveC++

    宁可以编译器替换预处理器 define 不被视为语言的一部分,因此也就有可能在预处理阶段被优化掉,导致相关变量出现错误. #define ASPECT_RATIO 1.63 //可以尝试将其替换为 c ...