範囲ループ内でマップから選択したキーを削除しても安全ですか?


135

選択したキーをマップから削除するにはどうすればよいですか?delete()以下のコードのように、範囲と組み合わせても安全ですか?

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

回答:


174

これは安全です!また、Effective Goにも同様のサンプルがあります。

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

そして言語仕様

マップの反復順序は指定されておらず、反復ごとに同じであることは保証されていません。まだ到達していないマップエントリが反復中に削除された場合、対応する反復値は生成されません。イテレーション中にマップエントリが作成された場合、そのエントリはイテレーション中に生成されるか、スキップされます。選択は、作成されたエントリごとに、また反復ごとに異なる場合があります。マップがnilの場合、反復回数は0です。


key.expired undefined(type stringにはフィールドがないか、メソッドの期限が切れています)

4
@kristen-上記の例では、キーは文字列ではなく、func (a T) expired() boolインターフェースを実装するカスタムタイプである必要があります。この例の目的のために、あなたが試みることができる: 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

とても紛らわしい。
g10guang

150

セバスチャンの答えは正確ですが、なぜそれが安全であるの知りたいので、マップのソースコードを掘り下げました。の呼び出しのように見えますがdelete(k, v)、実際に値を削除するのではなく、基本的にフラグを設定するだけでなく(カウント値を変更する):

b->tophash[i] = Empty;

(空は値の定数です0

マップが実際に実行しているように見えるのは、マップのサイズに応じて設定された数のバケットを割り当てることです。これは、2^Bこのソースコードから)の割合で挿入を実行すると増加します。

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

したがって、割り当てられているバケットの数は、使用している数よりも多いことがほとんどです。rangeマップ上でaを実行すると、その中tophashの各バケットの値をチェックして、2^Bスキップできるかどうかを確認します。

要約deleteするrangeと、データは技術的にはまだ残っているので、内は安全ですが、チェックするtophashと、スキップするだけで、range実行中の操作に含めることができないことがわかります。ソースコードには以下も含まれますTODO

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

これは、delete(k,v)関数を使用しても実際にはメモリが解放されない理由を説明しています。アクセスを許可されているバケットのリストからそれを削除するだけです。実際のメモリを解放したい場合は、ガベージコレクションが実行されるように、マップ全体を到達不能にする必要があります。これは、次のような行を使用して実行できます。

map = nil

2
つまり、「現在の」値だけでなく、マップから任意の値を削除しても安全だと言っているようですね。そして、私が以前に任意に削除したハッシュを評価するときになると、それは安全にそれをスキップしますか?
Flimzy、2015

@Flimzy正解です。この遊び場play.golang.org/p/FwbsghzrsOから確認できます。削除するインデックスが範囲内の最初のインデックスである場合、k、vに既に書き込まれているため、インデックスが表示されますが、インデックスを最初のインデックス以外に設定すると、範囲で2つのキーしか表示されないことに注意してください。パニックではなく、3つではなく/ valueのペア。
Verran

1
「実際にメモリを解放していない」というのはまだ関係がありますか?そのコメントをソースで見つけようとしましたが、見つかりません。
Tony

11
重要な注意:これは現在の実装にすぎず、将来変更される可能性があるため、「サポート」されているように見える追加のプロパティに依存しないでください。あなたが持っている唯一の保証は、仕様によって提供されるものですセバスチャンの答えで説明したように(とは
いえ

4

メモリリークが発生するのではないかと思っていました。だから私はテストプログラムを書いた:

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

GCはメモリを解放するようです。だから大丈夫です。


0

要するに、はい。以前の回答を参照してください。

そしてこれも、ここから:

ianlancetaylorが2015年2月18日にコメントしました
これを理解するための鍵は、for / rangeステートメントの本体の実行中に現在の反復がないことを理解することだと思います。表示されている値のセットと表示されていない値のセットがあります。本文の実行中に、表示されたキーと値のペアの1つ(最新のペア)が、範囲ステートメントの変数に割り当てられました。そのキーと値のペアについて特別なことは何もありません。これは、反復中にすでに見られたものの1つにすぎません。

彼が回答している質問は、range操作中にマップ要素を変更することに関するものです。そのため、「現在の反復」について言及しています。ただし、ここでも関係があります。範囲内でキーを削除できます。つまり、範囲内で後でそれらのキーが表示されないことを意味します(既にキーを見ている場合は、問題ありません)。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.