3

更新

我已经更新了这个问题以更准确地描述我的问题的原因,并包含了一个比我最初使用的更简单的示例。

我在下面包含了一个简单的示例来显示我遇到的性能问题。当我用一个普通的 ArrayList 支持我的 JXTable 时,它​​的性能相当不错。但是,如果我将 ArrayList 切换为 EventList 并使用 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 支持的表是基于字符串值而不是数值排序的,但我不知道为什么。这是来自分析器的几个屏幕截图,其中有一百万行正在排序。

第一个表

第二张表

有任何想法吗?

4

1 回答 1

5

我遇到的问题是 SwingXTableRowSorterModelWrapper与 GlazedLists 的工作方式的结合TableFormat

使用 GlazedLists 时TableFormat,不为表列提供类类型。当未提供类类型时,JXTable 最终会根据 a 提供的字符串值对列进行排序ComponentProvider。如果ComponentProvider使用转换器构造,FormatStringValue则列中的每个项目将在用于排序期间的比较之前进行格式化。对 的实际调用ComponentProvider发生在TableRowSorterModelWrapper.

就我而言,当我添加自定义渲染器时,我将默认值替换ComponentProviderLabelProvider使用FormatStringValueNumberFormat.getCurrencyInstance().

使用 my 的表SimpleTableModel没有遇到同样的性能问题的原因是它提供了列类类型。由于BigDecimalimplements Comparable,排序操作不需要调用 来ComponentProvider获取(可能格式化的)字符串值。

解决方法很简单;使用 GlazedLists'AdvancedTableFormat而不是TableFormat并为每个表列提供类类型。以下将与我的问题中的示例一起使用。

private static class EventTableModelFormat implements AdvancedTableFormat<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;
    }

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

    @Override
    public Comparator getColumnComparator(int column) {
        return null;
    }
}
于 2012-09-09T23:56:41.230 回答