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的更多相关文章

  1. HashMap源码分析 JDK1.8

    本文按以下顺序叙述: HashMap的感性认识. 官方文档中对HashMap介绍的解读. 到源码中看看HashMap这些特性到底是如何实现的. 把源码啃下来有一种很爽的感觉, 相信你读完后也能体会到~ ...

  2. HashMap源码分析-jdk1.7

    注:转载请注明出处!!!!!!!这里咱们看的是JDK1.7版本的HashMap 学习HashMap前先知道熟悉运算符合 *左移 << :就是该数对应二进制码整体左移,左边超出的部分舍弃,右 ...

  3. ArrayList源码分析--jdk1.8

    ArrayList概述   1. ArrayList是可以动态扩容和动态删除冗余容量的索引序列,基于数组实现的集合.  2. ArrayList支持随机访问.克隆.序列化,元素有序且可以重复.  3. ...

  4. ReentrantLock源码分析--jdk1.8

    JDK1.8 ArrayList源码分析--jdk1.8LinkedList源码分析--jdk1.8HashMap源码分析--jdk1.8AQS源码分析--jdk1.8ReentrantLock源码分 ...

  5. JDK1.8 HashMap源码分析

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

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

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

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

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

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

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

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

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

随机推荐

  1. (转载)Newtonsoft.Json使用总结

    Newtonsoft.Json使用总结 初识JSON.......................................................................... ...

  2. CefSharp试用

    Github地址: https://github.com/cefsharp/CefSharp 首先下载所有源代码下来 然后直接打开Sln 然后就可以直接调试WinForm.Wpf的Example了 注 ...

  3. UVA 11992 Fast Matrix Operations (降维)

    题意:对一个矩阵进行子矩阵操作. 元素最多有1e6个,树套树不好开(我不会),把二维坐标化成一维的,一个子矩阵操作分解成多条线段的操作. 一次操作的复杂度是RlogC,很容易找到极端的数据(OJ上实测 ...

  4. Python XML 解析

    什么是 XML? XML 指可扩展标记语言(eXtensible Markup Language). XML 被设计用来传输和存储数据. XML 是一套定义语义标记的规则,这些标记将文档分成许多部件并 ...

  5. SpringMVC的controller层的方法返回值

    1.ModelAndView  既带着数据,又返回视图路劲 2.String 返回试图路径  model带数据  (官方或企业推荐使用此种方式 ,此方法符合解耦思想,即数据,视图,分离 MVC) 3. ...

  6. Mybatis学习记录(2)

    1.mybatis与hibernate不同 Mybatis和hibernate,mybatis不完全是一个ORM框架,因为Mybatis需要程序员自己编写sql语句.mybatis可以通过xml或注解 ...

  7. VueX源码分析(2)

    VueX源码分析(2) 剩余内容 /module /plugins helpers.js store.js helpers要从底部开始分析比较好.也即先从辅助函数开始再分析那4个map函数mapSta ...

  8. win10安装pytorch——前面有坑,快跳进去鸭

    嗯!花费了不少时间才把pytorch安装成功.主要原因就是: 清华和中科大的Anaconda国内镜像源关闭了 activate.bat 不是内部或外部命令(这个真实奇怪) 1. 安装过程 可以去Ana ...

  9. for_each_node(node)

    遍历各个pg_data_t节点. 1.定义在include/linux/nodemask.h中 /* * Bitmasks that are kept for all the nodes. */ en ...

  10. launchMode

    launchMode在多个Activity跳转的过程中扮演着重要的角色,它可以决定是否生成新的Activity实例,是否重用已存在的Activity实例,是否和其他Activity实例公用一个task ...