2013-07-13 17 views
5

Deflater.setLevel() nie działa zgodnie z oczekiwaniami.Czy Deflater.setLevel() działa?

static void test1() throws Exception { 
    byte[] output = new byte[20]; 
    Deflater compresser = new Deflater(); 
    // compresser.setLevel(Deflater.BEST_COMPRESSION); 
    compresser.setInput("blah".getBytes("UTF-8")); 
    compresser.finish(); 
    int len = compresser.deflate(output); 
    System.out.println("len="+ len+ " " +Arrays.toString(output)); 
} 

Powyższe działa OK dla mnie (Java 7), ale kiedy odkomentuj linię compresser.setLevel(), przerywa (deflate() zwraca 0 bajtów). To samo dzieje się z każdym poziomem kompresji, z wyjątkiem DEFAULT. Mówiąc dokładniej, "działa" (a raczej jest nieszkodliwy), gdy zestaw poziomów jest taki sam, jaki został ustawiony (explicit lub implicit, jak tutaj) w konstruktorze - to znaczy, może być użyty tylko wtedy, gdy jest bezużyteczny.

Zobacz przykład pod adresem Ideone.

This question wskazuje na ten sam problem, a przyjęta odpowiedź w zasadzie mówi: nie ustawiaj poziomu za pomocą settera, zrób to w konstruktorze. Daleko od zadowalającego, IMO - dlaczego istnieje setLevel()? Czy to jest zepsute, czy coś nam brakuje?

+0

Wygląda na to, że jest to błąd w API 'Deflater' ... Czytając javadoc, nie jest wcale jasne, co naprawdę robi' .finish(). Czy obejście zaproponowane w ostatniej odpowiedzi działa? – fge

Odpowiedz

5

Wkopałem się trochę w kod źródłowy JDK. Rzeczywiście ustala poziom. Jeśli postępujesz zgodnie z setLevel() z compresser.deflate(new byte[0]);, to będzie działać.

Co się dzieje, to pierwsza rozmowa deflate() po setLevel() zobaczy, że poziom został zmieniony, i zadzwoń do funkcji zlib na deflateParams(), aby ją zmienić. deflateParams() następnie skompresuje dostępne dane, ale fakt, że użytkownik poprosił o finish(), nie został przekazany dalej. Implementacja JDK nie wywołuje wtedy deflate() z Z_FINISH. W rezultacie dane, które podałeś, są wysyłane w celu kompresji, kompresor gromadzi dane, ale nie wysyła skompresowanego bloku, ponieważ nie został poproszony o zakończenie. Więc nic nie dostajesz.

Musisz zadzwonić pod numer deflate() po setLevel(), aby właściwie ustawić poziom. Następnie kolejne dane zostaną skompresowane na nowym poziomie.

Ważne jest, aby pamiętać, że dane przekazane do pierwszego deflate() rozmowy po setLevel() będą kompresowane ze starym poziomie kompresji. Tylko dane dostarczone po tej rozmowie deflate() będą korzystać z nowego poziomu. Jeśli więc w twoim przykładzie po prostu zrobiłeś kolejny deflate() po ostatnim, to zastosowałbyś finish() i otrzymasz skompresowane dane, ale używałby on domyślnego poziomu kompresji.

+0

"Fakt, że prosiłeś o zakończenie() nie został przekazany dalej" To byłby błąd, prawda? – leonbloy

+0

Powiedziałbym "tak". Zwłaszcza, że ​​nie widzę, gdzie to zachowanie jest udokumentowane. –

3

Podejrzewam, że to błąd.

Jeśli spojrzeć na source code, można zauważyć, że tylko w konstruktorze nazywają natywny sposób init który faktycznie ustawia poziom kompresji. Wygląda na to, że poziom kompresji musi być ustawiony przed wywołaniem rodzimych wywołań init. Przed utworzeniem obiektu Deflater.

Po prostu ustawia poziom dla obiektu powierzchownie. Nie ma połączenia z rodzimą biblioteką.

+0

Byłoby wystarczająco źle, że 'setLevel()' był NOP w przebraniu (poprawiony w ... [1998] (http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4152809)), ale jest gorzej, wydaje się, że przełamuje dekompresję. – leonbloy

+0

Następne wywołanie 'deflate()' ustawia poziom. –