Sinnfragen, um eine riesige Verzeichnisstruktur zu durchsuchen und zu verarbeiten

piro

Aktives Mitglied
Hallo zusammen,

ich möchte euch gerne einmal fragen, wie Ihr die folgende Aufgabenstellung umsetzen würdet.

Es handelt sich um ein Verzeichnis mit bis zu 100 Ordner, die wiederum mehrere Unterordner besitzen und die ebenfalls mehrere Unterordner besitzen; bis in einem Ordner nur noch Datei sind, die dann ausgewertet werden sollen. Das heißt, in einem Ordner mit Dateien ist die Namensgebung definiert, d.h. z.B.: 12345_1_blabla.tif.

Die ersten 5 Stellen sind die Sachnummer und danach kommt der Index (Alter der Datei). Es kann vorkommen, dass diese Datei mit unterschiedlichen Indexen existiert.

Beispiel für einen Ordner
Code:
12345_1_blabla.tif
12345_2_blabla.tif
12345_3_blabla.tif
11223_2_blubblub.tif
11223_4_blubblub.tif
11223_5_blubblub.tif
Davon dürfen nur folgende Dateien bestehen bleiben und die anderen müssen verschoben werden.
Code:
12345_3_blabla.tif
11223_5_blubblub.tif

Ich muss dafür sorgen, dass die Datei in Bezug auf die ersten 5 Stellen mit dem höchsten Index im Verzeichnis bleibt und die anderen Dateien, sollen in einen speziellen Ordner.

Mein Vorgehen wäre folgendes.
1. Ich lese die gesamte Verzeichnisstruktur rekrusiv in eine HSQLDB ein mit folgenden Spalten
- Dateiname
- Index (7. Stelle, Zerlegung des Dateinamen)
- Pfad der Datei
2. Dann kann ich mit SQL Befehlen, die Dateien auslesen, die relevant sind bzw. zu alt und kann sie verschieben.

Was haltet ihr davon? Macht das Sinn oder sollte man einen anderen Weg verfolgen.

Vielen Dank im Voraus für Eure Anregung oder Beiträge.
Sven
 
Hm. Um wie viele Dateien geht's denn da? Das "on the fly" programmatisch zu machen könnte den Vorteil haben, dass man das ganze auch problemlos auf viele Dateien anwenden kann, ohne eine riesige DB füllen zu müssen. Ist aber nur ein erster Gedanke...
 
einfach so was:
Java:
Queue<File> folders = new Queue<File>();
folders.add(root);
do {
    File current = folders.poll();
    // add child folders to queue
    // [...]
    // process children
    // [...]
} while (!folders.isEmpty());
😉
 
Wieso mit einer Queue?
Wie wäre es, wenn du eine Map hast, die für die ersten 5 Ziffern ein File zuordnet. Also
Code:
Map<String, File>
oder
Code:
Map<Integer, File>
(innerhalb einer Directory). Wenn du über alle Files iterierst, schaust du zuerst nach, ob die ersten fünf Ziffern bereits in dieser Map sind - falls ja, wird überprüft, welcher Index größer ist (via
Code:
Integer.valueOf(myFile.getName().substring(7, 8 /* oder eine passendere Methode für mehrstellige Zahlen */) ).compareTo( map.get( myIndex ).getName().substring(7, 8) );
Falls die Datei in der Map den größeren Index hat, wird
Code:
myFile
in den speziellen Ordner geworfen. Ansonsten wird
Code:
map.get( myIndex )
in den speziellen Ordner geworfen und
Code:
myFile
übernimmt nun den Platz in der Map.
 
Ich würde das auch eher mit ' ner Index-Sequentiellen Datei machen, dessen Datenformat ich selbst bestimme, statt eine Datenbank zu bemühen, ansonsten wäre der Ansatz dafür aber okay.
Fragwürdig ist aber die Verzeichnisstruktur. Müssen die Indices der Dateien, welche anscheinend die Anzahl der erstellten Backups darstellt, Teil des Dateinamens sein? Wäre es nicht günstiger der bestehenden Verzeichnisstruktur eine Ebene hinzuzufügen genauer gesagt voranzustellen?
Code:
CurrentCopy
Backup0
Backup1
Backup2
...
BackupN
So wäre es egal, wie die Dateien heissen oder in welchem Unterverzeichnis sie sich befinden, das wird in jedem BU-Verzeichnis derselbe sein. Will ich nun BUs einer oder mehrerer spezieller Dateien löschen, ändere ich CurrentCopy in fortlaufend Backup0, Backup1 usw. und lösche sie dort. Will ich nun rigoros alle Backups löschen, lösche ich halt die Verzeichnisse Backup0 - BackupN. Backups erstellen geht natürlich ebenso einfach; [c]rename "CurrentCopy/..." to "Backup0/..."[/c] rekursiv mit fileExists bis zum letzten BU-Verzeichnis, im welchen Vorhandene Dateien gleich überschrieben werden. Im übrigen benötige ich für dieses Verfahren weder eine Map noch eine Queue, es sei denn, ich wollte meine Bilder zwecks schnellem Wiederauffinden indizieren. Dann würde ich auch zu 'ner TreeMap greifen.
 
Zuletzt bearbeitet von einem Moderator:
Mujahiddin, ob Stack oder Queue spielt keine Rolle. Fakt ist, dass man aber immer ein Element (Ordner) benötigt, um weiterzuarbeiten. Man könnte also auch [c]File current = list.remove(0);[/c] verwenden...
 
Rekursion ist m.E. immer "so eine Sache". Zum Durchsuchen von Verzeichnisstrukturen gehe ich jedenfalls immer so vor :

Java:
import java.io.File;
import java.util.Stack;

public class SearchFileTest {

	/**
	 * 
	 */
	private void listFiles(File root) {
		Stack<File> directories = new Stack<File>();
		if (root != null && root.isDirectory()) {
			directories.push(root);
			while (directories.size() > 0) {
				File currentDirectory = directories.pop();
				File[] files = currentDirectory.listFiles();
				if (files != null) {
					for (int i = 0; i < files.length; i++) {
						System.out.println("files[" + i + "] = " + files[i]);
						if (files[i] != null && files[i].isDirectory()) {
							directories.push(files[i]);
						} else if (files[i] != null && files[i].isFile()) {
							// ...
						}
					}
				}
			}
		}
	}

	/**
	 * 
	 */
	public static void main(String[] args) {
		// new File("C:/Program Files") / new File("C:/Programme")
		new SearchFileTest().listFiles(new File("C:/Program Files"));
	}
}
 
Genaugenommen ist das der einzige (!) Unterschied zwischen einer Tiefen- und einer Breitensuche 🙂

Warum Tiefensuche, wenn er die Files in einer Directory untereinander vergleichen muss? Wenn er zwei Dateien "12345_1_blabla.txt" und "12345_2_blabla.txt" in dem gleichen Ordner hat, dann muss die erste Datei in einen anderen Ordner. Wenn die sich in anderen Ordnern befinden, ist das egal (laut OP)
 
Die Dateien können nur in einem Verzeichnis, also dem letzten Unterordner eines jenen Hauptordners sein.

Ich verwende folgenden Code, um die Dateien auszulesen.
Java:
public void listDir(File dir) {
 
        File[] files = dir.listFiles();
        if (files != null) {
            for (int i = 0; i < files.length; i++) {
                
                if (files[i].isDirectory()) {
                    listDir(files[i]);
                } else {
                    System.out.println("Datei     : " + files[i].getName());
                    System.out.println("Pfad      : " + files[i].getParent());
                    System.out.println("Datei+Pfad: " + files[i].getAbsolutePath());
                    System.out.println("");
                    
                }
            }
        }
    }

Mein Ansatz war jetzt die Verzeichnisse rückwärts zu lesen und dann den Vergleich auf den Index zu machen. Dadurch habe ich meinen Code abgeändert.
Code:
for (int i = files.length-1; i >= 0; i--)

Würde mein Ansatz Sinn machen oder sollte auf die oben beschrieben Methode, z.B. Map umschwenken und es damit machen?
Bin noch ziemlich neu mit Java unterwegs. Sorry für die Fragen.

Sven
 
Zuletzt bearbeitet:
Benutze die Methode
Code:
Files.walkFileTree
Deine Methode ist rekursiv, es handelt sich um eine Tiefensuche. Es ist also gut möglich, dass deine Methode in einen Unterordner wechselt, ohne die Dateien im momentanen Ordner abgearbeitet zu haben (ich bezweifle, dass das dein Vorhaben ist)

Du bräuctest sowas:

Java:
try {
	Files.walkFileTree( superdir, new SimpleFileVisitor<Path>() {
		
		@Override
		public FileVisitResult visitFile(Path file, BasicFileAttributes bfa) {
			// hier kommt dein Code rein, der "file" abarbeitet.
			// evtl ein "myMap.put( file.getInfoX(), file );" oder was weiß ich.
			return FileVisitResult.CONTINUE;
		}
		
		@Override
		public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
			// Hier kommt dein Code rein, der den Anfang einer neuen Directory 'einläutet'
			// evtl ein "myMap.clear();"
			return FileVisitResult.CONTINUE;
		}
	});
} catch( IOException e ) {
	e.printStackTrace();
}

Code:
Files.walkFileTree
iteriert, soweit ich weiß, erst über alle regulären Dateien, bevor die Unterverzeichnisse abgeklappert werden. (Basiert nur auf bisherige Erfahrung)... Sollte das nicht immer so sein, wäre eine Lösung, eine
Code:
Map<Path /* dir */, Map<Object /* identifier */, Path /* file */>>
zu erstellen, die jedem Verzeichnis eine Map zuweist (oder eben ein eigener Algorithmus mit Breitensuche).
 
Zuletzt bearbeitet:
@Mujahiddin: Danke für den Tipp. Ich dachte immer er arbeitet erst ein Verzeichnis ab und geht dann weiter.
In meinen Tests, hat es bis jetzt immer gut geklappt und er hat alles ordentlich durchsucht.

WalkFileTree ist ja schon um einiges komplexer, muss ich mich erstmal einarbeiten.

Können das Anderer von Euch bestätigen, dass er beim Rekrusiven Durchsuchen auch mal in ein anderes Unterverzeichnis springt obwohl er noch nicht alle Dateien verarbeitet hat? Oder ist das eine generelle Eigenschaft von rekrusiven Suchen.

Danke.
Sven
 
Das dürfte OS- genauer gesagt Dateisystem abhängig sein. In Windows lässt sich das Verhalten leicht nachvollziehen. wenn man grössere Ordnerstrukturen kopiert. Anno dazumal (bis zu welcher Version das so war, weiss ich nicht) wurde nur alphabetisch sortiert kopiert, heute kopiert Windows erst alle Dateien und dann die Ordner. Mit eigenen implementationen bekommt man durchaus auch wieder das alte Verfahren hin.
Aber ob das sinnvoll ist, ist die zweite Frage: Welches Dateisystem ist wohl effizienter? Eines, welches die Verzeichnisse als Node-Tree verwaltet und in den einzelnen Nodes nur noch Dateiverweise hält oder eines, welches Pfadnamenteile in Clustern ablegt, wo die einzelnen Einträge noch mindestens ein Flag mehr (isDirectory) benötigen?
 
Ich würde mich grundsätzlich nicht darauf verlassen, dass er die Dateien vor den Verzeichnissen auflistet (bei File#list / File#listFiles)... Und selbst wenn es heute in (fast) allen Implementationen der Fall ist, wird ein Leser des Codes schnell verwirrt!

Auch bei walkFileTree scheint es nicht sichergestellt zu sein, dass zuerst die normalen Dateien kommen.

Du willst ja Breitensuche anwenden. Verwende am besten eine eigenständige Methode. Etwas wie das hier:

Java:
/** breadth-first file tree walker */
public static void fileTreeWalker(Path start) throws IOException {
	if( !Files.isDirectory( start ) )
		throw new IllegalArgumentException( "Starting path must be a directory!" );
	Queue<Path> queue = new LinkedList<>();
	queue.offer( start );
	while( !queue.isEmpty() ) {
		Path curr = queue.poll();
		if( curr == null ) {
			preVisitDirectory();
			continue;
		}
		if( Files.isDirectory( curr ) ) {
			queue.offer( null );/* null == Beginn eines Directories */
			for( Path p : Files.newDirectoryStream( curr ) )
				queue.offer( p );
		} else if( Files.isRegularFile( curr ) ) {
			visitFile( curr );
		}
	}
}

private static void visitFile(Path path) {
	/* do something with path */
}

private static void preVisitDirectory() {
	/* map.clear() oder was du auch willst */
}


[OT]Mal ne Frage, falls jemand das hier zufällig liest:
Gibt es keine normale Queue<>, die man einfach verwenden kann? LinkedList<> scheint schon für eine simple Queue zu viel (finde ich), gibt es keine vordefinierte Java-Klasse die eine normale [non-blocking] Queue implementiert?[/OT]
 
Zuletzt bearbeitet:
Sorry fürs erneute Nachfragen.

Meine Verzeichnisstruktur sieht wie folgt aus und wird sich auch nicht ändern.

Code:
-- Startordner
       |
       |--- Ordner 01
                |--- Ordner 010
                         |--- Ordner 010xxx
                                   |--- Datei 010000_01.tif
                                   |--- Datei 010011_02.tif --> die zu alt und wird weggeschoben
                                   |--- Datei 010011_03.tif
                         |--- Ordner 019xxx
                                   |--- Datei 019000_02.tif
                |--- Ordner 012
                          |--- Ordner 012xxx
                                   |--- Datei 012000_01.tif
                                   |--- Datei 012011_02.tif --> die zu alt und wird weggeschoben
                                   |--- Datei 012011_03.tif
       |--- Ordner ...
       |--- Ordner 99

Wenn ich mich in einem Unterordner z.B.: Ordner 010xxx befinde, sind hier nur Dateien enthalten. Nur zum Verständnis, dann kann er doch nicht zu einem anderen Unterverzeichnis z.B.: Ordner 019xxx oder Ordner 012, oder doch?

Kann Files.walkFileTree auch Reverse lesen?

Danke im Voraus.
Sven
 
Zuletzt bearbeitet:
Wenn Unterverzeichnisse und betroffene Dateien in Verzeichnissen nicht gemischt vorkommen, bist du mit walkFileTree sowie mit deiner Tiefensuche auf der richtigen Spur.

Was meinst du mit Reverse?
Du kannst einen FileVisitor schreiben, dieser hat vier Methoden:

preVisitDirectory(Path, BasicFileAttributes) - wird aufgerufen bevor in einen Ordner gewechselt wird.
visitFile(Path, BasicFileAttributes) - wird für jede reguläre Datei aufgerufen.
postVisitDirectory(Path, BasicFileAttributes) - wird nach Abarbeitung eines Ordners aufgerufen.
visitFileFailed(Path, IOException) - falls Besuchen der Datei fehlschlägt mit geworfener IOException
 
Danke für die Antwort. Das klingt doch vielversprechend.

Mit Reverse meine ich das er die Ordner und Dateien wie meine Methode von unten nach oben liest, also mit Ordner 99 anfängt und die Dateien dann dementsprechend auch.
Dies macht es nämlich einfach den Index in Bezug auf die gleichen Dateien abzufragen.

Vielen vielen Dank für eure Hilfe und besonders Danke an Mujahiddin.

Schönen Abend noch.
 
Ich kann nun die Dateien aus den Ordnern und Unterordnern auslesen.

Nun stellt sich mir die Frage, wie ich die Dateien verarbeite.

Ich muss die Dateien zerlegen und nach gewissen Kriterien (z.B. 7-8 Stelle = Index) entscheiden, was damit passieren sollen.

Ich könnte das on the fly erledigen. Mir schwebt aber vor die Dateien mit den zerlegten Informationen in eine Klasse zu speichern und diese dann in ein Array.

Ich kenne das von Delphi. Man legt ein Record an mit den Attributen und lässt dann ein Array alles speichern. Das Problem ist nur, dass ich nicht weiß, wieviele Dateien kommen und somit nicht das Array mit der festen Größe definieren kann.

Welche Möglichkeiten habe ich hier mit Java?

Danke.
Sven
 
Du verwendest eine [japi]List[/japi], also z.B. eine [japi]ArrayList[/japi]. Als Alternative könntest du auch eine andere [japi]Collection[/japi] als List verwenden, nämlich [japi]Set[/japi] bzw. [japi]HashSet[/japi]. Das eignet sich in deinem Fall aber warscheinlich nicht, habs nur toll gefunden, so viele Typen hier zu nennen 😉 Schau dir die Vererbungsstruktur aller Interfaces und Klassen beginnend oben bei [japi]Collection[/japi] an.
 

Zurück
Oben