はじめてのGo―シンプルな言語仕様,型システム,並行処理

第4章 標準パッケージ―JSON,ファイル,HTTP,HTMLを扱う

この記事を読むのに必要な時間:およそ 8.5 分

os,ioパッケージ

次に,ファイルへの書き込みと読み出しを行い,先ほどのJSONをファイルに保存するようにしましょう。ファイルの作成や取得はosパッケージ,書き込みや読み出しはioパッケージを用いて行います。

ファイルの生成

まずosパッケージを用いてファイルを作成してみましょう。os.Create()関数にファイル名を渡すと,*os.File構造体へのポインタが取得できます。このとき第二戻り値にエラーが返るため最初にエラー処理をします。

package main

import (
    "log"
    "os"
)

func main() {
    // ファイルを生成
    file, err := os.Create("./file.txt")
    if err != nil { // エラー処理
        log.Fatal(err)
    }
    // プログラムが終わったらファイルを閉じる
    defer file.Close()
}

*os.Fileは,io.ReadWriteCloserというインタフェース型であり,これは,Read()Write()Close()の3つのメソッドを実装していることを意味します。開いたファイルは使い終わったら閉じる必要があるので,deferを用いてClose()main()の終わりで実行するようにします。

ファイルへの書き込み

続いてファイルにデータを書き込んでみましょう。

先ほど取得した*os.Fileは,io.Writerインタフェースを実装していました。これは次のように定義されています。

type Writer interface {
    Write(p []byte) (n int, err error)
}

[]byteを引数として渡すと,その中身を対象に書き込み,戻り値として書き込んだバイト数とエラーを返します。

これを利用して,hello worldを書き込むには次のようにします。

func main() {
    // ファイルを生成
    file, err := os.Create("./file.txt")
    if err != nil { // エラー処理
        log.Fatal(err)
    }

    // プログラムが終わったらファイルを閉じる
    defer file.Close()

    // 書き込むデータを[]byteで用意する
    message := []byte("hello world\n")

    // Write()を用いて書き込む
    _, err = file.Write(message)
    if err != nil { // エラー処理
        log.Fatal(err)
    }
}

ここでは,Write()の第一戻り値である書き込まれたバイト数は無視していますが,第二戻り値であるエラーは受け取ってエラー処理をしています。

実行し,生成されたファイルに文字列が書き込まれていれば成功です。

$ go run write.go
$ cat file.txt
hello world

また,WriteString()を用いると,毎回[]byteに変換する必要がなくなります。

_, err = file.WriteString("hello world\n")

書き込む対象のio.WriterWriteString()のようなメソッドを実装していない場合は,fmt.Fprint()を用いると,[]byteを経由せずio.Writerに対して文字列を直接書き込むことができます。

_, err = fmt.Fprint(file, "hello world\n")

このようにデータを書き込む方法はいくつかありますが,いずれも対象がio.Writerインタフェース型であることを利用している点を意識するとよいでしょう。

ファイルからの読み出し

次に書き込んだデータを読み出してみます。すでにあるファイルを開く場合はos.Open()を用います。

func main() {
    // ファイルを開く
    file, err := os.Open("./file.txt")
    if err != nil { // エラー処理
        log.Fatal(err)
    }

    // プログラムが終わったらファイルを閉じる
    defer file.Close()
}

ファイルの読み出しにはio.Readerインタフェースを用います。

type Reader interface {
    Read(p []byte) (n int, err error)
}

Read()は,読み出したデータを格納するのに十分な長さを持ったスライスを渡すと,そこにデータが格納されます。ここではhello world\nという12byteのデータを読み出すため,長さが12のbyteのスライスを用意し,そこにデータを読み出します。戻り値として読み出したバイト数とエラーを返します(ここでは,戻り値のバイト数のほうは無視します⁠⁠。

func main() {
    // ファイルを開く
    file, err := os.Open("./file.txt")
    if err != nil { // エラー処理
        log.Fatal(err)
    }

    // プログラムが終わったらファイルを閉じる
    defer file.Close()

    // 12byte格納可能なスライスを用意する
    message := make([]byte, 12)

    // ファイル内のデータをスライスに読み出す
    _, err = file.Read(message)
    if err != nil { // エラー処理
        log.Fatal(err)
    }

    // 文字列にして表示
    fmt.Print(string(message))
}

以上が,*os.Fileが実装しているio.ReadWrite Closerインタフェースを用いた最も基本的なファイル操作です。これはファイル操作以外に,ネットワークへのI/O処理でも同様に役に立つ知識です。

JSONのEncoder/Decoder経由の保存

ここまでの2つを組み合わせると,JSONをファイルに保存するには,JSONとファイルの間でデータを[]byteでやりとりすればよいことは容易に想像できるでしょう。しかし,JSONにはEncoder/Decoderというio.ReadeWriterを扱うAPIも用意されているため,同じくio.ReadWriterであるファイルを扱うにはこちらを用いることができます。

まず,json.Encoderを用いてJSONにデータを変換しつつ,io.Write()経由でファイルに書き込んでみましょう。

func main() {
    person := &Person{
        ID: 1,
        Name: "Gopher",
        Email: "gopher@example.org",
        Age: 5,
        Address: "",
        memo: "golang lover",
    }

    // ファイルを開く
    file, err := os.Create("./person.json")
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()

    // エンコーダの取得
    encoder := json.NewEncoder(file)

    // JSONエンコードしたデータの書き込み
    err = encoder.Encode(person)
    if err != nil {
        log.Fatal(err)
    }
}

JSONへの変換結果を[]byteとして受け取ることなく,そのまま*os.Fileに書き込んでいることがわかると思います。

同様に,json.Decoderを用いてファイル内のJSONデータを読み出し,Personにデコードしてみましょう。

func main() {
    // ファイルを開く
    file, err := os.Open("./person.json")
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()

    // データを書き込む変数
    var person Person

    // デコーダの取得
    decoder := json.NewDecoder(file)

    // JSONデコードしたデータの書き込み
    err = decoder.Decode(&person)
    if err != nil {
        log.Fatal(err)
    }

    // 読み出した結果の表示
    fmt.Println(person)
}

こちらも,ファイルから読み出した結果を[]byteとして受け取ることなく,そのままデコードしてPerson型の変数に格納しています。

このように,標準ライブラリにはio.Readerio.Writerを中心として設計されたAPIが多くあります。この点を意識してドキュメントを見ると,より適したAPIを選ぶことができます。

著者プロフィール