Rimozione di una sequenza di caratteri da un file binario di grandi dimensioni usando Python
-
03-07-2019 - |
Domanda
Vorrei tagliare lunghe sequenze dello stesso valore da un file binario in Python. Un modo semplice per farlo è semplicemente leggere il file e usare re.sub per sostituire la sequenza indesiderata. Questo ovviamente non funzionerà su file binari di grandi dimensioni. Può essere fatto in qualcosa come numpy?
Soluzione
Se non hai la memoria da fare open("big.file").read()
, allora numpy non ti aiuterà davvero .. Utilizza la stessa memoria delle variabili python (se hai 1GB di RAM, puoi caricare solo 1GB di dati in numpy )
La soluzione è semplice: leggi il file in blocchi. f = open("big.file", "rb")
, quindi esegui una serie di f.read(500)
, rimuovi la sequenza e riscrivila su un altro oggetto file. Praticamente come si fa a leggere / scrivere file in C ..
Il problema quindi è se ti manca lo schema che stai sostituendo. Ad esempio:
target_seq = "567"
input_file = "1234567890"
target_seq.read(5) # reads 12345, doesn't contain 567
target_seq.read(5) # reads 67890, doesn't contain 567
La soluzione ovvia è iniziare dal primo carattere nel file, selezionare len(target_seq)
caratteri, quindi andare avanti di un carattere, controllare di nuovo in avanti.
Ad esempio (pseudo codice!):
while cur_data != "":
seek_start = 0
chunk_size = len(target_seq)
input_file.seek(offset = seek_start, whence = 1) #whence=1 means seek from start of file (0 + offset)
cur_data = input_file.read(chunk_size) # reads 123
if target_seq == cur_data:
# Found it!
out_file.write("replacement_string")
else:
# not it, shove it in the new file
out_file.write(cur_data)
seek_start += 1
Non è esattamente il modo più efficiente, ma funzionerà e non richiederà di conservare una copia del file (o due).
Altri suggerimenti
Se due copie si adattano alla memoria, allora puoi facilmente fare una copia. La seconda copia è la versione compressa. Certo, puoi usare numpy, ma puoi anche usare l'array pacchetto. Inoltre, puoi trattare il tuo grande oggetto binario come una stringa di byte e manipolarlo direttamente.
Sembra che il tuo file possa essere DAVVERO di grandi dimensioni e non puoi inserire due copie in memoria. (Non hai fornito molti dettagli, quindi questa è solo una supposizione.) Dovrai fare la compressione in blocchi. Leggerai in un pezzo, eseguirai qualche elaborazione su quel pezzo e lo scriverai. Anche in questo caso, numpy, array o semplice stringa di byte funzionerà correttamente.
La soluzione di dbr è una buona idea, ma un po 'troppo complicata tutto ciò che devi fare è riavvolgere il puntatore del file per la lunghezza della sequenza che stai cercando, prima di leggere il tuo prossimo pezzo.
def ReplaceSequence(inFilename, outFilename, oldSeq, newSeq):
inputFile = open(inFilename, "rb")
outputFile = open(outFilename, "wb")
data = ""
chunk = 1024
while 1:
data = inputFile.read(chunk)
data = data.replace(oldSeq, newSeq)
outputFile.write(data)
inputFile.seek(-len(oldSequence), 1)
outputFile.seek(-len(oldSequence), 1)
if len(data) < chunk:
break
inputFile.close()
outputFile.close()
Il suggerimento AJMayorga va bene a meno che le dimensioni delle stringhe di ricambio siano diverse. Oppure la stringa di sostituzione si trova alla fine del blocco.
L'ho corretto in questo modo:
def ReplaceSequence(inFilename, outFilename, oldSeq, newSeq):
inputFile = open(inFilename, "rb")
outputFile = open(outFilename, "wb")
data = ""
chunk = 1024
oldSeqLen = len(oldSeq)
while 1:
data = inputFile.read(chunk)
dataSize = len(data)
seekLen= dataSize - data.rfind(oldSeq) - oldSeqLen
if seekLen > oldSeqLen:
seekLen = oldSeqLen
data = data.replace(oldSeq, newSeq)
outputFile.write(data)
inputFile.seek(-seekLen, 1)
outputFile.seek(-seekLen, 1)
if dataSize < chunk:
break
inputFile.close()
outputFile.close()
Devi rendere la tua domanda più precisa. Conosci i valori che vuoi tagliare in anticipo?
Supponendo che lo faccia, probabilmente cercherei le sezioni corrispondenti usando sottoprocesso
per eseguire " fgrep -o -b < cerca stringa >
" e quindi modificare le sezioni pertinenti del file utilizzando i metodi file
dell'oggetto cerca
, read
e write
dell'oggetto <. / p>
Questa versione basata su generatore manterrà esattamente un carattere del contenuto del file in memoria alla volta.
Nota che prendo il titolo della tua domanda piuttosto letteralmente: vuoi ridurre le esecuzioni dello stesso carattere a un singolo personaggio. Per sostituire i pattern in generale, questo non funziona:
import StringIO
def gen_chars(stream):
while True:
ch = stream.read(1)
if ch:
yield ch
else:
break
def gen_unique_chars(stream):
lastchar = ''
for char in gen_chars(stream):
if char != lastchar:
yield char
lastchar=char
def remove_seq(infile, outfile):
for ch in gen_unique_chars(infile):
outfile.write(ch)
# Represents a file open for reading
infile = StringIO.StringIO("1122233333444555")
# Represents a file open for writing
outfile = StringIO.StringIO()
# Will print "12345"
remove_seq(infile, outfile)
outfile.seek(0)
print outfile.read()