2009-06-25 9 views
3

Gibt es eine Möglichkeit, einen ByteBuffer mit einem BufferedReader zu lesen, ohne ihn erst zu einem String machen zu müssen? Ich möchte einen ziemlich großen ByteBuffer als Textzeilen durchlesen und aus Performance-Gründen möchte ich vermeiden, es auf die Festplatte zu schreiben. Der Aufruf von toString auf dem ByteBuffer funktioniert nicht, da der resultierende String zu groß ist (er löst java.lang.OutOfMemoryError: Java-Heapspeicher aus). Ich hätte gedacht, dass es etwas in der API geben würde, um einen ByteBuffer in einem geeigneten Leser zu verpacken, aber ich finde nichts passendes.BufferedReader für großen ByteBuffer?

Hier ist eine verkürzte Codebeispiel das zeigt, was ich tue):

// input stream is from Process getInputStream() 
public String read(InputStream istream) 
{ 
    ReadableByteChannel source = Channels.newChannel(istream); 
    ByteArrayOutputStream ostream = new ByteArrayOutputStream(bufferSize); 
    WritableByteChannel destination = Channels.newChannel(ostream); 
    ByteBuffer buffer = ByteBuffer.allocateDirect(writeBufferSize); 

    while (source.read(buffer) != -1) 
    { 
    buffer.flip(); 
    while (buffer.hasRemaining()) 
    { 
     destination.write(buffer); 
    } 
    buffer.clear(); 
    } 

    // this data can be up to 150 MB.. won't fit in a String. 
    result = ostream.toString(); 
    source.close(); 
    destination.close(); 
    return result; 
} 

// after the process is run, we call this method with the String 
public void readLines(String text) 
{ 
    BufferedReader reader = new BufferedReader(new StringReader(text)); 
    String line; 

    while ((line = reader.readLine()) != null) 
    { 
    // do stuff with line 
    } 
} 

Antwort

5

Es ist nicht klar, warum Sie ein Byte-Puffer verwenden, mit zu beginnen. Wenn Sie eine InputStream haben und Sie Zeilen dafür lesen möchten, warum verwenden Sie nicht einfach eine InputStreamReader verpackt in einem BufferedReader? Was bringt es, NIO zu involvieren?

Aufruf toString() auf einem ByteArrayOutputStream klingt wie eine schlechte Idee zu mir, auch wenn Sie den Raum für sie hatte: besser als ein Byte-Array zu erhalten und wickeln Sie es in einem ByteArrayInputStream und dann ein InputStreamReader, wenn Sie wirklich haben müssen a ByteArrayOutputStream. Wenn Sie wirklich wollen toString() anrufen, mindestens die Überladung verwenden, die den Namen der Zeichenkodierung zu verwenden verwendet - sonst wird es den Systemstandard verwenden, der wahrscheinlich nicht das ist, was Sie wollen.

EDIT: Okay, also wollen Sie wirklich NIO verwenden. Sie schreiben immer noch an eine ByteArrayOutputStream, so dass Sie am Ende mit einem BAOS mit den darin enthaltenen Daten enden. Wenn Sie vermeiden möchten, eine Kopie dieser Daten machen, müssen Sie von ByteArrayOutputStream, beispielsweise wie folgt abzuleiten:

public class ReadableByteArrayOutputStream extends ByteArrayOutputStream 
{ 
    /** 
    * Converts the data in the current stream into a ByteArrayInputStream. 
    * The resulting stream wraps the existing byte array directly; 
    * further writes to this output stream will result in unpredictable 
    * behavior. 
    */ 
    public InputStream toInputStream() 
    { 
     return new ByteArrayInputStream(array, 0, count); 
    } 
} 

Dann können Sie den Eingangsstrom erzeugen, wickeln Sie es in einer InputStreamReader, wickeln, dass in a BufferedReader, und du bist weg.

+0

Gute Frage - ich würde zustimmen, und das ist, was ich tun würde, wenn ich die Option hätte. Der Grund, warum ich in diesem Fall nicht kann, ist, dass ich nichts mit der Ausgabe aus dem Prozess (d. H. Dem InputStream) tun kann, bis der Prozess beendet ist, also muss ich ihn in einen Puffer setzen, um ihn später zu lesen. – Rob

+2

Also in ein Byte-Array mit ByteArrayOutputStream. Sobald Sie es als Byte-Array haben, geht es Ihnen gut. Das ist effektiv, was NIO sowieso machen wird, es ist einfacher mit BAOS. Wenn es gewaltig wird, möchten Sie möglicherweise Ihren eigenen ByteArrayOutputStream ableiten, der Ihnen direkten Zugriff auf das Byte-Array gibt, so dass Sie sich keine Gedanken über das Erstellen einer Kopie mit toByteArray() machen müssen. Es ist eine Schande, ByteArrayOutputStream hat keine "toByteArrayInputStream", damit Sie direkt von ihm lesen ... –

+1

Warum ich NIO benutze: teilweise, weil ich ein Masochist bin und bin entschlossen, NIO einmal und für herauszufinden alle (wenn das tatsächlich menschenmöglich ist), und teilweise, weil ich den InputStream so schnell wie möglich lesen möchte und NIO für solche Dinge schneller scheint. – Rob

4

Sie können NIO verwenden, aber hier besteht keine Notwendigkeit. Als Jon Skeet vorgeschlagen:

public byte[] read(InputStream istream) 
{ 
    ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
    byte[] buffer = new byte[1024]; // Experiment with this value 
    int bytesRead; 

    while ((bytesRead = istream.read(buffer)) != -1) 
    { 
    baos.write(buffer, 0, bytesRead); 
    } 

    return baos.toByteArray(); 
} 


// after the process is run, we call this method with the String 
public void readLines(byte[] data) 
{ 
    BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(data))); 
    String line; 

    while ((line = reader.readLine()) != null) 
    { 
    // do stuff with line 
    } 
} 
+1

Obwohl dies nicht die Antwort ist, die ich akzeptierte (da ich versuchen wollte, NIO zu verwenden), stellte sich heraus, dass das Verwenden von Standard-IO wie dieses schneller war als der NIO-Ansatz. Dennoch war es eine gute Lernerfahrung, NIO auszuprobieren. – Rob

0

Dies ist ein Beispiel:

public class ByteBufferBackedInputStream extends InputStream { 

    ByteBuffer buf; 

    public ByteBufferBackedInputStream(ByteBuffer buf) { 
     this.buf = buf; 
    } 

    public synchronized int read() throws IOException { 
     if (!buf.hasRemaining()) { 
      return -1; 
     } 
     return buf.get() & 0xFF; 
    } 

    @Override 
    public int available() throws IOException { 
     return buf.remaining(); 
    } 

    public synchronized int read(byte[] bytes, int off, int len) throws IOException { 
     if (!buf.hasRemaining()) { 
      return -1; 
     } 

     len = Math.min(len, buf.remaining()); 
     buf.get(bytes, off, len); 
     return len; 
    } 
} 

Und Sie es wie folgt verwenden können:

String text = "this is text"; // It can be Unicode text 
    ByteBuffer buffer = ByteBuffer.wrap(text.getBytes("UTF-8")); 

    InputStream is = new ByteBufferBackedInputStream(buffer); 
    InputStreamReader r = new InputStreamReader(is, "UTF-8"); 
    BufferedReader br = new BufferedReader(r); 
    BufferedReader br = new BufferedReader(r);