在我们的面试中HashMap基本上是一个逃不开的知识点,本文就带你一起学习HashMap的部分源码。如果有不正确的地方,欢迎指正。祝你学习愉快。


由于网上大部分的解析都是基于JDK1.8的,然而我的JDK是11。所以我就来写一篇HashMap在JDK11中的源码剖析。如果后面有机会,我会再写关于1.8的(不过貌似源码差不多)。

文章目录

  • 1、类的基本关系
  • 2、成员变量
  • 3、构造方法
  • 4、成员方法
    • 4.1、put 添加key-value(重点)
    • 4.2、treeifyBin 链表转换成红黑树(重点)
    • 4.3、resize 扩容数组(重点)
    • 4.4、remove 根据key删除元素
    • 4.5、get 获取元素

如果想看理论性的东西,下面的传送门欢迎你!!!

HashMap原理讲解


1、类的基本关系

说明:

  • Cloneable接口:该接口为系统的一个克隆接口
  • Serializable 接口:实现该接口的类,可以被序列化和反序列化

我们发现AbstractMap 和HashMap类都实现了Map类,这貌似有一点多余吧,这里穿插一点历史

据 java 集合框架的创始人Josh Bloch描述,这样的写法是一个失误。在java集合框架中,类似这样的写法很多,最开始写java集合框架的时候,他认为这样写,在某些地方可能是有价值的,直到他意识到错了。显然的,JDK的维护者,后来不认为这个小小的失误值得去修改,所以就这样存在下来了。


2、成员变量

1、序列化版本号

private static final long serialVersionUID = 362498820763181265L;

2、default_initial_capacity(默认的初始容量),这里指集和的默认初始容量为16

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

3、maximum_capacity(最大容量),这里指集和的最大容量是2的30次

static final int MAXIMUM_CAPACITY = 1 << 30;

4、default_load_factor(默认的加载因子),与map集和的容量有关

static final float DEFAULT_LOAD_FACTOR = 0.75f;

5、treeify_threshold(树的阈值),链表转换为红黑树的边界值

static final int TREEIFY_THRESHOLD = 8;

6、untretreeify_threshold(不是树的阈值),红黑树转换成链表的边界值

static final int UNTREEIFY_THRESHOLD = 6;

7、min_treeify_capacity(最小容量),hash表的结构由链表转换成红黑树的最小容量

static final int MIN_TREEIFY_CAPACITY = 64;

8、用来初始化(1.7时Node为Entry<K,V>)

transient Node<K,V>[] table;
//在后文出现的这个类,负责存储键值对的数据
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; }//返回对应的hashCode值public final int hashCode() {return Objects.hashCode(key) ^ Objects.hashCode(value);}//设置key对应的Valuepublic final V setValue(V newValue) {V oldValue = value;value = newValue;return oldValue;}//比较传入的对象与自己是否相同public final boolean equals(Object o) {if (o == this)return true;//instanceof是Java中的一个双目运算符,用来测试一个对象是否为一个类的实例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;}
}

9、用来存放缓存

transient Set<Map.Entry<K,V>> entrySet;

10、size表示存储map集和的元素个数,这个个数和数组的长度有区别

transient int size;

11、记录集和修改的次数

transient int modCount;

12、集和容量的临界值,用来扩大集和

int threshold;

13、加载因子,前面是有一个默认的,我们也可以进行修改

final float loadFactor;

3、构造方法

1、自定义集和初始容量和自定义加载因子的构造方法

//实例化的时候,HashMap(自定义集和初始容量,自定义加载因子)。当然,如果我们不传入,也会有对应的默认值(1 << 4,0.75)
public HashMap(int initialCapacity, float loadFactor) {//如果初始容量小于0,抛出初始化异常if (initialCapacity < 0)throw new IllegalArgumentException("Illegal initial capacity: " +initialCapacity);//如果自定义的初始化容量大于集和的最大容量1 << 30,那么就直接将最大值复制给我们if (initialCapacity > MAXIMUM_CAPACITY)initialCapacity = MAXIMUM_CAPACITY;//如果加载因子<=0,或者传入的加载因子所表示的值是NaN(具体查看补充方法一)时抛出异常if (loadFactor <= 0 || Float.isNaN(loadFactor))throw new IllegalArgumentException("Illegal load factor: " +loadFactor);//将自定义的加载因子进行传值this.loadFactor = loadFactor;//返回一个2的幂次方数,赋值给我们的集和容量(具体查看补充方法二),即如果我们如果传入的数据不是2的次幂方数,这里会进行一个转换,系统帮我们转换成一个2的幂次方数(涉及数学运算)this.threshold = tableSizeFor(initialCapacity);
}

补充方法一:判断是否是NaN类型

//这个可以类比JS中得NaN,传入一个正常的数字,肯定会返回false,但如果传入的是一个NaN类型就为true(0.0f/0.0f就是一个NaN类型)
public static boolean isNaN(float v) {return (v != v);
}

补充方法二:返回比自定义初始化容量大的最小的2的n次幂

//该方法返回比自定义初始化容量大的最小的2的n次幂
static final int tableSizeFor(int cap) {//将-1进行无符号的右移与 运算结果返回相同的位数(具体查看补充方法三)int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1);return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

补充方法三:获取传入数字的最高为1的位置

//该方法就是用来获取传入数字的最高为1的位置
//i为我们自定义的集和的初始容量-1
public static int numberOfLeadingZeros(int i) {if (i <= 0)//如果i为-1,返回回值也位0,且我们的自定义数组长度为0;如果i为0,则返回32(即-1无符号右移32位)return i == 0 ? 32 : 0;int n = 31;//获取最高为(一顿骚操作的数学运算,可自行验证)if (i >= 1 << 16) { n -= 16; i >>>= 16; }if (i >= 1 <<  8) { n -=  8; i >>>=  8; }if (i >= 1 <<  4) { n -=  4; i >>>=  4; }if (i >= 1 <<  2) { n -=  2; i >>>=  2; }return n - (i >>> 1);
}

2、自定义集和初始化容量的构造方法

public HashMap(int initialCapacity) {//自定义了初始化容量,加载因子使用系统默认的this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

3、无参构造,默认初始容量(16)和默认负载因子(0.75)

public HashMap() {//使用系统默认的参数this.loadFactor = DEFAULT_LOAD_FACTOR;
}

4、包含Map的构造方法

//传入一个map集,与指定的KV键值对拥有继承关系
public HashMap(Map<? extends K, ? extends V> m) {//赋值默认的加载因子this.loadFactor = DEFAULT_LOAD_FACTOR;//将map集和的数据传入到HashMap中(具体查看补充方法四)putMapEntries(m, false);
}

补充方法四:将两个集和进行数据交换(该方法不再下探)

final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {//获取传入的集和的长度int s = m.size();if (s > 0) {//判断table是否被初始化(成员变量第8个)if (table == null) { //未初始化,s为m的实际元素个数(在后面我们会讲解数组的实际存储长度=加载因子*数组的长度)//加1.0F与(int)ft相当于是对小数做一个向上取整以尽可能的保证更大容量,更大的容量能够减少resize的调用次数。所以 + 1.0F是为了获取更大的容量float ft = ((float)s / loadFactor) + 1.0F;//判断传入的集和最大容量的关系,int t = ((ft < (float)MAXIMUM_CAPACITY) ?(int)ft : MAXIMUM_CAPACITY);// 计算得到的t大于集和容量的临界值,则初始化这个集和容量if (t > threshold)//具体查看补充的方法体二,返回比自定义初始化容量大的最小的2的n次幂threshold = tableSizeFor(t);}// 已初始化,并且m集和个数大于结合容量,则进行扩容处理else if (s > threshold)//由于该方法的代码量较多,我们后面讲解,此处你只需要记住,这个方法是用来扩大数组的resize();// 将m集和中的所有元素添加至HashMap集和中for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {K key = e.getKey();V value = e.getValue();//这里就是一个依次赋值的方法,会后讲put时会讲解putVal(hash(key), key, value, false, evict);}}
}

4、成员方法

4.1、put 添加key-value(重点)

//当我们调用put方法时,其实是调用了putVal方法
public V put(K key, V value) {//传入参数位:key的hash值,key,value,以及两个布尔值。具体计算hash值的反法,hash具体方法的详情请查看补充方法五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; 表示存储Map集合中元素的数组。//Node<K,V> p p表示原来的节点Node<K,V>[] tab; Node<K,V> p; int n, i;//(tab = table) == null 表示用来初始化的空的table赋值给tab,然后判断tab是否等于null,第一次肯定是null //(n = tab.length) == 0 表示将数组tab的长度0赋值给n,然后判断n是否等于0,为真,则执行代码 n = (tab = resize()).length; 进行数组初始化。并将初始化好的数组长度赋值给n.if ((tab = table) == null || (n = tab.length) == 0)//n = (tab = resize()).length,得到扩容以后数组的长度n = (tab = resize()).length;//1、i = (n - 1) & hash 表示利用数组的长度和hash值,计算数组的索引赋值给i//2、p = tab[i = (n - 1) & hash]表示获取计算出的索引位置的数据赋值给原来的节点p//3、判断节点是否为null,如果为空则执行对应代码if ((p = tab[i = (n - 1) & hash]) == null)//创建一个新的节点,将数据放入数组tab的指定索引位置上tab[i] = newNode(hash, key, value, null);//如果不为空else {Node<K,V> e; K k;//1、p.hash == hash 判断原来数据的hash值和添加数据的hash值是否相等//2、(k = p.key) == key 获取原来数据的key赋值给k,然后再与添加的数据key进行地址值的比较//3、(key != null && key.equals(k)) 添加的数据key是都为空且添加的key和原来数据的key是否相同if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))//如果条件判断为真,则将p节点赋值给新的节点e//总结一句话就是:如果新传入的key的hash值与之前的存在的节点相同,那么就会调用equals方法进行判断,如果key的内容也相同,则可以认为是相同的key,则进行key与value的替换e = p;//在节点不为空且key的hash值不同的前提下,判断原来的节点是否为红黑树类型else if (p instanceof TreeNode)//如果是,则进行对应红黑树的操作(本文暂不讲解该块知识点)e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);else {//节点不为空,key也不相同,又不是红黑树节点,则说明该节点是链表节点//循环遍历整个链表for (int binCount = 0; ; ++binCount) {//如果在遍历的过程中,遇到了某个节点的下一个节点为null,则说明此时遍历到了链表的底端if ((e = p.next) == null) {//将新的节点数据进行插入p.next = newNode(hash, key, value, null);//如果插入以后长度大于等于了树的阈值8-1(成员变量5),就将链表数据结构,转换成红黑树的数据结构if (binCount >= TREEIFY_THRESHOLD - 1) //后面专门讲解这个方法treeifyBin(tab, hash);break;}//如果在遍历的过程中,遇到了某个节点的key的hash与内容都相同时,则跳出循环if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;//将赋值成功的新的节点e,重新返回赋值给p(与前文代码照应)p = e;}}//将之前数据key的hash值与key的内容相同的进行value的修改if (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;//访问后进行回调(让数据的顺序保持与之前的相同,JDK1.7就因为修改后数据的顺序不同造成死锁)具体方法详情,请查看补充方法六afterNodeAccess(e);return oldValue;}}//修改次数加1++modCount;//判断插入数据后,存储的元素是否大于数组长度if (++size > threshold)resize();//插入后回调,具体方法详情,请查看补充方法六afterNodeInsertion(evict);return null;
}

补充方法五:

static final int hash(Object key) {int h;//进行相应的运算(这里又是一顿数学运算)return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

补充方法六:

// Callbacks to allow LinkedHashMap post-actions(仅从writeObject调用,以确保排序兼容。)
//这涉及到LinkedHashMap,emmm我还没学,不知道!!!
void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }

补充一个put方法的程序流程图:

4.2、treeifyBin 链表转换成红黑树(重点)

for (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);//binCount从0开始,0表示第一个节点,6表示第七个节点,不会进入循环。7表示第八个节点,会进入循环,继而进行红黑树的转换(7表示本身已经有八个节点,再加上新添加的节点数据,就有9个了,就会进行红黑树的转换)if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);break;}......
}
final void treeifyBin(Node<K,V>[] tab, int hash) {int n, index; Node<K,V> e;//如果当前数组为空或者数组的长度小于进行树形化的阈值(MIN_TREEIFY_CAPACITY = 64),if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)//进行扩容resize();//执行到这里,说明hash表中的数组长度已经大于64了//e = tab[index = (n - 1) & hash]表示将集和数组中的元素取出赋值给e,e是哈希表中指定位置里的链表节点,从第一个开始else if ((e = tab[index = (n - 1) & hash]) != null) {//hd:红黑树的头结点   tl :红黑树的尾结点TreeNode<K,V> hd = null, tl = null;//链表转换成红黑树do {//新创建一个树的节点,内容和当前链表节点e一致TreeNode<K,V> p = replacementTreeNode(e, null);if (tl == null)//将新创键的p节点赋值给红黑树的头结点hd = p;else {//p.prev = tl:将上一个节点p赋值给现在的p的前一个节点p.prev = tl;//tl.next = p;将现在节点p作为树的尾结点的下一个节点tl.next = p;}tl = p;//e = e.next 将当前节点的下一个节点赋值给e,如果下一个节点不等于null,循环整个链表,将数据转换成红黑树} while ((e = e.next) != null);//让数组中的元素指向新建的红黑树的节点,以后这个位置里的元素就是红黑树而不是链表了       if ((tab[index] = hd) != null)hd.treeify(tab);}
}

4.3、resize 扩容数组(重点)

每次扩容,都需要重新分配所有元素的位置

当HashMap中的其中一个链表的对象个数如果达到了8个,此时如果数组长度没有达到64,那么HashMap会先扩容解决,如果已经达到了64,那么这个链表会变成红黑树,节点类型由Node变成TreeNode类型。当然,如果映射关系被移除后,下次执行resize方法时判断树的节点个数低于6,也会再把树转换为链表

final Node<K,V>[] resize() {//获得当前数组Node<K,V>[] oldTab = table;//判断数组是否为null,并返回数组长度int oldCap = (oldTab == null) ? 0 : oldTab.length;//集和容量=数组长度*负载因子(默认为16*0.75=12)int oldThr = threshold;int newCap, newThr = 0;//如果原数组的长度大于0if (oldCap > 0) {//原数组的长度大于1<<30,则超过了最大值if (oldCap >= MAXIMUM_CAPACITY) {//将Integer的最大值赋值给这个数组的边界(最大值为21亿多)threshold = Integer.MAX_VALUE;return oldTab;}//(newCap = oldCap << 1) < MAXIMUM_CAPACITY 扩大到2倍之后容量是否小于最大容量?//oldCap >= DEFAULT_INITIAL_CAPACITY 原数组长度是否大于等于数组初始化长度16else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY)//如果是就将数组的容量扩大一倍newThr = oldThr << 1; // double threshold}//如果原数组的容量是大于0,就直接将原来的数组容量赋值给新得数组长度else if (oldThr > 0) // initial capacity was placed in thresholdnewCap = oldThr;//除此之外,新得数组长度和数组容量使用默认值else {               // zero initial threshold signifies using defaults//新的数组长度为16newCap = DEFAULT_INITIAL_CAPACITY;//新得数组容量为12newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);}//计算新的resize最大上限if (newThr == 0) {float ft = (float)newCap * loadFactor;newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?(int)ft : Integer.MAX_VALUE);}//将新的数组容量赋值给默认得数组容量变量threshold = newThr;//创建一个新的hash表,且数组长度为新的数组长度@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) {//原来的数据赋值为null 便于GC回收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;//这里来判断如果等于true e这个节点在resize之后不需要移动位置if ((e.hash & oldCap) == 0) {if (loTail == null)loHead = e;elseloTail.next = e;loTail = e;}// 此外,原索引+oldCapelse {if (hiTail == null)hiHead = e;elsehiTail.next = e;hiTail = e;}} while ((e = next) != null);// 原索引放到对应的位置if (loTail != null) {loTail.next = null;newTab[j] = loHead;}// 原索引+oldCap放到对用位置里if (hiTail != null) {hiTail.next = null;newTab[j + oldCap] = hiHead;}}}}}return newTab;
}

扩容后各元素的位置:要么在原位置,要么在原位置+原数组长度的那个位置上(for循环代码)

4.4、remove 根据key删除元素

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;//根据hash找到对应的位置,如果位置不为nullif ((tab = table) != null && (n = tab.length) > 0 &&(p = tab[index = (n - 1) & hash]) != null) {Node<K,V> node = null, e; K k; V v;//如果对应位置的节点就是要找的key,则将node指向该节点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 {//判断是否以链表方式处理hash冲突,是的话则通过遍历链表来寻找要删除的节点do {if (e.hash == hash &&((k = e.key) == key ||(key != null && key.equals(k)))) {node = e;break;}p = e;} while ((e = e.next) != null);}}//比较找到的key的value和要删除的是否匹配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;elsep.next = node.next;//记录修改次数++modCount;//变动的数量减1--size;//又是LinkedHashMap相关的操作afterNodeRemoval(node);return node;}}return null;
}

4.5、get 获取元素

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;//如果哈希表不为空并且key对应的的索引位置上也不为空if ((tab = table) != null && (n = tab.length) > 0 &&(first = tab[(n - 1) & hash]) != null) {//是否满足hash值与第一个位置的hash值相同,并且第一个位置上的key是否和传入的key相同或内容相同if (first.hash == hash && // always check first node 总是检查第一个元素((k = first.key) == key || (key != null && key.equals(k))))//如果满足,则说明第一个元素就是要查找的元素,那就直接返回return first;if ((e = first.next) != null) {//判断是否是红黑树,是的话调用红黑树中的getTreeNode方法获取对应的节点if (first instanceof TreeNode)return ((TreeNode<K,V>)first).getTreeNode(hash, key);//如果不是红黑树结构,那就是链表结构,通过循环遍历链表结构中是否存在符合条件的keydo {if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))return e;} while ((e = e.next) != null);}}return null;
}

补充红黑树查找的对应数据的方法(有兴趣可以查看)

final TreeNode<K,V> getTreeNode(int h, Object k) {return ((parent != null) ? root() : this).find(h, k, null);}
final TreeNode<K,V> find(int h, Object k, Class<?> kc) {TreeNode<K,V> p = this;//添加数据如果是添加到红黑树中,那么数据就是有序的。此时利用循环进行折半查找//对比节点的哈希值和要查找的哈希值是否相等,再判断key是否相等,相等就直接返回。不相等就继续查找do {int ph, dir; K pk;TreeNode<K,V> pl = p.left, pr = p.right, q;if ((ph = p.hash) > h)p = pl;else if (ph < h)p = pr;else if ((pk = p.key) == k || (k != null && k.equals(pk)))return p;else if (pl == null)p = pr;else if (pr == null)p = pl;else if ((kc != null ||(kc = comparableClassFor(k)) != null) &&(dir = compareComparables(kc, k, pk)) != 0)p = (dir < 0) ? pl : pr;else if ((q = pr.find(h, k, kc)) != null)return q;elsep = pl;} while (p != null);return null;
}

get方法实现的步骤:

  1. 通过hash值获取该key映射到的桶

  2. 桶上的key就是要查找的key,则直接找到并返回

  3. 桶上的key不是要找的key,则查看后续的节点:

    • 如果后续节点是红黑树节点,通过调用红黑树的方法根据key获取value
    • 如果后续节点是链表节点,则通过循环遍历链表根据key获取value

至此,hashmap的几个重要方法的执行流程已经讲解完毕,但是面试基本只会问原理,源码的流程只是为了让我们更好的理解原理

传送门:
HashMap原理讲解

HashMap源码剖析(代码基于JDK11)相关推荐

  1. 【Java集合源码剖析】HashMap源码剖析

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/36034955 您好,我正在参加CSDN博文大赛,如果您喜欢我的文章,希望您能帮我投一票,谢 ...

  2. Java HashSet和HashMap源码剖析

    转载自 Java HashSet和HashMap源码剖析 总体介绍 之所以把HashSet和HashMap放在一起讲解,是因为二者在Java里有着相同的实现,前者仅仅是对后者做了一层包装,也就是说Ha ...

  3. Java集合:HashMap源码剖析

    一.HashMap概述 二.HashMap的数据结构 三.HashMap源码分析      1.关键属性      2.构造方法      3.存储数据      4.调整大小 5.数据读取     ...

  4. 查询已有链表的hashmap_源码分析系列1:HashMap源码分析(基于JDK1.8)

    1.HashMap的底层实现图示 如上图所示: HashMap底层是由  数组+(链表)=(红黑树) 组成,每个存储在HashMap中的键值对都存放在一个Node节点之中,其中包含了Key-Value ...

  5. 源码分析系列1:HashMap源码分析(基于JDK1.8)

    1.HashMap的底层实现图示 如上图所示: HashMap底层是由  数组+(链表)+(红黑树) 组成,每个存储在HashMap中的键值对都存放在一个Node节点之中,其中包含了Key-Value ...

  6. Java HashMap源码剖析

    一.HashMap概述 HashMap基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了不同步和允许使用 null 之外,HashMap  ...

  7. 详细解析:HashMap源码剖析

    目录: 一.HashMap概述二.HashMap的数据结构三.HashMap源码分析 1.关键属性 2.构造方法 3.存储数据 4.调整大小 5.数据读取 6.HashMap的性能参数 7.Fail- ...

  8. python源码剖析代码例子_Python源码剖析笔记5-模块机制

    python中经常用到模块,比如import xxx,from xxx import yyy这样子,里面的机制也是需要好好探究一下的,这次主要从黑盒角度来探测模块机制,源码分析点到为止,详尽的源码分析 ...

  9. 基于python3写的源码剖析_基于python3生成标签云代码解析

    这篇文章主要介绍了基于python3生成标签云代码解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 标签云是现在大数据里面最喜欢使用的一种展现方式 ...

  10. STM32实例源码剖析(基于51单片机的摇摇棒制作)

    #include #define uchar unsigned char #define uint unsigned intsbit key = P3^0;//定义切换画面开关 uchar Key_n ...

最新文章

  1. impala连接使用方法
  2. dll 源码_重新编译mono——Android动态更新dll
  3. 通过Spring Integration和RabbitMQ获得高可用性的AMQP支持的消息通道
  4. 二十.激光、视觉和惯导LVIO-SLAM框架学习之相机内参标定
  5. 机器学习(1)——基础概念
  6. 编程菜鸟的日记-初学尝试编程-C++ Primer Plus 第6章编程练习6
  7. 过滤钩子驱动程序一(微软DDK文档,FLASHSKY翻译) (转)
  8. 详解SourceOffsite的安装配置解决VSS共享数据库目录的问题
  9. xfs文件系统修复问题
  10. 关于NGS中“depth”和“coverage”的理解
  11. 哈佛结构和冯诺依曼结构特点
  12. 使用命令行强制注销远程登录用户
  13. php 图片渲染,vue.js图片怎么渲染
  14. 软件工程与软件开发模型、软件开发方法
  15. XServer 使用说明
  16. osx制作u盘安装盘
  17. Android File格式上传图片
  18. 文献阅读-ICRA2020-具有软流体驱动执行器的经皮MRI引导针机器人的设计
  19. PTA 数据结构及算法 7-47 打印选课学生名单
  20. The class java.lang.IllegalArgumentException may be caused by the wrapped ProcessingEnvironment obje

热门文章

  1. BZOJ4530[BJOI2014] 大融合
  2. 通过Downward API传递pod元数据
  3. 标签和标签选择器、label selector
  4. kubernetes集群架构和组件
  5. 导入grafana的dashboards模板
  6. 自动基线校正 python_红外光谱的
  7. 更改应用程序图标_苹果手机升级iOS14试试自定义应用图标
  8. python3的fft_FFT乘法Python 3.4.3
  9. rust狗阳的师傅是谁_杨幂迪丽热巴新剧同天官宣,你更期待谁呢?
  10. 文件上传后input怎么回显_tftp上传文件报错,tftp上传文件报错怎么办