Вопрос по dictionary, go, for-loop – Безопасно ли удалять выбранные ключи с карты в цикле диапазона?

91

Как удалить выбранные ключи с карты? Безопасно ли сочетать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

Ваш Ответ

4   ответа
3

может ли произойти утечка памяти. Итак, я написал тестовую программу:

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 освобождает память. Так что все в порядке.

123

Это безопасно! Вы также можете найти аналогичный образец вЭффективный Go:

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

А такжеспецификация языка:

Порядок итераций для карт не указан и не гарантируется, что он будет одинаковым для каждой итерации. Если записи на карте, которые еще не были достигнуты,удалено во время итерациисоответствующие значения итерации не будут получены. Если записи на картесоздан во время итерацииЭта запись может быть создана во время итерации или может быть пропущена. Выбор может варьироваться для каждой созданной записи и от одной итерации к следующей. Если карта равна нулю, количество итераций равно 0.

@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
key.expired undefined (строка типа не имеет поля или метод не просрочен) user776942
122

Ответ Себастьяна точен, но я хотел знатьЗачем это было безопасно, поэтому я немного покопался вИсходный код карты, Похоже на звонокdelete(k, v)в основном он просто устанавливает флаг (а также изменяет значение счетчика) вместо фактического удаления значения:

b->tophash[i] = Empty;

(Пусто является константой для значения0)

Похоже, что карта на самом деле делает, это выделяет определенное количество сегментов в зависимости от размера карты, которое увеличивается по мере того, как вы выполняете вставки со скоростью2^B (отэтот исходный код):

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

Таким образом, почти всегда выделяется больше сегментов, чем вы используете, и когда вы делаетеrange по карте это проверяет, что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
Актуально ли "не освобождает ли память"? Я пытался найти в источнике этот комментарий, но не могу его найти. Tony
Важная заметка: помните, что это простотекущая реализация, и это может измениться в будущем, поэтому вы не должны полагаться на какие-либо дополнительные свойства, которые могут казаться «поддерживающими».только те гарантии, которые у вас есть, указаны в спецификации,как описано в ответе Себастьяна. (Тем не менее, изучение и объяснение внутренних элементов Go, безусловно, интересно, познавательно и в целом потрясающе!) akavel
Похоже, вы говорите, что безопасно удалить любое произвольное значение с карты, а не только текущее, верно? И когда придет время оценить хэш, который я ранее произвольно удалил, он будет безопасно пропустить его? Flimzy
@Flimzy Это правильно, как вы можете видеть на этой площадкеplay.golang.org/p/FwbsghzrsO , Обратите внимание, что если индекс, который вы удаляете, является первым в диапазоне, он все равно будет показывать этот индекс, поскольку он уже записан в k, v, но если вы установите индекс на любой, кроме первого, который найдет диапазон, он будет отображать только два ключа. / значение пары вместо трех и не паникуйте. Verran
0

А также это, изВот:

ianlancetaylor прокомментировал 18 фев 2015
Я думаю, что ключом к пониманию этого является осознание того, что при выполнении тела оператора for / range не существует текущей итерации. Существует набор значений, которые были замечены, и набор значений, которые не были замечены. Во время выполнения тела одна из увиденных пар ключ / значение - самая последняя пара - была назначена переменной (ам) оператора диапазона. В этой паре ключ / значение нет ничего особенного, это всего лишь одно из тех, что уже были замечены во время итерации.

Он отвечает на вопрос об изменении элементов карты на месте во времяrange операция, поэтому он упоминает «текущую итерацию». Но это также уместно: вы можете удалять ключи во время диапазона, и это просто означает, что вы не увидите их позже в диапазоне (и если вы уже видели их, это нормально).

Похожие вопросы