Có an toàn để loại bỏ các phím được chọn khỏi bản đồ trong một vòng lặp phạm vi không?


135

Làm cách nào để xóa các phím đã chọn khỏi bản đồ? Có an toàn để kết hợp delete()với phạm vi, như trong mã dưới đây?

package main

import "fmt"

type Info struct {
    value string
}

func main() {
    table := make(map[string]*Info)

    for i := 0; i < 10; i++ {
        str := fmt.Sprintf("%v", i)
        table[str] = &Info{str}
    }

    for key, value := range table {
        fmt.Printf("deleting %v=>%v\n", key, value.value)
        delete(table, key)
    }
}

https://play.golang.org/p/u1vufvEjSw

Câu trả lời:


174

Điều này là an toàn! Bạn cũng có thể tìm thấy một mẫu tương tự trong Go hiệu quả :

for key := range m {
    if key.expired() {
        delete(m, key)
    }
}

đặc tả ngôn ngữ :

Thứ tự lặp trên bản đồ không được chỉ định và không được đảm bảo giống nhau từ lần lặp này sang lần lặp tiếp theo. Nếu các mục bản đồ chưa đạt được bị loại bỏ trong quá trình lặp , các giá trị lặp tương ứng sẽ không được tạo ra. Nếu các mục bản đồ được tạo trong quá trình lặp , mục đó có thể được tạo trong quá trình lặp hoặc có thể bị bỏ qua. Sự lựa chọn có thể khác nhau cho mỗi mục được tạo và từ lần lặp này sang lần lặp tiếp theo. Nếu bản đồ là 0, số lần lặp là 0.


key.recired không xác định (chuỗi kiểu không có trường hoặc phương thức hết hạn)

4
@kristen - trong ví dụ được mô tả ở trên, khóa không phải là một chuỗi mà là một số loại tùy chỉnh thực hiện func (a T) expired() boolgiao diện. Đối với mục đích của ví dụ này, bạn có thể thử: m := make(map[int]int) /* populate m here somehow */ for key := range (m) { if key % 2 == 0 { /* this is just some condition, such as calling expired */ delete(m, key); } }
abanana

Rất bối rối.
g10guang

150

Câu trả lời của Sebastian là chính xác, nhưng tôi muốn biết tại sao nó an toàn, vì vậy tôi đã đào sâu vào mã nguồn Map . Có vẻ như trong một cuộc gọi đến delete(k, v), về cơ bản nó chỉ đặt một cờ (cũng như thay đổi giá trị đếm) thay vì thực sự xóa giá trị:

b->tophash[i] = Empty;

(Trống là hằng số cho giá trị 0)

Những gì bản đồ dường như thực sự đang làm là phân bổ một số nhóm đã đặt tùy thuộc vào kích thước của bản đồ, sẽ tăng lên khi bạn thực hiện chèn theo tỷ lệ 2^B(từ mã nguồn này ):

byte    *buckets;     // array of 2^B Buckets. may be nil if count==0.

Vì vậy, hầu như luôn có nhiều thùng được phân bổ hơn so với bạn đang sử dụng và khi bạn thực hiện rangetrên bản đồ, nó sẽ kiểm tra tophashgiá trị của từng nhóm trong đó 2^Bđể xem liệu nó có thể bỏ qua không.

Tóm lại, deletebên trong a rangelà an toàn vì dữ liệu vẫn còn ở đó, nhưng khi kiểm tra tophashnó thấy rằng nó chỉ có thể bỏ qua nó và không bao gồm nó trong bất kỳ rangehoạt động nào bạn đang thực hiện. Mã nguồn thậm chí bao gồm TODO:

 // TODO: consolidate buckets if they are mostly empty
 // can only consolidate if there are no live iterators at this size.

Điều này giải thích tại sao việc sử dụng delete(k,v)chức năng không thực sự giải phóng bộ nhớ, chỉ cần xóa nó khỏi danh sách các nhóm bạn được phép truy cập. Nếu bạn muốn giải phóng bộ nhớ thực, bạn sẽ cần làm cho toàn bộ bản đồ không thể truy cập được để bộ sưu tập rác sẽ bước vào. Bạn có thể thực hiện việc này bằng cách sử dụng một dòng như

map = nil

2
Vì vậy, có vẻ như bạn đang nói rằng an toàn để xóa bất kỳ giá trị tùy ý khỏi bản đồ, không chỉ là 'hiện tại', đúng không? Và khi đến lúc đánh giá một hàm băm mà trước đây tôi đã tự ý xóa, nó sẽ bỏ qua nó một cách an toàn?
Flimzy

@Flimzy Điều đó là chính xác, như bạn có thể thấy từ sân chơi này play.golang.org/p/FwbsghzrsO . Lưu ý rằng nếu chỉ mục bạn xóa là chỉ mục đầu tiên trong phạm vi, nó sẽ vẫn hiển thị chỉ mục đó vì nó đã được ghi thành k, v nhưng nếu bạn đặt chỉ mục thành bất kỳ ngoài chỉ mục đầu tiên mà phạm vi tìm thấy, nó sẽ chỉ hiển thị hai khóa / cặp giá trị thay vì ba và không hoảng loạn.
V Địa

1
Có phải "không thực sự giải phóng bộ nhớ" vẫn còn liên quan? Tôi đã cố gắng tìm trong nguồn bình luận nhưng không thể tìm thấy nó.
Tony

11
Lưu ý quan trọng: hãy nhớ rằng đây chỉ là triển khai hiện tại và nó có thể thay đổi trong tương lai, vì vậy bạn không được dựa vào bất kỳ thuộc tính bổ sung nào mà nó có thể xuất hiện để "hỗ trợ". Các đảm bảo duy nhất bạn có là những thứ được cung cấp bởi đặc tả, như được mô tả trong câu trả lời của Sebastian . (Điều đó nói rằng, khám phá và giải thích nội bộ Go chắc chắn rất thú vị, có tính giáo dục và nói chung là tuyệt vời!)
akavel

4

Tôi đã tự hỏi nếu một rò rỉ bộ nhớ có thể xảy ra. Vì vậy, tôi đã viết một chương trình thử nghiệm:

package main

import (
    log "github.com/Sirupsen/logrus"
    "os/signal"
    "os"
    "math/rand"
    "time"
)

func main() {
    log.Info("=== START ===")
    defer func() { log.Info("=== DONE ===") }()

    go func() {
        m := make(map[string]string)
        for {
            k := GenerateRandStr(1024)
            m[k] = GenerateRandStr(1024*1024)

            for k2, _ := range m {
                delete(m, k2)
                break
            }
        }
    }()

    osSignals := make(chan os.Signal, 1)
    signal.Notify(osSignals, os.Interrupt)
    for {
        select {
        case <-osSignals:
            log.Info("Recieved ^C command. Exit")
            return
        }
    }
}

func GenerateRandStr(n int) string {
    rand.Seed(time.Now().UnixNano())
    const letterBytes = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    b := make([]byte, n)
    for i := range b {
        b[i] = letterBytes[rand.Int63() % int64(len(letterBytes))]
    }
    return string(b)
}

Có vẻ như GC làm giải phóng bộ nhớ. Vậy là ổn rồi.


0

Tóm lại, vâng. Xem câu trả lời trước.

Và cũng thế này, từ đây :

ianlancetaylor đã nhận xét vào ngày 18 tháng 2 năm 2015
Tôi nghĩ rằng chìa khóa để hiểu điều này là nhận ra rằng trong khi thực hiện phần thân của một câu lệnh for / phạm vi, không có sự lặp lại hiện tại. Có một tập hợp các giá trị đã được nhìn thấy và một tập hợp các giá trị chưa được nhìn thấy. Trong khi thực thi phần thân, một trong các cặp khóa / giá trị đã được nhìn thấy - cặp gần đây nhất - đã được gán cho (các) biến của câu lệnh phạm vi. Không có gì đặc biệt về cặp khóa / giá trị đó, nó chỉ là một trong những cặp đã được nhìn thấy trong quá trình lặp.

Câu hỏi anh ấy trả lời là về việc sửa đổi các yếu tố bản đồ tại chỗ trong một rangehoạt động, đó là lý do tại sao anh ấy đề cập đến "phép lặp hiện tại". Nhưng nó cũng có liên quan ở đây: bạn có thể xóa các phím trong một phạm vi và điều đó chỉ có nghĩa là bạn sẽ không thấy chúng sau này trong phạm vi (và nếu bạn đã nhìn thấy chúng, điều đó không sao).

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.