Java源码——HashMap的源码分析及原理学习记录
2021-07-03 17:06
标签:更新 amp 复杂 sar 读取 比较 uri when specified 学习HashMap时,需要带着这几个问题去,会有很大的收获: 一、什么是哈希表 二、HashMap实现原理 三、为何HashMap的数组长度一定是2的次幂? 四、重写equals方法需同时重写hashCode方法 一.什么是哈希表 在了解哈希表之前,先了解下其他数据结构的操作执行性能,数据结构的物理存储结构只有两种方式:顺序存储结构和链式存储结构(栈,队列,数,图等) 数组:采用一段连续的存储单元来存储数据,对于指定下标的查找,时间复杂度为O(1);根据确定的值来查找,需要遍历数组,逐一进行比较,时间复杂度为O(n)。对于有序数组,可以采用二分查找,插值查找,斐波那契查找等方式,复杂度为O(logn);对于一般的插入删除操作。需要数组的移动,平均复杂度未O(n)。 线性表:对于链表的新增,删除操作(在找到指定操作位置后),仅需要处理结点间的引用即可,时间复杂度为O(1),而查找操作需要遍历链表逐一比较,复杂度为O(n) 二叉树:对于一颗相对平衡的有序二叉树,进行插入,查找没删除等操作,平均复杂度为O(logn)。 哈希表:在不考虑哈希冲突的情况下,仅仅一次定位就可以完成添加,删除,查找等操作,时间复杂度为O(1),因为哈希表的主干就是数组。 比如:需要新增或者查找某个元素,我们通过把当前元素的关键字通过某个函数映射到数组中的某个位置,通过数组下表一次定位就可以完成操作。 其中这个函数一般称为哈希函数,这个函数的设计好坏会直接影响到哈希表的优劣。下图为在哈希表中执行插入操作:
查找操作同理,先通过哈希函数计算出实际存储地址,然后从数组中对应地址取出即可。 哈希冲突 如果两个不同的元素,通过哈希函数得出的实际存储地址相同,换句话说,当对某个元素进行哈希运算,得到一个存储地址,然后要进行插入的时候,发现一家被其他元素占用了,这就是产生了哈希冲突,也叫哈希碰撞。哈希冲突的解决方法有多种:开放地址法(发生冲突,继续寻找下一块未被占用的存储地址),再散列函数法,链地址法(jdk1.8之前HashMap采用该方法,也就是数组+链表的方式,jdk1.8当hash值的节点数不小于8时,采用数组+链表+红黑树)。
图一表示jdk1.8之前的hashmap结构,左边部分代表哈希表,也称为哈希数组,数组的每个元素都是一个单链表的头结点,链表是用来解决冲突的,如果不同的key映射到了数组的同一位置上,就将其放入链表中。所以当hash值相等的元素较多时,通过key依次在链表查找的效率较低,时间复杂度为O(n)。 图二表示jdk1.8的hashmap的在同意hash值的节点数不小于8时的存储结构,不再采用单链表形式存储,而是采用红黑树。 二.HashMap的实现原理 HashMap的主干是一个Entry数组。Entry是HashMap的基本组成单元,每一个Entry包含一个key-value键值对 (1)Entry是HashMap 的一个静态内部类 (2)在HashMap中重要的属性字段 HashMap有4个构造器,其他构造器如果用户没有传入initialCapacity 和loadFactor这两个参数,会使用默认值 initialCapacity默认为16,loadFactory默认为0.75 (3)在HashMap中重要的方法,在常规构造器中,没有为数组table分配内存空间(有一个入参为指定Map的构造器例外),而是在执行put操作的时候才真正构建table数组 (4扩容函数resize()分析 Java源码——HashMap的源码分析及原理学习记录 标签:更新 amp 复杂 sar 读取 比较 uri when specified 原文地址:https://www.cnblogs.com/FanJava/p/9621784.html//HashMap的主干数组,可以看到就是一个Entry数组,初始值为空数组{},主干数组的长度一定是2的次幂,至于为什么这么做,后面会有详细分析。
transient Entry
static class Entry
//table就是存储Node类的数组,就是对应上图中左边那一栏,
/**
* The table, initialized on first use, and resized as
* necessary. When allocated, length is always a power of two.
* (We also tolerate length zero in some operations to allow
* bootstrapping mechanics that are currently not needed.)
*/
transient Node
//可以自己指定初始容量和装载因子
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;
//重新定义了扩容的门限
this.threshold = tableSizeFor(initialCapacity);
}
/**
* Returns a power of two size for the given target capacity.
*/
static final int tableSizeFor(int cap) {
int n = cap - 1;
//先移位再或运算,最终保证返回值是2的整数幂
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n = MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
/**
* 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);
}
/**
* 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
}
//将map直接放入hashmap中
public HashMap(Map extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
final void putMapEntries(Map extends K, ? extends V> m, boolean evict) {
int s = m.size();
if (s > 0) {
if (table == null) { // pre-size
float ft = ((float)s / loadFactor) + 1.0F;
int t = ((ft float)MAXIMUM_CAPACITY) ?
(int)ft : MAXIMUM_CAPACITY);
if (t > threshold)
threshold = tableSizeFor(t);
}
else if (s > threshold)
resize();
for (Map.Entry extends K, ? extends V> e : m.entrySet()) {
K key = e.getKey();
V value = e.getValue();
putVal(hash(key), key, value, false, evict);
}
}
}
/**
* Basic hash bin node, used for most entries. (See below for
* TreeNode subclass, and in LinkedMyHashMap for its Entry subclass.)
*/
在hashMap的结构图中,hash数组就是用Node型数组实现的,许多Node类通过next组成链表,key、value实际存储在Node内部类中。
public static class Node
/**
* Associates the specified value with the specified key in thismap.
* If the map previously contained a mapping for the key, the old
* value is replaced.
*
*/
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
//key的值为null时,hash值返回0,对应的table数组中的位置是0
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
/**
* Implements Map.put and related methods
*
* @param hash hash for key
* @param key the key
* @param value the value to put
* @param onlyIfAbsent if true, don‘t change existing value
* @param evict if false, the table is in creation mode.
* @return previous value, or null if none
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node
/**
* Initializes or doubles table size. If null, allocates in
* accord with initial capacity target held in field threshold.
* Otherwise, because we are using power-of-two expansion, the
* elements from each bin must either stay at same index, or move
* with a power of two offset in the new table.
*
* @return the table
*/
final Node
文章标题:Java源码——HashMap的源码分析及原理学习记录
文章链接:http://soscw.com/index.php/essay/101357.html