はじめに
「学生ひよこ界隈が送るGo/Javaで実現する「はじめてのバックエンド」Advent Calendar 2025」12日目の記事は、バックエンド開発において使用する機会も多い、サーバー上でのファイル操作についてです。
バックエンド開発は、API上でDB処理を行ってJSONを返すだけではありません。
- 「ユーザーがアップロードしたCSVファイルを処理してDBに一括登録したい」
- 「システムログをファイルに書き出したい」
- 「その他ファイルを読み込みたい」
など、ファイルを扱う場面は数多くあります。
この記事では、Goの強力な標準ライブラリだけを使って、基本的なファイル操作を行う方法をハンズオン形式で解説します。
今回は、テキストファイル(.txt)の読み書きと、より構造化されたデータ形式であるCSVファイルの取り扱いについて解説します。
- Goでサーバーサイドのファイル操作を学びたい方
- バッチ処理やデータインポート/エクスポート機能の実装に興味がある方
- Goの
osパッケージやencoding/csvパッケージの基本的な使い方を知りたい方
この記事で、Goの標準ライブラリを使って、テキストファイルとCSVファイルの基本的な読み書きを行って、バックエンド開発をさらにレベルアップできるように頑張りましょう!
なぜバックエンドでファイル操作が必要なのか?
Webアプリケーションにおいて、ファイル操作は様々な場面で登場します。
- データインポート/エクスポート: ユーザーが商品一覧をCSV形式でアップロードしたり、注文履歴をCSVでダウンロードしたりする機能。
- バッチ処理: 夜間にログファイルを解析して、統計レポートを生成するような、リクエストを伴わない処理。
- 設定ファイルの読み込み: アプリケーションの挙動を変えるための設定を、JSONやYAMLファイルから読み込む。
- 画像や動画のアップロード/ダウンロード: ユーザーが投稿したメディアファイルをサーバーの特定の場所に保存する。
これらの処理を実装するためには、サーバーサイドでのファイルI/O(入力/出力)の知識が不可欠です。
テキストファイル操作の基本
Goのファイル操作は、主に標準ライブラリのosパッケージが中心となります。
このパッケージは、OSの機能と対話するための、プラットフォームに依存しないインターフェースを提供します。
簡単なテキストファイルの書き込み
少量のテキストデータをファイルに書き込む場合の簡単な例を以下に示します。
go mod initを行った状態でmain.goに以下のコードを書いてみます。
package main
import (
"fmt"
"log"
"os"
)
func main() {
content := []byte("Hello, Go File I/O!\nここは新しい行です")
// "hello.txt"というファイルに、contentの内容を書き込む
// 0666 はファイルのパーミッション(所有者、グループ、その他ユーザーの読み書き権限)
err := os.WriteFile("hello.txt", content, 0666)
if err != nil {
log.Fatal(err)
}
fmt.Println("ファイルの作成に成功しました")
}
このコードを実行すると、hello.txtというファイルが作成され、指定した内容が書き込まれます。
os.WriteFileは、ファイルの作成、書き込み、クローズまでを一度に行ってくれる便利な関数です。
簡単なテキストファイルの読み込み
ファイルを丸ごと読み込むには、os.ReadFile関数が便利です。
以下に使用例を示します。
package main
import (
"fmt"
"log"
"os"
)
func main() {
// "hello.txt"を丸ごと読み込む
content, err := os.ReadFile("hello.txt")
if err != nil {
log.Fatal(err)
}
// 読み込んだ内容は[]byte型なので、stringに変換して表示
fmt.Println(string(content))
}
以下のコードを実行すると、先ほど作成したhello.txtの内容がコンソールに出力されると思います。
os.ReadFileは、メモリに収まる程度の比較的小さなファイルを扱う場合に適しています。
大きなファイルを一行ずつ読む
先ほどはメモリに収まる程度の小さなファイルを扱いましたが、数GBにもなるような巨大なログファイルをos.ReadFileで読み込むと、メモリをすべて使い果たしてしまいます。
このような場合は、ファイルを一行ずつ処理すると巨大なファイルも読み込むことができます。
これにはbufioパッケージのScannerを使います。
package main
import (
"bufio"
"fmt"
"log"
"os"
)
func main() {
// ファイルを開く
file, err := os.Open("hello.txt")
if err != nil {
log.Fatal(err)
}
// 関数終了時にファイルを確実に閉じる
defer file.Close()
// ファイルをスキャンするためのスキャナを作成
scanner := bufio.NewScanner(file)
// 一行ずつスキャンして処理
for scanner.Scan() {
// scanner.Text()で現在行のテキストを取得
fmt.Println("Line:", scanner.Text())
}
// スキャン中にエラーが発生したかチェック
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
}
上記のコードを実行すると、コンソールに指定したファイルのすべての内容が出力されていると思います。
この方法はメモリ効率が非常に良く、どんなに大きなファイルでも安定して処理できます。
CSVファイルの読み書き (encoding/csv パッケージ)
CSV (Comma-Separated Values) は、データをカンマ区切りで表現する、非常に一般的なファイル形式です。
Goには、このCSVを扱うためのencoding/csvパッケージが標準で用意されています。
CSVファイルの書き込み
GoのデータをCSVファイルに書き込んでみましょう。
以下に例を示します。
package main
import (
"encoding/csv"
"log"
"os"
)
func main() {
// 書き込むデータ
records := [][]string{
{"name", "email", "age"},
{"James", "hello@example.com", "25"},
{"Mamenz", "mamenz@example.com", "30"},
}
// "users.csv"というファイルを作成
file, err := os.Create("users.csv")
if err != nil {
log.Fatal("ファイルを作成できませんでした", err)
}
defer file.Close()
// CSVライターを作成
writer := csv.NewWriter(file)
defer writer.Flush() // バッファに残っているデータを最後にすべて書き出す
// データを一括で書き込む
err = writer.WriteAll(records)
if err != nil {
log.Fatal("データを書き込むことができませんでした", err)
}
log.Println("CSVファイルが正常に作成されました")
}
csv.NewWriterでライターを作成し、writer.WriteAllでデータを書き込みます。最後に
writer.Flush()はパフォーマンスのためにバッファリングされているデータを、確実にファイルに書き出すために行なっています。
deferで確実に呼び出しましょう。
CSVファイルの読み込み
次に、先ほど作成したusers.csvを読み込んでみましょう。
package main
import (
"encoding/csv"
"fmt"
"io"
"log"
"os"
)
func main() {
// "users.csv"を開く
file, err := os.Open("users.csv")
if err != nil {
log.Fatal("ファイルを開くことができませんでした", err)
}
defer file.Close()
// CSVリーダーを作成
reader := csv.NewReader(file)
// forループで一行ずつ読み込む
for {
record, err := reader.Read()
// ファイルの終端に達したらループを抜ける
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}
// 読み込んだ1行([]string)を表示
fmt.Println(record)
}
}
上記のコードを実行すると、ターミナルは以下のように出力するはずです。
[name email age]
[James hello@example.com 25]
[Mamenz mamenz@example.com 30]
csv.NewReaderでリーダーを作成し、forループの中でreader.Read()を呼び出すことで、一行ずつレコード([]string)を取得できます。
ファイルの終端に達するとio.EOFというエラーが返ってくるので、これをループの終了条件にします。
この方法もメモリ効率が良く、巨大なCSVファイルを扱うのに適しています。
おわりに
この記事では、GoにおけるテキストファイルとCSVファイルの扱いについて学びました。
今回はローカルファイルの読み書きを行いましたが、実際のアプリケーションでは、HTTPリクエスト経由でアップロードされたファイルを処理するケースが頻繁にあります。
次は、net/httpパッケージと連携させ、ユーザーがアップロードしたCSVファイルをサーバーサイドでパースし、その内容をDBに保存する、といったより実践的な機能の実装に挑戦してみたいです。
最後までお読みいただき、ありがとうございました!
この「学生ひよこ界隈が送るGo/Javaで実現する「はじめてのバックエンド」Advent Calendar 2025」では、GoやJavaを使い、APIの作り方、データベースとの接続、テストやDockerといった気になったバックエンド技術の基本を振り返った学びを共有しています。
ぜひ他の記事もチェックして、筆者がこのひとりアドカレを完遂することができるか、確認してみてください(^^)
学生ひよこ界隈が送るGo/Javaで実現する「はじめてのバックエンド」Advent Calendar 2025
それでは、明日の「学生ひよこ界隈が送るGo/Javaで実現する「はじめてのバックエンド」Advent Calendar 2025」の記事もお楽しみに!
参考文献