一. 1.7 和1.8区别

  • 数据结构:

    • 1.7: 数组 + 链表
    • 1.8 : 数组 + 链表 + 红黑树
  • put:
    • 1.7: 头插法
    • 1.8: 尾插法
  • hash计算:
    • 1.7 : Objects.hashCode(getKey()) ^ Objects.hashCode(getValue())
    • 1.8: Objects.hashCode(getKey()) ^ Objects.hashCode(getValue()) -- > (h = key.hashCode()) ^ (h >>> 16)
  • 扩容:
    • 1.7: 再次哈希
    • 1.8: 分为 j 和 j + oldvalue
  • 初始化
    • 1.7: 默认有一个空的数组,table指向这个数组,当put时会判断是否为EMPTY_TABLE,然后进行初始化
    • 1.8: put时resize,然后对oldTab.length进行判断

二.源码部分

1.基本属性

AbstractMap<K, V> :AbstractMap 提供了 Map 的基本实现,使得我们以后要实现一个 Map 不用从头开始,只需要继承 AbstractMap, 然后按需求实现/重写对应方法即可。

Map是Java集合框架的根接口,另一个是Collection接口

Cloneable接口是一个标记接口,也就是没有任何内容

Serializable 接口之所以定义为空,是因为它只起到了一个标识的作用,告诉程序实现了它的对象是可以被序列化的,但真正序列化和反序列化的操作并不需要它来完成。

public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
{ /**
* The default initial capacity - MUST be a power of two.
* 默认的初始容量-必须是2的幂。
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 /**
* 最大容量:1,073,741,824
*/
static final int MAXIMUM_CAPACITY = 1 << 30; /**
* The load factor used when none specified in constructor.
* 默认负载因子0.75
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f; /**
* An empty table instance to share when the table is not inflated.
* 创建对象的时候默认table指向EMPTY_TABLE
*/
static final Entry<?,?>[] EMPTY_TABLE = {}; /**
* The table, resized as necessary. Length MUST Always be a power of two.
* 存放键值对
*/
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE; /**
* The number of key-value mappings contained in this map.
* 实际数量
*/
transient int size; //阈值
int threshold; /**
* The load factor for the hash table.
* 负载因子
* @serial
*/
final float loadFactor; transient int modCount; static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;

2.构造函数

和1.8相同有4种情况

  • 空参: 默认容量16,加载因子0.75
  • 指定容量
  • 指定容量、负载因子
  • 已有集合传入

3.put

public V put(K key, V value) {
//如果表没初始化,则去初始化
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
//key为空,抛出异常
if (key == null)
return putForNullKey(value);
//计算hash值
int hash = hash(key);
//找到数组下标h & (length-1)
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.equals(k))) {
V oldValue = e.value;
e.value = value;
//这个方法是留给LinkedHashMap实现的
e.recordAccess(this);
return oldValue;
}
}
//找不到添加新的结点
modCount++;
addEntry(hash, key, value, i);
return null;
}

4.addEntry

//put方法传入hash.key.vualue.和数组下标
void addEntry(int hash, K key, V value, int bucketIndex) {
//添加前还是判断是否需要扩容
if ((size >= threshold) && (null != table[bucketIndex])) {
//2 * table.length
resize(2 * table.length);
//重新计算数组下标
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
//创建结点
createEntry(hash, key, value, bucketIndex);
}
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
//头插法
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}

5.inflateTable()

//初始化表
private void inflateTable(int toSize) {
// Find a power of 2 >= toSize
//找到>=size的最小2的幂
int capacity = roundUpToPowerOf2(toSize);
//越界判断
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
//为table新建个数组
table = new Entry[capacity];
initHashSeedAsNeeded(capacity);
}

6.扩容

  • 和1.8不一样的一小点是,因为1.7的参数直接为新容量的大小
  • 因为1.8没有默认的空表,通过capacity和threshold来区别各种情况,而1.7不需要进行初始化的操作逻辑就简单的多。
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
//如果原来的容量已经为最大值,则将阈值也调整为最大,return
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
//创建新容量的数组
Entry[] newTable = new Entry[newCapacity];
//数据迁移
transfer(newTable, initHashSeedAsNeeded(newCapacity));
table = newTable;
//重新计算阈值
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
/**
* Transfers all entries from current table to newTable.
* 将所有表项从当前表转移到newTable。
*/
void transfer(Entry[] newTable, boolean rehash) {
//取新容量
int newCapacity = newTable.length;
//遍历表
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;
//如果是true就重新计算key的hash值(?防止哈希冲突)
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
//获取数组下标
int i = indexFor(e.hash, newCapacity);
//头插法
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}

HashMap(1.7)源码学习的更多相关文章

  1. Java集合专题总结(1):HashMap 和 HashTable 源码学习和面试总结

    2017年的秋招彻底结束了,感觉Java上面的最常见的集合相关的问题就是hash--系列和一些常用并发集合和队列,堆等结合算法一起考察,不完全统计,本人经历:先后百度.唯品会.58同城.新浪微博.趣分 ...

  2. HashMap与HashTable源码学习及效率比较分析

    一.个人学习后的见解: 首先表明学习源码后的个人见解,后续一次依次进行分析: 1.线程安全:HashMap是非线程安全的,HashTable是线程安全的(HashTable中使用了synchroniz ...

  3. HashMap(1.8)源码学习

    一.HashMap介绍 1.哈希表(hash table) 在哈希表中进行添加,删除,查找等操作,时间复杂度为O(1) 存储位置 = f(关键字) 其中,这个函数f一般称为哈希函数,这个函数的设计好坏 ...

  4. hashMap源码学习记录

    hashMap作为java开发面试最常考的一个题目之一,有必要花时间去阅读源码,了解底层实现原理. 首先,让我们看看hashMap这个类有哪些属性 // hashMap初始数组容量 static fi ...

  5. 基于jdk1.8的HashMap源码学习笔记

    作为一种最为常用的容器,同时也是效率比较高的容器,HashMap当之无愧.所以自己这次jdk源码学习,就从HashMap开始吧,当然水平有限,有不正确的地方,欢迎指正,促进共同学习进步,就是喜欢程序员 ...

  6. HashSet源码学习,基于HashMap实现

    HashSet源码学习 一).Set集合的主要使用类 1). HashSet 基于对HashMap的封装 2). LinkedHashSet 基于对LinkedHashSet的封装 3). TreeS ...

  7. HashMap的源码学习以及性能分析

    HashMap的源码学习以及性能分析 一).Map接口的实现类 HashTable.HashMap.LinkedHashMap.TreeMap 二).HashMap和HashTable的区别 1).H ...

  8. 【JDK1.8】 Java小白的源码学习系列:HashMap

    目录 Java小白的源码学习系列:HashMap 官方文档解读 基本数据结构 基本源码解读 基本成员变量 构造器 巧妙的tableSizeFor put方法 巧妙的hash方法 JDK1.8的putV ...

  9. JDK1.8源码学习-HashMap

    JDK1.8源码学习-HashMap 目录 一.HashMap简介 HashMap 主要用来存放键值对,它是基于哈希表的Map接口实现的,是常用的Java集合之一. 我们都知道在JDK1.8 之前 的 ...

随机推荐

  1. 【从小白到专家】Istio技术实践专题(四):应用接入Istio的正确姿势

    上一篇文章中,我们介绍了Istio针对单集群的三种主流部署安装方式:使用Istioctl安装.使用Helm自定义安装.独立Operator安装.本文将向大家介绍kubernetes中的应用接入Isti ...

  2. HDU-1004(C语言描述)

    Let the Balloon Rise 输入 输入包含多个测试用例.每个测试用例都以数字 N (0 < N < = 1000) 为起点, 分布的气球总数.下 N 行包含一个颜色.气球的颜 ...

  3. vue3路由的使用,保证你有所收获!

    路由变量 有的小伙伴,可能是第一次听见路由变量这个词. 什么是路由变量了,顾名思义就是这个路由地址是动态的,不是固定的. 它的运用场景是哪里呢? 比如说:1.详情页的地址,我们就可以去使用路由变量. ...

  4. JAVA实现对阿里云DNS的解析管理

    1.阿里云DNS的SDK依赖 <dependency> <groupId>com.aliyun</groupId> <artifactId>tea-op ...

  5. 5.12-jsp分页功能学习

    1.分页功能相关资料查询 分页须知知识点: (1)JDBC2.0的可滚动结果集. (2)HTTP GET请求. 一.可滚动结果集   Connection con  = DriverManager.g ...

  6. gin初识

    Gin 是一个用 Go (Golang) 编写的 web 框架. 它是一个类似于 martini 但拥有更好性能的 API 框架, 由于 httprouter,速度提高了近 40 倍. 如果你是性能和 ...

  7. 返回值List是JsonArray

    MyController中: index.jsp中

  8. HTTP-常用请求头

    HTTP-常用请求头 Accept:表示客户端可以接收的数据类型 Accpet-Language:表示客户端可以接收的语言类型 User-Agent:表示客户端浏览器的信息 Host:表示请求时的服务 ...

  9. Servlet-概念及实现一个Servlet程序

    Servlet技术 一,Servlet概念 1,Servlet是JavaEE规范之一.规范就是接口 2,Servlet就是JavaWeb三大组件之一.三大组件分别是:Servlet程序.Filter过 ...

  10. 集合框架-ArrayList集合存储自定义对象

    1 package cn.itcast.p3.arraylist.test; 2 3 import java.util.ArrayList; 4 import java.util.Iterator; ...