HashMap的實(shí)現(xiàn)原理

HashMap的實(shí)現(xiàn)原理

1.HashMap概述

HashMap是基于哈希表的Map接口的非同步實(shí)現(xiàn)打厘。此實(shí)現(xiàn)提供所有可選的映射操作,并允許使用null值和null鍵余掖。此類不保證映射的順序贿肩,特別是它不保證該順序恒久不變。


2.HashMap的數(shù)據(jù)結(jié)構(gòu)

在 Java 編程語言中徒扶,最基本的結(jié)構(gòu)就是兩種,一個(gè)是數(shù)組根穷,另外一個(gè)是指針(引用)姜骡,HashMap 就是通過這兩個(gè)數(shù)據(jù)結(jié)構(gòu)進(jìn)行實(shí)現(xiàn)。HashMap實(shí)際上是一個(gè)“鏈表散列”的數(shù)據(jù)結(jié)構(gòu)屿良,即數(shù)組和鏈表的結(jié)合體圈澈。


image

每個(gè)元素存儲(chǔ)的是一個(gè)鏈表的頭結(jié)點(diǎn)。那么這些元素是按照什么樣的規(guī)則存儲(chǔ)到數(shù)組中呢? 一般情況是通過hash(key)%len獲得管引,也就是元素的key的哈希值對(duì)數(shù)組長度取模得到士败。比如上述哈希表中闯两,12%16=12,28%16=12,108%16=12,140%16=12褥伴。所以12、28漾狼、108以及140都存儲(chǔ)在數(shù)組下標(biāo)為12的位置重慢。

HashMap其實(shí)也是一個(gè)線性的數(shù)組實(shí)現(xiàn)的,所以可以理解為其存儲(chǔ)數(shù)據(jù)的容器就是一個(gè)線性數(shù)組。這可能讓我們很不解逊躁,一個(gè)線性的數(shù)組怎么實(shí)現(xiàn)按鍵值對(duì)來存取數(shù)據(jù)呢似踱?這里HashMap有做一些處理。

首先HashMap里面實(shí)現(xiàn)一個(gè)靜態(tài)內(nèi)部類Entry稽煤,其重要的屬性有 key , value, next核芽,從屬性key,value我們就能很明顯的看出來Entry就是HashMap鍵值對(duì)實(shí)現(xiàn)的一個(gè)基礎(chǔ)bean,我們上面說到HashMap的基礎(chǔ)就是一個(gè)線性數(shù)組酵熙,這個(gè)數(shù)組就是Entry[]轧简,Map里面的內(nèi)容都保存在Entry[]里面。


3. HashMap的初始化過程

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

DEFAULT_LOAD_FACTOR : 負(fù)載因子的默認(rèn)值0.75 表示數(shù)據(jù)填充的臨界值,即數(shù)據(jù)達(dá)到總數(shù)據(jù)的75%時(shí)就開始準(zhǔn)備擴(kuò)容了.

DEFAULT_INITIAL_CAPACITY : 默認(rèn)傳入Map中的數(shù)據(jù)默認(rèn)值為1<<4 (實(shí)際就是16)
從上面看出 現(xiàn)實(shí)調(diào)用自己的構(gòu)造方法,然后創(chuàng)建存儲(chǔ)的Table(實(shí)際是數(shù)組),最后把值添加到創(chuàng)建的table中

  • this(var1,var2)實(shí)際調(diào)用的構(gòu)造方法
   /**
    * Constructs an empty <tt>HashMap</tt> with the specified initial
    * capacity and load factor.
    *
    * @param  initialCapacity the initial capacity
    * @param  loadFactor      the load factor
    * @throws IllegalArgumentException if the initial capacity is negative
    *         or the load factor is nonpositive
    */
   public HashMap(int initialCapacity, float loadFactor) {
       if (initialCapacity < 0)
           throw new IllegalArgumentException("Illegal initial capacity: " +
                                              initialCapacity);
       if (initialCapacity > MAXIMUM_CAPACITY) {
           initialCapacity = MAXIMUM_CAPACITY;
       } else if (initialCapacity < DEFAULT_INITIAL_CAPACITY) {
           initialCapacity = DEFAULT_INITIAL_CAPACITY;
       }

       if (loadFactor <= 0 || Float.isNaN(loadFactor))
           throw new IllegalArgumentException("Illegal load factor: " +
                                              loadFactor);
       threshold = initialCapacity;
       init();
   }

initialCapacity : ==即初始化申請(qǐng)空間的值,不等于Map實(shí)際初始化的內(nèi)部數(shù)組的長度(稍后解釋為什么)==,若不填寫默認(rèn)是使用DEFAULT_INITIAL_CAPACITY也就是16
initialCapacity的最大值為1 << 30 也就是2^30次方

loadFactor : 負(fù)載因子的初始化值,若不填寫默認(rèn)為DEFAULT_LOAD_FACTOR也就是0.75

threshold : 下次擴(kuò)容時(shí)的申請(qǐng)空間值

  • inflateTable方法
    /**
     * Inflates the table.
     */
    private void inflateTable(int toSize) {
        // Find a power of 2 >= toSize
        int capacity = roundUpToPowerOf2(toSize);

        // Android-changed: Replace usage of Math.min() here because this method is
        // called from the <clinit> of runtime, at which point the native libraries
        // needed by Float.* might not be loaded.
        float thresholdFloat = capacity * loadFactor;
        if (thresholdFloat > MAXIMUM_CAPACITY + 1) {
            thresholdFloat = MAXIMUM_CAPACITY + 1;
        }

        threshold = (int) thresholdFloat;
        table = new HashMapEntry[capacity];
    }
這里面有個(gè)重點(diǎn)

實(shí)際申請(qǐng)的內(nèi)部數(shù)組的大小 int capacity = roundUpToPowerOf2(toSize);

     private static int 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;
 }

這段代碼在傳入的number在正常數(shù)值都會(huì)走 Integer.highestOneBit(number)和Integer.bitCount(number)
里面的代碼都是位運(yùn)算 ==roundUpToPowerOf2這個(gè)方法實(shí)際上的功能就是返回一個(gè)最小的是2^n且它大于等于number的值==

例如 number= 4 那么roundUpToPowerOf2的返回值就是2^2 = 4

number = 5 那么roundUpToPowerOf2的返回值就是2^3 = 8

那么問題來了 為啥內(nèi)部數(shù)組大小為啥是2^n呢?

而當(dāng)數(shù)組長度為16時(shí)匾二,即為2的n次方時(shí)哮独,2n-1得到的二進(jìn)制數(shù)的每個(gè)位上的值都為1(比如(24-1)2=1111),這使得在低位上&時(shí)察藐,得到的和原h(huán)ash的低位相同皮璧,加之hash(int h)方法對(duì)key的hashCode的進(jìn)一步優(yōu)化,加入了高位計(jì)算分飞,就使得只有相同的hash值的兩個(gè)值才會(huì)被放到數(shù)組中的同一個(gè)位置上形成鏈表悴务。

所以說,當(dāng)數(shù)組長度為2的n次冪的時(shí)候譬猫,不同的key算得得index相同的幾率較小惨寿,那么數(shù)據(jù)在數(shù)組上分布就比較均勻邦泄,也就是說碰撞的幾率小,相對(duì)的裂垦,查詢的時(shí)候就不用遍歷某個(gè)位置上的鏈表顺囊,這樣查詢效率也就較高了。

  • putAllForCreate方法
    private void putAllForCreate(Map<? extends K, ? extends V> m) {
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
            putForCreate(e.getKey(), e.getValue());
    }

這里是個(gè)foreach循環(huán),將m中的數(shù)據(jù)拿出來一一添加到map, 下面看具體的putForCreate方法

    /**
     * This method is used instead of put by constructors and
     * pseudoconstructors (clone, readObject).  It does not resize the table,
     * check for comodification, etc.  It calls createEntry rather than
     * addEntry.
     */
    private void putForCreate(K key, V value) {
        int hash = null == key ? 0 : sun.misc.Hashing.singleWordWangJenkinsHash(key);
        int i = indexFor(hash, table.length);

        /**
         * Look for preexisting entry for key.  This will never happen for
         * clone or deserialize.  It will only happen for construction if the
         * input Map is a sorted map whose ordering is inconsistent w/ equals.
         */
        for (HashMapEntry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k)))) {
                e.value = value;
                return;
            }
        }

        createEntry(hash, key, value, i);
    }
1.計(jì)算key的hash值

key==null -> hash=0

key!=null -> hash = hash(key)

2.計(jì)算數(shù)組下標(biāo)(根據(jù)hash值求余,即余數(shù)相同的hash值放到相同的數(shù)組下標(biāo)對(duì)應(yīng)的一個(gè)鏈表上)按位取并蕉拢,作用上相當(dāng)于取模mod或者取余%特碳。這意味著數(shù)組下標(biāo)相同,并不表示hashCode相同晕换。
    /**
     * Returns index for hash code h.
     */
    static int indexFor(int h, int length) {
        // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
        return h & (length-1);
    }
3.在對(duì)應(yīng)的key上賦值或者添加一組map

4.HashMap的存取實(shí)現(xiàn)

HashMap的基本存取過程基本如下

// 存儲(chǔ)時(shí):
int hash = key.hashCode(); // 這個(gè)hashCode方法這里不詳述,只要理解每個(gè)key的hash是一個(gè)固定的int值
int index = hash % Entry[].length;
Entry[index] = value;

// 取值時(shí):
int hash = key.hashCode();
int index = hash % Entry[].length;
return Entry[index];
  • 存儲(chǔ)數(shù)據(jù)
    public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        int hash = sun.misc.Hashing.singleWordWangJenkinsHash(key);
        int i = indexFor(hash, table.length);
        for (HashMapEntry<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++;
        addEntry(hash, key, value, i);
        return null;
    }

從上面的源代碼中可以看出:
1.Map支持key=null

    private V putForNullKey(V value) {
        for (HashMapEntry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
        addEntry(0, null, value, 0);
        return null;
    }

從上面的代碼中可以看到當(dāng)key==null 會(huì)將值放到table[0]索引下,并且當(dāng)數(shù)據(jù)重復(fù)時(shí),新數(shù)據(jù)會(huì)覆蓋原數(shù)據(jù),并返回原數(shù)據(jù),若不重復(fù)則添加到table hash=0,bucketIndex=0;

2.當(dāng)我們往HashMap中put元素的時(shí)候午乓,先根據(jù)key的hashCode重新計(jì)算hash值,根據(jù)hash值得到這個(gè)元素在數(shù)組中的位置(即下標(biāo))闸准,如果數(shù)組該位置上已經(jīng)存放有其他元素了益愈,那么在這個(gè)位置上的元素將以鏈表的形式存放,新加入的放在鏈頭夷家,最先加入的放在鏈尾蒸其。如果數(shù)組該位置上沒有元素,就直接將該元素放到此數(shù)組中的該位置上库快。

addEntry方法

void addEntry(int hash, K key, V value, int bucketIndex) {
    // 獲取指定 bucketIndex 索引處的 Entry 
    Entry<K,V> e = table[bucketIndex];
    // 將新創(chuàng)建的 Entry 放入 bucketIndex 索引處摸袁,并讓新的 Entry 指向原來的 Entry
    table[bucketIndex] = new Entry<K,V>(hash, key, value, e); //參數(shù)e, 是Entry.next
    // 如果 Map 中的 key-value 對(duì)的數(shù)量超過了極限
    if (size++ >= threshold)
    // 把 table 對(duì)象的長度擴(kuò)充到原來的2倍。
            resize(2 * table.length);
}

儲(chǔ)HashMap中的key-value對(duì)時(shí)义屏,完全沒有考慮Entry中的value靠汁,僅僅只是根據(jù)key來計(jì)算并決定每個(gè)Entry的存儲(chǔ)位置。我們完全可以把 Map 集合中的 value 當(dāng)成 key 的附屬闽铐,當(dāng)系統(tǒng)決定了 key 的存儲(chǔ)位置之后蝶怔,value 隨之保存在那里即可。

hash(int h)方法根據(jù)key的hashCode重新計(jì)算一次散列兄墅。此算法加入了高位計(jì)算踢星,防止低位不變,高位變化時(shí)察迟,造成的hash沖突斩狱。

當(dāng)然HashMap里面也包含一些優(yōu)化方面的實(shí)現(xiàn),這里也說一下扎瓶。比如:Entry[]的長度一定后所踊,隨著map里面數(shù)據(jù)的越來越長,這樣同一個(gè)index的鏈就會(huì)很長概荷,會(huì)不會(huì)影響性能秕岛?HashMap里面設(shè)置一個(gè)因子(loadFactor),隨著map的size越來越大,Entry[]會(huì)以一定的規(guī)則加長長度继薛。

  • 讀取數(shù)據(jù)

    public V get(Object key) {
     if (key == null)
         return getForNullKey();
     Entry<K,V> entry = getEntry(key);

     return null == entry ? null : entry.getValue();
 }

當(dāng)key=null 直接取table的索引為0下的值;
當(dāng)key!=null

    final Entry<K,V> getEntry(Object key) {
     if (size == 0) {
         return null;
     }

     int hash = (key == null) ? 0 : sun.misc.Hashing.singleWordWangJenkinsHash(key);
     for (HashMapEntry<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;
 }

從HashMap中g(shù)et元素時(shí)修壕,首先計(jì)算key的hashCode,在通過indexFor方法找到數(shù)組中對(duì)應(yīng)位置的某一元素遏考,然后通過key的equals方法在對(duì)應(yīng)位置的鏈表中找到需要的元素慈鸠。

歸納起來簡單地說,HashMap 在底層將 key-value 當(dāng)成一個(gè)整體進(jìn)行處理灌具,這個(gè)整體就是一個(gè) Entry 對(duì)象青团。HashMap 底層采用一個(gè) Entry[] 數(shù)組來保存所有的 key-value 對(duì),當(dāng)需要存儲(chǔ)一個(gè) Entry 對(duì)象時(shí)咖楣,會(huì)根據(jù)hash算法來決定其在數(shù)組中的存儲(chǔ)位置督笆,在根據(jù)equals方法決定其在該數(shù)組位置上的鏈表中的存儲(chǔ)位置;當(dāng)需要取出一個(gè)Entry時(shí)诱贿,也會(huì)根據(jù)hash算法找到其在數(shù)組中的存儲(chǔ)位置娃肿,再根據(jù)equals方法從該位置上的鏈表中取出該Entry。


5.HashMap的擴(kuò)容

當(dāng)HashMap中的元素越來越多的時(shí)候珠十,hash沖突的幾率也就越來越高料扰,因?yàn)閿?shù)組的長度是固定的。所以為了提高查詢的效率宵睦,就要對(duì)HashMap的數(shù)組進(jìn)行擴(kuò)容记罚,數(shù)組擴(kuò)容這個(gè)操作也會(huì)出現(xiàn)在ArrayList中墅诡,這是一個(gè)常用的操作壳嚎,而在HashMap數(shù)組擴(kuò)容之后,最消耗性能的點(diǎn)就出現(xiàn)了:原數(shù)組中的數(shù)據(jù)必須重新計(jì)算其在新數(shù)組中的位置末早,并放進(jìn)去烟馅,這就是resize。

那么HashMap什么時(shí)候進(jìn)行擴(kuò)容呢然磷?當(dāng)HashMap中的元素個(gè)數(shù)超過數(shù)組大小*loadFactor時(shí)郑趁,就會(huì)進(jìn)行數(shù)組擴(kuò)容

    void resize(int newCapacity) {
      HashMapEntry[] oldTable = table;
      int oldCapacity = oldTable.length;
       //如果當(dāng)前的數(shù)組長度已經(jīng)達(dá)到最大值,則不在進(jìn)行調(diào)整
      if (oldCapacity == MAXIMUM_CAPACITY) {
          threshold = Integer.MAX_VALUE;
          return;
      }
//根據(jù)傳入?yún)?shù)的長度定義新的數(shù)組
      HashMapEntry[] newTable = new HashMapEntry[newCapacity];
      //按照新的規(guī)則姿搜,將舊數(shù)組中的元素轉(zhuǎn)移到新數(shù)組中
      transfer(newTable);
      table = newTable;
        //更新臨界值
      threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
  }

舊數(shù)組的數(shù)據(jù)轉(zhuǎn)到新數(shù)組

    void transfer(HashMapEntry[] newTable) {
      int newCapacity = newTable.length;
      for (HashMapEntry<K,V> e : table) {
          while(null != e) {
              HashMapEntry<K,V> next = e.next;
              int i = indexFor(e.hash, newCapacity);
              e.next = newTable[i];
              newTable[i] = e;
              e = next;
          }
      }
  }

6.Fail-Fast機(jī)制:

我們知道java.util.HashMap不是線程安全的寡润,因此如果在使用迭代器的過程中有其他線程修改了map,那么將拋出ConcurrentModificationException舅柜,這就是所謂fail-fast策略梭纹。

這一策略在源碼中的實(shí)現(xiàn)是通過modCount域,modCount顧名思義就是修改次數(shù)致份,對(duì)HashMap內(nèi)容的修改都將增加這個(gè)值变抽,那么在迭代器初始化過程中會(huì)將這個(gè)值賦給迭代器的expectedModCount。

 HashIterator() {
            expectedModCount = modCount;
            if (size > 0) { // advance to first entry
                HashMapEntry[] t = table;
                while (index < t.length && (next = t[index++]) == null)
                    ;
            }
        }

在迭代過程中,判斷modCount跟expectedModCount是否相等绍载,如果不相等就表示已經(jīng)有其他線程修改了Map:

注意到modCount聲明為volatile诡宗,保證線程之間修改的可見性。(volatile之所以線程安全是因?yàn)楸籿olatile修飾的變量不保存緩存击儡,直接在內(nèi)存中修改塔沃,因此能夠保證線程之間修改的可見性)

         final Entry<K,V> nextEntry() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            HashMapEntry<K,V> e = next;
            if (e == null)
                throw new NoSuchElementException();

            if ((next = e.next) == null) {
                HashMapEntry[] t = table;
                while (index < t.length && (next = t[index++]) == null)
                    ;
            }
            current = e;
            return e;
        }

在HashMap的API中指出:

由所有HashMap類的“collection 視圖方法”所返回的迭代器都是快速失敗的:在迭代器創(chuàng)建之后,如果從結(jié)構(gòu)上對(duì)映射進(jìn)行修改阳谍,除非通過迭代器本身的 remove 方法芳悲,其他任何時(shí)間任何方式的修改,迭代器都將拋出ConcurrentModificationException边坤。因此名扛,面對(duì)并發(fā)的修改,迭代器很快就會(huì)完全失敗茧痒,而不保證在將來不確定的時(shí)間發(fā)生任意不確定行為的風(fēng)險(xiǎn)肮韧。

注意,迭代器的快速失敗行為不能得到保證旺订,一般來說弄企,存在非同步的并發(fā)修改時(shí),不可能作出任何堅(jiān)決的保證区拳【辛欤快速失敗迭代器盡最大努力拋出 ConcurrentModificationException。因此樱调,編寫依賴于此異常的程序的做法是錯(cuò)誤的约素,正確做法是:迭代器的快速失敗行為應(yīng)該僅用于檢測程序錯(cuò)誤.

解決辦法

  • 第一、使用線程安全的ConcurrentHashMap或HashTable,它就不會(huì)產(chǎn)生ConcurrentModificationException異常笆凌,也就是它使用迭代器完全不會(huì)產(chǎn)生fail-fast機(jī)制
  • 第二.Collections.synchronizedMap將HashMap包裝起來

返回由指定映射支持的同步(線程安全的)映射圣猎。為了保證按順序訪問,必須通過返回的映射完成對(duì)底層映射的所有訪問乞而。在返回的映射或其任意 collection 視圖上進(jìn)行迭代時(shí)送悔,強(qiáng)制用戶手工在返回的映射上進(jìn)行同步:

Map m = Collections.synchronizedMap(new HashMap());
...
Set s = m.keySet();  //不需要加鎖
...
synchronized(m) {  // 對(duì)Map的對(duì)象m加鎖
Iterator i = s.iterator(); // 必須加鎖的模塊
    while (i.hasNext())
        foo(i.next());
}

參考資料

深入Java集合學(xué)習(xí)系列:HashMap的實(shí)現(xiàn)原理

HashMap實(shí)現(xiàn)原理分析

HashMap, HashTable, ConcurrentHashMap的區(qū)別

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市爪模,隨后出現(xiàn)的幾起案子欠啤,更是在濱河造成了極大的恐慌,老刑警劉巖屋灌,帶你破解...
    沈念sama閱讀 212,332評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件洁段,死亡現(xiàn)場離奇詭異,居然都是意外死亡声滥,警方通過查閱死者的電腦和手機(jī)眉撵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,508評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門侦香,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人纽疟,你說我怎么就攤上這事罐韩。” “怎么了污朽?”我有些...
    開封第一講書人閱讀 157,812評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵散吵,是天一觀的道長。 經(jīng)常有香客問我蟆肆,道長矾睦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,607評(píng)論 1 284
  • 正文 為了忘掉前任炎功,我火速辦了婚禮枚冗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蛇损。我一直安慰自己赁温,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,728評(píng)論 6 386
  • 文/花漫 我一把揭開白布淤齐。 她就那樣靜靜地躺著股囊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪更啄。 梳的紋絲不亂的頭發(fā)上稚疹,一...
    開封第一講書人閱讀 49,919評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音祭务,去河邊找鬼内狗。 笑死,一個(gè)胖子當(dāng)著我的面吹牛待牵,可吹牛的內(nèi)容都是我干的其屏。 我是一名探鬼主播喇勋,決...
    沈念sama閱讀 39,071評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼缨该,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了川背?” 一聲冷哼從身側(cè)響起贰拿,我...
    開封第一講書人閱讀 37,802評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎熄云,沒想到半個(gè)月后膨更,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,256評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡缴允,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,576評(píng)論 2 327
  • 正文 我和宋清朗相戀三年荚守,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了珍德。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,712評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡矗漾,死狀恐怖锈候,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情敞贡,我是刑警寧澤泵琳,帶...
    沈念sama閱讀 34,389評(píng)論 4 332
  • 正文 年R本政府宣布,位于F島的核電站誊役,受9級(jí)特大地震影響获列,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蛔垢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,032評(píng)論 3 316
  • 文/蒙蒙 一击孩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鹏漆,春花似錦溯壶、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至板驳,卻和暖如春又跛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背若治。 一陣腳步聲響...
    開封第一講書人閱讀 32,026評(píng)論 1 266
  • 我被黑心中介騙來泰國打工慨蓝, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人端幼。 一個(gè)月前我還...
    沈念sama閱讀 46,473評(píng)論 2 360
  • 正文 我出身青樓礼烈,卻偏偏與公主長得像,于是被迫代替她去往敵國和親婆跑。 傳聞我的和親對(duì)象是個(gè)殘疾皇子此熬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,606評(píng)論 2 350

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

  • 實(shí)際上,HashSet 和 HashMap 之間有很多相似之處滑进,對(duì)于 HashSet 而言犀忱,系統(tǒng)采用 Hash 算...
    曹振華閱讀 2,510評(píng)論 1 37
  • 1. HashMap概述: HashMap是基于哈希表的Map接口的非同步實(shí)現(xiàn)(Hashtable跟HashMap...
    Ten_Minutes閱讀 579評(píng)論 0 5
  • 5.1、對(duì)于HashMap需要掌握以下幾點(diǎn) Map的創(chuàng)建:HashMap() 往Map中添加鍵值對(duì):即put(Ob...
    rochuan閱讀 671評(píng)論 0 0
  • 我常以“一輩子寫一本好書就行”為準(zhǔn)繩搀庶。 不是說不愿出書拐纱,不愿過早成名,而且要保證質(zhì)量哥倔,起碼要自己覺得夠好戳玫。如果粗制...
    觀察員yog閱讀 2,730評(píng)論 12 27
  • 某君一早給我發(fā)來一截圖,某名人寫另一名人的未斑。言辭不失恭維咕宿。我回他一笑臉說,想起兩個(gè)詞語:“四面玲玲”與“八面威風(fēng)”...
    凌宗偉閱讀 1,699評(píng)論 0 1