FileTreeModel sortiert . uiuiui

Status
Nicht offen für weitere Antworten.

m@nu

Bekanntes Mitglied
hi!

ich benutze in meinem TreeModel welches eine dateisystem-struktur anzeigt folgende methoden:

Code:
    private File[] getContent(File parent, boolean needSort) {
        File[] content = parent.listFiles(new FileFilter() {
            public boolean accept(File file) {
                if(file.isDirectory()) {
                    return true;
                } else if(file.isFile() && showFiles) {
                    return true;
                } else {
                    return false;
                }
            }
        });
        
        if(content == null) {
            content = new File[0];
        } else {
            if(needSort) {
    	        ArrayList list = new ArrayList();
    	        for(int i = 0; i < content.length; i++) {
    	            list.add(content[i]);
    	        }
    	        Collections.sort(list, new FileComparator());
    	        list.toArray(content);
            }
        }
        
        /* Rückgabe: */
        return content;
    }
    
    
    // Hilfklassen -------------------------------------------------------------
    /**
     * Diese <code>Comperator</code>-Implementierung vergleicht <code>File</code>-
     * Instanzen.

     * Es wird folgende Sortierung angestrebt:

     *  - Alle Ordner (dem Alphabeth nach sortiert)
     *  - Alle Dateien (dem Alphabeth nach sortiert)
     * 
     * @author Manuel Alabor
     */
    private class FileComparator implements Comparator {
        public int compare(Object o1, Object o2) {
            if(o1 instanceof File && o2 instanceof File) {
                File file1 = (File)o1;
                File file2 = (File)o2;
                
                if((file1.isDirectory() && file2.isDirectory())
                    || (!file1.isDirectory() && !file2.isDirectory())) {
                    // Beide File-Instanzen sind Ordner:
                    // -> Namen der beiden vergleichen
                    return file1.getName().compareTo(file2.getName());
                
                } else if(file1.isDirectory() && !file2.isDirectory()) {
                    // Nur file1 ist ein Ordner:
                    // -> -1
                    return -1;
                } else if(!file1.isDirectory() && file2.isDirectory()) {
                    // Nur file2 ist ein Ordner:
                    // -> 1
                    return 1;
                
                } else {
                    // Sonst immer 0 zurückgeben:
                    return 0;
                }
            
            } else {
                throw new ClassCastException("Cannot compare other objects than File's");
            }
        }
    }

wie ihr euch denken könnt, braucht das ganze relativ lange um den inhalt eines ordners mit vielen unterordnern und dateien einzulesen. (beispiel: windows ordner)
ich konnte die performance über den parameter needSort bereits um einiges verbessern (z.b. für getChildCount wird wohl kaum ein sortiertes array benötigt.
hat jemand vielleicht eine idee wie man das ganze weiter optimieren könnte? vielleicht könnte man das sortieren einfacher gestalten oder das umfüllen in eine arraylist irgendwie vermeiden?

viele dank im voraus & grüsse
m@nu
 

Karl

Aktives Mitglied
Hallo,

die Hilfsklasse Arrays des JDK hat wie Collections eine sort-Methode, der man einen Comparator
mitgeben kann. Umfüllen ist also nicht notwendig.

Ansonsten vielleicht noch (übersichtlicher):

Code:
if((file1.isDirectory() == file2.isDirectory()) {
   return file1.getName().compareTo(file2.getName());  //da ist schon die 0 abgehandelt!
}
else if (file1.isDirectory()) {
    return -1;
}
else {
    return 1;
}

Spürbar schneller wird's aber nicht.

[EDIT] Mir ist noch eine Sache aufgefallen: Die Meldung, dass ein falsches Objekt vergleichen werden soll, erscheint
auch, falls mal ein "null" in die Liste gerät. Das bereitet graue Haare bei der Fehlersuche, weil man immer denkt,
es wäre irgendein anderes Objekt hineingeraten. Da man ja Comparatoren gerne "mal eben" wiederverwendet, ist ein wenig null-Robustheit oder eine detailliertere Fehlermeldung an der Stelle nicht verkehrt. Du kannst ja die Prüfung auf null in den Zweig mit der Fehlermeldung einbauen. Dann kostet Dich das nicht einmal Performance.

Gruß,
Karl
 

Bleiglanz

Gesperrter Benutzer
wie ihr euch denken könnt, braucht das ganze relativ lange um den inhalt eines ordners mit vielen unterordnern und dateien einzulesen. (beispiel: windows ordner)
kann ich mir nicht denken, sollte auch bei ein paar tausend Dateien noch einigermassen schnell sein?!

liest du etwa rekursiv auch gleich die Unterverzeichnisse mit ein?? Das ganze sollte doch "lazy" gemacht werden, d.h. man liest einen Unterordner erst dann, wenn er angeklickt wurde!

und: deine sortierung funktioniert nicht, Dateien werden ja namensweise überhaupt nicht verglichen!

ich würde über den FileFilter zweimal lesen (einmal die Ordner, dann die Files), und dann getrennt sortieren und dann zusammenfügen

wenn du 500 Ordner und 500 Dateien hast, dann kommen bei dir ja zig unnötige Vergleiche vor!
 

Karl

Aktives Mitglied
Hallo Bleiglanz,

doch, die Sortierung funktionierte bei Ihm auch, nur war sie etwas schwer zu lesen, daher mein Hinweis, es umzuformulieren. Ansonsten gebe ich Dir Recht, ein Performanceproblem ist im Comparator nicht zu erwarten.
Ich habe mal spaßeshalber ein Array mit 200.000 Datei- und Unterordner-File-Dummies (zufällige Namen) sortiert: 1080 ms auf P4, kann man also getrost vergessen.
Allerdings sollte der Baum auf jeden Fall lazy Ast für Ast gelesen werden.

Gruß,
Karl
 

m@nu

Bekanntes Mitglied
doch, die sortierung funktioniert.
zuerst kommen alle ordner (A-Z), anschliessend alle dateien (A-Z) ...
nein, ich lese nicht zu beginn alles rekursiv ein... (sonst würd ich ja jedesmal zuerst ne halbe stunde vor'm rechner sitzen ;) )

danke für eure anregungen! ich werd mich heute nachmittag mal daran machen, das ganze ein wenig zu optimieren :)

<edit>
gg sorry, hab die page nicht aktualisiert und eure reply's oben zum thema "funktioniert nicht" übersehen ;)
</edit>
 

m@nu

Bekanntes Mitglied
aaaalso... unten seht ihr meine TreeModel-implementierung komplett.
meiner meinung nach ist das eine "lazy" implementierung... die getChild-methode wird ja erst aufgerufen wenn ein ast aufgeklappt wurde, oder irre ich mich da? (hab das nur mal per sysout getestet... scheint mir eigentlich so zu sein.)

Code:
/*
 * Created on 03.06.2005
 */
package filetree;

import java.io.File;
import java.io.FileFilter;
import java.util.Arrays;
import java.util.Comparator;

import javax.swing.event.TreeModelListener;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;

/**
 * Dieses <code>TreeModel</code> zeigt eine Ordner- & Dateistruktur aus einem
 * Dateisystem an.
 * 
 * @author Manuel Alabor
 */
public class FileTreeModel implements TreeModel {

    /* Objekte: */
    private File root;
    
    /* Einstellungen: */
    private boolean showFiles;
    
    /**
     * Standartkonstruktor.
     * 
     * @param root File-Instanz welche als Root dient
     * @param showFiles Dateien ebenfalls anzeigen
     */
    public FileTreeModel(File root, boolean showFiles) {
        this.root = root;
        this.showFiles = showFiles;
    }
    
    /**
     * Konstruktor welcher automatisch nur Ordner anzeigt.
     * 
     * @param root
     */
    public FileTreeModel(File root) {
        this(root, false);
    }
    
    // TreeModel-Implementierung -----------------------------------------------
    /**
     * Liefert ein Unterelement von <code>parentNode</code> mit dem Index
     * <code>index</code>
     * 
     * @param parentNode
     * @param index
     * @return
     */
    public Object getChild(Object parentNode, int index) {
        File parent = (File)parentNode;
        
        File[] content = getContent(parent, true);
        return content[index];
    }
    
    /**
     * Liefert die Anzahl Unterelement von <code>node</code>.
     * 
     * @param node
     * @return
     */
    public int getChildCount(Object node) {
        int length = 0;
        
        if(node != null) {
            File parent = (File)node;
            File[] content = getContent(parent, false);
        
            length = content.length;
        }
        
        return length;
    }
    
    /**
     * Liefert den Index des Unterelements <code>child</code> unter
     * <code>parentNode</code>.
     * 
     * @param parentNode
     * @param child
     * @return
     */
    public int getIndexOfChild(Object parentNode, Object child) {        
        if(parentNode != null && child != null) {
            File parent = (File)parentNode;
            File[] content = getContent(parent, true);
            
	        for(int i = 0; i < content.length; i++) {
	            File file = content[i];
	            if(file.equals(child)) {
	                return i;
	            }
	        }
        }
        
        return -1;
    }
    
    /**
     * Liefert das Root von diesem Model.
     * 
     * @return
     */
    public Object getRoot() {
        return root;
    }
    
    /**
     * Ermittelt ob <code>node</code> am Ende eines Astes steht.
     * 
     * @param node
     * @return
     */
    public boolean isLeaf(Object node) {
        File file = (File)node;
        
        if(file.isFile()) {
            return true;
        } else if(file.isDirectory()) {
            if(getContent(file, false).length > 0) {
                return false;
            } else {
                return true;
            }
        } else {
            return false;
        }
    }
    
    
    // Event-Handling ----------------------------------------------------------
    public void valueForPathChanged(TreePath arg0, Object arg1) {

    }
    
    
    // Listener-Verwaltung -----------------------------------------------------
    public void removeTreeModelListener(TreeModelListener listener) {

    }
    
    public void addTreeModelListener(TreeModelListener listener) {

    }

    
    // Hilfsmethoden -----------------------------------------------------------
    private File[] getContent(File parent, boolean needSort) {
        File[] content = parent.listFiles(new FileFilter() {
            public boolean accept(File file) {
                if(file.isDirectory()) {
                    return true;
                } else if(file.isFile() && showFiles) {
                    return true;
                } else {
                    return false;
                }
            }
        });
        
        if(content == null) {
            content = new File[0];
        } else {
            if(needSort) {
    	        Arrays.sort(content, new FileComparator());
            }
        }
        
        /* Rückgabe: */
        return content;
    }
    
    
    // Hilfklassen -------------------------------------------------------------
    /**
     * Diese <code>Comperator</code>-Implementierung vergleicht <code>File</code>-
     * Instanzen.

     * Es wird folgende Sortierung angestrebt:

     *  - Alle Ordner (dem Alphabet nach sortiert)
     *  - Alle Dateien (dem Alphabet nach sortiert)
     * 
     * @author Manuel Alabor
     */
    private class FileComparator implements Comparator {
        public int compare(Object o1, Object o2) {
            if(o1 instanceof File && o2 instanceof File) {
                File file1 = (File)o1;
                File file2 = (File)o2;
                
                if((file1.isDirectory() && file2.isDirectory())
                    || (!file1.isDirectory() && !file2.isDirectory())) {
                    // Beide File-Instanzen sind Ordner:
                    // -> Namen der beiden vergleichen
                    return file1.getName().compareTo(file2.getName());
                
                } else if(file1.isDirectory() && !file2.isDirectory()) {
                    // Nur file1 ist ein Ordner:
                    // -> -1
                    return -1;
                } else if(!file1.isDirectory() && file2.isDirectory()) {
                    // Nur file2 ist ein Ordner:
                    // -> 1
                    return 1;
                
                } else {
                    // Sonst immer 0 zurückgeben:
                    return 0;
                }
            
            } else {
                throw new ClassCastException("Cannot compare other objects than File's");
            }
        }
    }
    
}

leider dauert es immernoch relativ lange, bis das JTree seinen inhalt anzeigt.
es kann natürlich sein dass es an meinem büro-rechner liegt... sollte das ganze mal zuhause laufen lassen...
 

Bleiglanz

Gesperrter Benutzer
nicht das Einlesen eines Knotens dauert, sondern die Anzeige!

Warum liest du immer wieder das selbe Direcoty in ein Array, wärs nicht besser du packst das in eine Objektstruktur DirKnoten (bestehend aus File, und dem File[] Inhalt, und einen booleanFlag isLoaded) usw.??

a) warum machst du isLeaf nicht einfach zu "isDirectory", wär doch einfacher und würd auch keinen so grossen unterschied machen...du verbrätst bei der Methode ein ganzes File[] Array, ohne es wirklich zu brauchen

a) getIndexOfChild - getChildCount

auch da rufst du das getContent wieder auf? wärs nicht besser, das erhaltene Array ein für allemal zwischenzuspeichern??
 

m@nu

Bekanntes Mitglied
damit ich dich jetzt richtig verstehe:
ich erstelle intern in meinem model einen cache in welchem ich bereits gelesene daten ablege? resp. ich bilde die bereits gelesenen ordnerstrukturen selber nocheinmal im speicher ab?
 

AlArenal

Top Contributor
Ganz genau.

Als ich mit Javan anfing hatte ich mir mal scherzeshalber zwei TableModels gebaut. Im ersten implementierte ich die Methoden direkt in JDBC um auf meine Daten (hsqldb als in-process DB) zuzugreifen. Im zweiten Fall las ich die benötigten Daten über JDBC einmal ein und ließ das Model über meine eingelesenen Daten hühnern.

Rate mal was schneller war... ;)

Du musst ja nur mal überlegen was für nen Overhead Zugriffe auf Netzwerk oder Dateisystem erzeugen. Allein die Latenzzeiten sind aus Sicht des Java-Proggies Äonen... Da summieren sich Millisekunden schnell zusammen und bei der Masse an Methodenaufrufen reichen schon wenige Datensätze um alles sehr zäh zu machen.
 

m@nu

Bekanntes Mitglied
ok, dann werd ich mich mal dranhängen (hab sowas damals auch in VB6.0 realisiert... da musste ich auch mit caches arbeiten)

werd' meine arbeit ganz opensource-like dann hier wieder posten :) nochma' thx für die anregungen! :)
 

Karl

Aktives Mitglied
Hallo m@nu,

leider habe ich momentan nicht viel Zeit, vielleicht Morgen mehr.
- Du kannst das Cachen on-the-fly in der Methode getContent() abhandeln. Um schnell zu
sein und trotzdem aktuell, bietet es sich an, Timestamps zu benutzen und content nur dann neu
zu ermitteln, wenn mehr als eine Sekunde vergangen ist.
- Aktualisierung funktioniert bei Dir nicht. Ändere spaßeshalber mal einen Dateinamen, nachdem
Du den Baum geladen hast. Das gibt Chaos, weil der Tree das nicht richtig mitbekommt.
- Schau mal, ob Du nicht doch auf TreeNodes umsteigst (DefaultMutableTreeNode ableiten), dann kannst Du als Nutzlast
Dein File mitgeben und Dir noch andere Dinge merken.

Gruß,
Karl
 

m@nu

Bekanntes Mitglied
hm, hier mein erster ansatz:

Code:
/*
 * Created on 03.06.2005
 */
package net.msites.drivesync.data.reusable;

import java.io.File;
import java.io.FileFilter;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Hashtable;

import javax.swing.event.TreeModelListener;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;

/**
 * Dieses <code>TreeModel</code> zeigt eine Ordner- & Dateistruktur aus einem
 * Dateisystem an.
 * 
 * @author Manuel Alabor
 */
public class CachedFileTreeModel implements TreeModel {

    /* Objekte: */
    private FolderInfoBean root;
    private Hashtable cache;
    
    /* Einstellungen: */
    private boolean showFiles;
    
    
    /**
     * Standartkonstruktor.
     * 
     * @param root File-Instanz welche als Root dient
     * @param showFiles Dateien ebenfalls anzeigen
     */
    public CachedFileTreeModel(File root, boolean showFiles) {
        cache = new Hashtable();
        
        this.root = new FolderInfoBean(root);
        cache.put(root.getPath(), this.root);
        this.showFiles = showFiles;
    }
    
    /**
     * Konstruktor welcher automatisch nur Ordner anzeigt.
     * 
     * @param root
     */
    public CachedFileTreeModel(File root) {
        this(root, false);
    }
    
    // TreeModel-Implementierung -----------------------------------------------
    /**
     * Liefert ein Unterelement von <code>parentNode</code> mit dem Index
     * <code>index</code>
     * 
     * @param parentNode
     * @param index
     * @return
     */
    public Object getChild(Object parentNode, int index) {
        File parent = (File)parentNode;

        File[] content = getContent(parent);
        return content[index];
    }
    
    /**
     * Liefert die Anzahl Unterelement von <code>node</code>.
     * 
     * @param node
     * @return
     */
    public int getChildCount(Object node) {
        int length = 0;
        
        if(node != null) {
            File parent = (File)node;
            File[] content = getContent(parent);
        
            length = content.length;
        }
        
        return length;
    }
    
    /**
     * Liefert den Index des Unterelements <code>child</code> unter
     * <code>parentNode</code>.
     * 
     * @param parentNode
     * @param child
     * @return
     */
    public int getIndexOfChild(Object parentNode, Object child) {        
        if(parentNode != null && child != null) {
            File parent = (File)parentNode;
            File[] content = getContent(parent);
            
	        for(int i = 0; i < content.length; i++) {
	            File file = content[i];
	            if(file.equals(child)) {
	                return i;
	            }
	        }
        }
        
        return -1;
    }
    
    /**
     * Liefert das Root von diesem Model.
     * 
     * @return
     */
    public Object getRoot() {
        return root.getFolder();
    }
    
    /**
     * Ermittelt ob <code>node</code> am Ende eines Astes steht.
     * 
     * @param node
     * @return
     */
    public boolean isLeaf(Object node) {
        File file = (File)node;
        
        if(file.isFile()) {
            return true;
        } else if(file.isDirectory()) {
            if(getContent(file).length > 0) {
                return false;
            } else {
                return true;
            }
        } else {
            return false;
        }
    }
    
    
    // Event-Handling ----------------------------------------------------------
    public void valueForPathChanged(TreePath arg0, Object arg1) {

    }
    
    
    // Listener-Verwaltung -----------------------------------------------------
    public void removeTreeModelListener(TreeModelListener listener) {

    }
    
    public void addTreeModelListener(TreeModelListener listener) {

    }

    
    // Hilfsmethoden -----------------------------------------------------------
    private File[] getContent(File parent) {
        
        /* Daten holen: */
        FolderInfoBean cachedFolder;
        String path = parent.getPath();
        
        if(cache.containsKey(path)) {
            // Daten aus Cache holen:
            cachedFolder = (FolderInfoBean)cache.get(path);
        } else {
            // Daten neu einlesen & in Cache schreiben:
            cachedFolder = new FolderInfoBean(parent);
            cache.put(path, cachedFolder);
        }
        
        
        /* Inhalt des Ordners: */
        if(!cachedFolder.isContentLoaded()) {
            // Noch nicht geladen:
            File[] contents = parent.listFiles(new FileFilter() {
                public boolean accept(File file) {
                    if(file.isDirectory()) {
                        return true;
                    } else if(file.isFile() && showFiles) {
                        return true;
                    } else {
                        return false;
                    }
                }
            });
            
            if(contents == null) {
                contents = new File[0];
            } else {
                Arrays.sort(contents, new FileComparator());
            }
            cachedFolder.setContents(contents);
        }
        
        File[] content = cachedFolder.getContents();
        
        /* Rückgabe: */
        return content;
    }
    
    
    // Hilfklassen -------------------------------------------------------------
    /**
     * Diese <code>Comperator</code>-Implementierung vergleicht <code>File</code>-
     * Instanzen.

     * Es wird folgende Sortierung angestrebt:

     *  - Alle Ordner (dem Alphabet nach sortiert)
     *  - Alle Dateien (dem Alphabet nach sortiert)
     * 
     * @author Manuel Alabor
     */
    private class FileComparator implements Comparator {
        public int compare(Object o1, Object o2) {
            if(o1 instanceof File && o2 instanceof File) {
                File file1 = (File)o1;
                File file2 = (File)o2;
                
                if((file1.isDirectory() && file2.isDirectory())
                    || (!file1.isDirectory() && !file2.isDirectory())) {
                    // Beide File-Instanzen sind Ordner:
                    // -> Namen der beiden vergleichen
                    return file1.getName().compareTo(file2.getName());
                
                } else if(file1.isDirectory() && !file2.isDirectory()) {
                    // Nur file1 ist ein Ordner:
                    // -> -1
                    return -1;
                } else if(!file1.isDirectory() && file2.isDirectory()) {
                    // Nur file2 ist ein Ordner:
                    // -> 1
                    return 1;
                
                } else {
                    // Sonst immer 0 zurückgeben:
                    return 0;
                }
            
            } else {
                throw new ClassCastException("Cannot compare other objects than File's");
            }
        }
    }
    
    /**
     * Speichert die Informationen zu einem Ordner und dessen Inhalt.
     * 
     * @author Manuel Alabor
     */
    private class FolderInfoBean {
        
        /* Objekte: */
        private File folder;
        private File[] contents = null;
        
        /**
         * Konstruktor
         * 
         * @param folder
         */
        public FolderInfoBean(File folder) {
            this.folder = folder;
        }
        
        // Getter-Methoden -----------------------------------------------------
        /**
         * Liefert die <code>File</code>-Instanz zu dem Ordner, welches dieses
         * Bean beschreibt.
         * 
         * @return
         */
        public File getFolder() {
            return folder;
        }
        
        /**
         * Prüft ob der Inhalt dieses Ordners bereits geladen wurde.
         * 
         * @return
         */
        public boolean isContentLoaded() {
            if(contents == null) {
                return false;
            } else {
                return true;
            }
        }
        
        /**
         * Liefert den Inhalt dieses Ornders als <code>File</code>-Array.
         * 
         * @return
         */
        public File[] getContents() {
            return contents;
        }
        
        /**
         * Setzt den Inhalt dieses Ordners.
         * 
         * @param contents
         */
        public void setContents(File[] contents) {
            this.contents = contents;
        }
        
    }
    
}

zugegeben, die lösung ist relativ primitiv, da das model nicht auf änderungen in der ordnerstruktur eingeht.
doch ich denke ich werd das ganze in einem ersten schritt mal so verwenden. (ich brauch keinen dateibrowser... nur einen ordnerauswahl-dialog)

das verwenden von eigenen treenodes möchte ich vermeiden. am liebsten "alles aus einer hand" ;) ... reicht mir schon dass ich einen eigenen cellrenderer brauche gg
 

Karl

Aktives Mitglied
Hallo m@nu,

(ich brauch keinen dateibrowser... nur einen ordnerauswahl-dialog)

Upps!? Ich dachte, Du machst das zu Übungszwecken. Es gibt eine sehr gute JDK-Klasse (JFileChooser), mit
der man eine Ordnerauswahl machen kann. Da sollte man nichts eigenes basteln.

Gruß,
Karl
 

m@nu

Bekanntes Mitglied
den JFileChooser hab ich schon versucht... hat mich aber nicht wirklich überzeugt... möchte da mehr den "windows-style"-ordnerdialog haben...
 
Status
Nicht offen für weitere Antworten.

Ähnliche Java Themen

Neue Themen


Oben