1. HashMap的数据结构

数据结构中有数组和链表来实现对数据的存储,但这两者基本上是两个极端。

数组

数组存储区间是连续的,占用内存严重,故空间复杂的很大。但数组的二分查找时间复杂度小,为O(1);数组的特点是:寻址容易,插入和删除困难;

链表

链表存储区间离散,占用内存比较宽松,故空间复杂度很小,但时间复杂度很大,达O(N)。链表的特点是:寻址困难,插入和删除容易。

哈希表

那么我们能不能综合两者的特性,做出一种寻址容易,插入删除也容易的数据结构?答案是肯定的,这就是我们要提起的哈希表。哈希表((Hash table)既满足了数据的查找方便,同时不占用太多的内容空间,使用也十分方便。

哈希表有多种不同的实现方法,我接下来解释的是最常用的一种方法—— 拉链法,我们可以理解为“链表的数组” ,如图:

  HashMap其实也是一个线性的数组实现的,所以可以理解为其存储数据的容器就是一个线性数组。这可能让我们很不解,一个线性的数组怎么实现按键值对来存取数据呢?这里HashMap有做一些处理。

  首先HashMap里面实现一个静态内部类Entry,其重要的属性有 key , value, next,从属性key,value我们就能很明显的看出来Entry就是HashMap键值对实现的一个基础bean,我们上面说到HashMap的基础就是一个线性数组,这个数组就是Entry[],Map里面的内容都保存在Entry[]里面。

    /**
     * The table, resized as necessary. Length MUST Always be a power of two.
     */

transient Entry[] table;

Entry的构造函数

Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}

HashMap里面用到链式数据结构的一个概念。上面我们提到过Entry类里面有一个next属性,作用是指向下一个Entry。打个比方, 第一个键值对A进来,通过计算其key的hash得到的index=0,记做:Entry[0] = A。一会后又进来一个键值对B,通过计算其index也等于0,现在怎么办?HashMap会这样做:B.next = A,Entry[0] = B,如果又进来C,index也等于0,那么C.next = B,Entry[0] = C;这样我们发现index=0的地方其实存取了A,B,C三个键值对,他们通过next这个属性链接在一起。所以疑问不用担心。也就是说数组中存储的是最后插入的元素。到这里为止,HashMap的大致实现,我们应该已经清楚了。

2. HashMap的具体实现

package java.util;
import java.io.*;
publicclass HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
    /*
HashMap 的实例有两个参数影响其性能:初始容量 和加载因子。
容量是哈希表中桶的数量,初始容量只是哈希表在创建时的容量。
加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度。
当哈希表中的条目数超出了加载因子与当前容量的乘积时,
则要对该哈希表进行 rehash 操作(即重建内部数据结构),
从而哈希表将具有大约两倍的桶数。
加载因子默认值为0.75,默认哈希表容量为16
*/
    //初始化容量16 hashMap的容量必须是2的指数倍,Hashtable是11
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
//最大容量2的30次方
static final int MAXIMUM_CAPACITY = 1 << 30;
//默认加载因子默认的平衡因子为0.75,这是权衡了时间复杂度与空间复杂度之后的最好取值(JDK说是最好的),过高的因子会降低存储空间但是查找(lookup,包括HashMap中的put与get方法)的时间就会增加。
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//用来存储键值对的Entry数组,用于设置刚刚初始化的HashMap对象,用来减少存储空间
static final Entry<?,?>[] EMPTY_TABLE = {};
//大小必须是2的倍数
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
//存储的键值对的数目
transient int size;
//阈值,当size超过threshold时,table将会扩容. //threshold = capacity * loadFactor
int threshold;
//加载因子
final float loadFactor;
//修改次数,用于检查线程是否同步
transient int modCount;
//默认的阀值
static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE; privatestaticclass Holder {staticfinalint ALTERNATIVE_HASHING_THRESHOLD;
static {
//获取jdk内置的阀值
String altThreshold = java.security.AccessController.doPrivileged(
new sun.security.action.GetPropertyAction(
"jdk.map.althashing.threshold")); int threshold;
try {
//设置当前阀值
threshold = (null != altThreshold)
? Integer.parseInt(altThreshold)
: ALTERNATIVE_HASHING_THRESHOLD_DEFAULT;
// disable alternative hashing if -1 if (threshold == -1) {
threshold = Integer.MAX_VALUE;
}
if (threshold < 0) {
thrownew IllegalArgumentException("value must be positive integer.");
}
} catch(IllegalArgumentException failed) {
thrownew Error("Illegal value for 'jdk.map.althashing.threshold'", failed);
}
ALTERNATIVE_HASHING_THRESHOLD = threshold;
}
} //使用初始化容量和加载因子初始化HashMap public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
thrownew IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
thrownew IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
threshold = initialCapacity;
init();
} public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
} public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
} /*
* Constructs a new HashMap with the same mappings as the
* specified Map. The HashMap is created with
* default load factor (0.75) and an initial capacity sufficient to
* hold the mappings in the specified Map.
*/public HashMap(Map<? extends K, ? extends V> m) {
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
inflateTable(threshold); putAllForCreate(m);
} /**
* A randomizing value associated with this instance that is applied to
* hash code of keys to make hash collisions harder to find.
If 0 then alternative hashing is disabled.
*/
transient int hashSeed = 0; //工具函数,将number扩展成2的倍数
privatestaticint roundUpToPowerOf2(int number) {
// assert number >= 0 : "number must be non-negative"; int rounded = number >= MAXIMUM_CAPACITY
? MAXIMUM_CAPACITY
: (rounded = Integer.highestOneBit(number)) != 0
? (Integer.bitCount(number) > 1) ? rounded << 1 : rounded
: 1; return rounded;
} //将表格大小扩展到toSize
privatevoid inflateTable(int toSize) {
// Find a power of 2 >= toSize int capacity = roundUpToPowerOf2(toSize);
//重新设置阀值
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
//重新设置table
table = new Entry[capacity];
//根据capacity初始化hashSeed
initHashSeedAsNeeded(capacity);
} // internal utilities void init() {
} /**
* Initialize the hashing mask value. We defer initialization until we
* really need it.
*/
finalboolean initHashSeedAsNeeded(int capacity) {
boolean currentAltHashing = hashSeed != 0;
//根据系统函数得到一个hash boolean useAltHashing = sun.misc.VM.isBooted() &&
(capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
boolean switching = currentAltHashing ^ useAltHashing;
//如果hashSeed初始化为0则跳过switching //否则使用系统函数得到新的hashSeed if (switching) {
hashSeed = useAltHashing
? sun.misc.Hashing.randomHashSeed(this)
: 0;
}
return switching;
} /*
哈希算法的核心:哈希函数
* Retrieve object hash code and applies a supplemental hash function to the
* result hash, which defends against poor quality hash functions. This is
* critical because HashMap uses power-of-two length hash tables, that
* otherwise encounter collisions for hashCodes that do not differ
* in lower bits. Note: Null keys always map to hash 0, thus index 0.
*/ */
finalint hash(Object k) {
int h = hashSeed;
//通过hashSeed初始化的值的不同来选择不同的hash方式
if (0 != h && k instanceof String) {
//String类采用不同的hash函数
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
} //Returns index for hash code h.通过得到的hash值来确定它在table中的位置
staticint indexFor(int h, int length) {
// assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
return h & (length-1);
} publicint size() {
return size;
} publicboolean isEmpty() {
return size == 0;
} public V get(Object key) {
if (key == null)
return getForNullKey();
Entry<K,V> entry = getEntry(key);//查看调用函数,在下面 returnnull == entry ? null : entry.getValue();
} private V getForNullKey() {
if (size == 0) {
returnnull;
}
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null)
return e.value;
}
returnnull;
} publicboolean containsKey(Object key) {
return getEntry(key) != null;
} final Entry<K,V> getEntry(Object key) {
if (size == 0) {
returnnull;
}
//通过key的hash值确定table下标(null对应下标0) int hash = (key == null) ? 0 : hash(key);
//indexFor() = h & (length-1) = hash&(table.length-1) for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next)
//对冲突的处理办法是将线性探查,即将元素放到冲突位置的下一个可用位置上
{
Object k;
/*注意:因为元素可能不是刚好存在它对应hash值得下一个位置
(如果该位置之前有元素,则要放在下两个的位置,以此类推)
*/if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
//所以不仅要判断hash还要判断key(因为不同的key可能有相同的hash值) return e;
}
returnnull;
} /*
* 1. 通过key的hash值确定table下标
* 2. 查找table下标,如果key存在则更新对应的value
* 3. 如果key不存在则调用addEntry()方法
*/public V put(K key, V value) {
if (table == EMPTY_TABLE) {
//初始化存储表空间
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
/*
注意:
我不断的寻找,hash值对应位置之后的可用位置在哪里
*/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;
}
}
//上面的循环结束表示当前的key不存在与表中,需要另外增加
modCount++;
addEntry(hash, key, value, i);//函数在下面 returnnull;
} /*
为减少篇幅,删除了一些功能实现类似的方法
大家可以自行阅读分析
*//**
* Transfers all entries from current table to newTable.
*/void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;
//是否重新进行hash计算 if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
} //扩展到指定的大小
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
} Entry[] newTable = new Entry[newCapacity];
//重新hash
transfer(newTable, initHashSeedAsNeeded(newCapacity));
table = newTable;
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
} //Entry类就是一个简单的键值对的类
static class Entry<K,V> implements Map.Entry<K,V> {final K key;
V value;
Entry<K,V> next;//这是一种类似指针的东西 int hash;//还要存放hash值 /*
下面是一些十分基本的构造函数以及get,set方法
*/
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
} publicfinal K getKey() {
return key;
} publicfinal V getValue() {
return value;
} publicfinal V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
} //必须要key和value都一样才equals
public final boolean equals(Object o) {
if (!(o instanceof Map.Entry))
returnfalse;
Map.Entry e = (Map.Entry)o;
Object k1 = getKey();
Object k2 = e.getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
Object v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
returntrue;
}
returnfalse;
} publicfinalint hashCode() {
return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
} publicfinal String toString() {
return getKey() + "=" + getValue();
} /**
* This method is invoked whenever the value in an entry is
* overwritten by an invocation of put(k,v) for a key k that's already
* in the HashMap.
*/void recordAccess(HashMap<K,V> m) {
} /**
* This method is invoked whenever the entry is
* removed from the table.
*/void recordRemoval(HashMap<K,V> m) {
}
} //根据需要,可能要扩容 //由于它由Put函数调用,调用之前已经确定表中没有key的记录 //addEntry默认当前表中没有指定key的记录,直接增加记录
void addEntry(int hash, K key, V value, int bucketIndex) {
//计算存放位置 if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);//将容量翻倍
hash = (null != key) ? hash(key) : 0;
//寻找指定hash值对应的存放位置
bucketIndex = indexFor(hash, table.length);
} createEntry(hash, key, value, bucketIndex);
} //由于默认没有key的记录,所以直接增加
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
} //类似于Entry数组的迭代器,主要是对table进行操作
private abstract class HashIterator<E> implements Iterator<E> {
Entry<K,V> next; // next entry to return int expectedModCount; // For fast-fail intindex; // current slot
Entry<K,V> current; // current entry HashIterator() {
expectedModCount = modCount;
if (size > 0) { // advance to first entry
Entry[] t = table;
while (index < t.length && (next = t[index++]) == null)
;
}
} publicfinalboolean hasNext() {
return next != null;
} final Entry<K,V> nextEntry() {
if (modCount != expectedModCount)
thrownew ConcurrentModificationException();
Entry<K,V> e = next;
if (e == null)
thrownew NoSuchElementException(); if ((next = e.next) == null) {
Entry[] t = table;
while (index < t.length && (next = t[index++]) == null)
;
}
current = e;
return e;
} publicvoid remove() {
if (current == null)
thrownew IllegalStateException();
if (modCount != expectedModCount)
thrownew ConcurrentModificationException();
Object k = current.key;
current = null;
HashMap.this.removeEntryForKey(k);
expectedModCount = modCount;
}
} privatefinalclass ValueIterator extends HashIterator<V> {public V next() {
return nextEntry().value;
}
} privatefinalclass KeyIterator extends HashIterator<K> {public K next() {
return nextEntry().getKey();
}
} privatefinalclass EntryIterator extends HashIterator<Map.Entry<K,V>> {public Map.Entry<K,V> next() {
return nextEntry();
}
} // Subclass overrides these to alter behavior of views' iterator() method
Iterator<K> newKeyIterator() {
returnnew KeyIterator();
}
Iterator<V> newValueIterator() {
returnnew ValueIterator();
}
Iterator<Map.Entry<K,V>> newEntryIterator() {
returnnew EntryIterator();
} // Views private transient Set<Map.Entry<K,V>> entrySet = null; /**
* Returns a link Set view of the keys contained in this map.
*/public Set<K> keySet() {
Set<K> ks = keySet;
return (ks != null ? ks : (keySet = new KeySet()));
} privatefinalclass KeySet extends AbstractSet<K> {public Iterator<K> iterator() {
return newKeyIterator();
}
publicint size() {
return size;
}
publicboolean contains(Object o) {
return containsKey(o);
}
publicboolean remove(Object o) {
return HashMap.this.removeEntryForKey(o) != null;
}
publicvoid clear() {
HashMap.this.clear();
}
} /**
* Returns a Collection view of the values contained in this map.
*/public Collection<V> values() {
Collection<V> vs = values;
return (vs != null ? vs : (values = new Values()));
} privatefinalclass Values extends AbstractCollection<V> {public Iterator<V> iterator() {
return newValueIterator();
}
publicint size() {
return size;
}
publicboolean contains(Object o) {
return containsValue(o);
}
publicvoid clear() {
HashMap.this.clear();
}
} /**
return a set view of the mappings contained in this map
*/public Set<Map.Entry<K,V>> entrySet() {
return entrySet0();
} private Set<Map.Entry<K,V>> entrySet0() {
Set<Map.Entry<K,V>> es = entrySet;
return es != null ? es : (entrySet = new EntrySet());
} privatefinalclass EntrySet extends AbstractSet<Map.Entry<K,V>> {public Iterator<Map.Entry<K,V>> iterator() {
return newEntryIterator();
}
publicboolean contains(Object o) {
if (!(o instanceof Map.Entry))
returnfalse;
Map.Entry<K,V> e = (Map.Entry<K,V>) o;
Entry<K,V> candidate = getEntry(e.getKey());
return candidate != null && candidate.equals(e);
}
publicboolean remove(Object o) {
return removeMapping(o) != null;
}
publicint size() {
return size;
}
publicvoid clear() {
HashMap.this.clear();
}
}
}

Hashtable是HashMap的线程安全版本,它的实现和HashMap实现基本一致,除了它不能包含null值的key和value,并且它在计算hash值和数组索引值的方式要稍微简单一些。 
Hashtable线程安全实现方式是将所有方法都标记成synchronized,但这样加锁的粒度大,容易引起一些性能问题,所以目使用java.concurrent.ConcurrentHashMap类性能更佳

在JDK1.7之后,HashMap和HashTable的哈希函数都一样了,但由hash值转换成表索引的方式不一样:

    • HashMap使用&位操作 : h & (length-1);
    • HashTable使用取余操作 : (hash & 0x7FFFFFFF) % tab.length;

     上面的hash()方法和indexFor()是hashMap当中的一个重点。

看到这么多位操作,是不是觉得晕头转向了呢,还是搞清楚原理就行了,毕竟位操作速度是很快的,不能因为不好理解就不用了。 
在哈希表容量(也就是buckets或slots大小)为length的情况下,为了使每个key都能在冲突最小的情况下映射到[0,length)(注意是左闭右开区间)的索引(index)内,一般有两种做法:

  • 方法1:让length为素数,然后用hashCode(key) mod length的方法得到索引
  • 方法2:让length为2的指数倍,然后用hashCode(key) & (length-1)的方法得到索引

HashTable用的是方法1,HashMap用的是方法2。重点说说方法2的情况,方法2其实也比较好理解: 
因为length为2的指数倍,所以length-1所对应的二进制位都为1,然后在与hashCode(key)做与运算,即可得到[0,length)内的索引。但是这里有个问题,如果hashCode(key)的大于length的值,而且hashCode(key)的二进制位的低位变化不大,那么冲突就会很多,举个例子: 
Java中对象的哈希值都32位整数,而HashMap默认大小为16,那么有两个对象那么的哈希值分别为:0xABAB0000与0xBABA0000,它们的后几位都是一样,那么与16异或后得到结果应该也是一样的,也就是产生了冲突。造成冲突的原因关键在于16限制了只能用低位来计算,高位直接舍弃了,所以我们需要额外的哈希函数而不只是简单的对象的hashCode方法了。具体来说,就是HashMap中hash函数干的事了。

roundUpToPowerOf2这个方法是用来返回大于等于最接近number的2的冪数
  int rounded = number >= MAXIMUM_CAPACITY
                ? MAXIMUM_CAPACITY
                : (rounded = Integer.highestOneBit(number)) != 0
                    ? (Integer.bitCount(number) > 1) ? rounded << 1 : rounded
                    : 1;
Integer中的highestOneBit方法是用来返回小于等于最接近number的2的冪数,
例如:number=5,对应的二进制为 101,
highestOneBit(5)=100,既在number最高位为1的位置开始,后面全部补0

Integer中的bitCount方法返回number二进制中1的个数
如果number是2的冪数 b1=1,返回就是number,
如果number不是2的冪数,则highestOneBit>1,因为rounded = Integer.highestOneBit(number),rounded已经是最接近小于number的2的冪数,故,rounded<<1,左移一位,就成为最接近大于等于number的2的冪数了

3. 解决hash冲突的办法

  1. 开放定址法(线性探测再散列,二次探测再散列,伪随机探测再散列)
  2. 再哈希法
  3. 链地址法
  4. 建立一个公共溢出区

Java中hashmap的解决办法就是采用的链地址法。

4. 再散列rehash过程

当哈希表的容量超过默认容量时,必须调整table的大小。当容量已经达到最大可能值时,那么该方法就将容量调整到Integer.MAX_VALUE返回,这时,需要创建一张新表,将原表的映射到新表中。

   /**
     * Rehashes the contents of this map into a new array with a
     * larger capacity.  This method is called automatically when the
     * number of keys in this map reaches its threshold.
     *
     * If current capacity is MAXIMUM_CAPACITY, this method does not
     * resize the map, but sets threshold to Integer.MAX_VALUE.
     * This has the effect of preventing future calls.
     *
     * @param newCapacity the new capacity, MUST be a power of two;
     *        must be greater than current capacity unless current
     *        capacity is MAXIMUM_CAPACITY (in which case value
     *        is irrelevant).
     */
    void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
 
        Entry[] newTable = new Entry[newCapacity];
        transfer(newTable);
        table = newTable;
        threshold = (int)(newCapacity * loadFactor);

}

    /**
     * Transfers all entries from current table to newTable.
     */
    void transfer(Entry[] newTable) {
        Entry[] src = table;
        int newCapacity = newTable.length;
        for (int j = 0; j < src.length; j++) {
            Entry<K,V> e = src[j];
            if (e != null) {
                src[j] = null;
                do {
                    Entry<K,V> next = e.next;
                    //重新计算index
                    int i = indexFor(e.hash, newCapacity);
                    e.next = newTable[i];
                    newTable[i] = e;
                    e = next;
                } while (e != null);
            }
        }

}

【Java深入研究】3、HashMap源码解析(jdk 1.7)的更多相关文章

  1. java容器三:HashMap源码解析

    前言:Map接口 map是一个存储键值对的集合,实现了Map接口的主要类有以下几种 TreeMap:用红黑树实现 HashMap:数组和链表实现 HashTable:与HashMap类似,但是线程安全 ...

  2. 【转】Java HashMap 源码解析(好文章)

    ­ .fluid-width-video-wrapper { width: 100%; position: relative; padding: 0; } .fluid-width-video-wra ...

  3. Java中的容器(集合)之HashMap源码解析

    1.HashMap源码解析(JDK8) 基础原理: 对比上一篇<Java中的容器(集合)之ArrayList源码解析>而言,本篇只解析HashMap常用的核心方法的源码. HashMap是 ...

  4. HashMap源码解析 非原创

    Stack过时的类,使用Deque重新实现. HashCode和equals的关系 HashCode为hash码,用于散列数组中的存储时HashMap进行散列映射. equals方法适用于比较两个对象 ...

  5. HashMap源码解析和设计解读

    HashMap源码解析 ​ 想要理解HashMap底层数据的存储形式,底层原理,最好的形式就是读它的源码,但是说实话,源码的注释说明全是英文,英文不是非常好的朋友读起来真的非常吃力,我基本上看了差不多 ...

  6. java集合系列之HashMap源码

    java集合系列之HashMap源码 HashMap的源码可真不好消化!!! 首先简单介绍一下HashMap集合的特点.HashMap存放键值对,键值对封装在Node(代码如下,比较简单,不再介绍)节 ...

  7. 死磕 java同步系列之CyclicBarrier源码解析——有图有真相

    问题 (1)CyclicBarrier是什么? (2)CyclicBarrier具有什么特性? (3)CyclicBarrier与CountDownLatch的对比? 简介 CyclicBarrier ...

  8. 死磕 java同步系列之Phaser源码解析

    问题 (1)Phaser是什么? (2)Phaser具有哪些特性? (3)Phaser相对于CyclicBarrier和CountDownLatch的优势? 简介 Phaser,翻译为阶段,它适用于这 ...

  9. 死磕 java同步系列之StampedLock源码解析

    问题 (1)StampedLock是什么? (2)StampedLock具有什么特性? (3)StampedLock是否支持可重入? (4)StampedLock与ReentrantReadWrite ...

  10. 死磕 java同步系列之Semaphore源码解析

    问题 (1)Semaphore是什么? (2)Semaphore具有哪些特性? (3)Semaphore通常使用在什么场景中? (4)Semaphore的许可次数是否可以动态增减? (5)Semaph ...

随机推荐

  1. appium精简教程

    环境配置 package appium; import io.appium.java_client.android.*; import java.io.File; import java.io.IOE ...

  2. 一个docker容器中运行多个服务还是弄一堆docker容器运行?

    不建议直接在单个 Docker 容器中运行多个程序. 以 2017年 10 月18 日 Docker 官方支持 Kubernetes 为分水岭计算,Kubernetes 赢得容器编排之战的最终胜利已经 ...

  3. Django(ORM查询1)

    day69 参考:http://www.cnblogs.com/liwenzhou/p/8660826.html 在Python脚本中调用Django环境 orm1.py import os if _ ...

  4. 初探日志框架Logback

    一. 背景 最近因为学习项目时需要使用logback日志框架来打印日志, 使用过程中碰到很多的疑惑, 而且需要在控制台打印mybatis执行的sql语句, 于是决定沉下心来 研究一下logback的使 ...

  5. Swift5 语言参考(八) 模式

    模式表示单个值或复合值的结构.例如,元组的结构是两个元素的逗号分隔列表.因为模式表示值的结构而不是任何一个特定值,所以可以将它们与各种值匹配.例如,模式匹配元组和任何其他两元素元组.除了将模式与值匹配 ...

  6. select2插件使用小记2 - 多选联动 - 笔记

    这是select2插件使用的第二篇,可参考第一篇 select2插件使用小记.上一篇主要是关于基本的使用,这篇主要是关于多选,及联动的.侧重点不同. 效果图如下: 遵从W3C标准:结构.样式.行为.以 ...

  7. java后端导入excel将数据写入数据库

    参考:https://www.cnblogs.com/hanfeihanfei/p/7079210.html @RequestMapping("/importExcel.do") ...

  8. 不一样的日期、时间转换(moment.js)

    无意中遇到了一种很奇怪的日期格式,从接口中返回的日期是这样的,如 2018-02-06T11:59:22+08:00 .然而这却不是我们想要的,我们要的是这种,YYYY-MM-DD HH:mm:ss. ...

  9. MapReducer

    MapReducer    概述        是一个分布式的计算框架(编程模型),最初由由谷歌的工程师开发,基于GFS的分布式计算框架.后来Cutting根据<Google Mapreduce ...

  10. Spring WebSocket踩坑指南

    Spring WebSocket踩坑指南 本次公司项目中需要在后台与安卓App间建立一个长连接,这里采用了Spring的WebSocket,协议为Stomp. 关于Stomp协议这里就不多介绍了,网上 ...