CSV分解は「カンマの意味をちゃんと理解する」技
CSV を読むときに、line.split(",") と書きたくなる気持ちはよく分かります。
でもそれをやると、名前にカンマが入っていたり、ダブルクォートで囲まれた値があった瞬間に、きれいに壊れます。
CSV分解で大事なのは、「見た目のカンマ全部で分割する」のではなく、「CSV のルールに従って“区切りとしてのカンマ”だけを認識する」ことです。
そのために、最低限のルールを自前で実装した小さなユーティリティを持っておくと、業務で扱う CSV にかなり強くなれます。
まずは「CSV一行のルール」を頭に入れる
ダブルクォートの内側と外側でカンマの意味が変わる
CSV の一行は、ざっくり言うと「フィールドがカンマで区切られている」だけですが、ダブルクォートが絡むと話が変わります。
1,山田太郎,営業部
これは素直に ["1", "山田太郎", "営業部"] です。
1,"山田,太郎","彼は""特別""なメンバーです"
これは ["1", "山田,太郎", "彼は"特別"なメンバーです"] です。
ここで重要なのは、ダブルクォートで囲まれた部分のカンマは「区切りではない」ということです。
さらに、ダブルクォート自体を表現するために "" と二重に書かれているので、
分解するときはこれを元の " に戻してあげる必要があります。
つまり、CSV分解は「ダブルクォートの内側か外側かを意識しながら、一文字ずつ読んでいく」処理になります。
最小限の「CSV一行分解ユーティリティ」を作る
状態を持ちながら一文字ずつ読む
split(",") を封印して、自前で一文字ずつ読んでいく実装を作ってみます。
ここでは「1行の CSV を List<String> に分解する」ユーティリティを用意します。
import java.util.ArrayList;
import java.util.List;
public final class CsvLineParser {
private CsvLineParser() {}
public static List<String> parseLine(String line) {
List<String> result = new ArrayList<>();
if (line == null || line.isEmpty()) {
result.add("");
return result;
}
StringBuilder current = new StringBuilder();
boolean inQuotes = false;
int len = line.length();
for (int i = 0; i < len; i++) {
char c = line.charAt(i);
if (c == '"') {
if (inQuotes && i + 1 < len && line.charAt(i + 1) == '"') {
current.append('"');
i++;
} else {
inQuotes = !inQuotes;
}
} else if (c == ',' && !inQuotes) {
result.add(current.toString());
current.setLength(0);
} else {
current.append(c);
}
}
result.add(current.toString());
return result;
}
}
Javaここで深掘りしたい重要ポイントは三つです。
一つ目は、「inQuotes というフラグで“今ダブルクォートの内側かどうか”を管理している」ことです。
これによって、「内側のカンマは区切りではない」「外側のカンマだけ区切り」と判断できます。
二つ目は、「"" を一つの " に戻している」ことです。inQuotes 中に " を見つけたとき、次の文字も " なら、それはエスケープされたダブルクォートなので、" を一つだけ追加し、インデックスを一つ進めています。
三つ目は、「ループが終わったあとに最後のフィールドを必ず追加している」ことです。
行末にはカンマがないので、ループ中では追加されません。
ここを忘れると、最後の列が消えるという地味に痛いバグになります。
例題:さっきの「CSV一行生成」と組み合わせて往復させる
生成→分解で「ルールが対になっている」ことを確認する
以前作った CsvLineBuilder と今回の CsvLineParser を組み合わせて、
「一度 CSV にしてから分解しても元に戻る」ことを確認してみます。
public class CsvRoundTripExample {
public static void main(String[] args) {
String original1 = "1";
String original2 = "山田,太郎";
String original3 = "彼は\"特別\"なメンバーです";
String line = CsvLineBuilder.buildLine(original1, original2, original3);
System.out.println("line = " + line);
var fields = CsvLineParser.parseLine(line);
System.out.println("parsed1 = " + fields.get(0));
System.out.println("parsed2 = " + fields.get(1));
System.out.println("parsed3 = " + fields.get(2));
}
}
Javaここでのポイントは、「生成側と分解側のルールが“ペア”になっている」ことです。
生成側は「必要ならダブルクォートで囲み、" を "" にする」。
分解側は「"" を " に戻し、ダブルクォートの内外でカンマの扱いを変える」。
このペアが揃っていると、「どんな値でも安全に CSV に出して、安全に元に戻せる」という安心感が生まれます。
例題:CSVファイルを一行ずつ読みながら分解する
Files.lines と組み合わせてストリーム処理する
一行分解ユーティリティができたので、実際に CSV ファイルを読むコードに組み込んでみます。
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
public class CsvReader {
public void read(Path path) throws IOException {
try (var lines = Files.lines(path)) {
lines.forEach(line -> {
List<String> fields = CsvLineParser.parseLine(line);
System.out.println("cols=" + fields.size() + " : " + fields);
});
}
}
}
Javaここでの重要ポイントは、「一行の分解ロジックを完全に CsvLineParser に閉じ込めている」ことです。CsvReader は「ファイルから行を読む」ことだけに集中し、
「カンマやダブルクォートをどう扱うか」は一切気にしていません。
こうしておくと、「TSV にも対応したい」「セミコロン区切りにも対応したい」といったときに、
分解ロジック側だけを差し替えればよくなります。
CSV分解の「やりがちな失敗」とどう避けるか
split(“,”) で済ませてしまう
一番多い失敗は、やはりこれです。
String[] cols = line.split(",");
Javaこれだと、ダブルクォートで囲まれたカンマも区切りとして扱ってしまうので、
「列数が合わない」「値が途中で切れる」といった事故が頻発します。
「テストデータでは動いていたのに、本番データを流したら壊れた」という典型パターンなので、
「CSV を読むときに split は使わない」というマイルールを持ってしまっていいくらいです。
改行を含むフィールドを考慮していない
CSV 仕様上、ダブルクォートで囲まれていれば、フィールドの中に改行を含めることもできます。
今回の parseLine は「一行の文字列」を前提にしているので、
「ファイルから読むときに、改行を含むフィールドをどう扱うか」は別途考える必要があります。
実務でそこまで複雑な CSV を扱わないなら、「1レコード=1行」という前提で割り切るのも現実的です。
もし改行を含む可能性があるなら、既存ライブラリ(OpenCSV など)を使うほうが安全です。
既存ライブラリを使うか、自前でいくかの判断
OpenCSV などを知ったうえで、あえて「最小限だけ自前」もアリ
Java には OpenCSV や Jackson CSV モジュールなど、CSV を安全に扱うライブラリがいくつもあります。
本格的に CSV を読み書きするなら、それらを使うのが王道です。
一方で、「業務でよくある“そこまで複雑じゃない CSV”をちょっと読むだけ」という場面では、
ここまでのような「一行分解ユーティリティ」を自前で持っておくのも十分現実的です。
大事なのは、「CSV は split で済むほど単純ではない」「ダブルクォートのルールを守る必要がある」という感覚を持ったうえで、
「どこまで自前でやるか」「どこからライブラリに任せるか」をチームで決めることです。
まとめ:CSV分解ユーティリティで身につけたい感覚
CSV分解は、「カンマを見たら即 split」ではなく、「ダブルクォートの内外を意識しながら読む」ための技です。
押さえておきたい感覚は、まず「一文字ずつ読みながら inQuotes フラグで状態を管理する」こと。
次に、「"" を " に戻す」「行末の最後のフィールドを忘れずに追加する」といった細かいルールをユーティリティに閉じ込めること。
そして、「生成側と分解側のルールをペアにしておくと、往復しても壊れない」という設計の気持ちよさを知ることです。
