DeepEqual неверен после сериализации карты в gob

Я столкнулся с каким-то странным поведением с отражением. У меня есть объект типа map[string][]stringс одним ключом, значение которого является пустым срезом. Когда я использую gob для кодирования этого объекта, а затем декодирую его в другую карту, эти две карты не равны в соответствии с отражением.DeepEqual (даже если содержимое идентично).

package main

import (
    "fmt"
    "bytes"
    "encoding/gob"
    "reflect"
)

func main() {
    m0 := make(map[string][]string)
    m0["apple"] = []string{}

    // Encode m0 to bytes
    var network bytes.Buffer
    enc := gob.NewEncoder(&network)
    enc.Encode(m0)

    // Decode bytes into a new map m2
    dec := gob.NewDecoder(&network)
    m2 := make(map[string][]string)
    dec.Decode(&m2)

    fmt.Printf("%t\n", reflect.DeepEqual(m0, m2)) // false
    fmt.Printf("m0: %+v != m2: %+v\n", m0, m2) // they look equal to me!
}

Выход:

false
m0: map[apple:[]] != m2: map[apple:[]]

Пара замечаний из последующих экспериментов:

Если я сделаю ценность m0["apple"] например, непустой срез m0["apple"] = []string{"pear"}, тогда DeepEqual возвращает истину.

Если я сохраню значение как пустой фрагмент, но создаю идентичную карту с нуля, а не с использованием gob, тогда DeepEqual возвращает true:

m1 := make(map[string][]string)
m1["apple"] = []string{}
fmt.Printf("%t\n", reflect.DeepEqual(m0, m1)) // true!

Так что не проблема в том, как DeepEqual обрабатывает пустые фрагменты; это странное взаимодействие между этим и сериализацией Гоба.

1 ответ

Решение

Это потому, что вы кодируете пустой фрагмент, и во время декодирования encoding/gob Пакет выделяет фрагмент только в том случае, если предоставленный фрагмент (цель для декодирования) недостаточно велик для размещения закодированных значений. Это задокументировано по адресу: gob: Типы и значения:

В общем случае, если требуется выделение, декодер выделит память. Если нет, он обновит переменные назначения значениями, считанными из потока.

Поскольку там закодировано 0 элементов, а nil Срез идеально подходит для размещения 0 элементов, срез не будет выделен. Мы можем проверить это, если напечатаем результат сравнения срезов в nil:

fmt.Println(m0["apple"] == nil, m2["apple"] == nil)

Вывод вышесказанного (попробуйте на игровой площадке Go):

true false

Обратите внимание, что fmt печать на упаковке nil значения срезов и пустые срезы аналогично: как [], вы не можете полагаться на его вывод, чтобы судить, является ли срезы nil или нет.

А также reflect.DeepEqual() лечит nil срез и пустой, но не nil срез разные (неглубокие равные):

Обратите внимание, что ненулевой пустой слайс и нулевой слайс (например, []byte{} и []byte(nil)) не являются глубоко равными.

Другие вопросы по тегам