martes, 12 de noviembre de 2013

JTABLE, TABLEMODEL Y RENDIMIENTO

Tags

¿Porque implementar Modelos?

Francesc Rosés / lulio 2004


Con frecuencia nos vemos obligados a visualizar una gran cantidad de datos mediante una JTable. El resultado es una carga lenta, un scroll penoso y un consumo de RAM intolerable. Ciertamente, hay alternativas. Uno siempre intenta minimizar los datos que carga e incluso se inventa algún tipo de caché que necesita un montón de pruebas hasta que se da por aceptable. La mayor parte de los datos en las cargas masivas suelen tener su origen en una base de datos a la que accedemos mediante un driver JDBC. En este artículo hago una propuesta de un uso racional tanto del TableModel como de las posibilidades que nos ofrece JDBC 2.0 (y posteriores) para reducir a prácticamente nada el coste de la representación de grandes volúmenes de datos mediante JTables.

¿QUÉ HACEMOS MAL?

Antes de exponer mi propuesta creo que es conveniente analizar algunas de las acciones más frecuentes que nos llevan a la ineficiencia. Usar lo que ya está hecho Bien, “usar lo que ya está hecho” no es malo si lo que está hecho funciona como es debido. Este no es el caso, por ejemplo, de una de las clases “ya hechas” de JTableModel: DefaultTableModel. Los más comodones nos limitamos a usarla directamente, y los que lo son menos, se permiten subclasearla. Sin embargo, DefaultTableModel es el final de una cadena de dos elementos: la interficie JTableModel y la clase abstracta que la implementa: AbstractTableModel. La clase DefaultTableModel no es más que una solución de compromiso que nos ofrece Sun. Yo diría que, en realidad, es tan solo un ejemplo práctico, más o menos afortunado. No leer con detalle las APIs que tocan
En realidad, nuestra primera aproximación se basaba en un “copy/paste” del código de un compañero que en cinco minutos, mientras tomábamos un café, nos contó que eso de las tablas y el JDBC era muy sencillito, pero que el Swing, ya se sabe, es más lento que el caballo del malo. En definitiva, sin ver un Javadoc ni por el forro, nos hemos lanzado a escribir nuestra primera versión de la aplicación. Os invito a hacer una prueba. Preguntad a vuestros compañeros (y a vosotros mismos, claro está) si tienen una bookmark de las APIs de Java en su explorador. Veréis qué poquitos la tienen. 

¿POR QUÉ ESTÁ MAL LO QUE HACEMOS?

Usar lo que ya está hecho: DefaultTableModel La clase DefaultTableModel extiende AbstractTableModel y, si bien para algunos casos simples y con poco volumen de datos puede ser útil, tiene varios problemas. El primero es el uso exhaustivo de la clase Vector. No entraremos en detalles, sólo decir que Vector tiene todos sus métodos sincronizados lo que es bastante caro y, en la mayor parte de los casos, innecesario. Sería más conveniente usar una ArrayList para el almacenamiento de datos. Es equivalente a Vector,
pero no tiene sus métodos sincronizados. Para aquellos casos simples en los que puede ser interesante usar un modelo con las funcionalidades de DefaultTableModel, he desarrollado un nuevo modelo con la misma funcionalidad que DefaultTableModel pero basado en una ArrayList: ArrayListTableModel. Si bien ArrayListTableModel da un mayor rendimiento que DefaultTableModel (un 24% aproximadamente), no es la panacea. Las pruebas se han realizado con una tabla de 68.719 registros que ocupa, aproximadamente, 3,16 MB. Esto significa que, en ambos casos hemos de tener cargados en RAM una List de, al menos, 3,16 MB.

Bueno, hemos conseguido mejorar el rendimiento, pero no los requisitos de memoria. No leer con detalle las APIs que tocan Sé que no se puede leer todo con detalle, pero es conveniente dar una leidita a los Javadoc de las APIs involucradas en nuestro problema y al menos mantener pointers a lo que nos ha parecido interesante (aunque no le hayamos visto una aplicación inmediata). ¿Qué APIs están involucradas en nuestro problema? Básicamente, nueve:

• javax.swing.JTable
• javax.swing.table.JTableModel
• javax.swing.table.AbtractTableModel
• javax.swing.table.TableCellRenderer
• java.sql.Connection
• java.sql.DatabaseMetaData
• java.sql.Statement
• java.sql.ResultSet
• java.sql.ResultSetMetaData

Todas aparecen en la solución propuesta y comentaremos con más detalle, en su momento, los
aspectos que nos interesan de cada una de ellas. SOLUCIÓN CON ArrayList Se recomienda utilizar ArrayList en vez de Vector siempre que podamos. Tened en cuenta que Vector existe desde el JDK 1.0 mientras que ArrayList aparece en JDK 1.2. Algo debe aportar...

Hemos comentado que el factor determinante es que ArrayList no tiene métodos synchronized. Esto implica que si otro thread modifica nuestra ArrayList, hay que hacer la sincronización a mano. Si consideramos que este hecho no es importante para nuestra aplicación, y creo que en el 90% de los casos de modelos de JTable no lo es, podemos usar un modelo basado en ArrayLists en nuestro modelo, Si lo es, podemos seguir usando el consabido DefaultTableModel.


El problema es que Sun no proporciona ningún modelo basado en ArrayList y nos tocará escribirlo desde cero. Yo he escrito un nuevo modelo basado en ArrayList, ArrayListTableModel, que se supone que debe funcionar exactamente igual que DefaultTableModel y lo pongo a vuestra disposición. Mis pruebas de rendimiento, como he comentado más arriba, me dan un 24% de mejora de rendimiento respecto a
DefaultTableModel. Recordad, sin embargo, que nadie nos quita el hecho de tener todos los registros en RAM.

Como es lógico, ArrayListTableModel extiende AbstractTableModel. Si tenéis que escribir vuestro propio modelo, extended siempre AbstractTableModel, no extendáis DefaultTableModel. 


Descargar Articulo - Java Hispono Completo


Nota: Todas las ejercicios que tengo en mi blog estan implementado con modelos de Tablas AbstractTableModel, lo cual es lo recomendado. 

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------