搜尋
首頁後端開發Golang進行切片和子鏈條:了解共享內存並避免```append''

Go Slices and Subslices: Understanding Shared Memory and Avoiding `append()` Pitfalls

深入理解Go語言切片:共享內存與append()陷阱

大家好!歡迎回到我的博客。 ?如果您在這裡,您可能剛接觸Golang,或者您是經驗豐富的開發者,想深入了解切片的內部工作原理。那麼,讓我們開始吧!

Go語言因其簡潔性和高效性而備受讚譽——正如人們常說的那樣,“Go語言就是能完成工作”。對於我們這些來自C、C 或Java等語言的開發者來說,Go語言簡潔明了的語法和易用性令人耳目一新。然而,即使在Go語言中,某些特性也可能讓開發者感到困惑,尤其是在處理切片和子切片時。讓我們揭開這些細微之處,更好地理解如何避免append()和切片共享內存的常見陷阱。

Go語言中的切片是什麼?

通常,當您需要一種數據結構來存儲一系列值時,切片是Go語言中的首選。它們的靈活性來自於這樣一個事實:它們的長度不是其類型的一部分。此特性克服了數組的限制,使我們能夠創建一個可以處理任何大小切片的單個函數,並使切片能夠根據需要增長或擴展。

雖然切片與數組有一些相似之處,例如都是可索引的並且具有長度,但它們在數據管理方式上有所不同。切片充當對底層數組的引用,該數組實際上存儲切片的數據。從本質上講,切片提供對該數組的某些或所有元素的視圖。因此,當您創建一個切片時,Go會自動處理創建保存切片元素/數據的底層數組。

切片的共享內存

數組是連續的內存塊,但使切片有趣的是它們如何引用此內存。讓我們分解切片的結構:

type slice struct {
    array unsafe.Pointer // 指向底层数组的指针
    len   int           // 切片中的元素数量
    cap   int           // 底层数组的容量
}

當您創建一個切片時,它包含三個組件:

  1. 指向底層數組的指針
  2. len 切片的長度(它包含的元素數量)
  3. cap 容量(在需要增長之前它可以包含的元素數量)

這就是事情變得有趣的地方。如果您有多個派生自同一數組的切片,則通過一個切片進行的更改將體現在其他切片中,因為它們共享相同的底層數組。

讓我們看下面的例子:

package main

import "fmt"

func main() {
    // 创建一个具有初始值的切片
    original := []int{1, 2, 3, 4, 5}

    // 创建一个子切片——两个切片共享相同的底层数组!
    subslice := original[1:3]

    fmt.Println("未修改的子切片:", subslice)  // 输出 => 未修改的子切片: [2 3]

    // 修改子切片
    subslice[0] = 42

    fmt.Println("原始切片:", original) // 输出 => 原始切片: [1 42 3 4 5]
    fmt.Println("修改后的子切片:", subslice)  // 输出 => 修改后的子切片: [42 3]
}

理解切片容量

在我們進一步深入之前,讓我們嘗試理解切片容量cap()。當您從現有的Go語言切片中獲取子切片時,新子切片的容量由原始切片從子切片開始位置的剩餘容量決定。讓我們稍微分解一下:

當您從數組創建切片時,切片的長度是它最初包含的元素數量,而它的容量是它在需要增長之前可以包含的元素總數。

獲取子切片

當您從現有切片中獲取子切片時:

  • 子切片的長度是您指定的元素數量。
  • 容量計算為原始切片的容量減去子切片的起始索引。

讓我們看一個詳細的例子:

type slice struct {
    array unsafe.Pointer // 指向底层数组的指针
    len   int           // 切片中的元素数量
    cap   int           // 底层数组的容量
}
  • 原始切片有 5 個元素,長度和容量均為 5。
  • 當您使用 subslice := original[1:4] 時,它指向從索引 1 到 3 的元素 (2, 3, 4)。
  • subslice長度是 4 - 1 = 3。
  • subslice容量是 5 - 1 = 4,因為它從索引 1 開始,並包含到原始切片末尾的元素。

append()陷阱!

這就是開發者經常被困住的地方。 Go語言中的append()函數在處理子切片時可能會導致意外行為。

未使用的容量共享

子切片的容量包括不屬於其長度但位於原始切片容量範圍內的元素。這意味著如果子切片增長,它可以訪問或修改這些元素。

讓我們考慮這個例子:

package main

import "fmt"

func main() {
    // 创建一个具有初始值的切片
    original := []int{1, 2, 3, 4, 5}

    // 创建一个子切片——两个切片共享相同的底层数组!
    subslice := original[1:3]

    fmt.Println("未修改的子切片:", subslice)  // 输出 => 未修改的子切片: [2 3]

    // 修改子切片
    subslice[0] = 42

    fmt.Println("原始切片:", original) // 输出 => 原始切片: [1 42 3 4 5]
    fmt.Println("修改后的子切片:", subslice)  // 输出 => 修改后的子切片: [42 3]
}
  • subslice 最初指向 2, 3,容量為 4(它可以增長到原始切片的末尾)。
  • 當您向subslice追加 60, 70 時,它使用原始切片的剩餘容量。
  • originalsubslice都反映了這些更改,因為它們共享相同的底層數組。

驚訝嗎? append()操作修改了原始切片,因為底層數組中有足夠的容量。但是,如果我們超過容量或追加的元素超過容量允許的範圍,Go將為子切片分配一個新的數組,從而打破與原始切片的共享:

func main() {
    // 原始切片
    original := []int{1, 2, 3, 4, 5}

    // 创建一个子切片
    subslice := original[1:4] // 指向元素 2, 3, 4

    fmt.Println("子切片:", subslice)    // 输出 => 子切片: [2 3 4]
    fmt.Println("子切片的长度:", len(subslice)) // 输出 => 子切片的长度: 3
    fmt.Println("子切片的容量:", cap(subslice)) // 输出 => 子切片的容量: 4
}

在這種情況下,append()創建了一個新的底層數組,因為原始容量已超過。

避免陷阱的最佳實踐

  • 明確容量
func main() {
    original := []int{1, 2, 3, 4, 5}
    subslice := original[1:3] // 指向元素 2, 3

    fmt.Println("追加前原始切片:", original) // 输出 => [1 2 3 4 5]
    fmt.Println("追加前子切片:", subslice)       // 输出 => [2 3]
    fmt.Println("子切片的容量:", cap(subslice))    // 输出 => 4

    // 在容量范围内追加到子切片
    subslice = append(subslice, 60, 70)

    // 追加到子切片后打印
    fmt.Println("追加后原始切片:", original)  // 输出 => [1 2 3 60 70]
    fmt.Println("追加后子切片:", subslice)        // 输出 => [2 3 60 70]
}

主要優點是:

i. make([]int, len(subslice)) 創建一個具有其自身獨立底層數組的新切片。這至關重要——它不僅僅是一個新的切片頭,而是在內存中一個全新的數組。

ii. copy() 然後只傳輸值,而不是內存引用。這就像複印一份文件而不是共享原始文件。

  • 使用完整的切片表達式
func main() {
    original := []int{1, 2, 3, 4, 5}
    subslice := original[1:3] // 指向元素 2, 3

    // 追加超出子切片容量的元素
    subslice = append(subslice, 60, 70, 80)

    fmt.Println("大容量追加后原始切片:", original) // 输出 => [1 2 3 4 5]
    fmt.Println("大容量追加后子切片:", subslice)       // 输出 => [2 3 60 70 80]
}
  • 在將切片傳遞給不應修改原始數據的函數時,考慮不變性
// 假设我们从这里开始
original := []int{1, 2, 3, 4, 5}
subslice := original[1:3]  // subslice 指向 original 的底层数组

// 这是我们的解决方案:
newSlice := make([]int, len(subslice))  // 步骤 1:创建新的底层数组
copy(newSlice, subslice)               // 步骤 2:复制值

主要優點是:

i. 數據保護:原始數據保持不變,防止意外副作用

ii. 可預測的行為:函數對輸入沒有隱藏的影響

iii. 並發安全:在處理過程中可以在其他 goroutine 中安全地使用原始數據

記住:

  • 切片是對底層數組的引用
  • 子切片與父切片共享內存
  • append() 是否創建新的底層數組取決於容量
  • 當向具有可用容量的子切片追加元素時,它會修改父切片的數據。
  • 當您想要避免共享時,請使用顯式內存管理
  • 當處理子切片時,請執行以下任一操作:
type slice struct {
    array unsafe.Pointer // 指向底层数组的指针
    len   int           // 切片中的元素数量
    cap   int           // 底层数组的容量
}

祝您編碼愉快。記住,能力越大,責任越大,尤其是在共享內存方面! ?


恭喜您閱讀完本文。

您覺得這篇資源有幫助嗎?您有問題嗎?或者您發現了錯誤或錯別字?請在評論中留下您的反饋。

不要忘記與可能從中受益的其他人分享此資源。關注我以獲取更多信息。

以上是進行切片和子鏈條:了解共享內存並避免```append''的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
測試代碼依賴於INET功能的代碼測試代碼依賴於INET功能的代碼May 03, 2025 am 12:20 AM

whentestinggocodewithinitfunctions,useexplicitseTupfunctionsorseParateTestFileSteSteTepteTementDippedDependendendencyOnInItfunctionsIdeFunctionSideFunctionsEffect.1)useexplicitsetupfunctionStocontrolglobalvaribalization.2)createSepEpontrolglobalvarialization

將GO的錯誤處理方法與其他語言進行比較將GO的錯誤處理方法與其他語言進行比較May 03, 2025 am 12:20 AM

go'serrorhandlingurturnserrorsasvalues,與Javaandpythonwhichuseexceptions.1)go'smethodensursexplitirorhanderling,propertingrobustcodebutincreasingverbosity.2)

設計有效界面的最佳實踐設計有效界面的最佳實踐May 03, 2025 am 12:18 AM

AnefactiveInterfaceingoisminimal,clear and promotesloosecoupling.1)minimizeTheInterfaceForflexibility andeaseofimplementation.2)useInterInterfaceForabStractionToswaPimplementations withoutchangingCallingCode.3)

集中式錯誤處理策略集中式錯誤處理策略May 03, 2025 am 12:17 AM

集中式錯誤處理在Go語言中可以提升代碼的可讀性和可維護性。其實現方式和優勢包括:1.將錯誤處理邏輯從業務邏輯中分離,簡化代碼。 2.通過集中處理錯誤,確保錯誤處理的一致性。 3.使用defer和recover來捕獲和處理panic,增強程序健壯性。

init in Init函數的替代方案,用於go中的包裝初始化init in Init函數的替代方案,用於go中的包裝初始化May 03, 2025 am 12:17 AM

Ingo,替代詞InivestoIniTfunctionsIncludeCustomInitializationfunctionsandsingletons.1)customInitializationfunctions hownerexpliticpliticpliticconconconconconconconconconconconconconconconconconconconconconconconconconconconconconconconconconconconconconconconconconconconconconconconconconconconconcontirization curssetupssetupssetups.2)單次固定無元素限制ininconconcurrent

與GO接口鍵入斷言和類型開關與GO接口鍵入斷言和類型開關May 02, 2025 am 12:20 AM

Gohandlesinterfacesandtypeassertionseffectively,enhancingcodeflexibilityandrobustness.1)Typeassertionsallowruntimetypechecking,asseenwiththeShapeinterfaceandCircletype.2)Typeswitcheshandlemultipletypesefficiently,usefulforvariousshapesimplementingthe

使用errors.is和錯誤。使用errors.is和錯誤。May 02, 2025 am 12:11 AM

Go語言的錯誤處理通過errors.Is和errors.As函數變得更加靈活和可讀。 1.errors.Is用於檢查錯誤是否與指定錯誤相同,適用於錯誤鏈的處理。 2.errors.As不僅能檢查錯誤類型,還能將錯誤轉換為具體類型,方便提取錯誤信息。使用這些函數可以簡化錯誤處理邏輯,但需注意錯誤鏈的正確傳遞和避免過度依賴以防代碼複雜化。

在GO中進行性能調整:優化您的應用程序在GO中進行性能調整:優化您的應用程序May 02, 2025 am 12:06 AM

tomakegoapplicationsRunfasterandMorefly,useProflingTools,leverageConCurrency,andManageMoryfectily.1)usepprofforcpuorforcpuandmemoryproflingtoidentifybottlenecks.2)upitizegorizegoroutizegoroutinesandchannelstoparalletaparelalyizetasksandimproverperformance.3)

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

SublimeText3 英文版

SublimeText3 英文版

推薦:為Win版本,支援程式碼提示!

Safe Exam Browser

Safe Exam Browser

Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器

VSCode Windows 64位元 下載

VSCode Windows 64位元 下載

微軟推出的免費、功能強大的一款IDE編輯器