Java HashMap源码分析
貌似HashMap跟ConcurrentHashMap是面试经常考的东西,抽空来简单分析下它的源码
构造函数
- /**
- * Constructs an empty <tt>HashMap</tt> with the default initial capacity
- * (16) and the default load factor (0.75).
- */
- public HashMap() {
- this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
- }
- /**
- * Constructs an empty <tt>HashMap</tt> 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 <tt>HashMap</tt> with the specified initial
- * capacity and load factor.
- *
- * @param initialCapacity the initial capacity
- * @param loadFactor the load factor
- * @throws IllegalArgumentException if the initial capacity is negative
- * or the load factor is nonpositive
- */
- 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);
- }
第二个构造函数是调用了第三个构造函数,第三个构造函数是用用户给定的初始容量和装填因子,初始化threshold和装填因子两个变量,而threshold在代码中的描述如下:
- /**
- * The next size value at which to resize (capacity * load factor).
- *
- * @serial
- */
- // (The javadoc description is true upon serialization.
- // Additionally, if the table array has not been allocated, this
- // field holds the initial array capacity, or zero signifying
- // DEFAULT_INITIAL_CAPACITY.)
- int threshold;
从英语的字面意思上看,是指下一次map resize之后的大小,而计算的方法是通过tableSizeFor得到
- /**
- * Returns a power of two size for the given target capacity.
- */
- static final int tableSizeFor(int cap) {
- int n = cap - 1;
- n |= 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;
- }
tableSizeFor方法,是将给定的初始容量格式化成2的幂的方法,比如输入4,得到4,输入7得到8。
常用的关于HashMap的操作,主要是put(增,改),get(查),remove(删),isEmpty(查)
依次来看,首先观察下HashMap是怎么往里面(put)加入数据的:
- /**
- * Associates the specified value with the specified key in this map.
- * If the map previously contained a mapping for the key, the old
- * value is replaced.
- *
- * @param key key with which the specified value is to be associated
- * @param value value to be associated with the specified key
- * @return the previous value associated with <tt>key</tt>, or
- * <tt>null</tt> if there was no mapping for <tt>key</tt>.
- * (A <tt>null</tt> return can also indicate that the map
- * previously associated <tt>null</tt> with <tt>key</tt>.)
- */
- public V put(K key, V value) {
- return putVal(hash(key), key, value, false, true);
- }
- /**
- * 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<K,V>[] tab; Node<K,V> p; int n, i;
- if ((tab = table) == null || (n = tab.length) == 0)
- n = (tab = resize()).length;
- if ((p = tab[i = (n - 1) & hash]) == null)
- tab[i] = newNode(hash, key, value, null);
- else {
- 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;
- }
- }
- 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();
- afterNodeInsertion(evict);
- return null;
- }
先来读一下put的函数说明,它是把特定的值(value)和特定的键(key)关联在map中,如果map中之前就已经包含了key,那么之前key对应的值就会被替代
返回的值表示:key之前对应的value值或者null。其中返回null值,有两种情况,1.之前在map中没有这个key存在 2.这个key值之前对应的value值就是null。
具体的代码实现,是调用了putVal方法,因此,接下来查看下putVal方法。
在看putVal方法的时候,先仔细观察下putVal方法的函数签名,前面两个是key和key对应的hash值,第三个表示要put的value,第四个是是否不更改已经存在的值,第五个是是否是
creation模式。前面三个好理解, 根据我们之前,对put方法的解读,我们可以了解到put是要干这么一件事:如果key存在,那我改value;如果key,不存在,我往里面加这个value。总之,要把给定KV 加进map里的,因此,第四个传的肯定是false,第五个是true。回过头来看put调用putVal方法给的值,就没有疑问了。
接着看putVal方法的具体实现:
putVal实现上,第一行代码就出现了Node,第一眼就蒙了,别急,接着看下Node的定义
- /**
- * Basic hash bin node, used for most entries. (See below for
- * TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
- */
- static class Node<K,V> implements Map.Entry<K,V> {
- final int 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; }
- 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;
- }
- }
Node的说明,没看太懂,但它实现的代码很简单,实现了Entry接口,然后是一个链表结构,里面还存在一个next节点
现在回过头看putVal的内容,
- Node<K,V>[] tab; Node<K,V> p; int n, i;
- if ((tab = table) == null || (n = tab.length) == 0)
- n = (tab = resize()).length;
- if ((p = tab[i = (n - 1) & hash]) == null)
- tab[i] = newNode(hash, key, value, null);
第一行,申明这种局部变量,紧接着,将table 赋值给tab 判断是否为null,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<K,V>[] table;
说的是table在第一次使用的时候,初始化,resized为必需的空间,长度始终是2的幂。然后这个变量不可被序列化。第一次调用的时候table等于null,因而tab等于null,所以重新使用resize方法将值赋给tab,并将长度赋值给n.
接着来看resize方法,
- /**
- * 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<K,V>[] resize() {
- Node<K,V>[] oldTab = table;
- int oldCap = (oldTab == null) ? 0 : oldTab.length;
- int oldThr = threshold;
- int newCap, newThr = 0;
- if (oldCap > 0) {
- if (oldCap >= MAXIMUM_CAPACITY) {
- threshold = Integer.MAX_VALUE;
- return oldTab;
- }
- 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;
- 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"})
- 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;
- 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 { // preserve order
- 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;
- }
resize方法是初始化table或者double table的大小,如果table为null,那么就用初始容量来赋值给threshold
oldCap的值等于table的长度,oldThr的值等于threshold,我们使用完构造函数初始化的时候,table是null的,因而oldCap为0;而无參构造函数没处理threshold,因而值为0,有參构造函数,将它变成赋值为一个[1,MAXIMUM_CAPACITY]的值。因而,在无參构造函数的时候——————》newCap会被赋值默认初始容量,newThr会被赋值给(默认初始容量与装填因子的乘积);在有參构造函数的时候-----》只会更改newCap赋值为threshold的值。这两步都更改了newCap的值,如果newThr没有被更改,即threshold>0,将newCap和装填因子的乘积赋给newThr,对边界判定,再重新赋值给threshold。(无參的时候,是将容量复制给threshold,现在在reSize的时候改回来了)如果当table不为空threshold也大于0的时候,边界判定,如果OK,newCap扩大一倍,newThr扩大一倍。最后,再把之前的结果,加入到新newTab中,将oldTab中的元素置为null。
OK先到这里,回到putVal方法tab resize之后,变成16个(初始容量)的Node数组,n等于初始容量。紧接着,看table[(n-1)&hash]是否为空,等于null,将其放置在这个位置上,不为空,那么就需要解决冲突,将其放入到合适的位置。里面有一个变量modCount,定义如下
- /**
- * The number of times this HashMap has been structurally modified
- * Structural modifications are those that change the number of mappings in
- * the HashMap or otherwise modify its internal structure (e.g.,
- * rehash). This field is used to make iterators on Collection-views of
- * the HashMap fail-fast. (See ConcurrentModificationException).
- */
- transient int modCount;
表示的是结构修改的次数,好像是用来检测并发修改异常?另外有一点需要注意下当一个bin(桶)装的数据太多的时候,会把Node转成TreeNode, TREEIFY_THRESHOLD = 8(常量)
- 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;
}
结合上面转成TreeNode的场景,从下面treeifyBin的代码清楚的看到,它会将对应的桶的节点转成TreeNode,先不看TreeNode的定义,从下面转化的代码,可以猜测到TreeNode除了KV属性,还包括prev(字面意思前一个),next(下一个),而且显然,他也要继承Node。
- /**
- * Replaces all linked nodes in bin at index for given hash unless
- * table is too small, in which case resizes instead.
- */
- final void treeifyBin(Node<K,V>[] tab, int hash) {
- int n, index; Node<K,V> e;
- if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
- resize();
- else if ((e = tab[index = (n - 1) & hash]) != null) {
- TreeNode<K,V> hd = null, tl = null;
- do {
- TreeNode<K,V> p = replacementTreeNode(e, null);
- if (tl == null)
- hd = p;
- else {
- p.prev = tl;
- tl.next = p;
- }
- tl = p;
- } while ((e = e.next) != null);
- if ((tab[index] = hd) != null)
- hd.treeify(tab);
- }
- }
TreeNode 简单列举下,代码太长了,就不全列了,TreeNode 应该是一个红黑树实现的
- static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
- TreeNode<K,V> parent; // red-black tree links
- TreeNode<K,V> left;
- TreeNode<K,V> right;
- TreeNode<K,V> prev; // needed to unlink next upon deletion
- boolean red;
- TreeNode(int hash, K key, V val, Node<K,V> next) {
- super(hash, key, val, next);
- }
- /**
- * Returns root of tree containing this node.
- */
- final TreeNode<K,V> root() {
- for (TreeNode<K,V> r = this, p;;) {
- if ((p = r.parent) == null)
- return r;
- r = p;
- }
- }
- /**
- * HashMap.Node subclass for normal LinkedHashMap entries.
- */
- static class Entry<K,V> extends HashMap.Node<K,V> {
- Entry<K,V> before, after;
- Entry(int hash, K key, V value, Node<K,V> next) {
- super(hash, key, value, next);
- }
- }
两个回调函数
afterNodeAccess,afterNodeInsertion,是空函数?这是为啥?留存
- // Callbacks to allow LinkedHashMap post-actions
- void afterNodeAccess(Node<K,V> p) { }
- void afterNodeInsertion(boolean evict) { }
至此,put方法实现简单的看完了,现在来看get方法
- /**
- * Returns the value to which the specified key is mapped,
- * or {@code null} if this map contains no mapping for the key.
- *
- * <p>More formally, if this map contains a mapping from a key
- * {@code k} to a value {@code v} such that {@code (key==null ? k==null :
- * key.equals(k))}, then this method returns {@code v}; otherwise
- * it returns {@code null}. (There can be at most one such mapping.)
- *
- * <p>A return value of {@code null} does not <i>necessarily</i>
- * indicate that the map contains no mapping for the key; it's also
- * possible that the map explicitly maps the key to {@code null}.
- * The {@link #containsKey containsKey} operation may be used to
- * distinguish these two cases.
- *
- * @see #put(Object, Object)
- */
- public V get(Object key) {
- Node<K,V> e;
- return (e = getNode(hash(key), key)) == null ? null : e.value;
- }
- /**
- * Implements Map.get and related methods
- *
- * @param hash hash for key
- * @param key the key
- * @return the node, or null if none
- */
- 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);
- do {
- if (e.hash == hash &&
- ((k = e.key) == key || (key != null && key.equals(k))))
- return e;
- } while ((e = e.next) != null);
- }
- }
- return null;
- }
get方法的注解挺简单的,大概意思是说返回key对应的value值,如果map中不含有key,那么返回null.注意一点地是,返回null,也有可能是key对应的值,因此,如果需要区分这两种情况,使用
containsKey方法。值得注意的一点,containsKey和get都是调用getNode方法,因此,只需要看它即可,了解二者是怎样区分的。
前面,我们在看put的时候,可以发现KV是存在Node中的,而getNode就是返回这样的Node,当存在样的Node节点的时候,getNode返回这个Key对应的节点,否则返回null。而get之所以区分不开,是因为下面这行代码
- return (e = getNode(hash(key), key)) == null ? null : e.value;
getNode返回的节点e 为null的时候,(表示不存在),get也返回null。当e不为null的时候,返回e.value。而e.value有可能为null。
remove操作和put操作差不多,它调用的是removeNode
- public V remove(Object key) {
- Node<K,V> e;
- return (e = removeNode(hash(key), key, null, false, true)) == null ?
- null : e.value;
- }
- /**
- * Implements Map.remove and related methods
- *
- * @param hash hash for key
- * @param key the 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;
- 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;
- 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)
- tab[index] = node.next;
- else
- p.next = node.next;
- ++modCount;
- --size;
- afterNodeRemoval(node);
- return node;
- }
- }
- return null;
- }
简单总结下:
1. HashMap 的实现方式还是传统的数组+链表的方式,但是比链表做了一个优化是,当一个桶装的元素过多的时候,他会把其转成一棵红黑树,从而做一个优化。
2. HashMap 的元素是放在table中,但table是一个transient数据,是不包括在序列化中,难道HashMap不支持序列化吗?我们从HashMap的申明,可以看到
- public class HashMap<K,V> extends AbstractMap<K,V>
- implements Map<K,V>, Cloneable, Serializable {
显然,它是可以序列化的,这样说来,他就自己重新定义了序列化的方法,我们找到了下面代码
- /**
- * Save the state of the <tt>HashMap</tt> instance to a stream (i.e.,
- * serialize it).
- *
- * @serialData The <i>capacity</i> of the HashMap (the length of the
- * bucket array) is emitted (int), followed by the
- * <i>size</i> (an int, the number of key-value
- * mappings), followed by the key (Object) and value (Object)
- * for each key-value mapping. The key-value mappings are
- * emitted in no particular order.
- */
- 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);
- }
- /**
- * Reconstitute the {@code HashMap} instance from a stream (i.e.,
- * deserialize it).
- */
- 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);
- }
- }
- }
Java HashMap源码分析的更多相关文章
- Java HashMap源码分析(含散列表、红黑树、扰动函数等重点问题分析)
写在最前面 这个项目是从20年末就立好的 flag,经过几年的学习,回过头再去看很多知识点又有新的理解.所以趁着找实习的准备,结合以前的学习储备,创建一个主要针对应届生和初学者的 Java 开源知识项 ...
- java HashMap源码分析(JDK8)
这两天在复习JAVA的知识点,想更深层次的了解一下JAVA,所以就看了看JAVA的源码,把自己的分析写在这里,也当做是笔记吧,方便记忆.写的不对的地方也请大家多多指教. JDK1.6中HashMap采 ...
- 【JAVA集合】HashMap源码分析(转载)
原文出处:http://www.cnblogs.com/chenpi/p/5280304.html 以下内容基于jdk1.7.0_79源码: 什么是HashMap 基于哈希表的一个Map接口实现,存储 ...
- Java集合源码分析(四)HashMap
一.HashMap简介 1.1.HashMap概述 HashMap是基于哈希表的Map接口实现的,它存储的是内容是键值对<key,value>映射.此类不保证映射的顺序,假定哈希函数将元素 ...
- 【Java】HashMap源码分析——常用方法详解
上一篇介绍了HashMap的基本概念,这一篇着重介绍HasHMap中的一些常用方法:put()get()**resize()** 首先介绍resize()这个方法,在我看来这是HashMap中一个非常 ...
- 【Java】HashMap源码分析——基本概念
在JDK1.8后,对HashMap源码进行了更改,引入了红黑树.在这之前,HashMap实际上就是就是数组+链表的结构,由于HashMap是一张哈希表,其会产生哈希冲突,为了解决哈希冲突,HashMa ...
- Java BAT大型公司面试必考技能视频-1.HashMap源码分析与实现
视频通过以下四个方面介绍了HASHMAP的内容 一. 什么是HashMap Hash散列将一个任意的长度通过某种算法(Hash函数算法)转换成一个固定的值. MAP:地图 x,y 存储 总结:通过HA ...
- Java源码解析——集合框架(五)——HashMap源码分析
HashMap源码分析 HashMap的底层实现是面试中问到最多的,其原理也更加复杂,涉及的知识也越多,在项目中的使用也最多.因此清晰分析出其底层源码对于深刻理解其实现有重要的意义,jdk1.8之后其 ...
- Java 集合源码分析(一)HashMap
目录 Java 集合源码分析(一)HashMap 1. 概要 2. JDK 7 的 HashMap 3. JDK 1.8 的 HashMap 4. Hashtable 5. JDK 1.7 的 Con ...
随机推荐
- 初涉JSP+JDBC 基于SQL2008的登陆验证程序
简单的以代码的形式纪念一下,因为现在还没有解决SQL2008驱动的问题,并且有好多东西要学,所以日后会有更新~ 所安装的软件有:SQL2008,eclipse,tomcat,JDK,涉及环境配置.等等 ...
- Java中实现PCA降维
package com.excellence.splitsentence; import java.net.UnknownHostException; import java.util.ArrayLi ...
- 1014 C语言文法定义
<程序>→<外部声明>|<程序><外部声明><外部声明>→<函数定义>|<声明><函数定义>→<数 ...
- Internet History, Technology and Security (Week 5-1)
Week 5 Technology: Internets and Packets Welcome to Week 5! This week, we'll be covering internets a ...
- git add -A 和 git add . 的区别
git add -A和 git add . git add -u在功能上看似很相近,但还是存在一点差别 git add . :他会监控工作区的状态树,使用它会把工作时的所有变化提交到暂存区,包括文 ...
- Linux adduser
- [知乎]老狼:深入PCI与PCIe之二:软件篇
深入PCI与PCIe之二:软件篇 https://zhuanlan.zhihu.com/p/26244141 我们前一篇文章(深入PCI与PCIe之一:硬件篇 - 知乎专栏)介绍了PCI和PCIe的硬 ...
- Windows 10 正式版原版ISO镜像
Win10正式版32位简体中文版(含家庭版.专业版)文件名: cn_windows_10_multiple_editions_x86_dvd_6846431.isoSHA1:21B824F402927 ...
- python web调用docker-py
在 /etc/init.d/docker的start()函数末尾加入:chmod 777 /var/run/docker.sock 否则web程序会没有权限去操作
- response和request的setCharacterEncoding区别
一.request.setCharacterEncoding():是设置从request中取得的值或从数据库中取出的值. 指定后可以通过getParameter()则直接获得正确的字符串,如果不指定, ...