コンテンツにスキップ

Go/ファイル入出力

出典: フリー教科書『ウィキブックス(Wikibooks)』
< Go

ファイル入出力

[編集]

プログラム開発において、ファイルの読み書きはデータ永続化や設定管理、ログ記録など多くの場面で必要とされる基本的な操作です。Goでは、シンプルかつ効率的なファイル操作を実現するための豊富な標準パッケージが提供されています。本章では、Goにおけるファイル入出力の基本から応用までを、実際のコード例とともに詳しく解説していきます。

ファイル操作の基本

[編集]

Goでファイルを操作する際には、まずosパッケージを使用してファイルを開く必要があります。ファイルを開くにはos.Open()関数を使用しますが、この関数はファイルを読み取り専用で開きます。ファイル操作が完了したら、リソースリークを防ぐため必ずClose()メソッドを呼び出してファイルを閉じなければなりません。この際、defer文を使用すると、関数の終了時に確実にファイルが閉じられるため便利です。

以下の例では、テキストファイルを開いてその内容を読み込む基本的な方法を示しています。まずos.Open()でファイルを開き、deferでクローズ処理を登録します。そしてio.ReadAll()関数を使用してファイルの内容全体を一度に読み込んでいます。この方法は小さいファイルを扱う場合に適しています。

package main

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

func main() {
	if file, err := os.Open("example.txt"); err != nil {
		log.Fatalf("ファイルを開けませんでした: %v", err)
	} else {
		defer file.Close()

		if content, err := io.ReadAll(file); err != nil {
			log.Fatalf("ファイルの読み込みに失敗しました: %v", err)
		} else {
			fmt.Printf("内容:\n%s", content)
		}
	}
}

ファイルの作成と書き込み

[編集]

新しいファイルを作成するにはos.Create()関数を使用します。この関数は指定した名前のファイルを作成し、書き込み可能な状態で開きます。もし同じ名前のファイルが既に存在する場合、その内容は空になりますので注意が必要です。

ファイルへの書き込みにはいくつかの方法がありますが、最も簡単なのはWriteString()メソッドを使用する方法です。このメソッドは文字列を直接ファイルに書き込むことができます。また、Write()メソッドを使用するとバイトスライスを書き込むことができます。

以下の例では、新しいファイルを作成し、複数の書き込み方法を使用してデータを書き込んでいます。最後にファイルをクローズすることで、書き込みが完了し、変更がディスクに保存されます。

package main

import (
	"log"
	"os"
)

func main() {
	file, err := os.Create("output.txt")
	if err != nil {
		log.Fatalf("ファイルを作成できませんでした: %v", err)
	} else {
		defer file.Close()

		// 文字列を直接書き込む
		if _, err = file.WriteString("Hello, World!\n"); err != nil {
			log.Fatalf("書き込みに失敗しました: %v", err)
		}

		// バイトスライスを書き込む
		data := []byte{65, 66, 67, 68, 69} // A, B, C, D, E
		if _, err = file.Write(data); err != nil {
			log.Fatalf("書き込みに失敗しました: %v", err)
		}

		// 改行を追加
		if _, err = file.Write([]byte("\n")); err != nil {
			log.Fatalf("書き込みに失敗しました: %v", err)
		}
	}
}

効率的なファイル操作

[編集]

Goでは、ファイル操作をより効率的に行うための便利な関数や方法が提供されています。os.ReadFile()os.WriteFile()は、ファイルのオープン、読み書き、クローズを一括で処理する便利関数です。特に小さいファイルを扱う場合に有用です。

一方、大きなファイルを扱う場合やメモリ効率を考慮する必要がある場合は、バッファ付きI/Oを使用すると良いでしょう。bufioパッケージのScannerは、大きなファイルを行単位で効率的に処理するのに適しています。また、bufio.Readerやbufio.Writerを使用すると、小さな読み書きをまとめて処理できるため、パフォーマンスが向上します。

以下の例では、これらの方法を実際に使用しています。最初にos.ReadFile()で簡単にファイル全体を読み込み、次にbufio.Scannerを使用して大きなファイルを行単位で処理する方法を示しています。最後に、bufio.Writerを使用してバッファリングされた書き込みを行う方法を紹介しています。

package main

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

func main() {
	// 簡単なファイル読み込み
	if content, err := os.ReadFile("input.txt"); err != nil {
		log.Fatalf("ファイルの読み込みに失敗しました: %v", err)
	} else {
		fmt.Printf("内容:\n%s\n", content)
	}

	// 大きなファイルを行単位で処理
	if file, err := os.Open("largefile.txt"); err != nil {
		log.Fatalf("ファイルを開けませんでした: %v", err)
	} else {
		defer file.Close()

		scanner := bufio.NewScanner(file)
		for lineNumber := 1; scanner.Scan(); lineNumber++ {
			fmt.Printf("%d: %s\n", lineNumber, scanner.Text())
		}
		if err := scanner.Err(); err != nil {
			log.Fatalf("ファイルのスキャン中にエラーが発生しました: %v", err)
		}
	}

	// バッファ付き書き込み
	if outputFile, err := os.Create("buffered_output.txt"); err != nil {
		log.Fatalf("ファイルを作成できませんでした: %v", err)
	} else {
		defer outputFile.Close()

		writer := bufio.NewWriter(outputFile)
		for i := 0; i < 100; i++ {
			if _, err := writer.WriteString(fmt.Sprintf("Line %d\n", i)); err != nil {
				log.Fatalf("書き込みに失敗しました: %v", err)
			}
		}
		writer.Flush() // バッファの内容を確実にファイルに書き込む
	}
}

バイナリファイルの操作

[編集]

Goはバイナリデータの操作も簡単に行えます。バイナリファイルを読み書きする場合、基本的にはテキストファイルと同様の方法を使用しますが、データの解釈方法が異なります。encoding/binaryパッケージを使用すると、構造化されたバイナリデータを簡単に読み書きできます。

以下の例では、バイナリデータの書き込みと読み込みを行っています。まず独自のバイナリデータを作成してファイルに書き込み、次にそのファイルを読み込んでデータを再構築しています。このような方法は、独自のバイナリフォーマットを扱う際や、データをコンパクトに保存する必要がある場合に役立ちます。

package main

import (
	"encoding/binary"
	"fmt"
	"log"
	"os"
)

type Point struct {
	X, Y   int32
	Active bool
}

func main() {
	// バイナリデータの書き込み
	if file, err := os.Create("points.bin"); err != nil {
		log.Fatalf("ファイルを作成できませんでした: %v", err)
	} else {
		defer file.Close()

		points := []Point{
			{10, 20, true},
			{30, 40, false},
			{50, 60, true},
		}

		for _, p := range points {
			err := binary.Write(file, binary.LittleEndian, &p)
			if err != nil {
				log.Fatalf("バイナリ書き込みに失敗しました: %v", err)
			}
		}
	}

	// バイナリデータの読み込み
	if file, err := os.Open("points.bin"); err != nil {
		log.Fatalf("ファイルを開けませんでした: %v", err)
	} else {
		defer file.Close()

		var readPoints []Point
		for {
			var p Point
			if err := binary.Read(file, binary.LittleEndian, &p); err != nil {
				break // ファイル終端またはエラー
			}
			readPoints = append(readPoints, p)
		}

		fmt.Println("読み込んだポイントデータ:")
		for i, p := range readPoints {
			fmt.Printf("%d: X=%d, Y=%d, Active=%t\n", i+1, p.X, p.Y, p.Active)
		}
	}
}

高度なファイル操作

[編集]

Goでは、ファイルのメタ情報の取得や、ファイルの存在チェック、権限の変更など、さまざまな高度なファイル操作も可能です。osパッケージのStat()関数を使用すると、ファイルサイズ、修正日時、パーミッションなどの情報を取得できます。

また、ファイルの移動や削除、ディレクトリの操作なども簡単に行えます。以下の例では、これらの高度な操作を実演しています。ファイルの情報取得、存在チェック、権限変更、そしてディレクトリ操作など、実際のアプリケーション開発で役立つ技術を網羅しています。

package main

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

func main() {
	// ファイル情報の取得
	if fileInfo, err := os.Stat("example.txt"); err != nil {
		log.Fatalf("ファイル情報の取得に失敗しました: %v", err)
	} else {

		fmt.Println("ファイル名:", fileInfo.Name())
		fmt.Println("サイズ:", fileInfo.Size(), "バイト")
		fmt.Println("パーミッション:", fileInfo.Mode())
		fmt.Println("最終変更日時:", fileInfo.ModTime())
		fmt.Println("ディレクトリかどうか:", fileInfo.IsDir())
	}
	// ファイルの存在チェック
	if _, err := os.Stat("nonexistent.txt"); os.IsNotExist(err) {
		fmt.Println("ファイルは存在しません")
	}

	// ファイルの権限変更
	if err := os.Chmod("example.txt", 0644); err != nil {
		log.Fatalf("パーミッションの変更に失敗しました: %v", err)
	}

	// ファイルの名前変更/移動
	if err := os.Rename("oldname.txt", "newname.txt"); err != nil {
		log.Fatalf("ファイルの名前変更に失敗しました: %v", err)
	}

	// ファイルの削除
	if err := os.Remove("unneeded.txt"); err != nil {
		log.Fatalf("ファイルの削除に失敗しました: %v", err)
	}

	// ディレクトリの作成
	if err := os.Mkdir("newdir", 0755); err != nil {
		log.Fatalf("ディレクトリの作成に失敗しました: %v", err)
	}

	// ディレクトリ内のファイル一覧
	if files, err := os.ReadDir("."); err != nil {
		log.Fatalf("ディレクトリの読み込みに失敗しました: %v", err)
	} else {
		fmt.Println("カレントディレクトリの内容:")
		for _, file := range files {
			if info, err := file.Info(); err != nil {
				log.Fatalf("ファイル情報の取得に失敗しました: %v", err)
			} else {
				fmt.Printf("%s (%d bytes, %v)\n", file.Name(), info.Size(), info.ModTime())
			}
		}
	}
	// ディレクトリの削除
	if err := os.RemoveAll("olddir"); err != nil {
		log.Fatalf("ディレクトリの削除に失敗しました: %v", err)
	}
}

まとめ

[編集]

本章では、Goにおけるファイル入出力の基本から応用までを詳細に解説しました。ファイルの読み書きから始まり、効率的な操作方法、バイナリデータの扱い方、そして高度なファイル操作まで、実際のコード例とともに学びました。Goのファイル操作はシンプルなAPIで設計されており、他の言語と比較しても非常に直感的に使用できることがわかります。

特に、deferを使ったリソース管理や、bufioパッケージによるバッファリング、encoding/binaryパッケージによるバイナリデータの扱いなどは、Goならではの特徴的な実装方法です。これらの技術を適切に使い分けることで、パフォーマンスの良い堅牢なファイル操作を実現できます。

実際のプロジェクトでは、エラーハンドリングを丁寧に行い、リソースリークが発生しないよう注意しながら、本章で学んだ技術を応用していくと良いでしょう。ファイル操作は多くのアプリケーションで必要とされる基本的な技術であり、しっかりと理解しておくことが重要です。