更新
我已经更新了这个问题以更准确地描述我的问题的原因,并包含了一个比我最初使用的更简单的示例。
我在下面包含了一个简单的示例来显示我遇到的性能问题。当我用一个普通的 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 支持的表是基于字符串值而不是数值排序的,但我不知道为什么。这是来自分析器的几个屏幕截图,其中有一百万行正在排序。
有任何想法吗?