HashMap及LinkedHashMap
HashMap是Map族中最为常用的一种,也是Java Collection Framework的重要成员。HashMap和双向链表合二为一即是LinkedHashMap。所谓LinkedHashMap,其落脚点在HashMap,因此更准确地说,它是一个将所有Node节点链入一个双向链表的HashMap。
1、HashMap概述
Map是 Key-Value键值对映射的抽象接口,该映射不包括重复的键,即一个键对应一个值。HashMap是Java Collection Framework的重要成员,也是Map族(如下图所示)中我们最为常用的一种。
在HashMap中,其会根据hash算法来计算key-value的存储位置并进行快速存取。特别地,HashMap最多只允许一条Node的key为Null,但允许多条Node的value为Null。此外,HashMap是Map 的一个非同步的实现。
HashMap(int initialCapacity, float loadFactor)构造函数意在构造一个指定初始容量和指定负载因子的空HashMap,
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
//容量最大为2的30次方
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
//这里调用函数计算触发扩容的阈值,threshold/loadFactor就是容量
this.threshold = tableSizeFor(initialCapacity);
}
1.3、HashMap的快速存取
在HashMap中,我们最常用的两个操作就是:put(Key,Value)和get(Key)。我们都知道,HashMap中的Key是唯一的,那它是如何保证唯一性的呢?
我们首先想到的是用equals比较,没错,这样是可以实现的,但随着元素的增多,put和get的效率将越来越低,这里的时间复杂度是O(n)。也就是说,假如HashMap有1000个元素,那么put时就需要比较1000次,这是相当耗时的,远达不到HashMap快速存取的目的。
实际上,HashMap很少会用到equals方法,因为其内通过一个哈希表管理所有元素,利用哈希算法可以快速的存取元素。当我们调用put方法存值时,HashMap首先会调用Key的hashCode方法,然后基于此值获取Key的哈希码,通过哈希码快速找到某个位置,这个位置可以被称之为 bucketIndex。
根据equals方法与hashCode的协定可以知道,如果两个对象的hashCode不同,那么equals一定为 false;如果其hashCode相同,equals也不一定为true。所以,理论上,hashCode 可能存在碰撞的情况,当碰撞发生时,这时会取出bucketIndex桶内已存储的元素(如果该桶next引用不空,即有了链表也要遍历链表),并通过hashCode()和equals()来逐个比较以判断Key是否已存在。
如果已存在,则使用新Value值替换旧Value值,并返回旧Value值;如果不存在,则存放新的键值对<Key, Value>到链表中。因此,在HashMap中,equals()方法只有在哈希码碰撞时才会被用到。
HashMap的put操作:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//第一次put元素时,table数组为空,先调用resize生成一个指定容量的数组
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//hash值和n-1的与运算结果为桶的位置,如果该位置空就直接放置一个Node
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
//如果计算出的bucket不空,即发生哈希冲突,就要进一步判断
else {
Node<K,V> e; K k;
//判断当前Node的key与要put的key是否相等
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//判断当前Node是否是红黑树的节点
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
//以上都不是,说明要new一个Node,加入到链表中
else {
for (int binCount = 0; ; ++binCount) {
//在链表尾部插入新节点,注意jdk1.8是在链表尾部插入新节点
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
// 如果当前链表中的元素大于树化的阈值,进行链表转树的操作
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//在链表中继续判断是否已经存在完全相同的key
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//走到这里,说明本次put是更新一个已存在的键值对的value
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
//在hashMap中,afterNodeAccess方法体为空,交给子类去实现
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//如果当前size超过临界值,就扩容。注意是先插入节点再扩容
if (++size > threshold)
resize();
//在hashMap中,afterNodeInsertion方法体为空,交给子类去实现
afterNodeInsertion(evict);
return null;
}
1.7 HashMap的线程不安全
于LinkedHashMap是HashMap的子类,所以LinkedHashMap自然会拥有HashMap的所有特性。比如,LinkedHashMap也最多只允许一条Entry的键为Null(多条会覆盖),但允许多条Entry的值为Null。
此外,LinkedHashMap 也是 Map 的一个非同步的实现。此外,LinkedHashMap还可以用来实现LRU (Least recently used, 最近最少使用)算法。
LinkedHashMap定义及构造函数
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>
{
/**
* HashMap.Node subclass for normal LinkedHashMap entries.
*/
static class Entry<K,V> extends HashMap.Node<K,V> {
//再加两个引用,分别指向前一个插入的Entry与后一个插入的Entry
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
/**
* The head (eldest) of the doubly linked list.
* 头节点引用
*/
transient LinkedHashMap.Entry<K,V> head;
/**
* The tail (youngest) of the doubly linked list.
* 尾节点引用
*/
transient LinkedHashMap.Entry<K,V> tail;
/**
* The iteration ordering method for this linked hash map: <tt>true</tt>
* for access-order, <tt>false</tt> for insertion-order.
* true表示按照访问顺序迭代,false时表示按照插入顺序
* @serial
*/
final boolean accessOrder;
...
}
LinkedHashMap采用的hash算法和HashMap相同,但是它重新定义了Entry。LinkedHashMap中的Entry继承了HashMap.Node,但增加了两个指针before 和 after,它们分别用于维护双向链接列表。特别需要注意的是,next用于维护HashMap各个Node的连接顺序,before、after用于维护Entry插入的先后顺序。
HashMap及LinkedHashMap的更多相关文章
- HashMap和LinkedHashMap的区别
参考:https://blog.csdn.net/a822631129/article/details/78520111 java为数据结构中的映射定义了一个接口java.util.Map;它有四个实 ...
- TreeMap,HashMap,LinkedHashMap区别,很简单解释
TreeMap,HashMap,LinkedHashMap之间的区别和TreeSet,HashSet,LinkedHashSet之间的区别相似. TreeMap:内部排序. HashMap:无序. L ...
- TreeSet VS HashSet VS LinkedHashSet; TreeMap VS HashMap VS LinkedHashMap
From online resources Set HashSet is much faster than TreeSet (constant-time versus log-time for mos ...
- HashMap与LinkedHashMap的区别
/** * remark: * HashMap与LinkedHashMap的区别 * 这里必须使用LinkedHashMap: * 原因 ...
- HashMap和LinkedHashMap区别
import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.uti ...
- HashMap和LinkedHashMap的比较使用
由于现在项目中用到了LinkedHashMap,并不是太熟悉就到网上搜了一下. import java.util.HashMap; import java.util.Iterator; impor ...
- Map,Hashmap,LinkedHashMap,Hashtable,TreeMap
java为数据结构中的映射定义了一个接口java.util.Map;它有四个实现类,分别是HashMap Hashtable LinkedHashMap 和TreeMap. Map主要用于存储健值对, ...
- LRU算法实现,HashMap与LinkedHashMap源码的部分总结
关于HashMap与LinkedHashMap源码的一些总结 JDK1.8之后的HashMap底层结构中,在数组(Node<K,V> table)长度大于64的时候且链表(依然是Node) ...
- 程序员必须知道的数据结构:HashMap 与 LinkedHashMap
为什么要说 HashMap 与 LinkedHashMap?第一:这两种数据结构是 Java Coder 中经常使用的数据结构.第二:这两种结构是最合适的能说明链表与数组的结构关系.在开始之前首先必须 ...
- 接口java.util.Map的四个实现类HashMap Hashtable LinkedHashMap TreeMap
java中HashMap,LinkedHashMap,TreeMap,HashTable的区别 :java为数据结构中的映射定义了一个接口java.util.Map;它有四个实现类,分别是HashMa ...
随机推荐
- flink-----实时项目---day07-----1.Flink的checkpoint原理分析 2. 自定义两阶段提交sink(MySQL) 3 将数据写入Hbase(使用幂等性结合at least Once实现精确一次性语义) 4 ProtoBuf
1.Flink中exactly once实现原理分析 生产者从kafka拉取数据以及消费者往kafka写数据都需要保证exactly once.目前flink中支持exactly once的sourc ...
- 大数据学习day18----第三阶段spark01--------0.前言(分布式运算框架的核心思想,MR与Spark的比较,spark可以怎么运行,spark提交到spark集群的方式)1. spark(standalone模式)的安装 2. Spark各个角色的功能 3.SparkShell的使用,spark编程入门(wordcount案例)
0.前言 0.1 分布式运算框架的核心思想(此处以MR运行在yarn上为例) 提交job时,resourcemanager(图中写成了master)会根据数据的量以及工作的复杂度,解析工作量,从而 ...
- STM32 CAN用队列缓冲接收的例子
[1]CAN接收用队列缓冲的例子: 发单帧没有问题,多帧或者连续发两帧就有问题.
- Spring Cloud Feign原理详解
目录 1.什么是Feign? 2.Open Feign vs Spring Cloud Feign 2.1.OpenFeign 2.2.Spring Cloud Open Feign 3.Spring ...
- linux 操作只读变量
由于该操作需要用到 gdb,所以需要先 安装好 gdb 1. 查询是否有gdb: 2. 如果没有,需要先执行 yum install gdb 命令进行安装 3. 定义 只读变量 abc 并打印值: a ...
- JQuery 和 CSS 等选择器:
JQuery 选择器: CSS 选择器:
- 基于Annotation(注解)的装配
一.常用注解 1.@Component 是一种通用注解,可用于任何Bean 2.@Repository 通常用于注解DAO层类,即持久层 3.@Service 通常用于注解Service类,即服务层 ...
- 【C/C++】01背包问题/动态规划
按小蓝书上写的大数据情况下没过,按解答区一个大佬的修改了过了 #include <bits/stdc++.h> using namespace std; class Solution { ...
- vue双向绑定和深浅拷贝
现象描述: vue 在使用的时候,当table绑定了某个data的时候.假如某个el-table-column下面的有个方法传参(data.row),然后在方法中用一个obj=data.row.(这里 ...
- 转:ios delegate
首先,大家应该都明白的是委托是协议的一种,顾名思义,就是委托他人帮自己去做什么事.也就是当自己做什么事情不方便的时候,就可以建立一个委托,这样就可以委托他人帮自己去实现什么方法. 其次,我简单的总结了 ...