3

更新しました

この質問を更新して、問題の原因をより正確に説明し、最初に使用したより簡単な例を含めました。

私が抱えているパフォーマンスの問題を示すために、以下に簡単な例を含めました。JXTable を通常の ArrayList でバックアップすると、かなりうまく機能します。ただし、EventList の ArrayList を切り替えて、EventTableModel を使用してテーブルを構築すると、並べ替えが大幅に遅くなります (この場合、約 10 倍遅くなります)。

Maven または Gradle を使用している場合、使用しているアーティファクト座標は次のとおりです。

apply plugin: 'java'
apply plugin: 'application'
mainClassName = "SortPerfMain"

dependencies {
    compile "net.java.dev.glazedlists:glazedlists_java15:1.8.0"
    compile "org.swinglabs.swingx:swingx-core:1.6.4"
}

そして、これがその例です。EventList を使用しようとした唯一の理由は、TableModel の外部で変更して必要な通知を発生させることができるデータ構造が必要だったからです。

    import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.gui.TableFormat;
import ca.odell.glazedlists.swing.EventTableModel;
import org.jdesktop.swingx.JXTable;
import org.jdesktop.swingx.renderer.*;
import org.jdesktop.swingx.table.TableColumnExt;

import javax.swing.*;
import javax.swing.table.*;
import java.awt.*;
import java.math.BigDecimal;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;

import static javax.swing.WindowConstants.EXIT_ON_CLOSE;

/* This class creates a JFrame with two JXTables displayed side by side.  Both
 * tables have a single column that holds Item objects.  Each Item has one
 * property; amount.  The amount property is a BigDecimal, but the performance
 * disparity is still present when using int instead.
 *
 * The first table is backed by a simple ArrayList.  The second table is backed
 * by an EventList (GlazedLists).
 *
 * When sorting 1,000,000 rows, the first table takes about 1 second and the
 * second table takes about 10 seconds.
 */

public class SortPerfMain {
    @SuppressWarnings("FieldCanBeLocal")
    private final boolean useDebugRenderer = true;

    // The number of items that should be added to the model.
    @SuppressWarnings("FieldCanBeLocal")
    private final int itemCount = 2;

    // The number of visible rows in each table.
    @SuppressWarnings("FieldCanBeLocal")
    private final int visibleRowCount = 2;

    public static void main(String[] args) {
        new SortPerfMain();
    }

    public SortPerfMain() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                List<Item> itemList = createItemList();

                JPanel leftPanel = createTablePanel(
                        createTable(createSimpleModel(itemList)));

                JPanel rightPanel = createTablePanel(
                        createTable(createGlazedModel(itemList)));

                JPanel mainPanel = new JPanel(new GridLayout(1, 2));
                mainPanel.add(leftPanel);
                mainPanel.add(rightPanel);

                JFrame mainFrame = new JFrame("Table Sort Perf");
                mainFrame.setContentPane(mainPanel);
                mainFrame.pack();
                mainFrame.setSize(600, mainFrame.getHeight());
                mainFrame.setLocationRelativeTo(null);
                mainFrame.setDefaultCloseOperation(EXIT_ON_CLOSE);
                mainFrame.setVisible(true);
            }
        });
    }

    private List<Item> createItemList() {
        List<Item> itemList = new ArrayList<>(itemCount);
        for (int i = 0; i < itemCount; i++) {
            itemList.add(new Item(i));
        }
        return itemList;
    }

    private JXTable createTable(TableModel model) {
        JXTable table = new JXTable(model);
        table.setVisibleRowCount(visibleRowCount);
        addRenderer(table);
        return table;
    }

    private void addRenderer(JXTable table) {
        TableColumnExt column = table.getColumnExt(Columns.AMOUNT.ordinal());
        column.setCellRenderer(createCurrencyRenderer());
    }

    private JPanel createTablePanel(JXTable table) {
        JLabel panelLabel = new JLabel(table.getModel().getClass().getName());
        JPanel panel = new JPanel(new BorderLayout());

        panel.add(panelLabel, BorderLayout.NORTH);
        panel.add(new JScrollPane(table), BorderLayout.CENTER);

        return panel;
    }

    private TableModel createSimpleModel(List<Item> items) {
        return new SimpleTableModel(items);
    }

    private TableModel createGlazedModel(List<Item> items) {
        EventList<Item> itemList = new BasicEventList<>();
        itemList.addAll(items);
        return new EventTableModel<>(itemList, new EventTableModelFormat());
    }

    private TableCellRenderer createCurrencyRenderer() {
        //noinspection ConstantConditions
        if (useDebugRenderer) {
            return new DebugRenderer();
        }

        return new DefaultTableRenderer(
                new LabelProvider(new FormatStringValue(
                        NumberFormat.getCurrencyInstance())));
    }

    // Enum for managing table columns
    private static enum Columns {
        AMOUNT("Amount", BigDecimal.class);

        private final String name;
        private final Class type;

        private Columns(String name, Class type) {
            this.name = name;
            this.type = type;
        }
    }

    // Each table holds a list of items.
    private static class Item {
        private final BigDecimal amount;

        private Item(BigDecimal amount) {
            this.amount = amount;
        }

        private Item(int amount) {
            this(new BigDecimal(amount));
        }
    }

    // A simple model that doesn't perform any change notification
    private static class SimpleTableModel extends DefaultTableModel {
        private final List<Item> itemList;

        public SimpleTableModel(List<Item> items) {
            this.itemList = items;
        }

        @Override
        public int getRowCount() {
            if (itemList == null) {
                return 0;
            }

            return itemList.size();
        }

        @Override
        public int getColumnCount() {
            return Columns.values().length;
        }

        @Override
        public Object getValueAt(int rowIndex, int columnIndex) {
            switch (Columns.values()[columnIndex]) {
                case AMOUNT:
                    return itemList.get(rowIndex).amount;
            }

            return null;
        }

        @Override
        public String getColumnName(int column) {
            return Columns.values()[column].name;
        }

        @Override
        public Class<?> getColumnClass(int column) {
            return Columns.values()[column].type;
        }
    }

    // Table format for use with the EventTableModel
    private static class EventTableModelFormat implements TableFormat<Item> {
        @Override
        public int getColumnCount() {
            return 1;
        }

        @Override
        public String getColumnName(int i) {
            return Columns.values()[i].name;
        }

        @Override
        public Object getColumnValue(Item item, int i) {
            return item.amount;
        }
    }

    /* The following classes are used to add println statements to the part
     * of the component hierarchy we're interested in for debugging.
     */

    private class DebugRenderer extends DefaultTableRenderer {
        private DebugRenderer() {
            super(new DebugProvider());
        }

        @Override
        public Component getTableCellRendererComponent(
                JTable table,
                Object value,
                boolean isSelected,
                boolean hasFocus,
                int row,
                int column) {
            System.out.println("Renderer requested for " + value.toString());
            return super.getTableCellRendererComponent(
                    table, value, isSelected, hasFocus, row, column);
        }
    }

    private class DebugProvider extends LabelProvider {
        private DebugProvider() {
            super(new DebugFormatter());
        }

        @Override
        public String getString(Object value) {
            System.out.println("Providing string for " + value.toString());
            return super.getString(value);
        }
    }

    private class DebugFormatter extends FormatStringValue {
        private DebugFormatter() {
            super(NumberFormat.getCurrencyInstance());
        }

        @Override
        public String getString(Object value) {
            System.out.println("Formatting object: " + value.toString());
            return super.getString(value);
        }
    }
}

また、EventTableModel に基づくテーブルが、数値ではなく文字列値に基づいて並べ替えられていることにも気付きましたが、その理由はわかりません。これは、100 万行が並べ替えられたプロファイラーのスクリーンショットです。

最初のテーブル

セカンドテーブル

何か案は?

4

1 に答える 1