HashMap源码分析jdk1.6
HashMap数组每个元素的初始值为NULL
1、定义
public interface Map<K,V> {
int size();
boolean isEmpty();
boolean containsKey(Object key);
boolean containsValue(Object value);
V get(Object key);
V put(K key, V value);
V remove(Object key);
void putAll(Map<? extends K, ? extends V> m);
void clear();
Set<K> keySet();
Collection<V> values();
Set<Map.Entry<K, V>> entrySet();
interface Entry<K,V> {
V getValue();
V setValue(V value);
boolean equals(Object o);
int hashCode();
}
boolean equals(Object o);
int hashCode();
}
hash是“散列”:hash就是通过散列算法,将一个任意长度关键字转换为一个固定长度的散列值,但是有一点要指出的是,不同的关键字可能会散列出相同的散列值
2、HashMap类
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
3.底层存储
// 默认初始容量为16,必须为2的n次幂
static final int DEFAULT_INITIAL_CAPACITY = 16; // 最大容量为2的30次方
static final int MAXIMUM_CAPACITY = 1 << 30; // 默认加载因子为0.75f
static final float DEFAULT_LOAD_FACTOR = 0.75f; // Entry数组,长度必须为2的n次幂
transient Entry[] table; // 已存储元素的数量
transient int size ; // 下次扩容的临界值,size>=threshold就会扩容,threshold等于capacity*load factor
int threshold; // 加载因子
final float loadFactor ;
Entry数组
static class Entry<K,V> implements Map.Entry<K,V> {
final K key ;
V value;
Entry<K,V> next; // 指向下一个节点
final int hash;
Entry( int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
public final K getKey() {
return key ;
}
public final V getValue() {
return value ;
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry)o;
Object k1 = getKey();
Object k2 = e.getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
Object v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
return true;
}
return false;
}
public final int hashCode() {
return (key ==null ? 0 : key.hashCode()) ^
( value==null ? 0 : value.hashCode());
}
public final String toString() {
return getKey() + "=" + getValue();
}
// 当向HashMap中添加元素的时候调用这个方法,这里没有实现是供子类回调用
void recordAccess(HashMap<K,V> m) {
}
// 当从HashMap中删除元素的时候调动这个方法 ,这里没有实现是供子类回调用
void recordRemoval(HashMap<K,V> m) {
}
}

HashMap采用将相同的散列值存储到一个链表中,也就是说在一个链表中的元素他们的散列值绝对是相同的。
4.构造方法
private void putAllForCreate(Map<? extends K, ? extends V> m) {
for(Iterator<?extendsMap.Entry<?extendsK, ?extendsV>> i = m.entrySet().iterator(); i.hasNext(); ) {
Map.Entry<? extends K, ? extends V> e = i.next();
putForCreate(e.getKey(), e.getValue());
}
}
/**
* This method is used instead of put by constructors and
* pseudoconstructors (clone, readObject). It does not resize the table,
* check for comodification, etc. It calls createEntry rather than
* addEntry.
*/
private void putForCreate(K key, V value) {
int hash = (key == null) ? 0 : hash(key.hashCode());
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 != null && key.equals(k)))) {
e. value = value;
return;
}
}
createEntry(hash, key, value, i);
}
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
size++;
}
5、增加
public V put(K key, V value) {
// 如果key为null,调用putForNullKey方法进行存储
if (key == null)
return putForNullKey(value);
// 使用key的hashCode计算key对应的hash值
int hash = hash(key.hashCode());
// 通过key的hash值查找在数组中的index位置
int i = indexFor(hash, table.length );
// 取出数组index位置的链表,遍历链表找查看是有已经存在相同的key
for (Entry<K,V> e = table [i]; e != null; e = e. next) {
Object k;
// 通过对比hash值、key判断是否已经存在相同的key
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
// 如果存在,取出当前key对应的value,供返回
V oldValue = e. value;
// 用新value替换之旧的value
e. value = value;
e.recordAccess( this);
// 返回旧value,退出方法
return oldValue;
}
}
// 如果不存在相同的key
// 修改版本+1
modCount++;
// 在数组i位置处添加一个新的链表节点
addEntry(hash, key, value, i);
// 没有相同key的情况,返回null
return null;
}
private V putForNullKey(V value) {
// 取出数组第1个位置(下标等于0)的节点,如果存在则覆盖不存在则新增,和上面的put一样不多讲,
for (Entry<K,V> e = table [0]; e != null; e = e. next) {
if (e.key == null) {
V oldValue = e. value;
e. value = value;
e.recordAccess( this);
return oldValue;
}
}
modCount++;
// 如果key等于null,则hash值等于0
addEntry(0, null, value, 0);
return null;
}
hash函数
length = 2^n , m & (length-1) 相当于 m % length
更加符合,Hash算法均匀分布的原则
/**
* Applies a supplemental hash function to a given hashCode, which
* defends against poor quality hash functions. This is critical
* because HashMap uses power -of- two length hash tables, that
* otherwise encounter collisions for hashCodes that do not differ
* in lower bits. Note: Null keys always map to hash 0, thus index 0.
*/
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);
} /**
* Returns index for hash code h.
*/
static int indexFor(int h, int length) {
return h & (length-1);
}
addEntry函数
/**
* 增加一个k-v,hash组成的节点在数组内,同时可能会进行数组扩容。
*/
void addEntry( int hash, K key, V value, int bucketIndex) {
// 下面两行行代码的逻辑是,创建一个新节点放到单向链表的头部,旧节点向后移
// 取出索引bucketIndex位置处的链表节点,如果节点不存在那就是null,也就是说当数组该位置处还不曾存放过节点的时候,这个地方就是null,
Entry<K,V> e = table[bucketIndex];
// 创建一个节点,并放置在数组的bucketIndex索引位置处,并让新的节点的next指向原来的节点
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
// 如果当前HashMap中的元素已经到达了临界值,则将容量扩大2倍,并将size计数+1
if (size ++ >= threshold)
resize(2 * table.length );
}
新节点一直插入在最前端,新节点始终是单向列表的头节点。
/**
* Rehashes the contents of this map into a new array with a
* larger capacity. This method is called automatically when the
* number of keys in this map reaches its threshold.
*
* If current capacity is MAXIMUM_CAPACITY, this method does not
* resize the map, but sets threshold to Integer.MAX_VALUE.
* This has the effect of preventing future calls.
*
* @param newCapacity the new capacity, MUST be a power of two;
* must be greater than current capacity unless current
* capacity is MAXIMUM_CAPACITY (in which case value
* is irrelevant).
*/
void resize( int newCapacity) {
// 当前数组
Entry[] oldTable = table;
// 当前数组容量
int oldCapacity = oldTable.length ;
// 如果当前数组已经是默认最大容量MAXIMUM_CAPACITY ,则将临界值改为Integer.MAX_VALUE 返回
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
} // 使用新的容量创建一个新的链表数组
Entry[] newTable = new Entry[newCapacity];
// 将当前数组中的元素都移动到新数组中
transfer(newTable);
// 将当前数组指向新创建的数组
table = newTable;
// 重新计算临界值
threshold = (int)(newCapacity * loadFactor);
} /**
* Transfers all entries from current table to newTable.
*/
void transfer(Entry[] newTable) {
// 当前数组
Entry[] src = table;
// 新数组长度
int newCapacity = newTable.length ;
// 遍历当前数组的元素,重新计算每个元素所在数组位置
for (int j = 0; j < src. length; j++) {
// 取出数组中的链表第一个节点
Entry<K,V> e = src[j];
if (e != null) {
// 将旧链表位置置空
src[j] = null;
// 循环链表,挨个将每个节点插入到新的数组位置中
do {
// 取出链表中的当前节点的下一个节点
Entry<K,V> next = e. next;
// 重新计算该链表在数组中的索引位置
int i = indexFor(e. hash, newCapacity);
// 将下一个节点指向newTable[i]
e. next = newTable[i];
// 将当前节点放置在newTable[i]位置
newTable[i] = e;
// 下一次循环
e = next;
} while (e != null);
}
}
}
transfer方法中,由于数组的容量已经变大,也就导致hash算法indexFor已经发生变化,原先在一个链表中的元素,在新的hash下可能会产生不同的散列值,so所有元素都要重新计算后安顿一番
hashmap的resize非常的低效
6、删除
/**
* 根据key删除元素
*/
public V remove(Object key) {
Entry<K,V> e = removeEntryForKey(key);
return (e == null ? null : e. value);
} /**
* 根据key删除链表节点
*/
final Entry<K,V> removeEntryForKey(Object key) {
// 计算key的hash值
int hash = (key == null) ? 0 : hash(key.hashCode());
// 根据hash值计算key在数组的索引位置
int i = indexFor(hash, table.length );
// 找到该索引出的第一个节点
Entry<K,V> prev = table[i];
Entry<K,V> e = prev; // 遍历链表(从链表第一个节点开始next),找出相同的key,
while (e != null) {
Entry<K,V> next = e. next;
Object k;
// 如果hash值和key都相等,则认为相等
if (e.hash == hash &&
((k = e. key) == key || (key != null && key.equals(k)))) {
// 修改版本+1
modCount++;
// 计数器减1
size--;
// 如果第一个就是要删除的节点(第一个节点没有上一个节点,所以要分开判断)
if (prev == e)
// 则将下一个节点放到table[i]位置(要删除的节点被覆盖)
table[i] = next;
else
// 否则将上一个节点的next指向当要删除节点下一个(要删除节点被忽略,没有指向了)
prev. next = next;
e.recordRemoval( this);
// 返回删除的节点内容
return e;
}
// 保存当前节点为下次循环的上一个节点
prev = e;
// 下次循环
e = next;
} return e;
}
8、查找
public V get(Object key) {
// 如果key等于null,则调通getForNullKey方法
if (key == null)
return getForNullKey();
// 计算key对应的hash值
int hash = hash(key.hashCode());
// 通过hash值找到key对应数组的索引位置,遍历该数组位置的链表
for (Entry<K,V> e = table [indexFor (hash, table .length)];
e != null;
e = e. next) {
Object k;
// 如果hash值和key都相等,则认为相等
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
// 返回value
return e.value ;
}
return null;
}
private V getForNullKey() {
// 遍历数组第一个位置处的链表
for (Entry<K,V> e = table [0]; e != null; e = e. next) {
if (e.key == null)
return e.value ;
}
return null;
}
9、是否包含
代码和get几乎一样
10、容量
/**
* Returns the number of key -value mappings in this map.
*
* @return the number of key- value mappings in this map
*/
public int size() {
return size ;
} /**
* Returns <tt>true</tt> if this map contains no key -value mappings.
*
* @return <tt> true</tt> if this map contains no key -value mappings
*/
public boolean isEmpty() {
return size == 0;
}
HashMap源码分析jdk1.6的更多相关文章
- HashMap源码分析 JDK1.8
本文按以下顺序叙述: HashMap的感性认识. 官方文档中对HashMap介绍的解读. 到源码中看看HashMap这些特性到底是如何实现的. 把源码啃下来有一种很爽的感觉, 相信你读完后也能体会到~ ...
- HashMap源码分析-jdk1.7
注:转载请注明出处!!!!!!!这里咱们看的是JDK1.7版本的HashMap 学习HashMap前先知道熟悉运算符合 *左移 << :就是该数对应二进制码整体左移,左边超出的部分舍弃,右 ...
- ArrayList源码分析--jdk1.8
ArrayList概述 1. ArrayList是可以动态扩容和动态删除冗余容量的索引序列,基于数组实现的集合. 2. ArrayList支持随机访问.克隆.序列化,元素有序且可以重复. 3. ...
- ReentrantLock源码分析--jdk1.8
JDK1.8 ArrayList源码分析--jdk1.8LinkedList源码分析--jdk1.8HashMap源码分析--jdk1.8AQS源码分析--jdk1.8ReentrantLock源码分 ...
- JDK1.8 HashMap源码分析
一.HashMap概述 在JDK1.8之前,HashMap采用数组+链表实现,即使用链表处理冲突,同一hash值的节点都存储在一个链表里.但是当位于一个桶中的元素较多,即hash值相等的元素较多时 ...
- HashMap 源码分析 基于jdk1.8分析
HashMap 源码分析 基于jdk1.8分析 1:数据结构: transient Node<K,V>[] table; //这里维护了一个 Node的数组结构: 下面看看Node的数 ...
- 源码分析系列1:HashMap源码分析(基于JDK1.8)
1.HashMap的底层实现图示 如上图所示: HashMap底层是由 数组+(链表)+(红黑树) 组成,每个存储在HashMap中的键值对都存放在一个Node节点之中,其中包含了Key-Value ...
- 【JAVA集合】HashMap源码分析(转载)
原文出处:http://www.cnblogs.com/chenpi/p/5280304.html 以下内容基于jdk1.7.0_79源码: 什么是HashMap 基于哈希表的一个Map接口实现,存储 ...
- 【Java】HashMap源码分析——基本概念
在JDK1.8后,对HashMap源码进行了更改,引入了红黑树.在这之前,HashMap实际上就是就是数组+链表的结构,由于HashMap是一张哈希表,其会产生哈希冲突,为了解决哈希冲突,HashMa ...
随机推荐
- openfire4.0.2源码 使用 IntelliJ IDEA 搭建开发环境
从官网下载压缩包,解压,直接打开build目录下的project 打开后, 相关的设置 fix直接修复或者下载 设置 设置每个插件目录下的java目录为source 编译openfire和plugin ...
- Codeforces Round #321 (Div. 2) C Kefa and Park(深搜)
dfs一遍,维护当前连续遇到的喵的数量,然后剪枝,每个统计孩子数量判断是不是叶子结点. #include<bits/stdc++.h> using namespace std; ; int ...
- Codeforces Round #Pi (Div. 2) 567E President and Roads ( dfs and similar, graphs, hashing, shortest paths )
图给得很良心,一个s到t的有向图,权值至少为1,求出最短路,如果是一定经过的边,输出"YES",如果可以通过修改权值,保证一定经过这条边,输出"CAN",并且输 ...
- Spark集锦
1 Spark官网 http://spark.apache.org/ 2 Spark书籍 http://down.51cto.com/tag-spark%E4%B9%A6%E7%B1%8D.html
- 【转】json格式化、高亮库jsonFormater
http://leo108.com/pid-1996.asp JsonFormater 基于jQuery的json格式化.高亮库 核心代码参考天马行空工作室,本人只做了模块化和一些代码优化. demo ...
- python之道08
1.有如下文件,a1.txt,里面的内容为: 某某是最好的学校, 全心全意为学生服务, 只为学生未来,不为牟利. 我说的都是真的.哈哈 分别完成以下的功能: a,将原文件全部读出来并打印. 答案 f ...
- C10 C语言数据结构
目录 枚举 结构体 共用体 枚举 enum enum枚举是 C 语言中的一种基本数据类型,它可以让数据更简洁,更易读. 枚举语法定义格式为: enum 枚举名 {枚举元素1,枚举元素2,……}; 枚举 ...
- C# Dictionary 的几种遍历方法,排序
Dictionary<string, int> list = new Dictionary<string, int>(); list.Add(); //3.0以上版本 fore ...
- Fedora 28 系统基础配置以及常用软件安装方式
实验说明: 很多人说Linux很难用,很难上手,其实不然,倘若不玩游戏,其实很多发行版Linux都可以成为主力系统,就比如本章要讲的 Fedora 28.本章会从镜像来源.系统安装.基础配置和常用软件 ...
- python入门:while循环里面True和False的作用,真和假
#!/usr/bin/env python # -*- coding:utf-8 -*- #while循环里面True和False的作用,真和假 """ n1等于真(Tr ...