HashMap的源码分析(一)
1.hashMap的关键值
DEFAULT_INITIAL_CAPACITY:默认初始容量16,∈(0,1<<30),实际大小为2的整数次幂;
DEFAULT_LOAD_FACTOR:默认加载因子0.75,
threshold:阈值,resize的判断条件。(threshold=容量*加载因子)
2.hashMap 的构造函数
public HashMap(int initialCapacity, float loadFactor) {//传入指定容量、加载因子
if (initialCapacity < )
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
threshold = initialCapacity;
init();
}
public HashMap(int initialCapacity) { //指定容量,加载因子0.75
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public HashMap() { //默认容量16,加载因子0.75
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
public HashMap(Map<? extends K, ? extends V> m) {//构造一个映射关系与指定 Map 相同的新 HashMap.(一直不明白这样构造和new HashMap(); 然后 map.putAll(map2) 有什么区别?希望看到的大神讲解一下··)
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
inflateTable(threshold);
putAllForCreate(m);
}
3.hashMap的get()、put()方法
①put
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null) //如果key为null①
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length); //key不为null,根据key的hash值计算出table位置 (i = hash & 容量 )
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { //遍历对比key==this.key?新值替换旧值,返回旧值:新增entry
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);//②
return null; //返回值不为null,说明这次put发生了新旧值得替换
}
private V putForNullKey(V value) { //put进去key为null的键值对 ①
for (Entry<K,V> e = table[0]; e != null; e = e.next) { //key为null的entry默认放在table[0],遍历table[0]的entry
if (e.key == null) { //若果以前有key为null的entry
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue; //新值替换旧值,并且返回旧值
}
}
modCount++;
addEntry(0, null, value, 0); //以前没有当前key值,新增entry,②
return null;
}
void addEntry(int hash, K key, V value, int bucketIndex) {//新增entry②
if ((size >= threshold) && (null != table[bucketIndex])) { //size(key-value对)达到阈值 && 当前table不是空的---》resize④
resize(2 * table.length); //扩容为当前2倍,根据新的容量重新计算分配map中entry的位置
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex); //新建entry ③
}
void createEntry(int hash, K key, V value, int bucketIndex) { //新建entry ③
Entry<K,V> e = table[bucketIndex]; //那到当前table的第一个entry(马上第一要变第二了!)
table[bucketIndex] = new Entry<>(hash, key, value, e); //把新entry插入到table的第一格
size++; //key-value对数量加一
}
②get

public V get(Object key) {
if (key == null)
return getForNullKey(); //返回key为null的value ①
Entry<K,V> entry = getEntry(key); //拿到key的entry ②
return null == entry ? null : entry.getValue(); //返回entry的value
}
private V getForNullKey() { //如果map.get(null) 返回table[0]中key为null的value,没有返回null ①
if (size == 0) { //hashMap已占用槽的长度
return null;
}
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null)
return e.value;
}
return null;
}
final Entry<K,V> getEntry(Object key) { ②
if (size == 0) { //判断map中的key-value对不是0
return null;
}
int hash = (key == null) ? 0 : hash(key); //获取key的hash值
for (Entry<K,V> e = table[indexFor(hash, table.length)]; //通过hash值和容量计算出table位置n,遍历table[n]里面的entry对比key相等的返回entry
e != null;
e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
③remove
public V remove(Object key) {
Entry<K,V> e = removeEntryForKey(key);
return (e == null ? null : e.value); //remove方法返回删除掉的value
}
final Entry<K,V> removeEntryForKey(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : hash(key);
int i = indexFor(hash, table.length); //通过hash计算index
Entry<K,V> prev = table[i]; //首次遍历是未 table[i] 的头entry
Entry<K,V> e = prev; //用于遍历的entry
while (e != null) {
Entry<K,V> next = e.next; //拿到后项entry
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) { //找到要删除的key的entry
modCount++;
size--; //删除后size减一
if (prev == e) //如果要删除的是 table[i]的头entry
table[i] = next; //老二晋升为 table[i]的头entry
else
prev.next = next; //前一entry的next指向后一entry(删除当前entry)
e.recordRemoval(this);
return e;
}
prev = e; //将当前entry设为前女友
e = next; //下一个
}
return e;
}
④clear
public void clear() {
modCount++; //对map的操作次数,用于迭代时的线程安全
Arrays.fill(table, null);
size = 0; //数组中的entry数量设为0,此时的容量没有缩小。仍为clear前的容量
}
public static void fill(Object[] a, Object val) {
for (int i = 0, len = a.length; i < len; i++) //遍历将数组的所有值设为null,
a[i] = val;
}
⑤containsValue
public boolean containsValue(Object value) {
if (value == null)
return containsNullValue();
Entry[] tab = table;
for (int i = 0; i < tab.length ; i++) //遍历数组
for (Entry e = tab[i] ; e != null ; e = e.next) //遍历entry
if (value.equals(e.value))
return true;
return false;
}
private boolean containsNullValue() {
Entry[] tab = table;
for (int i = 0; i < tab.length ; i++)
for (Entry e = tab[i] ; e != null ; e = e.next)
if (e.value == null)
return true;
return false;
}
HashMap的源码分析(一)的更多相关文章
- HashMap的源码分析与实现 伸缩性角度看hashmap的不足
本文介绍 1.hashmap的概念 2.hashmap的源码分析 3.hashmap的手写实现 4.伸缩性角度看hashmap的不足 一.HashMap的概念 HashMap可以将其拆分为Hash散列 ...
- HashMap的源码分析
hashMap的底层实现是 数组+链表 的数据结构,数组是一个Entry<K,V>[] 的键值对对象数组,在数组的每个索引上存储的是包含Entry的节点对象,每个Entry对象是一个单链表 ...
- Java——HashMap底层源码分析
1.简介 HashMap 根据键的 hashCode 值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的. HashMap 最多只允许一条记录的key为 nu ...
- Java中HashMap的源码分析
先来回顾一下Map类中常用实现类的区别: HashMap:底层实现是哈希表+链表,在JDK8中,当链表长度大于8时转换为红黑树,线程不安全,效率高,允许key或value为null HashTable ...
- HashMap方法源码分析
本文将分析put(),resize(),get()和remove()方法的源码 putval()方法 大致步骤:计算key的hash值:根据hash值计算数组下标:判断下标处是否有节点,无节点则直接插 ...
- Java源码——HashMap的源码分析及原理学习记录
学习HashMap时,需要带着这几个问题去,会有很大的收获: 一.什么是哈希表 二.HashMap实现原理 三.为何HashMap的数组长度一定是2的次幂? 四.重写equals方法需同时重写hash ...
- HashMap LinkedHashMap源码分析笔记
MapClassDiagram
- Java HashMap实例源码分析
引言 HashMap在键值对存储中被经常使用,那么它到底是如何实现键值存储的呢? 一 Entry Entry是Map接口中的一个内部接口,它是实现键值对存储关键.在HashMap中,有Entry的实现 ...
- HashMap从源码分析数据结构
1. HashMap在链表中存储的是键值对 2. 数组是一块连续的固定长度的内存空间,再好的哈希函数也不能保证得到的存储地址绝对不发生冲突.那么哈希冲突如何解决呢?哈希冲突的解决方案有多种:开放定址法 ...
随机推荐
- Eclipse无法打开项目中的任何文件
今天,Eclipse居然打不开项目的任何一个文件,网上也没搜到,我决定重置下视图试试: Windows-Reset Persective-然后能正常打开项目的文件了,解决问题!
- node.js的fs核心模块读写文件操作 -----由浅入深
node.js 里fs模块 常用的功能 实现文件的读写 目录的操作 - 同步和异步共存 ,有异步不用同步 - fs.readFile 都不能读取比运行内存大的文件,如果文件偏大也不会使用readFil ...
- 【转载】js常用方法和片段
在网上看了不少js方法的总结没,自己也尝试总结过,这篇只迄今为止觉得最清楚的,尤其是call和apply的方法总结,很到位!! 1.javascript删除元素节点 IE中有这样一个方法:remove ...
- JAVA线程池应用的DEMO
在做很多高并发应用的时候,单线程的瓶颈已经满足不了我们的需求,此时使用多线程来提高处理速度已经是比较常规的方案了.在使用多线程的时候,我们可以使用线程池来管理我们的线程,至于使用线程池的优点就不多说了 ...
- Python有哪些高大上的项目?
Python作为程序员的宠儿,得到了越来越多人的关注,使用Python进行应用程序开发的越来也多.那么,Python有哪些高大上的项目?这里有十个: 1.NuPIC 它是一个以HTM学习算法为工具的 ...
- java 8 总结 之lambda
lambda表达式 目的:行为参数化 Lambda表达式是简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表.函数主体.返回类型,可能还有一个可以抛出的异常列表. Lambda的基本语法 ...
- PHP初入,基础知识点分享(a标签&表格的嵌套&文字的处理)
<!DOCTYPE html><html> <head> <meta charset="utf-8" /> <title> ...
- MySQL (六)--外键、联合查询、子查询
1 外键 外键:foreign key,外面的键(键不在自己表中),如果一张表中有一个字段(非主键)指向另外一张表的主键,那么将该字段称为外键. 1.1 增加外键 外键可以在创建表的时候或创建表之后增 ...
- sudoku作业
1.Github项目地址: https://github.com/ataiyang/ls 2.PSP表格 PSP2.1 Personal Software Process Stages 预估耗时(分钟 ...
- 201521123095 《Java程序设计》第8周学习总结
1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结集合与泛型相关内容. 2. 书面作业 本次作业题集集合 Q1 List中指定元素的删除(题目4-1) 1.1 实验总结 对于删除函数 ...