คุณจะล้างชิ้นส่วนใน Go ได้อย่างไร?


125

วิธีที่เหมาะสมในการล้างชิ้นส่วนใน Go คืออะไร?

นี่คือสิ่งที่ฉันพบในฟอรัม go :

// test.go
package main

import (
    "fmt"
)

func main() {
    letters := []string{"a", "b", "c", "d"}
    fmt.Println(cap(letters))
    fmt.Println(len(letters))
    // clear the slice
    letters = letters[:0]
    fmt.Println(cap(letters))
    fmt.Println(len(letters))
}

ถูกต้องหรือไม่

เพื่อความชัดเจนบัฟเฟอร์จะถูกล้างเพื่อให้สามารถใช้ซ้ำได้

ตัวอย่างคือฟังก์ชันBuffer.Truncateในแพ็คเกจไบต์

สังเกตว่ารีเซ็ตเพียงแค่เรียก Truncate (0) ดังนั้นดูเหมือนว่าในกรณีนี้บรรทัดที่ 70 จะประเมิน: b.buf = b.buf [0: 0]

http://golang.org/src/pkg/bytes/buffer.go

// Truncate discards all but the first n unread bytes from the buffer.
60  // It panics if n is negative or greater than the length of the buffer.
61  func (b *Buffer) Truncate(n int) {
62      b.lastRead = opInvalid
63      switch {
64      case n < 0 || n > b.Len():
65          panic("bytes.Buffer: truncation out of range")
66      case n == 0:
67          // Reuse buffer space.
68          b.off = 0
69      }
70      b.buf = b.buf[0 : b.off+n]
71  }
72  
73  // Reset resets the buffer so it has no content.
74  // b.Reset() is the same as b.Truncate(0).
75  func (b *Buffer) Reset() { b.Truncate(0) }

1
การทดสอบอย่างรวดเร็วบน: play.golang.org/p/6Z-qDQtpbgดูเหมือนว่าจะใช้งานได้ (จะไม่เปลี่ยนความจุ แต่จะตัดความยาวออก)
Jason Sperske

คำตอบ:


120

ทุกอย่างขึ้นอยู่กับคำจำกัดความของคำว่า "ชัดเจน" ของคุณ หนึ่งในสิ่งที่ถูกต้องคือ:

slice = slice[:0]

แต่มีการจับ หากองค์ประกอบชิ้นส่วนเป็นประเภท T:

var slice []T

จากนั้นบังคับlen(slice)ให้เป็นศูนย์โดย "เคล็ดลับ" ข้างต้น จะไม่ทำให้องค์ประกอบใด ๆ ของ

slice[:cap(slice)]

มีสิทธิ์ได้รับการเก็บขยะ นี่อาจเป็นแนวทางที่ดีที่สุดในบางสถานการณ์ แต่ก็อาจเป็นสาเหตุของ "หน่วยความจำรั่ว" เช่นกัน - หน่วยความจำไม่ได้ใช้ แต่สามารถเข้าถึงได้ (หลังจากหั่น "slice" อีกครั้ง) จึงไม่ใช่ขยะที่ "เก็บรวบรวมได้"


1
น่าสนใจ มีวิธีอื่นใดในการลบองค์ประกอบทั้งหมดออกจากอาร์เรย์พื้นฐานของชิ้นส่วนในขณะที่ไม่เปลี่ยนแปลงความจุพื้นฐาน
Chris Weber

3
@ChrisWeber: เพียงแค่วนซ้ำเหนืออาร์เรย์พื้นฐานและตั้งค่าองค์ประกอบทั้งหมดเป็นค่าใหม่
newacct

2
@jnml ฉันต้องการใช้ชิ้นส่วนซ้ำ (และที่เก็บอาร์เรย์พื้นฐาน) ดังนั้นฉันจึงไม่ได้จัดสรรสไลซ์ใหม่ (พร้อมอาร์เรย์) อย่างต่อเนื่อง ฉันได้แก้ไขคำถามของฉันเพื่อชี้แจงและเพื่อแสดงโค้ดตัวอย่างจากไลบรารีมาตรฐาน
Chris Weber

1
ฉันเพิ่งเริ่มใช้ Go โปรดอธิบายเพิ่มเติมว่าเหตุใดจึงเป็นแนวทางที่ดีที่สุดได้โปรด? ขอบคุณล่วงหน้า.
satoru

คุณแน่ใจหรือว่าการรีเซ็ตขนาดชิ้นส่วนทำให้หน่วยความจำรั่ว? ฉันไม่สามารถทำซ้ำได้
Tommaso Barbugli

197

การตั้งค่า slice nilเป็นวิธีที่ดีที่สุดในการล้าง slice nilชิ้นส่วนในไปนั้นทำงานได้ดีอย่างสมบูรณ์และการตั้งค่าให้ชิ้นส่วนnilนั้นปล่อยหน่วยความจำพื้นฐานไปยังตัวเก็บขยะ

ดูสนามเด็กเล่น

package main

import (
    "fmt"
)

func dump(letters []string) {
    fmt.Println("letters = ", letters)
    fmt.Println(cap(letters))
    fmt.Println(len(letters))
    for i := range letters {
        fmt.Println(i, letters[i])
    }
}

func main() {
    letters := []string{"a", "b", "c", "d"}
    dump(letters)
    // clear the slice
    letters = nil
    dump(letters)
    // add stuff back to it
    letters = append(letters, "e")
    dump(letters)
}

พิมพ์

letters =  [a b c d]
4
4
0 a
1 b
2 c
3 d
letters =  []
0
0
letters =  [e]
1
1
0 e

โปรดทราบว่าชิ้นส่วนสามารถใช้นามแฝงได้อย่างง่ายดายเพื่อให้สองส่วนชี้ไปที่หน่วยความจำพื้นฐานเดียวกัน การตั้งค่าเป็นnilจะลบนามแฝงนั้น

วิธีนี้เปลี่ยนความจุเป็นศูนย์แม้ว่า


นิคขอบคุณสำหรับการตอบสนอง โปรดดูการอัปเดตของฉันที่คุณต้องการ ฉันกำลังล้างชิ้นส่วนเพื่อนำมาใช้ใหม่ ดังนั้นฉันจึงไม่ต้องการให้หน่วยความจำพื้นฐานปล่อยไปยัง GC เพราะฉันจะต้องจัดสรรอีกครั้ง
Chris Weber

มันคือสิ่งที่ฉันค้นหา!)
Timur Fayzrakhmanov

5
ตามชื่อเรื่อง "How do you clear a slice in Go?" นี่เป็นคำตอบที่ปลอดภัยกว่าและไกลกว่าและควรเป็นคำตอบที่ยอมรับ คำตอบที่สมบูรณ์แบบคือการรวมกันของคำตอบที่ยอมรับในตอนแรกและคำตอบนี้เพื่อให้ผู้คนสามารถตัดสินใจด้วยตัวเองได้
Shadoninja

1
appending to a nilslice ได้ผลเสมอใน Go?
alediaferia

@alediaferia นับตั้งแต่ไป 1.0 อย่างแน่นอน
Nick Craig-Wood

4

ฉันกำลังตรวจสอบปัญหานี้เล็กน้อยเพื่อจุดประสงค์ของตัวเอง ฉันมีโครงสร้างส่วนหนึ่ง (รวมถึงตัวชี้บางตัว) และฉันต้องการให้แน่ใจว่าฉันทำถูกต้อง จบลงที่หัวข้อนี้และต้องการแบ่งปันผลลัพธ์ของฉัน

ในการฝึกฝนฉันได้ไปสนามเด็กเล่นเล็กน้อย: https://play.golang.org/p/9i4gPx3lnY

สิ่งใดที่ทำให้สิ่งนี้:

package main

import "fmt"

type Blah struct {
    babyKitten int
    kittenSays *string
}

func main() {
    meow := "meow"
    Blahs := []Blah{}
    fmt.Printf("Blahs: %v\n", Blahs)
    Blahs = append(Blahs, Blah{1, &meow})
    fmt.Printf("Blahs: %v\n", Blahs)
    Blahs = append(Blahs, Blah{2, &meow})
    fmt.Printf("Blahs: %v\n", Blahs)
    //fmt.Printf("kittenSays: %v\n", *Blahs[0].kittenSays)
    Blahs = nil
    meow2 := "nyan"
    fmt.Printf("Blahs: %v\n", Blahs)
    Blahs = append(Blahs, Blah{1, &meow2})
    fmt.Printf("Blahs: %v\n", Blahs)
    fmt.Printf("kittenSays: %v\n", *Blahs[0].kittenSays)
}

การเรียกใช้รหัสนั้นตามสภาพจะแสดงที่อยู่หน่วยความจำเดียวกันสำหรับทั้งตัวแปร "meow" และ "meow2" ว่าเหมือนกัน:

Blahs: []
Blahs: [{1 0x1030e0c0}]
Blahs: [{1 0x1030e0c0} {2 0x1030e0c0}]
Blahs: []
Blahs: [{1 0x1030e0f0}]
kittenSays: nyan

ซึ่งฉันคิดว่าเป็นการยืนยันว่าโครงสร้างเป็นขยะที่เก็บรวบรวม ผิดปกติพอโดยไม่ใส่เครื่องหมายแสดงความคิดเห็นในบรรทัดการพิมพ์ที่แสดงความคิดเห็นจะให้ที่อยู่หน่วยความจำที่แตกต่างกันสำหรับ meows:

Blahs: []
Blahs: [{1 0x1030e0c0}]
Blahs: [{1 0x1030e0c0} {2 0x1030e0c0}]
kittenSays: meow
Blahs: []
Blahs: [{1 0x1030e0f8}]
kittenSays: nyan

ฉันคิดว่านี่อาจเป็นเพราะการพิมพ์ถูกเลื่อนออกไปในทางใดทางหนึ่ง (?) แต่ภาพประกอบที่น่าสนใจเกี่ยวกับพฤติกรรมการควบคุมหน่วยความจำบางอย่างและอีกหนึ่งคะแนนสำหรับ:

[]MyStruct = nil

ตัวอย่างโดยละเอียดที่ดี ขอบคุณ!
Dolanor

2
สิ่งนี้ไม่ได้แสดงที่อยู่หน่วยความจำของ meo1 และ meow2 ที่เหมือนกัน: 0x1030e0c0ไม่เท่ากับ0x1030e0f0(เดิมลงท้ายc0ด้วยหลังในf0)
carbocation

ต้องเห็นด้วยกับ @carbocation ที่นี่ที่อยู่หน่วยความจำเหล่านั้นไม่เหมือนกัน ฉันไม่ได้อ้างว่าสามารถอธิบายได้ดีขึ้นว่าเกิดอะไรขึ้นที่นี่ แต่นี่ไม่ได้ใช้เป็นหลักฐานสำหรับฉัน ฉันเห็นความคลาดเคลื่อน 8 ไบต์เดียวกันในที่อยู่ของการmeow2รันแต่ละครั้ง ...
rbrtl
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.