C#でファイルをBase64形式に変換するときにメモリ不足になった話
Created|Updated
最近、SQL ServerにPDFファイルなどを保存する必要があって、Insert文を生成するためのツールを作成していました。
その中で、C#プログラムがメモリ不足で落ちてしまうことがあり、いろいろ調べたのでまとめておきます。
tl;dr
- SQL文でファイルをSQL Serverのvarbinay型カラムにInsertしたかった
- SQL文を生成するツールをC#で作ったがメモリ不足で落ちた
- 原因:ファイルを一気に読み込んでいたため
- 解決策:BinaryReaderで少しずつ読み込む
問題がおきたコード
やりたいこととしては、ファイルを読みこんでBase64形式の文字列に変換したい、ただそれだけでした。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 
 | public void メモリ不足になるコード() {
 string[] files = System.IO.Directory.GetFiles(@"フォルダパス", "*", System.IO.SearchOption.AllDirectories);
 
 foreach (var file in files)
 {
 using (var fs = new System.IO.FileStream(file, System.IO.FileMode.Open, System.IO.FileAccess.Read))
 {
 
 byte[] bs = new byte[fs.Length];
 fs.Read(bs, 0, bs.Length);
 fs.Close();
 
 
 string base64str = System.Convert.ToBase64String(bs);
 }
 }
 
 Console.WriteLine("読み込み完了");
 }
 
 | 
上記のコードを実行すると、3GBくらいのメモリを食ってしまってることが分かりました。

解決策
調べてみると、
| 1
 | fs.Read(bs, 0, bs.Length)
 | 
のところで、ファイルを一気に読み込んでしまっているのが原因らしいと言うことが分かりました。
そのため、少しずつ読み込んで行く方針に変えてみるとメモリ使用量を減らすことができました。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 
 | public void 軽くしたバージョン(){
 
 string[] files = System.IO.Directory.GetFiles(@"フォルダパス", "*", System.IO.SearchOption.AllDirectories);
 
 foreach (var file in files)
 {
 using (var fs = new System.IO.FileStream(file, System.IO.FileMode.Open, System.IO.FileAccess.Read))
 {
 
 
 using (var br = new System.IO.BinaryReader(fs))
 {
 
 const int CHUNK_SIZE = 1024;
 
 
 byte[] bs = new byte[CHUNK_SIZE];
 bs = br.ReadBytes(CHUNK_SIZE);
 
 StringBuilder base64str = new StringBuilder();
 
 
 while (bs.Length > 0)
 {
 
 base64str.Append(System.Convert.ToBase64String(bs));
 
 bs = br.ReadBytes(CHUNK_SIZE);
 }
 }
 }
 }
 
 Console.WriteLine("読み込み完了");
 }
 
 | 
上記のコードだと、メモリ使用量を減らすことができました。
(それでも結構食ってますが…。落ちることはなくなりました。)

まとめ
ファイル数が多かったり、大きすぎると、一気にファイルを読み込むとメモリ不足になる可能性があります。
BinaryReaderで少しずつ読み込んで処理しましょう。
参考