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. OpenCV.问题&解决

    ZC:PDF:D:\_eBook\OpenCV\学习OpenCV(中文版Linuxidc.com).pdf 1.函数cvCaptureFromAVI(...) & cvCreateFileCa ...

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

    本实验链接:mit 6.828 lab1 Exercise 7. 题目 Exercise 7. Use QEMU and GDB to trace into the JOS kernel and st ...

  3. java23种设计模式之七: 观察者模式

    一.应用背景     观察者模式又称为发布/订阅(Publish/Subscribe)模式,我们可以理解为:只有关注信公众号关注后才能收到信息 二.优.缺点 优点: 1.可以动态的改变对象的行为 缺点 ...

  4. idea调试框架时如何能够进入maven依赖包的源码

    最近用idea使用spring security做登录与权限控制,在调试过程中碰到问题,经常需要断点追踪源码,深入查看其中问题.但是maven中引用的包,进入后都是.class文件,非源码.导致查看时 ...

  5. Education Reform(CodeForces-119C)【DP】

    题意:从m门课选出n个排到n天,每天一门,难度须递增,每门课对应着一个作业量Xi,且Xi = Xi-1 + k or Xi - Xi-1 * k,总作业量要尽可能大,问能否排布,若能排布,求方案. 思 ...

  6. RabbitMQ快速开始

    ①:安装rabbitmq所需要的依赖包 yum install build-essential openssl openssl-devel unixODBC unixODBC-devel make g ...

  7. java的设计模式的一些链接,站在巨人的肩膀上,才能看的更远。(均来源与网上的各个大牛的博客中)

    创建型抽象工厂模式 http://www.cnblogs.com/java-my-life/archive/2012/03/28/2418836.html工厂方法 http://www.cnblogs ...

  8. nginx做为web容器部署静态资源以及做负载均衡反向代理实现

    需求:  此时前台开发完成打包生成静态资源文件,要做到以下方面: 使用nginx部署静态资源,同时nginx要实现端口转发,隐藏真实后台地址,同时后台需要做一个负载均衡. localhost:7001 ...

  9. 怎样理解在函数中声明var x = y = 1后调用函数时, x是局部变量, y是全局变量

    下面这段代码在执行的时候, 打印的结果是1, Error: undefined; function fn() { var x = y = 1; } fn(); console.log(y); // 1 ...

  10. Faster RCNN学习笔记

    感谢知乎大神的分享 https://zhuanlan.zhihu.com/p/31426458 Ross B. Girshick在2016年提出了新的Faster RCNN,在结构上,Faster R ...