2017-04-08 3 views
2

Java 프로그램은 웹 소켓을 통해 클라이언트에서 gzip을 사용하여 압축 된 매우 큰 파일을 가져와야하며 서버에서 파일 내용의 일부 바이트 패턴을 확인해야합니다.Java는 GZIP 스트림을 순차적으로 압축 해제합니다.

클라이언트가 소유 프로토콜에 포함 된 파일 청크를 보내어 클라이언트에서 메시지가 도착하면 메시지를 파싱하고 메시지를 파싱하고 gzipped 파일 내용을 추출합니다.

나는 각 청크를 압축 해제하고 데이터를 처리하고 다음 청크로 계속 진행하려고하므로 프로그램 메모리에 전체 파일을 저장할 수 없습니다.

나는 다음과 같은 코드를 사용하고 있습니다 :

public static String gzipDecompress(byte[] compressed) throws IOException { 
    String uncompressed; 
    try (
     ByteArrayInputStream bis = new ByteArrayInputStream(compressed); 
     GZIPInputStream gis = new GZIPInputStream(bis); 
     Reader reader = new InputStreamReader(gis); 
     Writer writer = new StringWriter() 
    ) { 

     char[] buffer = new char[10240]; 
     for (int length = 0; (length = reader.read(buffer)) > 0;) { 
     writer.write(buffer, 0, length); 
     } 
     uncompressed = writer.toString(); 
    } 

    return uncompressed; 
    } 

을하지만 첫 번째 압축 된 덩어리와 함수 호출 할 때 나는 다음과 같은 예외를 받고 있어요 :

java.io.EOFException: Unexpected end of ZLIB input stream 
    at java.util.zip.InflaterInputStream.fill(InflaterInputStream.java:240) 
    at java.util.zip.InflaterInputStream.read(InflaterInputStream.java:158) 
    at java.util.zip.GZIPInputStream.read(GZIPInputStream.java:117) 
    at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284) 
    at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326) 
    at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178) 
    at java.io.InputStreamReader.read(InputStreamReader.java:184) 
    at java.io.Reader.read(Reader.java:140) 

그것은 그 I를 언급하는 것이 중요합니다을 어떤 청크도 건너 뛰지 않고 순차적으로 청크를 압축 해제하려고하지 않습니다.

무엇이 누락 되었습니까?

+1

이 데이터의 출처는 분명하지 않습니다. * 모든 * 데이터를 읽을 스트림을 하나 만들고 GZipInputStream에 래핑해야합니다. 메모리 *에 모든 데이터 *가있을 필요는 없지만 단일 스트림이어야합니다. –

답변

2

문제는 이러한 청크를 수동으로 재생한다는 것입니다.

올바른 방법은 으로 포장하고 데이터를 읽는 것입니다. InputStream을 얻는 것이 좋습니다. 당신은 단지 당신의 reader에서 한 번에 10킬로바이트을 요구하는 경우 스트림 방식으로

InputStream is = // obtain the original gzip stream 

    GZIPInputStream gis = new GZIPInputStream(is); 
    Reader reader = new InputStreamReader(gis); 

    //... proceed reading and so on 

GZIPInputStream 작품은, 그래서, 전체 메모리 사용량에 관계없이 초기 GZIP 파일의 크기 낮을 것이다. 질문이

를 업데이트 한

업데이트 후 상황에 가능한 솔루션은 클라이언트 프로토콜 핸들러에 의해 덩어리에에 넣어되고있다 바이트 스트림 InputStream 구현을 작성하는 것입니다.

public class ProtocolDataInputStream extends InputStream { 
    private BlockingQueue<byte[]> nextChunks = new ArrayBlockingQueue<byte[]>(100); 
    private byte[] currentChunk = null; 
    private int currentChunkOffset = 0; 
    private boolean noMoreChunks = false; 

    @Override 
    public synchronized int read() throws IOException { 
     boolean takeNextChunk = currentChunk == null || currentChunkOffset >= currentChunk.length; 
     if (takeNextChunk) { 
      if (noMoreChunks) { 
       // stream is exhausted 
       return -1; 
      } else { 
       currentChunk = nextChunks.take(); 
       currentChunkOffset = 0; 
      } 
     } 
     return currentChunk[currentChunkOffset++]; 
    } 

    @Override 
    public synchronized int available() throws IOException { 
     if (currentChunk == null) { 
      return 0; 
     } else { 
      return currentChunk.length - currentChunkOffset; 
     } 
    } 

    public synchronized void addChunk(byte[] chunk, boolean chunkIsLast) { 
     nextChunks.add(chunk); 
     if (chunkIsLast) { 
      noMoreChunks = true; 
     } 
    } 
} 

클라이언트 프로토콜 핸들러 (Reader를 통해) 귀하의 압축 해제 코드는이 스트림의 출력 데이터를 가져옵니다 동안, addChunk()를 사용하여 바이트 덩어리를 추가 : 여기

은 프로토 타입입니다.

이 코드는 몇 가지 문제가 있습니다 : 사용

  1. 큐는 제한된 크기를 갖는다. addChunk()이 너무 자주 호출되는 경우 대기열이 채워져 addChunk()을 차단합니다. 이것은 바람직 할 수도 있고 그렇지 않을 수도 있습니다.
  2. 설명 목적으로 만 read() 메서드가 구현되었습니다. 성능을 위해서는 동일한 방식으로 read(byte[])을 구현하는 것이 좋습니다.
  3. 보수적 인 동기화는 독자 (압축 풀기)와 작성자 (프로토콜 처리자 호출 addChunk())가 서로 다른 스레드라는 가정하에 사용됩니다.
  4. InterruptedException은 너무 많은 세부 사항을 피하기 위해 take()에서 처리되지 않습니다. Reader과 당기 때 InputStream 또는 Reader.ready()를 사용하여 당기는 때

당신의 압축 해제 및 addChunk()이 (같은 루프) 같은 스레드에서 실행하면, 당신은 InputStream.available() 방법을 사용을 시도 할 수 있습니다.

+1

ByteArrayInputStream 또는 바이트 배열을 입력 스트림으로 감싸는 다른 InputStream을 GZIPInputStream에 전달할 수 있습니까? 내 상황에서는 서버의 데이터를 가져 오는 원래 InputSteam을 실제로 사용할 수 없습니다. – Eldad

+0

원래'InputStream'을 사용할 수없는 이유는 무엇입니까? 'GZIPInputStream'을 내가 알고있는 바이트로 먹일 수있는 유일한 안전한 방법은 모든 바이트를 먼저 메모리로 읽어들이는 것입니다. 이것은 큰 파일로 원하는 것이 아닙니다. –

+0

상황을 더 자세히 설명하기 위해 세부 정보가 추가되었으므로 독점 프로토콜 안에 파일 청크가 포함되어있어 InputStream에서 전체 프로토콜 메시지를 가져 와서 구문 분석 한 다음 파일 청크를 추출한 다음 청크를 압축 해제 할 수 있습니다. 클라이언트를 제어하지 않고 다음 파일 청크를 포함하는 다음 메시지가 도착할 때를 모릅니다. 감사와 나쁜 설명에 대한 미안. – Eldad

0

gzip 된 스트림의 임의의 바이트 시퀀스가 ​​유효한 독립형 gzip 데이터가 아닙니다. 한 가지 방법 또는 다른 방법으로 모든 바이트 청크를 연결해야합니다.

가장 쉬운 방법은 간단한 파이프 그들 모두를 축적하는 것입니다

import java.io.PipedOutputStream; 
import java.io.IOException; 
import java.util.zip.GZIPInputStream; 

public class ChunkInflater { 
    private final PipedOutputStream pipe; 

    private final InputStream stream; 

    public ChunkInflater() 
    throws IOException { 
     pipe = new PipedOutputStream(); 
     stream = new GZIPInputStream(new PipedInputStream(pipe)); 
    } 

    public InputStream getInputStream() { 
     return stream; 
    } 

    public void addChunk(byte[] compressedChunk) 
    throws IOException { 
     pipe.write(compressedChunk); 
    } 
} 

지금 당신은 당신이 원하는 무엇이든 단위로 읽을 수있는 InputStream를 가지고있다. 예 :

ChunkInflater inflater = new ChunkInflater(); 

Callable<Void> chunkReader = new Callable<Void>() { 
    @Override 
    public Void call() 
    throws IOException { 
     byte[] chunk; 
     while ((chunk = readChunkFromSource()) != null) { 
      inflater.addChunk(chunk); 
     } 

     return null; 
    } 
}; 
ExecutorService executor = Executors.newSingleThreadExecutor(); 
executor.submit(chunkReader); 
executor.shutdown(); 

Reader reader = new InputStreamReader(inflater.getInputStream()); 
// read text here