热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Java源码分析HashMap的工作原理及实现

HashMap是什么?有什么特点?initialCapacity和loadFactor是什么?有哪些常用的方法?分别是如何实现的?实例引出HashMapimportjava.u
  • HashMap是什么?有什么特点?
  • initialCapacity 和 loadFactor 是什么?
  • 有哪些常用的方法?分别是如何实现的?

实例引出HashMap
import java.util.HashMap;
import java.util.Map;

public class Main {
public static void main(String[] args) {
HashMap map = new HashMap<>();
map.put("一月", 1);
map.put("二月", 2);
map.put("三月", 3);
map.put("四月", 4);
map.put("五月", 5);
map.put("六月", 6);
map.put("七月", 7);
map.put("八月", 8);
System.out.println(map);
for(Map.Entry entry : map.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
}

结果如下:

{一月=1, 六月=6, 二月=2, 三月=3, 四月=4, 八月=8, 七月=7, 五月=5}
一月: 1
六月: 6
二月: 2
三月: 3
四月: 4
八月: 8
七月: 7
五月: 5

下面我们来一步步看这个过程。

HashMap的定义
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable

这是HashMap类的一个定义部分,继承了类 AbstractMap ,实现了 Map 接口。

官方文档中还有这样的描述:

Hash table based implementation of the Map interface. This implementation provides all of the optional map operations, and permits null values and the null key. (The HashMap class is roughly equivalent to Hashtable, except that it is unsynchronized and permits nulls.) This class makes no guarantees as to the order of the map; in particular, it does not guarantee that the order will remain constant over time.

有几个关键点:
1. HashMap是一个实现了 Map 接口的hash table
2. 允许空值(null value)和空键(null key)
3. 和HashTable的区别是:HashMap是非同步,允许空values和key
4. HashMap中的序不保证和插入顺序一致,也不保证序不随时间变化
5. 由于基于Hash原理,所以 put 和 get 都是线性的

HashMap的构造函数

构造函数一


/**
* The load factor used when none specified in constructor.
*/

static final float DEFAULT_LOAD_FACTOR = 0.75f;


····//省略无关代码

/**
* The load factor for the hash table.
*
* @serial
*/

final float loadFactor;

····


/**
* Constructs an empty HashMap with the default initial capacity
* (16) and the default load factor (0.75).
*/

public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

在我们的例子中,

 HashMap<String, Integer> map = new HashMap<>();

用的就是这个无参的构造函数,引出一个关键点,

loadFactor 装载引子

Load factor The load factor is a measure of how full the hash table is allowed to get before its capacity is automatically increased.

Load factor就是bucket填满程度的最大比例, 默认值(DEFAULT_LOAD_FACTOR)为 0.75.

构造函数二
    /**
* Constructs an empty HashMap with the specified initial
* capacity and the default load factor (0.75).
*
* @param initialCapacity the initial capacity.
* @throws IllegalArgumentException if the initial capacity is negative.
*/

public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

引出第二个关键点,

Initial capacity 初始容量


Initial capacity The capacity is the number of buckets in the hash table, The initial capacity is simply the capacity at the time the hash table is created.

容量就是指哈希表创建时表中桶的个数。

好接着看这句:

 this(initialCapacity, DEFAULT_LOAD_FACTOR);

调用了另外一个构造函数

构造函数三
/**
* 生成一个空的HashMap,并指定其容量大小和负载因子
*
*/

public HashMap(int initialCapacity, float loadFactor) {
//保证初始容量大于等于0
if (initialCapacity <0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
//保证初始容量不大于最大容量MAXIMUM_CAPACITY
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//loadFactor小于0或为无效数字
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}

总结

当我们创建 HashMap 的时候,可以根据根据需求去指定 initialCapacity 和 loadFactor ,也可以让系统使用默认值 16 和 0.75

如果对迭代性能要求很高的话不要把capacity设置过大,也不要把load factor设置过小。

当bucket中的entries的数目大于capacity*load factor时就需要调整bucket的大小为当前的2倍。

    /**
* The default initial capacity - MUST be a power of two.
*/

static final int DEFAULT_INITIAL_CAPACITY = 1 <<4; // aka 16
   /**
* The load factor used when none specified in constructor.
*/

static final float DEFAULT_LOAD_FACTOR = 0.75f;

put方法

好,我们已经分析了

HashMap<String, Integer> map = new HashMap<>();

这行代码,希望我已经讲清楚了HashMap的定义,以及它的构造函数,以及 initialCapacity 和 loadFactor 。桶式也希望读者在读我这篇文章的时候,能边看Java源码边思考。

接着

 map.put("一月", 1);

put函数无疑是最常用的方法之一,关于HashMap的put方法在面试中也会经常问到。

  1. 底层维护一个 Node 数组,数组中每一项都是一个Node
  2. 向hashMap放置的对象实际上是存储在数组中,Map中的 key value 则以Node 的形式存放在数组中。
  3. 数组中的每个位置,通常被成为位桶和 hash 桶, 即hash相同的Node全放在同一个位置,冲突用 链表相连或者红黑树处理。

    put 函数的过程如下:

  4. 对key的hashCode()做hash,然后再计算index;
  5. 如果当前map中无数据,执行resize方法。并且返回n
  6. 如果要插入的键值对要存放的这个位置刚好没有元素,那么把他封装成Node对象,放在这个位置上
  7. 否则的话,说明这上面有元素(也就是说要hash碰撞)
  8. 如果这个元素的key与要插入的一样,那么就替换一下
  9. 如果当前节点是TreeNode类型的数据,执行putTreeVal方法
  10. 该链为链表,遍历这条链子上的数据
  11. 超过load factor*current capacity,resize
    public V put(K key, V value) {
// 1. 对key的hashCode()做hash,然后再计算index;
return putVal(hash(key), key, value, false, true);
}


final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
// 2. 底层维护一个 Node 数组,数组中每一项都是一个Node,关于这个Node后面再详细介绍
Node[] tab; Node p; int n, i;
// 如果当前map中无数据,执行resize方法。并且返回n
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//如果要插入的键值对要存放的这个位置刚好没有元素,那么把他封装成Node对象,放在这个位置上
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
//否则的话,说明这上面有元素(也就是说要hash碰撞)
Node e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//如果当前节点是TreeNode类型的数据,执行putTreeVal方法
else if (p instanceof TreeNode)
e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);
else {
// 该链为链表,遍历这条链子上的数据
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;

// 超过load factor*current capacity,resize
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}

Node

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

静态内部类Node实现了Map接口中的Map.Entry接口,有四个成员变量, hash 值, key, value, 以及指向下一个元素的next 。

更多

Java HashMap工作原理及实现

HashMap工作原理


推荐阅读
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • Python实现变声器功能(萝莉音御姐音)的方法及步骤
    本文介绍了使用Python实现变声器功能(萝莉音御姐音)的方法及步骤。首先登录百度AL开发平台,选择语音合成,创建应用并填写应用信息,获取Appid、API Key和Secret Key。然后安装pythonsdk,可以通过pip install baidu-aip或python setup.py install进行安装。最后,书写代码实现变声器功能,使用AipSpeech库进行语音合成,可以设置音量等参数。 ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文介绍了如何在给定的有序字符序列中插入新字符,并保持序列的有序性。通过示例代码演示了插入过程,以及插入后的字符序列。 ... [详细]
  • 使用在线工具jsonschema2pojo根据json生成java对象
    本文介绍了使用在线工具jsonschema2pojo根据json生成java对象的方法。通过该工具,用户只需将json字符串复制到输入框中,即可自动将其转换成java对象。该工具还能解析列表式的json数据,并将嵌套在内层的对象也解析出来。本文以请求github的api为例,展示了使用该工具的步骤和效果。 ... [详细]
  • PHP中的单例模式与静态变量的区别及使用方法
    本文介绍了PHP中的单例模式与静态变量的区别及使用方法。在PHP中,静态变量的存活周期仅仅是每次PHP的会话周期,与Java、C++不同。静态变量在PHP中的作用域仅限于当前文件内,在函数或类中可以传递变量。本文还通过示例代码解释了静态变量在函数和类中的使用方法,并说明了静态变量的生命周期与结构体的生命周期相关联。同时,本文还介绍了静态变量在类中的使用方法,并通过示例代码展示了如何在类中使用静态变量。 ... [详细]
  • ALTERTABLE通过更改、添加、除去列和约束,或者通过启用或禁用约束和触发器来更改表的定义。语法ALTERTABLEtable{[ALTERCOLUMNcolu ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • 预备知识可参考我整理的博客Windows编程之线程:https:www.cnblogs.comZhuSenlinp16662075.htmlWindows编程之线程同步:https ... [详细]
  • 重入锁(ReentrantLock)学习及实现原理
    本文介绍了重入锁(ReentrantLock)的学习及实现原理。在学习synchronized的基础上,重入锁提供了更多的灵活性和功能。文章详细介绍了重入锁的特性、使用方法和实现原理,并提供了类图和测试代码供读者参考。重入锁支持重入和公平与非公平两种实现方式,通过对比和分析,读者可以更好地理解和应用重入锁。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • 本文介绍了如何使用Express App提供静态文件,同时提到了一些不需要使用的文件,如package.json和/.ssh/known_hosts,并解释了为什么app.get('*')无法捕获所有请求以及为什么app.use(express.static(__dirname))可能会提供不需要的文件。 ... [详细]
  • 本文介绍了使用哈夫曼树实现文件压缩和解压的方法。首先对数据结构课程设计中的代码进行了分析,包括使用时间调用、常量定义和统计文件中各个字符时相关的结构体。然后讨论了哈夫曼树的实现原理和算法。最后介绍了文件压缩和解压的具体步骤,包括字符统计、构建哈夫曼树、生成编码表、编码和解码过程。通过实例演示了文件压缩和解压的效果。本文的内容对于理解哈夫曼树的实现原理和应用具有一定的参考价值。 ... [详细]
author-avatar
万超英先生
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有