Go/ファイル入出力

< Go

ファイル入出力編集

ファイルの作成・読出し・書込みなどの操作を「ファイル入出力」と言います。 Goのファイル入出力機能は、 "os" パッケージで実装されています。 "os" パッケージで定義された関数 os.Open 関数でファイルをオープンできます。 os.Open 関数を使用する際、この関数は戻り値を2つ返します(多値返却)。 os.Open 関数の第1戻値は正常動作時の戻値(*os.File)、第2戻値はエラー時に nil 以外を返します。

ファイルのオープンと読出し編集

バッファリングなし編集

ファイルのオープンと読出し(バッファリングなし)
package main

import (
	"io"
	"log"
	"os"
)

func main() {
	f, err := os.Open("/etc/hosts")
	if err != nil {
		log.Fatal(err)
	}
	defer func() {
		err := f.Close()
		if err != nil {
			log.Fatal(err)
		}
	}()

	b := make([]byte, 4096)
	for {
		c, err := f.Read(b)
		switch {
		case c == 0:
			return
		case err == io.EOF:
			return
		case err != nil:
			log.Fatal(err)
		case err == nil:
			os.Stdout.Write(b[:c])
		default:
			panic("???")
		}
	}
}
解説
	f, err := os.Open("/etc/hosts")
os.Open 関数は、ディフォルトでは読出しモードでファイルを開きます。
遅延実行
	defer func() {
		err := f.Close()
		if err != nil {
			log.Fatal(err)
		}
	}()
defer は遅延実行で、関数スコープを抜けるときに実行する処理を登録します。
この場合は main関数を抜けるときに f.Close() が必ず実行され、エラーがあればlogします。
C++のディストラクターと似ていますが、特定のオブジェクトには紐付いてはおらず複数の遅延実行の実行順を制御できるところが違います。
C言語の at_exit()関数とも似ていますが、at_exit()はプログラムの終了のタイミングに実行されるのに対して、defer は関数スコープを抜けたときに実行されます。
一旦 defer で登録処理を取り消す方法はありません。登録された処理側で条件を判定する必要があります。
Goでは goroutine もそうですが[1]、呼び出し先の生殺与奪の権を呼び出し元に握らせるのではなく、予め決めた手順で通信を行い協調して状態の遷移を管理するスタイルです。
		c, err := f.Read(b)
(*os.File).Readは引数のバッファサイズまでファイルからデーターを読みだします。
このとき読まれるのはバイト配列なので
			os.Stdout.Write(b[:c])
と文字列に変換せずそのまま []byte で書き出します。

エラーハンドリング編集

		log.Fatal(err)

をコールすると

2009/11/10 23:00:00 open /etc/hosts: no such file or directory
exit status 1

の様に診断メセージを書き出し、exit(1)を呼び出します(呼び出しもとには帰りません。C言語風にいうと _Noreturn 関数修飾子が着いた状態です)。

バッファリングあり編集

ファイルのオープンと読出し(バッファリングあり)
package main

import (
	"bufio"
	"fmt"
	"log"
	"os"
)

func main() {
	f, err := os.Open("/etc/hosts")
	if err != nil {
		log.Fatal(err)
	}
	defer func() {
		err := f.Close()
		if err != nil {
			log.Fatal(err)
		}
	}()

	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		fmt.Println(scanner.Text())
	}
	if err := scanner.Err(); err != nil {
		log.Fatal(err)
	}
}
解説
	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		fmt.Println(scanner.Text())
	}
	if err := scanner.Err(); err != nil {
		log.Fatal(err)
	}
os.Open() f.Close() はバッファリングしない場合と同じです。
目新しいのは bufio.NewScanner() で、戻値は *bufio.Scanner型 で scanner に代入しました。
scanner.Scan() は変数を渡していませんが、1行分読み込んで次の scanner.Text() で返します。
scanner.Text() 名前からも判るようにバイト列ではなくテキストを返します(行末の改行は取り除かれます)
scanner.Scan() はファイルの終わりまで来ると nil を返します(ので for ループを抜けます)。
エラーが有った場合も scanner.Scan() は nil を返すので scanner.Err() を使ってエラーが出ていたか調べています。
バッファリング

バッファリングは、複数の機器やプログラムの間でデーターを転送する場合に、予め用意した記憶領域(バッファー)に送受信データを一時的に保存する手法で

  1. 処理速度や転送速度の差を補う
  2. 通信の減速や中断に備える

ことを目的としています。

バッファリングすることで、

  1. 動作速度の向上
  2. 入出力要求の回数の削減
  3. ジッターの防止あるいは低減

などが図れます。

バッファーにある未処理のデーターを強制的に次の処理に送り出すことを、フラッシュ(flash)と呼びます。


ファイルの作成と書込み編集

"os" パッケージで定義された関数 os.Create 関数でファイルを作成できます。 os.Create 関数を使用する際、この関数は戻り値を2つ返します(多値返却)。 os.Create 関数の第1戻値は正常動作時の戻値(*os.File)、第2戻値はエラー時に nil 以外を返します。

ファイルの作成と書込み
package main

import (
	"log"
	"os"
)

func main() {
	f, err := os.Create("file-create-test.txt")
	if err != nil {
		log.Fatal(err)
	}
	defer func() {
		err := f.Close()
		if err != nil {
			log.Fatal(err)
		}
	}()

	if _, err := f.Write([]byte("TEST TEXT\n")); err != nil {
		log.Fatal(err)
	}
}
解説
	if _, err := f.Write([]byte("TEST TEXT\n")); err != nil {
fp.Write は[]byte(byte型のスライス)を引数にするので、明示的に型変換しています。
遅延実行
	defer func() {
		err := f.Close()
		if err != nil {
			log.Fatal(err)
		}
	}()
defer は遅延実行で、関数スコープを抜けるときに実行する処理を登録します。

脚註編集

  1. ^ goroutine には、スレッドにありがちな kill や join はなく、チェンネルなどの通信を使って実現します。このため go 文には値がありません(スレッドならスレッドIDを返すところ)。