Input/Output Dateisuche mit NIO

DnSbrt86

Mitglied
Guten Abend zusammen,

ich möchte gerne eine Liste von <Path> mit allen Childs eines bestimmten RootFolder erstellen. Bspws: Baum des FileSystems: Root/Child(1)/ChildChild(1)/....Child(N)Child(N)
Root/Child(N).......

Meine bisherige Arbeit (I am a total noob ;-)) sieht so aus:


Java:
	public List<Path> getAllDirectories() throws TokenFinderException {
		Path rootFolder = Paths.get(task.getRootFolder());
		int i = 0;
		int nameCount = 1;
		List<Path> filePaths = new ArrayList<Path>();
		filePaths.add(rootFolder);

		while (i != nameCount) {
			i++;
			try (DirectoryStream<Path> rootFolderStream = Files
					.newDirectoryStream(rootFolder)) {
				for (Path next : rootFolderStream) {
					filePaths.add(next);
					nameCount = next.getNameCount();
					rootFolder = next;

				}

			} catch (IOException e) {
				throw new TokenFinderException(e);
			}
		}
		return filePaths;
	}

Zum einen bietet diese Methode nicht die gewünschte Funktionalität und zum anderen kommt es mir komisch vor, für so eine Standardfunktionalität einen solchen "Workaround" schreiben zu müssen.
Würde mich über konstruktive Kritik, und Hilfestellungen sehr freuen!!!

VG
 

turtle

Top Contributor
In Java NIO gibt es das FileVisitor Interface.

Somit kannst du eine Klasse schreiben, die dieses Interface implementiert.

Diese nutzt du bei
Java:
Files.walkFileTree(Path start, FileVisitor<? super Path> visitor)
Was da deine Klasse macht, ist natürlich deine Sache, hört sich aber so an, das du eine List<Path> bei preVisitDirectory() füllen würdest.

Hier gibt es vielleicht ein paar Informationen.

PS: Habs gerade mal in einem kleinen Beispiel gemacht. So ungefähr stelle ich es mir vor
Java:
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;

public class Crawl {

    public static void main(String[] args) throws IOException {
	Path homeFolder = Paths.get("C:/");
	FileVisitor<Path> fileVisitor = new FileDirVisitor();
	Files.walkFileTree(homeFolder, fileVisitor);
	List<Path> allDirectory = ((FileSizeVisitor) fileVisitor).getAllDirectory();
	for (Path path : allDirectory) {
	    System.out.println("Directory " + path);
	}
    }
}

class FileDirVisitor implements FileVisitor<Path> {
    private List<Path> allDirectory;

    public FileDirVisitor() {
	allDirectory = new ArrayList<>();

    }

    @Override
    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
	allDirectory.add(dir);
	return FileVisitResult.CONTINUE;
    }

    public List<Path> getAllDirectory() {
	return allDirectory;
    }

    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
	return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
	return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
	return FileVisitResult.CONTINUE;
    }
}
 
Zuletzt bearbeitet:

DnSbrt86

Mitglied
Hi Turtle,

vielen Dank für die schnelle und ausführliche Antwort. Habe mir überlegt, das Problem in zwei Teile zu zerlegen. Zunächst möchte ich alle Pfade "sammeln" und habe mir hierzu folgende Methode - der Parameter ist der gegebene rootFolder, directories eine entsprechende Instanzvariable- überlegt:

Java:
	public List<Path> getDirectories(Path dir) throws TokenFinderException {
		try (DirectoryStream<Path> directoryPath = Files
				.newDirectoryStream(dir)) {
			for (Path element : directoryPath) {
				if (Files.isDirectory(element)) {
					directories.add(element);
					getDirectories(element);

				}
			}

		} catch (IOException e) {
			throw new TokenFinderException(e);
		}
		return directories;

	}

Erste marginale Tests brachten das gewünschte Ergebnis, siehst du bei diesem Vorgehen Probleme?

VG
 
Zuletzt bearbeitet:

turtle

Top Contributor
siehst du bei diesem Vorgehen Probleme?
Nein, Probleme sehe ich nicht...

Finde aber deinen Code "unleserlicher" als meine Variante;)

Man sieht sofort bei Files.walkFileTree(, das da rekursiv ein Pfad durchlaufen wird und bei List<Path> allDirectory = ((FileDirVisitor) fileVisitor).getAllDirectory() das da eine Liste von Directories abgerufen wird.

(Scheint also genau das zu sein, was du als alle Pfade "sammeln" bezeichnet hast.)

Problematischer bei deiner Version UND auch bei meiner ist es, das es ziemlich lange dauern kann, bis die Liste fertig ist. Bei mir auf einer 2-TB Platte (mittel gefüllt), dauert es schon ungefähr ein-zwei Minuten, wenn gesamt C: durchsucht wird.

Wenn du deinen Code in Ordnung findest, sehe ich kein Problem damit weiter zu arbeiten:D
 

DnSbrt86

Mitglied
Bezüglich der Übersichtlichkeit gebe ich dir recht. Werde trotzdem meine Variante verwenden. Hintergrund ist. Das ich den Code im Rahmen einer Prüfung verteidigen muss und uns die von mir verwendeten Methoden mitgegeben wurden. Implementierte über das we den Rest, wäre Klasse , wenn du das komplette Aws dann nochmal kritisch betrachten würdest. BTW: kann eine arraylist beliebig viele Objekte speichern?
 

nvidia

Bekanntes Mitglied
Java:
FileDirVisitor implements FileVisitor<Path>
[...]

Das geht etwas kürzer

Java:
    public static List<Path> getAllDirectoriesWithVisitor(File root) throws IOException {
        Objects.requireNonNull(root);
        if(root.isFile()){
            throw new IllegalArgumentException();
        }

        List<Path> filePaths = new ArrayList<>();
        Files.walkFileTree(root.toPath(), new SimpleFileVisitor<Path>(){
            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                FileVisitResult result = super.preVisitDirectory(dir, attrs);
                filePaths.add(dir);
                return result ;
            }

            @Override
            public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
                return FileVisitResult.CONTINUE;
            }
        });

        return filePaths;
    }

Persönlich bevorzuge ich Folgendes ABER das fliegt einem relativ schnell um die Ohren, sobald man auf Dateien/Ordner trifft die z.B. nicht lesbar sind oder ähnliches. Ein Filter auf File.isReadable() hilft auch nicht und man hat zusätzlich noch das Problem, wenn man anstelle der Methodenreferenzen aus Files Lambda-Ausdrücke verwendet man auch irgendwie die Exceptions innerhalb der L-Ausdrücke verwerten muss (-> hässlich).

Java:
    public static List<Path> getAllDirectoriesWithStream(File root) throws IOException {
        Objects.requireNonNull(root);
        if(root.isFile()){
            throw new IllegalArgumentException();
        }

        return Files
                .walk(root.toPath())
                .filter(Files::isDirectory)
//                .filter(Files::isReadable)
                .collect(toList());
    }
 

DnSbrt86

Mitglied
Hallo,

habe mir, auf der vorherigen Diskussion aufbauend, einen TokenFinder geschrieben. Dieser soll in einem gegeben Pfad alle Datein mit einer bestimmten Endung auf den gegeben Token durchsuchen. Das Ergebnis soll auf der Konsole und in einer result.txt ausgegeben werden. Würde mich über konstruktive Kritik wirklich sehr freuen!

Java:
package de.uniba.wiai.dsg.ajp.assignment1.search.impl;

import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.PatternSyntaxException;

import de.uniba.wiai.dsg.ajp.assignment1.search.SearchTask;
import de.uniba.wiai.dsg.ajp.assignment1.search.TokenFinder;
import de.uniba.wiai.dsg.ajp.assignment1.search.TokenFinderException;

public class SimpleTokenFinder implements TokenFinder {
	private Path rootFolderPath;
	private String fileExtension;
	private String token;
	private Path resultFilePath;
	private List<Path> searchDirectories;
	private List<Path> fileDirectories;
	private int projectTokenCount;
	private List<String> outputList;

	/**
	 * Default constructor
	 */
	public SimpleTokenFinder() {
	}

	/**
	 * 
	 * @return
	 */

	public Path getRootFolderPath() {
		return rootFolderPath;
	}

	/**
	 * 
	 * @param rootFolderPath
	 */

	public void setRootFolderPath(Path rootFolderPath) {
		this.rootFolderPath = rootFolderPath;
	}

	/**
	 * 
	 * @return
	 */

	public String getFileExtension() {
		return fileExtension;
	}

	/**
	 * 
	 * @param fileExtension
	 */

	public void setFileExtension(String fileExtension) {
		this.fileExtension = fileExtension;
	}

	/**
	 * 
	 * @return
	 */

	public Path getResultFilePath() {
		return resultFilePath;
	}

	/**
	 * 
	 * @param resultFilePath
	 */

	public void setResultFilePath(Path resultFilePath) {
		this.resultFilePath = resultFilePath;
	}

	/**
	 * 
	 * @return
	 */

	public List<Path> getSearchDirectories() {
		return searchDirectories;
	}

	/**
	 * 
	 * @param searchDirectories
	 */

	public void setSearchDirectories(List<Path> searchDirectories) {
		this.searchDirectories = searchDirectories;
	}

	/**
	 * 
	 * @return
	 */

	public int getProjectTokenCount() {
		return projectTokenCount;
	}

	/**
	 * 
	 * @param projectTokenCount
	 */

	public void setProjectTokenCount(int projectTokenCount) {
		this.projectTokenCount = projectTokenCount;
	}

	/**
	 * 
	 * @return
	 */

	public List<String> getOutputList() {
		return outputList;
	}

	/**
	 * 
	 * @param outputList
	 */

	public void setOutputList(List<String> outputList) {
		this.outputList = outputList;
	}

	/**
	 * 
	 * @param token
	 */

	public void setToken(String token) {
		this.token = token;
	}

	/**
	 * 
	 * @param fileDirectories
	 */

	public void setFileDirectories(List<Path> fileDirectories) {
		this.fileDirectories = fileDirectories;
	}

	/**
	 * TODO: Comment this
	 */
	public void search(final SearchTask task) throws TokenFinderException {
		if (task.getRootFolder() == null) {
			throw new TokenFinderException(
					"The path to the root folder may not be null!!");
		} else {
			rootFolderPath = Paths.get(task.getRootFolder());
		}
		if (task.getFileExtension() == null) {
			throw new TokenFinderException(
					"The file extension may not be null!!!");
		} else {
			fileExtension = task.getFileExtension();
		}

		if (task.getToken() == null) {
			throw new TokenFinderException("The Token my not be null");
		} else {
			token = task.getToken();
		}
		if (task.getResultFile() == null) {
			throw new TokenFinderException(
					"The path to the result file may not be null!!!");
		} else {
			resultFilePath = Paths.get(task.getResultFile());
		}
		searchDirectories = new ArrayList<Path>();
		searchDirectories.add(rootFolderPath);
		getSearchDirectories(rootFolderPath);
		fileDirectories = new ArrayList<Path>();
		getFileDirectories();
		getToken();
		writeOutputfile();

	}

	/**
	 * TODO: Comment this
	 * 
	 * @param dir
	 */
	private void getSearchDirectories(Path dir) {
		try (DirectoryStream<Path> streamToDirectories = Files
				.newDirectoryStream(dir)) {
			for (Path directory : streamToDirectories) {
				if (Files.isDirectory(directory)) {
					searchDirectories.add(directory);
					getSearchDirectories(directory);
				}
			}
		} catch (IOException | SecurityException e) {
			e.printStackTrace();
		}
	}

	/**
	 * TODO: Comment this
	 */
	private void getFileDirectories() {
		for (Path directory : searchDirectories) {
			try (DirectoryStream<Path> fileStream = Files.newDirectoryStream(
					directory, "*." + fileExtension)) {
				for (Path filePath : fileStream) {
					if (Files.isSameFile(resultFilePath, filePath) == true) {

					} else if (Files.isRegularFile(filePath)
							&& Files.isReadable(filePath)) {
						fileDirectories.add(filePath);
					} else {
						throw new TokenFinderException("File: " + filePath
								+ " either not readable or not a regular file");
					}
				}
			} catch (PatternSyntaxException | SecurityException | IOException
					| TokenFinderException e) {
				e.printStackTrace();
			}
		}
	}

	// TODO: Comment this
	private void getToken() throws TokenFinderException {
		outputList = new ArrayList<>();
		String tempOutput;

		for (Path filePath : fileDirectories) {
			try (BufferedReader fileReader = Files.newBufferedReader(filePath,
					StandardCharsets.UTF_8)) {
				String actualLine = null;
				int rowCount = 0;
				int fileTokenCount = 0;
				while ((actualLine = fileReader.readLine()) != null) {
					rowCount++;
					if (actualLine.contains(token)) {
						fileTokenCount++;
						String preText = actualLine.substring(0,
								actualLine.indexOf(token));
						String postText = actualLine.substring((actualLine
								.indexOf(token) + token.length()));

						tempOutput = filePath + ": " + rowCount + ","
								+ preText.length() + " " + ">" + " " + preText
								+ "**" + token + "**" + postText;
						System.out.println(tempOutput);
						outputList.add(tempOutput);

						while (actualLine.length() >= token.length()) {
							actualLine = postText;
							if (actualLine.contains(token)) {
								fileTokenCount++;
								preText = preText
										+ token
										+ actualLine.substring(0,
												actualLine.indexOf(token));
								postText = actualLine.substring((actualLine
										.indexOf(token) + token.length()));
								tempOutput = filePath + ": " + rowCount + ","
										+ preText.length() + " " + ">" + " "
										+ preText + "**" + token + "**"
										+ postText;
								System.out.println(tempOutput);
								outputList.add(tempOutput);

							}
						}
					}

				}
				tempOutput = filePath + " includes" + " " + token + " "
						+ fileTokenCount + " time(s)";
				System.out.println(tempOutput);
				outputList.add(tempOutput);

				projectTokenCount = projectTokenCount + fileTokenCount;
				tempOutput = "\n";
				System.out.println();
				outputList.add(tempOutput);

			} catch (IOException | SecurityException e) {
				e.printStackTrace();
			}
		}
		// TODO: Check print path
		tempOutput = "The Project includes " + token + " " + projectTokenCount
				+ " times";
		System.out.println(tempOutput);
		outputList.add(tempOutput);
	}

	// TODO: Comment this
	private void writeOutputfile() throws TokenFinderException {
		try {
			Files.write(resultFilePath, outputList, StandardCharsets.UTF_8);
		} catch (IOException e) {
			throw new TokenFinderException(e);
		}
	}
}

Die SearchTaskKlasse:
Java:
package de.uniba.wiai.dsg.ajp.assignment1.search;

/**
 * Groups together all parameters for a search task and does not contain any logic.
 */
public class SearchTask {

	/**
	 * the root folder from which the search is started
	 */
	private String rootFolder;

	/**
	 * the extension of the files which are searched
	 */
	private String fileExtension;

	/**
	 * the string to be searched
	 */
	private String token;

	/**
	 * the path for the search result output
	 */
	private String resultFile;

	public String getRootFolder() {
		return rootFolder;
	}

	public void setRootFolder(String rootFolder) {
		this.rootFolder = rootFolder;
	}

	public String getFileExtension() {
		return fileExtension;
	}

	public void setFileExtension(String fileExtension) {
		this.fileExtension = fileExtension;
	}

	public String getToken() {
		return token;
	}

	public void setToken(String token) {
		this.token = token;
	}

	public String getResultFile() {
		return resultFile;
	}

	public void setResultFile(String resultFile) {
		this.resultFile = resultFile;
	}

}

Schönen Feiertag!!!

VG
 

nvidia

Bekanntes Mitglied
kannst du das bitte präzisieren? :oops:

Konzentrier dich mehr auf das Wesentliche, versuche Methoden übersichtlich zu halten und sie nur eine bestimmte Aufgabe abarbeiten zu lassen. Die getToken-Methode müsste überarbeitet werden, vor allem diese Stringzerlegung. Das lenkt ab und wird das wirklich alles benötigt?. Die Sysouts müssen weg. Die Validierung von SearchTask in der Methode search() könnte ausgelagert werden, einfach damit es sich flüssiger liest. Das Exceptionhandlig könnte auch verbessert werden.
Und dann mal darüber reflektieren ob sowas wie "SearchTask" wirklich einen Nutzen hat und ob die ganzen Instanzvariablen in SimpleTokenFinder benötigt werden. Und du solltest dich immer fragen, verstehe ich das morgen noch ohne mir eine halbe Stunde den Code ansehen zu müssen?

Eine andere Umsetzung angelehnt an deine Aufgabe (welches auch besser geht) mit etwas übersichtlicherer Struktur, die etwas demonstriert was ich meine, auch ohne Java8-Features wäre es nur unwesentlich länger (vll. 60 Zeilen oder so).

Java:
public class FileFinder {
    public static List<Path> listAllFilesWith(File root, String filePattern) throws IOException {
        Objects.requireNonNull(root);
        Objects.requireNonNull(filePattern);
        if(root.isFile()){
            throw new IllegalArgumentException();
        }

        PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:" + filePattern);
        List<Path> paths = new ArrayList<>();

        Files.walkFileTree(root.toPath(), new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path file,  BasicFileAttributes attrs) {
                Path name = file.getFileName();
                if (name != null && matcher.matches(name)) {
                    paths.add(file);
                }
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
                return FileVisitResult.CONTINUE;
            }
        });

        return paths;
    }

    private static long countToken(Path file, String tokenPattern) {
        if(file.toFile().isDirectory()){
            throw new IllegalArgumentException();
        }

        Pattern pattern = Pattern.compile(tokenPattern);

        try (BufferedReader fileReader = Files.newBufferedReader(file, StandardCharsets.UTF_8)){
            return fileReader
                        .lines()
                        .mapToInt(s -> {
                            int count = 0;
                            Matcher matcher = pattern.matcher(s);
                            while (matcher.find()) count++;
                            return count;
                        })
                        .sum();
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

    public static Map<Path, Long> getFilesWithTokenCount(List<Path> files, String tokenPattern){
        Objects.requireNonNull(files);
        Objects.requireNonNull(tokenPattern);
        return files.stream().collect(toMap(p -> p,  p -> countToken(p,tokenPattern)));
    }

    public static List<String> generateResultFileContent(Map<Path, Long> paths, String token){
        return paths
                .entrySet()
                .stream()
                .filter(e -> e.getValue() > 0)
                .map(e -> e.getKey() + " includes " + token + " " + e.getValue() + " times(s)").collect(toList());
    }

    public static void writeResultToFile(Path target, List<String> content) throws IOException {
        Objects.requireNonNull(target);
        Objects.requireNonNull(content);
        Files.write(target, content);
    }

    public static void main(String ... args) throws IOException {
        List<Path> filesEndingIn = listAllFilesWith(new File("D:/"), "*.test");
        Map<Path, Long> filesContaining = getFilesWithTokenCount(filesEndingIn, "foo");
        List<String> content = generateResultFileContent(filesContaining, "foo");
        content.forEach(System.out::println);
        writeResultToFile(new File("D:/", "results.txt").toPath(), content );
    }
}
 
Zuletzt bearbeitet:

Ähnliche Java Themen

Neue Themen


Oben