CSV結合ツールを自作しよう!(WindowsForm版)

当ページのリンクには広告が含まれています。

以前、WindowsFromを使ったCSV結合ツールの作り方をテーマに、全6回に分けて詳しく解説した記事を公開していましたが、本記事はその中で最も人気が高かった内容をリニューアルしたものになります。

本記事で紹介しているプログラムは、指定フォルダに存在するCSVファイルを全て1つのファイルとして結合するものであり、ヘッダが複数行ある場合はそれを考慮して結合します。

また、CSVの文字コードを変換することも可能なので、EXCELで文字化けするようなCSVであっても、Shift-jis に変換することで文字化けを無くすことが出来ます。

プログラム自体はシンプルなので、皆さんが必要な機能をソースコードに追加し、趣味や仕事に役立てて頂ければと思います。

尚、本記事からダウンロード可能なプロジェクトファイルは Visual Studio 2022 で動作を確認しております。

プロジェクトファイルの中に実行ファイル(CsvJoin.exe)も同梱しているので、単にプログラムを使うだけであれば、ダウンロードファイルを解凍後、CsvJoin.exeだけを任意のフォルダに解凍し、実行して頂くことも可能です。

目次

画面レイアウト

プログラムを起動すると下記の画面が表示されます。シンプルなので解説は不要だと思いますが、結合対象フォルダと出力ファイル名の入力欄には、ファイルやフォルダをドラッグ&ドロップにより指定することが可能になっています。

ヘッダ行数は0~4まで選択可能ですが、任意の数値を手入力することができます。例えば、5を入力するとヘッダが5行あることを前提に結合します。

「ファイル名でソートした順に結合」にチェックを入れておくと、ファイル名でソートした順番にファイルを結合してくれます。

試用しているコントロール名

画面で使っているコントロールには以下の名前を付けています。

CSV結合ツールのアルゴリズム

指定されたフォルダにあるファイル名を1つづつオープンし、1行づつ取り出して出力ファイルに書き出すという処理を繰り返します。CSVファイルを一旦メモリに読み込むようなことはしないので、どれだけ巨大なCSVであっても、メモリオーバーすることなく結合できるのが特徴です。

CSV結合ツールのアルゴリズム(処理手順)方は次と通りです。

  • 出力ファイルを書き込みモードでオープンする
  • 結合対象フォルダから1個取り出す
  • 取り出したCSVファイルから順次1行読んでは出力ファイルに書き出すことを繰り返す
  • 取り出したCSVファイルが2個目以降の場合、ヘッダは読み飛ばす(読み込んだヘッダを無視)
  • 出力ファイルをクローズする

1個目か2個目以上かを判定するため、 file_no という変数を使用しています。

また、ヘッダ行を読み飛ばすため、CSVファイルの読み込んだ行数を数え、画面から入力されたヘッダ行数に達するまで読み飛ばすという処理を行っています。

line_cnt を0から始めているので、line_cnt < head_cnt (読み込んだ行数がヘッダ数未満)という条件判断にしています。

line_cnt = 1 にすると、line_cnt <= head_cnt(読み込んだ行数がヘッダ数以下)という書き方ができるのですが、多くのプログラミング言語はカウントを0から開始する習慣があるので、あえてline_cnt=0にしています。 

以上のことを念頭に、フローチャートをざっくり確認してみて下さい。

ソースコード

それでは、全体のソースコードを掲載しておきます。

Visual Studio 2022 の プロジェクトファイルは、下記からダウンロード可能です。

ファイルを1行ずつ読み書きするクラス

これまで作ってきたプログラムでファイルを読み書きする場合、Fileクラスのメソッド(ReadAllLInes、 WriteAllLines、AppendAllLines)を使ってきました。

これらのメソッドは1行でファイルの読み書きが出来ますが、それは対象となるファイルを一旦メモリに格納する必要がありました。

ことのとにより、「巨大なサイズのファイルはメモリに収まりきらなくなるため扱えない」という制限が生じていました。

こういう場合は、1行ごとに読み書きすることでメモリ量を大幅に減らすことができますが、この時に使うクラスが StreamReader や StreamWrite クラスです。

ファイルのオープン

ファイルに対して読み書きする場合、OSに対して「今からこのファイルを読み込むよ」とか「今からこのファイルに書き込みするよ」という宣言を行い、その許可をもらう必要があります。

この行為をファイルをオープンすると呼びます。

オープンすることで、OSは特定のプログラムにだけ参照や書き込みを許可し、他のプログラムからの要求を跳ね除けたりするのですが、この状態を「ロックされている」とか「掴まれている」という風に表現します。

ファイルに対しては、「読み込みのみ」、「書き込み(追加書き込み含む)のみ」、「読み書き両方」の3パターンが選べるのですが、それぞれにクラスが用意されています。

今回は「読み込みのみ」と「書き込みのみ」の2つ、つまりStreamReader と StreamWriteを使っています。

ファイルのクローズ

プログラムはファイルをオープンした後で、必ずクローズする必要があります。

クローズとは、OSに対して「もう処理が終わりました」という宣言をすることです。

これによりOSは、他のプログラムからアクセスできるような状態に戻すと共に、ファイルスタンプを変更します。

もし、プログラムがオープンしたままクローズせず終了したらどうなるでしょう?

OSがファイルを掴んだままになるため、他のプログラムがアクセスしようとしても出来くなってしまいます。

また、 StreamReaderやStreamWriteを new した時に確保されたメモリも暫くは残ったままになるため、ループ処理の中で数多くのファイルを処理する場合、メモリ不測に陥る可能性も否めません。

以上のことから、ファイルをオープンしたら、必ずクローズするという処理が必要になります。

クローズするには Closeメソッドを呼ぶだけなので、ファイルを扱う場合は是非このメソッドを呼ぶことを心がけてください。

自動でCloseしてくれる using 文

C#には便利な機能があって、明示的にCloseメソッドを呼ばなくても、自動的にクローズするように記述が出来ます。

それが using です。

ソースコードの冒頭に using System; とかいう記述が数行続きますが、これとは全く違います。

その点が少しややこしいのですが、全く別物です。

書き方は

using( クラスのインスタンス生成 ) { 処理 }

です。

では、実際の例を見てみましょう。

以上の様に、using( ) { } で括ることで、明示的に Closeメソッドを呼ばなくとも、自動的にクローズ処理をしてくれるようになります。

ファイルの終わりまでループ

ファイルを1行ずつ、最後まで読み込むには次のように記述します。

これは こちらの記事の最後にも登場しましたね。

ファイル読み込みの処理をGoogle検索すると、次の様な書き方を目にすることもあります。

こちらは、ファイルの終わりに達した状態でReadLineメソッドを呼ぶと null が返ってくるので、それを利用した方法になります。

これは好みの問題なので、どちらでも良いと思います。

2個目以降とヘッダ行数の判定

最後に要となるヘッダを読み飛ばす際の判定条件です。

1個目のファイルか2個目以降のファイルかを判定するために、ファイル1個を処理するたびに file_noをカウントアップしています。

これを利用すると、file_no が0の時は1個目、0以外の時(0より大きい時)は2個目以降であると判断できます。

そして、line_cnt は1行読む度にカウントアップしていますので、行数が head_cnt より小さい場合はヘッダだと判断して、読み飛ばし(continue により、forループの先頭に無条件にジャンプ)を行っている訳です。

ちょっとだけ注意する点としては、line_cnt は int 型で定義しているので 、 -2147483648~2147483648(プラスマイナス2億1千)の値が扱える範囲になります。

つまり、2億1千行を超えるCSVを扱う場合、途中でline_cntの値がマイナスになって挙動がおかしくなるという危険性はあります。

実質そんな巨大なファイルを結合することは無いと思いますが、もし気になるようでしたら long line_cnt とすれば -9223372036854775808~9223372036854775808というくらいとてつもない行数に対応できるようになります。

ちなみに、file_no > 0 と書いていますが、 file_no != 0 と書いても同じです。

それから、 line_cnt ++ と書いていますが、これはC言語っぽい書き方です。

++演算子

++ は演算子で、変数の値に1加えるという記述です。

C言語には次のような記述方法が用意されていて、 1を加えるだけなら ++ を使います。

++ は変数の後だけではなく、前にも記述できます。

1行の中に単独で書く場合、どちらの記述でも結果は同じです。

しかし条件判断の中で使う場合は意味が変わってきます。

++を変数の後に書く場合、条件判断を実行した後で変数を加算します。
逆に、変数の前に記述すると、変数に値を加算してから条件判断します

条件判断で用いる場合、この違いは大きいのでご注意ください。

まとめ

如何でしたでしょうか。

StreadReader とStreamWrite を使って1行毎に読み込みと書き込みを繰り返すことで、メモリ消費量を減らしました。

これでメモリにファイルを丸ごと読み込む必要がなくなったので、実質メモリ不測という事は無くなります。

数G単位のファイルを扱う場合はこの方法が有効になりますので、機会があれば是非ご活用下さい。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次