Является ли кодирование /gob детерминированным?

Можем ли мы ожидать для двух объектов Go x, y, таких, что x равен y (при условии отсутствия хитрости с интерфейсами и картами, только структуры и массивы), что выходные данные gob_encode(x) и gob_encode(y) всегда будут одинаковыми?

изменить (8 июня 2018 года):

Кодирование Гоба является недетерминированным, когда карты вовлечены. Это связано со случайным порядком итераций карт, в результате чего их сериализация будет случайным образом упорядочена.

2 ответа

Решение

Вы не должны заботиться, пока это "делает работу". Но ток encoding/gob реализация является детерминированной. Но (продолжайте читать)!

Поскольку:

Поток гоблов самоописывает себя. Каждому элементу данных в потоке предшествует спецификация его типа, выраженная в виде небольшого набора предопределенных типов.

Это означает, что если вы кодируете значение типа в первый раз, информация о типе будет отправлена. Если вы закодируете другое значение того же типа, описание типа не будет передано снова, просто ссылка на его предыдущую спецификацию. Таким образом, даже если вы дважды закодируете одно и то же значение, оно будет создавать различные последовательности байтов, поскольку первая будет содержать спецификацию типа и значение, а вторая будет содержать только тип ref (например, идентификатор типа) и значение.

Смотрите этот пример:

type Int struct{ X int }

b := &bytes.Buffer{}
e := gob.NewEncoder(b)

e.Encode(Int{1})
fmt.Println(b.Bytes())

e.Encode(Int{1})
fmt.Println(b.Bytes())

e.Encode(Int{1})
fmt.Println(b.Bytes())

Вывод (попробуйте на Go Playground):

[23 255 129 3 1 1 3 73 110 116 1 255 130 0 1 1 1 1 88 1 4 0 0 0 5 255 130 1 2 0]
[23 255 129 3 1 1 3 73 110 116 1 255 130 0 1 1 1 1 88 1 4 0 0 0 5 255 130 1 2 0 5 255 130 1 2 0]
[23 255 129 3 1 1 3 73 110 116 1 255 130 0 1 1 1 1 88 1 4 0 0 0 5 255 130 1 2 0 5 255 130 1 2 0 5 255 130 1 2 0]

Как видела первая Encode() генерирует много байтов плюс значение для нашего Int ценность бытия [5 255 130 1 2 0], второй и третий вызовы добавляют то же самое [5 255 130 1 2 0] последовательность.

Но если вы создаете 2 разных gob.Encoder Если вы напишите одинаковые значения в одном и том же порядке, они приведут к точным результатам.

Обратите внимание, что в предыдущем утверждении "тот же порядок" также важно. Поскольку спецификация типа передается при отправке первого значения такого типа, отправка значений разных типов в разном порядке также будет передавать спецификации типов в другом порядке, и поэтому ссылки / идентификаторы типов могут отличаться, что означает, что когда значение такой тип закодирован, ссылка / идентификатор другого типа будет использоваться / отправляться.

Также обратите внимание, что реализация gob пакет может меняться от выпуска к выпуску. Эти изменения будут обратно совместимыми (они должны явно указывать, если по какой-то причине они будут вносить обратно несовместимые изменения), но обратная совместимость не означает, что выходные данные будут одинаковыми. Таким образом, разные версии Go могут давать разные результаты (но все они декодируются всеми совместимыми версиями).

Вероятно, следует отметить, что принятый ответ неверен: encoding / gob не упорядочивает элементы карты детерминистическим образом: https://play.golang.org/p/Hh3_5Kb3Znn

Я разветвил кодирование / gob и добавил код для упорядочения карт по ключу, прежде чем записывать их в поток. Это повлияет на производительность, но моему конкретному приложению не требуется высокая производительность. Помните, что пользовательские маршалеры могут сломать это, поэтому используйте с осторожностью: https://github.com/dave/stablegob

Это также не детерминировано, если вы используете разные типы и разные кодировщики.

Пример:

package main

import (
    "bytes"
    "crypto/sha1"
    "encoding/gob"
    "encoding/hex"
    "log"
)

func main() {
    encint()
    encint64()
    encstring()

}

func encint() {
    s1 := []int{0, 2, 4, 5, 7}
    buf2 := bytes.Buffer{}
    enc2 := gob.NewEncoder(&buf2)
    enc2.Encode(s1)
}

func encint64() {
    s1 := []int64{0, 2, 4, 5, 7}
    buf2 := bytes.Buffer{}
    enc2 := gob.NewEncoder(&buf2)
    enc2.Encode(s1)
}

func encstring() {
    s1 := []string{"a", "b", "c", "d"}
    buf2 := bytes.Buffer{}
    enc2 := gob.NewEncoder(&buf2)
    enc2.Encode(s1)
    log.Println(buf2.Bytes())

    hash := sha1.New()
    hash.Write(buf2.Bytes())
    ret := hash.Sum(nil)
    log.Println(hex.EncodeToString(ret))
}

Беги на игровой площадке Go

Обратите внимание, если вы закомментируете encint() или же encint64() то encstring создаст разные байты и другой хэш-код.

Это происходит несмотря на использование разных объектов / указателей.

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