はじめに
Java の実務で。「数十万件のCSVデータを処理するバッチ」「DBから大量レコードを取得して集計する処理」等、大量データを扱う場面は少なくありません。こうした処理でよく発生するのが、
- 本番で突然メモリ不足(OutOfMemoryError)
- 開発環境では動くのに、本番では異常に遅い
といったトラブルです。
今回は、Javaで大量データを「安全かつ高速」に処理するための考え方と実装ポイントを、記載していきたいと思います。
他にも、体系的にJavaを学びたい方には以下の教材がおすすめです:
👉スッキリわかるJava入門
👉スッキリわかるJava入門 実践編
Stream と for文、どちらを使うべきか
結論から言うと、以下のような使い分けとなります。
- 可読性重視・中小規模データ :Stream
- 性能重視・大量データ : for文
Streamのメリットとデメリット
list.stream()
.filter(x -> x > 10)
.map(x -> x * 2)
.forEach(System.out::println);
メリット
- 処理の流れが宣言的で読みやすい
- 処理内容がシンプルに書ける
デメリット
- 中間処理オブジェクトが増える
- デバッグしづらい
- 無意識に一度に全件メモリを載せてしまう
for文が向いている理由
for (int i = 0; i < list.size(); i++) {
int value = list.get(i);
if (value > 10) {
System.out.println(value * 2);
}
}
- オブジェクト生成がStreamと比べて少ない
- 処理の流れが追いやすい
- ブレークポイントでデバッグしやすい
メモリを食い潰すNG例
例① 全件をListに溜め込む
件数が増えると一気にメモリを消費するため、一度に全件Listに溜め込む行為は、大量データを扱う上で、エラーとなる危険性が高いです。
List<Data> all = repository.findAll();
例② Streamでcollectしてから処理
Streamでcollectを行う場合、一度すべてメモリに保持が行われるため、こちらも大量データを扱う上で、エラーとなる危険性が高いです。
List<Result> results = list.stream()
.map(this::convert)
.collect(Collectors.toList());
例③ ログを出しすぎる
大量にrp具を出してしまうと、I/O処理が多くなり、処理時間が遅くなってしまいます。大量データを実施するさいはログの粒度を下げた設計とするのが良いです。
ページング処理で大量データを安全に扱う
大量データを安全に扱うには、一度に全て処理しないことを心がけます。
- 一定件数ずつ処理
- メモリ使用量が安定
- 途中失敗しても再開しやすい
- 処理時間の見積もりがしやすい
例
int pageSize = 1000;
int offset = 0;
while (true) {
List<Data> page = repository.findPage(offset, pageSize);
if (page.isEmpty()) {
break;
}
for (Data data : page) {
process(data);
}
offset += pageSize;
}
まとめ
大量データ処理は、速く書くことではなく、最後まで安全に処理できる設計を選ぶことです。Streamは可読性が高く便利ですが、大量データでは for文やページング処理の方が、メモリ・デバッグ・安定性の面で有利になるケースが多くあります。
今回の記事を参考に、大量データの処理を意識して処理を実施してみてください。
ドキュメント
【公式ドキュメント】
Java SE Specifications (oracle.com)
最後に
Javaの環境構築は、この記事を参照してみてください。
【開発環境構築】VS CodeでJavaを使用するための環境構築を実施する – SEもりのLog (selifemorizo.com)
以上、ログになります。
これからも継続していきましょう!!


コメント