Dateien kopieren, verschieben, lesen und schreiben

copy
move
write
read
java

#1

Bei einem Projekt wird die meiste Zeit kopiert oder verschoben. Das Programm läuft schon mal seine fünf Stunden, daher wollte ich mal nachfragen, ob die von mir verwendeten Java-Befehle noch aktuell sind, oder es ggf. schon schnellere gibt.

Während das Kopieren auf unterschiedlichen Speichermedien stattfindet, wird auf der gleichen Platte verschoben, daher müsste das eigentlich sehr schnell gehen. Den Eindruck habe ich bisher nicht. Es ist zwar schneller als das Kopieren, aber nicht signifikant.

Genaue Messungen habe ich noch nicht vorgenommen, das müsste ich vermutlich tun, um aussagekräftige Hinweise zu haben, wo die Zeit verbraten wird.

Ich verwende zum Kopieren und Verschieben die Klassen aus java.nio.file:

public static void copyFile(String source, String target) { Path sourcePath = Paths.get(source); Path targetPath = Paths.get(target); try { Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING); } catch (IOException exception) { // Fehlerbehandlung hier, die ist für die Frage ja unwichtig. } }

public static void moveFile(String source, String target) { Path sourcePath = Paths.get(source); Path targetPath = Paths.get(target); try { Files.move(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING); } catch (IOException exception) { // Fehlerbehandlung hier, die ist für die Frage ja unwichtig. } }

Ferner bearbeite ich zwischendurch noch Dateien, die werden so bearbeitet (ohne Behandlung der Ausnahmen, die ich sehr wohl mache, hier aber der Übersichtlichkeit halber weglasse).

Schreiben:

public static final String LINE_BREAK = System.getProperty("line.separator")

// Öffnen des Writers:
String charset = "ISO-8859-1";
boolean append = true; // oder halt false
OutputStream outputStream = new FileOutputStream(fileName, append);
OutputStreamWriter writer = new OutputStreamWriter(outputStream, charset)


// Schreiben ganzer Zeilen:
writer.write(text + LINE_BREAK);


// Schließen:
writer.close();

Lesen:

// Öffnen des Readers:
String charset = "ISO-8859-1";
InputStream inputStream = new FileInputStream(fileName);
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, charset);
LineNumberReader lineReader = new LineNumberReader(inputStreamReader);

//Lesen einer Zeile:
lineReader.readLine();

// Schließen:
lineReader.close();

Das ganze ist auch hübsch gekapselt in eigenen Klassen, aber auch das lasse ich hier der Übersichtlichkeit halber weg.

Das funktioniert alles wie es soll, die Frage ist nur, kann ich eventuell durch Benutzen anderer Klassen Zeit sparen?


#2

Also das erste, das mir auffällt… wo ist das Multithreading? Wenn man jede einzelne Operation nacheinander durchführt, dauerts insgesamt natürlich länger. Auf jeden Fall würde ich Multithreading einbauen, diese aber zumindest bei den dann parallel laufenden Kopiervorgängen auf 10 Threads begrenzen.


#3

Auch wenn es physikalisch die gleichen beiden Platten sind?


#4

Kopier und Verschiebeoperationen sind stark abhängig vom jeweiligen Betriebssystem.

Die neuen Java Implementierungen können das zum Teil nutzen.
https://docs.oracle.com/javase/tutorial/essential/io/move.html
On UNIX systems, moving a directory within the same partition generally consists of renaming the directory.
Während sich Windows meiner Erfahrung nach da immer schön abmüht.

Wird beim Lesen überhaupt die LineNumber benötigt?
In diesem Beispiel ist dies ja nicht der Fall.
https://docs.oracle.com/javase/7/docs/api/java/nio/file/Files.html#readAllLines(java.nio.file.Path,%20java.nio.charset.Charset)

Lesen würde wenn die Dateigröße überschaubar bliebe auf ein einfacheres reduziert werden können.

try {
  for(String line : Files.readAllLines(Paths.get(filename), Charset.forName("ISO-8859-1")) {
     process(line);
  }
} catch (Exception e) {}

Schreiben einer Liste von Strings ginge z.B. auch mit https://docs.oracle.com/javase/7/docs/api/java/nio/file/Files.html#write(java.nio.file.Path,%20java.lang.Iterable,%20java.nio.charset.Charset,%20java.nio.file.OpenOption...)

Ein Arbeitsablauf könnte also sein

List<String> lines = Files.readAllLines(…);
List<String> processed = lines.stream()
  .map( )
  .collect(toList());
Files.wirte(path, processed, …);

Womit interne Mechanismen zum effektiven Schreiben genutzt werden können.


#5

Soweit ich weiß, kann man in Java kaum feststellen, wann man es mit mit welchen Platten zu tun hat - das ja nicht mal für das Dateisystems des Betriebssystems interessant, sondern nur für den Hardwaretreiber.

Die Begrenzung der Threadanzahl hat ja nur den Grund, dass die meiste Zeit der Kopiervorgänge nicht mit dem Hin- und Herbewegen der Plattenköpfe (sofern vorhanden) verplempert wird und dies geschieht hauptsächlich dann, wenn kopiert wird - gilt also auch für das Verschieben einer Datei von einer Platte auf eine andere.


#6

Sieht alles recht aktuell aus. Beim Multithreading wäre ich auch skeptisch. Am schnellsten ist eine Platte (HDD, aber auch SSD) wenn sie einen großen Chunk rausschreiben muss. Mehrere Threads bewirken dann besten- (bzw. schlechtesten) Falls, dass der Kopf wild rumhüpft und es langsamer wird. Aber ohne Benchmarks ist das alles recht ins Blaue geraten.

Ein potentiell wichtiger Punkt: Beim Überfliegen scheint nirgendwo ein Buffered... vorzukommen. Die reader und besonders die writer können durch buffering schon deutlich schneller werden.


#7

Danke für eure Kommentare.

Das mit dem Buffered... muss ich mir nochmal anschauen, auch wenn das hier vermutlich nicht viel ausmachen dürfte, weil das alles sehr kleine Dateien sind, die damit gelesen / geschrieben werden.

Wenn ich das richtig sehe, müsste ich dafür nur einen BufferedWriter um den OutputStreamWriter wickeln, oder?

Edit:
Nach

scheint auch der OutputStreamWriter bereits intern zu puffern. Hmmm.

Edit 2:
Auch der LineNumberReader scheint zu puffern: “Create a new line-numbering reader, using the default input-buffer size.” und es gibt einen anderen Konstruktor, wo man die gewünschte Größe angeben kann.

Das ist nach dem Lesen von

LineNumberReader extends BufferedReader

auch nicht so überraschend. *schmunzelt*

Also bleibt da höchstens das Schreiben.


#8

Also was das Lesen und Schreiben von Daten vom (bzw. ins) Dateisystem angeht, bin ich mir relativ sicher, dass das Puffern ohnehin von der Hardware bzw. dem Betriebssystem-Treiber bereitgestellt wird. Beim direkten Kopieren bzw. Umbenennen per Filenamen benötigt man es deswegen schon mal explizit in Java gar nicht. Puffern bringt es, wenn überhaupt nur dann, wenn Daten in oder aus dem Java-Speicher geschrieben werden, aber ob das bei Dateien etwas bringt, kann man wohl nur ausprobieren, aber sicher sind die Methoden write, close und open0 in FileOutputStream nicht umsonst native.