はじめに
巨大なテキストファイルを読み込むとき、1行目だけ別処理したくなる場合って、ありますよね??
たとえばCSVファイルの1行目にヘッダが書かれていて、カラム名などを保存しておきたい、とか。
どうやったらスマートに記述できるのか、いろいろ試行錯誤してみました。
ちなみに、ParallelStreamは対象外です。
試行錯誤その1 男は黙ってwhile文!
Streamはどこへやら。。
try (BufferedReader reader = Files.newBufferedReader(Paths.get(file))) {
String line;
for (int index = 0; (line = reader.readLine()) != null; index++) {
if (index == 0) {
System.out.println("Header: " + line);
} else {
System.out.println("Body: " + line);
}
}
}
(line = reader.readLine()) != null
とか、排除したいですね。
でもメリットはあります。Java7で使える!🌼
レガシーなシステムで制約がある場合は、必然的にこうなるかと思います。
試行錯誤その2 思い切ってスキップ
スキップだと、元の要件、満たしていないですね💦
でも、コードはスマートです。。
try (Stream<String> stream = Files.lines(Paths.get(file))) {
stream.skip(1).forEach(e -> System.out.println("Body : " + e));
}
スキップしてしまうと、スキップした分のデータが取れないのは、困ります。
試行錯誤その3 判定を配列で
自前で「ヘッダかどうか」の判定処理を入れるとなると、ラムダ式の外に変数を持たせる必要が出てきます。
ラムダ式の外の変数をラムダ式の中で使用しようとすると、その変数は自動的に final
になるため、値の上書きができない!
となると、値の書き換えができるように、「配列にする」という策が出てきます。
try (Stream<String> stream = Files.lines(Paths.get(file))) {
boolean[] firstLine = {true};
stream.forEach(e -> {
if (firstLine[0]) {
System.out.println("Header: " + e);
firstLine[0] = false;
} else {
System.out.println(e);
}
});
}
わざわざヘッダかどうかを判定するだけのために、配列を導入するのは、意図が違っているので、スマートではないですね。
試行錯誤その4 配列がダメならオブジェクトで
final
な変数になっても、オブジェクトなら中身を書き換えれられる!
ということで、 AtomicBoolean
の登場です。
try (Stream<String> stream = Files.lines(Paths.get(file))) {
AtomicBoolean firstLine = new AtomicBoolean(true);
stream.forEach(e -> {
if (firstLine.getAndSet(false)) {
System.out.println("Header: " + e);
} else {
System.out.println("Body : " + e);
}
});
}
これも用途が微妙に違うので、すっきりしません。無理やり感がにじみ出ています。。
ちなみに、「Atomicを使っているから、ParallelStreamでも対応できるじゃないか」と思われる方もいらっしゃるかもしれませんが、firstLine
変数の操作がAtomicなだけであって、本当に最初の1行が最初に getAndSet
されるかは、定かではありません(と思っています)。
試行錯誤その5 Iteratorに変換
StreamをIteratorにすると、意外と便利かも?
try (Stream<String> stream = Files.lines(Paths.get(file))) {
Iterator<String> iterator = stream.iterator();
System.out.println("Header: " + iterator.next());
while (lineIterator.hasNext()) {
System.out.println("Body : " + iterator.next());
}
}
ヘッダ行があるかどうかのチェックは省略していますが、変なオブジェクトとか登場させずに済むので、まだ良い方なんじゃないかと思います。
試行錯誤その6 インデックスを付けちゃえ
世の中には便利なライブラリがあるじゃないか!
と、protonpackというライブラリを使えば、インデックス付きのストリームができあがります。
try (Stream<String> stream = Files.lines(Paths.get(file))) {
StreamUtils.zipWithIndex(stream).forEach(e -> {
if (e.getIndex() == 0) {
System.out.println("Header: " + e.getValue());
} else {
System.out.println("Body : " + e.getValue());
}
});
}
インデックスが付いていると、ヘッダ行かどうかだけでなく、応用が利きそう。
結論
個人的には、5のIteratorか6のインデックス付与が、スマートな部類かと考えています。
ただ、他にも書き方がある気がしていて、引き続き試行錯誤してみようと思っています。
そもそも、「ストリームで条件分岐すんなや」というのもあるかもしれませんが。。