三芍躏、通勤路上搞定 Java 集合面試

漫談面試系列

前言

從這章開始,我們正式進(jìn)入Java代碼的相關(guān)面試,部分較為基礎(chǔ)的、帶坑的面試題我將放在《漫聊系列》里面,而體系相對較為龐大的內(nèi)容我將單獨(dú)分為一個(gè)章節(jié)放在本系列當(dāng)中進(jìn)行講解炬转。

我們先來看Java中的集合荐吵,集合不僅是面試問的多,日常使用也多,只不過有許多細(xì)節(jié)在我們使用的過程沒有留意,而這些細(xì)節(jié)便是面試經(jīng)常問的內(nèi)容粮呢。下面我們通過幾道常問的面試題進(jìn)行相關(guān)知識點(diǎn)的學(xué)習(xí):

1. Map懒浮,List 和 Set 的區(qū)別。
2. ArrayList隨機(jī)增刪效率真的比LinkedList低嗎?
3. HashMap 的擴(kuò)容過程是怎么樣的?
4. HashMap 是線程安全的嗎侵歇,為什么不是線程安全的(JDK7,8)骂澄?
5. 編寫一個(gè)LRU緩存工具類

本系列不會講解過多的源碼坟冲,只會以結(jié)論的形式給讀者直接提供面試題的答案紊遵,如果讀者需要往更深層次發(fā)展娃善,可以去閱覽相應(yīng)的博客進(jìn)行深度學(xué)習(xí)。

在進(jìn)入具體講解之前瑞佩,我們先看看java的集合家族:


Collection
Map

通過上面兩張圖咧最,我們可以基本了解到集合分為兩大類,分布是Collection和Map御雕,接下來,我們會根據(jù)面試題進(jìn)一步講解這兩大集合類的點(diǎn)點(diǎn)滴滴滥搭。


1. Map酸纲,List 和 Set 的區(qū)別

Map 存儲的是映射關(guān)系即 key-value 鍵值對,而Collection存儲的是單列的集合瑟匆,其中List子類可以有序的存儲重復(fù)的值闽坡,而 Set 子類存儲無序并且不可重復(fù)的值。

一般來說愁溜,大部分人都能回答以上兩點(diǎn)疾嗅,但是回答完這些,只能說是基本知識掌握了冕象,更深層次的問題會問:

  1. Map 沒有繼承 Iterable 接口代承,他是如何實(shí)現(xiàn)循環(huán)的?
  2. Set 是利用什么來實(shí)現(xiàn)不可重復(fù)的渐扮?

以上問題就算面試官不問论悴,但是也盡量回答一下掖棉,基本上能回答這兩個(gè)問題說明你對Java的集合有更深層次的了解。

    1. Map 沒有繼承 Iterable 接口膀估,他是如何實(shí)現(xiàn)foreach循環(huán)的?
      第一個(gè)問題幔亥,Map 類通過使用一個(gè) Set 類來實(shí)現(xiàn)一種類似于視圖的功能,并利用這個(gè) Set 實(shí)現(xiàn) foreach (因?yàn)?Set 頂層是 Iterable 接口)察纯,但是實(shí)際上這個(gè) Set 類的方法在實(shí)現(xiàn)上都是通過調(diào)用 Map 自身的方法或字段帕棉,即該 Set 類只能算是一種工具類,本身不存儲數(shù)據(jù)饼记,下面我們通過 HashMap 來簡單了解下 Map 如何通過 EntrySet 類實(shí)現(xiàn)相關(guān)功能笤昨。
      EntrySet與Map的關(guān)系.png

可能會有人問為什么 Map 不直接繼承 Iterable 或 Collection 接口從而獲取對應(yīng)的循環(huán)功能,這里涉及到一個(gè)面向?qū)ο蟮慕涌诜蛛x原則握恳,由于 Map 主要是存儲一系列的鍵值對操作瞒窒,有針對鍵值對的操作方式,而 Collection 是存儲一個(gè)個(gè)符合某個(gè)特征對象的集合乡洼,操作方式與 Map 不一樣崇裁,再細(xì)一點(diǎn)講,Map并不算集合的一員束昵,如果想對 Map 進(jìn)行遍歷操作拔稳,則可以利用組合的方式,“借用” Collection 的特性來實(shí)現(xiàn) Map 的遍歷锹雏,而無需添加 Map 類的負(fù)擔(dān)巴比。

    1. Set 是利用什么來實(shí)現(xiàn)不可重復(fù)的?
      先說結(jié)論礁遵,Set 本質(zhì)上是只有 key 的 Map轻绞,因?yàn)?Map 的 key 是不允許重復(fù)的,重復(fù)的話就無法指定一個(gè) key 搜索正確的 value 了佣耐。
      同 Map 不直接繼承 Collection 或 Iterable 接口的理由一樣政勃,既然已經(jīng)有接口實(shí)現(xiàn)了對某個(gè)值的重復(fù)判定,那我就利用組合的形式借用一下這種特性兼砖,因此在 HashSet 中奸远,里面會有個(gè) HashMap 字段,當(dāng)我們 new HashSet() 的時(shí)候讽挟,構(gòu)造函數(shù)里面代碼其實(shí)是 new HashMap() 懒叛;我們調(diào)用 Set 的 add() 方法實(shí)際上就是將對象存入 HashMap當(dāng)中,只不過 value 為 同一個(gè)Object對象罷了耽梅。
      下面我們通過一張圖來了解下 HashSet 如何組合 HashMap 實(shí)現(xiàn)其基本的方法薛窥。
      HashSet

    經(jīng)過上面兩個(gè)問題,不知道讀者有沒有看出一些共同點(diǎn)褐墅。 Set 這個(gè)類本身都不存數(shù)據(jù)拆檬,一直是一個(gè)“工具人” 的身份洪己,真正存數(shù)據(jù)的類最終還是歸屬于 Map 來存,即便是 TreeSet竟贯,內(nèi)部也是引用了一個(gè) TreeMap 類的字段答捕,只不過這些 Map 引用的 value 可能為 null 也可能為同一個(gè) Object。Set 只不過在對數(shù)據(jù)操作的時(shí)候可能有一些額外的操作屑那,即 Set 可以說是 Map 的視圖拱镐。

2. ArrayList隨機(jī)增刪效率真的比LinkedList低嗎

很多時(shí)候,面試都會問 ArrayList 和 LinkedList 的區(qū)別持际,大部分人都能回答出

ArrayList 基于數(shù)組 沃琅, LinkedList 基于鏈表,前者的隨機(jī)訪問效率高蜘欲,后者的隨機(jī)增刪效率高益眉。

大體上,這應(yīng)該是90%面試人都會回答的內(nèi)容姥份,但是郭脂,在執(zhí)行效率上,如果不區(qū)分業(yè)務(wù)場景直接這樣回答澈歉,就無法體現(xiàn)出你在日常開發(fā)中有沒有根據(jù)業(yè)務(wù)進(jìn)行技術(shù)選型展鸡。
接下來讓我們了解下兩者在一些操作中的性能開銷情況:

操作 ArrayList LinkedList
隨機(jī)查詢 由于是數(shù)組,幾乎無開銷 需要從鏈表頭進(jìn)行遍歷
隨機(jī)插入 1.插入位置后的數(shù)組后移
2.可能出現(xiàn)數(shù)組擴(kuò)容帶來的開銷
1.需要從鏈表頭進(jìn)行遍歷
2.對node節(jié)點(diǎn)進(jìn)行new
隨機(jī)刪除 數(shù)組遷移 需要從鏈表頭進(jìn)行遍歷

從上表可以看出埃难,

  1. ArrayList 的開銷主要體現(xiàn)在數(shù)組的部分遷移以及數(shù)組擴(kuò)容莹弊。
  2. LinkedList 的開銷體現(xiàn)在鏈表的遍歷以及新增時(shí)候?qū)?node 節(jié)點(diǎn)的 new 操作。
如果業(yè)務(wù)場景是:
1. 預(yù)先知道數(shù)組大小區(qū)間范圍
2. 需要大量的隨機(jī)訪問
3. 只需要在數(shù)組靠中后的位置進(jìn)行隨機(jī)增刪 
那我們就選用 ArrayList

如果業(yè)務(wù)場景是:
1.  需要經(jīng)常在鏈表靠前的位置進(jìn)行數(shù)據(jù)隨機(jī)的新增(例如隊(duì)列)或刪除
2.  數(shù)組長度不可預(yù)知
那么我們就選用 LinkedList

因此涡尘,回答題目這個(gè)問題忍弛,主要還是體現(xiàn)在需要增刪的“位置”上,如果是在靠前位置的增刪悟衩,那么 LinkedList 效率高剧罩,反之,ArrayList 的效率高座泳。不過整體的隨機(jī)增刪,ArrayList 的效率整體也是相較于 LinkedList 高幕与,因此除非某些上述特定的場景挑势,大部分時(shí)候用 ArrayList 即可。想了解更多信息可以參考以下博客內(nèi)容:ArrayList和LinkedList插入效率對比

3. HashMap 的擴(kuò)容過程是怎么樣的啦鸣?

目前 HashMap 在筆試或者技術(shù)面中幾乎是100%考的一個(gè)知識點(diǎn)潮饱,因?yàn)?HashMap 涉及了數(shù)據(jù)結(jié)構(gòu)、功能設(shè)計(jì)诫给、算法等大量的知識點(diǎn)香拉,基本上能搞懂 HashMap 的底層啦扬,可以側(cè)面說明你個(gè)人編程能力方面,以及技術(shù)方面有鉆研的精神凫碌,因此扑毡,目前對 HashMap 底層了解只能算是 Java 開發(fā)人員的基本功。
由于網(wǎng)上關(guān)于 HashMap 底層原理的介紹已經(jīng)非常多了(需要補(bǔ)基礎(chǔ)的點(diǎn)擊這里HashMap源碼解析)盛险,接下來我會簡單通過動(dòng)圖的形式介紹下jdk1.7和1.8下 HashMap 的擴(kuò)容過程瞄摊。HashMap 本身是一個(gè)是一個(gè)數(shù)組+鏈表/紅黑樹(jdk1.8)的數(shù)據(jù)結(jié)構(gòu),而存儲的每個(gè)數(shù)據(jù)元素都是一個(gè)Entry/Node(jdk1.8) 類苦掘,每次擴(kuò)容主要是針對數(shù)組進(jìn)行擴(kuò)容换帜,然后將元素的hash進(jìn)行一系列的操作獲取其在新數(shù)組的下標(biāo)值,然后存放到對應(yīng)的新數(shù)組位置上鹤啡。因此 HashMap 擴(kuò)容的核心主要是元素?cái)?shù)據(jù)的遷移惯驼,以下代碼為jdk1.7下 HashMap 元素遷移的核心代碼:

void transfer(Entry[] newTable) {
        Entry[] src = table;
        int newCapacity = newTable.length;
        for (int j = 0; j < src.length; j++) {
            //循環(huán)數(shù)組
            Entry<K,V> e = src[j];
            if (e != null) {
                src[j] = null;
                 //循環(huán)鏈表
                do {
                    Entry<K,V> next = e.next;
                    int i = indexFor(e.hash, newCapacity);
                    e.next = newTable[i];
                    newTable[i] = e;
                    e = next;
                } while (e != null);
            }
        }
    }

接下來讓我們看個(gè)動(dòng)圖來了解下jdk1.7下 HashMap 在擴(kuò)容的時(shí)候是如何將一個(gè)鏈表上的元素放入到新的數(shù)組里面:

jdk1.7 Hashmap同一鏈表下的元素遷移過程

而在jdk1.8下,HashMap 的數(shù)據(jù)遷移則進(jìn)行了一些優(yōu)化递瑰,具體操作是不再進(jìn)行rehash祟牲,而直接將舊數(shù)組的長度與元素的hashCode進(jìn)行與'&'操作(這里用的不是key的hash,而是Node的hash)泣矛,然后為0的元素存放到原本的下標(biāo)位置疲眷,而為1的則存到原本的下標(biāo)+舊數(shù)組長度的位置,下面通過擴(kuò)容源碼以及幾張圖簡單了解下jdk1.8下的 HashMap 如何進(jìn)行數(shù)據(jù)遷移您朽。

final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        //.....判斷目前數(shù)組的長度或長度*2是否大于最大值狂丝,不是的話才進(jìn)行擴(kuò)容
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        //數(shù)據(jù)遷移前便將table指向新數(shù)組
        table = newTab;
        //對老數(shù)據(jù)進(jìn)行遷移操作
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    //如果這個(gè)鏈表只有一個(gè)元素,直接存放
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    //如果這個(gè)節(jié)點(diǎn)屬于紅黑樹節(jié)點(diǎn)哗总,則進(jìn)行額外操作几颜,這里不做過多闡述
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        //最核心的點(diǎn),如果該鏈表有多個(gè)元素讯屈,進(jìn)行循環(huán)遷移
                        //這里將鏈表內(nèi)的元素分成了兩個(gè)鏈表蛋哭,一個(gè)是高位鏈表一個(gè)是低位鏈表
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            //通過舊數(shù)組長度與元素的hash進(jìn)行,如果為0則算入低位鏈表
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                //與操作結(jié)果為1涮母,設(shè)置高位鏈表元素
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        //將低位鏈表放入新數(shù)組的原下標(biāo)中
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        //將高位鏈表放入新數(shù)組的原下標(biāo)+舊數(shù)組長度的位置中
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
}

結(jié)合上面的代碼以及下面的動(dòng)圖谆趾,讀者應(yīng)該很容易理解jdk1.8下HashMap元素的遷移過程

jdk1.8下HashMap元素的遷移過程.gif

了解完 HashMap 的擴(kuò)容過程,在下一節(jié)我們一起看看 HashMap 在高并發(fā)情況下多個(gè)線程進(jìn)行擴(kuò)容的時(shí)候會發(fā)生什么叛本。

jdk1.7下沪蓬,HashMap 在擴(kuò)容以及數(shù)據(jù)遷移結(jié)束后,才會將 table字段指向新的數(shù)組来候,而jdk1.8下跷叉,在數(shù)據(jù)遷移前便會將 table字段指向新的數(shù)組。

4. HashMap 是線程安全的嗎,為什么不是線程安全的云挟?

介紹完基本的擴(kuò)容過程梆砸,接下來就提一提 HashMap 高并發(fā)時(shí)候產(chǎn)生的線程安全問題,因?yàn)檫@個(gè)問題也是非常不好理解的(特別jdk1.7)园欣,下面讓我們一起看看如何回答這個(gè)面試題帖世。
首先出現(xiàn)線程不安全主要體現(xiàn)在兩個(gè)點(diǎn):

  1. 多個(gè)線程同時(shí)進(jìn)行 put 操作的時(shí)候,可能出現(xiàn)添加的數(shù)據(jù)被覆蓋俊庇。
  2. 多個(gè)線程同時(shí)進(jìn)行擴(kuò)容的操作時(shí)狮暑,則容易出現(xiàn)環(huán)鏈表,導(dǎo)致下次有線程搜索相應(yīng)的 key 的時(shí)候進(jìn)入死循環(huán) 辉饱;而在 jdk1.8 以后由于使用了尾插法搬男,基本不會出現(xiàn)環(huán)形鏈表,但會出現(xiàn)數(shù)據(jù)丟失彭沼。

接下來讓我們看看缔逛,高并發(fā)擴(kuò)容的情況下,如何導(dǎo)致查詢死循環(huán)的:

環(huán)形鏈表產(chǎn)生過程

可以看出姓惑,導(dǎo)致環(huán)形鏈表的原因就是“頭插法”褐奴,因?yàn)樵诿看蝖ashmap進(jìn)行數(shù)據(jù)遷移的時(shí)候,會將鏈表進(jìn)行倒置(如果剛好這個(gè)鏈表上面的數(shù)據(jù)rehash后都處于同一個(gè)新鏈表的話)于毙,而對掛起的線程來說他并不知道鏈表已經(jīng)被倒置了敦冬,因此在他進(jìn)行新一輪的遷移的時(shí)候,又會進(jìn)行一次倒置唯沮,最終導(dǎo)致相關(guān)的節(jié)點(diǎn)next指針指向了他前面的元素脖旱。

網(wǎng)上找了許多有關(guān)于高并發(fā)下HashMap導(dǎo)致線程死循環(huán)的博客,他們線程A的條件是處于 'Entry next = e.next;' 這個(gè)語句的時(shí)候進(jìn)行掛起介蛉,但是根據(jù)實(shí)際流程走下來萌庆,這種情況不會導(dǎo)致查詢的時(shí)候進(jìn)入死循環(huán),而是在線程A進(jìn)行擴(kuò)容的時(shí)候就已經(jīng)進(jìn)入死循環(huán)了币旧,因?yàn)楹罄m(xù)的一個(gè)語句 'e.next = newTable[i];'會將當(dāng)前e的next指針指向一個(gè)非null值践险,因此這個(gè)線程會一直在while語句塊里面,有興趣的讀者可以自己一步一步走一遍流程吹菱。

在jdk1.8中巍虫,HashMap 的數(shù)據(jù)遷移改為“尾插法”,即鏈表的元素順序不會改變鳍刷,因此jdk1.8中的高并發(fā)下使用 HashMap 幾乎不會出現(xiàn)死循環(huán)的問題垫言,但是仍然會出現(xiàn)數(shù)據(jù)的丟失,具體丟失原因還是由于多個(gè)線程同時(shí)寫的時(shí)候出現(xiàn)了數(shù)據(jù)的覆蓋倾剿,無論是否處于擴(kuò)容狀態(tài)。

5. 編寫一個(gè)LRU緩存工具類

可能不少人在筆試或面試的時(shí)候都會遇到這個(gè)問題,那就是如何快速編寫一個(gè)LRU緩存的工具類前痘,有的讀者不明白為什么在集合這章節(jié)會出現(xiàn)LRU緩存凛捏,其實(shí)我們目前可以通過一個(gè) Map 實(shí)現(xiàn)類來迅速編寫一個(gè) LRU 緩存工具類,這個(gè) Map 類便是 LinkedHashMap芹缔。LinkedHashMap 繼承于 HashMap 坯癣,只不過重寫了 HashMap 中的Node 類,改為帶有前后指針的 Entry 類最欠。
可能許多人只以為 LinkedHashMap 只是一個(gè)有序的 HashMap 示罗,但這個(gè)有序是有兩個(gè)場景應(yīng)用的,在他的構(gòu)造函數(shù)里面是表明了你這個(gè)有序到底是插入順序還是訪問順序芝硬,如果我們要實(shí)現(xiàn)LRU緩存蚜点,那么我們就得選擇訪問順序。
下面代碼中拌阴,參數(shù) accessOrder 便是用來設(shè)置你是插入順序還是訪問順序绍绘,如果為true則為訪問順序,在其他構(gòu)造函數(shù)中迟赃,該成員變量都會默認(rèn)設(shè)置為false陪拘。

LinkedHashMap 當(dāng)中,如果 accessOrder 為 true纤壁,那么頭節(jié)點(diǎn)便屬于最少使用的節(jié)點(diǎn)左刽,而尾節(jié)點(diǎn)屬于最近使用過的節(jié)點(diǎn)

public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }

同時(shí),在 HashMap 里面酌媒,就已經(jīng)提前準(zhǔn)備了相應(yīng)的回調(diào)方法:獲取節(jié)點(diǎn)后的回調(diào)欠痴、插入節(jié)點(diǎn)后的回調(diào)以及刪除節(jié)點(diǎn)后的回調(diào):

// Callbacks to allow LinkedHashMap post-actions
void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }

而這些方法,LinkedHashMap 都進(jìn)行了重寫馍佑,接下來我們主要講解下獲取回調(diào)以及插入回調(diào)斋否。

  1. 獲取回調(diào)
    獲取回調(diào)主要是將get的節(jié)點(diǎn)放入鏈表的尾部,即將當(dāng)前節(jié)點(diǎn)賦值給 tail 成員變量拭荤,如果該節(jié)點(diǎn)存在前后關(guān)聯(lián)的節(jié)點(diǎn)茵臭,那么便將前后關(guān)聯(lián)的節(jié)點(diǎn)進(jìn)行連接。
  2. 插入回調(diào)
    插入回調(diào)最主要的是判斷是否要?jiǎng)h除頭節(jié)點(diǎn)舅世,從而實(shí)現(xiàn) LRU 緩存旦委,這個(gè)方法會調(diào)用一個(gè) removeEldestEntry() 方法來判斷是否刪除頭節(jié)點(diǎn),而開發(fā)者便可以通過重寫這個(gè)方法來實(shí)現(xiàn)一些自定義的判斷邏輯雏亚,最終實(shí)現(xiàn)業(yè)務(wù)相關(guān)的 LRU 緩存
void afterNodeInsertion(boolean evict) { // possibly remove eldest
      LinkedHashMap.Entry<K,V> first;
      if (evict && (first = head) != null && removeEldestEntry(first)) {
          K key = first.key;
          removeNode(hash(key), key, null, false, true);
      }
}
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
      return false;
}

所以缨硝,如果我們要實(shí)現(xiàn)自己的 LRU 緩存工具類,只需要繼承 LinkedHashMap 罢低,并重寫其 removeEldestEntry 方法即可查辩,如果返回true胖笛,則代表要?jiǎng)h除節(jié)點(diǎn)。最后宜岛,我們看個(gè)簡單的例子长踊,來了解如何實(shí)現(xiàn)一個(gè)基本的LRU 緩存工具類。

public class LRUCache extends LinkedHashMap<String,Object> {
    private static final int maxSize = 32;

    public LRUCache(int initialCapacity, float loadFactor){
        super(initialCapacity,loadFactor,true);
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<String, Object> eldest) {
        return this.size() > maxSize;
    }
}

總結(jié)

本章節(jié)主要是介紹了集合中的一些平時(shí)經(jīng)常被忽略的細(xì)節(jié)萍倡,同時(shí)這些細(xì)節(jié)也是面試中常問的一些點(diǎn)身弊。集合作為一個(gè)開發(fā)人員經(jīng)常使用的技術(shù)點(diǎn),頻繁的出現(xiàn)在我們的項(xiàng)目當(dāng)中列敲,要說不會用阱佛,那是不可能的,但是里面的一些細(xì)節(jié)點(diǎn)如功能設(shè)計(jì)戴而、底層原理等凑术,確實(shí)值得我們留意和思考。

接下來填硕,我們回到最初的五個(gè)問題

1. Map麦萤,List 和 Set 的區(qū)別。
2. ArrayList隨機(jī)增刪效率真的比LinkedList低嗎扁眯?
3. HashMap 的擴(kuò)容過程是怎么樣的壮莹?
4. HashMap 是線程安全的嗎,為什么不是線程安全的(JDK7,8)姻檀?
5. 編寫一個(gè)LRU緩存工具類

如果你們可以很流暢的回答這些問題命满,那么恭喜你,該章節(jié)的內(nèi)容已經(jīng)全部掌握绣版,如果不行胶台,希望可以回到對應(yīng)問題講解的地方,或者對某個(gè)不了解的點(diǎn)進(jìn)行額外的知識搜索杂抽,盡量用自己組織的語言回答這些問題诈唬。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市缩麸,隨后出現(xiàn)的幾起案子铸磅,更是在濱河造成了極大的恐慌,老刑警劉巖杭朱,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件阅仔,死亡現(xiàn)場離奇詭異,居然都是意外死亡弧械,警方通過查閱死者的電腦和手機(jī)八酒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來刃唐,“玉大人羞迷,你說我怎么就攤上這事界轩。” “怎么了闭树?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵耸棒,是天一觀的道長。 經(jīng)常有香客問我报辱,道長,這世上最難降的妖魔是什么单山? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任碍现,我火速辦了婚禮,結(jié)果婚禮上米奸,老公的妹妹穿的比我還像新娘昼接。我一直安慰自己,他們只是感情好悴晰,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布慢睡。 她就那樣靜靜地躺著,像睡著了一般铡溪。 火紅的嫁衣襯著肌膚如雪漂辐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天棕硫,我揣著相機(jī)與錄音髓涯,去河邊找鬼。 笑死哈扮,一個(gè)胖子當(dāng)著我的面吹牛纬纪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播滑肉,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼拟糕,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了应又?” 一聲冷哼從身側(cè)響起艾栋,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎惶洲,沒想到半個(gè)月后按声,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡恬吕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年签则,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片铐料。...
    茶點(diǎn)故事閱讀 40,015評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡渐裂,死狀恐怖豺旬,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情柒凉,我是刑警寧澤族阅,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站膝捞,受9級特大地震影響坦刀,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蔬咬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一鲤遥、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧林艘,春花似錦盖奈、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至啥酱,卻和暖如春爹凹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背懈涛。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工逛万, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人批钠。 一個(gè)月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓宇植,卻偏偏與公主長得像,于是被迫代替她去往敵國和親埋心。 傳聞我的和親對象是個(gè)殘疾皇子指郁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評論 2 355