• category: swing folder: FastRemoveOfListItems title: JListからの大量アイテム削除を高速化する tags: [JList, ListModel, AbstractListModel, DefaultListModel, SpringLayout] author: aterai pubdate: 2018-03-19T16:16:24+09:00 description: JListのListModelからの大量のアイテムを高速に削除する方法をテストします。 image: https://drive.google.com/uc?id=1w4uURJH6pPCGk68BT_XlCjxnZQlOxv1n7w hreflang:
       href: https://java-swing-tips.blogspot.com/2018/04/move-large-numbers-of-items-to-another.html
       lang: en

概要

JListListModelからの大量のアイテムを高速に削除する方法をテストします。

サンプルコード

private static <E> void move1(JList<E> from, JList<E> to) {
  ListSelectionModel sm = from.getSelectionModel();
  int[] selectedIndices = from.getSelectedIndices();

  DefaultListModel<E> fromModel = (DefaultListModel<E>) from.getModel();
  DefaultListModel<E> toModel = (DefaultListModel<E>) to.getModel();
  List<E> unselectedValues = new ArrayList<>();
  for (int i = 0; i < fromModel.getSize(); i++) {
    if (!sm.isSelectedIndex(i)) {
      unselectedValues.add(fromModel.getElementAt(i));
    }
  }
  if (selectedIndices.length > 0) {
    for (int i: selectedIndices) {
      toModel.addElement(fromModel.get(i));
    }
    fromModel.clear();
    // unselectedValues.forEach(fromModel::addElement);
    DefaultListModel<E> model = new DefaultListModel<>();
    unselectedValues.forEach(model::addElement);
    from.setModel(model);
  }
}
View in GitHub: Java, Kotlin

解説

上記のサンプルでは、大量のアイテムをもつJListを左右に配置し、選択アイテムを><ボタンで移動するテストを行っています。

  • default remove(5000件)
    • 移動元のJListで選択されているアイテムのインデックスをJList#getSelectedIndices()メソッドで取得
    • 上で取得したインデックス配列をforでループし、移動元のDefaultListModelからget(idx)でアイテムを取得、これを移動先のDefaultListModeladdElement(...)でコピー
    • 上で取得したインデックス配列をforで末尾からループし、移動元のDefaultListModelからremove(idx)でアイテムを削除
    • DefaultListModel#remove(idx)内で実行されるAbstractListModel#fireIntervalRemoved(...)が遅いため、アイテムを大量に削除する場合非常に時間が掛かる
    • DefaultListModel#removeAllElements()で全削除、JListの選択モードがListSelectionModel.MULTIPLE_INTERVAL_SELECTIONDefaultListModel#removeRange(...)メソッドで範囲削除が可能な場合、AbstractListModel#fireIntervalRemoved(...)は最後に一回呼ばれるだけなので高速
  • clear + addElement(20000件)
    • 移動元のJListで選択されていないアイテムを別のArrayListに保存
    • 移動元のJListの選択インデックス配列をforループで回して、移動元のDefaultListModeladdElement(...)でコピー
    • DefaultListModel#clear()で移動元のJListをクリア
    • 保存していた未選択アイテムリストから移動元のJListにアイテムを復元
    • AbstractListModel#fireIntervalRemoved(...)DefaultListModel#clear()で一回呼ばれるだけなので高速
    • リストの先頭の1件を選択して移動すると時間がかかる
      • 先頭を含まない場合は高速
      • DefaultListModelを新規生成して入れ替えれば高速
  • addAll + remove(20000件)
    • AbstractListModelを継承するリストモデルを作成
    • DefaultListModelで使用しているVectorではなく、ArrayListをアイテムの保持に使用
    • 選択アイテムのインデックス配列を引数にしてまとめて削除を実行するメソッドを追加
      • AbstractListModel#fireIntervalRemoved(...)は最後に一回呼ばれるだけなので高速
        public void remove(int... selectedIndices) {
          if (selectedIndices.length > 0) {
            int max = selectedIndices.length - 1;
            for (int i = max; i >= 0; i--) {
              delegate.remove(selectedIndices[i]);
            }
            fireIntervalRemoved(this, selectedIndices[0], selectedIndices[max]);
          }
        }
        

  • JTableでも大量の行を高速に削除する場合は、同様の方法を取る必要がある
    List<Vector> unselectedRows = new ArrayList<>(model.getRowCount());
    ListSelectionModel sm = table.getSelectionModel();
    for (int i = 0; i < model.getRowCount(); i++) {
      if (!sm.isSelectedIndex(i)) {
        int idx = table.convertRowIndexToModel(i);
        unselectedRows.add((Vector) model.getDataVector().get(idx));
      }
    }
    model.setRowCount(0);
    unselectedRows.forEach(model::addRow);
    

参考リンク

コメント