一.HashMap的存储结构
二.HashMap成员变量
//默认初始容量,总为2的次方值
static final int DEFAULT_INITIAL_CAPACITY = 16;
//最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
//默认加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//Entry数组,每一个Entry是一个键值对实体
transient Entry[] table;
//实际存的Entry个数
transient int size;
//数组扩容的阀值,当size+1 > threshold时,扩充到以前容量的两倍
//threshold = table.length * loadFactor
int threshold;
//负载比率
final float loadFactor;
//Map结构一旦变化,如put remove clear等操作的时候,modCount随之变化
transient volatile int modCount;
三.Entry对象
//很简单的一个键值对实体而已
static class Entry<K,V> implements Map.Entry<K,V> {
final K key; //key
V value; //value
Entry<K,V> next; //next Entry
final int hash; //计算出key的hash值
/**
* Creates new entry.
*/
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
.....
}
四.构造函数
// 构造函数
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: "
+ initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: "
+ loadFactor);
// 将传入的initialCapacity值,转变成2的次方值capacity去实例化hashmap的属性
// 比喻传入initialCapacity = 100,则算出来capacity = 2 << 7 = 128,
// 最终threshold = 128 * 0.75 = 96,table = new Entry[128]
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
this.loadFactor = loadFactor;
threshold = (int) (capacity * loadFactor);
table = new Entry[capacity];
// 模板方法模式,子类想在init里面做点什么重写init就好了
init();
}
五.hash算法和index算法
/**
* 让hashMap里面的元素尽量分布均需,方便查找
* @param h entry中key的hash值
* @return 打散后的hash值
*/
static int hash(int h) {
// 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);
}
/**
* 类似求模运算, 将hash值同length-1(必定是01111...1)相与,运算的结果处于1到length-1间,0刚好用来保存key为null和0的元素
* @param h 打散后的hash值
* @param length 数组的长度
* @return 数组下标 1到lenght-1
*/
static int indexFor(int h, int length) {
return h & (length-1);
}
六.取数据
public V get(Object key) {
if (key == null)
return getForNullKey();
//key != null时
//1.根据key计算出hash和index
int hash = hash(key.hashCode());
//2.从index处的链表头Entry e开始遍历链表,
//如果e满足hash(key.hashCode()) == e.hash && (key == e.key || key.equals(e.key)),就是我们要找的entry
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))) //注释3里的逻辑更好理解
return e.value;
}
return null;
}
//如果key为空,直接从table[0]链表里面遍历寻找value
private V getForNullKey() {
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null)
return e.value;
}
return null;
}
七.存数据,扩容
public V put(K key, V value) {
//1.key为空时候,调用putForNullKey方法
if (key == null)
return putForNullKey(value);
//2.key不为空
//2-1.计算hash和index
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
//2-2.根据index得到当前链表头e=table[i]不为空
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//2-3.如果e满足hash(key.hashCode())=e.hash && key==e.key || key.equals(e.key),value覆盖oldValue
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
//2-4.table[i]为空,直接插入entry
modCount++;
addEntry(hash, key, value, i);
return null;
}
private V putForNullKey(V value) {
//1.从0位置的链表头开始,遍历
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
//1-1.e.key==null时,直接替代value
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
//2.头Entry不存在时,直接插入entry
modCount++;
addEntry(0, null, value, 0);
return null;
}
/**
* @param hash 计算后key的hash值
* @param key
* @param value
* @param bucketIndex 应该插入到Entry[]的哪个位置
*/
void addEntry(int hash, K key, V value, int bucketIndex) {
//1.当前bucketIndex位置的头Entry e
Entry<K,V> e = table[bucketIndex];
//2.在bucketIndex位置新建一个Entry,新建Entry.next=原头Entry,也就是addEntry的Entry都被加到了链表头
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
//hashmap在每次addEntry完后检查是否扩容,而list是在每次加入之前检查:ensureCapacity(size + 1);elementData[size++] = e;
if (size++ >= threshold)
resize(2 * table.length);
}
//扩容,把hashmap的容量设置为新容量newCapacity
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
//每次扩到到原来的两倍,总有一个时候到MAXIMUM_CAPACITY
if (oldCapacity == MAXIMUM_CAPACITY) {
//到了MAXIMUM_CAPACITY设置threshold后直接return
threshold = Integer.MAX_VALUE;
return;
}
//1.新建newCapacity大小的Entry数组
Entry[] newTable = new Entry[newCapacity];
//2.把当前Entry的数据全部移到新Entry数组中
transfer(newTable);
//3.用已经就绪的新Entry数组重新给table赋值
table = newTable;
//4.设置新的threshold
threshold = (int)(newCapacity * loadFactor);
}
void transfer(Entry[] newTable) {
Entry[] src = table;
int newCapacity = newTable.length;
//1.从0开始遍历原Entry数组
for (int j = 0; j < src.length; j++) {
//2.拿到下标为j的链表头Entry e
Entry<K,V> e = src[j];
//3.遍历链表
if (e != null) {
src[j] = null;
do {
//3-1.e的下一个Entry,为了继续遍历做准备
Entry<K,V> next = e.next;
//3-2.计算e应该存放在新Entry数组的位置下标
int i = indexFor(e.hash, newCapacity);
//3-3.将e插入到新Entry[i]链表的表头
e.next = newTable[i];
//3-4.将Entry[i]表头的Entry重新定位为e,这样就完成了一个元素的重新hash
newTable[i] = e;
//3-5.继续链表的下一个Entry
e = next;
} while (e != null);
}
}
}
八.删数据
public V remove(Object key) {
Entry<K, V> e = removeEntryForKey(key);
return (e == null ? null : e.value);
}
/**
* Removes and returns the entry associated with the specified key in the
* HashMap. Returns null if the HashMap contains no mapping for this key.
*/
final Entry<K, V> removeEntryForKey(Object key) {
// 1.计算hash和index
int hash = (key == null) ? 0 : hash(key.hashCode());
int i = indexFor(hash, table.length);
Entry<K, V> prev = table[i];
Entry<K, V> e = prev;
// 2.遍历i位置的Entry链表
while (e != null) {
Entry<K, V> next = e.next;
Object k;
// 2-1.e满足hash(key.hashCode()) == e.hash && (key == e.key ||
// key.equals(e.key)),找到了要移除的Entry
if (e.hash == hash
&& ((k = e.key) == key || (key != null && key.equals(k)))) {
// 2-2.移除操作modCount++, size--
modCount++;
size--;
// 2-3-1.如果prev和e相等,说明要删除的Entry是链表头,直接将table[i]位置的Entry设置为next,就删除了e
if (prev == e)
table[i] = next;
else
// 2-3-2.如果不相等e =
// prev.next,说明要删除的Entry是除链表头外的其他Entry,将prev.next设置为next,就删除了e
prev.next = next;
e.recordRemoval(this);
return e;
}
// 把prev和e到移到下一个
prev = e;
e = next;
}
return e;
}
/**
* 还是将o的key拿到,然后和removeEntryForKey(Object key)一样了
*
* @param o
* must be Map.Entry
* @return
*/
final Entry<K, V> removeMapping(Object o) {
if (!(o instanceof Map.Entry))
return null;
Map.Entry<K, V> entry = (Map.Entry<K, V>) o;
Object key = entry.getKey();
......
}
九.对table[0]位置存放数据的理解
package com.zzy.collection2;
import java.util.Map;
public class TestHashMapEntry0 {
public static void main(String[] args) {
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
map.put(null, 1);
map.put(null, 2);
map.put(0, 3);
map.put(0, 4);
}
}
- put(null, value)其本质是addEntry(0, null, value, 0);
- put(0, value)其本质是addEntry(0, 0, value, 0);
- put(null, value1)后put(0, value2)都是向table[0]链表中处添加Entry,但 0 != null,所有value2不会覆盖value1。
- put(null, value1)后再次put(null, value2),value2覆盖value1。
分享到:
相关推荐
在上一篇博客 Java容器之HashMap源码分析(妈妈再也不用担心我不懂HashMap了) 从源码层次分析了HashMap容器的底层实现,在本篇博客将继续从源码层次分析Hashtable的底层实现。 注明:以下源码分析都是基于jdk ...
源码分析见我博文:http://blog.csdn.net/wabiaozia/article/details/50684556
允许放入null元素,除该类未实现同步外,其余跟Hashtable大致相同,跟TreeMap不同,该容器不保证元素顺序,根据需要该容器可能会对元素重新哈希,元素的顺序也会被重新打散,因此不同时间迭代同一个HashMap的顺序...
源码分析: ArrayList 源码+扩容机制分析 HashMap(JDK1.8)源码+底层数据结构分析 ConcurrentHashMap 源码+底层数据结构分析 IO IO 基础知识总结 IO 设计模式总结 IO 模型详解 并发 知识点/面试题总结 : (必看 ) ...
源码分析 : ArrayList 源码+扩容机制分析 HashMap(JDK1.8)源码+底层数据结构分析 ConcurrentHashMap 源码+底层数据结构分析 IO IO 基础知识总结 IO 设计模式总结 IO 模型详解 并发 知识点/面试题总结 : (必看 ) ...
hashmap源码 to-be-architect to be a Java architect,you should learn these.This page is updated irregularly. Java基础 深入分析 Java SPI 机制和原理 并发编程专题 Executors线程池 线程池ThreadPoolExecutor...
Java版水果管理系统源码 some knowledge Android Android性能优化方案(如内存优化、网络优化、布局...使用经过优化后的数据容器,常规的HashMap效率低下,建议使用Android优化过的数据容器,如SparseArray,它可以避
当客户机第一次调用一个Stateful Session Bean 时,容器必须立即在服务器中创建一个新的Bean实例,并关联到客户机上,以后此客户机调用Stateful Session Bean 的方法时容器会把调用分派到与此客户机相关联的Bean实例...
如何设计一个可达性分析系统 类加载机制 双亲委派机制 SPI 并发 synchronized 偏向锁/轻量级锁/重量级锁的原理 ReentractLock synchronized与ReentractLock区别 volatile 线程池原理和参数配置 内存的多级缓存机 CAS...
但通常情况下,由于Java Bean是被容器所创建(如Tomcat)的,所以Java Bean应具有一个无参的构造器,另外,通常Java Bean还要实现Serializable接口用于实现Bean的持久性。Java Bean实际上相当于微软COM模型中的本地...
java内核源码 Java指南针 基础核心 基础知识 反射 泛型 动态代理 JDK8新特性 集合容器 多线程与并发 Spring SpringMVC SpringBoot Mybatis 数据结构与算法 入门基础 基础数据结构 数组&链表 数组&链表进阶 栈 队列 ...
学生提问:老师,我想学习Java编程,到底是学习Eclipse好呢,还是学习JBuilder好呢? 21 1.9 本章小结 22 本章练习 22 第2章 理解面向对象 23 2.1 面向对象 24 2.1.1 结构化程序设计简介 24 2.1.2 程序的三种...
11.4.1 实现类HashMap284 11.4.2 实现类LinkedHashMap285 11.4.3 实现类TreeMap286 11.4.4 实现类Properties287 11.5 Collections类288 11.6 泛型概述292 11.7 本章习题300 第12章 12.1 理解线程304 12.1.1 什么是多...