Làm thế nào để bạn xóa một lát trong Go?


124

Cách thích hợp để xóa một lát trong Go là gì?

Đây là những gì tôi đã tìm thấy trong các diễn đàn đi :

// 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))
}

Điều này có đúng không?

Để làm rõ, bộ đệm được xóa để nó có thể được sử dụng lại.

Một ví dụ là hàm Buffer.Truncate trong gói byte.

Lưu ý rằng Đặt lại chỉ gọi Truncate (0). Vì vậy, trong trường hợp này, dòng 70 sẽ đánh giá: 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
Một thử nghiệm nhanh trên: play.golang.org/p/6Z-qDQtpbg dường như đề xuất rằng nó sẽ hoạt động (sẽ không thay đổi công suất nhưng nó sẽ cắt ngắn độ dài)
Jason Sperske

Câu trả lời:


119

Tất cả phụ thuộc vào định nghĩa của bạn về 'rõ ràng'. Một trong những cái hợp lệ chắc chắn là:

slice = slice[:0]

Nhưng có một nhược điểm. Nếu các phần tử lát có kiểu T:

var slice []T

sau đó thực thi len(slice)bằng 0, bằng "mẹo" ở trên, không tạo ra bất kỳ yếu tố nào của

slice[:cap(slice)]

đủ điều kiện để thu gom rác. Đây có thể là cách tiếp cận tối ưu trong một số tình huống. Nhưng nó cũng có thể là một nguyên nhân của "rò rỉ bộ nhớ" - bộ nhớ không được sử dụng, nhưng có khả năng truy cập được (sau khi cắt lại 'lát') và do đó không phải là rác "có thể thu thập".


1
Hấp dẫn. Có cách nào khác để loại bỏ tất cả các phần tử khỏi mảng bên dưới của lát cắt trong khi vẫn giữ nguyên dung lượng cơ bản không?
Chris Weber

3
@ChrisWeber: chỉ cần lặp lại mảng bên dưới và đặt tất cả các thành phần thành một giá trị mới
newacct

2
@jnml, tôi muốn sử dụng lại lát cắt (và bộ lưu trữ mảng bên dưới) vì vậy tôi không liên tục phân bổ một lát cắt mới (có mảng). Tôi đã chỉnh sửa câu hỏi của mình để làm rõ và hiển thị một số mã ví dụ từ thư viện chuẩn.
Chris Weber

1
Tôi mới đi. Bạn có thể vui lòng giải thích thêm về lý do tại sao điều này có thể là một cách tiếp cận tối ưu xin vui lòng? Cảm ơn trước.
satoru

Bạn có chắc chắn rằng thiết lập lại kích thước lát gây rò rỉ bộ nhớ? Tôi không thể sao chép nó
Tommaso Barbugli

196

Đặt lát cắt nillà cách tốt nhất để xóa một lát. nilcác lát cắt được xử lý hoàn hảo và thiết lập lát cắt nilsẽ giải phóng bộ nhớ bên dưới cho trình thu gom rác.

Xem sân chơi

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)
}

Bản in

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

Lưu ý rằng các lát có thể dễ dàng được đặt bí danh để hai lát chỉ vào cùng một bộ nhớ bên dưới. Các thiết lập nilsẽ loại bỏ răng cưa đó.

Phương pháp này thay đổi công suất về 0 mặc dù.


Nick cảm ơn phản hồi. Xin vui lòng xem cập nhật của tôi, bạn sẽ. Tôi đang dọn dẹp lát cắt để tái sử dụng. Vì vậy, tôi không nhất thiết muốn bộ nhớ cơ bản được phát hành cho GC vì tôi sẽ phải phân bổ lại.
Chris Weber

đó là những gì tôi đã tìm kiếm!)
Timur Fayzrakhmanov 19/12/14

5
Dựa trên tiêu đề "Làm thế nào để bạn xóa một lát trong Go?" đây là câu trả lời an toàn hơn và nên được chấp nhận. Một câu trả lời hoàn hảo sẽ là sự kết hợp của câu trả lời được chấp nhận ban đầu và câu trả lời này để mọi người có thể tự quyết định.
Shadoninja

1
appending đến một nillát luôn luôn làm việc trong Go?
alediaferia

@alediaferia kể từ khi đi 1.0 chắc chắn.
Nick Craig-Wood

4

Tôi đã xem xét vấn đề này một chút cho mục đích của riêng tôi; Tôi đã có một lát cấu trúc (bao gồm một số gợi ý) và tôi muốn chắc chắn rằng tôi đã hiểu đúng; đã kết thúc chủ đề này, và muốn chia sẻ kết quả của tôi.

Để luyện tập, tôi đã thực hiện một sân chơi nhỏ: https://play.golang.org/p/9i4gPx3lnY

mà tránh điều này:

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)
}

Chạy mã như hiện tại sẽ hiển thị cùng một địa chỉ bộ nhớ cho cả hai biến "meow" và "meow2" giống nhau:

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

mà tôi nghĩ xác nhận rằng cấu trúc là rác được thu thập. Thật kỳ lạ, không chú ý đến dòng in nhận xét, sẽ mang lại các địa chỉ bộ nhớ khác nhau cho các meo:

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

Tôi nghĩ rằng điều này có thể là do bản in bị trì hoãn theo một cách nào đó (?), Nhưng minh họa thú vị về một số hành vi mgmt bộ nhớ và thêm một phiếu bầu cho:

[]MyStruct = nil

Ví dụ chi tiết đẹp. Cảm ơn!
Dolanor

2
Điều này không hiển thị các địa chỉ bộ nhớ của meo1 và meow2 giống nhau: 0x1030e0c0không bằng 0x1030e0f0(kết thúc trước c0, cuối sau f0).
carbocation

Phải đồng ý với @carbocation ở đây, những địa chỉ bộ nhớ đó không giống nhau. Tôi không khẳng định có thể giải thích rõ hơn những gì đang diễn ra ở đây, nhưng điều này không đóng vai trò là bằng chứng cho tôi. Tôi thấy sự khác biệt 8 byte tương tự trong các địa chỉ của meow2mỗi lần chạy ...
rbrtl
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.