1、关键属性与内部类

1.1属性

  使用拉链法解决hash冲突的hash表,hash表里存了多少个元素、数组有多长以及当什么情况下需要扩容是重要的参数。

transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
transient int size;
int threshold;
final float loadFactor;
  • table,用来实现拉链法的数组
  • size,该数组里一共有多少个节点包括拉链法链表里的节点
  • threshold,临界值,当size大于threhold的时候HashMap会扩容。threshold=Capacity*loadFactory
  • loadFactory,负载因子,用来衡量HashMap的满的程度
  • Capacity,数组的长度

1.2内部类

 static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
int hash;

  存储的值、指向下一个node的指针、hash值。HashMap拉链法使用的是单链表。

2、构造器

2.1 无参构造器

  一般的集合类都会提供一个无参的构造器,无参构造器使用默认的参数传入有参构造器。

    public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}

2.2 有参构造器

  • initialCapacity。用于初始化threshold

  • loadFactory。初始化loadFactory

  有参构造器在两个地方有点违反我的理解:

  • 我知道HashMap内部维护一个数组,但是HashMap的构造器并没有新建该数组
  • 数组的长度用Capacity变量存储,该构造器有一个initialCapacity的参数,但这个参数没有赋值给Capacity而是threshold。init方法里也没有执行新建数组的操作。结合之前阅读源码的经验:数组的初始化一定发生在第一次put的时候。
    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); this.loadFactor = loadFactor;
threshold = initialCapacity;
init();
}

4、put  

  果然table的初始化在put里,这可以可以理解成HashMap的懒加载机制,只有用户在Put一个值的时候说明HashMap才真正被使用,也只有在这个时候才需要初始化数组。

  整个put方法可以分为三个部分

  • 检查是否是第一次使用HashMap,如果是就初始化table
  • 如果传入的KV中的K是Null放到一个固定的地方
  • 找到K的Index并对可能出现的哈希冲突使用拉链发解决
  • 如果到了扩容条件就扩容
 public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
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;
e.recordAccess(this);
return oldValue;
}
} modCount++;
addEntry(hash, key, value, i);
return null;
}

4.1 第一次扩容

  在Put方法的开始检查table是否为EMPTY_TABLE。EMPTY_TABLE是static修饰的数组,在HashMap类被加载的时候一同加载。当新建一个HashMap对象且没有使用的时候,table指向EMPTY_TABLE。所以检查table是否指向EMPTY_TABLE数组就是检查HashMap是否已经初始化过,如果没有就需要初始化。

    /**
* An empty table instance to share when the table is not inflated.
*/
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;

  新建数组传入的参数是threshold,但最终table的大小并不是threshold。在inflaatTable中:首先计算出比threshold大的最小的2的幂次作为Capacity,其次根据Capacity和loadFactory计算出threshold替换原有的threshold。

这是一个很别扭的过程:threshold--->Capacity-->threshold。也就是说传入的threshold并不是最终的threshold。 

    private void inflateTable(int toSize) {
// Find a power of 2 >= toSize
int capacity = roundUpToPowerOf2(toSize); threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
table = new Entry[capacity];
initHashSeedAsNeeded(capacity);
}

4.2 如果key为Null

  HashMap支持放入key为null的节点,key为Null的节点会同一放到table[0]的位置,即在table[0]的位置形成一条链。

4.3 解决可能哈希冲突

  在key不等于null的情况下,首先计算出key的哈希值,然后根据key的哈希值计算出key在table里的位置i。接下来就是解决在位置i处可能存在的哈希冲突了,遍历table[i]处的链表,如果在该链表上找到一个节点的key和待插入的节点的key相同,用新的value替换掉旧的value并返回旧的value。如果没有找到就执行addEntry方法。

4.4 插!

  当走到addEntry方法的时候有两种可能:1、table[i]是一个空的链表,即for循环一次没有执行 2、table[i]的链表里没有找到和key重复的节点 ,总而言之需要在table[i]处插入一个节点。在插入节点之前首先要检查是否需要resize一次,然后才执行createEntry插入节点。

 void addEntry(int hash, K key, V value, int bucketIndex) {
    //检查是否需要扩容
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
    //插入节点
createEntry(hash, key, value, bucketIndex);
}

  典型的头插法插入链表,插入链表之后size++

    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、resize

 void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
} createEntry(hash, key, value, bucketIndex);
}

  当两个条件同时达到时才可以扩容:

  • size>threshold
  • table[i]处有节点

  总结起来就是容量不太够的情况下又发生了哈希冲突,所以此时需要扩容,扩容的大小是原来旧数组大小的两倍。

 void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
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);
}

  扩容是新建数组再把旧数组拷贝过去,关键是这个拷贝的过程需要重新计算哈希transfer,其实就是执行了一次插入过程,也是头插法。

 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;
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;
}
}
}

4、get

  和put方法相比get方法平平无奇,当key不为Null的时候计算出key的索引,然后遍历该索引上的链表直至找到需要的值

    public V get(Object key) {
if (key == null)
return getForNullKey();
Entry<K,V> entry = getEntry(key); return null == entry ? null : entry.getValue();
}
    final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
} int hash = (key == null) ? 0 : hash(key);
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 != null && key.equals(k))))
return e;
}
return null;
}

HashMap 1.7的更多相关文章

  1. HashMap与TreeMap源码分析

    1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Ja ...

  2. HashMap的工作原理

    HashMap的工作原理   HashMap的工作原理是近年来常见的Java面试题.几乎每个Java程序员都知道HashMap,都知道哪里要用HashMap,知道HashTable和HashMap之间 ...

  3. 计算机程序的思维逻辑 (40) - 剖析HashMap

    前面两节介绍了ArrayList和LinkedList,它们的一个共同特点是,查找元素的效率都比较低,都需要逐个进行比较,本节介绍HashMap,它的查找效率则要高的多,HashMap是什么?怎么用? ...

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

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

  5. 学习Redis你必须了解的数据结构——HashMap实现

    本文版权归博客园和作者吴双本人共同所有,转载和爬虫请注明原文链接博客园蜗牛 cnblogs.com\tdws . 首先提供一种获取hashCode的方法,是一种比较受欢迎的方式,该方法参照了一位园友的 ...

  6. HashMap与HashTable的区别

    HashMap和HashSet的区别是Java面试中最常被问到的问题.如果没有涉及到Collection框架以及多线程的面试,可以说是不完整.而Collection框架的问题不涉及到HashSet和H ...

  7. JDK1.8 HashMap 源码分析

    一.概述 以键值对的形式存储,是基于Map接口的实现,可以接收null的键值,不保证有序(比如插入顺序),存储着Entry(hash, key, value, next)对象. 二.示例 public ...

  8. HashMap 源码解析

    HashMap简介: HashMap在日常的开发中应用的非常之广泛,它是基于Hash表,实现了Map接口,以键值对(key-value)形式进行数据存储,HashMap在数据结构上使用的是数组+链表. ...

  9. java面试题——HashMap和Hashtable 的区别

    一.HashMap 和Hashtable 的区别 我们先看2个类的定义 public class Hashtable extends Dictionary implements Map, Clonea ...

  10. 再谈HashMap

    HashMap是一个高效通用的数据结构,它在每一个Java程序中都随处可见.先来介绍些基础知识.你可能也知 道,HashMap使用key的hashCode()和equals()方法来将值划分到不同的桶 ...

随机推荐

  1. WPF实现主题更换的简单DEMO

    WPF实现主题更换的简单DEMO 实现主题更换功能主要是三个知识点: 动态资源 ( DynamicResource ) INotifyPropertyChanged 接口 界面元素与数据模型的绑定 ( ...

  2. thinkphp——通过在线编辑器添加的内容在模板里正确显示(只显示内容,而不是html代码)

    thinkphp编辑器回显问题如下: 解决办法如下: 对于编辑器发布的内容,前台模板显示为html的解决办法是: 在模板输出字段加入html_entity_decode()函数 也就是:PHP输出时的 ...

  3. 使用Gson将对象类转成Json对象时出现\u003d的问题

    Gson将对象转成Json对象的方法 Gson gson=new Gson(); String json=gson.toJson(Student.class); 这种情况,如果Student属性中的某 ...

  4. Java 线程方法

    线程标识相关 方法 描述 public Thread(Runnable target, String name)  带参数的构造方法, 第二个参数是线程名称 public static Thread ...

  5. Openlayer3中应用的技术

    ol3-ext有很多很丰富的效果,可以不用重复造轮子,ol3-ext示例大全:http://viglino.github.io/ol3-ext/ 在本次项目中使用到了ol3-ext的两个功能:图层管理 ...

  6. Android项目实战(五十一):浅谈GreenDao

    比较出名的数据库框架 GreenDao使用步骤: 1.app目录下的build.gradle文件 添加依赖 compile 'org.greenrobot:greendao:3.2.0' 顶部添加插件 ...

  7. Android 方法数超过64k、编译OOM、编译过慢解决方案。

    目前将项目中的leancloud的即时通讯改为环信的即时通讯.当引入easeui的时候 出现方法数超过上限的问题. 搜索一下问题,解决方法很简单. 这里简单记录一下,顺序记录一下此解决方案导致的另一个 ...

  8. 新闻思考-阿里进军游戏产业,苹果发力ARM芯片

    2018.04.03 大家好,这是我开通博客的第一篇文章,我希望在这里分享我的知识,也学习更多的知识,希望大家学习愉快. 阿里进军游戏产业,拿下旅行青蛙的代理权.腾讯一直在进攻阿里的核心业务:电商和支 ...

  9. C#多线程图片爬虫

    写了个简单的多线程图片爬虫,整理一下.数据已经爬下来了,图片URL需要自行拼接,首先从Lawyers表中取的RawData字段,RawData中有一个list字段是json格式的数据,需要的只是lis ...

  10. SQLServer基础之数据页类型:GAM,SGAM,PFS

    简介 我们已经知道SQL Server IO最小的单位是页,连续的8个页是一个区.SQL Server需要一种方式来知道其所管辖的数据库中的空间使用情况,这就是GAM页和SGAM页. GAM页 GAM ...