一、简介

HashMap是开发中用的非常多的一个哈希表数据结构,HashMap类位于java.util包中。下面对HashMap做一个简介:

  • HashMap是一个用于存储Key-Value键值对的集合,底层使用数组 + 链表 + 红黑树实现,每一个键值对也叫做Entry,这些键值对(Entry)分散存储在一个数组中。HashMap数组的每一个元素不止是一个Entry对象,也是一个链表的头节点。每一个Entry对象通过Next指针指向它的下一个Entry节点。
  • HashMap最多只允许一条记录的键为null,允许多条记录的值为null。HashMap 类大致相当于Hashtable,只是它是不同步,允许为空。
  • 一般来说,默认的负载因子(0.75)在时间和空间成本之间提供了很好的权衡。更高的值减少了空间开销,但是增加了查找成本(反映在HashMap类的大多数操作中,包括get和put)。在设置map的初始容量时,应该考虑map中条目的期望数量及其负载因子,从而最小化rehash操作的数量。如果初始容量大于条目的最大数量除以负载因子,则不会发生重排操作。
  • 注意HashMap实现不是同步的。如果多个线程同时访问一个散列映射,可能存在线程安全问题。
  • 为了防止意外对映射的非同步访问,可以使用:Map m = Collections.synchronizedMap(new HashMap(...));

下图为HashMap结构简化图:

下面我们去看看HashMap在JDK1.8中的实现:

  • HashMap继承自抽象类AbstractMap,并且实现了Map<K,V>, Cloneable, Serializable接口:
public class HashMap<K,V> extends AbstractMap<K,V>implements Map<K,V>, Cloneable, Serializable {//......}
  • 属性
//序列化ID
private static final long serialVersionUID = 362498820763181265L;//默认的初始容量-必须是2的幂。
//1<<4左移4位,相当于2 * 2 * 2 * 2 = 16,即默认初始容量为16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;//最大容量,如果更高的值是由任何一个带有参数的构造函数隐式指定的,则使用该值。必须是2的幂<= 1<<30。
static final int MAXIMUM_CAPACITY = 1 << 30;//在构造函数中没有指定时使用的负载因子。
//默认负载因子为0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;//使用树而不是列表的容器计数阈值。当向至少有这么多节点的bin中添加元素时,bin将被转换为树。该值必须大于2,并且应该至少为8,以便与树木移除时关于收缩后转换回普通桶的假设相吻合。
//当链表的长度大于8时,并且桶的数量大于64(MIN_TREEIFY_CAPACITY)时,链表会转换为红黑树结构
static final int TREEIFY_THRESHOLD = 8;//在调整大小操作期间取消(拆分)存储的存储库计数阈值。应小于TREEIFY_THRESHOLD,且最多6个网格进行收缩检测下去除。
//当链表的长度小于6时,红黑树结构会转换为链表结构
static final int UNTREEIFY_THRESHOLD = 6;//可以对容器进行treeified的最小表容量。(否则,如果一个bin中有太多节点,就会重新调整表的大小。)至少4 * TREEIFY_THRESHOLD,以避免调整大小和treeification阀值之间的冲突。
static final int MIN_TREEIFY_CAPACITY = 64;//哈希表,第一次使用时初始化,并根据需要调整大小。当分配时,长度总是2的幂。(在某些操作中,我们还允许长度为零,以允许当前不需要的引导机制。)
//哈希表结构(链表散列:数组+链表)实现当链表超过8且数据总量超过64才会转红黑树
transient Node<K,V>[] table;//保存缓存entrySet ()。注意,AbstractMap字段用于keySet()和values()。
transient Set<Map.Entry<K,V>> entrySet;//map包含的键值映射的数量(实际存在的键值对数量)
transient int size;//用来记录HashMap内部结构发生变化的次数,主要用于迭代的快速失败
transient int modCount;//所能容纳的key-value对的最大数量
//在数组定义好长度之后,负载因子越大,所能容纳的键值对个数越多
int threshold;//哈希表的加载因子。
final float loadFactor;
  • Node类:基本的哈希bin节点,实质就是键值对。
//实现 Map.Entry<K,V>接口,本质是就是一个映射(键值对)
static class Node<K,V> implements Map.Entry<K,V> {//哈希值,用来定位数组索引位置final int hash;//键final K key;//值V value;//链表的下一个NodeNode<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; }//toString()输出方法public final String toString() { return 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<?,?>Map.Entry<?,?> e = (Map.Entry<?,?>)o;//键相同并且对应的值也相同,返回true,否则返回falseif (Objects.equals(key, e.getKey()) &&Objects.equals(value, e.getValue()))return true;}return false;}
}
  • 构造方法
//使用指定的初始容量和负载因子构造一个空的HashMap。
public HashMap(int initialCapacity, float loadFactor) {//如果初始容量小于0,抛出非法参数异常if (initialCapacity < 0)throw new IllegalArgumentException("Illegal initial capacity: " +initialCapacity);//初始容量最大只能为2的30次方,即1073741824if (initialCapacity > MAXIMUM_CAPACITY)initialCapacity = MAXIMUM_CAPACITY;//如果负载因子小于等于0或者为NaN,抛出非法参数异常if (loadFactor <= 0 || Float.isNaN(loadFactor))throw new IllegalArgumentException("Illegal load factor: " +loadFactor);this.loadFactor = loadFactor;//返回给定目标容量的两倍幂。this.threshold = tableSizeFor(initialCapacity);
}//返回给定目标容量的两次幂。 initialCapacity <= 2的n次幂
//假设cap = 12
static final int tableSizeFor(int cap) {// n = 12 - 1 = 11int n = cap - 1;// 0000 1011 = 11
//往右移一位: 0000 0101 = 5
// 或运算之后:0000 1111 = 15,所以此时n = 15n |= n >>> 1;//以此类推..n |= n >>> 2;n |= n >>> 4;n |= n >>> 8;n |= n >>> 16;return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}//构造一个空的HashMap,具有指定的初始容量和默认负载因子(0.75)。
//initialCapacity:初始化容量
public HashMap(int initialCapacity) {//使用默认负载因子0.75this(initialCapacity, DEFAULT_LOAD_FACTOR);
}//构造一个空的HashMap,默认初始容量(16)和默认负载因子(0.75)。
public HashMap() {//赋值默认负载因子this.loadFactor = DEFAULT_LOAD_FACTOR;
}//使用与指定的Map相同的映射构造一个新的HashMap。HashMap是使用默认负载因子(0.75)和足够容纳指定Map中的映射的初始容量创建的。
public HashMap(Map<? extends K, ? extends V> m) {//使用默认负载因子0.75this.loadFactor = DEFAULT_LOAD_FACTOR;putMapEntries(m, false);
}//evict: 在最初构造这个映射时为false,否则为true
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {int s = m.size();//判断当前map的大小if (s > 0) {//判断当前线性表Node<K,V>[] table数组是否为空if (table == null) { // pre-size// 容量 = (容量/ 负载因子) + 1// 初始化容量float ft = ((float)s / loadFactor) + 1.0F;// 判断是否超过最大容量int t = ((ft < (float)MAXIMUM_CAPACITY) ?(int)ft : MAXIMUM_CAPACITY);if (t > threshold)//返回给定目标容量t的两倍幂。threshold = tableSizeFor(t);}else if (s > threshold)//扩容操作resize();//循环遍历当前map中所有的键值对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);}}
}
  • hash()
//计算key的哈希值
static final int hash(Object key) {int h;//如果key不为空,使用key的哈希码与(key哈希码进行右移16位)进行异或运算(相同为0,相异为1)//目的是提高hashcode的随机性,有效减小hash冲突率(让高位参与下标的计算)return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
  • resize()
//扩容
//生成一个新的数组,原来的所有数据需要重新计算哈希码值重新分配到新的数组,所以扩容的操作非常消耗性能.
//如果数据很大的情况下,扩展时将会带来性能的损失,在性能要求很高的地方,这种损失很可能很致命。
final Node<K,V>[] resize() {//老的线性表初始值Node<K,V>[] oldTab = table;//扩容前:旧容量int oldCap = (oldTab == null) ? 0 : oldTab.length;//扩容前:旧阈值int oldThr = threshold;//扩容后:newCap新容量, newThr新阈值int newCap, newThr = 0;//判断旧容量是否大于0if (oldCap > 0) {//是否超过最大容量1073741824if (oldCap >= MAXIMUM_CAPACITY) {//超过最大容量,无法扩容,将阈值设置为整数最大值,直接返回旧容量threshold = Integer.MAX_VALUE;return oldTab;}//如果新容量(旧容量的2倍)是否大于最大容量,并且旧容量是否大于等于默认容量16else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY)//设置新阈值是旧阈值的2倍newThr = oldThr << 1; // double threshold}else if (oldThr > 0) // initial capacity was placed in threshold//如果旧容量小于0,并且旧阈值大于0,则设置新容量为旧阈值newCap = oldThr;else {               // zero initial threshold signifies using defaults//初始阈值为零表示使用默认值//新容量为默认容量 = 16newCap = DEFAULT_INITIAL_CAPACITY;//新阈值 = 默认负载因子 * 默认初始化容量 = 0.75 *16 = 12//比如table 数组大小为 16,装载因子为 0.75 时,threshold 就是12,当 table 的实际大小超过 12 时,table就需要动态扩容;newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);}//如果新阈值为0,if (newThr == 0) {//阈值 = 默认负载因子 * 新容量float ft = (float)newCap * loadFactor;//新阈值最大只能为Integer.MAX_VALUEnewThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?(int)ft : Integer.MAX_VALUE);}//为当前的容量阈值赋值threshold = newThr;@SuppressWarnings({"rawtypes","unchecked"})//创建一个新容量newCap的数组Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];table = newTab;//如果旧线性表数组不为空,需要对原来的元素进行重新定位if (oldTab != null) {for (int j = 0; j < oldCap; ++j) {Node<K,V> e;//获取j位置的元素if ((e = oldTab[j]) != null) {oldTab[j] = null;//判断原来j位置上是否存在元素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 { // preserve order// 链表优化重hash的代码块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)//不存在尾部元素,则将e作为头部元素loHead = e;else//存在尾部元素,则将e拼接上尾部loTail.next = e;loTail = e;}else {//元素在扩容后的位置=原始位置+扩容后的旧位置。if (hiTail == null)hiHead = e;elsehiTail.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;
}

二、常用API

【a】size()、isEmpty()、get()

//返回map中的键值映射的数目
public int size() {return size;
}//如果此映射不包含键值映射,则返回true,否则返回false
public boolean isEmpty() {return size == 0;
}//返回指定键映射到的值,如果此映射不包含键的映射,返回null。
public V get(Object key) {Node<K,V> e;return (e = getNode(hash(key), key)) == null ? null : e.value;
}//根据哈希码和key返回对应的节点
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) {if (first.hash == hash && // always check first node((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);//顺序遍历链表,equals()方法查找相同 Node 链表中 K 值对应的 V 值。do {if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))return e;} while ((e = e.next) != null);}}return null;
}

【b】containsKey()

//如果此映射包含指定键的映射,则返回<tt>true</tt>。
public boolean containsKey(Object key) {return getNode(hash(key), key) != null;
}

【c】put()

//将指定值与此映射中的指定键关联。如果映射之前包含键的映射,则替换旧值。
public V put(K key, V value) {//根据key获取hashcode值return putVal(hash(key), key, value, false, true);
}//onlyIfAbsent: false   evict: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;//判断线性表table是否为空或者长度为0if ((tab = table) == null || (n = tab.length) == 0)//扩容,初始化table的长度n = (tab = resize()).length;//假设n = 16,则((n - 1) & hash) = (15 & hash) = i//计算出来的i就是元素在线性表数组tab中的下标// p = tab[i] = Node<K,V>// 其实p相当于是链表。// (n - 1) & hash其实是求余运算,为了提高计算性能if ((p = tab[i = (n - 1) & hash]) == null)//创建新节点,直接放在tab[i]位置tab[i] = newNode(hash, key, value, null);else {//tab[i]链表上已经存在元素Node<K,V> e; K k;//判断p的hash是否等于当前的hash值, 节点key存在,直接覆盖valueif (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))//判断table[i]中的元素是否与插入的key一样,若相同那就直接使用插入的值p替换掉旧的值e。e = p;//判断是否是树结构(JDK1.8提出红黑树优化方案)        else if (p instanceof TreeNode)//基于红黑树的插入逻辑, 直接在树中插入键值对e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);else {//链表插入元素for (int binCount = 0; ; ++binCount) {//判断p的下一个元素是否为空if ((e = p.next) == null) {//新创建节点,设置p的下一个元素为新节点p.next = newNode(hash, key, value, null);//判断当前链表的数量是否大于树结构的阈值if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st//转换数据结构,将链表转换为红黑树,目的是优化查询性能//当碰撞导致链表大于 TREEIFY_THRESHOLD = 8 时,就把链表转换成红黑树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 keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}//修改次数+1++modCount;//判断当前数组大小是否大于阈值,如果大于阈值,需要进行扩容if (++size > threshold)resize();afterNodeInsertion(evict);return null;
}//将指定映射的所有映射复制到此映射。这些映射将替换当前指定映射中任何键的映射。
public void putAll(Map<? extends K, ? extends V> m) {putMapEntries(m, true);
}final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {int s = m.size();if (s > 0) {if (table == null) { // pre-sizefloat 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);}}
}

【d】remove()

//如果存在,则从此映射中删除指定键的映射。
public V remove(Object key) {Node<K,V> e;return (e = removeNode(hash(key), key, null, false, true)) == null ?null : e.value;
}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;//判断线性表是否为空或者长度是否为0// index = (n - 1) & hash// p = tab[index] 是否为空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;//hash相同并且key相同,说明hash没有冲突的情况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 {//链表的删除//循环遍历链表,定位要删除的节点node do {if (e.hash == hash &&((k = e.key) == key ||(key != null && key.equals(k)))) {node = e;break;}p = e;} while ((e = e.next) != null);}}//node就是需要删除的元素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)//链表的删除节点tab[index] = node.next;else//将p元素的下一个节点指向node节点的下一个节点,这样就跳过了node节点,达到删除的效果p.next = node.next;++modCount;--size;afterNodeRemoval(node);return node;}}return null;
}

【e】clear()

//从该映射中删除所有映射。这个调用返回后,映射将为空。
public void clear() {Node<K,V>[] tab;modCount++;//如果table不为空时if ((tab = table) != null && size > 0) {//将长度置为0size = 0;//循环遍历table,将每个Node置空for (int i = 0; i < tab.length; ++i)tab[i] = null;}
}

【f】containsValue()

//如果此映射将一个或多个键映射到指定的值,则返回true。
public boolean containsValue(Object value) {Node<K,V>[] tab; V v;//当table不为空时if ((tab = table) != null && size > 0) {//循环tabfor (int i = 0; i < tab.length; ++i) {//依次拿出每一个Node<K,V>节点,然后再遍历链表,拿出value挨个比较for (Node<K,V> e = tab[i]; e != null; e = e.next) {if ((v = e.value) == value ||(value != null && value.equals(v)))return true;}}}//如果table为空,直接返回falsereturn false;
}

【g】keySet()

//返回包含在此映射中的键key的Set集合
public Set<K> keySet() {Set<K> ks = keySet;if (ks == null) {//重新new一个KeySetks = new KeySet();keySet = ks;}return ks;
}final class KeySet extends AbstractSet<K> {public final int size()                 { return size; }public final void clear()               { HashMap.this.clear(); }public final Iterator<K> iterator()     { return new KeyIterator(); }public final boolean contains(Object o) { return containsKey(o); }public final boolean remove(Object key) {return removeNode(hash(key), key, null, false, true) != null;}public final Spliterator<K> spliterator() {return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);}public final void forEach(Consumer<? super K> action) {Node<K,V>[] tab;if (action == null)throw new NullPointerException();if (size > 0 && (tab = table) != null) {int mc = modCount;for (int i = 0; i < tab.length; ++i) {for (Node<K,V> e = tab[i]; e != null; e = e.next)action.accept(e.key);}if (modCount != mc)throw new ConcurrentModificationException();}}
}

【h】values()

//返回包含在map中的值的集合。
public Collection<V> values() {Collection<V> vs = values;if (vs == null) {vs = new Values();values = vs;}return vs;
}final class Values extends AbstractCollection<V> {public final int size()                 { return size; }public final void clear()               { HashMap.this.clear(); }public final Iterator<V> iterator()     { return new ValueIterator(); }public final boolean contains(Object o) { return containsValue(o); }public final Spliterator<V> spliterator() {return new ValueSpliterator<>(HashMap.this, 0, -1, 0, 0);}public final void forEach(Consumer<? super V> action) {Node<K,V>[] tab;if (action == null)throw new NullPointerException();if (size > 0 && (tab = table) != null) {int mc = modCount;for (int i = 0; i < tab.length; ++i) {for (Node<K,V> e = tab[i]; e != null; e = e.next)action.accept(e.value);}if (modCount != mc)throw new ConcurrentModificationException();}}
}

【i】entrySet()

//返回包含在map中的键值对的映射的集合。
public Set<Map.Entry<K,V>> entrySet() {Set<Map.Entry<K,V>> es;return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
}final class EntrySet extends AbstractSet<Map.Entry<K,V>> {public final int size()                 { return size; }public final void clear()               { HashMap.this.clear(); }public final Iterator<Map.Entry<K,V>> iterator() {return new EntryIterator();}public final boolean contains(Object o) {if (!(o instanceof Map.Entry))return false;Map.Entry<?,?> e = (Map.Entry<?,?>) o;Object key = e.getKey();Node<K,V> candidate = getNode(hash(key), key);return candidate != null && candidate.equals(e);}public final boolean remove(Object o) {if (o instanceof Map.Entry) {Map.Entry<?,?> e = (Map.Entry<?,?>) o;Object key = e.getKey();Object value = e.getValue();return removeNode(hash(key), key, value, true, true) != null;}return false;}public final Spliterator<Map.Entry<K,V>> spliterator() {return new EntrySpliterator<>(HashMap.this, 0, -1, 0, 0);}public final void forEach(Consumer<? super Map.Entry<K,V>> action) {Node<K,V>[] tab;if (action == null)throw new NullPointerException();if (size > 0 && (tab = table) != null) {int mc = modCount;for (int i = 0; i < tab.length; ++i) {for (Node<K,V> e = tab[i]; e != null; e = e.next)action.accept(e);}if (modCount != mc)throw new ConcurrentModificationException();}}
}

三、总结

总结一下HashMap需要搞懂的几点,也是面试常问到的点:

  • 初始容量是多少?为啥初始容量一定要是2的n次幂? (高位参与运算)
  • hash()求哈希码函数的实现方式? (key.hashcode() ^ (key.hashcode() >>> 16))
  • resize() 自动扩容的处理流程?(创建新数组,将旧数组的数据移动到新数组中,rehash操作)
  • hash冲突时的解决方案(链表-拉链法)? (链表尾插)
  • put() 获取数组下标的方法? hash(key) & (length - 1)? (确保hash尽量分散均匀)

本文是笔者第一次阅读HashMap源码时的一些总结,其中涉及到红黑树部分的暂时还没有去研究,后面学习完数据结构以及算法之后会重新再补充这部分的内容,HashMap源码相对比较复杂,得多看几遍才能领悟其中设计的妙处,希望下次再读的时候会有一些新的见解和认识。

HashMap源码阅读相关推荐

  1. HashMap源码阅读笔记

    HashMap是Java编程中常用的集合框架之一. 利用idea得到的类的继承关系图可以发现,HashMap继承了抽象类AbstractMap,并实现了Map接口(对于Serializable和Clo ...

  2. HashMap源码阅读启读

    上一篇文章让是我对整个java集合的基础认识,让我能够使用集合,下面我将对其中几个重要集合的(ArrayList,LinkList,HashMap)源码进行阅读. 一.HashMap是什么? 弄清楚这 ...

  3. HashMap 源码阅读

    前言 之前读过一些类的源码,近来发现都忘了,再读一遍整理记录一下.这次读的是 JDK 11 的代码,贴上来的源码会去掉大部分的注释, 也会加上一些自己的理解. Map 接口 这里提一下 Map 接口与 ...

  4. HashMap jdk1.7源码阅读与解析

    转载自  HashMap源码阅读与解析 一.导入语 HashMap是我们最常见也是最长使用的数据结构之一,它的功能强大.用处广泛.而且也是面试常见的考查知识点.常见问题可能有HashMap存储结构是什 ...

  5. jdk源码阅读-HashMap

    前置阅读: jdk源码阅读-Map : http://www.cnblogs.com/ccode/p/4645683.html 在前置阅读的文章里,已经提到HashMap是基于Hash表实现的,所以在 ...

  6. mybatis源码阅读

    说下mybatis执行一个sql语句的流程 执行语句,事务等SqlSession都交给了excutor,excutor又委托给statementHandler SimpleExecutor:每执行一次 ...

  7. Java源码详解二:HashMap源码分析--openjdk java 11源码

    文章目录 HashMap.java介绍 1.HashMap的get和put操作平均时间复杂度和最坏时间复杂度 2.为什么链表长度超过8才转换为红黑树 3.红黑树中的节点如何排序 本系列是Java详解, ...

  8. HashMap 源码详细分析(JDK1.8)

    1. 概述 本篇文章我们来聊聊大家日常开发中常用的一个集合类 - HashMap.HashMap 最早出现在 JDK 1.2中,底层基于散列算法实现.HashMap 允许 null 键和 null 值 ...

  9. Java8 Hashtable 源码阅读

    一.Hashtable 概述 Hashtable 底层基于数组与链表实现,通过 synchronized 关键字保证在多线程的环境下仍然可以正常使用.虽然在多线程环境下有了更好的替代者 Concurr ...

  10. Java8 LinkedHashMap 源码阅读

    如果你对 HashMap 的源码有了解的话,只需要一图就能知道 LinkedHashMap 的原理了,但是具体的实现细节还是需要去读一下源码. 一.LinkedHashMap 简介 1.1 继承结构 ...

最新文章

  1. 在CentOS 6.8 x86_64上利用devtoolset搭建GCC 4.9.2和5.3.1开发环境
  2. bash-shell中使用的特殊字符总结
  3. shell的if和else
  4. C51 动态数码管 个人笔记
  5. 服务器不删档的设置_CCD2服务器已经开启,准备好刷刷刷了吗
  6. seurat提取表达矩阵_本周最新文献速递20200719
  7. Linux Mint 安装网易云音乐
  8. 延时等待的gcode
  9. tsp 近似算法 matlab,TSP问题—近似算法
  10. Django框架零基础入门
  11. 全外显子数据分析流程
  12. 解决微信支付、微信一键登陆在安卓10以上无法调起问题
  13. php超链接打不开了,excel超链接无法打开怎么办
  14. 按教师名单分配学生抽签程序
  15. Android Studio完成简单UI设计
  16. org.apache.hadoop.hive.metastore.HiveMetaException: Failed to get schema version, Cause:Table ‘hive.
  17. 标准差-standard deviation
  18. dann(胆囊结石的疼痛特点包括)
  19. Oriented RepPoints for Aerial Object Detection
  20. 【树莓派】保姆级教程,如何优雅的使用ssh连接树莓派

热门文章

  1. 计算机操作系统详细学习笔记(一):计算机操作系统概述
  2. POJ 3667 Hotel (线段树区间合并)
  3. POJ - 2533(动态规划 —— 最长子序列)
  4. 应急响应的整体思路一
  5. 美图欣赏,转载[原文链接http://toutiao.com/a4001258776/]
  6. 快速突破面试算法之数学运算篇
  7. php改时间戳,如何实现转换php时间戳
  8. 经典教程 | 基于Spark GraphX实现微博二度关系推荐
  9. 攻防世界 web2 write up
  10. 创新课程管理系统——测试心得