2010-09-29 29 views
8

Pytam bazę danych i archiwizuję wyniki za pomocą Pythona, i próbuję skompresować dane podczas ich zapisywania w plikach dziennika. Mam jednak z tym pewne problemy.Jak działają kodeki kompresujące w Pythonie?

Mój kod wygląda następująco:

log_file = codecs.open(archive_file, 'w', 'bz2') 
for id, f1, f2, f3 in cursor: 
    log_file.write('%s %s %s %s\n' % (id, f1 or 'NULL', f2 or 'NULL', f3)) 

Jednak mój plik wyjściowy ma rozmiar 1,409,780. Uruchomienie pliku bunzip2 powoduje, że plik o rozmiarze 943,634 i działający pod numerem bzip2 osiąga rozmiar 217,275. Innymi słowy, nieskompresowany plik jest znacznie mniejszy niż plik skompresowany przy użyciu kodeka bzip Pythona. Czy istnieje sposób naprawienia tego, inny niż uruchamianie bzip2 w wierszu polecenia?

Próbowałem kodeku gzip Pythona (zmieniając linię na codecs.open(archive_file, 'a+', 'zip')), aby sprawdzić, czy naprawił problem. Nadal dostaję duże pliki, ale dostaję także błąd gzip: archive_file: not in gzip format podczas próby dekompresji pliku. Co tam się dzieje?


EDIT: I pierwotnie miał plik otwierany w trybie dopisywania, nie tryb zapisu. Chociaż może to być problem, ale nie musi, pytanie pozostaje nadal aktualne, jeśli plik jest otwarty w trybie "w".

+0

Dlaczego otwierasz plik do dodania? – JoshD

+0

Powoduje to stopniowe przycinanie rekordów z bazy danych i zapisywanie ich w pliku archiwum, dzięki czemu plik archiwum stopniowo rośnie, aż zostanie skopiowany z samego komputera. –

Odpowiedz

2

Jak zauważyły ​​inne plakaty, problem polega na tym, że biblioteka codecs nie używa enkodera inkrementalnego do kodowania danych ; zamiast tego koduje każdy fragment danych podawanych do metody write jako skompresowany blok. Jest to okropnie nieefektywne i po prostu straszna decyzja projektowa dla biblioteki zaprojektowanej do pracy ze strumieniami.

Ironiczne jest to, że istnieje całkiem sensowny przyrostowy koder bz2 wbudowany w Pythonie. Nie jest trudno stworzyć klasę "podobną do pliku", która automatycznie wykonuje właściwą akcję.

import bz2 

class BZ2StreamEncoder(object): 
    def __init__(self, filename, mode): 
     self.log_file = open(filename, mode) 
     self.encoder = bz2.BZ2Compressor() 

    def write(self, data): 
     self.log_file.write(self.encoder.compress(data)) 

    def flush(self): 
     self.log_file.write(self.encoder.flush()) 
     self.log_file.flush() 

    def close(self): 
     self.flush() 
     self.log_file.close() 

log_file = BZ2StreamEncoder(archive_file, 'ab') 

zastrzeżenie W tym przykładzie, mam otwarty plik w trybie dopisywania; dołączanie wielu skompresowanych strumieni do pojedynczego pliku działa doskonale z bunzip2, ale sam Python nie może sobie z tym poradzić (chociaż jest tam is a patch). Jeśli chcesz przeczytać skompresowane pliki, które tworzysz ponownie w Pythonie, trzymaj się jednego strumienia na plik.

0

Problem wynika z używania trybu append, który powoduje, że pliki zawierają wiele skompresowanych bloków danych. Spójrz na ten przykład:

>>> import codecs 
>>> with codecs.open("myfile.zip", "a+", "zip") as f: 
>>>  f.write("ABCD") 

W moim systemie tworzy plik o rozmiarze 12 bajtów. Zobaczmy, co zawiera:

>>> with codecs.open("myfile.zip", "r", "zip") as f: 
>>>  f.read() 
'ABCD' 

Dobra, zróbmy kolejny zapis w trybie dopisywania:

>>> with codecs.open("myfile.zip", "a+", "zip") as f: 
>>>  f.write("EFGH") 

plik jest teraz 24 bajtów, a jego zawartość:

>>> with codecs.open("myfile.zip", "r", "zip") as f: 
>>>  f.read() 
'ABCD' 

To, co się tutaj dzieje, polega na tym, że unzip oczekuje pojedynczego spakowanego strumienia. Będziesz musiał sprawdzić specyfikacje, aby zobaczyć, jakie jest oficjalne zachowanie z wieloma połączonymi strumieniami, ale z mojego doświadczenia wynika, że ​​przetwarzają one pierwsze i ignorują pozostałe dane. Tak właśnie robi Python.

Oczekuję, że bunzip2 robi to samo. W rzeczywistości plik jest skompresowany i jest znacznie mniejszy niż zawarte w nim dane. Ale kiedy uruchomisz go przez bunzip2, otrzymujesz z powrotem tylko pierwszy zestaw zapisanych w nim rekordów; reszta jest odrzucana.

+0

Po pierwsze, różnice wielkości są wynikiem jednorazowego uruchomienia programu. Uruchamianie go z "w" daje dokładnie ten sam plik, co "a +", który jest o 30% większy niż wersja nieskompresowana. Po drugie, mimo że Python nie czyta poza pierwszym skompresowanym blokiem danych, robi to "bunzip2". –

0

Nie jestem pewien, jak bardzo jest to odmienne od sposobu kodeków, ale jeśli używasz GzipFile z modułu gzip możesz przyrostowo dołączyć do pliku, ale nie będzie on kompresował bardzo dobrze, chyba że piszesz duże ilości danych na raz (może> 1 KB). Jest to po prostu charakter algorytmów kompresji. Jeśli dane, które piszesz, nie są zbyt ważne (tzn. Możesz sobie poradzić z ich utratą, jeśli twój proces zginie), wtedy możesz napisać buforowaną klasę GzipFile, owijającą zaimportowaną klasę, która zapisuje większe porcje danych.

1

Problem polega na tym, że dane wyjściowe są zapisywane na każdym write(). Powoduje to kompresję każdej linii we własnym bloku bzip.

Chciałbym spróbować zbudować znacznie większy ciąg (lub listę ciągów, jeśli martwisz się o wydajność) w pamięci przed zapisaniem go do pliku. Dobry rozmiar do zrobienia to 900 KB (lub więcej), ponieważ jest to rozmiar bloku, który bzip2 używa