- 投稿日:2021-01-23T23:43:03+09:00
AtCoder Beginner Contest 189のメモ
前置き
Atcoderをやってみたので、自分用のメモです。
あとから加筆・修正する予定です。問題
https://atcoder.jp/contests/abc189
A
Q_A.gopackage main import ( "fmt" "strings" ) func main() { var C string fmt.Scanf("%s", &C) s := strings.Split(C, "") var flag bool = true var c string = s[0] for i:=1; i<3; i++{ if c != s[i]{ flag = false } } if flag{ fmt.Printf("Won\n") } else { fmt.Printf("Lost\n") } }B
Q_B.gopackage main import ( "fmt" ) func main() { var N int var X int64 fmt.Scanf("%d %d", &N, &X) X = X*100 var alcohole int64 = 0 var v, p int64 var ans int = -1 for i:=0; i<N; i++{ fmt.Scanf("%d %d", &v, &p) alcohole += v * p if alcohole > X { ans = i + 1 break } } fmt.Printf("%d\n", ans) }C
Q_C.gopackage main import ( "fmt" ) func main() { var N int64 fmt.Scanf("%d", &N) A := make([]int64, N) var i int64 for i=0; i<N; i++{ fmt.Scanf("%d", &A[i]) } var mikan int64 = 0 var max, min int64 var l,r int64 for l=0; l<N; l++{ min = A[l] for r=l; r<N; r++{ if A[r] < min { min = A[r] } max = min * (r - l + 1) if max > mikan { mikan = max } } } fmt.Printf("%d\n", mikan) }D
Q_D.gopackage main import ( "fmt" ) func dfs(S []string, N int, i int) int64{ var count int64 = 0 if i == 0{ return 1 } count += dfs(S, N, i-1) if S[i] == "OR"{ var c int64 = 1 for j:=0; j<i; j++{ c = c * 2 } count += c } return count } func main() { var N int fmt.Scanf("%d", &N) S := make([]string, N+1) // AND/OR for i:=1; i<=N; i++{ fmt.Scanf("%s", &S[i]) } var count int64 = 0 count += dfs(S, N, N) fmt.Printf("%d\n", count) }E
覚えてたら後で書きます。
F
覚えてたら後で書きます。
- 投稿日:2021-01-23T21:47:33+09:00
GolangでGoogle Calender APIを使って日本の祝日情報を取得する
祝日の一覧が欲しいなと思った時にGoogle Calendarから取得する方法を調べたので
まとめておきたいと思います。
GolangでGoogle Calender APIを使って取得します。はじめに
google calender api ドキュメント
にgolangで利用する時のクイックスタートが乗っているので、参考に進めていきます。1. 作業ディレクトリの作成
$ mkdir google_calender $ cd google_calender/2. Google APIを利用するためのcredentialsを取得
「Enable the Google Calender API」をクリックして進むとキー情報が書いてあるcredenrial.jsonがダウンロードできるので、作業ディレクトリに置きます。3. 必要なモジュールの追加
ドキュメントに従ってモジュールを追加していきます
$ go get -u google.golang.org/api/calendar/v3 $ go get -u golang.org/x/oauth2/google4. 実行ファイル作成
$ touch quickstart.goquickstart.go
githubにコードがあるので、コピペします。mod初期コマンド
$ go mod init google_calenderビルドします
$ go build5. tokenの取得
この状態で実行するとURLが表示されるので、実行した状態でブラウザにコピペして利用を許可します。
$ go run quickstart.go https://accounts.google.com/o/oauth2/auth...利用許可を進めていくと最後に「このコードをコピーしてアプリケーションに貼り付けてください」と表示されるので、先ほどの実行した状態のコンソールにコピペします。
成功すると「token.json」というファイルが出来ていると思います。
ここまででquickstartを実行する準備が整いました。6. 祝日を取得するようにコードを修正する
quickstartのままだと利用許可したアカウントのカレンダーに登録されている予定を取得するようになっています。
これを祝日が取得できるカレンダーから取得するようにします。quickstartで使用されている
L.109events, err := srv.Events.List("primary")のドキュメントを見ると
golang google api doc
パラメータでcalender idを渡せるようです。
※primaryはアカウントのプライマリカレンダー調べた所、日本の祝日用のcalender idが
ja.japanese#holiday@group.v.calendar.google.comこちらになるようです。
先ほどのL.109のcalender idを書き換えます。
L.109events, err := srv.Events.List("ja.japanese#holiday@group.v.calendar.google.com")7. 実行して祝日のリストを取得する
実行します
$ go run quickstart.go Upcoming events: 建国記念の日 (2021-02-11) 天皇誕生日 (2021-02-23) 春分の日 (2021-03-20) 昭和の日 (2021-04-29) 憲法記念日 (2021-05-03) みどりの日 (2021-05-04) こどもの日 (2021-05-05) 海の日 (2021-07-22) 体育の日 (2021-07-23) 山の日 (2021-08-08)取れました!!!!
quickstartでは現在時刻から10件取得するようになっているので、L.110のパラメータを修正すれば期間や件数を変更できます。おわりに
簡単に祝日の一覧を取得する事ができました!!!!!
- 投稿日:2021-01-23T20:47:47+09:00
go言語学習雑記2
go言語の勉強雑記その2
今回は文法for文
goの言語は他の言語と比べて非常に柔軟性があります。
まずは普通のfor文for i := 0; i < 10; i++ { sum += i }この条件式をいじるとwhile文のような挙動を実現できます
sum1 := 1 for sum < 1000 { sum1 += sum }これで条件式を満たさなくなるまで処理を続けます。
さらにいじると無限ループができます。
for {}if文
if x>0 {}シンプルにifを書くだけ、 但し評価を()でくくる必要はないです
また、その場で変数を代入して比較を行うこともできます
if i:=3; i<4 { // 処理 }switch文
switch文は評価される変数を省略できる。
t := 11 switch { case t < 12: fmt.Println("Good morning!") case t < 17: fmt.Println("Good afternoon.") default: fmt.Println("Good evening.") }なおbreakは自動で挿入されるため書く必要はない
defer文
関数の呼び出し順序を操作する文法。
defer文に実行したい関数を渡すと、呼び出し元の処理が終わった段階で実行される。
func main() { defer fmt.Println("world") fmt.Println("hello") }main関数が終了した時点で defer文の関数が実行されるため出力される文字列は
hello
world
になる。
deferはstackで実行する関数の順番を操作するので、
fmt.Println("counting") for i := 0; i < 10; i++ { defer fmt.Println(i) } fmt.Println("done")上記の場合は
counting 9 8 7 6 5 4 3 2 1 0 doneとなります。
stack は LIFO になるので、入れた順番とは逆の順番で出力されるので、上記のような結果になります。最後に
defer文の使いどきがわからない。下手に使うと可読性が落ちそうなのでチーム開発でのルール決めの難しいそう。
- 投稿日:2021-01-23T19:54:54+09:00
AWS Translateを使ってGo言語で翻訳するサンプル
タイトルの通りですが、公式ドキュメントにサンプルが含まれていなかったので記事にしました。
サンプルソースはGithubにも置いてあります。
https://github.com/yuukimiyo/go-aws-translate-sample簡単なコードですが、73言語(2021年1月現在)の双方向翻訳が可能な簡易ツールとして使えます。
Public Domainですので、コピペなどご自由に。package main import ( "flag" "fmt" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/translate" ) func main() { sourceText := flag.String("text", "これは翻訳のテストです", "source text") sourceLC := flag.String("slc", "ja", "source language code [en|ja|fr]...") targetLC := flag.String("tlc", "en", "target language code [en|ja|fr]...") flag.Parse() sess := session.Must(session.NewSession()) trs := translate.New(sess) result, err := trs.Text(&translate.TextInput{ SourceLanguageCode: aws.String(*sourceLC), TargetLanguageCode: aws.String(*targetLC), Text: aws.String(*sourceText), }) if err != nil { panic(err) } fmt.Printf(*result.TranslatedText) }上記ドキュメントを読み、Text()関数で翻訳を実施するんだ、という事さえわかれば難しいことは何もないです。
手元で試したい人向け
前提
AWSアカウントと、次のポリシーをアタッチしたユーザが必要です。
- TranslateFullAccess
無料期間もありますがAWS Translateは有料サービスです、公式サイトの料金表は必ず確認してください。
AWS TranslateGo言語の実行環境が必要です。
厳密にどのバージョンからかは把握していませんが、開発/確認には次の環境を利用しています。Ubuntu20.04(WSL2)
go1.15.7.linux-amd64環境変数の設定
他言語のAWS SDKでも同じですが、次の環境変数をセットしておきます。
- AWS_REGION
- AWS_ACCESS_KEY_ID
- AWS_SECRET_ACCESS_KEY
bashの場合
export AWS_REGION=ap-northeast-1 export AWS_ACCESS_KEY_ID=<access key id> export AWS_SECRET_ACCESS_KEY=<Secret access key>fish shellの場合
set -x AWS_REGION ap-northeast-1 set -x AWS_ACCESS_KEY_ID <access key id> set -x AWS_SECRET_ACCESS_KEY <Secret access key>Download
git clone git@github.com:yuukimiyo/go-aws-translate-example.git動かしてみる
デフォルトでは、日本語ー>英語の翻訳を行います。
go run main.go --text="翻訳のテストです" > Testing the translation言語を指定したい場合は次のように言語コードを指定します。
(例えば英語ー>日本語の場合)go run main.go --text="This is test of AWS Translate" --slc="en" --tlc="ja" > これは AWS 翻訳のテストです利用可能な言語コードの一覧は次のURLにあります。
Supported Languages and Language Codes
- 投稿日:2021-01-23T15:38:23+09:00
go module のバージョニング
はじめに
自身で作った go package を公開し、別のプロジェクトで利用されるというケースで、バージョンがどのように扱われるのか調べました。
バージョン指定なし
githubに
hello
というリポジトリを作り、そこにパッケージを作成します。go.modmodule github.com/username/hello go 1.15hello.gopackage hello import "fmt" func Hello() { fmt.Println("this is version v0.0.0") }パッケージを呼び出す
パッケージを呼び出すためのクライアントを用意します。
$ go mod init hello_client $ go get -u github.com/username/hello以下のようなgo.modができます。バージョン(v0.0.0)と参照しているコミット(40fe1c06b3a8)がわかります。
go.modmodule hello_client go 1.15 require github.com/username/hello v0.0.0-20210123030850-40fe1c06b3a8 // indirectmain.gopackage main import ( m "github.com/username/hello" ) func main() { m.Hello() }$ go run main.go this is version v0.0.0意図通りに実行できました。
特にバージョンについて考慮しないと、
master
ブランチを参照することがわかりました。例えばこのあとmaster
に更新がありその最新版を取得したい場合は、再びgo get -u github.com/username/hello
を実行することで最新のコミットを参照するようにgo.mod
が更新されます。補足: masterブランチの参照
「
master
ブランチを参照する」というのは多少の語弊があって、正しくは「デフォルトブランチを参照」します。例えばmaster
ブランチを起点にmain
ブランチを作り、main
ブランチをデフォルトブランチにするように設定が変更された場合、go get -u github.com/username/hello
はmain
ブランチを参照します。v1
バージョンを指定しない場合の問題点は、
go get -u
を実行したタイミングによって参照される資材が異なってしまう点です。そこで常に同じ資材を参照できるようにバージョン管理をします。hello.goに変更を加えてプッシュしたら、タグを作成します。
hello.gopackage hello import "fmt" func Hello() { fmt.Println("this is version v1.0.0") }$ git tag -a v1.0.0 -m "Version 1.0.0" $ git push --tags Enumerating objects: 1, done. Counting objects: 100% (1/1), done. Writing objects: 100% (1/1), 167 bytes | 167.00 KiB/s, done. Total 1 (delta 0), reused 0 (delta 0) To https://github.com/username/hello.git * [new tag] v1.0.0 -> v1.0.0パッケージを呼び出す
先ほど作成したクライアントで、パッケージをアップデートします。
$ go get -u github.com/username/hellogo.modは以下のようになります。
go.modmodule hello_client go 1.15 require github.com/username/hello v1.0.0実行してみます。
$ go run main.go this is version v1.0.0タグを打つことで、
master
ブランチではなくv1.0.0
タグを参照するようになりました。
これでmaster
ブランチで開発を続けても、利用者は常にv1.0.0
を参照することができるようになります。v1.1
マイナーバージョンを一つ上げてタグを作成してみます。手順は、v1の時と同様ですので省きます。
パッケージを呼び出す
クライアントも同じようにパッケージをアップデートして実行してみます。
$ go get -u github.com/username/hello $ go run main.go this is version v1.1.0参照先が
v1.1.0
に切り替わったことがわかります。このようにマイナーアップデートやパッチアップデートが行われたら、それらが最新版として認識されることがわかりました。
// プレフィックスとしての
v
は必須のようです。例えば1.2.0
のようなタグの切り方はv1の最新版として認識されませんでした。
v1.0.0
を参照したいときは明示的にバージョンを指定します。go get github.com/username/hello@v1.0.0v2
セマンティックバージョニングに従うと、メジャーバージョンのアップデートは互換性のない変更が行われた時に発生します。つまり、v1とv2は自動的にアップデートされるべきでもないし、別物として扱うべきです。
そのため、v1の時と同じようなバージョンのアップデートの仕方をしてもうまくいきません。
(v0 / v1
とv2 ~
で扱いが変わります。詳しくは、公式ブログを参照:https://blog.golang.org/v2-go-modules)パッケージ側
hello.gopackage hello import "fmt" func Hello() { fmt.Println("this is version v2.0.0") }$ git tag -a v2.0.0 -m "Version 2.0.0"クライアント側
$ go get -u github.com/username/hello $ go run main.go this is version v1.1.0ここで
-u
オプションについて再確認します。メジャーアップデートはされないことがわかります。The -u flag instructs get to update modules providing dependencies of packages named on the command line to use newer minor or patch releases when available.
また、
@2.0.0
とサフィックスを指定してもエラーになります。$ go get github.com/username/hello@v2.0.0 go get github.com/username/hello@v2.0.0: github.com/username/hello@v2.0.0: invalid version: module contains a go.mod file, so major version must be compatible: should be v0 or v1, not v2ということで、v2以降のバージョンを認識させるためにパッケージの
go.mod
を修正して、v2.0.1
を作成してみます。go.modmodule github.com/username/hello/v2 go 1.15$ git tag -a v2.0.1 -m "Version 2.0.1"パッケージを呼び出す
パスが変わりましたので、クライアントも呼び出し方を合わせて変更します。
go get -u github.com/username/hello/v2
go.mod
をみてみると別々のパッケージのように扱われていることがわかります。go.modmodule hello_client go 1.15 require ( github.com/username/hello v1.1.0 github.com/username/hello/v2 v2.0.1 )main.gopackage main import ( m1 "github.com/username/hello" m2 "github.com/username/hello/v2" ) func main() { m1.Hello() m2.Hello() }実行してみると以下のようになります。
$ go run main.go this is version v1.1.0 this is version v2.0.1このようにメジャーバージョンが上がるとパスが別々になり、それぞれのバージョンごとにメンテナンスができるようになります。
- 投稿日:2021-01-23T14:23:01+09:00
Gormの1.9と1.20で失敗したお話
最初に
2021年1月23日時点で、GORMの最新版はv1.20.11ですが、Qiitaや巷の記事はv1.9.xの物が多く(特に日本語)
公式ドキュメントは、基本v1.20がヒットするのでGorm初心者の私は混乱したというお話です。バージョンはよく確認しよう
当たり前のことですが、パッケージのバージョンと参考記事のバージョンはよく確認しましょう。
GORMのバージョン
v1.9.x系 import ( "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/mysql" )v1.20.11 import ( "gorm.io/gorm" "gorm.io/driver/mysql" )1.9系と1.20系でインポートするパッケージが異なります。
ネットの記事をそのままに、「go get github.com/jinzhu/gorm」すると1.9系がインストールされます。変更点
ほかに記事を上げてくださっている方がいらっしゃるので、すべては書きませんが
私が影響を受けたのは、
・「RecordNotFound」がなくなったこと
・DB接続の書き方が変わったこと
・バッチインサートができるようになったこと!(めっちゃうれしい)
がまず影響を受けました。まとめ
Goのパッケージは、現在も開発が活発で進化していますが、
そのぶん技術系記事も更新前の情報がひっかかりやすいです。
基本的なことでお恥ずかしい限りですが、
久しぶりに引っかかったので初心を忘れないために記事にしました。ありがとうございました。
- 投稿日:2021-01-23T14:05:22+09:00
GinでBodyを取得する際の最大サイズについて
概要
ginで不確定なjosnをinterfaceに変換する際、一定サイズを超えると変換できない状態になった。
コード
func SAmple(c *gin.Context) { ~~~ //bodyを取得 buf := make([]byte, 1028) n, _ := c.Request.Body.Read(buf) body := string(buf[0:n]) //bodyをmapに変換 var json_parse map[string]interface{} err := json.Unmarshal([]byte(b), &json_parse) ~~~ }c.Request.Body.Readでバイトスライスにボディを格納してstring形に変換していますが、
jsonのサイズが一定値を超えるとパースできなくなりました。私の環境ではnが2575で上限になりそれ以降のリクエストボディがbodyに格納されずjson.Unmarssamlでエラーになっていました。
修正前に
Webサーバがbodyを制限なく受け入れるとbodyサイズだけサーバのメモリが消費されるため、
大きなbodyを送信しサーバをダウンさせるセキュリティホールになります。
そのため、API側で受け取れるbodyに上限を設けることは、Webに公開するAPIの場合必須になります。その点は注意したうえで実装してください。
修正後
func SAmple(c *gin.Context) { ~~~ //bodyを取得 var bodyBytes []byte if c.Request.Body != nil { bodyBytes, _ = ioutil.ReadAll(c.Request.Body) } c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) bodyString := string(bodyBytes) //変換 var json_parse map[string]interface{} err := json.Unmarshal([]byte(b), &json_parse) ~~~ }ioutil.ReadAllですべてのbodyを読み込めるので問題なくjsonをinterfaceへ変換できます。
まとめ
c.Request.Body.Read(buf)にはサイズに上限がある。
全部読み込みたい場合は、ioutil.ReadAll(c.Request.Body)で読み込みましょうありがとうございました。
- 投稿日:2021-01-23T12:04:05+09:00
自宅内の見守りカメラをアップデートした:④実装編:Go言語の基本を駆使してラズパイ制御&HTTPサーバー
はじめに
本記事はこちらのパート④にあたります。
もし内容に興味を持たれましたら、他のパートもご覧頂けると幸いです。前回の記事までで、作成するシステムの構成まで固まりましたので、いよいよゴリゴリ実装していきます…!
成果物(クライアント側)
コード
こちらに全て掲載しました。
https://github.com/tenkoh/go-home-camera補足説明など
自動調整の実行
UIにある自動調整実行ボタンに対して、jQueryのGETメソッドを送信する関数を設定しました。
リクエスト先はapi/calibration
としてあり、サーバ側ではそのURIにリクエストが送られると、一旦静止画撮影を停止し、キャリブレーションを実行するようにしています。
(URIにリクエストが届くと、静止画撮影のgoroutineとのチャネルに値を送信するようにし、goroutine側ではチャネルに値が入ったら静止画撮影を一旦やめて自動キャリブレーションを行うようにして実現しています)UI
自分が使うデバイスがiPhoneかiPadなので、それに合わせたレスポンシブデザインを簡単に作っておしまいにしてあります。
(あんまりcssの編集が得意ではないので、必要最低限で…)おわりに
最初に作ったシステムよりも段違いにサーバーのレスポンスも早く、また静止画撮影設定の固定化+自動調整機能実装によって見やすい画像が取得できるようになりました!
また自分でシステム構成を考えることで、学習教材を使った勉強の中ではいまいちピンと来ていなかったAPIの作成やその利用についても深く理解できたような気がします。よい勉強になりました。後日談
しばらく使い込んでいたら、ある日カメラモジュールのフラットケーブルがお亡くなりになりました…。
せっかくここまでシステムを作ったので、ケーブルを交換して再稼働させようと思います![]()
出典など
Go言語の学習にあたってはUdemyさんの講座にお世話になりました。
https://www.udemy.com/course/go-fintech/
- 投稿日:2021-01-23T11:24:31+09:00
自宅内の見守りカメラをアップデートした:③構成を変更しよう編:ラズパイカメラモジュールのマニュアル撮影を極める…!
はじめに
本記事はこちらのパート③にあたります。
もし内容に興味を持たれましたら、他のパートもご覧頂けると幸いです。(順次執筆中…)システム構成の変更
ハードウェアは据え置きで、ソフトウェアのみ構成を変更します。
パフォーマンス改善:画像取得方法の変更
カメラモジュールを使って単純に静止画を取得したいだけなので、標準コマンドを自作プログラムの中から呼び出して使うようにします。
Before After Opencv-python ラズパイ標準コマンド raspistill
取得画像の明るさ適正化:画像取得設定の固定化とキャリブレーションモードの導入
画像取得設定を固定化するには、
raspistill
のオプションで各種自動調整を切りつつ、必要なパラメータを全て指定する必要があります。
ここで言うところの自動調整とは、オートホワイトバランス(awb)と自動ゲインコントロール(agc)です。
それらをoffにすると、下記の項目を指定する必要があります。公式ドキュメントを見ても端的にまとまっていなかったため、どのオプションを与えれば良いかを明確にするまでが大変でした…。
オプション 内容 -ss シャッター開放時間 -br 画像の明るさ -awbg ホワイトバランス -ag アナログゲイン -dg デジタルゲイン これをいちいちトライアンドエラーするのは辛いです。
そこで同じくraspistill
のオプションで実現できる、自動調整結果の標準出力-set
オプションを使います。
-set
オプションを使うと以下のようなカメラ設定値が標準出力されます。何行も出力されてきますが、基本的には一番最後の行が調整完了済み結果となります。
そこから次の対応付けをして各オプションに値を与えればOKです。inputraspistill -set
outputCamera control callback cmd=0x48435045mmal: Exposure now 499982, analog gain 1075/256, digital gain 256/256 mmal: AWB R=314/256, B=708/256 Camera control callback cmd=0x48435045mmal: Exposure now 499982, analog gain 1075/256, digital gain 258/256 mmal: AWB R=317/256, B=698/256 Camera control callback cmd=0x48435045mmal: Exposure now 499982, analog gain 1130/256, digital gain 256/256 mmal: AWB R=309/256, B=729/256
オプション 標準出力結果との対応 -ss Exposure now の値をそのまま与える -awbg AWB R, Bをそれぞれ小数に直して与える。例: -awbg 1.3, 1.7
-ag analog gainを小数に直して与える -dg digital gainを小数に直して与える これにより、一度自動調整を実行して取得したカメラ設定を使って連続して静止画を撮影することができます。
awb, agcを行う必要がなくなるため、撮影のインターバルを狭めることもできるようになります!最終的な撮影コマンドを以下に示しておきます。xxxは適宜設定する値です。
raspistill -n -awb off -ex off -ss xxx -md xxx -o xxx.jpg -br 50 -awbg xxx,xxx -ag xxx -dg xxx -t xxxまた自動調整時の実行コマンドも同様に示しておきます。
raspistill -set -br 0 -ex xxxここで、
-ex
は露出設定です。auto
やnight
など、シーンごとの設定が用意されているため、ここは使用する時間帯等に応じて変更できるようにしたほうが良さそうです。システム構成図の落書き
以上を踏まえたシステム構成図の落書きを示します。
前回記事で明言したようにGo言語決め打ち!としたので、UIを配信するHTTPサーバと並行して動く撮影用goroutineを用意し、ユーザ操作によって撮影設定の自動調整(calibration)が指示されたときのみ、撮影設定を上書きするような構成とします。
このあたりの仕組はちょうど学習していたGo言語を使ったapi構築を応用してみました。おわりに
raspistill
のマニュアル撮影設定方法を調べるのが一番大変でした…。
なんとかシステム構成が決まりましたので、いよいよ実装→稼働です…!!
- 投稿日:2021-01-23T10:12:06+09:00
[Go] データをファイルに書き込み方法をまとめる
色んな書き込み方があり、何かとごちゃまぜになったので、一旦まとめてみました。
パターンがあり、いつ何を使えば良いか見えてきたので、同じく混乱している方の参考になればと思います。ざっくりと
こんな感じになる。
- 書き込み先を決める (例: file, buffer)。ファイルの場合
defer Close()
を忘れずに- データを変換する (例: []byte, Marshal), または 書き込むやつを取得する (例: Writer, Encoder)
- 書き込む (例: Write, Encode)
データ格納先へのデータの渡し方が多少違うので、覚えてなければ、都度マニュアルを確認しましょ。
単純に文字列をファイルに書き込む
なんでもいいからファイルに書き込みたいときに使います。
全体の流れ
- 書き込み先のファイル作成 (
f := os.Create
)- バイト文字列に変換 (
d := []byte{"文字列"}
)- 書き込み (
f.Write(d)
)コード①
package main import ( "fmt" "log" "os" ) func main() { // 1. 書き込み先のファイル作成 f, err := os.Create("test.txt") if err != nil { log.Fatal(err) } defer f.Close() // 2. バイト文字列に変換 d := []byte("バイト文字列に変換") // 3. 書き込み n, err := f.Write(d) if err != nil { log.Fatal(err) } fmt.Printf("%d bytes 書き込んだよ!", n) }コード②:
ioutil.WriteFile
を使えばファイル作成ステップ(①)を省略できるpackage main import ( "io/ioutil" "log" ) func main() { // 2. バイト文字列に変換 d := []byte("バイト文字列に変換") // 3. 書き込み err := ioutil.WriteFile("test.txt", d, 0644) if err != nil { log.Fatal(err) } }ライター (
Writer
) を使って書き込むbufio や CSV で使います。
参考: NewWriter全体の流れ
- 書き込み先のファイル生成 (
f := os.Create
)- 生成したファイルに書き込む、ライターを取得 (
w := NewWriter(f)
)
- パッケージによっては、
defer w.Flush()
を忘れずに (例: bufio & CSV)- 書き込み (
w.Write()
)
- CSV の場合、引数は
[]string
を渡す。- Bufio の場合、引数は
[]byte
を渡す。ただWriteString
で[]string
を渡せるコード
package main import ( "encoding/csv" "log" "os" ) func main() { data := [][]string{ {"name", "age"}, {"田中", "25"}, {"加藤", "30"}, } // 1. 書き込み先のファイル生成 f, err := os.Create("test.csv") if err != nil { log.Fatalln("failed to open file", err) } defer f.Close() // 2. ライターを取得 w := csv.NewWriter(f) defer w.Flush() // 3. 書き込み for _, record := range data { err := w.Write(record) if err != nil { log.Fatal(err) } } }Encoder を使って書き込む
JSON や XML で使います。
参考: NewEncoder全体の流れ
- 書き込み先のファイル生成 (
f := os.Create("test.json")
)- ファイルに書き込む、エンコーダを取得 (
encoder := json.newEncoder(f)
)- 構造体を JSON データにエンコードする (
encoder.Encode(&data)
)コード ①
package main import ( "encoding/json" "log" "os" ) type User struct { Name string `json:"name"` Age int `json:"age"` } func main() { data := []User{ User{Name: "田中", Age: 25}, User{Name: "加藤", Age: 30}, } // 1. 書き込み先のファイル生成 f, err := os.Create("test.json") if err != nil { log.Fatal(err) } // 2. ファイルに書き込む、エンコーダを取得 encoder := json.NewEncoder(f) // 3. data をファイルに書き込み err = encoder.Encode(&data) if err != nil { log.Fatal(err) } }コード ②: エンコーダーの取得と書き込み、一連の流れで書ける
package main import ( "encoding/json" "log" "os" ) type User struct { Name string `json:"name"` Age int `json:"age"` } func main() { data := []User{ User{Name: "田中", Age: 25}, User{Name: "加藤", Age: 30}, } // 1. 書き込み先のファイル生成 f, err := os.Create("test.json") if err != nil { log.Fatal(err) } // 2. ファイルに書き込む、エンコーダを取得 & 3 書き込み err = json.NewEncoder(f).Encode(&data) if err != nil { log.Fatal(err) } }Marshal を使って書き込む
JSON や XML で使います。
全体の流れ
- 書き込み先のファイル生成 (
f := os.Create("test.json)
)- 構造体を JSON データに変換する (
output, _ := json.Marshal(&data)
)- データを書き込む (
f.Write(output)
)コード
import ( "encoding/json" "fmt" "log" "os" ) type User struct { Name string `json:"name"` Age int `json:"age"` } func main() { data := []User{ User{Name: "田中", Age: 25}, User{Name: "加藤", Age: 30}, } // 1. 書き込み先のファイル生成 f, err := os.Create("test.json") if err != nil { log.Fatal(err) } // 2. バイト文字列の JSON データに変換 output, err := json.Marshal(&data) if err != nil { log.Fatal(err) } // 3. data を書き込み n, err := f.Write(output) if err != nil { log.Fatal(err) } fmt.Printf("%d bytes 書き込んだよ!", n) }番外編: 書き込み先がバッファの場合
もちろん、ファイルだけでなくバッファ (
buf := new(bytes.Buffer)
) にも書き込めますpackage main import ( "bytes" "encoding/json" "fmt" "log" ) type User struct { Name string `json:"name"` Age int `json:"age"` } func main() { data := []User{ User{Name: "田中", Age: 25}, User{Name: "加藤", Age: 30}, } // 1. 書き込み先のバッファを生成 buf := new(bytes.Buffer) // 2. バッファに書き込む、エンコーダを取得 encoder := json.NewEncoder(buf) // 3. data をバッファに書き込む err := encoder.Encode(&data) if err != nil { log.Fatal(err) } }番外編: Encode vs Marshal どっちを使うべきか
JSON や XML は共に、Encode と Marshal が使え、同じ結果が得られます。
では違いはなんなのでしょうか?Stack Overflow( 参考: in Golang, what is the difference between json encoding and marshalling ) によると、違いはこのようです。
- Marshal => String
- Encode => Stream
使い分けとして、文字列やバイト文字としてして扱いたい場合は Marshal、それ以外では Encode を使うようです。
また、JSON と XML はどちらの方法でも使えますが、ベンチマークでは Encode の方が早いようです。(TODO: 後で確認する)
- 投稿日:2021-01-23T10:12:06+09:00
[Go] データをファイルに書き込む方法をまとめる
色んな書き込み方があり、何かとごちゃまぜになったので、一旦まとめてみました。
パターンがあり、いつ何を使えば良いか見えてきたので、同じく混乱している方の参考になればと思います。ざっくりと
こんな感じになる。
- 書き込み先を決める (例: file, buffer)。ファイルの場合
defer Close()
を忘れずに- データを変換する (例: []byte, Marshal), または 書き込むやつを取得する (例: Writer, Encoder)
- 書き込む (例: Write, Encode)
データ格納先へのデータの渡し方が多少違うので、覚えてなければ、都度マニュアルを確認しましょ。
単純に文字列をファイルに書き込む
なんでもいいからファイルに書き込みたいときに使います。
全体の流れ
- 書き込み先のファイル作成 (
f := os.Create
)- バイト文字列に変換 (
d := []byte{"文字列"}
)- 書き込み (
f.Write(d)
)コード①
package main import ( "fmt" "log" "os" ) func main() { // 1. 書き込み先のファイル作成 f, err := os.Create("test.txt") if err != nil { log.Fatal(err) } defer f.Close() // 2. バイト文字列に変換 d := []byte("バイト文字列に変換") // 3. 書き込み n, err := f.Write(d) if err != nil { log.Fatal(err) } fmt.Printf("%d bytes 書き込んだよ!", n) }コード②:
ioutil.WriteFile
を使えばファイル作成ステップ(①)を省略できるpackage main import ( "io/ioutil" "log" ) func main() { // 2. バイト文字列に変換 d := []byte("バイト文字列に変換") // 3. 書き込み err := ioutil.WriteFile("test.txt", d, 0644) if err != nil { log.Fatal(err) } }ライター (
Writer
) を使って書き込むbufio や CSV で使います。
参考: NewWriter全体の流れ
- 書き込み先のファイル生成 (
f := os.Create
)- 生成したファイルに書き込む、ライターを取得 (
w := NewWriter(f)
)
- パッケージによっては、
defer w.Flush()
を忘れずに (例: bufio & CSV)- 書き込み (
w.Write()
)
- CSV の場合、引数は
[]string
を渡す。- Bufio の場合、引数は
[]byte
を渡す。ただWriteString
で[]string
を渡せるコード
package main import ( "encoding/csv" "log" "os" ) func main() { data := [][]string{ {"name", "age"}, {"田中", "25"}, {"加藤", "30"}, } // 1. 書き込み先のファイル生成 f, err := os.Create("test.csv") if err != nil { log.Fatalln("failed to open file", err) } defer f.Close() // 2. ライターを取得 w := csv.NewWriter(f) defer w.Flush() // 3. 書き込み for _, record := range data { err := w.Write(record) if err != nil { log.Fatal(err) } } }Encoder を使って書き込む
JSON や XML で使います。
参考: NewEncoder全体の流れ
- 書き込み先のファイル生成 (
f := os.Create("test.json")
)- ファイルに書き込む、エンコーダを取得 (
encoder := json.newEncoder(f)
)- 構造体を JSON データにエンコードする (
encoder.Encode(&data)
)コード ①
package main import ( "encoding/json" "log" "os" ) type User struct { Name string `json:"name"` Age int `json:"age"` } func main() { data := []User{ User{Name: "田中", Age: 25}, User{Name: "加藤", Age: 30}, } // 1. 書き込み先のファイル生成 f, err := os.Create("test.json") if err != nil { log.Fatal(err) } // 2. ファイルに書き込む、エンコーダを取得 encoder := json.NewEncoder(f) // 3. data をファイルに書き込み err = encoder.Encode(&data) if err != nil { log.Fatal(err) } }コード ②: エンコーダーの取得と書き込み、一連の流れで書ける
package main import ( "encoding/json" "log" "os" ) type User struct { Name string `json:"name"` Age int `json:"age"` } func main() { data := []User{ User{Name: "田中", Age: 25}, User{Name: "加藤", Age: 30}, } // 1. 書き込み先のファイル生成 f, err := os.Create("test.json") if err != nil { log.Fatal(err) } // 2. ファイルに書き込む、エンコーダを取得 & 3 書き込み err = json.NewEncoder(f).Encode(&data) if err != nil { log.Fatal(err) } }Marshal を使って書き込む
JSON や XML で使います。
全体の流れ
- 書き込み先のファイル生成 (
f := os.Create("test.json)
)- 構造体を JSON データに変換する (
output, _ := json.Marshal(&data)
)- データを書き込む (
f.Write(output)
)コード
import ( "encoding/json" "fmt" "log" "os" ) type User struct { Name string `json:"name"` Age int `json:"age"` } func main() { data := []User{ User{Name: "田中", Age: 25}, User{Name: "加藤", Age: 30}, } // 1. 書き込み先のファイル生成 f, err := os.Create("test.json") if err != nil { log.Fatal(err) } // 2. バイト文字列の JSON データに変換 output, err := json.Marshal(&data) if err != nil { log.Fatal(err) } // 3. data を書き込み n, err := f.Write(output) if err != nil { log.Fatal(err) } fmt.Printf("%d bytes 書き込んだよ!", n) }番外編: 書き込み先がバッファの場合
もちろん、ファイルだけでなくバッファ (
buf := new(bytes.Buffer)
) にも書き込めますpackage main import ( "bytes" "encoding/json" "fmt" "log" ) type User struct { Name string `json:"name"` Age int `json:"age"` } func main() { data := []User{ User{Name: "田中", Age: 25}, User{Name: "加藤", Age: 30}, } // 1. 書き込み先のバッファを生成 buf := new(bytes.Buffer) // 2. バッファに書き込む、エンコーダを取得 encoder := json.NewEncoder(buf) // 3. data をバッファに書き込む err := encoder.Encode(&data) if err != nil { log.Fatal(err) } }番外編: Encode vs Marshal どっちを使うべきか?
JSON や XML は共に、Encode と Marshal が使え、同じ結果が得られます。
では違いはなんなのでしょうか?Stack Overflow( 参考: in Golang, what is the difference between json encoding and marshalling ) によると、違いはこのようです。
- Marshal => String
- Encode => Stream
使い分けとして、文字列やバイト文字としてして扱いたい場合は Marshal、それ以外では Encode を使うようです。
また、GitHub の Q&A でも、
- (メモリ上の) バイト文字列を取り扱う場合は、Marshal
- Reader/Writer (ファイルなどの stream) を取り扱う場合は、Encode
と回答されてますね。