Checksumme für ein File berechnen

reibi

Top Contributor
Hi

Hab ein kleines abgespektes Programm, welches eigentlich nur das prinzip zeigen soll, welches ich da nutze; so siehts aus :


//IMPORTS
Java:
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;

import java.util.Properties;
import java.util.zip.CRC32;
import java.util.zip.CheckedInputStream;

//METHODE
Java:
    public static long getChecksum(File thefile) throws Exception {
        FileInputStream myFIS = new FileInputStream(thefile);
        CheckedInputStream myCIS = new CheckedInputStream(myFIS, new CRC32());

        BufferedInputStream myBIS = new BufferedInputStream(myCIS);

        //Schleife
        while (myBIS.read() != -1) {
        } // end while

        //Checksumme holen
        return myCIS.getChecksum().getValue();
    } // end getChecksum()

Es liefert die Checksumme der Datei.

Also ich hab WinXP laufen auf n Core2Duo mit 3,16 Ghz

Das berechnen der Checksumme für:
100MB-Files dauert 1 Sekunde --> Kein Problem
3GB-Files dauer 40 Sekunden ---> Das ist ein Problem

Hab rausgefunden das :
Java:
        //Checksumme holen
        return myCIS.getChecksum().getValue();
das hier praktisch "NICHTS" an Zeit braucht ... max ein paar MilliSecs

und das hier:
Java:
        //Schleife
        while (myBIS.read() != -1) {
        } // end while
die 40 Sekunden.


nun die FRAGE:
Kann ich das irgebndwie zeitlich abkürzen=
ZB mit nem RandomAccessFile oder ApacheCommonsIO oder was Andres ... oder das ich den STream vorher nicht durchrödeln muss oder es gibt n ganz anderen Ansatz.

Danke Jungs und Mädels für Eure potentielle GehirnSchmalzEnergieVerbrauchsAktion ;-)
 
was ist schon eine Checksumme, wenn sie nicht jedes Byte wenigstens einmal liest?
schneller wirds nicht gehen, oder kann z.B. dein Betriebssystem die 3GB mal eben schneller an eine andere Stelle kopieren (nicht verschieben)?

Why FileInputStream is slow. | Preston L. Bannister { random memes }
and 51MB/s reading from disk (which we can assume is as fast as data can be read from this machine’s SATA disk).

du hast vielleicht 100 MB/s oder noch höher, egal, irgendwo ist eine Grenze,
und wenn man nur eine Datei groß genug wählt ist es irgendwann langsam

-----

auch Parallelverarbeitung dürfe das nicht erhöhen,
es ginge höchstens wenn die Datei bereits auf mehreren Festplatten/ vielleicht unterschiedlichen Rechnern vorliegt,
dann könnten diese parallel je 50 MB pro Sekunde schaffen, unterschiedliche Teile der Datei lesen, nach x Sec ihre y Teilergebnissie zusammentragen und die dann nur noch passend verrechnen 😉
 
Zuletzt bearbeitet von einem Moderator:
Java:
        //Schleife
        while (myBIS.read() != -1) {
        } // end while
nicht byteweise lesen, sondern mit ein bisschen Puffer.
Java:
int buffersize = 1024;//*1024
byte[] buffer = new byte[buffersize];
while(myBIS.read(buffer) ....

Edit: Sehe gerade den Vorposter: Wäre es mit dem Buffer nicht wirklich "etwas" schneller. War nun ungetest mal ausm Kopf geholt 🙂
 
kann sein dass es durch den Buffer schneller wird, aber wohl nur minimal,
es ist ja schon ein BufferedInputStream in der Kette, der liest auf jeden Fall dicke Buffer,
ob die bytes dann in ein neues byte-Array kopiert werden oder mit tausenden Einzelaufrufen..,

ok, wird schon bisschen was bringen, für Verringerung von Methodenaufrufen bin ich eigentlich auch immer 😉
 
Hi Geee unds Slater

also das mit dem Buffer bringts wirklich !! DANKE ;-)

Checksumme erstellen für ein 3 GB file, dauert nun noch 12Sek anstatt 40Sek. Also das ist auf jeden schon eine 3mal bessere Performance.

Um das mit dem Puffer richtig zu verstehen, noch Fragen:

1.) Es ist völlig egal ob der Buffer 1024, 2048 oder 4096 gross ist, schneller gehts durch vergrößern des Puffers auch nicht, warum eigentlich?
2.) Kann der Puffer eine falsche Größe haben, (zB 1117 oder 1023) gibt es irgendwelche Probleme?
3.) Warum ist denn der defaultPuffer nicht gleich 1024? Ist er kleiner? - Warscheinlich ja - warum eigentlich?

Danke und Gruss ;-)
 
Um das mit dem Puffer richtig zu verstehen, noch Fragen:

1.) Es ist völlig egal ob der Buffer 1024, 2048 oder 4096 gross ist, schneller gehts durch vergrößern des Puffers auch nicht, warum eigentlich?
2.) Kann der Puffer eine falsche Größe haben, (zB 1117 oder 1023) gibt es irgendwelche Probleme?
3.) Warum ist denn der defaultPuffer nicht gleich 1024? Ist er kleiner? - Warscheinlich ja - warum eigentlich?

Danke und Gruss ;-)

1) Kommt auf deine Hardware, Betriebsysteme und Treiber an. Eine Festplatte hat nunmal eine maximale Leserate, einen festgelegten Cache und der Rest der IO Operationen in System sind da auch interessant. In 12s 3GB lesen ist schon recht sportlich - macht immer hin 256MB/s.

2) Jein, direkt schlimm ist das nicht. Aber wenn von einer Platte Blöcke gelesen werden sind das auch immer 2er Potenzen an bytes.

3) [c]The default buffer size of 8192[/c]
BufferedReader
 
Hi
The default buffer size of 8192

Ja das steht auch so in der Javadoc.

Nur, wenn ich "8192" als Puffergroße explizit angebe,
dann geht das auch 3 mal schneller als wenn ich das nicht täte.

Auf die Lesezeit hat es auch KEINEN Einfluss
wenn ich als Puffergröße irgendwas zwischen 128 und 8192 eingebe. Das geht immer 3mal schneller als ohne Angabe des Puffers(Default).


Kommt auf deine Hardware, Betriebsysteme und Treiber an.

Wo könnte es denn da Probleme geben, wenn ich die Puffergröße auf 1024 stelle?
Bei älteren Geräten bzw.Festplatten oder eher bei weiterentwickelten, die erst in 5 Jahren rauskommen?
Kann es sein, das meine Software auf irgendwelchen Rechnern dann nicht läuft, egal ob alt oder neu?

Gruss
reibsen
 
da habe ich mich ja gehörig verschätzt mit der Optimierungsmöglichkeit, ich bin aber nach wie vor der Meinung, dass durch den BufferedInputStream immer genau 8192 gelesen werden,
in 8192-Blöcken gehen sie auch am CheckedInputStream vorbei,

der Rest ist nur die Frage, wie diese Daten von BufferedInputStream an den Aufrufer weitergeleitet werden,
ob vom byte[] in BufferedInputStream alles ein einen oder wenigen Aufrufen in ein neues Array kopiert oder per 8000 Einzelanfragen immer nur ein byte zurückgegeben wird,

inwiefern die Buffer-Anfrage schneller ist kann man auch mit folgender Untersuchung etwas besser testen:
Java:
public class Test
{

    public static void main(String[] args)
        throws Exception
    {
        File f = new File("test.txt");

        System.out.println("start");
        System.out.println(", " + getChecksum(f));
        for (int i = 17; i >= 0; i--)
        {
            int size = (int)Math.pow(2, i);
            System.out.println(", " + getChecksumBuf(f, size));
        }
    }

    public static long getChecksum(File thefile)
        throws Exception
    {
        FileInputStream myFIS = new FileInputStream(thefile);
        CheckedInputStream myCIS = new CheckedInputStream(myFIS, new CRC32());
        InputStream myBIS = new BufferedInputStream(myCIS);

        long time = System.currentTimeMillis();
        while (myBIS.read() != -1)
        {
        }
        long t = (System.currentTimeMillis() - time);
        System.out.print("time: " + t);
        return myCIS.getChecksum().getValue();
    }

    public static long getChecksumBuf(File thefile, final int size)
        throws Exception
    {
        FileInputStream myFIS = new FileInputStream(thefile);
        CheckedInputStream myCIS = new CheckedInputStream(myFIS, new CRC32());
        InputStream myBIS = new BufferedInputStream(myCIS);
        // InputStream myBIS = myCIS;

        long time = System.currentTimeMillis();
        final byte[] buf = new byte[size];
        while (myBIS.read(buf, 0, size) != -1)
        {
        }
        long t = (System.currentTimeMillis() - time);
        System.out.print("time: " + t + ", size: " + size);
        return myCIS.getChecksum().getValue();
    }
}
Ausgabe bei mir bei einer 100 MB-Datei:
Code:
time: 3690, 2353550512
time: 313, size: 131072, 2353550512
time: 297, size: 65536, 2353550512
time: 313, size: 32768, 2353550512
time: 297, size: 16384, 2353550512
time: 312, size: 8192, 2353550512
time: 329, size: 4096, 2353550512
time: 328, size: 2048, 2353550512
time: 328, size: 1024, 2353550512
time: 329, size: 512, 2353550512
time: 344, size: 256, 2353550512
time: 375, size: 128, 2353550512
time: 453, size: 64, 2353550512
time: 548, size: 32, 2353550512
time: 781, size: 16, 2353550512
time: 1173, size: 8, 2353550512
time: 2001, size: 4, 2353550512
time: 3721, size: 2, 2353550512
time: 7146, size: 1, 2353550512
300 ms ist der Basissatz, soviel dauert das Einlesen in 8192er-Blöcken, evtl. gehts schneller wenn man den Buffer von BufferedInputStream erhöht,
die restlichen Sekunden darüber hängen meiner Meinung nach direkt mit der Anzahl der Aufrufe zusammen,

bei size 1 ist der Buffer-Aufruf langsamer als ein read(), weil unnötig in einen Buffer kopiert wird, mehr Parameter sind zu übergeben usw.,
bei size 2 halbiert sich die Anzahl nötiger Aufrufe, es werden ca. 3.4 Sekunden gespart,
wenn man von den insgesamt ~7.1 Sekunden 0.3 als Grundzeit abzieht, dann ist das genau die Hälfte der variablen Zeit

im weiteren funktioniert das ungefähr genauso

wenn man erstmal bei 512 oder 1024 ist, dann hat man sowieso schon 99.8% und mehr der nötigen Aufrufe eingespart,
eine weitere Verdopplung bringt deshalb gar nichts mehr, außer im Promille-Bereich

edit:
der ganze BufferedInputStream ist eigentlich unnötiger Overhead, da werden endlos bytes kopiert ohne Nutzen,
lies mit 8192-Buffer direkt aus dem CheckedInputStream oder vielleicht mit noch höheren Buffern
(edit: selbst getestet, bringt aber nix)
 
Zuletzt bearbeitet von einem Moderator:
Java:
    private static long getChecksumFast(File file) throws IOException {
        CRC32 crc = new CRC32();
        FileChannel channel = null;
        try {
            FileInputStream fis = new FileInputStream(file);
            channel = fis.getChannel();
            ByteBuffer buffer = ByteBuffer.allocate(8192); //16384
            buffer.clear();

            while (channel.read(buffer) != -1) {
                buffer.flip();
                crc.update(buffer.array(), 0, buffer.limit());
            }
        } finally {
            if (channel != null) {
                channel.close();
            }
        }
        return crc.getValue();
    }

//EDIT: Bzw. nimm die von SlaterB vorgeschlagene Methode.
 
Zuletzt bearbeitet:
da habe ich mich ja gehörig verschätzt mit der Optimierungsmöglichkeit, ich bin aber nach wie vor der Meinung, dass durch den BufferedInputStream immer genau 8192 gelesen werden,
...

Das Problem liegt meines Erachtens einfach am Call von read(). Da wird nur ein char / int eingelesen und ich glaube auch nicht, dass er in dem Moment anfängt, 8192 bytes zu lesen und nur einen Teil davon ausgibt, damit er sie dann später ausm Cache holen kann 🙂
 
was kannst du sonst sagen außer dass du etwas lesen willst?
wie viel ist egal, wenn du nur soviel haben willst wie du anfragst, dann direkt auf dem Stream darüber,
wenn du beliebige Datenanfragen in verdeckten 8192er-Anfragen organisieren willst, dann Buffered
 

Zurück
Oben