Chapter 24《Collections in Depth》

可變和不可變集合

    1. Scala中的集合可分為可變集合和不可變集合。可變集合可以當場被更新,不可變集合本身是不可變的。
    1. 所有的集合類都可以在scala.collection或者其子包mutable,immutable或者generic中找到。大多數使用的集合分為三個變種,分別對應不同的可變性特征。分別位于scala.collection,scala.collection.immutable以及scala.collection.mutable
    1. scala.collection中的集合既可以是可變的,也可以是不變的。scala.collection.IndexedSeq[T]scala.collection.immutable.IndexedSeq[T]scala.collection.mutable.IndexedSeq[T]的超類。一般來說,collection中定義了與不可變集合相同的接口,mutable包中的可變集合會在不可變接口上添加一些有副作用的修改操作。
    1. Scala的默認集合實現是不可變集合,因為scala包引入的默認綁定是不可變的。

集合的一致性

  • 每一種集合都可以使用一致的語法創建對象,List(1, 2, 3),Seq(1, 2, 3),所有集合的toString方法也會產生一致的輸出格式。類型名稱加圓括號,其中的元素使用逗號隔開。類的層次結構如下:
    Traversable
        Iterable
           Seq
               IndexedSeq
                  Vector
                  ResizableArray
                  GenericArray
               LinearSeq
                  MutableList
                  List
                  Stream
           Set
               SortedSet
                  TreeSet
               HashSet(immutable)
               LinkedHashSet
               HashSet(mutable)
               BitSet
               EmptySet, Set1, Set2, Set3, Set4
           Map
               SortedMap
                  TreeMap
               HashMap(immutable)
               LinkedHashMap
               HashMap(mutable)
               EmptyMap, Map1, Map2, Map3, Map4
    

Traversable特質

    1. Traversable中定義了集合的基本操作,都在TraversableLike中。唯一需要重寫的方法是foreach:def foreach[U](f: Elem => U),事實上,foreach并不會保存任何計算結果,返回的是Unit類型。
    1. Traversable中的方法可以分為以下幾類:
    • 添加,++
    • 映射,map, flatMap, collect
    • 轉換,toIndexedSeq,toIterable,toStream,toArray,toList,toSeq,toSet,toMap,如果原集合已經匹配則直接返回該集合
    • copycopyToBuffer:ArrayBuffer和ListBuffercopyToArray
    • 大小操作,isEmpty,nonEmpty,sizehasDefiniteSize,如果hasDefiniteSizefalse,則size方法會報錯或者根本不返回。
    • 元素獲取,head,last,headOption,lastOption,findfind會選中集合中滿足條件的首個元素。并不是所有集合都會有明確的定義,第一個和最后一個。如果某個集合總是以相同的順序交出元素,那么它就是有序的,否則就是無序的;順序通常對測試比較重要,Scala對所有集合都提供了有序版本。HashSet的有序版本為LinkedHashSet
    • 子集獲取操作,takeWhile,tail,init,slice,take,drop,filter,dropWhile,filterNot,withFilter
    • 細分,splitAt(x take n, x drop n),span(x takeWhile p, x dropWhile p),partition(x filter p, x filterNot p),groupBygroupBy生成的是一個Map,分類依據為key,對應的列表為value,將集合元素切分為若干子集合
    • 元素測試,exists,forAll,count
    • 折疊,foldLeft,foldRight,/:,:\,reduceLeft,reduceRight
    • 特殊折疊,sum,product,minmax,用于操作特定類型的集合,數值型或者可比較類型
    • 字符串操作,mkString,addString,stringPrefix
    • 視圖操作,view,是一個惰性求值的方法

Iterable特質

    1. Iterable特質的所有方法都是使用iterator方法來定義的,這個抽象方法的作用是逐個交出集合的元素。Iterable中還有兩個方法返回迭代器,一個是grouped,一個是sliding
    • List(1,2,3,4,5): grouped(3)交出的是List(1,2,3)List(4,5)
    • List(1,2,3,4,5): sliding(3)交出的是List(1,2,3),List(2,3,4),List(3,4,5),再使用next迭代器就會出錯。
    1. Iterable還對Traversable添加了其他的一些方法,這些方法只有在有迭代器的情況下才能高效實現。
    • 子集合:takeRight,包含后n個元素的集合,如果集合沒有順序,那就是任意的n個。
    • dropRight,集合除去takeRight的部分。
    • 拉鏈:zip,zipAll(ys, x, y)xs長度不夠使用x填充,ys長度不夠使用y填充,zipWithIndexsameElements ys,檢查xsys是否含有相同順序的相同元素。
    1. 為什么同時會有TraversableIterable
      額外增加一層foreach的原因是有時候提供foreach比提供Iterator更容易。

序列型特質Seq,IndexedSeq和LinearSeq

    1. Seq特質代表序列,序列是一種有長度并且元素有固定下標位置的Iterable
    • 下標和長度操作:applyisDefinedAt:測試入參是否在indices中,length:集合類通用size的別名,indiceslengthCompare:用于比較兩個集合的長度,無限長度的也可以比較,Seq[T]是一個偏函數
    • 下標檢索操作:indexOf,lastIndexOf,indexOfSlice,lastIndexOfSlice,indexWhere,lastIndexWhere,segmentLength(p,i):從xs(i)開始滿足p的片段的長度,prefixLength(p)xs中滿足p的最長前綴長度。
    • 添加:+:,:+,padTo
    • 更新:updated,patch,對mutable.Seq而言還有一個update操作,s(1)=8,可以當場更改元素,但是updated不會。
    • 排序:sorted,sortwith:使用lessThan函數進行比較,sortBy:對元素應用f后進行排序得到
    • 反轉:reverse,revereIterator,reverseMap f:對元素應用f后進行倒序交出
    • 比較:startsWith,endsWith,contains,corresponds(ys, p):查看xsys對應元素是否滿足pcontainsSlice
    • 集合操作:intersect,diff,union,distinct
    1. 特質Seq有兩個子特質,IndexedSeqLinearSeq,并沒有添加新的操作,不過各自擁有不同的性能特征。LinearSeq擁有高效的headtail操作,IndexedSeq擁有高效的apply,length和隨機訪問操作。ListStream都是常用的LinearSeqArrayArrayBuffer則是常用的IndexedSeqVector是混用兩種達到添加和檢索都比較高效的數據結構。
緩沖
    1. 可變序列中比較重要的就是緩沖,緩沖允許對已有元素進行更新,同時還允許元素插入,殺出以及在尾部高效地添加元素。兩個常用的BufferListBufferArrayBuffer,都能夠高效地轉到對應的數據結構,常用的操作有添加和刪除。
    • 添加:+= 3,+= (3,4,5),++= xs,x +=: buf,添加在頭部,xs ++=: buf,insert(i, x),insertAll(i, xs)
    • 刪除操作:-= 3,remove(i),remove(i, n),trimStart n:移除緩沖中前n個元素,trimEnd:移除緩沖中后n個元素,clear
    • 克隆:clone

    1. Set是沒有重復元素的Iterable,主要的操作有:
    • 測試:contains,apply等同于containssubsetOf
    • 添加:+++
    • 移除:---
    • 集合操作:intersect,union,diff,符號版本的有&,|,&~
    1. 可變集合擁有就地修改的能力,擁有的方法是:
    • 添加:+=,++=,add
    • 移除:-=,--=,remove,clear,retain
    • 更新:update(elem, boolean),如果booleantrue,就把elem放入到set中;如果booleanfalse,就把elemset中移除。
    1. 可變集合還提供了addremove作為+=-=的變種,addremove返回值表示該操作是否讓集合發生了改變。
    1. 目前可變集的默認實現使用了哈希表來保存集的元素,不可變集的默認實現使用了一種可以跟集的元素數量適配的底層表示。空集使用單個對象表示,4個元素以內的集合使用Set1,Set2,Set3,Set4的對象表示,更多元素的集使用哈希字典樹實現。4個元素以內的集,不可變集合的實現比可變集合的實現更緊湊。如果使用到的集合比較小,盡量使用不可變集。

映射

    1. Map是由鍵值對組成的IterablePredef中提供了一個隱式轉換,可以使用key -> value這樣的寫法表示(key, value)這個對偶。Map的操作和Set基本相似,可變Map提供額外的更多操作。
    • 查找:apply,get,getOrElse,contains,isDefinedAt
    • 更新:+,++,updated
    • 移除:-,--
    • 產生子集:keys,KeySet,KeysIterator,valuesIterator,values
    • 變換:filterKeys,mapValues
    1. 可變Map的操作:
    • 更新:ms(k) = v,+=,++=,put,getOrElseUpdate
    • 移除:-=,--=,remove,retain
    • 變換:transform
    • 復制:copy
    1. getOrElseUpdate通常被用做緩存。getOrElseUpdate(k, f),第二個參數是傳名參數,只有在get(k) = None的時候才會被觸發調用。

具體的不可變集合類

列表
  • 列表,headtail以及在頭部添加元素是常量時間,剩余的許多操作都是線性時間。
  • 流和列表很像,不過其元素是惰性計算的,流可以是無限長的,只有被請求到的元素才會被計算,剩余的特征性能和列表是一樣的。流的構造方法
    scala> val str = 1 #:: 2 #:: 3 #:: Stream.empty
    str: scala.collection.immutable.Stream[Int] = Stream(1, ?)
    
    流可以用在無限的遞歸調用中,使用toList會強制計算其中的惰性部分。
    scala> def fibFrom(a: Int, b: Int): Stream[Int] = a #:: fibFrom(b, a + b)
    scala> val fibs = fibFrom(1, 1).take(7)
    fibs: scala.collection.immutable.Stream[Int] = Stream(1, ?)
    scala> fibs.toList
    res23: List[Int] = List(1, 1, 2, 3, 5, 8, 13)
    
向量
    1. 向量可以訪問和修改任意位置的元素。向量使用:+:+來添加元素,Vector.empty
    1. 向量的內部是寬而淺的樹,樹的每個節點上多達32個元素或者32個其他節點。元素個數在32以內的可以使用單個節點解決,2^10個元素可以使用一跳解決,對于正常大小的向量,選擇一個元素最多需要5次基本操作就可以。向量的更新也是實效上的常量時間,只會復制從根節點到受影響元素這條path上的節點。目前是不可變IndexedSeq的默認實現。
不可變的棧
  • 很少使用,push相當于List::操作,pop相當于Listtail操作
不可變的隊列
  • enqueue(List(1,2,3))dequeue是個元組,第一個元素是出隊的元素,第二個元素是隊列中剩下的元素組成的Queue
  • 可變隊列的dequeue就直接是出隊的元素。
區間
  • range,它的內部表示占據常量的空間,因為Range的對象可以使用三個數來表示,start,end,step
哈希字典樹
  • 是實現高效的不可變集和不可變Map的方式,內部類似于向量,每個節點有32個元素或者32個節點,但是選擇是基于哈希碼,使用最低5位找到第一棵子樹,接下來的5位找到第二棵子樹,當某個節點所有元素的哈希碼各不相同時,這個選擇過程就停止了。因此并不是用到哈希碼的所有部分,哈希字典樹在隨機查找和插入刪除之間有比較好的平衡,因此是不可變集以及映射的默認實現。
位組
  • bit set是用來表示更大整數的小整數的集合。比如包含3,2,0的位組可以使用整數1101表示,轉成十進制就是13。從內部講,位組使用的是一個64位的Long數組,第一個Long表示0-63的整數,對位組的操作非常快,測試某個位組是否包含某個值只需要常量時間。
列表映射
  • ListMapMap表示為一個由鍵值對組成的鏈表,很少用,因為標準的不可變Map幾乎總是比ListMap快,除非經常使用列表中的首個元素。

具體的可變集合類

數組緩沖
  • 數組緩沖包含一個數組和一個大小,對數組緩沖的大部分操作跟數組一樣,因為這些操作只是簡單地訪問和修改底層的數組。數組緩沖可以周期尾部高效地添加數據,非常適用于通過往尾部追加新元素來高效構建大集合的場景。可以使用ArrayBuffer.toArray方法構建Array數組。
列表緩沖
  • 列表緩沖和數組緩沖很像,構建之后可以將列表緩沖轉換為列表,內部使用的是鏈表而不是數組。
字符串構造器
  • 字符串構造器有助于構建字符串,使用new StringBuilder創建即可,可使用toString方法
鏈表,2.11.0之后已經被deprecated了。

鏈表是由用next指針鏈接起來的節點組成的可變序列,在Scala中,并不使用null表示空鏈表,空鏈表的表示是next字段指向自己。LinkedList.empty.isEmpty返回的是true而不是拋出NullPointerException的異常。

可變列表
  • MutableList是由一個鏈表和一個指向該列表末端的空節點組成,這使得往列表尾部的追加操作是常量時間的,因為它免除了遍歷列表來尋找末端的需要,MutableList目前是scalamutable.LinearSeq的標準實現。
隊列
  • 在可變隊列中,enqueue是可以使用+=++=來替代的,dequeue方法返回的是頭部元素。
數組序列
  • 數組序列是固定大小的,內部使用Array[AnyRef]來存放元素,Scala中的實現是ArraySeq類。
  • 和不可變版本是一樣的,但是可以就地修改棧
數組棧
  • ArrayStack是可變棧的另一種實現,內部是一個Array,在需要時需要改變大小。提供了快速的下標索引,一般而言大多數操作都比普通的可變棧更快。
哈希表
  • 哈希表使用數組來存放元素,元素的存放位置取決于該元素的哈希碼。往哈希表中添加元素只需要常量時間,只要數組中不發生碰撞。只要哈希表中的對象能夠按照哈希碼分布地非常均勻,操作就很快。因此Scala中默認的可變映射和可變集的實現都是基于哈希表的。HashSetHashMap
弱哈希映射
  • 對于這種哈希映射,垃圾收集器并不會跟蹤映射到其中的鍵的鏈接。如果沒有其他引用指向這個鍵,那么關聯值就會消失。弱哈希映射對于類似緩存這樣的任務十分有用。如果是在常規的HashMap中,這個映射會無限增長,所有的鍵都不會被當做垃圾處理,使用弱哈希映射就可以避免這個問題。Scala中弱哈希的實現是基于Java中的WeakHashMap實現的。
并發映射
  • 并發映射可以被多個線程同時訪問。除了常見的Map操作之外,還提供了如下原子操作:m putIfAbsent(k, v)如果不存在k,v的綁定,添加k->v的綁定;m remove (k,v)如果k映射到v,移除該條目;m replace(k, old, new)如果k綁定到old,將k關聯的值更新為newm replace(k, v),如果k綁定到某個值,將k關聯的值替換為v。實現是基于JavaConcurrentHashMap
可變位組
  • 可變位組可以被當前修改,在更新方面比不可變位組高效一點。

數組

  • Scala中數組是一種特殊的集合,一方面,Scala的數組和Java的數組一一對應。Scala的數組Array[Int]Javaint[]表示,Array[Double]對應double[],另一方面,Scala提供了更多的功能。首先Scala的數組支持泛型,可以存在Array[T]T是類型參數,其次,Scala數組和Seq是兼容的,數組還支持所有的序列操作。
  • Scala的數據是用的Java中的數組表示的,如何支持序列的操作,主要是因為使用了隱式轉換。每當數組被用作Seq的時候,會被隱式的轉為Seq的子類,這個子類的名稱是mutable.WrappedArray。另外一個可以被應用的隱式轉換,這個轉換只是簡單地將所有的序列方法添加到數組,而不是將數組本身變成序列。將這個數組被包裝成另一個類型為ArrayOps的對象,這個對象支持所有的序列方法。這個對象的聲明周期很短,通常在調用完序列方法之后就不再訪問了,存儲空間可以被回收。現代的VM會完全避免創建這個對象。
  • 編譯器會選擇ArrayOpstoInt方法而不是WarppedArray.toInt方法主要是因為WarppedArray的隱式轉換定義在LowPriorityImplicits中,而ArrayOps的隱式轉換定義在Predef中,Predef繼承了LowPriorityImplicits
  • Scala中,可以使用Array[T]這樣的表示,像Array[T]這樣的泛型數組在運行的時候可以是Array[Int]等八種基本數據類型數組,也可以是對象的數組。在運行時,當類型為Array[T]的數組元素被更新或者訪問時,有一系列的類型檢查來決定實際的數據類型,在一定程度上減慢了數組操作。對泛型數組的訪問會比確定類型的數組訪問慢上幾倍。
    // This is wrong!因為類型擦除時T的真正類型被隱去了
    def evenElems[T](xs: Vector[T]): Array[T] = {
    val arr = new Array[T]((xs.length + 1) / 2)
    for (i <- 0 until xs.length by 2)
    arr(i / 2) = xs(i)
    arr
    }
    error: cannot find class tag for element type T
    val arr = new Array[T]((arr.length + 1) / 2)
    ^
    
    需要提供線索來幫助編譯器確定evenElems實際的類型參數是什么。使用scala.reflect.ClassTag的類標簽,類標簽描述的是給定類型"被擦除的類型",對于許多場景,編譯器可以自己生成類標簽,對于完全泛化的場景,通常的做法是使用上下文界定傳入類型標簽,設定了一個隱式參數ClassTag[T]。如果能夠找到,則可以正確構建數組。
    // This works
    import scala.reflect.ClassTag
    def evenElems[T: ClassTag](xs: Vector[T]): Array[T] = {
    val arr = new Array[T]((xs.length + 1) / 2)
    for (i <- 0 until xs.length by 2)
    arr(i / 2) = xs(i)
    arr
    }
    
    對于所有具體類型,編譯器都可以構建一個隱式的ClassTag對象,但是入參本身是一個類型而且不帶類標簽,則不能使用。
    scala> def wrap[U](xs: Vector[U]) = evenElems(xs)
    <console>:9: error: No ClassTag available for U
    def wrap[U](xs: Vector[U]) = evenElems(xs)
                 ^
    
    編譯器需要得到關于類型UClassTag對象,但是沒有找到,解決方法,在U后面使用上下文界定引入:ClassTag

字符串

  • 字符串轉為序列的隱式轉換也存在兩個,一個是優先級較低的WarppedString,將字符串轉換為序列,一個是優先級較高的StringOps,為字符串添加了所有不支持的序列操作。

性能特征

相等性
  • 集合類庫對相等性的處理和哈希的處理方式是一樣的。
      1. 首先分為三大類,SeqSetMap,不同類下的集合永不相等。
      1. 另一方面如果是有序的序列,在元素相等的情況下,元素的順序也要相等。使用可變元素作為哈希集或者哈希映射的鍵是不好的做法,因為使用鍵的哈希碼來進行查找,如果鍵改變了,哈希碼也就改變了,可能就找不到了,或者別的鍵也改變了,哈希碼恰巧為修改的那個鍵,那么查到的就是錯的。所以一般不推薦使用可變的對象來作為哈希的鍵值。
視圖
  • 集合中有mapfilter等變換器來接收一個集合并產出一個集合。變換器可以通過兩種主要的方法實現,一種是嚴格的,一種是惰性的。嚴格的變換器會構造出帶有所有元素的新集合,惰性的變換器只是構造出結果集合的一個代理,元素會按需構造。
    def lazyMap[T, U](coll: Iterable[T], f: T => U) =
    new Iterable[U] {
    def iterator = coll.iterator map f
    }
    

    只有當新集合使用iterator方法的時候才會生成新的集合。系統化的方式可以將每個集合轉換成惰性的版本,或者是反過來,這個就是集合視圖。視圖是一種特殊的集合,代表了某個基礎集合,但是使用惰性的方式實現所有的變換器。seq.view方法得到集合的視圖。例如 v map (_ + 1) map (_ * 2)會生成中間結果,如果一次性執行map操作會更快,方法就是將集合轉換為視圖,執行map操作,再將視圖轉回到集合。
    (v.view map (_ + 1) map (_ * 2)).force
    v.view產生一個SeqView對象,是一個惰性求值的Seq。有兩個類型參數,Int表示該視圖的元素類型,Vector[Int]表示該視圖取回時要轉回的類型集合。
    v.view map (_ + 1)得到一個SeqViewM[Int, Seq[]]表示一個含有函數map(+1)的操作需要被應用到v的包裝器,M表示Map操作。map (_ * 2)之后得到SeqViewMM對象,最后使用force可以得到轉換之后的結果。
    第一次應用Map的時候就丟失了關于集合類型Vector的信息,因為視圖的代碼非常多,通常只是對一般的集合而不是特定的集合實現視圖。
    findPalindrome(words take 1000000)會構造出來一個1000000容量的中間結果,不管回文單詞是否已經發生。會造成大量的不必要的開銷,使用words.view take 1000000可以減少中間結果的開銷。 實驗發現并沒有???
    view還沒有看完


迭代器

  • 迭代器不是集合,是逐個訪問集合元素的一種方式。兩個基本操作是nexthasNext,對it.next的調用會返回迭代器的下一個元素并將迭代器的狀態往前推進一步。對同一個迭代器再次調用next會交出在前一個返回的基礎上更進一步的元素。如果沒有更多的元素可以返回,那么對next的調用就會拋出NoSuchElementException,可以使用hasNext的方法來檢測是否還有更多的元素可以返回。

  • Scala的迭代器還提供了TraversableIterableSeq特質中的大部分方法,它們提供了foreach用來對迭代器返回的每個元素執行給定的過程。

  • 迭代器的foreach和可遍歷集合Traversable的同名方法有一個重要的區別,對迭代器調用foreach,執行完之后會將迭代器留在末端,再次調用next會拋出異常,但是可遍歷集合的foreach不會,可以連續兩次執行foreachIterator中其他操作也會出現同樣的問題,操作完之后迭代器被留在末端。比如說map方法。只有一個方法允許使用同一個迭代器,duplicate方法,返回一個元組。這兩個迭代器互相獨立,原始的迭代器it在這個方法調用完成之后被推到了末端。

  • 總體來說,IteratorTraversable的行為還是比較類似的,因此抽取出來了一個公共的接口為TraversableOnce,該接口的對象可以使用foreach進行遍歷,但是遍歷完之后該對象的狀態是不明確的,如果是Iterator,則迭代器在尾部,如果是Traversable,則對象保持原狀。

  • 迭代器的主要操作,next,hasNext,grouped,sliding,……和集合的操作是很相似的。添加的一個主要功能是buffered,將迭代器轉換為帶緩沖的迭代器。帶緩沖的迭代器等于是有一個前哨,可以使用head返回迭代器的第一個元素,但是不向前推進迭代器。例如:使用這樣的判斷語句進行判斷的時候,while (iterator.next() >= 2) {},會丟失迭代器中的一個元素,第一個<2的元素是得不到的,正確的方法是使用帶緩沖的iterator.head的方法來進行探尋。iterator.buffered得到的迭代器和iterator本身使用的是同一套迭代器。


從頭構建集合

  • Collection集合下面的所有類的伴生對象中都具有配套的apply方法,可以直接使用List(1,2,3)或者Map(1->3, 3->4)這樣的方式來構建集合。對于接口類型,Traversable(1,2,3)會調用該接口的默認實現。在每一個伴生對象中也定義了一個空對象emptyList.empty = List()

  • Seq的子類伴生對象中定義有許多新的功能,比如說fill可填充表達式,tabulate可填充表達式,iterate可對指定的元素進行指定次數的計算。


Java和Scala集合的轉換

  • Scala提供了JavaScala中主要集合之間的隱式轉換,這些轉換在import collection.JavaConversions._,轉換的時候使用的是wrapper的對象,不做copy的操作,如果從Java集合轉到了Scala集合,然后又從Scala集合轉回到Java,這兩個集合是同一個對象。在轉換到Java集合的時候不考慮是否為可變集合,如果在不可變集合上試圖進行可變操作,Java會拋出異常。

  • 有雙向轉換:

    Iterator ? java.util.Iterator
    Iterator ? java.util.Enumeration
    Iterable ? java.util.Iterable
    Iterable ? java.util.Collection
    mutable.Buffer ? java.util.List
    mutable.Set ? java.util.Set
    mutable.Map ? java.util.Map

  • 有單向轉換

    Seq ? java.util.List
    mutable.Seq ? java.util.List
    Set ? java.util.Set
    Map ? java.util.Map

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,237評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,957評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,248評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,356評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,081評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,485評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,534評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,720評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,263評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,025評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,204評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,787評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,461評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,874評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,105評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,945評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,205評論 2 375

推薦閱讀更多精彩內容