徹底理解Golang Slice

看完這篇文章笋庄,下面這些高頻面試題你都會答了吧

  1. Go slice的底層實(shí)現(xiàn)原理
  2. Go array和slice的區(qū)別
  3. Go slice深拷貝和淺拷貝
  4. Go slice擴(kuò)容機(jī)制是怎樣的缝彬?
  5. 為什么Go slice是非線程安全的工碾?

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

slice是無固定長度的數(shù)組,底層結(jié)構(gòu)是一個(gè)結(jié)構(gòu)體哪自,包含如下3個(gè)屬性

一個(gè) slice 在 golang 中占用 24 個(gè) bytes

type slice struct {
    array unsafe.Pointer 
    len   int 
    cap   int 
}

array : 包含了一個(gè)指向一個(gè)數(shù)組的指針渔伯,數(shù)據(jù)實(shí)際上存儲在這個(gè)指針指向的數(shù)組上,占用 8 bytes

len: 當(dāng)前 slice 使用到的長度蓝翰,占用8 bytes

cap : 當(dāng)前 slice 的容量,同時(shí)也是底層數(shù)組 array 的長度女嘲, 8 bytes

slice并不是真正意義上的動態(tài)數(shù)組畜份,而是一個(gè)引用類型。slice總是指向一個(gè)底層array欣尼,slice的聲明也可以像 array一樣爆雹,只是長度可變。golang中通過語法糖,使得我們可以像聲明array一樣钙态,自動創(chuàng)建slice結(jié)構(gòu)體

根據(jù)索引位置取切片slice 元素值時(shí)慧起,默認(rèn)取值范圍是(0~len(slice)-1),一般輸出slice時(shí)册倒,通常是指 slice[0:len(slice)-1]完慧,根據(jù)下標(biāo)就可以輸出所指向底層數(shù)組中的值

主要特性

引用類型

golang 有三個(gè)常用的高級類型slice、map剩失、channel, 它們都是引用類型屈尼,當(dāng)引用類型作為函數(shù)參數(shù)時(shí),可能會修改原內(nèi)容數(shù)據(jù)拴孤。

func sliceModify(s []int) {
    s[0] = 100
}

func sliceAppend(s []int) []int {
    s = append(s, 100)
    return s
}

func sliceAppendPtr(s *[]int) {
    *s = append(*s, 100)
    return
}

// 注意:Go語言中所有的傳參都是值傳遞(傳值)脾歧,都是一個(gè)副本,一個(gè)拷貝演熟。
// 拷貝的內(nèi)容是非引用類型(int鞭执、string、struct等這些)芒粹,在函數(shù)中就無法修改原內(nèi)容數(shù)據(jù)兄纺;
// 拷貝的內(nèi)容是引用類型(interface、指針化漆、map估脆、slice、chan等這些)座云,這樣就可以修改原內(nèi)容數(shù)據(jù)疙赠。
func TestSliceFn(t *testing.T) {
    // 參數(shù)為引用類型slice:外層slice的len/cap不會改變,指向的底層數(shù)組會改變
    s := []int{1, 1, 1}
    newS := sliceAppend(s)
    // 函數(shù)內(nèi)發(fā)生了擴(kuò)容
    t.Log(s, len(s), cap(s))
    // [1 1 1] 3 3
    t.Log(newS, len(newS), cap(newS)) 
    // [1 1 1 100] 4 6

    s2 := make([]int, 0, 5)
    newS = sliceAppend(s2)
    // 函數(shù)內(nèi)未發(fā)生擴(kuò)容
    t.Log(s2, s2[0:5], len(s2), cap(s2)) 
    // [] [100 0 0 0 0] 0 5
    t.Log(newS, newS[0:5], len(newS), cap(newS))
    // [100] [100 0 0 0 0] 1 5

    // 參數(shù)為引用類型slice的指針:外層slice的len/cap會改變朦拖,指向的底層數(shù)組會改變
    sliceAppendPtr(&s)
    t.Log(s, len(s), cap(s)) 
  // [1 1 1 100] 4 6
    sliceModify(s)
    t.Log(s, len(s), cap(s)) 
  // [100 1 1 100] 4 6
}

公眾號后臺caspar回復(fù)【代碼】獲取本文所有示例代碼

切片狀態(tài)

切片有3種特殊的狀態(tài)圃阳,分為「零切片」、「空切片」和「nil 切片」

func TestSliceEmptyOrNil(t *testing.T) {
    var slice1 []int           
  // slice1 is nil slice
    slice2 := make([]int, 0)    
    // slcie2 is empty slice
    var slice3 = make([]int, 2) 
    // slice3 is zero slice
    if slice1 == nil {
        t.Log("slice1 is nil.") 
        // 會輸出這行
    }
    if slice2 == nil {
        t.Log("slice2 is nil.") 
        // 不會輸出這行
    }
    t.Log(slice3) // [0 0]
}

非線程安全

slice不支持并發(fā)讀寫璧帝,所以并不是線程安全的捍岳,使用多個(gè) goroutine 對類型為 slice 的變量進(jìn)行操作,每次輸出的值大概率都不會一樣睬隶,與預(yù)期值不一致; slice在并發(fā)執(zhí)行中不會報(bào)錯(cuò)锣夹,但是數(shù)據(jù)會丟失

/**
* 切片非并發(fā)安全
* 多次執(zhí)行,每次得到的結(jié)果都不一樣
* 可以考慮使用 channel 本身的特性 (阻塞) 來實(shí)現(xiàn)安全的并發(fā)讀寫
 */
func TestSliceConcurrencySafe(t *testing.T) {
    a := make([]int, 0)
    var wg sync.WaitGroup
    for i := 0; i < 10000; i++ {
        wg.Add(1)
        go func(i int) {
            a = append(a, i)
            wg.Done()
        }(i)
    }
    wg.Wait()
    t.Log(len(a)) 
    // not equal 10000
}

如果想實(shí)現(xiàn)slice線程安全理疙,有兩種方式:

方式一:通過加鎖實(shí)現(xiàn)slice線程安全晕城,適合對性能要求不高的場景泞坦。

func TestSliceConcurrencySafeByMutex(t *testing.T) {
    var lock sync.Mutex //互斥鎖
    a := make([]int, 0)
    var wg sync.WaitGroup
    for i := 0; i < 10000; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            lock.Lock()
            defer lock.Unlock()
            a = append(a, i)
        }(i)
    }
    wg.Wait()
    t.Log(len(a)) 
    // equal 10000
}

方式二:通過channel實(shí)現(xiàn)slice線程安全窖贤,適合對性能要求高的場景。

func TestSliceConcurrencySafeByChanel(t *testing.T) {
    buffer := make(chan int)
    a := make([]int, 0)
    // 消費(fèi)者
    go func() {
        for v := range buffer {
            a = append(a, v)
        }
    }()
    // 生產(chǎn)者
    var wg sync.WaitGroup
    for i := 0; i < 10000; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            buffer <- i
        }(i)
    }
    wg.Wait()
    t.Log(len(a)) 
    // equal 10000
}

共享存儲空間

多個(gè)切片如果共享同一個(gè)底層數(shù)組,這種情況下赃梧,對其中一個(gè)切片或者底層數(shù)組的更改滤蝠,會影響到其他切片

/**
* 切片共享存儲空間
 */
func TestSliceShareMemory(t *testing.T) {
    slice1 := []string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"}
    Q2 := slice1[3:6]
    t.Log(Q2, len(Q2), cap(Q2)) 
    // [4 5 6] 3 9
    Q3 := slice1[5:8]
    t.Log(Q3, len(Q3), cap(Q3)) 
    // [6 7 8] 3 7
    Q3[0] = "Unkown"
    t.Log(Q2, Q3) 
    // [4 5 Unkown] [Unkown 7 8]

    a := []int{1, 2, 3, 4, 5}
    shadow := a[1:3]
    t.Log(shadow, a)             
    // [2 3] [1 2 3 4 5]
    shadow = append(shadow, 100) 
    // 會修改指向數(shù)組的所有切片
    t.Log(shadow, a)            
  // [2 3 100] [1 2 3 100 5]
}

常用操作

創(chuàng)建

slice 的創(chuàng)建有4種方式,如下:

func TestSliceInit(t *testing.T) {
    // 初始化方式1:直接聲明
    var slice1 []int
    t.Log(len(slice1), cap(slice1)) 
    // 0, 0
    slice1 = append(slice1, 1)
    t.Log(len(slice1), cap(slice1)) 
    // 1, 1, 24

    // 初始化方式2:使用字面量
    slice2 := []int{1, 2, 3, 4}
    t.Log(len(slice2), cap(slice2)) 
    // 4, 4, 24

    // 初始化方式3:使用make創(chuàng)建slice
    slice3 := make([]int, 3, 5)           
  // make([]T, len, cap) cap不傳則和len一樣
    t.Log(len(slice3), cap(slice3))       
  // 3, 5
    t.Log(slice3[0], slice3[1], slice3[2]) 
    // 0, 0, 0
    // t.Log(slice3[3], slice3[4]) 
    // panic: runtime error: index out of range [3] with length 3
    slice3 = append(slice3, 1)
    t.Log(len(slice3), cap(slice3)) 
    // 4, 5, 24

    // 初始化方式4: 從切片或數(shù)組“截取”
    arr := [100]int{}
    for i := range arr {
        arr[i] = i
    }
    slcie4 := arr[1:3]
    slice5 := make([]int, len(slcie4))
    copy(slice5, slcie4)
    t.Log(len(slcie4), cap(slcie4), unsafe.Sizeof(slcie4)) 
    // 2授嘀,99物咳,24
    t.Log(len(slice5), cap(slice5), unsafe.Sizeof(slice5)) 
    // 2,2蹄皱,24
}

增加

func TestSliceGrowing(t *testing.T) {
    slice1 := []int{}
    for i := 0; i < 10; i++ {
        slice1 = append(slice1, i)
        t.Log(len(slice1), cap(slice1))
    }
    // 1 1
    // 2 2
    // 3 4
    // 4 4
    // 5 8
    // 6 8
    // 7 8
    // 8 8
    // 9 16
    // 10 16
}

刪除

func TestSliceDelete(t *testing.T) {
    slice1 := []int{1, 2, 3, 4, 5}
    var x int
    // 刪除最后一個(gè)元素
    x, slice1 = slice1[len(slice1)-1], slice1[:len(slice1)-1] 
    t.Log(x, slice1, len(slice1), cap(slice1)) 
    // 5 [1 2 3 4] 4 5

    // 刪除第2個(gè)元素  
    slice1 = append(slice1[:2], slice1[3:]...) 
    t.Log(slice1, len(slice1), cap(slice1))    
    // [1 2 4] 3 5
}

查找

v := s[i] // 下標(biāo)訪問

修改

s[i] = 5 // 下標(biāo)修改

截取

/**
* 切片截取
 */
func TestSliceSubstr(t *testing.T) {
    slice1 := []int{1, 2, 3, 4, 5}
    slice2 := slice1[:]
    // 截取 slice[left:right:max]
    // left:省略默認(rèn)0
    // right:省略默認(rèn)len(slice1)
    // max: 省略默認(rèn)len(slice1)
    // len = right-left+1
    // cap = max-left
    t.Log(slice2, len(slice2), cap(slice2)) 
    // 1 2 3 4 5] 5 5
    slice3 := slice1[1:]
    t.Log(slice3, len(slice3), cap(slice3)) 
    // [2 3 4 5] 4 4
    slice4 := slice1[:2]
    t.Log(slice4, len(slice4), cap(slice4)) 
    // [1 2] 2 5
    slice5 := slice1[1:2]
    t.Log(slice5, len(slice5), cap(slice5)) 
    // [2] 1 4
    slice6 := slice1[:2:5]
    t.Log(slice6, len(slice6), cap(slice6)) 
    // [1 2] 2 5
    slice7 := slice1[1:2:2]
    t.Log(slice7, len(slice7), cap(slice7)) 
    // [2] 1 1
}

遍歷

切片有3種遍歷方式

/**
* 切片遍歷
 */
func TestSliceTravel(t *testing.T) {
    slice1 := []int{1, 2, 3, 4}
    for i := 0; i < len(slice1); i++ {
        t.Log(slice1[i])
    }
    for idx, e := range slice1 {
        t.Log(idx, e)
    }
    for _, e := range slice1 {
        t.Log(e)
    }
}

反轉(zhuǎn)

func TestSliceReverse(t *testing.T) {
    a := []int{1, 2, 3, 4, 5}
    for left, right := 0, len(a)-1; left < right; left, right = left+1, right-1 {
        a[left], a[right] = a[right], a[left]
    }
    t.Log(a, len(a), cap(a)) 
    // [5 4 3 2 1] 5 5
}

拷貝

開發(fā)中會經(jīng)常的把一個(gè)變量復(fù)制給另一個(gè)變量览闰,那么這個(gè)過程,可能是深淺拷貝巷折,那么今天幫大家區(qū)分一下這兩個(gè)拷貝的區(qū)別和具體的區(qū)別

深拷貝

拷貝的是數(shù)據(jù)本身压鉴,創(chuàng)造一個(gè)樣的新對象,新創(chuàng)建的對象與原對象不共享內(nèi)存锻拘,新創(chuàng)建的對象在內(nèi)存中開辟一個(gè)新的內(nèi)存地址油吭,新對象值修改時(shí)不會影響原對象值。既然內(nèi)存地址不同署拟,釋放內(nèi)存地址時(shí)婉宰,可分別釋放

值類型的數(shù)據(jù),默認(rèn)賦值操作都是深拷貝推穷,Array心包、Int、String馒铃、Struct谴咸、Float,Bool骗露。引用類型的數(shù)據(jù)如果想實(shí)現(xiàn)深拷貝岭佳,需要通過輔助函數(shù)完成

比如golang深拷貝copy 方法會把源切片值(即 from Slice )中的元素復(fù)制到目標(biāo)切片(即 to Slice )中,并返回被復(fù)制的元素個(gè)數(shù)萧锉,copy 的兩個(gè)類型必須一致珊随。copy 方法最終的復(fù)制結(jié)果取決于較短的那個(gè)切片,當(dāng)較短的切片復(fù)制完成柿隙,整個(gè)復(fù)制過程就全部完成了

/**
* 深拷貝
 */
func TestSliceDeepCopy(t *testing.T) {
    slice1 := []int{1, 2, 3, 4, 5}
    slice2 := make([]int, 5, 5)
    // 深拷貝
    copy(slice2, slice1)                   
    t.Log(slice1, len(slice1), cap(slice1)) 
    // [1 2 3 4 5] 5 5
    t.Log(slice2, len(slice2), cap(slice2)) 
    // [1 2 3 4 5] 5 5
    slice1[1] = 100                        
    t.Log(slice1, len(slice1), cap(slice1)) 
    // [1 100 3 4 5] 5 5
    t.Log(slice2, len(slice2), cap(slice2)) 
    // [1 2 3 4 5] 5 5
}

淺拷貝

拷貝的是數(shù)據(jù)地址叶洞,只復(fù)制指向的對象的指針,此時(shí)新對象和老對象指向的內(nèi)存地址是一樣的禀崖,新對象值修改時(shí)老對象也會變化衩辟。釋放內(nèi)存地址時(shí),同時(shí)釋放內(nèi)存地址波附。

引用類型的數(shù)據(jù)艺晴,默認(rèn)全部都是淺拷貝昼钻,Slice、Map等

目的切片和源切片指向同一個(gè)底層數(shù)組封寞,任何一個(gè)數(shù)組元素改變然评,都會同時(shí)影響兩個(gè)數(shù)組。

/**
* 淺拷貝
 */
func TestSliceShadowCopy(t *testing.T) {
    slice1 := []int{1, 2, 3, 4, 5}
    // 淺拷貝(注意 := 對于引用類型是淺拷貝狈究,對于值類型是深拷貝)
    slice2 := slice1     
    t.Logf("%p", slice1) // 0xc00001c120
    t.Logf("%p", slice2) // 0xc00001c120
    // 同時(shí)改變兩個(gè)數(shù)組碗淌,這時(shí)就是淺拷貝,未擴(kuò)容時(shí)抖锥,修改 slice1 的元素之后亿眠,slice2 的元素也會跟著修改
    slice1[0] = 10
    t.Log(slice1, len(slice1), cap(slice1)) 
    // [10 2 3 4 5] 5 5
    t.Log(slice2, len(slice2), cap(slice2)) 
    // [10 2 3 4 5] 5 5
    // 注意下:擴(kuò)容后,slice1和slice2不再指向同一個(gè)數(shù)組磅废,修改 slice1 的元素之后缕探,slice2 的元素不會被修改了
    slice1 = append(slice1, 5, 6, 7, 8)
    slice1[0] = 11   
  // 這里可以發(fā)現(xiàn),slice1[0] 被修改為了 11, slice1[0] 還是10
    t.Log(slice1, len(slice1), cap(slice1)) 
    // [11 2 3 4 5 5 6 7 8] 9 10
    t.Log(slice2, len(slice2), cap(slice2))
  // [10 2 3 4 5] 5 5
}

在復(fù)制 slice 的時(shí)候还蹲,slice 中數(shù)組的指針也被復(fù)制了爹耗,在觸發(fā)擴(kuò)容邏輯之前,兩個(gè) slice 指向的是相同的數(shù)組谜喊,觸發(fā)擴(kuò)容邏輯之后指向的就是不同的數(shù)組了

擴(kuò)容

擴(kuò)容會發(fā)生在slice append的時(shí)候潭兽,當(dāng)slice的cap不足以容納新元素,就會進(jìn)行擴(kuò)容

源碼:https://github.com/golang/go/blob/master/src/runtime/slice.go

func growslice(et *_type, old slice, cap int) slice {
      // 省略一些判斷...
    newcap := old.cap
    doublecap := newcap + newcap
    if cap > doublecap {
        newcap = cap
    } else {
        if old.len < 1024 {
            newcap = doublecap
        } else {
            // Check 0 < newcap to detect overflow
            // and prevent an infinite loop.
            for 0 < newcap && newcap < cap {
                newcap += newcap / 4
            }
            // Set newcap to the requested cap when
            // the newcap calculation overflowed.
            if newcap <= 0 {
                newcap = cap
            }
        }
    }
    // 省略一些后續(xù)...
}
  • 如果新申請容量比兩倍原有容量大斗遏,那么擴(kuò)容后容量大小 等于 新申請容量
  • 如果原有 slice 長度小于 1024山卦, 那么每次就擴(kuò)容為原來的 2 倍
  • 如果原 slice 大于等于 1024, 那么每次擴(kuò)容就擴(kuò)為原來的 1.25 倍

內(nèi)存泄露

由于slice的底層是數(shù)組诵次,很可能數(shù)組很大账蓉,但slice所取的元素?cái)?shù)量卻很小,這就導(dǎo)致數(shù)組占用的絕大多數(shù)空間是被浪費(fèi)的

Case1:

比如下面的代碼逾一,如果傳入的slice b是很大的铸本,然后引用很小部分給全局量a,那么b未被引用的部分(下標(biāo)1之后的數(shù)據(jù))就不會被釋放遵堵,造成了所謂的內(nèi)存泄漏箱玷。

var a []int

func test(b []int) {
    a = b[:1] // 和b共用一個(gè)底層數(shù)組
    return
}

那么只要全局量a在,b就不會被回收陌宿。

如何避免锡足?

在這樣的場景下注意:如果我們只用到一個(gè)slice的一小部分,那么底層的整個(gè)數(shù)組也將繼續(xù)保存在內(nèi)存當(dāng)中壳坪。當(dāng)這個(gè)底層數(shù)組很大舶得,或者這樣的場景很多時(shí),可能會造成內(nèi)存急劇增加爽蝴,造成崩潰沐批。

所以在這樣的場景下纫骑,我們可以將需要的分片復(fù)制到一個(gè)新的slice中去,減少內(nèi)存的占用

var a []int

func test(b []int) {
    a = make([]int, 1)
    copy(a, b[:0])
    return
}

Case2:

比如下面的代碼珠插,返回的slice是很小一部分惧磺,這樣該函數(shù)退出后颖对,原來那個(gè)體積較大的底層數(shù)組也無法被回收

func test2() []int{
    s = make([]int, 0, 10000)
    for i := 0; i < 10000; i++ {
        s = append(s, p)
    }
    s2 := s[100:102]
    return s2
}

如何避免捻撑?

將需要的分片復(fù)制到一個(gè)新的slice中去,減少內(nèi)存的占用

func test2() []int{
    s = make([]int, 0, 10000)
    for i := 0; i < 10000; i++ {
      // 一些計(jì)算...
        s = append(s, p)
    }
    s2 := make([]int, 2)
    copy(s2, s[100:102])
    return s2
}

切片與數(shù)組對比

數(shù)組是一個(gè)固定長度的缤底,初始化時(shí)候必須要指定長度顾患,不指定長度的話就是切片了

數(shù)組是值類型,將一個(gè)數(shù)組賦值給另一個(gè)數(shù)組時(shí)个唧,傳遞的是一份深拷貝江解,賦值和函數(shù)傳參操作都會復(fù)制整個(gè)數(shù)組數(shù)據(jù),會占用額外的內(nèi)存徙歼;切片是引用類型犁河,將一個(gè)切片賦值給另一個(gè)切片時(shí),傳遞的是一份淺拷貝魄梯,賦值和函數(shù)傳參操作只會復(fù)制len和cap桨螺,但底層共用同一個(gè)數(shù)組,不會占用額外的內(nèi)存酿秸。

//a是一個(gè)數(shù)組灭翔,注意數(shù)組是一個(gè)固定長度的,初始化時(shí)候必須要指定長度辣苏,不指定長度的話就是切片了
a := [3]int{1, 2, 3}
//b是數(shù)組肝箱,是a的一份深拷貝
b := a
//c是切片,是引用類型稀蟋,底層數(shù)組是a
c := a[:]
for i := 0; i < len(a); i++ {
 a[i] = a[i] + 1
}
//改變a的值后煌张,b是a的拷貝,b不變退客,c是引用唱矛,c的值改變
fmt.Println(a) 
//[2,3,4]
fmt.Println(b) 
//[1 2 3]
fmt.Println(c) 
//[2,3,4]
//a是一個(gè)切片,不指定長度的話就是切片了
a := []int{1, 2, 3}
//b是切片井辜,是a的一份拷貝
b := a
//c是切片绎谦,是引用類型
c := a[:]
for i := 0; i < len(a); i++ {
 a[i] = a[i] + 1
}
//改變a的值后,b是a的淺拷貝粥脚,b的值改派窃肠,c是引用,c的值改變
fmt.Println(a) 
//[2,3,4]
fmt.Println(b) 
//[2,3,4]
fmt.Println(c) 
//[2,3,4]

總結(jié)

  • 創(chuàng)建切片時(shí)可根據(jù)實(shí)際需要預(yù)分配容量刷允,盡量避免追加過程中進(jìn)行擴(kuò)容操作冤留,有利于提升性能
  • 使用 append() 向切片追加元素時(shí)有可能觸發(fā)擴(kuò)容碧囊,擴(kuò)容后將會生成新的切片
  • 使用 len()、cap()計(jì)算切片長度纤怒、容量時(shí)糯而,時(shí)間復(fù)雜度均為 O(1),不需要遍歷切片
  • 切片是非線程安全的泊窘,如果要實(shí)現(xiàn)線程安全熄驼,可以加鎖或者使用Channel
  • 大數(shù)組作為函數(shù)參數(shù)時(shí),會復(fù)制整個(gè)數(shù)組數(shù)據(jù)烘豹,消耗過多內(nèi)存瓜贾,建議使用切片或者指針
  • 切片作為函數(shù)參數(shù)時(shí),可以改變切片指向的數(shù)組携悯,不能改變切片本身len和cap祭芦;想要改變切片本身,可以將改變后的切片返回 或者 將切片指針作為函數(shù)參數(shù)憔鬼。
  • 如果只用到大slice的一小部分龟劲,建議將需要的分片復(fù)制到一個(gè)新的slice中去,減少內(nèi)存的占用

本文由博客一文多發(fā)平臺 OpenWrite 發(fā)布轴或!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末昌跌,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子侮叮,更是在濱河造成了極大的恐慌避矢,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件囊榜,死亡現(xiàn)場離奇詭異审胸,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)卸勺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進(jìn)店門砂沛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人曙求,你說我怎么就攤上這事碍庵。” “怎么了悟狱?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵静浴,是天一觀的道長。 經(jīng)常有香客問我挤渐,道長苹享,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任浴麻,我火速辦了婚禮得问,結(jié)果婚禮上囤攀,老公的妹妹穿的比我還像新娘。我一直安慰自己宫纬,他們只是感情好焚挠,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著漓骚,像睡著了一般蝌衔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上认境,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天胚委,我揣著相機(jī)與錄音挟鸠,去河邊找鬼叉信。 笑死,一個(gè)胖子當(dāng)著我的面吹牛艘希,可吹牛的內(nèi)容都是我干的硼身。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼覆享,長吁一口氣:“原來是場噩夢啊……” “哼佳遂!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起撒顿,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤丑罪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后凤壁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體吩屹,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年拧抖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了煤搜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,724評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡唧席,死狀恐怖擦盾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情淌哟,我是刑警寧澤迹卢,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站徒仓,受9級特大地震影響腐碱,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蓬衡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一喻杈、第九天 我趴在偏房一處隱蔽的房頂上張望彤枢。 院中可真熱鬧,春花似錦筒饰、人聲如沸缴啡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽业栅。三九已至,卻和暖如春谬晕,著一層夾襖步出監(jiān)牢的瞬間碘裕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工攒钳, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留帮孔,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓不撑,卻偏偏與公主長得像文兢,于是被迫代替她去往敵國和親焕檬。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評論 2 350

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