¿Por qué mi JXTable ordena mucho más lento cuando cambio a EventTableModel de GlazedLists?
Frecuentes
Visto 566 veces
3
Actualizado
He actualizado esta pregunta para describir con mayor precisión la causa de mi problema y he incluido un ejemplo más simple que el que usé originalmente.
He incluido un ejemplo simple a continuación para mostrar el problema de rendimiento que tengo. Cuando respaldo mi JXTable con un ArrayList normal, funciona razonablemente bien. Sin embargo, si cambio ArrayList por EventList y construyo la tabla usando un EventTableModel, la clasificación es mucho más lenta (~10 veces más lenta en este caso).
Si usa Maven o Gradle, aquí están las coordenadas del artefacto que estoy usando.
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"
}
Y aquí está el ejemplo. La única razón por la que estaba tratando de usar una EventList es porque quería una estructura de datos que pudiera modificar fuera del TableModel y que se produjera la notificación necesaria.
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);
}
}
}
También noté que la tabla respaldada por EventTableModel se ordena según los valores de cadena en lugar de los valores numéricos, pero no estoy seguro de por qué. Aquí hay un par de capturas de pantalla del generador de perfiles con un millón de filas ordenadas.
¿Alguna idea?
1 Respuestas
5
El problema que estaba teniendo con esto era una combinación de la forma en que SwingX TableRowSorterModelWrapper
funciona con GlazedLists' TableFormat
.
Al usar GlazedLists' TableFormat
los tipos de clase no se proporcionan para las columnas de la tabla. Cuando no se proporciona el tipo de clase, JXTable terminará ordenando la columna en función de los valores de cadena proporcionados por un ComponentProvider
. Si el ComponentProvider
se construye con un FormatStringValue
convertidor, cada elemento de la columna se formateará antes de ser utilizado para la comparación durante una ordenación. La llamada real a la ComponentProvider
sucede en el TableRowSorterModelWrapper
.
En mi caso, cuando agregué el renderizador personalizado, reemplacé el predeterminado ComponentProvider
con un LabelProvider
que estaba usando un FormatStringValue
que estaba usando el formateador devuelto de NumberFormat.getCurrencyInstance()
.
La razón por la que la tabla usa mi SimpleTableModel
no sufrió los mismos problemas de rendimiento porque proporcionó tipos de clase de columna. Ya que BigDecimal
implementos Comparable
, las operaciones de clasificación no requerían una llamada al ComponentProvider
para obtener un valor de cadena (posiblemente formateado).
La solución es muy simple; usar listas esmaltadas' AdvancedTableFormat
en lugar de TableFormat
y proporcione los tipos de clase para cada columna de la tabla. Lo siguiente funcionará con el ejemplo en mi pregunta.
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;
}
}
Respondido el 10 de Septiembre de 12 a las 00:09
No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas java swing swingx glazedlists jxtable or haz tu propia pregunta.
hmm ... el clasificador de filas no debe llamarse en absoluto (glasedLists se hace cargo por completo de la clasificación y el filtrado). Intente configurar AutoCreateRowSorter en falso, mejor antes de configurar el modelo. Curioso: ¿cuál es el motivo para crear una subclase de ComponentProvider en lugar de configurarlo con un FormattedStringValue y una alineación configurados? - kleopatra
@kleopatra setAutoCreateRowSorter (falso) no ayudó, pero lo descubrí. Estaba subclasificando ComponentProvider para agregar algunas declaraciones prinln durante la depuración. Actualizaré la pregunta y publicaré una respuesta en breve. Sus respuestas en muchos sitios diferentes me han ayudado en varias ocasiones, así que gracias. - Ryan J