4
1

More than 3 years have passed since last update.

Go バイナリファイルの読み書き

Posted at

Go バイナリファイルの読み書き

今回は、Goで固定長と可変長なデータがあるバイナリファイルを読み書きしてみたいと思います。

やりたいこと

複数個ファイルを1つのバイナリファイルにする。(結合)

バイナリファイルを複数ファイルに書き戻す。(分割)

データ構造

下記のデータ構造を1つのファイルとして扱い、連結していく。

Offset: 00 01 02 03 04 05 06 07
00 ファイル名サイズ
(4 byte 固定: BigEndian)
ファイルサイズ
(4 byte 固定: BigEndian)
08 ファイル名
(可変長: LittleEndian)
・・ ファイル内容
(可変長: LittleEndian)

データ例

  • ファイル名
    • HOGE.txt
  • ファイルサイズ
    • 8 byte
  • ファイル内容
    • 12345678
Offset: 00 01 02 03 04 05 06 07 データ値
00 00 00 00 08 00 00 00 08 0x00000008 0x00000008
08 48 4F 47 45 2E 74 78 74 H O G E . t x t
10 31 32 33 34 35 36 37 38 1 2 3 4 5 6 7 8

コマンド

↓ 2つのファイルを1つのファイルにまとめる

結合時
$ sample.exe join text.txt text.md
joined.dat

↓ 1つのファイルから2つのファイルを作る

分割時
$ sample.exe split joined.dat
text.txt
text.md

ソースコード

そしてザクっと作ったものがこちら。

結合時のファイル名とかは、固定にしてます。

sample.go
package main

import (
    "bytes"
    "encoding/binary"
    "fmt"
    "io"
    "io/ioutil"
    "log"
    "os"
    "path/filepath"
)

type head struct {
    FileNameSize uint32
    FileSize     uint32
}

func main() {
    // 引数が足りない
    if len(os.Args) < 2 {
        os.Exit(1)
    }

    // 引数1つ目はコマンドとして扱う
    switch os.Args[1] {
    case `join`:
        dist := `joined.dat`
        // 書き込み先のファイルを作成
        wf, err := os.Create(dist)
        if err != nil {
            log.Fatal(err)
        }
        defer wf.Close()

        for _, file := range os.Args[2:] {
            b, err := ioutil.ReadFile(file)
            if err != nil {
                log.Fatal(err)
            }

            // ファイルパスをファイル名のみに変更
            filename := filepath.Base(file)

            head := head{
                FileNameSize: uint32(len(filename)),
                FileSize:     uint32(len(b)),
            }
            // 固定長部分を書き込み
            err = binary.Write(wf, binary.BigEndian, &head)
            if err != nil {
                log.Fatal(err)
            }
            // データ部分を書き込み
            _, err = wf.Write(append([]byte(filename), b...))
            if err != nil {
                log.Fatal(err)
            }
        }
        fmt.Println(dist)
    case `split`:
        for _, file := range os.Args[2:] {
            b, err := ioutil.ReadFile(file)
            if err != nil {
                log.Fatal(err)
            }

            buf := bytes.NewReader(b)

            for {
                // 固定長部分を読み取る
                head := head{}
                err := binary.Read(buf, binary.BigEndian, &head)
                if err == io.EOF {
                    break
                } else if err != nil {
                    log.Fatal(err)
                }

                // ファイル名を読み取る
                filename := make([]byte, head.FileNameSize)
                err = binary.Read(buf, binary.LittleEndian, &filename)
                if err != nil {
                    log.Fatal(err)
                }

                // データを読み取る
                data := make([]byte, head.FileSize)
                err = binary.Read(buf, binary.LittleEndian, &data)
                if err != nil {
                    log.Fatal(err)
                }

                // ファイルに出力する
                if err := ioutil.WriteFile(string(filename), data, 0666); err != nil {
                    log.Fatal(err)
                }
                fmt.Println(string(filename))
            }
        }
    default:
        // コマンドが違う
        os.Exit(1)
    }
}

まとめ

固定長のところは、構造体を作って読み書き。

可変長部分のところは、
書くときは 何も考えずつなげて 吐き出す。
読むときは データ長のスライスを作って 読み込む。

という感じでコードを書いてみました。

良い書き方があったら教えてくださいネ~。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1