【Go】File operations (ファイル操作)

# Goでプログラミングの基礎を学ぶシリーズ
スクールでは教えてくれないプログラミングの基礎、**データ構造とアルゴリズム**をGoで学んでいくシリーズです。
そのデータ構造がどのようなものであるかは、理解を助けてくれるサイトを紹介しつつ、簡単に説明に留めさせていただきます。(ご自身でも調べてみてください！)
筆者自身、Exerciseに取り組みながら理解を深めていったので、コードの解説を中心に記事を書いていきたいと思います。

|  | タイトル |
|------|---------|
| #0 | [はじめに (環境構築と勉強方法)](https://qiita.com/miyyuk/items/0b959774a2e436f087c0) |
| #1 | [Pointer, Array, String (ポインタと配列と文字列と)](https://qiita.com/miyyuk/items/75fad6c02b76354fa49d) |
| #2 | File operations (ファイル操作) **☜ here** |
| #3 | [Linked List (連結リスト)](https://qiita.com/miyyuk/items/af9d3352f89f11365dc4)|
| #4 | [Stack & Queue (スタックとキュー)](https://qiita.com/miyyuk/items/a188def21c31367330f3) |
| #5 | [Search algorithms (探索アルゴリズム)](https://qiita.com/miyyuk/items/91920dee5151e9c4c43e) |
| #6 | [Tree (木構造)](https://qiita.com/miyyuk/items/a11c22a62bb95c1996ba) |
| #7 | [Sorting algorithms (ソートアルゴリズム)](https://qiita.com/miyyuk/items/85c2ed8176e9bdf7e677)|
| #8 | [String pattern matching algorithms (文字列探索アルゴリズム)](https://qiita.com/miyyuk/items/bce37f7f8c24b48cfd3f) |

今回は**#2 File operations (ファイル操作)**です。

# ファイル操作とは
読んで字の如く、ファイルを操作することです。
パソコンを触ったことがある人なら、マウスでファイルをクリックして開き、ファイルを編集して、ファイルを閉じたことが、一度はあると思います。
それです。
代表的な操作は以下の通りです。

| | |
|----|------------|
|Open|ファイルを開く|
|Read|ファイルを読み取る|
|Write|ファイルに書き込む|
|Close|ファイルを閉じる|

Goのファイル操作に関して、英語ですが以下のサイトが参考になると思います。

https://www.geeksforgeeks.org/how-to-read-and-write-the-files-in-golang/

コードを見たほうがわかりやすいと思いますので、早速Exerciseです。

### 💻 Exercise
事前に`read.txt`を作成し、適当な言葉を記述しておきます。
1. `write.txt`というファイルを作成しましょう。
2. `read.txt`に記述された言葉を一文字ずつ読み出しましょう。
3. `read.txt`から読み出した文字を`write.txt`に書き出しましょう。

☟ 解答例

▼ 事前に作成したファイル

```read.txt
some
writes

```
▼ プログラム

```go
package main

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

func check(e error) {
	if e != nil {
		panic(e)
	}
}

func main() {
	f, err := os.Open("read.txt")  // read.txtを開く
	check(err)

	defer f.Close() // read.txtを閉じる（f.Close()で詳説）

	fw, err := os.Create("write.txt") // write.txtという名前のファイルを作成
	check(err)

	defer fw.Close() // write.txtを閉じる

	b := make([]byte, 5) // 5byteの容量を持ったSlice

	// read.txtの中身を5byteずつ読み取り、読み取った分をwrite.txtに書き込む
	// read.txtに読み取るものがなくなるまで、5byteずつの読み書きを続ける
	for {
		n, err := f.Read(b) // read.txtの中身を5byte分読み取る
		if err != nil {
			if err != io.EOF {
				check(err)
			}
			break // read.txtに読み取るものが無くなったらbreakする(io.EOFで詳説)
		}
		fw.Write(b[:n]) // 読み取った分をwrite.txtに書き込む

		// log
		fmt.Println(n, b[:n])
		fmt.Println(" ", fmt.Sprintf("%c", b[:n]))
	}
}

// >log
// 5 [115 111 109 101 10]
//   [s o m e
// ]
// 5 [119 114 105 116 101]
//   [w r i t e]
// 2 [115 10]
//   [s
// ]

```
▼ 作成されたファイル

```write.txt
some
writes

```
`read.txt`から5byteずつ読み取って、それを`write.txt`に書き出します。
logをご覧いただければわかると思いますが、改行も1文字として扱われます。

#### f.Close()
`defer`は、[A Tour of Go](https://go-tour-jp.appspot.com/flowcontrol/12)によると、
>defer ステートメントは、 defer へ渡した関数の実行を、呼び出し元の関数の終わり(returnする)まで遅延させるものです。
defer へ渡した関数の引数は、すぐに評価されますが、その関数自体は呼び出し元の関数がreturnするまで実行されません。

つまり、Exerciseのコードでは、`write.txt`への書き込みが完了した後に、`read.txt`と`write.txt`を`Close()`しています。

なぜわざわざファイルを閉じる必要があるのかは、メモリの解放に関わっています。
ファイルはOpenするときにメモリを確保し、Closeするときに確保したメモリを解放します。
Closeしないと、使わなくなったメモリまでもが確保されたままになり、メモリの空きがなくなっていきます。
そのため、使わなくなるタイミングでCloseしてメモリを解放しておく必要があるのです。
参考: [【C言語】fopenしたならfcloseを忘れずにしっかりやりなさい。しないとどうなるのよ。](https://creepfablic.site/2019/09/01/clangu-fopen-fclose/)

#### io.EOF
EOFは、**E**nd **O**f **F**ileの略であり、ファイルの終わりを意味します。
参考: [Readers(A Tour of Go)](https://go-tour-jp.appspot.com/methods/21)
解答例のコードは以下のような流れで、読み取りを行っています。

```go
    for {
        n, err := f.Read(b)    // read.txtファイルを5byteずつ読み取る
        if err != nil {        // 読み取りでエラーがあった場合
            if err != io.EOF { // そのエラーがEOFでなかった場合
                check(err)     // panic!
            }
            break // そのエラーがEOFの場合、読み取るものがないということなので、breakする
        }
        // 省略
    }
```

では、ファイルの中身の読み取り方と、読み取るものを少し変えてみましょう。

### 💻 Exercise
一つ前のExerciseでは一文字ずつ読み取りましたが、次は一行ずつ読み取りましょう。
ついでに、行数も出力しましょう。
例えば、事前に用意したファイルが

```text.txt
This is sample file.
Have a nice day!
```
の場合、出力は以下のようになります。

```
1 This is sample file.
2 Have a nice day!
```

☟ 解答例

```go
package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
)

func check(e error) {
	if e != nil {
		if e != io.EOF {
			panic(e)
		}
	}
}

func main() {
	f, err := os.Open("text.txt")
	check(err)

	defer f.Close()

	r := bufio.NewReader(f) // Readerを初期化する

	var lines []string // 読み取ったStringを格納しておくSlice
	for {
		l, err := r.ReadString('\n') // 区切り文字である改行コード('\n')まで読み取る

		lines = append(lines, l) // 読み取ったStringをSliceに追加
		if err == io.EOF {
			break
		}
		check(err)
	}

	for i, l := range lines {
		fmt.Printf("%d %s", i+1, l)
	}
}

// >result
// 1 This is sample file.
// 2 Have a nice day!
```

### 💻 Exercise
次は構造体の読み書きをしてみましょう。
名前、電話番号、メールアドレスが記載されたアドレス帳を想定します。
アドレスが記載されたファイルから内容を読み取り、その中身をコピーしたファイルを作成します。
`fread()`でファイルから読み取り、`fwrite()`でファイルに書き込むプログラムを作成しましょう。

☟ 解答例

▼ 事前に作成したファイル

```address.txt
Sakura
090-1234-5678
sakura@example.com

Kaede
080-1234-5678
kaede@example.com

Yuzu
070-1234-5678
yuzu@example.com

```
▼ プログラム

```go
package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
)

type Address struct {
	name             string
	telephone_number string
	email_address    string
}

func check(e error) {
	if e != nil {
		if e != io.EOF {
			panic(e)
		}
	}
}

func fread(file string) []*Address {
	f, err := os.Open(file)
	check(err)

	defer f.Close()

	r := bufio.NewReader(f)
	var data []*Address
	for {
		n, err := r.ReadString('\n') // nameの読み取り
		check(err)
		t, err := r.ReadString('\n') // telephone_numberの読み取り
		check(err)
		e, err := r.ReadString('\n') // email_addressの読み取り
		check(err)

		address := &Address{name: n, telephone_number: t, email_address: e}
		data = append(data, address)

		_, err = r.ReadString('\n')
		if err == io.EOF {
			break
		}
		check(err)
	}

	return data
}

func fwrite(file string, data []*Address) {
	f, err := os.Create(file)
	check(err)

	defer f.Close()

	for _, v := range data {
		f.WriteString(fmt.Sprintf("%s%s%s\n", v.name, v.telephone_number, v.email_address))
	}
}

func main() {
	data := fread("address.txt")
	fwrite("addressCopy.txt", data)
}
```
▼ 作成されたファイル

```addressCopy.txt
Sakura
090-1234-5678
sakura@example.com

Kaede
080-1234-5678
kaede@example.com

Yuzu
070-1234-5678
yuzu@example.com


```
`main()`がかなりすっきりしました。
`fread()`では、読み取るファイル名を引数に取り、読み取った`address`を配列に格納して返しています。
`fwrite()`では、新たに作成するファイル名と`fread()`で返ってきたアドレスの配列を引数に取り、`fwrite()`内で作成されたファイルへの書き込みを行っています。

途中に出てくるポインタ(`[]*Address`とか`&Address{}`)がわからないよ！という方は、前の記事を参考にしてみてください。
参考: [【Go】Pointer, Array, String (ポインタと配列と文字列と)](https://qiita.com/miyyuk/items/75fad6c02b76354fa49d)

# おわりに
Exerciseの解答例はあくまで例なので、もっといい書き方あるよ！という方、ぜひコメントをお寄せください！
説明についても、筆者自身が初心者であるため、ご指摘や補足は大歓迎でございます。

株式会社Link Sportsでは、あらゆるスポーツを楽しむ人たちに送る、チームマネジメントアプリを開発しています。
未経験でも経験豊富なエンジニアの方でも、スポーツ好きなら活躍できる環境があります！
絶賛エンジニア募集中です！
Wantedly ▶︎ https://www.wantedly.com/projects/324177
Green ▶︎ https://www.green-japan.com/job/82003

次回は、データ構造とアルゴリズム**#3 Linked List (連結リスト)**です。