Если сжать файл с именем a.txt
, содержащий текст "hello"
(который является 5 символов), то результат молнии будет около 115 байт. Означает ли это, что формат zip неэффективен для сжатия текстовых файлов? Конечно нет. Существует накладные расходы. Если файл содержит "hello"
сто раз (500 байт), то при zipping это приведет к тому, что файл будет 120 байт! 1x"hello"
=> 115 байт, 100x"hello"
=> 120 байт! Мы добавили 495 байтов, но сжатый размер увеличился только на 5 байт.
Нечто подобное происходит и с encoding/gob
пакетом:
Реализация компилирует пользовательский кодек для каждого типа данных в потоке и является наиболее эффективным, когда один кодер используется для передачи потока значений, амортизировать стоимость компиляции.
Когда вы «первый» сериализовать значение типа, то определение типа также должна быть включена/передается, так что декодер может правильно интерпретировать и декодирования потока:
Поток глотков самоописателен.Каждому элементу данных в потоке предшествует спецификация его типа, выраженная в терминах небольшого набора предопределенных типов.
Вернемся к вашему примеру:
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
e := Entry{"k1", "v1"}
enc.Encode(e)
fmt.Println(buf.Len())
Он печатает:
48
Теперь давайте кодировать несколько из типа же:
enc.Encode(e)
fmt.Println(buf.Len())
enc.Encode(e)
fmt.Println(buf.Len())
сейчас выход:
60
72
Попробуйте на Go Playground.
Анализ результатов:
Дополнительные значения одного и того же Entry
типа только стоимость 12 байт, в то время как первый является 48
байт, потому что определение типа также включен (который составляет ~ 26 байт), но это одноразовые накладные расходы.
Так в основном вы передаете 2 string
S: "k1"
и "v1"
, которые 4 байта, а длина string
с также должен быть включен, используя 4
байт (размером int
на 32-разрядных архитектур) дает вам 12 байт , что является «минимумом». (Да, вы могли бы использовать меньший тип для длины, но это имело бы свои ограничения. Кодировка переменной длины была бы лучшим выбором для небольших чисел, см. encoding/binary
.)
В целом, encoding/gob
делает довольно хорошая работа для ваших нужд. Не обманывайте себя начальными впечатлениями.
Если это 12 байт для одного Entry
слишком «много» для вас, вы всегда можете обернуть поток в compress/flate
или compress/gzip
писателя для дальнейшего уменьшения размера (в обмен на более медленной кодирования/декодирования и немного выше требования к памяти для процесс).
Демонстрация:
Давайте тестировать 3 решения:
- не используя "голый" выход (без сжатия)
- Использование
compress/flate
для сжатия выходного сигнала encoding/gob
- Использование
compress/gzip
для сжатия выходного сигнала encoding/gob
Мы будем писать тысячу записей, изменение ключей и значений каждого из них, будучи "k000"
, "v000"
, "k001"
, "v001"
и т.д. Это означает, что размер несжатого из Entry
составляет 4 байта + 4 байта + 4 байта + 4 байта = 16 байты (2x 4 байта, длина 2x4 байтов).
код выглядит следующим образом:
names := []string{"Naked", "flate", "gzip"}
for _, name := range names {
buf := &bytes.Buffer{}
var out io.Writer
switch name {
case "Naked":
out = buf
case "flate":
out, _ = flate.NewWriter(buf, flate.DefaultCompression)
case "gzip":
out = gzip.NewWriter(buf)
}
enc := gob.NewEncoder(out)
e := Entry{}
for i := 0; i < 1000; i++ {
e.Key = fmt.Sprintf("k%3d", i)
e.Val = fmt.Sprintf("v%3d", i)
enc.Encode(e)
}
if c, ok := out.(io.Closer); ok {
c.Close()
}
fmt.Printf("[%5s] Length: %5d, average: %5.2f/Entry\n",
name, buf.Len(), float64(buf.Len())/1000)
}
Выход:
[Naked] Length: 16036, average: 16.04/Entry
[flate] Length: 4123, average: 4.12/Entry
[ gzip] Length: 4141, average: 4.14/Entry
Попробуйте на Go Playground.
Как вы можете видеть: «голый» выход - 16.04 bytes/Entry
, чуть меньше расчетного размера (из-за однократного крошечного накладного расхода, описанного выше).
Когда вы используете вентилятор или gzip для сжатия выходного сигнала, вы можете уменьшить выходной размер примерно до 4.13 bytes/Entry
, что составляет около ~ 26% от теоретического размера, я уверен, что вас удовлетворяет. (Обратите внимание, что с «реальными» данными коэффициент сжатия, вероятно, будет намного выше, поскольку ключи и значения, которые я использовал в тесте, очень похожи и, следовательно, очень хорошо сжимаются, а соотношение должно составлять около 50% с реальными данными).
Впечатляющий анализ (я всегда восхищаюсь вашими ответами), но в этом конкретном случае кажется, что объясняет науку о ракетах ребенку, который спросил, почему его трехколесный велосипед немного медленный. ;-) Хотя я думаю, что у 'gob' определенно есть свои возможности, для такой простой задачи, с которой, похоже, работает OP, я уверен, что для правильной переопределения того, что уже сделано на C++, требуется. Еще одним подходом к этому подходу является то, что новый код будет сопоставим с его устаревшими данными. – kostix
@kostix Это была моя первая мысль и впечатление об этом вопросе, но потом я увидел ее последнюю строку: «без ручного кодирования» _... Поэтому я решил остаться с «encoding/gob». – icza