Go言語ファイル処理

ファイル処理はGo言語で最も一般的な操作の1つです——設定の読み込み、ログの書き込み、データの永続化はどれもこれなしでは成り立ちません。Goの標準ライブラリは、単発の読み書きからストリーミング処理までのさまざまなシーンをカバーする、簡潔で強力なファイルI/Oインターフェースを提供しています。

ファイル処理に関連する主要パッケージは5つあり、それぞれ役割が異なります:

Goファイル処理の主要パッケージ os コア:作成、読み書き、削除、権限 io 汎用 Reader/Writer インターフェース bufio バッファ付き読み書き、パフォーマンス最適化 filepath クロスプラットフォームのパス処理 ioutil ⚠ Go 1.16 で非推奨 → os / io 典型的な使用フロー os.Open() bufio.Scanner データの処理 defer Close()

バージョンに関する注意:ioutilパッケージはGo 1.16で非推奨となり、その機能はos(例:os.ReadFile、os.WriteFile)とio(例:io.ReadAll)に移行しました。新しいコードでは直接osとioパッケージを使用します。


1. ファイル作成

os.Create新しいファイルを作成します。ファイルが既に存在する場合、は切り詰められます(内容が空になります)。返されたファイルオブジェクトは、システムリソースを解放するために閉じる必要があります:

例

package main

import (
        "log"
        "os"
)

func main() {
        // os.Create はファイルを作成する(既に存在する場合は空にする)
        file, err := os.Create("test.txt")
        if err != nil {
                log.Fatal(err)
        }
        defer file.Close() // defer で関数終了時にファイルを確実に閉じる

        // 内容を書き込んで作成成功を確認する
        file.WriteString("ファイル作成成功\n")
        log.Println("ファイル作成成功")
}

defer file.Close()はGoのファイル操作におけるベストプラクティスです。deferは、後続のコードでpanicが発生しても、ファイルが正しく閉じられることを保証し、ファイルディスクリプタのリークを防ぎます。


2. ファイルのオープンとクローズ

osパッケージはファイルを開く3つの方法を提供しており、さまざまなシーンに適しています:

関数 モード 説明
os.Open(name) 読み取り専用 最も簡単な方法で、読み取り専用で開く
os.Create(name) 読み書き + 作成/切り詰め 新しいファイルを作成するか、既存のファイルを空にします
os.OpenFile(name, flag, perm) カスタム 読み書き、追記、作成などのフラグを指定できます

例

package main

import (
        "fmt"
        "os"
)

func main() {
        // 方法1:読み取り専用で開く
        file, err := os.Open("example.txt")
        if err != nil {
                fmt.Println("オープン失敗:", err)
                return
        }
        defer file.Close()
        fmt.Println("ファイルのオープンに成功")

        // 方法2:OpenFile カスタムモード
        // os.O_WRONLY 書き込み専用
        // os.O_CREATE 存在しない場合は作成
        // os.O_APPEND 追記モード
        // os.O_TRUNC 存在する場合は切り詰め
        f, err := os.OpenFile("log.txt",
                os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
        if err != nil {
                fmt.Println("オープン失敗:", err)
                return
        }
        defer f.Close()
        f.WriteString("ログを1行追記\n")
}

OpenFile フラグの説明

フラグ 説明
os.O_RDONLY 読み取り専用(デフォルト)
os.O_WRONLY 書き込み専用
os.O_RDWR 読み書き
os.O_APPEND 追記モード。書き込んだ内容はファイル末尾に追加されます
os.O_CREATE ファイルが存在しない場合は作成します
os.O_TRUNC 開いたときにファイルの内容を空にします
os.O_EXCL CREATE と組み合わせると、ファイルが既に存在する場合にエラーを報告します

3. ファイル読み込み

Goでは、主に3つのファイル読み込み方法が提供されています。ファイルサイズと処理ニーズに応じて選択します:

3つのファイル読み込み方法 一括読み込み os.ReadFile("file.txt") 小ファイルに適しています(設定、JSONなど) 自動で開閉するため、最も簡潔です 行ごとの読み込み bufio.NewScanner(file) 大ファイルに適しています(ログ、CSVなど) メモリに優しく、行単位で処理します ストリーム読み込み io.ReadAll(reader) ネットワークレスポンスや開いているファイルに適しています 汎用 Reader インターフェース

3.1 一括読み込み(小ファイル推奨)

os.ReadFileは最も簡潔な方法です——ファイルのオープン、読み込み、クローズを自動で一度に行います:

例

package main

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

func main() {
        // ファイル全体を一括読み込み(Go 1.16+)
        data, err := os.ReadFile("config.json")
        if err != nil {
                log.Fatal(err)
        }

        fmt.Println(string(data))
}

3.2 行ごとの読み込み(大ファイル推奨)

大ファイルの場合は、bufio.Scannerを使って行単位で処理し、一度にメモリへ読み込むのを避けます:

例

package main

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

func main() {
        file, err := os.Open("large-file.log")
        if err != nil {
                log.Fatal(err)
        }
        defer file.Close()

        // 行ごとにスキャン
        scanner := bufio.NewScanner(file)
        lineNum := 0
        for scanner.Scan() {
                lineNum++
                line := scanner.Text() // 現在の行の内容を取得
                fmt.Printf("%d行目: %s\n", lineNum, line)
        }

        // スキャン中にエラーが発生したか確認する
        if err := scanner.Err(); err != nil {
                log.Fatal("読み込みエラー:", err)
        }
}

注意:bufio.Scannerデフォルトの最大行長は64KBです。ファイルに超長行がある場合は、呼び出すScan()前に設定しscanner.Buffer(make([]byte, 0), maxSize)てバッファを拡大します。

3.3 io.ReadAll を使った読み込み

既に開いているio.Reader(例:ネットワークレスポンス、既に開いているファイル)がある場合は、io.ReadAll:

例

package main

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

func main() {
        file, err := os.Open("example.txt")
        if err != nil {
                log.Fatal(err)
        }
        defer file.Close()

        // Reader からすべてのデータを読み込む
        data, err := io.ReadAll(file)
        if err != nil {
                log.Fatal(err)
        }

        fmt.Println(string(data))
}

4. ファイル書き込み

Goには、シンプルな一括書き込みから高性能なバッファ書き込みまで、複数の書き込み方法が用意されています:

4.1 一括書き込み

os.WriteFileデータをファイルに一括で書き込みます(既存の内容は上書きされます):

例

package main

import (
        "log"
        "os"
)

func main() {
        content := []byte("Hello, Go!\nこれは2行目\n")

        // 0644: 所有者は読み書き、その他のユーザーは読み取り専用
        err := os.WriteFile("output.txt", content, 0644)
        if err != nil {
                log.Fatal(err)
        }
        log.Println("書き込み成功")
}

4.2 File オブジェクトを使った書き込み

ファイルオブジェクト経由で書き込むことで、複数回に分けて内容を書き込めます:

例

package main

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

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

        // 方法1:文字列を書き込む
        file.WriteString("文字列を直接書き込み\n")

        // 方法2:バイトスライスを書き込む
        data := []byte("バイトスライスを書き込み\n")
        file.Write(data)

        // 方法3:フォーマット書き込み
        fmt.Fprintf(file, "フォーマット書き込み: %d + %d = %d\n", 3, 4, 7)
}

4.3 バッファ書き込み(大量データ推奨)

bufio.Writerは、データをまずメモリ内のバッファに書き込み、十分に溜まったらまとめてディスクに書き込むため、I/O回数を大幅に削減します。終了前に必ず呼び出してください。Flush():

例

package main

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

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

        // バッファ付きライターを作成
        writer := bufio.NewWriter(file)

        // 複数行を書き込む(まずバッファに入り、すぐにはディスクに書き込まれない)
        for i := 0; i < 1000; i++ {
                writer.WriteString("これは第 + itoa(i) + " 行\n")
        }

        // Flush はバッファに残っているデータをファイルに書き込む
        if err := writer.Flush(); err != nil {
                log.Fatal("バッファのフラッシュ失敗:", err)
        }
        log.Println("バッファ書き込み完了")
}

// int を string に変換する簡単なヘルパー関数
func itoa(n int) string {
        return fmt.Sprintf("%d", n)
}

5. ファイルへの追記書き込み

使用os.O_APPENDフラグで、既存のデータを上書きせずにファイル末尾に内容を追記します:

例

package main

import (
        "log"
        "os"
        "time"
)

func main() {
        // O_APPEND: 追記モード
        // O_CREATE: 存在しない場合は作成
        // O_WRONLY: 書き込み専用
        file, err := os.OpenFile("app.log",
                os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
        if err != nil {
                log.Fatal(err)
        }
        defer file.Close()

        // 実行するたびに追記され、上書きされません
        timestamp := time.Now().Format("2006-01-02 15:04:05")
        file.WriteString("[" + timestamp + ] アプリ起動\n")
}

比較:os.Createは既存の内容を空にします;os.OpenFileと組み合わせるとO_APPEND内容が追記されます。ログのシナリオでは必ず追記モードを使用してください。


6. ファイルの削除と名前変更

例

package main

import (
        "log"
        "os"
)

func main() {
        // ファイルの名前変更 / 移動
        err := os.Rename("old.txt", "new.txt")
        if err != nil {
                log.Fatal("名前変更に失敗:", err)
        }
        log.Println("名前変更に成功")

        // ファイルの削除
        err = os.Remove("temp.txt")
        if err != nil {
                log.Fatal("削除に失敗:", err)
        }
        log.Println("削除に成功")
}

7. ファイル情報とチェック

7.1 ファイル情報の取得

例

package main

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

func main() {
        info, err := os.Stat("test.txt")
        if err != nil {
                log.Fatal(err)
        }

        fmt.Println("ファイル名: ", info.Name())
        fmt.Println("サイズ: ", info.Size(), "バイト")
        fmt.Println("権限: ", info.Mode())
        fmt.Println("変更時間: ", info.ModTime())
        fmt.Println("ディレクトリか: ", info.IsDir())
}

7.2 ファイルの存在確認

Goには専用の「ファイル存在」関数はなく、os.Stat + os.IsNotExistで判断します:

例

package main

import (
        "fmt"
        "os"
)

// fileExists はファイルの存在を確認する
func fileExists(path string) bool {
        _, err := os.Stat(path)
        return !os.IsNotExist(err)
}

func main() {
        if fileExists("test.txt") {
                fmt.Println("ファイルが存在します")
        } else {
                fmt.Println("ファイルが存在しません")
        }
}

8. ディレクトリ操作

例

package main

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

func main() {
        // 単一のディレクトリを作成
        err := os.Mkdir("newdir", 0755)
        if err != nil {
                log.Fatal(err)
        }

        // 再帰的に複数階層のディレクトリを作成(親ディレクトリが存在しなくても自動的に作成)
        err = os.MkdirAll("path/to/deep/dir", 0755)
        if err != nil {
                log.Fatal(err)
        }

        // ディレクトリの内容を読み取る
        entries, err := os.ReadDir(".")
        if err != nil {
                log.Fatal(err)
        }
        for _, entry := range entries {
                mark := "&#x1f4c4;"
                if entry.IsDir() {
                        mark = "&#x1f4c1;"
                }
                fmt.Printf("%s %s\n", mark, entry.Name())
        }

        // 空のディレクトリを削除
        os.Remove("newdir")

        // ディレクトリとそのすべての内容を再帰的に削除(⚠ 注意して使用)
        os.RemoveAll("path")
}

9. 高度な操作

9.1 ファイルコピー

を使用してio.Copy2つのファイル間でデータを効率的にコピーします:

例

package main

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

// copyFile はファイルをコピーする汎用関数
func copyFile(src, dst string) (int64, error) {
        srcFile, err := os.Open(src)
        if err != nil {
                return 0, err
        }
        defer srcFile.Close()

        dstFile, err := os.Create(dst)
        if err != nil {
                return 0, err
        }
        defer dstFile.Close()

        // io.Copy はバッファを自動管理し、効率的にコピーする
        return io.Copy(dstFile, srcFile)
}

func main() {
        n, err := copyFile("source.txt", "destination.txt")
        if err != nil {
                log.Fatal(err)
        }
        log.Printf(コピー完了、合計 %d バイト, n)
}

9.2 一時ファイルとディレクトリ

例

package main

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

func main() {
        // 一時ファイルを作成(自動命名、プレフィックスは "app-")
        tmpFile, err := os.CreateTemp("", "app-*.txt")
        if err != nil {
                log.Fatal(err)
        }
        defer os.Remove(tmpFile.Name()) // 使用後はクリーンアップ
        fmt.Println("一時ファイル:", tmpFile.Name())

        // 一時ディレクトリを作成
        tmpDir, err := os.MkdirTemp("", "app-*")
        if err != nil {
                log.Fatal(err)
        }
        defer os.RemoveAll(tmpDir) // 使用後はクリーンアップ
        fmt.Println("一時ディレクトリ:", tmpDir)
}

9.3 ディレクトリの再帰走査

filepath.Walkディレクトリツリー内のすべてのファイルとサブディレクトリを再帰的に走査できます:

例

package main

import (
        "fmt"
        "log"
        "os"
        "path/filepath"
)

func main() {
        root := "." // 現在のディレクトリから走査を開始

        err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
                if err != nil {
                        return err // エラーが発生した場合はスキップまたは返すことを選択できる
                }

                // パスとサイズを出力
                size := ""
                if !info.IsDir() {
                        size = fmt.Sprintf(" (%d bytes)", info.Size())
                }
                fmt.Println(path + size)
                return nil
        })

        if err != nil {
                log.Fatal(err)
        }
}

9.4 クロスプラットフォームのパス結合

パスを文字列連結で作成しないでください(例:dir + "/" + file)、filepath.Joinを使用してクロスプラットフォームの互換性を確保してください:

// 错误:Windows 上路径分隔符是 \
path := "dir" + "/" + "file.txt"

// 正确:自动适配当前操作系统
path := filepath.Join("dir", "file.txt")

シーン別早見表

シナリオ 推奨方法 主要コード
小さいファイルの読み取り os.ReadFile data, err := os.ReadFile("file.txt")
大容量ファイルを1行ずつ読み取り bufio.Scanner scanner := bufio.NewScanner(file)
小さいファイルの書き込み os.WriteFile os.WriteFile("f.txt", data, 0644)
大量データの書き込み bufio.Writer writer := bufio.NewWriter(file)
ログの追記 os.OpenFile + O_APPEND os.OpenFile("log", os.O_APPEND|os.O_WRONLY, 0644)
ファイルコピー io.Copy io.Copy(dst, src)
ディレクトリの走査 filepath.Walk filepath.Walk(".", callback)
パス結合 filepath.Join filepath.Join("dir", "file.txt")
ファイル存在の確認 os.Stat + os.IsNotExist _, err := os.Stat(path)
一時ファイルの作成 os.CreateTemp os.CreateTemp("", "prefix-*.txt")

ファイル権限の説明:0644所有者は読み書き可能(6=4+2)、グループとその他のユーザーは読み取りのみ(4)を意味します。ディレクトリでは通常、0755(実行権限が追加されています。これがないとディレクトリに入れません)。

その他の拡張