jdk1.8.0_45源码解读——HashMap的实现

一、HashMap概述

  HashMap是基于哈希表的Map接口实现的,此实现提供所有可选的映射操作。存储的是<key,value>对的映射,允许多个null值和一个null键。但此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
   除了HashMap是非同步以及允许使用null外,HashMap 类与 Hashtable大致相同。
 
 此实现假定哈希函数将元素适当地分布在各桶之间,可为基本操作(getput)提供稳定的性能。迭代collection 视图所需的时间与 HashMap 实例的“容量”(桶的数量)及其大小(键-值映射关系数)成比例。所以,如果迭代性能很重要,则不要将初始容量设置得太高(或将加载因子设置得太低)。

  HashMap 的实例有两个参数影响其性能:初始容量加载因子容量 是哈希表中桶的数量,初始容量只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。

  通常,默认加载因子 (0.75) 在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查询成本(在大多数 HashMap 类的操作中,包括 getput 操作,都反映了这一点)。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少 rehash 操作次数。如果初始容量大于最大条目数除以加载因子,则不会发生 rehash 操作。
  注意,此实现不是同步的。
如果多个线程同时访问一个HashMap实例,而其中至少一个线程从结构上修改了列表,那么它必须保持外部同步。这通常是通过同步那些用来封装列表的
对象来实现的。但如果没有这样的对象存在,则应该使用{@link Collections#synchronizedMap
Collections.synchronizedMap}来进行“包装”,该方法最好是在创建时完成,为了避免对映射进行意外的非同步操作。

Map m = Collections.synchronizedMap(new HashMap(...));

二、HashMap的数据结构

  HashMap实际上是一个“链表的数组”的数据结构,每个元素存放链表头结点的数组,即数组和链表的结合体。

  从上图中可以看出,HashMap底层就是一个数组结构,数组中的每一项又是一个链表,链表中的每个元素为一个包含可以值,key哈希码,value值的Node节点。该数组的下标索引为key的哈希码。

三、HashMap源码解析

1.映射项Node<K,V>结构

   //实现Map.Entry<K,V>接口
static class Node<K,V> implements Map.Entry<K,V> {
final int hash; //hash码
final K key;
V value;
Node<K,V> next; //指向链表中下一个实例 Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
} public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; } //返回此映射项的哈希值:key值的哈希码与value值的哈希码按位异或的结果
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
} //用指定值替换对应于此项的值,并返回旧值
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
} //比较指定对象与此项的相等性
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}

2. HashMap类结构

//通过HahMap实现的接口可知,其支持所有映射操作,能被克隆,支持序列化
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable { private static final long serialVersionUID = 362498820763181265L; //默认初始容量16,必须为2的幂
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 //最大容量
static final int MAXIMUM_CAPACITY = 1 << 30; //默认加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f; //table是一个Node<K,V>[]数组类型,而Node<K,V>实际上就是一个元素值为<key,value>对的单向链表。
//哈希表的"key-value键值对"都是存储在Node<K,V>数组中的。
transient Node<K,V>[] table; //用来指向entrySet()返回的set集合
transient Set<Map.Entry<K,V>> entrySet; //HashMap的大小,即保存的键值对的数量
transient int size; //用来实现fail-fast机制的,记录HashMap结构化修改的次数
transient int modCount; //下次需扩容的临界值,size>=threshold就会扩容
//如果table数组没有被分配,则该值为初始容量值16;或若该值为0,也表明该值为初始容量值
int threshold; //加载因子
final float loadFactor; ......
}

HashMap包含了几个重要的成员变量:table, size, threshold, loadFactor。

(01) table是一个Node[]数组类型,而Node实际上就是一个单向链表。哈希表的"key-value键值对"都是存储在Node数组中的。

(02) size是HashMap的大小,它是HashMap保存的键值对的数量。

(03) threshold是HashMap的阈值,用于判断是否需要调整HashMap的容量。threshold的值="容量*加载因子",当HashMap中存储数据的数量达到threshold时(size>=threshold),就需要将HashMap的容量加倍。
(04) loadFactor就是加载因子。

注意:HashMap的数据结构和冲突处理方法?

通过HashMap数据存储数组Node<K,V>[] table,及数据节点Node<K,V>的数据结构可知:HashMap是实现"key-value键值对"的映射关系的,是通过“拉链法”解决哈希冲突的。简单的构造图如下所示:

  

3. 构造函数

HashMap提供了四种方式的构造器,可以构造一个带指定初始容量和加载因子的空HashMap,构造一个带指定初始容量和默认加载因子(0.75)的空 HashMap,构造一个默认初始容量为16和默认加载因子为0.75的空HashMap,以及构造一个包含指定Map的元素的HashMap,容量与指定Map容量相同,加载因子为默认的0.75。

    //找出“大于Capacity”的最小的2的幂,使Hash表的容量保持为2的次方倍
//算法的思想:通过使用逻辑运算来替代取余,这里有一个规律,就是当N为2的次方(Power of two),那么X%N==X&(N-1)。
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1; //>>> 无符号右移,高位补0
n |= n >>> 2; //a|=b的意思就是把a和b按位或然后赋值给a
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
} //构造一个带指定初始容量和加载因子的空HashMap
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;
this.threshold = tableSizeFor(initialCapacity);
} //构造一个带指定初始容量和默认加载因子(0.75)的空 HashMap
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
} //构造一个具有默认初始容量 (16)和默认加载因子 (0.75)的空 HashMap
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
} //构造一个映射关系与指定 Map相同的新 HashMap,容量与指定Map容量相同,加载因子为默认的0.75
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}

4.resize()方法

在HashMap的四种构造函数中并没有对其成员变量Node<K,V>[] table进行任何初始化的工作,那么HashMap是如何构造一个默认初始容量为16的空表的?该初始化的诱发条件是在向HashMap中添加第一对<key,value>时,通过put(K key, V value) -> putVal(hash(key), key, value, false, true) -> resize()方法。故HashMap中尤其重要的resize()方法主要实现了两个功能:

1.在table数组为null时,对其进行初始化,默认容量为16;

2.当tables数组非空,但需要调整HashMap的容量时,将hash表容量翻倍。

    //resize()方法作用有两种:1.初始化hash表的容量,为16; 2.将hash表容量翻倍
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table; //旧hash表
int oldCap = (oldTab == null) ? 0 : oldTab.length; //旧hash表容量
int oldThr = threshold; //旧hash表阈值
int newCap, newThr = 0; //新hash表容量与扩容临界值
//2.旧hash表非空,则表容量翻倍
if (oldCap > 0) {
//如果当前的hash表长度已经达到最大值,则不在进行调整
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
} //更新新hash表容量:翻倍
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
//更新扩容临界值
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
//1. 初始化hash表容量,设为默认值16
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
//创建一个初始容量为新hash表长度的newTab数组
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
//如果旧hash表非空,则按序将旧hash表中的元素重定向到新hash表
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e; //e按序指向oldTab数组中的元素,即每个链表中的头结点
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null) //如果链表只有一个头节点
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
//对链表进行秩序维护:因为我们使用的是两倍扩容的方法,所以每个桶里面的元素必须要么待在原来的
//索引所对应的位置,要么在新的桶中位置偏移两倍
else {
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}

5.查找

HashMap提供了get(Object key)、containsKey(Object key)、containsValue(Object value)这些查找键值对的方法。

    //返回指定key所映射的value;如果对于该键来说,此映射不包含任何映射关系,则返回 null
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
} final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) { //key的哈希值为数组下标
if (first.hash == hash && //检查第一个节点
((k = first.key) == key || (key != null && key.equals(k))))
return first;
//如果第一个节点不对,则向后检查
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
} //如果此映射包含对于指定键的映射关系,则返回 true。
public boolean containsKey(Object key) {
return getNode(hash(key), key) != null;
} //如果此映射将一个或多个键映射到指定值,则返回 true。
public boolean containsValue(Object value) {
Node<K,V>[] tab; V v;
if ((tab = table) != null && size > 0) {
//外层循环搜索数组
for (int i = 0; i < tab.length; ++i) {
//内层循环搜索链表
for (Node<K,V> e = tab[i]; e != null; e = e.next) {
if ((v = e.value) == value ||
(value != null && value.equals(v)))
return true;
}
}
}
return false;
}

6.添加

HashMap提供了put(K key, V value)、putAll(Map<? extends K, ? extends V> m)这些添加键值对的方法。

    /**
* 在此映射中关联指定值与指定键。如果该映射以前包含了一个该键的映射关系,则旧值被替换。
*
* @param key 指定值将要关联的键
* @param value 指定键将要关联的值
* @return 与 key关联的旧值;如果 key没有任何映射关系,则返回 null。(返回 null 还可能表示该映射之前将null与 key关联。)
*/
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
} /**
* 用于实现 Map.put()和相关的方法
*
* @param hash 键的hash码
* @param key 键
* @param value 值
* @param onlyIfAbsent if true, don't change existing value
* @param evict evict=false:表明该hash表处于初始化创建的过程中
* @return previous value, or null if none
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//此处分两种情况:1.当table为null时,用默认容量16初始化table数组;2.当table非空时
if ((tab = table) == null || (n = tab.length) == 0) //旧hash表为null或旧hash表长度为0
n = (tab = resize()).length; //初始化hash表的长度(16)
//此处又分为两种情况:1.key的hash值对应的那个节点为空;2.key的hash值对应的那个节点不为空
if ((p = tab[i = (n - 1) & hash]) == null) //该key的hash值对应的那个节点为空,即表示还没有元素被散列至此
tab[i] = newNode(hash, key, value, null); //则创建一个新的new Node<>(hash, key, value, next);
else {
//该key的hash值对应的那个节点不为空,先与链表上的第一个节点p比较
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)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; //向后查找
}
}
//若该key对应的value已经存在,则用新的value取代旧的value
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold) //如果加入该键值对后超过最大阀值,则进行resize操作
resize();
afterNodeInsertion(evict);
return null;
} //将指定映射的所有映射关系复制到此映射中,这些映射关系将替换此映射目前针对指定映射中所有键的所有映射关系。
public void putAll(Map<? extends K, ? extends V> m) {
putMapEntries(m, true);
} //用于帮助实现Map.putAll()方法 和Map构造器,当evict=false时表示构造初始HashMap。
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
int s = m.size(); //得到指定Map的大小
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); //得到按指定Map大小计算出的HashMap所需的容量
if (t > threshold) //如果容量大于阈值
threshold = tableSizeFor(t);
}
else if (s > threshold) //指定Map的大小>扩容临界值,扩容
resize();
//通过迭代器,将“m”中的元素逐个添加到HashMap中
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);
}
}
}

7.清空与删除

HashMap提供了remove(Object key)删除键值对、clear()清除所有键值对的方法。

    //从此映射中移除指定键的映射关系(如果存在)
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
} /**
* 用于实现 Map.remove()方法和其他相关的方法
*
* @param hash 键的hash值
* @param key 键
* @param value the value to match if matchValue, else ignored
* @param matchValue if true only remove if value is equal
* @param movable if false do not move other nodes while removing
* @return the node, or null if none
*/
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K,V>[] tab; Node<K,V> p; int n, index;
//table数组非空,键的hash值所指向的数组中的元素非空
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
Node<K,V> node = null, e; K k; V v; //node指向最终的结果结点,e为链表中的遍历指针
if (p.hash == hash && //检查第一个节点,如果匹配成功
((k = p.key) == key || (key != null && key.equals(k))))
node = p;
//如果第一个节点匹配不成功,则向后遍历链表查找
else if ((e = p.next) != null) {
if (p instanceof TreeNode)
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else {
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e;
break;
}
p = e;
} while ((e = e.next) != null);
}
}
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
else if (node == p) //删除node结点
tab[index] = node.next;
else
p.next = node.next;
++modCount;
--size;
afterNodeRemoval(node);
return node;
}
}
return null;
} //从此映射中移除所有映射关系
public void clear() {
Node<K,V>[] tab;
modCount++;
if ((tab = table) != null && size > 0) {
size = 0;
for (int i = 0; i < tab.length; ++i)
tab[i] = null;
}
}

8.其他公开的方法

size()、isEmpty()、clone()

    //返回此映射中的键-值映射关系数
public int size() {
return size;
} //如果此映射不包含键-值映射关系,则返回 true
public boolean isEmpty() {
return size == 0;
} //返回此 HashMap 实例的浅表副本
public Object clone() {
HashMap<K,V> result;
try {
result = (HashMap<K,V>)super.clone(); //调用父类clone方法
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
result.reinitialize();
result.putMapEntries(this, false); //将此HashMap元素放入result中
return result;
}

支持序列化的写入函数writeObject(java.io.ObjectOutputStream s)和读取函数readObject(java.io.ObjectInputStream s):

    //java.io.Serializable的写入函数,将HashMap的“总的容量,实际容量,所有的Entry”都写入到输出流中
private void writeObject(java.io.ObjectOutputStream s)
throws IOException {
int buckets = capacity();
// Write out the threshold, loadfactor, and any hidden stuff
s.defaultWriteObject();
s.writeInt(buckets);
s.writeInt(size);
internalWriteEntries(s);
} //java.io.Serializable的读取函数:根据写入方式读出,将HashMap的“总的容量,实际容量,所有的Entry”依次读出
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException {
// Read in the threshold (ignored), loadfactor, and any hidden stuff
s.defaultReadObject();
reinitialize();
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new InvalidObjectException("Illegal load factor: " +
loadFactor);
s.readInt(); // Read and ignore number of buckets
int mappings = s.readInt(); // Read number of mappings (size)
if (mappings < 0)
throw new InvalidObjectException("Illegal mappings count: " +
mappings);
else if (mappings > 0) { // (if zero, use defaults)
// Size the table using given load factor only if within
// range of 0.25...4.0
float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f);
float fc = (float)mappings / lf + 1.0f;
int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ?
DEFAULT_INITIAL_CAPACITY :
(fc >= MAXIMUM_CAPACITY) ?
MAXIMUM_CAPACITY :
tableSizeFor((int)fc));
float ft = (float)cap * lf;
threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ?
(int)ft : Integer.MAX_VALUE);
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] tab = (Node<K,V>[])new Node[cap];
table = tab; // Read the keys and values, and put the mappings in the HashMap
for (int i = 0; i < mappings; i++) {
@SuppressWarnings("unchecked")
K key = (K) s.readObject();
@SuppressWarnings("unchecked")
V value = (V) s.readObject();
putVal(hash(key), key, value, false, false);
}
}
} // Called only from writeObject, to ensure compatible ordering.
void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException {
Node<K,V>[] tab;
if (size > 0 && (tab = table) != null) {
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next) {
s.writeObject(e.key);
s.writeObject(e.value);
}
}
}

四、HashMap的键集、值集和键值对集

 HashMap是一个很有用的集合框架,通过下述三个方法可以分别得到HashMap的键集、值集和键值对集。

  键集:Set<K> keySet()

  值集:Collection<K> values()  

  键值对集:Set<Map.Entry<K,V>> entrySet()

 得到这三种collection 视图后,可以使用Iterator迭代器进行遍历。

【感谢】

Java集合之HashMap源码分析

Java 集合系列10之 HashMap详细介绍(源码解析)和使用示例

深入Java集合学习系列:HashMap的实现原理

jdk1.8.0_45源码解读——HashMap的实现的更多相关文章

  1. jdk1.8.0_45源码解读——HashSet的实现

    jdk1.8.0_45源码解读——HashSet的实现 一.HashSet概述 HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持.主要具有以下的特点: 不保证set的迭代顺 ...

  2. jdk1.8.0_45源码解读——Set接口和AbstractSet抽象类的实现

    jdk1.8.0_45源码解读——Set接口和AbstractSet抽象类的实现 一. Set架构 如上图: (01) Set 是继承于Collection的接口.它是一个不允许有重复元素的集合.(0 ...

  3. jdk1.8.0_45源码解读——Map接口和AbstractMap抽象类的实现

    jdk1.8.0_45源码解读——Map接口和AbstractMap抽象类的实现 一. Map架构 如上图:(01) Map 是映射接口,Map中存储的内容是键值对(key-value).(02) A ...

  4. jdk1.8.0_45源码解读——LinkedList的实现

    jdk1.8.0_45源码解读——LinkedList的实现 一.LinkedList概述 LinkedList是List和Deque接口的双向链表的实现.实现了所有可选列表操作,并允许包括null值 ...

  5. jdk1.8.0_45源码解读——ArrayList的实现

    jdk1.8.0_45源码解读——ArrayList的实现 一.ArrayList概述 ArrayList是List接口的可变数组的实现.实现了所有可选列表操作,并允许包括 null 在内的所有元素. ...

  6. 【JDK1.8】JDK1.8集合源码阅读——HashMap

    一.前言 笔者之前看过一篇关于jdk1.8的HashMap源码分析,作者对里面的解读很到位,将代码里关键的地方都说了一遍,值得推荐.笔者也会顺着他的顺序来阅读一遍,除了基础的方法外,添加了其他补充内容 ...

  7. JDK1.7.0_45源码阅读<java.lang.Boolean>

    本文适合的人群 其实感觉写这个标题的内容没有必要,只要你觉得对你有帮助那么就适合你,对你没帮助那么就不适合你.毕竟我不是专业作者,但咱会尽力的.其实最重要的一点是我不希望浪费您宝贵时间. 简要把内容在 ...

  8. 学习JDK1.8集合源码之--HashMap

    1. HashMap简介 HashMap是一种key-value结构存储数据的集合,是map集合的经典哈希实现. HashMap允许存储null键和null值,但null键最多只能有一个(HashSe ...

  9. 源码解读—HashMap

    什么是HashMap ? hashMap是用什么基础数据结构实现的?HashMap是如何解决hashCode冲突的? hashMap的基础容器是数组+链表(transient Entry[] tabl ...

随机推荐

  1. unity中camera摄像头控制详解

    目录 1. 缘起 2. 开发 2.1. 建立项目 2.2. 旋转 2.2.1. 四元数 2.3. 移动 2.3.1. 向量操作 2.4. 镜头拉伸 2.5. 复位 2.6. 优化 1 缘起 我们的产品 ...

  2. Java收发邮件过程中具体的功能是怎么实现的

    SMTP协议 用户连上邮件服务器后,要想给它发送一封电子邮件,需要遵循一定的通迅规则,SMTP协议就是用于定义这种通讯规则的. 因而,通常我们也把处理用户smtp请求(邮件发送请求)的邮件服务器称之为 ...

  3. ace how to guide

    Configuring the editor there are several ways to pass configuration to Ace 有几种方法可以将配置传递给ace // pass ...

  4. Android的发展历程及搭建

    Android的发展历程: 对于Android我比较不熟悉,因为我的第一只手机就是iphone,我没用过Android系统,但在中国使用带有Android系统的手机的人数是最多的,所以我想学习Andr ...

  5. RYU 的选择以及安装

    RYU 的选择以及安装 由于近期的项目需求,不得已得了解一下控制器内部发现拓扑原理,由于某某应用中的控制器介绍中使用的RYU,所以打算把RYU装一下试试.出乎意料的是,RYU竟是我之前装过最最轻便的控 ...

  6. 关于断言(Assert)

    断言,字面上的意思大致是十分肯定的说,也就是说我们相信这个结果是真的.如果我们的断言不为真,那这个这个结果就和我们预期的结果不一样.在编程上同理,如果程序运行出来的结果和你想要的结果不一致,那你的程序 ...

  7. 【Leetcode】209. Minimum Size Subarray Sum

    Question: Given an array of n positive integers and a positive integer s, find the minimal length of ...

  8. jar 命令详解

    jar 是随 JDK 安装的,在 JDK 安装目录下的 bin 目录中,Windows 下文件名为 jar.exe,Linux 下文件名为 jar.它的运行需要用到 JDK 安装目录下 lib 目录中 ...

  9. Windows10 版本说明 From wiki 20190104

    Windows版本说明 文字版本的: PC版本历史[编辑] 索引:       旧版本       旧版本,受支援       最新版本       最新预览版本 Version 1507(Windo ...

  10. 2 引入jquery和boot

    vue引入bootstrap——webpack https://blog.csdn.net/wild46cat/article/details/77662555(copy) 想要在vue中引入boot ...