Learning Go Chapter 11. The Standard Library

The Standard Library

  • Go の標準ライブラリは Python の "batteries included" の哲学と同じで、アプリケーション開発に必要なライブラリを標準装備している

io とその仲間たち

  • Goの入出力哲学の中心は、ioパッケージにある
    • ReaderWriter インタフェースはよく使われるインタフェース
// スライスを入力にして、直接変更される
type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}
func counteLetters(r io.Reader) (map[string]int, error) {
    buf := make([]byte, 2048)
    out := map[string]int{}

    for {
        n, err := r.Read(buf)
        for _, b := range buf[:n] {
            if (b >= 'A' && b < 'Z') || (b >= 'a' && b < 'z') {
                out[string(b)]++
            }
        }

        if err == io.EOF {
            return out, nil
        }

        if err != nil {
            return nil, err
        }
    }
}
  • 注意すべき点が3つ
    • 1.バッファを1回作成し、r.Readを呼び出すたびに再利用する
      • これにより、単一のメモリ割り当てを使用して、潜在的に大きなデータソースから読み取ることが可能
      • Readメソッドが[]バイトを返すように記述されている場合、呼び出しごとに新しい割り当てが必要になる
      • 各割り当てはヒープ上で終了するため、ガベージコレクターにとって非常に多くの作業が必要
      1. r.Readから返されたn値を使用して、バッファーに書き込まれたバイト数を確認し、bufスライスのサブスライスを反復処理して、読み取られたデータを処理する
      1. r.Readから返されたエラーがio.EOFの場合、rからの読み取りが完了したことがわかる
      2. このエラーは、実際にはエラーではない
      3. これは、io.Readerから読み取るものが何も残っていないことを示す
      4. io.EOFが返されると、処理が終了し、結果が返される
func testCountLetter() error {
    s := "The quick brown fox jumped over the lazy dog"
    sr := strings.NewReader(s)

    counts, err := countLetters(sr)
    if err != nil {
        return err
    }
    fmt.Println(counts)

    return nil
}
  • io.MultiReader: 複数のio.Readerインスタンスから次々に読み取るio.Readerが返される
  • io.LimitReader: 指定されたio.Readerから指定されたバイト数までしか読み取らないio.Readerが返される
  • io.MultiWriter: 複数のio.Writerインスタンスに同時に書き込むio.Writerが返される

  • 他によく使うインターフェース

    • 大抵 Close メソッドは defer と一緒に使われる
type Closer interface {
    Close() error
}

type Seeker interface {
    Seek(offset int64, whence int) (int64, error)
}
  • io.Seekerインターフェースは、リソースへのランダムアクセスに使用される

    • whenceの有効な値は、定数io.SeekStart、io.SeekCurrent、およびio.SeekEnd
    • ただし、whenceはint型
  • io パッケージは上記のインタフェースを組み合わせ様々なインタフェースを定義する

    • io.ReadCloser
    • io.ReadSeeker
    • io.ReadWriteCloser
    • io.ReadWriteSeeker
    • io.ReadWriter
    • io.WriteCloser
    • io.WriteSeeker
  • ioutilパッケージは、io.Reader実装全体を一度にバイトスライスに読み取る、ファイルの読み取りと書き込み、一時ファイルの操作などのためのいくつかの簡単なユーティリティを提供する

    • ioutil.ReadAll、ioutil.ReadFile、およびioutil.WriteFile関数は、小さなデータソースには適している
  • 大きなデータソースを操作するには、bufioパッケージのReader、Writer、およびScannerを使用する

time パッケージ

  • time.Duration と time.Time がメインで使用する型
  • time パッケージで指定する時刻フォーマット文字列は、フォーマット文字列ではなく、以下のような具体的な日時を指定する
// 第一引数がフォーマット文字列
    t, err := time.Parse("2006-02-01 15:04:04 -0700", "2009-13-01 13:04:04 -0700")
    if err != nil {
        fmt.Println("Error!")
    }
  • これは、1から7まで順番に書くと以下のようになるから
01/02 03:04:05PM ’06 -0700
  • 書式設定に使用される日付と時刻は巧妙なニーモニックを目的としていますが、覚えるのが難しく、使用するたびに調べる必要がある
    • 幸いなことに、最も一般的に使用される日付と時刻の形式には、時間パッケージで独自の定数が与えられている

Monotonic Time

  • ほとんどのオペレーティングシステムは、現在の時刻に対応する壁時計と、コンピューターの起動時から単純にカウントアップする単調時計の2種類の時刻を追跡する

    • 2つの異なる時計を追跡する理由は、掛け時計が均一に増加しないため
    • 夏時間、うるう秒、およびNTP(Network Time Protocol)の更新により、掛け時計が予期せず前後に移動する可能性がある
    • これにより、タイマーを設定したり、経過時間を確認したりするときに問題が発生する可能性がある
  • この潜在的な問題に対処するために、Goは単調な時間を使用して、タイマーが設定されているとき、またはtime.Timeインスタンスがtime.Nowで作成されるたびに経過時間を追跡する

    • このサポートは目に見えない
    • Subメソッドは、モントニッククロックを使用してtime.Durationを計算する(両方のtime.Timeインスタンスに設定されている場合)
    • そうでない場合(インスタンスの一方または両方がtime.Nowで作成されなかったため)、Subメソッドはインスタンスで指定された時間を使用してtime.Durationを計算する

encoding/json

メタデータを追加するために構造体タグを利用する

  • 構造体タグは構造体メンバの型名の後にバックスラッシュで囲った文字列で指定する
    • バックスラッシュの中身は tagName:"tagValue"のように指定する
    • マーシャリングまたはアンマーシャリング時にフィールドを無視する必要がある場合は、名前にダッシュ(-)を使用します。フィールドが空のときに出力から除外する必要がある場合は、名前の後に、omitempty を追加する

    • 残念ながら、「空」の定義は、ご想像のとおり、ゼロ値と正確に一致していません。構造体のゼロ値は空としてカウントされませんが、長さゼロのスライスまたはマップは空としてカウントされる

type Order struct {
    ID          string    `json:"id"`
    DateOrdered time.Time `json:"date_ordered"`
    CustomerID  string    `json:"customer_id"`
    Items       []Item    `json:"items"`
}

type Item struct {
    ID   string `json:"id"`
    Name string `json:"name"`
}
  • json.Unmarshal関数は、io.Readerインターフェースの実装と同様に、データを入力パラメーターに入力とする
    • これには2つの理由がある
      • まず、io.Readerの実装と同様に、これにより同じ構造体を何度も効率的に再利用できるため、メモリ使用量を制御できる
      • 第二に、それを行う他の方法がない
        • Goには現在ジェネリックがないため、読み取られるバイトを格納するためにインスタンス化する必要があるタイプを指定する方法がない