In dem Beispiel erzeugen wir eine große SWT Tabelle wie es in vielen Anwendungen der Fall ist. Denken wir z.B. an eine Ansicht über den Kundenstamm. So eine Tabelle kann locker mal 100000 Einträge haben. Ich habe das ganze erst einmal so geschrieben, wie SWT Tabellen in den meisten Handbüchern erklärt werden. Zuerst wird ein Fenster erstellt. In dem Fenster die Tabelle und dann befülle ich über eine Schleife die Tabelle mit 100000 Zeilen. Das dauert auf meinem Rechner mit Quadcore CPU und reichlich Speicher unerträglich lange 15 Sekunden und zusätzlich zeigt mir der Systemmonitor einen Speicherverbrauch von unvorstellbaren 514 Megabyte für so ein simples Programm an.
Hier mal der Code wie es oft gemacht wird, man es aber bei größeren Tabellen auf keinen Fall machen sollte:
public class TestNormalTable { public static void main(String[] args) { Display display=new Display(); Shell shell=new Shell(display); shell.setSize(600, 1024); shell.setLayout(new FillLayout()); Table table=new Table(shell, SWT.BORDER); table.setLinesVisible(true); table.setHeaderVisible(true); //Spalten erstellen. { TableColumn spalte=new TableColumn(table,SWT.NONE); spalte.setText("Spalte 1"); spalte=new TableColumn(table,SWT.NONE); spalte.setText("Spalte 2"); } //Zeilen befüllen for(int i=0;i<100000;i++){ TableItem zeile=new TableItem(table, SWT.NONE); zeile.setText(0,"Beispieltext zeile "); zeile.setText(1,new Integer(i).toString() ); } //Spaltenbreiten optimieren. for(TableColumn spalte:table.getColumns()){ spalte.pack(); } shell.open(); while(!shell.isDisposed()){ if(!display.readAndDispatch()) display.sleep(); } display.dispose(); } }
Screenshot des erstellen Fensters.
Bei oberflächlicher Betrachtung könnte man annehmen, dass Java einfach nicht schneller eine so simple Schleife mit 100.000 Wiederholungen durchlaufen kann. Wenn man das Programm aber durch einen Profiler schickt, dann sieht man sehr schnell, dass die verbrauchte Zeit und auch der verbrauchte Speicher gar nicht im Javacode steckt, sondern dass die meiste Zeit in nativen Code des Fenstertoolkit verschwendet wird. SWT zeichnet seine Komponenten zu denen auch die Tabelle gehört ja nicht selbst, sondern verwendet das native Fenstertoolkit der Plattform auf der das Programm läuft. In meinem Fall GTK+. Die Frage ist jetzt wie kann man vermeiden, dass bei einer so großen Tabelle alle nativen Tableitems erstellt werden müssen. Die Antwort ist ziemlich einfach und auch sehr effektiv. Man muss eine sogenannte Virtuelle SWT Tabelle verwenden.
Bei der virtuellen Tabelle wird die Tabelle mit dem style 'SWT.VIRTUAL' erstellt werden. Damit signalisiert man SWT, dass man eine Tabelle haben will, die dynamisch je nach Bedarf befüllt wird.
Hier mal der Code wie man eine hochperformante große Tabelle erstellt:
public class TestVirtualTable { /** * @param args */ public static void main(String[] args) { final String[] spalte1=new String[100000]; final int[] spalte2=new int[100000]; Display display = new Display(); Shell shell = new Shell(display); shell.setSize(600, 1024); shell.setLayout(new FillLayout()); final Table table = new Table(shell, SWT.BORDER | SWT.VIRTUAL); table.setLinesVisible(true); table.setHeaderVisible(true); //Arraytabelle befüllen. for(int i=0;i<100000;i++){ spalte1[i]="Beispieltext zeile"; spalte2[i]=i; } // Spalten erstellen. { TableColumn spalte = new TableColumn(table, SWT.NONE); spalte.setText("Spalte 1"); spalte = new TableColumn(table, SWT.NONE); spalte.setText("Spalte 2"); } // Grösse der Tabelle festlegen table.setItemCount(100000); // Callback Listener zum Auslesen der Daten hinzufügen. // Immer wenn eine neue Zeile am Bildschirm angezeigt wird, wird dieser // Listener aufgerufen um Daten anzufordern. // Es werden also nur die Zeilen erstellt, die der Benutzer wirklich // anschaut. table.addListener(SWT.SetData, new Listener() { @Override public void handleEvent(Event event) { TableItem zeile = (TableItem) event.item; // Ermittelen des Zeilenindex. Im echten Leben würde man mit // diesem Index z.B. auf ein Array zugreifen. int index = table.indexOf(zeile); zeile.setText(0, spalte1[index]); zeile.setText(1, new Integer(spalte2[index]).toString()); } }); // Spaltenbreiten optimieren. // Da die virtuelle Tabelle erst nach dem Anzeigen befüllt wird, müssen // wir die Spaltenbreitenoptimierung in die Eventwarteschlange hinten // anfügen. // Damit wird die Spaltenbreitenoptimierung nach dem Anzeigen der ersten // Sätze ausgeführt und sollte schon gute Ergebnisse bringen. Display.getDefault().asyncExec(new Runnable() { @Override public void run() { for (TableColumn spalte : table.getColumns()) { spalte.pack(); } } }); shell.open(); while (!shell.isDisposed()) { if (!display.readAndDispatch()) display.sleep(); } display.dispose(); } }
Diese Version wird auf meinen Rechner praktisch verzögerungsfrei angezeigt. Zusätzlich belegt sie nur etwas über 34 Megabyte Hauptspeicher. Bei völlig gleichem Ergebnis.
Hier nocheinmal im Detail die wichtigen Punkte:
- Zeile 13 Die Tabelle mit dem style 'SWT.VIRTUAL' erstellen.
- Zeile 29 mit setItemCount der Tabelle mitteilen wie viele Items die Tabelle hat.
- Zeile 35-45 einen Listener der der Tabelle hinzufügen, der jedes mal aufgerufen wird, wenn die Tabelle neue Items darstellen will.
- Zeile 52-59 Da die Tabelle vor dem anzeigen noch keine Items hat, funktioniert der pack zum Festlegen der Spaltenbreite nicht, wenn man es wie im Code ohne Virtual macht. Ich setze daher den Code in ein Runnable dass ich mit asyncExec in die Eventqueue stelle. Dieser Code, wird dann nach dem Anzeigen der Tabelle ausgeführt. Dann hat die Tabelle schon die Items der ersten Seite und die Spaltenbreitenoptmierung wird dann anhand dieser Daten gemacht.
Wie man an diesen Beispiel sieht, können oft schon kleine Änderungen an einem Programm den Unterschied machen ob das Programm ein quälend langsamer Ressourcenfresser oder hochperformant und effizient ist. Ich verwende die virtuellen Tabellen übrigens z.B. in einem Programm zur Anzeige der Änderungen im Journal unserer DB2 durchgeführt werden. Wenn man da die Änderungen eines ganzen Tages aufruft, dann kommen schnell mal 2 Millionen Änderungen zusammen. Auch diese riesen Tabellen werden Dank SWT.VIRTUAL ohne Verzögerungen angezeigt.
Danke für den Tipp (und die Erklärung).
ReplyDeleteKeine Ursache, freut mich wenn der Tipp jemanden nützt.
ReplyDelete