2015-06-05 2 views
2

В недавнем проекте Go мне нужно прочитать файл двоичных данных, сгенерированный Python, но из-за заполнения, binary.Read в Go не читает его правильно. Ниже приведен минимальный пример моей проблемы.binary.Read не обрабатывает прокладку структуры, как ожидалось

структура Я имею дело с тем, если в следующем формате

type Index struct{ 
    A int32 
    B int32 
    C int32 
    D int64 
} 

Как вы можете увидеть размер структуры составляет 4 + 4 + 4 + 8 = 20, но Python добавили дополнительные 4 байта для выравнивания , Таким образом, размер на самом деле 24.

Ниже приведен код работоспособного Python Я использую, чтобы написать эту структуру:

#!/usr/bin/env python 
# encoding=utf8 

import struct 

if __name__ == '__main__': 
    data = range(1, 13) 
    format = 'iiiq' * 3 
    content = struct.pack(format, *data) 
    with open('index.bin', 'wb') as f: 
     f.write(content) 

означает формат iiiq Есть три 32 разрядные целые чисел и один 64-битное целое в структурах, что совпадает с ранее описанной структурой Index. И работает этот код будет генерировать файл с именем index.bin размером 72, который равен 24 * 3.

и ниже код Go Я использую для чтения index.bin:

package main 

import (
     "encoding/binary" 
     "fmt" 
     "os" 
     "io" 
     "unsafe" 
) 

type Index struct { 
     A int32 
     B int32 
     C int32 
     D int64 
} 

func main() { 
     indexSize := unsafe.Sizeof(Index{}) 
     fp, _ := os.Open("index.bin") 
     defer fp.Close() 
     info, _ := fp.Stat() 
     fileSize := info.Size() 
     entryCnt := fileSize/int64(indexSize) 
     fmt.Printf("entry cnt: %d\n", entryCnt) 

     readSlice := make([]Index, entryCnt) 
     reader := io.Reader(fp) 
     _ = binary.Read(reader, binary.LittleEndian, &readSlice) 
     fmt.Printf("After read:\n%#v\n", readSlice) 
} 

И это выход:

entry cnt: 3 
After read: 
[]main.Index{main.Index{A:1, B:2, C:3, D:17179869184}, main.Index{A:0, B:5, C:6, D:7}, main.Index{A:8, B:0, C:9, D:47244640266}} 

Очевидно, что выход испорчен при чтении из созданного на Python файла.

Так что мой вопрос в том, как я могу прочитать файл сгенерированный питоном (с дополнением) в правильном режиме?

+1

Это http://golang.org/ref/spec#Size_and_alignment_guarantees может быть полезным. – alex

+0

@alex Если я серьезно не понимаю что-то, макет памяти здесь не имеет значения, так как 'binary.Read' просто считывает элементы структуры в порядке. –

+0

Возможно, это поможет http://play.golang.org/p/bCfWmKTP25. – alex

ответ

5

Вы можете просто подушечка ваш Go структура, чтобы соответствовать:

type Index struct { 
    A int32 
    B int32 
    C int32 
    _ int32 
    D int64 
} 

Который производит:

[]main.Index{main.Index{A:1, B:2, C:3, _:0, D:4}, main.Index{A:5, B:6, C:7, _:0, D:8}, main.Index{A:9, B:10, C:11, _:0, D:12}} 

binary.Read знает, чтобы пропустить _ поле:

При чтении в структурах, полевые данные для полей с пустыми (_) именами полей пропускаются; то есть имена пустых полей могут использоваться для заполнения.

(Так 0 значения _ не потому, что обивка в файле не было установлено равным нулю, а потому, что поле структура инициализировался 0 и не изменял, и обивка в файл был пропущен, а не читать .)

+0

Не могли бы вы объяснить, почему заполнение перед D? Я попытался заполнить D, прежде чем публиковать это, но значение не соответствует действительности. –

+0

Вот как Python заполнял свой вывод, чтобы выровнять его на 64-битных границах. '4 << 32' -' 17179869184', вот почему вы выбрали первый «D», не пропустив сначала прокладку. –

-1

@ Решение Barber является работоспособным, но я обнаружил, что добавление поля заполнения не очень удобно. И я нашел лучший способ сделать это.

Ниже новый golang прочитать код, который работает отлично:

package main 

import (
    "fmt" 
    "os" 
    "io" 
    "io/ioutil" 
    "unsafe" 
) 

type Index struct { 
    A int32 
    B int32 
    C int32 
    // Pad int32 
    D int64 
} 

func main() { 
    indexSize := unsafe.Sizeof(Index{}) 
    fp, _ := os.Open("index.bin") 
    defer fp.Close() 
    info, _ := fp.Stat() 
    fileSize := info.Size() 
    entryCnt := fileSize/int64(indexSize) 

    reader := io.Reader(fp) 
    allBytes, _ := ioutil.ReadAll(reader) 
    readSlice := *((*[]Index)(unsafe.Pointer(&allBytes))) 
    realLen := len(allBytes)/int(indexSize) 
    readSlice = readSlice[:realLen] 
    fmt.Printf("After read:\n%#v\n", readSlice) 
} 

Выход:

After read: 
[]main.Index{main.Index{A:1, B:2, C:3, D:4}, main.Index{A:5, B:6, C:7, D:8}, main.Index{A:9, B:10, C:11, D:12}} 

Это решение не требует явного поля заполнения.

Суть здесь в том, что если вы разрешите golang преобразовать все байты в фрагмент структуры Index, он, похоже, способен хорошо обрабатывать прокладку.

+1

До тех пор, пока вам удобно пользоваться 'unsafe', что называется по какой-то причине. Нет никакой гарантии, что прокладка Go будет согласованной между машинами или версиями. Вы здесь, полагаясь на то, что (на вашей машине и Go версии) Go добавляет свою структуру так же, как файл. –

1

Например,

package main 

import (
    "bufio" 
    "encoding/binary" 
    "fmt" 
    "io" 
    "os" 
) 

type Index struct { 
    A int32 
    B int32 
    C int32 
    D int64 
} 

func readIndex(r io.Reader) (Index, error) { 
    var index Index 
    var buf [24]byte 
    _, err := io.ReadFull(r, buf[:]) 
    if err != nil { 
     return index, err 
    } 
    index.A = int32(binary.LittleEndian.Uint32(buf[0:4])) 
    index.B = int32(binary.LittleEndian.Uint32(buf[4:8])) 
    index.C = int32(binary.LittleEndian.Uint32(buf[8:12])) 
    index.D = int64(binary.LittleEndian.Uint64(buf[16:24])) 
    return index, nil 
} 

func main() { 
    f, err := os.Open("index.bin") 
    if err != nil { 
     fmt.Fprintln(os.Stderr, err) 
     return 
    } 
    defer f.Close() 
    r := bufio.NewReader(f) 
    indexes := make([]Index, 0, 1024) 
    for { 
     index, err := readIndex(r) 
     if err != nil { 
      if err == io.EOF { 
       break 
      } 
      fmt.Fprintln(os.Stderr, err) 
      return 
     } 
     indexes = append(indexes, index) 
    } 
    fmt.Println(indexes) 
} 

Выход:

[{1 2 3 4} {5 6 7 8} {9 10 11 12}] 

Вход:

00000000 01 00 00 00 02 00 00 00 03 00 00 00 00 00 00 00 |................| 
00000010 04 00 00 00 00 00 00 00 05 00 00 00 06 00 00 00 |................| 
00000020 07 00 00 00 00 00 00 00 08 00 00 00 00 00 00 00 |................| 
00000030 09 00 00 00 0a 00 00 00 0b 00 00 00 00 00 00 00 |................| 
00000040 0c 00 00 00 00 00 00 00       |........| 
+0

Мне любопытно, есть ли причина для 'io.ReadAtLeast (r, buf [:], len (buf))' vs 'io.ReadFull (r, buf [:])'? Семантика в этом контексте одна и та же, но более поздняя версия здесь более читаема. –

+0

@DaveC: 'ReadFull' - специальная форма' ReadAtLeast'. Исправлена. – peterSO

Смежные вопросы