選択したキーをマップから削除するにはどうすればよいですか?以下のコードのように、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)
}
}
これは安全です!同様のサンプルは Effective Go にもあります。
for key := range m {
if key.expired() {
delete(m, key)
}
}
そして 言語仕様 :
マップの反復順序は指定されておらず、反復ごとに同じになる保証はありません。まだ到達していないマップエントリが反復中に削除された場合、対応する反復値は生成されません。マップエントリが反復中に作成された場合、そのエントリは反復中に生成されるか、スキップされる場合があります。選択は、作成されるエントリごとに、および反復ごとに異なる場合があります。マップがnilの場合、反復回数は0です。
セバスチャンの答えは正確ですが、私はなぜそれが安全であるかを知りたかったので、 Map source code 。 delete(k, v)
の呼び出しでは、実際に値を削除するのではなく、基本的に(カウント値を変更するだけで)フラグを設定するように見えます。
_b->tophash[i] = Empty;
_
(空は値_0
_の定数です)
マップが実際に行っているように見えるのは、マップのサイズに応じて一定数のバケットを割り当てることです。これは、_2^B
_( this source code のレートで挿入を実行するにつれて大きくなります) ):
_byte *buckets; // array of 2^B Buckets. may be nil if count==0.
_
そのため、ほとんどの場合、使用しているよりも多くのバケットが割り当てられており、マップ上でrange
を実行すると、その_2^B
_内の各バケットのtophash
値がチェックされます。スキップできるかどうかを確認します。
要約すると、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
_
メモリリークが発生する可能性があるかどうか疑問に思っていました。そこで、テストプログラムを作成しました。
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はメモリを解放するようです。大丈夫です.
要するに、はい。以前の回答を参照してください。
そしてこれも here から:
ianlancetaylorは2015年2月18日にコメントしました
これを理解するための鍵は、for/rangeステートメントの本体の実行中に、現在の反復がないことを認識することだと思います。表示されている値のセットと、表示されていない値のセットがあります。本体の実行中に、表示されたキー/値のペアの1つ(最新のペア)が範囲ステートメントの変数に割り当てられました。そのキー/値のペアについて特別なことは何もありません。これは、反復中にすでに見られたものの1つにすぎません。
彼が答えている質問は、range
操作中に所定の位置にあるマップ要素を変更することです。それが彼が「現在の反復」に言及している理由です。ただし、ここでも関連性があります。範囲内でキーを削除できます。これは、後で範囲内でそれらのキーが表示されないことを意味します(すでに見た場合は大丈夫です)。