Lesen Sie Blöcke von einem Dateiobjekt bis x Bytes vom Ende

Nov 22 2020

Ich muss Blöcke von 64 KB in einer Schleife lesen und verarbeiten, aber am Ende der Datei minus 16 Bytes anhalten : Die letzten 16 Bytes sind tagMetadaten.

Die Datei ist möglicherweise sehr groß, daher kann ich nicht alles im RAM lesen.

Alle Lösungen, die ich finde, sind etwas ungeschickt und / oder unpythonisch.

with open('myfile', 'rb') as f:
    while True:
        block = f.read(65536)
        if not block:
            break
        process_block(block)

Wenn ja 16 <= len(block) < 65536, ist es einfach: Es ist der letzte Block überhaupt. Also useful_data = block[:-16]undtag = block[-16:]

Wenn len(block) == 65536, könnte es drei Dinge bedeuten: dass der vollständige Block nützliche Daten sind. Oder dass dieser 64-KB-Block tatsächlich der letzte Block ist , also useful_data = block[:-16]und tag = block[-16:]. Oder dass auf diesen 64-KB-Block ein weiterer Block mit nur wenigen Bytes (sagen wir 3 Bytes) folgt, also in diesem Fall: useful_data = block[:-13]und tag = block[-13:] + last_block[:3].

Wie kann man mit diesem Problem besser umgehen, als all diese Fälle zu unterscheiden?

Hinweis:

  • Die Lösung sollte für eine Datei funktionieren, die mit geöffnet wurde open(...), aber auch für ein io.BytesIO()Objekt oder für eine entfernte geöffnete SFTP-Datei (mit pysftp).

  • Ich habe darüber nachgedacht, die Größe des Dateiobjekts mit zu ermitteln

    f.seek(0,2)
    length = f.tell()
    f.seek(0)
    

    Dann nach jedem

    block = f.read(65536)
    

    wir können wissen, ob wir weit vom Ende entfernt sind length - f.tell(), aber auch hier sieht die vollständige Lösung nicht sehr elegant aus.

Antworten

1 LiorCohen Nov 22 2020 at 20:31

Sie können einfach jede Iteration einlesen min(65536, L-f.tell()-16)

Etwas wie das:

from pathlib import Path

L = Path('myfile').stat().st_size

with open('myfile', 'rb') as f:
    while True:    
        to_read_length = min(65536, L-f.tell()-16)
        block = f.read(to_read_length)
        process_block(block)
        if f.tell() == L-16
            break

Ich habe das nicht ausgeführt, aber ich hoffe, Sie bekommen den Kern davon.

1 PresidentJamesK.Polk Nov 23 2020 at 00:16

Die folgende Methode basiert nur auf der Tatsache, dass die f.read()Methode am Ende des Streams (EOS) ein leeres Byte-Objekt zurückgibt. Es könnte somit für Steckdosen einfach durch Ersetzen f.read()durch übernommen werden s.recv().

def read_all_but_last16(f):
    rand = random.Random()  #  just for testing
    buf = b''
    while True:
        bytes_read = f.read(rand.randint(1, 40))  # just for testing
        # bytes_read = f.read(65536)
        buf += bytes_read
        if not bytes_read:
            break
        process_block(buf[:-16])
        buf = buf[-16:]
    verify(buf[-16:])

Es funktioniert durch die immer 16 Bytes am Ende verlassen , bufbis EOS, dann schließlich die Verarbeitung des letzten 16 Beachten Sie, dass , wenn es nicht mindestens 17 Bytes sind in bufdann buf[:-16]gibt das leere Bytes Objekt.