$30 off During Our Annual Pro Sale. View Details »

あつめたデータをどう扱うか

 あつめたデータをどう扱うか

2022.01.30 JJUG ナイトセミナー「コレクションフレームワーク特集」資料

Yuichi.Sakuraba

January 30, 2023
Tweet

More Decks by Yuichi.Sakuraba

Other Decks in Technology

Transcript

  1. あつめたデータをどう扱うか
    Java in the Box
    櫻庭 祐一

    View Slide

  2. Agenda
    • コレクションと繰り返し処理
    • 3種類のループ
    • 特定用途に使用するコレクション
    • 並列/並行処理でのコレクション

    View Slide

  3. 櫻庭 祐一
    • Java歴 27年
    • Javaに関する著作多数
    • WEB+DB Press連載 「見なおそう! モダンJavaの流儀」
    • 書籍「Java SE 7/8 速攻入門」 et al.
    • 日本で1人目のJava Champion
    • JJUG創設メンバー

    View Slide

  4. コレクションと繰り返し処理

    View Slide

  5. コレクションでデータをまとめる…
    • とはいうものの、まとめただけでは意味がない
    • まとめたデータをどうするのか
    • 特にリストではまとめたデータに対して処理を加えることが多い

    View Slide

  6. データの利用例 平均を求める
    • リストに4人の成績がまとめられているので、
    平均点を求める
    • 4人だからいいけど、人数が増えたら?
    • インデックス書き間違えない?
    • 人数が増えたとき人数を更新しわすれない?
    List scores = ...;
    int sum = 0;
    sum += scores.get(0);
    sum += scores.get(1);
    sum += scores.get(2);
    sum += scores.get(3);
    double ave = (double)sum/4;

    View Slide

  7. List scores = ...;
    int sum = 0;
    sum += scores.get(0);
    sum += scores.get(1);
    sum += scores.get(2);
    sum += scores.get(3);
    double ave = (double)sum/4;
    データの利用例 平均を求める
    • リストに4人の成績がまとめられているので、
    平均点を求める
    インデックスが異なるだけで
    同じ処理を繰り返している
    処理をくくりだして
    ループでまとめる

    View Slide

  8. 平均を求める - for文バージョン
    ループ変数 i の制御
    0からコレクションの要素数まで1ずつ増える
    ループ変数をインデックスに使用
    コレクションの要素数を使用
    List scores = ...;
    int sum = 0;
    for (int i = 0; i < scores.size(); i++) {
    sum += scores.get(i);
    }
    double ave = (double)sum/scores.size();

    View Slide

  9. コレクションとループ
    Collection 処理
    Collection 処理
    Collection 処理
    処理

    Collection
    Collection
    Ex 画面表示
    ファイル書き込み
    Ex 統計処理
    検索
    Ex 変換/マッピング
    テーブル操作
    Ex ファイル読み込み

    View Slide

  10. 例 移動平均を求める
    • 1時間ごとの温度データを保持したコレクションから
    直近n時間の移動平均を求める
    List calcMovingAveTemps(List temps, int n) {
    List movingAveTemps = new ArrayList<>();
    for (int i = n-1; i < temps.size(); i++) {
    var movingSum = 0.0;
    for (int j = 0; j < n; j++) {
    movingSum += temps.get(i - j);
    }
    movingAveTemps.add(movingSum/n);
    }
    return movingAveTemps;
    }

    View Slide

  11. for文の特徴
    • ループの制御を自分で行う必要がある
    • インデックスを自由に扱うことができる
    • 行列、テンソルなどのデータ構造も扱いやすい
    • for文は文であるため、ループの処理結果を直接返せない
    • 結果を保持させる変数をループの外側で定義する必要がある

    View Slide

  12. for文の特徴
    • ループの制御を自分で行う必要がある
    • インデックスを自由に扱うことができる
    • 行列、テンソルなどのデータ構造も扱いやすい
    • for文は文であるため、ループの処理結果を直接返せない
    • 結果を保持させる変数をループの外側で定義する必要がある
    とはいうものの、インデックスを直接扱う場合は少ない
    インデックスを不使用なら、ループ制御はやりたくない

    View Slide

  13. for-each文 (拡張for文)
    • ループの制御は不要
    • Iterableインタフェースを実装したクラスで使える
    • List
    • Set
    • Map.entrySet()
    • Map.keySet()
    • Map.values()
    • Queue, Deque

    View Slide

  14. 平均を求める – for-each文バージョン
    scoresの要素がscoreに代入される
    List scores = ...;
    int sum = 0;
    for (var score: scrores) {
    sum += score;
    }
    double ave = (double)sum/scores.size();

    View Slide

  15. List scores = ...;
    int sum = 0;
    for (var score: scrores) {
    sum += score;
    }
    double ave = (double)sum/scores.size();
    平均を求める – for-each文バージョン
    変数sumが書き換え可能
    ループで使用するためfinalにはできない
    変数の再代入を防ぐには…

    View Slide

  16. Stream API
    • ループで行う処理をラムダ式で記述
    • ループではなく、データに対する処理を中心に考える
    • 値を返すことができる
    • ストリームのソースとしてコレクションを使用可能
    • 慣れるまで、ちょっとたいへんかも
    • 慣れてしまえば、とっても便利

    View Slide

  17. final int sum = scores.stream()
    .collect(Collectors.summingInt(x -> x));
    final double ave = (double)sum/scores.size();
    平均を求める – Stream APIバージョン
    final int sum = scores.stream()
    .mapToInt(x -> x)
    .sum();
    final double ave = (double)sum/scores.size();
    final double ave = scores.stream()
    .collect(Collectors.averagingDouble(x -> x));
    Ex 1.
    Ex 2.
    Ex 3.

    View Slide

  18. record Price(ProductID id, int amount) {}
    Price maxPrice = prices.get(0);
    for (int i = 1; i < prices.size(); i++) {
    var price = prices.get(i);
    if (price.amount() > maxPrice.amount()) {
    maxPrice = price;
    }
    }
    例 最大を見つける – for文バージョン
    pricesに要素がない場合
    ArrayIndexOutOfBoundsException発生

    View Slide

  19. 例 最大を見つける – Stream APIバージョン
    pricesに要素がない場合のために
    Optionalクラスが使用される
    比較を行うComparatorインタフェースを
    ラムダ式で記述
    record Price(ProductID id, int amount) {}
    final Optional maxPrice
    = prices.stream()
    .collect(Collectors.maxBy(
    (p1, p2) -> p1.amount() - p2.amount()));

    View Slide

  20. • 顧客情報を保持するコレクションから東京の顧客を抽出
    record Customer(CustomerID id, String name, String prefecture) {}
    List tokyoCustomers = new ArrayList<>();
    for (int i = 0; i < customers.size(); i++) {
    var customer = customers.get(i);
    if (customer.prefecture().equals("Tokyo")) {
    tokyoCustomers.add(customer);
    }
    }
    例 条件に合致した要素を抽出 – for文バージョン

    View Slide

  21. record Customer(CustomerID id, String name, String prefecture) {}
    List tokyoCustomers =
    customers.stream()
    .filter(c -> c.prefecture().equals("Tokyo"))
    .toList();
    例 条件に合致した要素を抽出 – Stream APIバージョン
    for文バージョンの
    If文と同じ条件式

    View Slide

  22. コレクションに対するループのまとめ
    for文 for-each文 Stream API
    ループ制御 必要 不要 不要
    インデックス 〇 × ×
    ループ外の変数アクセス 〇 〇

    final変数 orフィールド
    値を返す × × 〇

    View Slide

  23. コレクションに対するループのまとめ
    • Stream APIで記述できるのであれば、Stream APIを使う
    • インデックスを使う必要があれば、for文を使う
    • for-each文を使う場面はほぼない
    • for-each文で記述できるコードのほとんどが
    Stream APIで記述できる

    View Slide

  24. 特定用途に使われるコレクション

    View Slide

  25. 特定用途に使われるコレクション
    • キュー Queue
    • 両端キュー Deque (Double Ended Queue)
    • 発音はデック

    View Slide

  26. Queue
    • First In, First Out
    • 最初に入れた要素が、最初に取りだされる
    • 先入れ、先出し
    A

    View Slide

  27. Queue
    • First In, First Out
    • 最初に入れた要素が、最初に取りだされる
    • 先入れ、先出し
    A
    B

    View Slide

  28. Queue
    • First In, First Out
    • 最初に入れた要素が、最初に取りだされる
    • 先入れ、先出し
    A
    B
    C

    View Slide

  29. Queue
    • First In, First Out
    • 最初に入れた要素が、最初に取りだされる
    • 先入れ、先出し
    B A
    C
    D

    View Slide

  30. Queue
    • First In, First Out
    • 最初に入れた要素が、最初に取りだされる
    • 先入れ、先出し
    C B
    D A

    View Slide

  31. Queue
    • First In, First Out
    • 最初に入れた要素が、最初に取りだされる
    • 先入れ、先出し
    D C B A

    View Slide

  32. Queue
    • First In, First Out
    • 最初に入れた要素が、最初に取りだされる
    • 先入れ、先出し
    • データを作る側と、使う側を結びつける
    • Publisher-Subscriberパターン
    • イベント駆動アーキテクチャ
    D C B A

    View Slide

  33. Queueの使用例 - GUI
    マウスクリック
    キー入力
    再描画
    GUIで発生したイベントを
    キューに入れる
    MouseEventHandler
    KeyEventHandler
    RepaintManager
    イベントに登録された
    ハンドラーを実行
    • マルチスレッドでのデータ間のやり取り
    • Blocking Queue

    View Slide

  34. Deque
    • First In, Last Out
    • 最初に入れた要素が、最後に取りだされる
    • 先入れ、後出し
    • スタック
    A

    View Slide

  35. Deque
    • First In, Last Out
    • 最初に入れた要素が、最後に取りだされる
    • 先入れ、後出し
    • スタック
    A
    B

    View Slide

  36. Deque
    • First In, Last Out
    • 最初に入れた要素が、最後に取りだされる
    • 先入れ、後出し
    • スタック
    A
    B
    C

    View Slide

  37. Deque
    • First In, Last Out
    • 最初に入れた要素が、最後に取りだされる
    • 先入れ、後出し
    • スタック
    A
    B
    C

    View Slide

  38. Deque
    • First In, Last Out
    • 最初に入れた要素が、最後に取りだされる
    • 先入れ、後出し
    • スタック
    A
    B
    C
    • 状態の保存
    • 逆ポーランド記法演算
    • Undo, Redo

    View Slide

  39. Dequeの使用例 - Undo, Redo
    • 操作をコマンドとして表現し、スタックで管理
    • Undo – スタックの最後のコマンドをキャンセル
    • Undoの回数制限がある場合、逆端からコマンドを削除
    File Open
    画像拡大
    色調整
    例 レタッチ処理

    View Slide

  40. Dequeの使用例 - Undo, Redo
    • 操作をコマンドとして表現し、スタックで管理
    • Undo – スタックの最後のコマンドをキャンセル
    • Undoの回数制限に達したら、逆端からコマンドを削除
    File Open
    画像拡大
    色調整
    例 レタッチ処理
    Undoのためコマンドをキャンセル

    View Slide

  41. 特定用途に使われるコレクションのまとめ
    • コレクションの特徴が特定用途にマッチする場合がある
    • Mapも特定用途に使用される
    • 辞書, Key-Valueストア
    • キャッシュ, メモ化 et al.
    • 標準ライブラリでは提供していないコレクションもある
    • リングバッファなど

    View Slide

  42. 並列/並行処理でのコレクション

    View Slide

  43. 簡単なまとめ
    • 並列/並行処理において
    複数スレッドからアクセスできるコレクションは
    できるだけ使わない

    View Slide

  44. なぜ並列コレクションが必要なのか?
    • 複数スレッドからの同時アクセスで予期せぬ動作が発生
    • 最悪の場合、コレクションが壊れる
    • 例 MapにおけるPut-If-Absent処理
    if (map.get(key) == null) {
    map.put(key, value);
    }

    View Slide

  45. Thread #1
    Thread #2
    If文 put()
    If文 put()
    nullなので
    put実行
    この時点でもnullなので
    put実行しvalueを上書きしてしまう
    これを防ぐためには
    Ifブロックの前にmapをロックし
    単一スレッドからのアクセスに制限する
    同期化

    View Slide

  46. 改訂版 Put If Absent処理
    ReentrantLock lock = new ReentrantLock();
    lock.lock();
    try {
    if (map.get(key) == null) {
    map.put(key, value);
    }
    } finally {
    lock.unlock();
    }
    注: Java 8でMapにputIfAbsentメソッドが追加されたので
    本来はそちらを使うべき

    View Slide

  47. 同期化の問題
    • 単一スレッドからのアクセスに制限するため
    ボトルネックになりやすい
    • スケールするように同期化するのは難しい
    • ある程度、スケールするように同期化したコレクションが
    並列コレクション

    View Slide

  48. 並列コレクション
    • インタフェース
    • BlockingQueu/BlockingDeque
    • ConcurrentMap
    • ConcurrentNavigableMap
    • 主なクラス
    • ConcurrentHashMap
    • ConcurrentLinkedQueue/Deque
    • CopyOnWriteArrayList/Set

    View Slide

  49. 並列コレクションと同時アクセス数のめやす
    同時アクセス数 生成後はReadのみ ほぼRead ReadもWriteも
    2 ~ 3 並列コレクション
    Immutable Collection
    並列コレクション 並列コレクション
    ~ 10程度 並列コレクション
    Immutable Collection
    並列コレクション BlockingQueu/Deque
    10 ~ Immutable Collection 使わない 使わない
    Immutable Collection:
    生成後の要素の追加/変更/削除を行わないコレクション
    Listであれば Collections. unmodifiableList(List)
    並列/並行処理ではコレクションに保持させる要素も
    できるかぎりRecordなどを使ってイミュータブルにする

    View Slide

  50. 並列/並行処理でのコレクションのまとめ
    • 並列コレクションは万能ではない
    • 同時アクセス数が増えたらお手上げ
    • 並列/並行処理ではイミュータブル性が重要
    • コレクションも要素もできるかぎりイミュータブルにする
    • そもそも同時アクセスされるような設計は防ぐ

    View Slide

  51. まとめ
    • コレクションとループは強い結びつき
    • ループの種類ごとの特徴を把握し、使い分ける
    • データ構造と用途の結びつきはいろいろ
    • いつでも使えるように引き出しは多く
    • 並列/並行処理ではデータの同時アクセスに注意
    • イミュータブル性が重要

    View Slide

  52. あつめたデータをどう扱うか
    Java in the Box
    櫻庭 祐一

    View Slide