golang 切片記憶體應用技巧

KevinYan發表於2019-12-12

在 Go 語言中切片是使用非常頻繁的一種聚合型別,它代表變長的序列,底層引用一個陣列物件。一個切片由三個部分構成:指標、長度和容量。指標指向該切片自己第一個元素對應的底層陣列元素的記憶體地址。

切片的型別宣告如下:

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

多個切片之間可以共享底層陣列的資料,並且引用的陣列區間可能重疊。利用切片 的這個特性我們可以在原有記憶體空間中對切片進行反轉、篩選和去重等操作,這樣就不用宣告一個指向新記憶體的切片來儲存結果,從而節省了記憶體空間以及擴充套件底層陣列的消耗,這在切片長度足夠大時效果就會非常顯著。

下面這些例子都是在切片底層陣列的記憶體空間上進行的操作,需要注意的是這些操作在底層陣列上生成新切片的同時也會更改底層陣列。

刪除指定位置的元素

下面的函式從原切片中刪除索引位置i上的元素

func remove(slice []int, i int) []int {
    copy(slice[i:], slice[i+1:])
    return slice[:len(slice)-1]
}

func main() {
    s := []int{5, 6, 7, 8, 9}
    fmt.Println(remove(s, 2)) // "[5 6 8 9]"
}

內建的copy函式可以方便地將一個切片複製另一個相同型別的切片上。

篩選元素

下面的函式從輸入的源切片中篩選出滿足條件的切片元素,返回一個滿足條件的元素組成的新切片。

type funcType func(T) bool //代表篩選邏輯函式,可以按需實現

func filter(a []T, f funcType) []T {
    b := a[:0]
    for _, x := range a {
        if f(x) { 
            b = append(b, x)
        }
    }
    return b
}

反轉切片

func reverse(a []T) []T {
    for i := len(a)/2-1; i >= 0; i-- {
        opp := len(a)-1-i
        a[i], a[opp] = a[opp], a[i]
    }

    return a
}

分組切片

下面的函式接收一個[]int 型別的源切片actions, 返回一個按指定長度分組的巢狀切片(解釋起來好難,用過PHP 的同學可以理解為 Go 版本的array_chunk 函式,沒用過的看下面例子)。假設切面值為:[]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},設定分組中元素長度batchSize為3,函式呼叫後返回的分組後的切片為[[0 1 2] [3 4 5] [6 7 8] [9]]

func chunk(actions []int, batchSize int) []int {
    var batches [][]int

    for batchSize < len(actions) {
        actions, batches = actions[batchSize:], append(batches, actions[0:batchSize:batchSize])
    }
    batches = append(batches, actions)

    return batches
}

func main() {
    actions := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    batchSize := 3
    chunks = chunk(actions, batchSize)
    //chunks 為[[0 1 2] [3 4 5] [6 7 8] [9]]
}

這裡順便說一下,完整的切片表示式形式如下:

input[low:high:max]

最後一個 max 的作用是,生成的切片的cap(容量)為max - low

原地去重(只針對可比較的切片型別)

import "sort"

func main() {
    in := []int{3,2,1,4,3,2,1,4,1} // any item can be sorted
    sort.Ints(in)
    j := 0
    for i := 1; i < len(in); i++ {
        if in[j] == in[i] {
            continue
        }
        j++

        in[j] = in[i]
    }
    result := in[:j+1]
    fmt.Println(result) // [1 2 3 4] 
}

文章中部分例子來自golang 官方的 GitHub 的 wiki ,在這個 wiki 裡介紹了很多的切片使用技巧,瞭解更多可以訪問golang 的 GitHub Wiki https://github.com/golang/go/wiki/SliceTricks#filtering-without-allocatinggolang切片記憶體應用技巧

本作品採用《CC 協議》,轉載必須註明作者和本文連結

公眾號:網管叨bi叨 | Golang、PHP、Laravel、Docker等學習經驗分享

相關文章