来来来,今天就跟hashmap杠到底。。。

不要叫我杠精了,主要是还是被问到hashmap的时候,我并不能很清晰明了得告知这种数据结构到底是一个什么构造,里面细节并不了解

既然这样,我们就把他解析一波,今天这篇也算是hashmap的收官之作了,主要用来红黑树部分我之前有博文写过,但是不用深究

自己实现一个hashmap

话不多说,直接上代码,我先把这几天的成就放上来,也就是自己实现的hashmap,还原到以前的版本,我把红黑树的部分代码给删除了

package y2019.collection;

import java.util.Map;
import java.util.Objects; /**
* @ProjectName: cutter-point
* @Package: y2019.collection
* @ClassName: MyMyHashMap
* @Author: xiaof
* @Description: 在JDK8中,当链表长度达到8,会转化成红黑树,以提升它的查询、插入效率
* 底层哈希桶的数据结构是数组,所以也会涉及到扩容的问题。
* 当MyHashMap的容量达到threshold域值时,就会触发扩容。扩容前后,哈希桶的长度一定会是2的次方。
* 这个类的目标是为了实现MyHashMap中的数组,hash扰动之后转链表的操作(后续可以考虑完善红黑树结构)
* @Date: 2019/6/25 9:08
* @Version: 1.0
*/
public class MyHashMap<K,V> { //容器最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
//用来存放NODE数据的数组
transient Node<K,V>[] table;
/**
* hash桶默认长度
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 //默认加载因子,加载因子是一个比例,当HashMap的数据大小>=容量*加载因子时,HashMap会将容量扩容
static final float DEFAULT_LOAD_FACTOR = 0.75f; //hash桶的阈值
int threshold;
//装载因子用来衡量HashMap满的程度
float loadFactor;
transient int modCount;
transient int size; 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;
}
} public static int hash(Object key) {
int h;
//也就将key的hashCode无符号右移16位然后与hashCode异或从而得到hash值在putVal方法中(n - 1)& hash计算得到桶的索引位置
//注意,这里h是int值,也就是32位,然后无符号又移16位,那么就是折半,折半之后和原来的数据做异或操作,正好整合了高位和低位的数据
//混合原始哈希码的高位和低位,以此来加大低位的随机性,而且混合后的低位掺杂了高位的部分特征,这样高位的信息也被变相保留下来。
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
} //测试,如果我们不做高位低位的操作看看hash冲突是大还是小
public static int hash2(Object key) {
return (int) key;
} public static int hash3(Object key) {
int h = key.hashCode();
//我们不做右移试试,那就自己跟自己异或。。。没意义,只能是0了
return (key == null) ? 0 : h ^ h;
} public static int hash4(Object key) {
int h;
//我们不做右移试试,或者右移8位试试
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 8);
} public static int hash5(Object key) {
int h;
//我们不做右移试试,或者右移8位试试
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 4);
} public static int hash6(Object key) {
int h;
//我们不做右移试试,或者右移8位试试
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 2);
} public int quyu1(int num, int n) {
//对num进行n取余
return num % n;
} public int quyu2(int num, int n) {
//对num进行n取余
return num & (n - 1);
} final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
//注意这一步中(n - 1) & hash 的值 等同于 hash(k)%table.length
if ((tab = table) != null && (n = tab.length) > 0 &&
//这里是计算相当于是取余的索引位置(n - 1) & hash 等价于hash % n
//而且由于hashmap中的length再tableSizeFor的时候,就把长度设置为2的n次幂了,那么n-1之后的值,就是最高位全都是0,下面位数全是1
//这个也就是取hash的低位的值
(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;
} public V get(Object key) {
MyHashMap.Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
} /**
*
* @program: y2019.collection.MyHashMap
* @description: 这个方法用于找到大于等于initialCapacity的最小的2的幂(initialCapacity如果就是2的幂,则返回的还是这个数)。
* @auther: xiaof
* 总结:
* 1.说白了就是为了保证所有的位数(二进制)都是1,那么就可以保证这个数就是2的幂
* 2.不断做无符号右移,是为了吧高位的数据拉下来做或操作,来保证对应的底位都是1
* @date: 2019/6/25 10:25
*/
public static final int tableSizeFor(int cap) {
//这是为了防止,cap已经是2的幂。如果cap已经是2的幂
int n = cap - 1;
//第一次右移,由于n不等于0(如果为0,不管几次右移都是0,那么最后有个n+1的操作),则n的二进制表示中总会有一bit为1
//这里无符号右移一位之后做或操作,那么会导致原来有1的地方紧接着也是1
//比如00000011xxxxxxxx
//还有一点无符号右移是为了避免前位补1,导致数据溢出,因为负数是以补码的形式存在的,那么就会再高位补1
n |= n >>> 1;
//第二次无符号右移,并做或操作
//00000011xxxxxxxx=>0000001111xxxxxx 这个时候就是4个1
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
//由于int最大也就是2的16次幂,所以到16停止
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
} public static final int tableSizeFor2(int cap) {
//这是为了防止,cap已经是2的幂。如果cap已经是2的幂
int n = cap - 1;
n |= n & 0xffff;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
} public static final int tableSizeFor3(int cap) {
//这是为了防止,cap已经是2的幂。如果cap已经是2的幂
int n = (cap - 1) & 0xffff;
String hex = Integer.toBinaryString(n);
return (cap <= 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : (int) Math.pow(2, hex.length());
} Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
return new Node<>(hash, key, value, next);
} public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
} public V put2(K key, V value) {
return putVal2(hash(key), key, value, false, 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为空时,这里初始化table,不是通过构造函数初始化,而是在插入时通过扩容初始化,有效防止了初始化HashMap没有数据插入造成空间浪费可能造成内存泄露的情况
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);
//当链表的长度大于等于树化阀值,并且hash桶的长度大于等于MIN_TREEIFY_CAPACITY,链表转化为红黑树
// 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;
}
}
//map中含有旧key,返回旧值
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
// afterNodeAccess(e);
return oldValue;
}
}
//map调整次数加1
++modCount;
//键值对的数量达到阈值需要扩容
if (++size > threshold)
resize();
// afterNodeInsertion(evict);
return null;
} final V putVal2(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//当table为空时,这里初始化table,不是通过构造函数初始化,而是在插入时通过扩容初始化,有效防止了初始化HashMap没有数据插入造成空间浪费可能造成内存泄露的情况
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize2()).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);
//当链表的长度大于等于树化阀值,并且hash桶的长度大于等于MIN_TREEIFY_CAPACITY,链表转化为红黑树
// 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;
}
}
//map中含有旧key,返回旧值
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
// afterNodeAccess(e);
return oldValue;
}
}
//map调整次数加1
++modCount;
//键值对的数量达到阈值需要扩容
if (++size > threshold)
resize2();
return null;
} //数组扩容
public Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
//如果旧hash桶不为空
if (oldCap > 0) {
////超过hash桶的最大长度,将阀值设为最大值
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
//新的hash桶的长度2被扩容没有超过最大长度,将新容量阀值扩容为以前的2倍
//扩大一倍之后,小于最大值,并且大于最小值
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
//左移1位,也就是扩大2倍
newThr = oldThr << 1;
}
else if (oldThr > 0) //如果旧的容量为空,判断阈值是否大于0,如果是那么就把容量设置为当前阈值
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
} //如果阈值还是0,重新计算阈值
if (newThr == 0) {
//当HashMap的数据大小>=容量*加载因子时,HashMap会将容量扩容
float ft = (float)newCap * loadFactor;
//如果容量还没超MAXIMUM_CAPACITY的loadFactor时候,那么就返回ft,否则就是反馈int的最大值
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
//hash桶的阈值
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;
//如果旧的hash桶不为空,需要将旧的hash表里的键值对重新映射到新的hash桶中
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
//如果是多个节点的链表,将原链表拆分为两个链表,两个链表的索引位置,一个为原索引,一个为原索引加上旧Hash桶长度的偏移量
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
// 在遍历原hash桶时的一个链表时,因为扩容后长度为原hash表的2倍,假设把扩容后的hash表分为两半,分为低位和高位,
// 如果能把原链表的键值对, 一半放在低位,一半放在高位,这样的索引效率是最高的
//这里的方式是e.hash & oldCap,
//经过rehash之后,元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置。对应的就是下方的resize的注释
//为什么是移动2次幂呢??注意我们计算位置的时候是hash&(length - 1) 那么如果length * 2 相当于左移了一位
//也就是截取的就高了一位,如果高了一位的那个二进制正好为1,那么结果也相当于加了2倍
//hash & (length * 2 - 1) = length & hash + (length - 1) & hash
if ((e.hash & oldCap) == 0) {
//如果这个为0,那么就放到lotail链表
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
//如果length & hash 不为0,说明扩容之后位置不一样了
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
//而这个loTail链表就放在原来的位置上
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
//因为扩容了2倍,那么新位置就可以是原来的位置,右移一倍原始容量的大小
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
} //数组扩容
public Node<K,V>[] resize2() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
//如果旧hash桶不为空
if (oldCap > 0) {
////超过hash桶的最大长度,将阀值设为最大值
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
//新的hash桶的长度2被扩容没有超过最大长度,将新容量阀值扩容为以前的2倍
//扩大一倍之后,小于最大值,并且大于最小值
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
//左移1位,也就是扩大2倍
newThr = oldThr << 1;
}
else if (oldThr > 0) //如果旧的容量为空,判断阈值是否大于0,如果是那么就把容量设置为当前阈值
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
} //如果阈值还是0,重新计算阈值
if (newThr == 0) {
//当HashMap的数据大小>=容量*加载因子时,HashMap会将容量扩容
float ft = (float)newCap * loadFactor;
//如果容量还没超MAXIMUM_CAPACITY的loadFactor时候,那么就返回ft,否则就是反馈int的最大值
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
//hash桶的阈值
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;
//如果旧的hash桶不为空,需要将旧的hash表里的键值对重新映射到新的hash桶中
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
//如果是多个节点的链表,将原链表拆分为两个链表,两个链表的索引位置,一个为原索引,一个为原索引加上旧Hash桶长度的偏移量
Node<K,V> next, pre;
pre = e;
do {
next = e.next;
//我们这里直接遍历设置进去试试
//对hash数据取余,当然如果还是再原来的位置,那么就不需要移动
if((e.hash & (oldCap - 1)) != (e.hash & (newCap - 1))) {
//1.先从原链表断开
pre.next = next;
//2.放到新位置上,我们可以使用头插法
Node<K,V> newHead, newNext;
newHead = newTab[e.hash & (newCap - 1)];
if(newHead == null) {
newHead = e;
} else {
//头插法
newNext = newHead.next;
newHead.next = e;
e.next = newNext;
}
}
pre = e;
} while ((e = next) != null);
}
}
}
}
return newTab;
} public Node<K, V>[] getTable() {
return table;
} public void setTable(Node<K, V>[] table) {
this.table = table;
} public int getThreshold() {
return threshold;
} public void setThreshold(int threshold) {
this.threshold = threshold;
} public float getLoadFactor() {
return loadFactor;
} public void setLoadFactor(float loadFactor) {
this.loadFactor = loadFactor;
} public int getModCount() {
return modCount;
} public void setModCount(int modCount) {
this.modCount = modCount;
}
}

注意resize 的扩容操作

1.啥时候扩容???

说白了就数据量超了就扩容被,那么什么时候叫超了呢???

很简单,就是hashmap的当前容量大于cap*loadFactor,cap是可以容纳的容量,loadFactor是一个百分比,就是到达多少的量了默认0.75f;

而且这个参数是可以改的

2.还有一种情况,网上说再扩容的时候,使用双链表直接连接的效率很高!!!

在遍历原hash桶时的一个链表时,因为扩容后长度为原hash表的2倍,假设把扩容后的hash表分为两半,分为低位和高位,
如果能把原链表的键值对, 一半放在低位,一半放在高位,这样的索引效率是最高的
这里的方式是e.hash & oldCap,
经过rehash之后,元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置。对应的就是下方的resize的注释
为什么是移动2次幂呢??注意我们计算位置的时候是hash&(length - 1) 那么如果length * 2 相当于左移了一位
也就是截取的就高了一位,如果高了一位的那个二进制正好为1,那么结果也相当于加了2倍
hash & (length * 2 - 1) = length & hash + (length - 1) & hash

我个人比较相信权威,但是我不是很理解,你这样双链表,你两个链表都要操作一次吧,所有的元素都要进行操作吧

那我为什么不用单链表,头插法搞呢???

我直接再原链表上断开元素连接,然后把新元素头插进入新位置会不会更快呢???

说干就干,来走一波!!!

搞,测试走起来。。。。

测试用例

@org.junit.jupiter.api.Test
public void testResize() { int init = 10000; for(int j = 0; j < 10; ++j) {
int size = (int) (init * Math.pow(2, j + 1)); HashMap HashMap1 = new HashMap();
long begin0 = System.currentTimeMillis();
for(int i = 0; i < size; ++i) {
HashMap1.put(i, "i" + i);
}
long end0 = System.currentTimeMillis();
System.out.print("jkd1.8(原滋原味)耗时:" + (end0 - begin0) + "\t"); MyHashMap myHashMap1 = new MyHashMap();
long begin = System.currentTimeMillis();
for(int i = 0; i < size; ++i) {
myHashMap1.put(i, "i" + i);
}
long end = System.currentTimeMillis();
System.out.print("jkd1.8(没有红黑树)耗时:" + (end - begin) + "\t"); MyHashMap myHashMap2 = new MyHashMap();
long begin2 = System.currentTimeMillis();
for(int i = 0; i < size; ++i) {
myHashMap2.put2(i, "i" + i);
}
long end2 = System.currentTimeMillis(); System.out.println("正常取余链表头插法耗时:" + (end2 - begin2));
} }

来看看结果吧。。。

这。。。

我这又懵逼了???

啥情况???

说好的大神操作呢?

我们再试2次?


 结果毫不意外的,简单的头插法的扩容效率好像比原版的效率高很多??jdk源码中的做法是不是有点过度设计了呢???

【数据结构】31、hashmap=》resize 扩容,不测不知道,一测吓一跳的更多相关文章

  1. 深入理解HashMap的扩容机制

    什么时候扩容: 网上总结的会有很多,但大多都总结的不够完整或者不够准确.大多数可能值说了满足我下面条件一的情况. 扩容必须满足两个条件: 1. 存放新值的时候当前已有元素的个数必须大于等于阈值 2. ...

  2. 数据结构之HashMap

    前言 在我们开发中,HashMap是我们非常常用的数据结构,接下来我将进一步去了解HashMap的原理.结构. 1.HashMap的实现原理 HashMap底层是基于Hash表(也称“散列”)的数据结 ...

  3. Java HashMap的扩容

    最近博主参加面试,发现自己对于Java的HashMap的扩容过程理解不足,故最近在此进行总结. 首先说明博主德Java为1.8版本 HashMap中的变量 首先要了解HashMap的扩容过程,我们就得 ...

  4. HashMap的扩容机制以及默认大小为何是2次幂

    HashMap的Put方法 回顾HashMap的put(Key k, Value v)过程: (1)对 Key求Hash值,对n-1取模计算出Hash表数组下标 (2)如果没有碰撞,直接放入桶中,即H ...

  5. HashMap resize导致死循环

    原文链接:https://blog.csdn.net/hll174/article/details/50915346 问题的症状 从前我们的Java代码因为一些原因使用了HashMap这个东西,但是当 ...

  6. 数据结构解析-HashMap

    概要 HashMap在JDK1.8之前的实现方式 数组+链表,但是在JDK1.8后对HashMap进行了底层优化,改为了由 数组+链表+红黑树实现,主要的目的是提高查找效率. 如图所示: JDK版本 ...

  7. HashMap的扩容机制---resize()

    虽然在hashmap的原理里面有这段,但是这个单独拿出来讲rehash或者resize()也是极好的. 什么时候扩容:当向容器添加元素的时候,会判断当前容器的元素个数,如果大于等于阈值---即当前数组 ...

  8. jdk1.8中hashmap的扩容resize

    当hashmap第一次插入元素.元素个数达到容量阀值threshold时,都会扩容resize(),源码: (假设hashmap扩容前的node数组为旧横向node数组,扩容后的node数组为新横向n ...

  9. jdk1.8 HashMap的扩容resize()方法详解

    /** * Initializes or doubles table size. If null, allocates in * accord with initial capacity target ...

随机推荐

  1. 树莓派挂载移动硬盘开启samba

    本文参考 [https://blog.csdn.net/u010906068/article/details/38455363],原文部分步骤在我的树莓派上,可能是版本不同吧,进行了修改后部署成功 一 ...

  2. Python 格式化输出 —— 小数转化为百分数

    比如将 0.1234 转化为 12.34% 的形式: rate = .1234 print('%.2f%%' % (rate * 100)) 第一个百分号和 .2f 相连,表示浮点数类型保留小数点后两 ...

  3. windows下安装ffmpeg

    一.下载地址: 网址:https://ffmpeg.org/ 选择Windows版本:https://ffmpeg.org/download.html#build-windows 二.解压安装: 下载 ...

  4. Optimizing concurrent accesses in a directory-based coherency protocol

    In one embodiment, the present invention includes a directory to aid in maintaining control of a cac ...

  5. 通用javascript脚本函数库

    /* 名字:Common.js 功能:通用javascript脚本函数库 包括: 1.Trim(str)--去除字符串两边的空格 2.XMLEncode(str)--对字符串进行XML编码 3.Sho ...

  6. VCL to UniGUI Migration Wizard

    Free Evaluation Edition of The Automatic Migration Scripting Wizard For Converting Legacy Delphi Cod ...

  7. matlab 工具函数(一) —— 添加指定 SNR 的噪声

    SNR=PsignalPnoise=10⋅log10∑x=1Nx∑y=1Nyf2(x,y)∑x=1Nx∑y=1Ny(f(x,y)−f^(x,y))2=20⋅log10∥f(x,y)∥∥f^(x,y)− ...

  8. 【转】opencart 源码解析

    前台控制程序列表-catalog/controller Catalog|controller|account 会员功能 |—— account.php 会员功能主頁|—— address.php 会员 ...

  9. XF 通过判断平台加载不同的图片

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  10. jquery 表格练习

    <!DOCTYPE html><html lang="en" xmlns="http://www.w3.org/1999/xhtml"> ...