HashMap源碼分析

HashMap源碼分析

HashMap是對(duì)Map接口的一種實(shí)現(xiàn)踢代,底層數(shù)據(jù)結(jié)構(gòu)使用了散列表(Hash table)。假設(shè)一個(gè)數(shù)組足夠長(zhǎng)瓜客,而且存在一個(gè)函數(shù)可以將每一個(gè)需要存儲(chǔ)的值的key映射到唯一的一個(gè)數(shù)組下標(biāo)中适瓦,那么就可以在O(1)的時(shí)間復(fù)雜度內(nèi)完成指定位置元素的讀寫(xiě)操作。但是資源是有限的谱仪,存儲(chǔ)空間是有限的,也沒(méi)辦法設(shè)計(jì)出一個(gè)完全保證一個(gè)值對(duì)應(yīng)一個(gè)數(shù)據(jù)索引的函數(shù)否彩,但是散列表就是基于這樣一種思想產(chǎn)生的疯攒。

散列表有兩個(gè)重要的概念,一個(gè)是散列函數(shù)列荔,將一個(gè)key映射到一個(gè)數(shù)組索引的函數(shù)敬尺。一個(gè)是沖突,因?yàn)闆](méi)辦法設(shè)計(jì)出完美的散列函數(shù)贴浙,所以當(dāng)兩個(gè)不同的key散列到同一個(gè)索引時(shí)就會(huì)產(chǎn)生沖突砂吞。沖突的解決也是散列表的關(guān)鍵。

繼續(xù)介紹HashMap崎溃,我們先看一下官方文檔

Hash table based implementation of the Map interface. This implementation provides all of the optional map operations, and permits null values and the null key. (The HashMap class is roughly equivalent to Hashtable, except that it is unsynchronized and permits nulls.) This class makes no guarantees as to the order of the map; in particular, it does not guarantee that the order will remain constant over time.

基于散列表實(shí)現(xiàn)蜻直,非同步,允許null鍵值等等。

底層結(jié)構(gòu)

/**
 * 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;
...
/**
 * The number of times this HashMap has been structurally modified
 * Structural modifications are those that change the number of mappings in
 * the HashMap or otherwise modify its internal structure (e.g.,
 * rehash).  This field is used to make iterators on Collection-views of
 * the HashMap fail-fast.  (See ConcurrentModificationException).
 */
transient int modCount;
int threshold;
final float loadFactor;

table就是剛才我們說(shuō)的概而,理想中無(wú)限大的數(shù)組呼巷。在HashMap創(chuàng)建的時(shí)候并沒(méi)有初始化,而是延遲到首次使用的時(shí)候赎瑰。HashMap要求table的大小是2^n王悍,下面會(huì)介紹這樣要求的目的。

modCount記錄HashMap結(jié)構(gòu)化修改的次數(shù)餐曼,如果在迭代過(guò)程中压储,出現(xiàn)結(jié)構(gòu)化修改的情況,那么迭代時(shí)modCount的值與迭代前的值就不同源譬,此時(shí)會(huì)拋出ConcurrentModificationException集惋。這是HashMapfail-fast機(jī)制。注意瓶佳,put新鍵值對(duì)芋膘,但是某個(gè)key對(duì)應(yīng)的value值被覆蓋不屬于結(jié)構(gòu)變化。

loadFactor是負(fù)載因子(默認(rèn)值是0.75)霸饲,thresholdHashMap所能容納的最大數(shù)據(jù)量的Node(鍵值對(duì))個(gè)數(shù)为朋。threshold = length * loadFactor。也就是說(shuō)厚脉,在數(shù)組定義好長(zhǎng)度之后习寸,負(fù)載因子越大,所能容納的鍵值對(duì)個(gè)數(shù)越多傻工。


static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
static final int MAXIMUM_CAPACITY = 1 << 30; //2^30
static final float DEFAULT_LOAD_FACTOR = 0.75f;

/**
 * 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;

/**
 * The smallest table capacity for which bins may be treeified.
 * (Otherwise the table is resized if too many nodes in a bin.)
 * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
 * between resizing and treeification thresholds.
 */
static final int MIN_TREEIFY_CAPACITY = 64;

DEFAULT_INITIAL_CAPACITYMAXIMUM_CAPACITY分別為初始默認(rèn)容量和最大容量霞溪。
DEFAULT_LOAD_FACTOR為默認(rèn)的狀態(tài)因子,當(dāng)數(shù)組中已使用的桶(bin)的數(shù)量超過(guò)容量和裝填因子的乘積中捆,就會(huì)進(jìn)行擴(kuò)容鸯匹。

HashMap解決沖突使用的是拉鏈法,在JDK8以前只是采用了單向鏈表的方式泄伪,哈希碰撞會(huì)給查找?guī)?lái)災(zāi)難性的影響殴蓬,最差情況下,HashMap會(huì)退化為一個(gè)單鏈表蟋滴。查找時(shí)間由O(1)退化為O(n)染厅。而在JDK 8中,如果單鏈表過(guò)長(zhǎng)則會(huì)轉(zhuǎn)換為一顆紅黑樹(shù)津函,使得最壞情況下查找的時(shí)間復(fù)雜度為 O(log n) 肖粮。紅黑樹(shù)節(jié)點(diǎn)的空間占用相較于普通節(jié)點(diǎn)要高出許多,通常只有在比較極端的情況下才會(huì)由單鏈表轉(zhuǎn)化為紅黑樹(shù)尔苦。通過(guò)TREEIFY_THRESHOLD涩馆、UNTREEIFY_THRESHOLDMIN_TREEIFY_CAPACITY來(lái)控制轉(zhuǎn)換需要的閾值行施。

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;
    }
    ...
}

HashMap不僅僅是存儲(chǔ)值,而是將鍵值都存儲(chǔ)到數(shù)組中凌净,就是這個(gè)Node靜態(tài)類(lèi)悲龟。Node類(lèi)包括了鍵、值冰寻、下一個(gè)節(jié)點(diǎn)的引用须教,以及鍵的hash值,避免重復(fù)計(jì)算hash斩芭。其實(shí)這個(gè)Node就是單向鏈表中的一個(gè)節(jié)點(diǎn)轻腺。

初始化

public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);
}

public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

public HashMap(Map<? extends K, ? extends V> m) {
    this.loadFactor = DEFAULT_LOAD_FACTOR;
    putMapEntries(m, false);
}

/**
 * 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;
}

上文說(shuō)HashMap需要保證容量為2^n,那么如果來(lái)保證呢划乖?其實(shí)關(guān)鍵在tableSizeFor()方法贬养。之前的文章在介紹ArrayDeque時(shí)也有介紹過(guò),使用五次右移和位或操作可以保證得到2^n-1的數(shù)琴庵,如下所示误算。然后再加1就可以得到2^n的數(shù)。

0 0 0 0 1 ? ? ? ? ?     //n
0 0 0 0 1 1 ? ? ? ?     //n |= n >>> 1;
0 0 0 0 1 1 1 1 ? ?     //n |= n >>> 2;
0 0 0 0 1 1 1 1 1 1     //n |= n >>> 4;

哈希計(jì)算

要設(shè)計(jì)出一個(gè)分布均勻的散列函數(shù)是很困難的迷殿,而且也不是我們所關(guān)心的儿礼,Java中的String和其它基本類(lèi)型的包裝類(lèi)的hashCode()返回的散列值已經(jīng)分布得很不錯(cuò)了,我們直接拿來(lái)用就可以了庆寺。

要將hashCode()方法返回的散列值再映射到數(shù)組的索引值蚊夫,我們能夠想到的一般是通過(guò)模運(yùn)算。例如懦尝,數(shù)組的長(zhǎng)度為length知纷,那么我們可以通過(guò)hashCode() % length來(lái)得到數(shù)組中放置bin的位置。但是HashMap并不是這樣的陵霉。

/**
 * Computes key.hashCode() and spreads (XORs) higher bits of hash
 * to lower.  Because the table uses power-of-two masking, sets of
 * hashes that vary only in bits above the current mask will
 * always collide. (Among known examples are sets of Float keys
 * holding consecutive whole numbers in small tables.)  So we
 * apply a transform that spreads the impact of higher bits
 * downward. There is a tradeoff between speed, utility, and
 * quality of bit-spreading. Because many common sets of hashes
 * are already reasonably distributed (so don't benefit from
 * spreading), and because we use trees to handle large sets of
 * collisions in bins, we just XOR some shifted bits in the
 * cheapest possible way to reduce systematic lossage, as well as
 * to incorporate impact of the highest bits that would otherwise
 * never be used in index calculations because of table bounds.
 */
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

在設(shè)計(jì)hash函數(shù)時(shí)琅轧,因?yàn)橐髷?shù)組table的長(zhǎng)度length必須為2^n,因而(length-1)的二進(jìn)制表示為0...011...11 的形式踊挠,位與操作后保留了 h 的低位鹰晨,實(shí)際上就是 h%length。所以計(jì)算下標(biāo)的時(shí)候止毕,可以使用&位操作,而不是%求余)漠趁。如下:

(n - 1) & hash

但是映射之后真正生效的是低位信息扁凛,高位信息被忽略了,所以容易發(fā)生沖突(collide)闯传。所以將高位和低位異或谨朝,引入高位信息,減少?zèng)_突的概率。

put方法實(shí)現(xiàn)

put方法大致的思路是:

  1. 對(duì)keyhashCode()hash字币,然后計(jì)算index
  2. 如果沒(méi)有沖突就直接放到桶(bin)里
  3. 如果沖突了则披,就以鏈表的形式存到bin的后面
  4. 如果碰撞導(dǎo)致鏈表過(guò)長(zhǎng)(大于等于TREEIFY_THRESHOLD),就把鏈表轉(zhuǎn)換成紅黑樹(shù)
  5. 如果節(jié)點(diǎn)已經(jīng)存在就替換old value(保證key的唯一性)
  6. 如果size超過(guò)load factor*current capacity洗出,就要resize
    具體代碼如下:
public V put(K key, V value) {
    // 對(duì)key的hashCode()做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; Node<K,V> p; int n, i;
    // tab為空則創(chuàng)建
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // 計(jì)算index士复,并對(duì)null做處理
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        // 節(jié)點(diǎn)存在
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        // 該鏈為樹(shù)
        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);
                    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;
            }
        }
        // 寫(xiě)入
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    // 超過(guò)load factor*current capacity,resize
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

get方法實(shí)現(xiàn)

大致思路如下:

  1. 求出keyhash翩活,再求出index
  2. 檢查bin的第一個(gè)節(jié)點(diǎn)阱洪,直接命中
  3. 如果有沖突,則通過(guò)key.equals(k)去查找對(duì)應(yīng)的entry
    若為樹(shù)菠镇,則在樹(shù)中通過(guò)key.equals(k)查找冗荸,O(logn);
    若為鏈表利耍,則在鏈表中通過(guò)key.equals(k)查找蚌本,O(n)。
    具體代碼實(shí)現(xiàn)如下:
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;
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (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) {
            // 在樹(shù)中g(shù)et
            if (first instanceof TreeNode)
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            // 在鏈表中g(shù)et
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}

因?yàn)?code>HashMap允許null值存在隘梨,所以調(diào)用get(Object key)方法返回null值不代表map中不存在這個(gè)key的映射程癌,也有可能這個(gè)key對(duì)應(yīng)的值是null〕鲟冢可以通過(guò)containsKey方法來(lái)區(qū)別兩種情況席楚。

resize方法的實(shí)現(xiàn)

當(dāng)put時(shí),如果發(fā)現(xiàn)目前HashMapsize大于load factor*current capacity税稼,那么就會(huì)發(fā)生resize烦秩。在resize的過(guò)程中,簡(jiǎn)單的說(shuō)就是將bin擴(kuò)充為2倍郎仆,并重新計(jì)算index只祠,把節(jié)點(diǎn)放入新的bin中。

Initializes or doubles table size. If null, allocates in accord with initial capacity target held in field threshold. Otherwise, because we are using power-of-two expansion, the elements from each bin must either stay at same index, or move with a power of two offset in the new table.

大致意思就是說(shuō)扰肌,當(dāng)超過(guò)限制的時(shí)候會(huì)resize抛寝,然而又因?yàn)槲覀兪褂玫氖?次冪的擴(kuò)展(指長(zhǎng)度擴(kuò)為原來(lái)2倍),所以曙旭,元素的位置要么是在原位置盗舰,要么是在原位置再移動(dòng)2次冪的位置。

怎么理解呢桂躏?例如我們從16(0x0F)擴(kuò)展為32(0x1F)時(shí)钻趋,具體的變化如下所示:

因此元素在重新計(jì)算hash之后,因?yàn)閚變?yōu)?倍剂习,那么n-1的mask范圍在高位多1bit(紅色)蛮位,因此新的index就會(huì)發(fā)生這樣的變化:

因此较沪,我們?cè)跀U(kuò)充HashMap的時(shí)候,不需要重新計(jì)算hash失仁,只需要看看原來(lái)的hash值新增的那個(gè)bit是1還是0就好了尸曼,是0的話索引沒(méi)變,是1的話索引變成“原索引+oldCap”萄焦】亟危可以看看下圖為16擴(kuò)充為32的resize示意圖:

這個(gè)設(shè)計(jì)確實(shí)非常的巧妙,既省去了重新計(jì)算hash值的時(shí)間楷扬,而且同時(shí)解幽,由于新增的1bit是0還是1可以認(rèn)為是隨機(jī)的,因此resize的過(guò)程烘苹,均勻的把之前的沖突的節(jié)點(diǎn)分散到新的bucket了躲株。

注:Java7的resize實(shí)現(xiàn)會(huì)倒置鏈表,而Java8不會(huì)镣衡。

具體實(shí)現(xiàn)如下:

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) {
        // 超過(guò)最大值就不再擴(kuò)充了霜定,就只好隨你碰撞去吧
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        // 沒(méi)超過(guò)最大值,就擴(kuò)充為原來(lái)的2倍
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            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);
    }
    // 計(jì)算新的resize上限
    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 = newTab;
    if (oldTab != null) {
        // 把每個(gè)bucket都移動(dòng)到新的buckets中
        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;
                        }
                        // 原索引+oldCap
                        else {
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    // 原索引放到bucket里
                    if (loTail != null) {
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    // 原索引+oldCap放到bucket里
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}

小結(jié)

關(guān)于Java集合的小抄

以Entry[]數(shù)組實(shí)現(xiàn)的哈希桶數(shù)組廊鸥,用Key的哈希值取模桶數(shù)組的大小可得到數(shù)組下標(biāo)望浩。

插入元素時(shí),如果兩條Key落在同一個(gè)桶(比如哈希值1和17取模16后都屬于第一個(gè)哈希桶)惰说,我們稱(chēng)之為哈希沖突磨德。

JDK的做法是鏈表法,Entry用一個(gè)next屬性實(shí)現(xiàn)多個(gè)Entry以單向鏈表存放吆视。查找哈希值為17的key時(shí)典挑,先定位到哈希桶,然后鏈表遍歷桶里所有元素啦吧,逐個(gè)比較其Hash值然后key值您觉。

在JDK8里,新增默認(rèn)為8的閾值授滓,當(dāng)一個(gè)桶里的Entry超過(guò)閥值琳水,就不以單向鏈表而以紅黑樹(shù)來(lái)存放以加快Key的查找速度。

當(dāng)然般堆,最好還是桶里只有一個(gè)元素在孝,不用去比較。所以默認(rèn)當(dāng)Entry數(shù)量達(dá)到桶數(shù)量的75%時(shí)淮摔,哈希沖突已比較嚴(yán)重浑玛,就會(huì)成倍擴(kuò)容桶數(shù)組,并重新分配所有原來(lái)的Entry噩咪。擴(kuò)容成本不低顾彰,所以也最好有個(gè)預(yù)估值。

取模用與操作(hash & (arrayLength-1))會(huì)比較快胃碾,所以數(shù)組的大小永遠(yuǎn)是2的N次方涨享, 你隨便給一個(gè)初始值比如17會(huì)轉(zhuǎn)為32。默認(rèn)第一次放入元素時(shí)的初始值是16仆百。

iterator()時(shí)順著哈希桶數(shù)組來(lái)遍歷厕隧,看起來(lái)是個(gè)亂序。

參考資料

Java HashMap工作原理及實(shí)現(xiàn)

Java 容器源碼分析之 HashMap

Java 8系列之重新認(rèn)識(shí)HashMap

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末俄周,一起剝皮案震驚了整個(gè)濱河市吁讨,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌峦朗,老刑警劉巖建丧,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異波势,居然都是意外死亡翎朱,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén)尺铣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)拴曲,“玉大人,你說(shuō)我怎么就攤上這事凛忿〕鹤疲” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵店溢,是天一觀的道長(zhǎng)叁熔。 經(jīng)常有香客問(wèn)我,道長(zhǎng)逞怨,這世上最難降的妖魔是什么者疤? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮叠赦,結(jié)果婚禮上驹马,老公的妹妹穿的比我還像新娘。我一直安慰自己除秀,他們只是感情好糯累,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著册踩,像睡著了一般泳姐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上暂吉,一...
    開(kāi)封第一講書(shū)人閱讀 49,741評(píng)論 1 289
  • 那天胖秒,我揣著相機(jī)與錄音缎患,去河邊找鬼。 笑死阎肝,一個(gè)胖子當(dāng)著我的面吹牛挤渔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播风题,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼判导,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了沛硅?” 一聲冷哼從身側(cè)響起眼刃,我...
    開(kāi)封第一講書(shū)人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎摇肌,沒(méi)想到半個(gè)月后擂红,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡朦蕴,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年篮条,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吩抓。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡涉茧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出疹娶,到底是詐尸還是另有隱情伴栓,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布雨饺,位于F島的核電站钳垮,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏额港。R本人自食惡果不足惜饺窿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望移斩。 院中可真熱鬧肚医,春花似錦、人聲如沸向瓷。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)猖任。三九已至你稚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背刁赖。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工搁痛, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人乾闰。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓落追,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親涯肩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • HashMap 是 Java 面試必考的知識(shí)點(diǎn)巢钓,面試官?gòu)倪@個(gè)小知識(shí)點(diǎn)就可以了解我們對(duì) Java 基礎(chǔ)的掌握程度病苗。網(wǎng)...
    野狗子嗷嗷嗷閱讀 6,661評(píng)論 9 107
  • 最近一直特別忙,好不容易閑下來(lái)了。準(zhǔn)備把HashMap的知識(shí)總結(jié)一下症汹,很久以前看過(guò)HashMap源碼硫朦。一直想把集...
    鵬_鵬閱讀 473評(píng)論 0 3
  • JAVA 8 HashMap 源碼分析 一 什么是HashMap? HashMap 繼承了AbstractMap,...
    gdutkyle閱讀 465評(píng)論 0 1
  • 背起行囊 吹自由的風(fēng) 越過(guò)山崗 到村戶落腳 飲一壺農(nóng)家自釀的美酒 醉著去看斜夕殘陽(yáng) 你心里裝著愿望 踩著單車(chē)去郊外...
    我是個(gè)詩(shī)人閱讀 243評(píng)論 0 2
  • 新元?dú)v2000年,血崖山下瞒斩,崖鎮(zhèn) 崖鎮(zhèn)雖地處山區(qū)破婆,但卻極盛繁華,街道上遍布擺攤小販胸囱,路上人流涌動(dòng)祷舀,時(shí)不時(shí)開(kāi)過(guò)幾輛私...
    墨千年客閱讀 352評(píng)論 2 1