Goでプログラミングの基礎を学ぶシリーズ
スクールでは教えてくれないプログラミングの基礎、データ構造とアルゴリズムをGoで学んでいくシリーズです。
そのデータ構造がどのようなものであるかは、理解を助けてくれるサイトを紹介しつつ、簡単に説明に留めさせていただきます。(ご自身でも調べてみてください!)
筆者自身、Exerciseに取り組みながら理解を深めていったので、コードの解説を中心に記事を書いていきたいと思います。
タイトル | |
---|---|
#0 | はじめに (環境構築と勉強方法) |
#1 | Pointer, Array, String (ポインタと配列と文字列と) |
#2 | File operations (ファイル操作) ☜ here |
#3 | Linked List (連結リスト) |
#4 | Stack & Queue (スタックとキュー) |
#5 | Search algorithms (探索アルゴリズム) |
#6 | Tree (木構造) |
#7 | Sorting algorithms (ソートアルゴリズム) |
#8 | String pattern matching algorithms (文字列探索アルゴリズム) |
今回は**#2 File operations (ファイル操作)**です。
ファイル操作とは
読んで字の如く、ファイルを操作することです。
パソコンを触ったことがある人なら、マウスでファイルをクリックして開き、ファイルを編集して、ファイルを閉じたことが、一度はあると思います。
それです。
代表的な操作は以下の通りです。
Open | ファイルを開く |
Read | ファイルを読み取る |
Write | ファイルに書き込む |
Close | ファイルを閉じる |
Goのファイル操作に関して、英語ですが以下のサイトが参考になると思います。
コードを見たほうがわかりやすいと思いますので、早速Exerciseです。
💻 Exercise
事前にread.txt
を作成し、適当な言葉を記述しておきます。
-
write.txt
というファイルを作成しましょう。 -
read.txt
に記述された言葉を一文字ずつ読み出しましょう。 -
read.txt
から読み出した文字をwrite.txt
に書き出しましょう。
☟ 解答例
▼ 事前に作成したファイル
some
writes
▼ プログラム
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
// ]
▼ 作成されたファイル
some
writes
read.txt
から5byteずつ読み取って、それをwrite.txt
に書き出します。
logをご覧いただければわかると思いますが、改行も1文字として扱われます。
f.Close()
defer
は、A Tour of Goによると、
defer ステートメントは、 defer へ渡した関数の実行を、呼び出し元の関数の終わり(returnする)まで遅延させるものです。
defer へ渡した関数の引数は、すぐに評価されますが、その関数自体は呼び出し元の関数がreturnするまで実行されません。
つまり、Exerciseのコードでは、write.txt
への書き込みが完了した後に、read.txt
とwrite.txt
をClose()
しています。
なぜわざわざファイルを閉じる必要があるのかは、メモリの解放に関わっています。
ファイルはOpenするときにメモリを確保し、Closeするときに確保したメモリを解放します。
Closeしないと、使わなくなったメモリまでもが確保されたままになり、メモリの空きがなくなっていきます。
そのため、使わなくなるタイミングでCloseしてメモリを解放しておく必要があるのです。
参考: 【C言語】fopenしたならfcloseを忘れずにしっかりやりなさい。しないとどうなるのよ。
io.EOF
EOFは、End Of Fileの略であり、ファイルの終わりを意味します。
参考: Readers(A Tour of 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では一文字ずつ読み取りましたが、次は一行ずつ読み取りましょう。
ついでに、行数も出力しましょう。
例えば、事前に用意したファイルが
This is sample file.
Have a nice day!
の場合、出力は以下のようになります。
1 This is sample file.
2 Have a nice day!
☟ 解答例
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()
でファイルに書き込むプログラムを作成しましょう。
☟ 解答例
▼ 事前に作成したファイル
Sakura
090-1234-5678
sakura@example.com
Kaede
080-1234-5678
kaede@example.com
Yuzu
070-1234-5678
yuzu@example.com
▼ プログラム
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)
}
▼ 作成されたファイル
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 (ポインタと配列と文字列と)
おわりに
Exerciseの解答例はあくまで例なので、もっといい書き方あるよ!という方、ぜひコメントをお寄せください!
説明についても、筆者自身が初心者であるため、ご指摘や補足は大歓迎でございます。
株式会社Link Sportsでは、あらゆるスポーツを楽しむ人たちに送る、チームマネジメントアプリを開発しています。
未経験でも経験豊富なエンジニアの方でも、スポーツ好きなら活躍できる環境があります!
絶賛エンジニア募集中です!
Wantedly ▶︎ https://www.wantedly.com/projects/324177
Green ▶︎ https://www.green-japan.com/job/82003
次回は、データ構造とアルゴリズム**#3 Linked List (連結リスト)**です。