Bilddatei kopieren, aber ohne verluste und ohne zu großen Speicherverbrauch

stevey

Aktives Mitglied
Hi Leute,
ich habe für meine Eltern ein Programm geschrieben, mit dem sie die vielen Bilder von unserer Kamera ansehen können und direkt in eine Liste schieben können, welche dann die ausgewählten Bilder enthält und gespeichert wird. Es ist schon alles fertig, nur das speichern macht noch Probleme. Ich habe es bis jetzt so gelöst, dass ich das Bild vom Originalverzeichnis ins Programm lade, mittels ImageIO.read und dann wieder in das neue Verzeichnis mittels ImageIO.write speichere. es ist also ein einfacher Kopiervorgang. das problem ist dann allerdings das Bildformat des gespeicherten Bildes. entweder ich speichere es als .jpg, dann ist allerdings die Qualität wgen der komprimierung nicht mehr so gut. andererseits, wenn ich es als .png speichere ist die qualität zwar die gleiche, aber der Speicherbedarf, also die Dateigröße erheblich größer als beim Ausgangsbild.

ich hoffe ihr könnt mir irgendwie weiterhelfen, das Bild in gleicher Qualität und gleicher Dateigröße zu speichern, euer stevey
 
Verwende ein beliebiges Codeschnipsel zum Kopieren von Dateien, findest du über Google in wenigen Sekunden. Du musst die Bilder nicht als Bilder einlesen, wenn auch ein einfacher Kopiervorgang der Dateien reicht.
 
Hmm, dann hab ich vorhin entweder nicht genau genug gesucht, oder war einfach zu faul ein Codesnipsel zu testen. Mit folgender Methode hats funktioniert :
Java:
public void copyFile(File in, File out) throws Exception 
	{
        FileInputStream fis = new FileInputStream(in);
        FileOutputStream fos = new FileOutputStream(out);
        byte[] buf = new byte[1024];
        int i = 0;
        while((i=fis.read(buf))!=-1) 
        {
            fos.write(buf, 0, i);
        }
        fis.close();
        fos.close();
    }

Mfg stevey
 
Für die Zukunft: 1024 Byte Buffer sind ein Witz. Bei Bildern machst bestimmt nichts aus, aber wenn du iwann mal < 1 Gb DAteien kopierst, dann wirst du merken, was ihc meine...

Nimm bei modernen Sataplatten ruhig < 64kb große Buffer. Um die maximale Geschwindigkeit mit minimaler CPU Last zu erreichen, nimm 1 MB große Puffer.
 
Schau dir mal den FileChannel an, der soll angeblich sehr viel schneller als Lesen/Schreiben direkt per Stream sein. Habs selbst nicht getestet aber wüsste auch nicht warum andere lügen sollten.

btw. warum 1 MB? Moderne HDDs haben ja bereits Buffer mit 8, 16 oder sogar noch mehr MB. Wäre es da nicht vorteilhafter, den Buffer voll zu füllen, bevor man dem Schreibvorgang startet, indem man ihm gleich so viel wie möglich schickt? Sonst muss er ja unnötig oft zwischen Source und Dest hin- und hersteuern.
 
Zuletzt bearbeitet:
Ja stimmt, natürlich wären größere Buffer noch besser. - doof formuliert.

Aber ich sehe das häufig, dass so kleine Puffer genommen werden... Also 8192 oder so... Da wäre 1Mb schon ne echte Verbesserung. Und fileChannel sind schneller, da dort direkte Puffer genommen werden und die Daten keinen Umweg über die JVM nehmen müssen...
 
sorry für die dumme Frage, aber was genau ist denn ein Buffer und wie arbeitet der code von oben überhaupt? hab den einfach ausm Internet kopiert, weil ich das Programm einfach fertig haben wollte.
 
Der Buffer speichert die gelesenen bytes zwischen, um sie quasi zwischen In- und Outputstream zu übertragen. Ein Array wird verwendet, damit die Köpfe nicht nach jeden gelesenen Byte zwischen den beiden Dateien hin und herschwenken müssen, sondern in einen durchgelesen/schrieben werden kann. (Natürlich nur solange defragmentiert ist.)


read(byte[]) ließt die Daten beginnend mit Feld 0 aus bis der Buffer voll ist, und liefert zurück wie viel gelesen wurde oder -1, wenn nichts mehr da ist. Das ist in der Regel aber nicht immer buffer.lenght oder "Dateigröße - bisher gelesene Daten". Ich weiß aber nicht mehr in welchen Fall es nicht so war.

Code:
Reads up to byte.length bytes of data from this input stream into an array of bytes. This method blocks until some input is available.

This method simply performs the call read(b, 0, b.length) and returns the result. It is important that it does not do in.read(b) instead; certain subclasses of FilterInputStream depend on the implementation strategy actually used.
Code:
Reads bytes from this byte-input stream into the specified byte array, starting at the given offset.

This method implements the general contract of the corresponding read method of the InputStream class. As an additional convenience, it attempts to read as many bytes as possible by repeatedly invoking the read method of the underlying stream. This iterated read continues until one of the following conditions becomes true:

    The specified number of bytes have been read,
    The read method of the underlying stream returns -1, indicating end-of-file, or
    The available method of the underlying stream returns zero, indicating that further input requests would block. 

If the first read on the underlying stream returns -1 to indicate end-of-file then this method returns -1. Otherwise this method returns the number of bytes actually read.

Subclasses of this class are encouraged, but not required, to attempt to read as many bytes as possible in the same fashion.

write macht halt genau das umgekehrte, also schreibt den Inhalt des Buffers im Stream.
Am Ende schließt close noch die Streams damit u. a. die Dateihandler wieder freigegeben werden, das umbedingt in ein finally setzen.
 
das würde ja dann heißen, je größer der buffer, umso schneller geht das speichern.das hat sich auch so bei testen gezeigt(beim kopieren von 100 bildern). stimmt das?
 
Nein, die optimale Buffergröße setzt sich aus vielen verschiedenen Faktoren zusammen. Da die aber idR. Maschinenabhängig sind wird mehr oder weniger geraten. Wenn dir der Kopiervorgang zu langsam ist und auch der FileChannel keine wirkliche Besserung bringt kannst du ja immer noch ändern und testen. Es gibt hier irgendwo eine Benchmark-Programmbasis, mit der du das sicher testen kannst. Aber ich beweifel irgendwie, dass sich beim Kopieren von Fotos überhaupt eine so starke Verzögerung zeigt, die eine Änderung nötig macht.
 
Aber ja, je größer der Buffer umso besser.... Aber auch nicht den Arbeitsspeicher vergessen 😉
Am ALLER BESTEN wäre es, die Datei komplett in den Arbeitsspeicher zu laden und dann zu schreiben.
 
Aber ja, je größer der Buffer umso besser.... Aber auch nicht den Arbeitsspeicher vergessen 😉
Am ALLER BESTEN wäre es, die Datei komplett in den Arbeitsspeicher zu laden und dann zu schreiben.

Nö.

Wenn du alles in den Speicher lädst, dann musst du die komplette Zeit warten bis der Lesevorgang vorbei ist, und dann nochmal bis alles geschrieben ist, nebenläufig kann da nichts passieren.

Wenn du blockweise liest kann etwas nebenläufig laufen, nämlich das Schreiben. Die Daten werden bei Schreiben erstmal in des Puffer des OS/Platte geschrieben und der Schreibaufruf im Code kehrt bereits zurück. So können bereits während das OS die Daten noch schreibt weitere Daten gelesen und verarbeitet werden.
 
Aber uach nur wenn es um 2 Festplatte geht 😉 Beim Kopieren auf einer Datei auf einer Festplatte ist es ja eh sequentiell... Ansonsten hast recht.. War gestern schon was spät...
 
Selbst bei einer Platte wird es was bringen. Du hast ja noch die Verarbeitungszeit im Programm während welcher die Daten aus dem Puffer geschrieben werden. Und dann natürlich noch das Multithreading, welches deine Anwendung schlafen legt.
 
Ein kleiner Test:

Java:
        ByteBuffer b = ByteBuffer.allocateDirect(1024 * 1024 * 110);

        FileChannel in = new FileInputStream("D:/TEST.MOV").getChannel();
        FileChannel out = new FileOutputStream("D:/TEST2.MOV").getChannel();

        Stopwatch s = new Stopwatch(true);


        in.read(b);

        b.flip();

        out.write(b);

        System.out.println(s.getTime());

Stopwathc ist eine Klasse von mir... Im Prinzip System.currentTimeMillies();

Dateigröße: 108 MB. Zeit: 953.0 Millisekunden.


Java:
        ByteBuffer b = ByteBuffer.allocateDirect(1024 * 64);

        FileChannel in = new FileInputStream("D:/TEST.MOV").getChannel();
        FileChannel out = new FileOutputStream("D:/TEST2.MOV").getChannel();

        Stopwatch s = new Stopwatch(true);
        while(in.read(b) != -1) {
            b.flip();
            out.write(b);
            
            b.clear();
        }
        

        System.out.println(s.getTime());

Dateigröße: 108 MB. Zeit: 771.0 Millisekunden.

Damit wäre bewiesen, dass du Recht hast. Auch wenn das Ergebnis knapp ist^^
 
Zumal 108mb je nach OS locker noch vom Betriebssystem gepuffert werden.
Wenn ich hier unter Linux Dateien auf den USB-Stick ziehe bricht die Rate erst ab >250Mb ein, weil dann die Puffer voll sind.
So ein Test sollte schon mit Dateien > 1Gb gemacht werden.

PS: Knapp würde ich das nicht nennen, das ist eine Beschleunigung um fast 20% ...
 

Zurück
Oben