HashMap了解吗?
- HashCode()
- HashMap 底层实现
- HashMap 的长度为什么默认初始长度是16,并且每次resize()的时候,长度必须是2的幂次方?
- HashMap 死链问题
- Java 8 与 Java 7对比
- 为什么要使用红黑树?
- 说说hashmap如何处理碰撞的,或者说说它的扩容?
一,简介
(1)桶(capacity)容量,即数组长度:DEFAULT_INITIAL_CAPACITY=1<<4;默认值为16,即在不提供有参构造的时候,声明的hashmap的桶容量;
(2)MAXIMUM_CAPACITY = 1 << 30;
极限容量,表示hashmap能承受的最大桶容量为2的30次方,超过这个容量将不再扩容,让hash碰撞起来吧!
(3)static final float DEFAULT_LOAD_FACTOR = 0.75f;
负载因子(loadfactor,默认0.75),负载因子有个奇特的效果,表示当当前容量大于(size/)时,将进行hashmap的扩容,扩容一般为扩容为原来的两倍。
(4)int threshold;阈值(yu)
阈值算法为capacity*loadfactory,大致当map中entry数量大于此阈值时进行扩容(1.8)
(5)transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;(默认为空{})
核心的数据结构,即所谓的数组+链表的部分。
(1)hashmap的数据结构是什么样子的?自己如何实现一个hashmap?
主要数据结构即为数组+链表。
在hashmap中的主要表现形式为一个table,类型为Entry<K,V>[] table 首先是一个Entry型的数组,Entry为hashmap的内部类:
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
int hash;
}
在这里可以看到,在Entry类中存在next,所以,它又是链表的形式。 这就是hashmap的主要数据结构。
(2)hash的计算规则,这又要看源码了:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
这是1.8的源码,1.7太复杂但原理是一致的,简单说这就是个“扰动函数”,最终的目的是让散列分布地更加均匀。
算法就是拿存储key的hashcode值先右移16位,再与hashcode值进行异或操作,即不求进位只求按位相加的值:

最后是如何获得,本key在table中的位置呢?本身应该是取得了hash进行取模除取余运算,但是,源码:
1 static int indexFor(int h, int length) {
2 // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
3 return h & (length-1);
4 }
为什么又做了个与运算求得位置呢?简单说,它的意义和取余一致。
不信可以自己算一下。
首先说,他利用了table的长度肯定是2的整数次幂的原理,假设当前length为16,2的4次方
而与&运算,又是只求进位运算,比如1111&110001结果为000001
只求进位运算(&),保证算出的结果一定在table的length之内,最大为1111。
故而,它的运算结果与价值等同于取余运算,并且即使不管hash值有多大都可以算出结果,并且在length之内。
并且,这种类型的运算,能够更加的节约计算机资源,少了加(计算机所有运算都是加运行)运算过程,更加地节省资源。
4、hashmap的存取过程
/**
*往hashmap中放数据
*/
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);//判断如果为空table,先对table进行构造
//构造通过前面的几个参数
}
//首先判断key是否为null,为null也可以存
//这里需要记住,null的key一定放在table的0号位置
if (key == null)
return putForNullKey(value);
//算出key的hash值
int hash = hash(key);
//根据hash值算出在table中的位置
int i = indexFor(hash, table.length);
//放入K\V,遍历链表,如果位置上存在相同key,进行替换value为新的,且将替换的旧的value返回
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
//增加一个entry,有两种情况,1、如果此位置存在entry,将此位置变为插入的entry,且将插入entry的next节点变为原来的entry;2、如果此位置不存在entry则直接插入新的entry
addEntry(hash, key, value, i);
return null;
}
取数据:
//根据key获得一个entry
public V get(Object key) {
//如果key为null,获取0号位的切key为null的值
if (key == null)
return getForNullKey();
//如果不是,获取entry,在下面方法
Entry<K,V> entry = getEntry(key);
//合法性判断
return null == entry ? null : entry.getValue();
}
//获取一个key不为null的entry
final Entry<K,V> getEntry(Object key) {
//如果table为null,则返回null
if (size == 0) {
return null;
}
//计算hash值
int hash = (key == null) ? 0 : hash(key);
//根据hash值获得table的下标,遍历链表,寻找key,找到则返回
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
5.扩容和碰撞
先说碰撞吧,由于hashmap在存值的时候并不是直接使用的key的hashcode,而是通过扰动函数算出了一个新的hash值,这个计算出的hash值可以明显的减少碰撞。
还有一种解决碰撞的方式就是扩容,扩容其实很好理解,就是将原来桶的容量扩为原来的两倍。这样争取散列的均匀,比如:
原来桶的长度为16,hash值为1和17的entry将会都在桶的0号位上,这样就出现了碰撞,而当桶扩容为原来的2倍时,hash值为1和17的entry分别在1和17号位上,整号岔开了碰撞。
(1)在Object 类中,hashCode()方法是一个被native修饰的类,Java Doc中描述的是返回该对象的哈希值。
那么哈希值这个返回值是有什么作用呢?
主要是保证基于散列的集合,如HashSet、HashMap以及HashTable等,在插入元素时保证元素不可重复,同时为了提高元素的插入删除便利效率而设计;主要是为了查找的便捷性而存在。
拿Set进行举例,
问题:众所周知,Set集合是不能重复,如果每次添加数据都拿新元素去和集合内部元素进行逐一地equal()比较,那么插入十万条数据的效率可以说是非常低的。
方案:所以在添加数据的时候就出现了哈希表的应用,哈希算法也称之为散列算法,当添加一个值的时候,先去计算出它的哈希值,根据算出的哈希值将数据插入指定位置。这样的话就避免了一直去使用equal()比较的效率问题。
具体表现在:
- 如果指定位置为空,则直接添加
- 如果指定位置不为空,调用equal() 判断两个元素是否相同,如果相同则不存储
上述第二种情况中,如果两个元素不相同,但是hashCode()相同,那就是发生了我们所谓的哈希碰撞。
哈希碰撞的概率取决于hashCode()计算方式和空间容量的大小。
HashMap为什么要用链表?:这种情况下,会在相同的位置,创建一个链表,把key值相同的元素存放到链表中。

在HashMap中就是使用拉链法来解决hashCode冲突。
解决hash冲突的办法之一:拉链法
小总结:
hashCode是一个对象的标识,Java中对象的hashCode是一个int类型值。通过hashCode来指定数组的索引可以快速定位到要找的对象在数组中的位置,之后再遍历链表找到对应值,理想情况下时间复杂度为O(1),并且不同对象可以拥有相同的hashCode。
基本面试题:
HashMap 基于哈希表的Map接口实现的,是以Key-Value存储形式存在;
非线程安全;
key value都可以为null;
HashMap中的映射不是有序的;
在 JDK1.8 中,HashMap 是由 数组+链表+红黑树构成,新增了红黑树作为底层数据结构;
当一个哈希桶存储的链表长度大于8 会将链表转换成红黑树,小于6时则从红黑树转换成链表;
(1)存储结构
在 JDK1.8 中,HashMap 是由 数组+链表+红黑树构成,新增了红黑树作为底层数据结构。
通过哈希来确认到数组的位置,如果发生哈希碰撞就以链表的形式存储 ,但是这样如果链表过长来的话,HashMap会把这个链表转换成红黑树来存储,阈值为8。
下面是HashMap的结构图:

(2)重要属性
2.1 table
/**
* 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;
在JDK1.8中我们了解到HashMap是由数组加链表加红黑树来组成的结构其中table就是HashMap中的数组。
2.2 size
/**
* The number of key-value mappings contained in this map.
*/
transient int size;
HashMap中 键值对存储数量。
2.3 loadFactor
/**
* The load factor for the hash table.
*
* @serial
*/
final float loadFactor;
负载因子。负载因子是权衡资源利用率与分配空间的系数。当元素总量(threshold) > 数组长度 * 负载因子时会进行扩容操作。
2.4 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;
扩容阈值。threshold = 数组长度 * 负载因子。超过后执行扩容操作。
2.5 TREEIFY_THRESHOLD/UNTREEIFY_THRESHOLD____treeify_threshold/untreeify_threshold
/**
* The bin count threshold for using a tree rather than list for a
* bin. Bins are converted to trees when adding an element to a
* bin with at least this many nodes. The value must be greater
* than 2 and should be at least 8 to mesh with assumptions in
* tree removal about conversion back to plain bins upon
* shrinkage.
*/
static final int TREEIFY_THRESHOLD = 8; /**
* The bin count threshold for untreeifying a (split) bin during a
* resize operation. Should be less than TREEIFY_THRESHOLD, and at
* most 6 to mesh with shrinkage detection under removal.
*/
static final int UNTREEIFY_THRESHOLD = 6;
树形化阈值。当一个哈希桶存储的链表长度大于8 会将链表转换成红黑树,小于6时则从红黑树转换成链表。
3. 增加元素
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
3.1 hash()
可以看到实际执行添加元素的是putVal()操作,在执行putVal()之前,先是对key执行了hash()方法,让我们看下里面做了什么
static final int hash(Object key) {
int h;
// key.hashCode():返回散列值也就是hashcode
// ^ :按位异或
// >>>:无符号右移,忽略符号位,空位都以0补齐
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
key==null说明,HashMap中是支持key为null的情况的。
同样的方法在Hashstable中是直接用key来获取hashCode,没有key==null的判断,所以Hashstable是不支持key为null的。
再回来说这个hash()方法。这个方法用专业术语来称呼就叫做扰动函数。
使用hash()也就是扰动函数,是为了防止一些实现比较差的hashCode()方法。换句话来说,就是为了减少哈希碰撞。
JDK 1.8 的 hash方法 相比于 JDK 1.7 hash 方法更加简化,但是原理不变。我们再看下JDK1.7中是怎么做的。
// code in JDK1.7
static int hash(int h) {
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
相比于 JDK1.8 的 hash 方法 ,JDK 1.7 的 hash 方法的性能会稍差一点点,因为毕竟扰动了 4 次。
3.2 putVal()
再来看真正执行增加元素操作的putVal()方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
// 当数组为空或长度为0,初始化数组容量(resize() 方法是初始化或者扩容用的)
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 计算数组下标 i = (n-1) & hash
// 如果这个位置没有元素,则直接创建Node并存值
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))))
// hash值、key值相等,用e变量获取到当前位置这个元素的引用,后面用于替换已有的值
e = p;
else if (p instanceof TreeNode)
// 当前是以红黑树方式存储,执行其特有的putVal方法 -- putTreeVal
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)
// onlyIfAbsent 如果为true - 不覆盖已存在的值
// 把新值赋值进去
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
// 记录修改次数
++modCount;
// 判断元素数量是否超过阈值 超过则扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
3.3 HashMap 的长度为什么默认初始长度是16,并且每次resize()的时候,长度必须是2的幂次方?
这是一个常见的面试题。这个问题描述的设计,实际上为了服务于从Key映射到数组下标index的Hash算法。
前面提到了,我们为了让HashMap存储高效,应该尽量减少哈希碰撞,也就是说,应该让元素分配得尽可能均匀。
Hash 值的范围值
-2147483648到2147483647,前后加起来大概40亿的映射空间,只要哈希函数映射得比较均匀松散,一般应用是很难出现碰撞的。但问题是一个40亿长度的数组,内存是放不下的。所以这个散列值是不能直接拿来用的。所以才需要一个映射的算法。这个计算方式就是3.2中有出现的
(n - 1) & hash。
我们来进一步演示一下这个算法:
假设有一个
key="book"计算
book的hashCode值,结果为十进制的3029737,二进制的101110001110101110 1001。假定HashMap长度是默认的16,计算Length-1的结果为十进制的15,二进制的1111。
把以上两个结果做与运算,101110001110101110 1001 & 1111 = 1001,十进制是9,所以 index=9。
通过这种与运算的方式,能够和取模运算一样的效果hashCode % length,在上述例子中就是3029737 % 16=9。
并且通过位运算的方式大大提高了性能。
可能到这里,你还是不知道为什么长度必须是2的幂次方,也是因为这种位运算的方法。
答案: **长度16或者其他2的幂,Length-1的值是所有二进制位全为1,这种情况下,index的结果等同于HashCode后几位的值。只要输入的HashCode本身分布均匀,Hash算法的结果就是均匀的。
**如果HashMap的长度不是2的幂次方,会出现某些index永远不会出现的情况,这个显然不符合均匀分布的原则和期望。所以在源码里面一直都在强调
power-of-two expansion和size must be power of two。
另外,HashMap 构造函数允许用户传入的容量不是 2 的 n 次方,因为它可以自动地将传入的容量转换为 2 的 n 次方。
/**
* 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;
}
4. HashMap 扩容
接下来我们来讲讲HashMap扩容相关的知识。
4.1 扩容
HashMap的初始长度是16,假设HashMap中的键值对一直在增加,但是table数组容量一直不变,那么就会发生哈希碰撞,查找的效率肯定会越来越低。所以当键值对数量超过某个阈值的时候,HashMap就会执行扩容操作。
那么扩容的阈值是怎么计算的呢?
阈值 = 数组长度 * 负载因子
threshold = capacity * loadFactor
每次扩容后,threshold 加倍
上述计算就出现在resize()方法中。下面会详细解析这个方法。我们先继续往下讲。
loadFactor这个参数,我们之前提到过,负载因子是权衡资源利用率与分配空间的系数。至于为什么是0.75呢?这个实际上就是一个作者认为比较好的权衡,当然你也可以通过构造方法手动设置负载因子 。
public HashMap(int initialCapacity, float loadFactor) {...)。接下去再来到这里的主角resize()方法
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;
}
// newCap 变成原来的 两倍
else 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
// 初始阈值被手动设置过
// 数组容量 = 初始阈值
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引用的数组
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;
}
上述代码红黑树和链表的处理不知道大家看懂了没有,我反正在第一次看的时候有点晕乎。但是理解了之后有感觉非常的巧妙。
拿链表处理打比方,它干的就是把在遍历旧的table数组的时候,把该位置的链表分成high链表和low链表。具体是什么意思呢?看下下面的举例。
有一个size为16的HashMap。有A/B/C/D/E/F六个元素,其中A/B/C的Hash值为5,D/E/F的Hash值为21,我们知道计算数组下标的方法是与运算(效果相当于取模运算),这样计算出来,A/B/C/D/E/F的index = 5,都会被存在index=5的位置上中。
假设它们是依次插入,那么在index为5的位置上,就会有
A->B->C->D->E->F这样一个链表。当这个HashMap要进行扩容的时候,此时我们有旧数组oldTable[],容量为16,新数组newTable[],容量为32(扩容数组容量加倍)。
当遍历到旧数组index=5的位置的时候,进入到上面提到的链表处理的代码段中,对链表上的元素进行
Hash & oldCapacity的操作,Hash值为5的A/B/C计算之后为0,被分到了low链表,Hash为21的D/E/F被分到了high链表。然后把low链表放入新数组的index=5的位置,把high链表放入到新数组的index=5+16=21的位置。
红黑树相关的操作虽然代码不同,但是实际上要干的事情是一样的。就是把相同位置的不同Hash大小的链表元素在新table数组中进行分离。希望讲到这里你能听懂。
4.2 HashMap 死链问题
Java7的HashMap会存在死循环的问题,主要原因就在于,Java7中,HashMap扩容转移后**,前后链表顺序倒置,在转移过程中其他线程修改了原来链表中节点的引用关系,导致在某Hash桶位置形成了环形链表,此时get(key),如果key不存在于这个HashMap且key的Hash结果等于那个形成了循环链表的Hash位置,那么程序就会进入死循环**;
Java8在同样的前提下并不会引起死循环,原因是Java8扩容转移后前后链表顺序不变,保持之前节点的引用关系。
5. Java 8 与 Java 7对比
发生hash冲突时,Java7会在链表头部插入,Java8会在链表尾部插入
扩容后转移数据,Java7转移前后链表顺序会倒置,Java8还是保持原来的顺序
引入红黑树的Java8极大程度地优化了HashMap的性能‘
put 操作达到阈值时,Java7中是先扩容再新增元素,Java8是先新增元素再扩容;
6. HashMap 遍历方式
// 遍历方式1
Iterator<Map.Entry<String, Integer>> entryIterator = map.entrySet().iterator();
while (entryIterator.hasNext()) {
Map.Entry<String, Integer> next = entryIterator.next();
System.out.println("key=" + next.getKey() + " value=" + next.getValue());
} // 遍历方式2
Iterator<String> iterator = map.keySet().iterator();
while (iterator.hasNext()){
String key = iterator.next();
System.out.println("key=" + key + " value=" + map.get(key)); }
这里建议使用,第一种方式进行遍历。
第一种可以把 key value 同时取出,第二种还得需要通过 key 取一次 value,效率较低。
7. 为什么要使用红黑树?
很多人可能都会答上一句,为了提高查找性能,但更确切地来说的话,采用红黑树的方法是为了提高在极端哈希冲突的情况下提高HashMap的性能。
三、结语
知识的学习应该是相互穿插并且印证,HashMap实际上和其他的Map有很多交叉的实现原理,比如ConcurrentHashMap大致原理和HashMap相同,只是前者使用分段锁确保线程安全,Hashstable和HashMap底层原理也很相似,只是Hashstable使用synchronized做同步,并且官方在Hashstable出来之后就没有再去更新过,属于过时的类;HashMap和TreeMap底层都涉及到了红黑树。
拓展一:解决Hash 冲突的不同方案
- 链地址法
- 开发地址:线性探测法、平方探测法
- 完全散列:布谷鸟散列
面试题:
HashMap了解吗?的更多相关文章
- HashMap与TreeMap源码分析
1. 引言 在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Ja ...
- HashMap的工作原理
HashMap的工作原理 HashMap的工作原理是近年来常见的Java面试题.几乎每个Java程序员都知道HashMap,都知道哪里要用HashMap,知道HashTable和HashMap之间 ...
- 计算机程序的思维逻辑 (40) - 剖析HashMap
前面两节介绍了ArrayList和LinkedList,它们的一个共同特点是,查找元素的效率都比较低,都需要逐个进行比较,本节介绍HashMap,它的查找效率则要高的多,HashMap是什么?怎么用? ...
- Java集合专题总结(1):HashMap 和 HashTable 源码学习和面试总结
2017年的秋招彻底结束了,感觉Java上面的最常见的集合相关的问题就是hash--系列和一些常用并发集合和队列,堆等结合算法一起考察,不完全统计,本人经历:先后百度.唯品会.58同城.新浪微博.趣分 ...
- 学习Redis你必须了解的数据结构——HashMap实现
本文版权归博客园和作者吴双本人共同所有,转载和爬虫请注明原文链接博客园蜗牛 cnblogs.com\tdws . 首先提供一种获取hashCode的方法,是一种比较受欢迎的方式,该方法参照了一位园友的 ...
- HashMap与HashTable的区别
HashMap和HashSet的区别是Java面试中最常被问到的问题.如果没有涉及到Collection框架以及多线程的面试,可以说是不完整.而Collection框架的问题不涉及到HashSet和H ...
- JDK1.8 HashMap 源码分析
一.概述 以键值对的形式存储,是基于Map接口的实现,可以接收null的键值,不保证有序(比如插入顺序),存储着Entry(hash, key, value, next)对象. 二.示例 public ...
- HashMap 源码解析
HashMap简介: HashMap在日常的开发中应用的非常之广泛,它是基于Hash表,实现了Map接口,以键值对(key-value)形式进行数据存储,HashMap在数据结构上使用的是数组+链表. ...
- java面试题——HashMap和Hashtable 的区别
一.HashMap 和Hashtable 的区别 我们先看2个类的定义 public class Hashtable extends Dictionary implements Map, Clonea ...
- 再谈HashMap
HashMap是一个高效通用的数据结构,它在每一个Java程序中都随处可见.先来介绍些基础知识.你可能也知 道,HashMap使用key的hashCode()和equals()方法来将值划分到不同的桶 ...
随机推荐
- 纸壳CMS现已支持自定义扩展字段
简介 纸壳CMS是开源免费的可视化内容管理系统. GitHub https://github.com/SeriaWei/ZKEACMS 自定义字段 纸壳CMS现已支持自定义字段,在不修改代码的情况下, ...
- 科研画图:散点连接并平滑(基于Matlab和Python)
导师要求参照别人论文中的图(下图),将其论文中的图画美观些,网上关于科研画图相关的代码比较少,就自己鼓捣了下. 附上自己整合验证过的代码: 功能:将散点连接并平滑 1)Matlab 效果图: x1=[ ...
- Vue你不知到的$this.emit()的用法
需求 需求:除了拿到false,还要拿到v-for中的index 如何解决:再使用一次父传子,:a="index" 将下标值传递给子组件 注意要加引号 <expert ...
- 【西北师大-2108Java】第五次作业成绩汇总
[西北师大-2108Java]第五次作业成绩汇总 作业题目 面向对象程序设计(JAVA) 第7周学习指导及要求 实验目的与要求 (1)掌握四种访问权限修饰符的使用特点: (2)掌握Object类的用途 ...
- WPF 精修篇 page
原文:WPF 精修篇 page 前言 前段时间看UML 大象 这本书 虽然马上看到了精华片 最后还是暂时暂停 因为这本书 很好 但是暂时对现在的我来说 有点超前 很多东西理解起来还是很难 但是 这本书 ...
- 部署Springboot项目到Linux云服务器
前言 环境:IDEA.Springboot.Maven(自己电脑安装的Maven) 一.打包jar包 检查自己的pom.xml文件下面有无Maven的依赖插件,即有无如下: <build> ...
- ubuntu升级pip报cannot import name 'main'解决方法
执行sudo vi /usr/bin/pip 将代码: from pip import main if __name__ == '__main__': sys.exit(main()) 修改为: fr ...
- Codeforces Round #602 (Div. 2, based on Technocup 2020 Elimination Round 3) E. Arson In Berland Forest 二分 前缀和
E. Arson In Berland Forest The Berland Forest can be represented as an infinite cell plane. Every ce ...
- VS 2017 + OpenCV + Spinnaker SDK(PointGrey) 配置
1. OpenCV 配置 1.1 下载 opencv 源码,并将其添加至环境变量 D:\opencv4.1\build\x64\vc15\bin 注:vs2015 选 vc14,vs2017 选 vc ...
- Python爬虫教程-实现百度翻译
使用python爬虫实现百度翻译功能python爬虫实现百度翻译: python解释器[模拟浏览器],发送[post请求],传入待[翻译的内容]作为参数,获取[百度翻译的结果] 通过开发者工具,获取发 ...