• HashCode()
  • HashMap 底层实现
  1. HashMap 的长度为什么默认初始长度是16,并且每次resize()的时候,长度必须是2的幂次方?
  2. HashMap 死链问题
  3. Java 8 与 Java 7对比
  4. 为什么要使用红黑树?
  5. 说说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。

基本面试题:

  1. HashMap 基于哈希表的Map接口实现的,是以Key-Value存储形式存在;

  2. 非线程安全;

  3. key value都可以为null;

  4. HashMap中的映射不是有序的;

  5. 在 JDK1.8 中,HashMap 是由 数组+链表+红黑树构成,新增了红黑树作为底层数据结构;

  6. 当一个哈希桶存储的链表长度大于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 值的范围值-21474836482147483647,前后加起来大概40亿的映射空间,只要哈希函数映射得比较均匀松散,一般应用是很难出现碰撞的。但问题是一个40亿长度的数组,内存是放不下的。所以这个散列值是不能直接拿来用的。

所以才需要一个映射的算法。这个计算方式就是3.2中有出现的(n - 1) & hash

我们来进一步演示一下这个算法:

  1. 假设有一个key="book"

  2. 计算book的hashCode值,结果为十进制的3029737,二进制的101110001110101110 1001。

  3. 假定HashMap长度是默认的16,计算Length-1的结果为十进制的15,二进制的1111。

  4. 把以上两个结果做与运算,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 expansionsize 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链表。具体是什么意思呢?看下下面的举例。

  1. 有一个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的位置上中。

  2. 假设它们是依次插入,那么在index为5的位置上,就会有A->B->C->D->E->F这样一个链表。

  3. 当这个HashMap要进行扩容的时候,此时我们有旧数组oldTable[],容量为16,新数组newTable[],容量为32(扩容数组容量加倍)。

  4. 当遍历到旧数组index=5的位置的时候,进入到上面提到的链表处理的代码段中,对链表上的元素进行Hash & oldCapacity的操作,Hash值为5的A/B/C计算之后为0,被分到了low链表,Hash为21的D/E/F被分到了high链表。

  5. 然后把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对比

  1. 发生hash冲突时,Java7会在链表头部插入,Java8会在链表尾部插入

  2. 扩容后转移数据,Java7转移前后链表顺序会倒置,Java8还是保持原来的顺序

  3. 引入红黑树的Java8极大程度地优化了HashMap的性能‘

  4. 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 冲突的不同方案

  • 链地址法
  • 开发地址:线性探测法、平方探测法
  • 完全散列:布谷鸟散列

面试题:

1,谈谈你对HashMap 的理解

2,java中HashMap原理?面试?你是谁,你在哪?

3,你用过HashMap,你能跟我说说它的数据结构吗?

HashMap了解吗?的更多相关文章

  1. HashMap与TreeMap源码分析

    1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Ja ...

  2. HashMap的工作原理

    HashMap的工作原理   HashMap的工作原理是近年来常见的Java面试题.几乎每个Java程序员都知道HashMap,都知道哪里要用HashMap,知道HashTable和HashMap之间 ...

  3. 计算机程序的思维逻辑 (40) - 剖析HashMap

    前面两节介绍了ArrayList和LinkedList,它们的一个共同特点是,查找元素的效率都比较低,都需要逐个进行比较,本节介绍HashMap,它的查找效率则要高的多,HashMap是什么?怎么用? ...

  4. Java集合专题总结(1):HashMap 和 HashTable 源码学习和面试总结

    2017年的秋招彻底结束了,感觉Java上面的最常见的集合相关的问题就是hash--系列和一些常用并发集合和队列,堆等结合算法一起考察,不完全统计,本人经历:先后百度.唯品会.58同城.新浪微博.趣分 ...

  5. 学习Redis你必须了解的数据结构——HashMap实现

    本文版权归博客园和作者吴双本人共同所有,转载和爬虫请注明原文链接博客园蜗牛 cnblogs.com\tdws . 首先提供一种获取hashCode的方法,是一种比较受欢迎的方式,该方法参照了一位园友的 ...

  6. HashMap与HashTable的区别

    HashMap和HashSet的区别是Java面试中最常被问到的问题.如果没有涉及到Collection框架以及多线程的面试,可以说是不完整.而Collection框架的问题不涉及到HashSet和H ...

  7. JDK1.8 HashMap 源码分析

    一.概述 以键值对的形式存储,是基于Map接口的实现,可以接收null的键值,不保证有序(比如插入顺序),存储着Entry(hash, key, value, next)对象. 二.示例 public ...

  8. HashMap 源码解析

    HashMap简介: HashMap在日常的开发中应用的非常之广泛,它是基于Hash表,实现了Map接口,以键值对(key-value)形式进行数据存储,HashMap在数据结构上使用的是数组+链表. ...

  9. java面试题——HashMap和Hashtable 的区别

    一.HashMap 和Hashtable 的区别 我们先看2个类的定义 public class Hashtable extends Dictionary implements Map, Clonea ...

  10. 再谈HashMap

    HashMap是一个高效通用的数据结构,它在每一个Java程序中都随处可见.先来介绍些基础知识.你可能也知 道,HashMap使用key的hashCode()和equals()方法来将值划分到不同的桶 ...

随机推荐

  1. JS&Jquery基础之窗口对象的关系总结

    1.top    该变更永远指分割窗口最高层次的浏览器窗口.如果计划从分割窗口的最高层次开始执行命令,就可以用top变量.2.opener opener用于在window.open的页面引用执行该wi ...

  2. ORA-04045: errors during recompilation/revalidation of LBACSYS.LBAC_EVENTS

    使用orachk工具检查数据库实例的时候,发现报告里面有类似下面这样一些错误(最近有给Oracle 10g应用补丁PSU 10.2.0.5.180717,不清楚是这个产生的还是其他原因导致),使用脚本 ...

  3. MySQL 数据库查询数据,过滤重复数据保留一条数据---(MySQL中的row_number变相实现方法)

    转自: http://www.maomao365.com/?p=10564 摘要: 下文讲述MySQL数据库查询重复数据时,只保留一条数据的方法 实现思路: 在MySQL数据库中没有row_numbe ...

  4. Java Web 学习(2) —— JSP

    JSP 一. 什么是 JSP JSP 和 Servlet Servlet 有两个缺点是无法克服的:首先,写在 Servlet 中的所有 HTML 标签必须包含 Java 字符串,这使得处理HTTP响应 ...

  5. LG1131 「ZJOI2007」时态同步 树形DP

    问题描述 LG1131 题解 正难则反,把从一个点出发到叶子结点看做从叶子结点走到那个点. DP方程很显然. \(\mathrm{Code}\) #include<bits/stdc++.h&g ...

  6. go 创建自己的区块

    package main import ( "time" "crypto/sha256" "bytes" ) //区块体 type Bloc ...

  7. Centos7升级gcc极简教程

    centos7默认gcc版本为4.8,一般不满足编译需求,因此升级gcc版本为常见操作: 现有博客中,大多数教程都是基于源码重新编译安装:但是源码编译过程等待时间很长且编译麻烦. 因此,直接基于命令升 ...

  8. 1+x 证书 Web 前端开发中级理论考试(试卷 7 ) 答案

    1+x 证书 Web 前端开发中级理论考试(试卷 7 ) 答案 转载请注明来源:妙笔生花个人博客http://blog.zh66.club/index.php/archives/438/ 官方QQ群 ...

  9. IT兄弟连 HTML5教程 HTML和CSS的关系

    HTML是描述网页的标记语言,是将内容放到网页上,虽然HTML本身也自带一些样式功能,通过自身的属性,来实现一些特定的效果,制作出来的只能是一个网页,而不是一个美观的网页.最主要的是在HTML里面,一 ...

  10. IT兄弟连 Java语法教程 流程控制语句 控制循环结构1

    Java语言没有提供goto语句来控制程序的跳转,这种做法提高了程序流程控制的可读性,但降低了程序流程控制的灵活性.为了弥补这种不足,Java提供了continue和break来控制循环结构.除此之外 ...