こんにちは!文系出身ながらも自力で勉強・実務をこなし、8年目に突入した現役プログラマの佐藤です。
プログラミング初級者から上級者まで必ず扱う、といっても過言ではないCSVファイルですが
「C#で読み込むには具体的にどうしたらいいのか?」
「CSVファイルを読み込むことはできたけど、思った通りにならなかった…」
CSVを読み込む方法は言語によって様々あって、どれを選べばいいのか迷ってしまいがちです。
また、簡単に出来そうなサンプルで実行してみても上手くいかなかったりして思ったより手間取ってしまいますよね。
今回はそんなお悩みを解決する
ずばり!2つのCSV読み込み方法と、複雑なCSVの読み込み方についてご紹介します。
「そもそもどんなファイルのなのかよくわからない」
CSVという言葉は知っていても、どんなファイルなのか名前だけでは想像がつきませんよね。
そんな方も理解できるよう、CSVファイルの仕組みについても解説しますのでご安心ください!
それでは一緒に学んでいきましょう!
CSVファイルとは?
カンマ区切りのデータのまとまり
CSVは、Comma Separated Valueの略で、「,」(カンマ)で値が区切られているファイルのことです。
データのまとまりは行単位になっているので、改行を入れると次のデータのまとまりとして扱われます。
そのまとまりのことを「レコード」と呼びます。
1つのCSVファイルの中身を開くと次のような形でデータが入っています。
<データの例>
北海道,札幌市,中央区
東京都,北区
大阪府,大阪市,中央区
必ずデータとデータの間にカンマが入っていますよね?
また、先程説明したとおり改行することでデータのまとまりを表していることがわかります。
このデータは3つのまとまりになっているのでレコードが3つあるデータです。
CSVの意外な仕組み
CSVファイルは上記で解説した通り簡単なデータを管理する仕組みですが、実は注意点があります。
区切り文字が「,」(カンマ)とは限らない
区切り文字がカンマ以外でもCSVファイルと呼ばれる点に注意しましょう
中身を開いて確認するか、CSVファイルを作成した人に確認してから扱います。
スペース区切りデータの例
北海道 札幌市 中央区
東京都 北区
大阪府 大阪市 中央区
先ほどと違いカンマの代わりにスペースが入っていることが分かります。
このように区切り文字はスペース、セミコロンなど…様々存在します。
ヘッダ(項目の名称)がついているファイルもある
先ほどのデータの例では、最初の行からデータが入力されていましたが、最初の行がヘッダ(項目の名称)のCSVファイルもあります。
ヘッダ付きデータの例
都道府県,市,区
北海道,札幌市,中央区
東京都,,北区
大阪府,大阪市,中央区
1行目にヘッダ(項目の名称)が入っていますね。
2行目からのデータの1つ目は「都道府県」、2つ目は「市」、3つ目は「区」が入っています。
ヘッダの有無が読み込む際のポイントになることもあるので、こちらも確認してから扱いましょう!
C#でCSVのデータを読み込んでみよう
ここからはC#でCSVを読み込むための具体的な方法について解説していきます。
サンプルコードを使って解説していきますので、一緒に学習していきましょう。
2つの方法で読み込めるんです!
実は、C#でCSVファイルを読み込む場合には、2つの方法を使って読み込むことができるんです!
今回は、StreamReaderでデータを読み込む方法について詳しく解説します。
StreamReaderとは、テキストデータやファイルを読み込む際に使用されるクラスです。
CSV以外のファイルを読み込む際にも使用されるなど、とても多くの機能をもっています。
ここで全て解説はしませんが、興味がある方は次のStreamReaderについて書いた記事をぜひご覧ください。StreamReader自体の解説はもちろん、一気にファイル全体を読み込む方法なども紹介しています。
StreamReaderを使って読み込んでみよう!
では実際にCSVファイルを読み込んで、コンソール画面に表示してみましょう。
読み込むファイルはtest.csvで、以下の内容です。
北海道,札幌市,中央区
東京都,,北区
大阪府,大阪市,中央区
実際のコードはこちらになります。
using System.Collections.Generic; using System.IO; namespace CsvRead { class Test { static void Main() { // 読み込みたいCSVファイルのパスを指定して開く StreamReader sr = new StreamReader(@"test.csv"); { // 末尾まで繰り返す while (!sr.EndOfStream) { // CSVファイルの一行を読み込む string line = sr.ReadLine(); // 読み込んだ一行をカンマ毎に分けて配列に格納する string[] values = line.Split(','); // 配列からリストに格納する List<string> lists = new List<string>(); lists.AddRange(values); // コンソールに出力する foreach (string list in lists) { System.Console.Write("{0} ", list); } System.Console.WriteLine(); } System.Console.ReadKey(); } } } }
実行結果:
北海道 札幌市 中央区 東京都 北区 大阪府 大阪市 中央区
実行後、CSVの内容がコンソール画面に表示されましたか?
コードとコメントだけでは分かりにくい部分があるので、ポイントを絞ってコードを解説していきます!
先程説明したStreamReaderを早速使っていきます。
StreamReaderを利用するためにはまず、何のファイルを開くか指定しなければなりません。
それを指示しているのが次の場所になります。
// 読み込みたいCSVファイルのパスを指定して開く StreamReader sr = new StreamReader(@"test.csv");
ここでは、読み込みたいCSVファイルのパスをカッコの中に指定して開きます。
ファイルの名前ではないので、注意しましょう。
開くこと出来たら、早速CSVファイルを一行ずつ読み込んでいく部分に移ります。
まずはwhile文を使い、ファイルの最終行まで読み込む為のループを作ります。
// 末尾まで繰り返す while (!sr.EndOfStream)
sr.EndOfStreamは開いたCSVファイルの最後の行を読み込むとTrueを返します。
これを利用して、最後の行まで読み込むようにFalseの間ループさせましょう。
最終行までループさせる部分が完成したら、次はデータを一行読み込みましょう!
一行読み込むためのメソッドが用意されていますので、そちらを次のように利用します。
// CSVファイルの一行を読み込む string line = sr.ReadLine();
sr.ReadLine()で開いたCSVファイルの一行分を読み込むことができます。
一行目の場合で言えば、変数:lineの中には「北海道,札幌市,中央区」が格納されます。
一行読み込むことが出来たら、完成までもう少しです!
最後に読み込んだ文字列をカンマ毎に分けて格納していきましょう。
// 読み込んだ一行をカンマ毎に分けて配列に格納する string[] values = line.Split(',');
Splitメソッドは文字列を改行や区切り文字で分割して、文字列の配列にするメソッドです。
今回はカンマ区切りなので、区切り文字を指定する部分に「,」を入力します。
次が非常に大切なポイントです!
配列のままでも読み込んだCSVファイルを扱うことは出来ますが、リストに格納し直します。
次の場所をご覧ください。
// 配列からリストに格納する List<string> lists = new List<string>(); lists.AddRange(values);
配列では要素の削除が行えませんが、リストに格納し直す事で要素の削除が行えるようになります。
要素の削除ができるようになると複雑なCSVを読み込むのが簡単になります!
配列をそのままリストに格納してくれるAddRangeメソッドを使って、リストに格納し直しましょう。
以上でCSVを読み込むことが出来ました!
StreamReaderを使えば簡単に読み込むことができますので、慣れるまではこの方法でCSVを扱ってみましょう。
TextFieldParserを使う方法
もう一つの方法はTextFieldParserを使用して読み込む方法です。
TextFieldParserを使用すると複雑なCSVも比較的簡単に読み込むことが出来ますが、Microsoft.VisualBasicアセンブリの参照を追加する必要があり難易度が上がります。
今回はコードの解説は行いませんが、興味のある方はこちらのサイトなどを参考にして是非マスターしてみてくださいね!
TextFieldParserを利用してCSVファイルやTSVファイルを読み込む (C#プログラミング)
複雑なCSVのデータを読み込んでみよう
データの中にダブルクォーテーションが入っている時
ここからはちょっと複雑なCSVの読み込み方について紹介します。
まずは、「”」(ダブルクォーテーション)で項目が囲まれている場合です。
この場合のデータをStreamReaderで読み込もうとすると、上手く読み込めません。
そこで、「”」(ダブルクォーテーション)で項目を囲う理由と、回避方法について解説します!
「”」(ダブルクォーテーション)で項目を囲う理由
項目に区切り文字が入ってしまっている時、「”」で項目を囲う必要があります。
ちょっとわかりにくいので、さっそく例をみて確認していきましょう!
次の例は金額の項目がある場合です。
・データの例
光熱費,”25,000”
食費,”30,000”
金額の区切りのカンマと、データの区切りのカンマを区別するために「”」で項目を囲っています。
このように「”」で囲うことで、区切り文字が入っている部分も一つのデータにしています。
「”」付きのデータを上手く読み込む方法
実は、何もしなければ「”」付きのデータはStreamReaderでうまく読み込めません。
先程の例をStreamReaderで読み込もうとすると
・StreamReaderを使って読み込んだ例
光熱費 “25 000”
食費 “30 000”
このように、3つのデータがあると認識してしまい、思ったようなデータになりません。
でも大丈夫、ちょっとした工夫でこの現象は回避できます!
サンプルコードを使って解説していきます
using System.Collections.Generic; using System.IO; namespace CsvRead { class Test { static void Main() { // 読み込みたいCSVファイルを指定して開く StreamReader sr = new StreamReader(@"test.csv"); { // 末尾まで繰り返す while (!sr.EndOfStream) { // CSVファイルの一行を読み込む string line = sr.ReadLine(); // 読み込んだ一行をカンマ毎に分けて配列に格納する string[] values = line.Split(','); // 配列からリストに格納する Listlists = new List (); lists.AddRange(values); // 項目分繰り返す for (int i =0; i < lists.Count; ++i) { //先頭のスペースを除去して、ダブルクォーテーションが入っていないか判定する if(lists[i] != string.Empty && lists[i].TrimStart()[0] == '"') { // もう一回ダブルクォーテーションが出てくるまで要素を結合 while (lists[i].TrimEnd()[lists[i].TrimEnd().Length - 1] != '"') { lists[i] = lists[i] + "," + lists[i + 1]; //結合したら要素を削除する lists.RemoveAt(i + 1); } } } // コンソールに出力する foreach (string list in lists) { System.Console.Write("{0} ", list); } System.Console.WriteLine(); } System.Console.ReadKey(); } } } }
実行結果:
光熱費 ”25,000” 食費 ”30,000”
途中までは先ほど紹介した方法と変わりませんが、リストに格納し直した事がここで効いてきます!
しっかり理解できるよう、こちらのコードのポイントも解説していきます。
まずは、リストの要素分ループするfor文を作ります。
次にデータの先頭にダブルクォーテーションが入っているかif文で判定していきます。
ここのポイントは2つあります。
データが空文字だった場合は判定しないようにする事と、スペースを除去してから判定を行う事です。
さっそく詳しく見ていきましょう!
//先頭のスペースを除去して、ダブルクォーテーションが入っていないか判定する if(lists[i] != string.Empty && lists[i].TrimStart()[0] == '"')
一つ目のポイントです。
lists[i] != string.Emptyの部分で、空文字かどうかの判定をしています。
この部分がないと、空文字のデータがあった場合、「先頭の文字がダブルクォーテーションかどうか」の判定時に「文字がないから判定できない!」とエラーを起こしてしまいます。
上手くいかない原因になりがちなので、注意して必ず空文字かどうかの判定を入れましょう。
二つ目のポイントはTrimStart()を使って、先頭のスペースを取り除いている部分です。
「 “12,000”」のようなデータがあった場合、ダブルクォーテーションがついているのに先頭の文字は「 」(スペース)になってしまいます。
このように、判定をすり抜けてしまう可能性があるので先頭のスペースは除去してしまいましょう。
スペースを除去した後、[0]と入力することで先頭の文字を取得することができます。
こちらも1ではなく0から始まる点に注意しましょう。
次は先頭の文字がダブルクォーテーションの場合の処理を作ります。
要素の結合と削除を利用するので、分かりやすいよう後半は表も使って説明していきます!
まずはコードの次の場所を見ていきましょう。
// もう一回ダブルクォーテーションが出てくるまで要素を結合 while (lists[i].TrimEnd()[lists[i].TrimEnd().Length - 1] != '"')
要素を結合した結果、文字列の末尾にダブルクォーテーションが現れるまでループするwhile文を作成します。
ここでも、先程と同じ理由でスペースの除去を行いたいので、TrimEnd()を使用して末尾のスペースを取り除きましょう。
そして先頭の文字を取得したい場合は[0]でしたが、次は末尾の文字を取得したいので「末尾のスペースを取り除いた後の文字数(lists[i].TrimEnd().Length)」から「1引いた値」を使用します。
ちょっと複雑なので例を使って説明します。
今回のサンプルだと、初めてここに到達したときはlists[1]の「”25」という文字列です。
末尾のスペースを取り除いた後の文字数(lists[i].TrimEnd().Length)は「3」になります。
先頭の文字を取得したいときは[0]だったので、3文字目は「3」から「1引いた値」の[2]を指定すれば取得出来ることになります。
ここまでいくと、末尾の「5」という文字列を取得でき、ダブルクォーテーションが入っているか確認できるという流れです!
次はいよいよ要素を結合して削除していきます。
ここも例を使って説明します。
今回のサンプルはこの時点で3つの要素に分かれています。
インデックス番号 | 値 |
---|---|
0 | 光熱費 |
1 | "25 |
2 | 000" |
1番にカンマと2番を結合すれば、本来の読み込みたかった形になるのがわかりますね。
次の場所で実際に文字列を結合しています。
lists[i] = lists[i] + "," + lists[i + 1];
i + 1することで、次の要素を取得し結合しているのがポイントです。
この時点で、サンプルは次のように変化しています。
インデックス番号 | 内容 |
---|---|
0 | 光熱費 |
1 | "25,000" |
2 | 000" |
こうなると2番の要素が不要になりますね。
ここで要素の削除を利用します!
リストの要素を削除するときはRemoveAtメソッドを使用します。
//結合したら要素を削除する lists.RemoveAt(i + 1);
かっこの中に削除する要素のインデックス番号を入力します。
配列のままだと要素の削除が出来ませんが、リストに格納し直す事で簡単に行うことが出来ます。
ここで削除を行うことで、サンプルは以下のようになりました。
インデックス番号 | 内容 |
---|---|
0 | 光熱費 |
1 | "25,000" |
これで読み込みたかった形式のデータが完成しました!
この方法を使用すれば、データが分割されずに読み込まれます。
複雑な点もありましたが、ぜひ挑戦してみてください。
ヘッダがついているCSVファイルを読み込んでみよう
CSVの仕組みの説明で「最初の行がヘッダ(項目の名称)のCSVファイル」があると説明しました。
読み込む際にヘッダも同じように読み込んで扱うこともありますが、ヘッダは要らない場合もあるかと思います。
その時は、ループの前に一行分読む命令を書き加えましょう。
サンプルコードの一部を抜粋してご紹介します。
// 読み込みたいCSVファイルを指定して開く StreamReader sr = new StreamReader(@"test.csv"); { // 一行読み飛ばす sr.ReadLine(); // 末尾まで繰り返す while (!sr.EndOfStream) { // CSVファイルの一行を読み込む string line = sr.ReadLine();
一行目を読み込み、変数に格納しないことでヘッダを読み飛ばすことが出来ます。
その時の状況に合わせて使い分けてみてください!
区切り文字がカンマ以外のデータを読み込んでみよう
こちらも先ほどの仕組みの説明の中で、カンマ区切り以外のCSVファイルもあるとご説明しました。
カンマ以外で区切られていても、StreamReader でしっかり読み込めますので安心してくださいね。
方法はとても簡単です。
line.Splitのあとのカンマをファイルに合わせた区切り文字に変更するだけで上手くいきます。
コードの一部を抜粋してご説明します。
// 読み込んだ一行をセミコロン毎に分けて配列に格納する string[] values = line.Split(';');
これでセミコロン区切りのCSVを読み込むことが出来ます。
ほかにもスペースや、タブ区切りにも対応できますので、読み込むCSVに合わせて変更しましょう。
読み込む前にCSVファイルの区切り確認を忘れずに!
文字化けしている時の対処法
読み込んでみたら上手くいかなかったパターンで多いのが「文字化け」だと思います。
文字化けは、読み込む時に自動で判断された文字コードが、CSVファイルの文字コードと一致していないと起こってしまいます。
文字化けが起こってしまった場合は、StreamReaderで開く際に文字コードを指定しましょう。
// 読み込みたいCSVファイルを指定して開く StreamReader sr = new StreamReader(@"test.csv", System.Text.Encoding.GetEncoding("shift_jis"));
このサンプルは読み込み時にShift_JISの文字コードを使うように設定して読み込んでいます。
ほかにも文字コードは様々あるので、文字化けした場合は読み込みたいCSVに合った文字コードを設定してみましょう。
つまづきやすい複雑なCSVファイルの読み込み方についてご紹介しました!
読み込んでみて、思っていたようなデータにならなかった場合も諦めず、紹介した方法で解決できないか確認してくださいね。
まとめ
CSVのしくみ、読み込み方について理解できましたか?
最初はStreamReaderが難しいと感じるかもしれませんが、慣れれば今まで以上にCSVが扱えるようになり確実にステップアップできます!
何度もやってみることが大切だと思いますので、この記事を読んでいろいろなCSVに挑戦してみてくださいね。
最後までお読みいただきありがとうございました。