LoginSignup
2
1

More than 5 years have passed since last update.

Streamでヘッダ行だけスマートに別処理したい

Last updated at Posted at 2018-05-28

はじめに

巨大なテキストファイルを読み込むとき、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のインデックス付与が、スマートな部類かと考えています。
ただ、他にも書き方がある気がしていて、引き続き試行錯誤してみようと思っています。

そもそも、「ストリームで条件分岐すんなや」というのもあるかもしれませんが。。

2
1
9

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1