2009-05-08 14 views
83

Ho un String che voglio usare come InputStream. In Java 1.0, è possibile utilizzare java.io.StringBufferInputStream, ma che è stato @Deprecrated (con buona ragione - non è possibile specificare la codifica del set di caratteri):Come posso ottenere un java.io.InputStream da una java.lang.String?

Questa classe non converte correttamente caratteri in byte. A partire da JDK 1.1, , il modo migliore per creare uno stream da una stringa è tramite la classe StringReader .

è possibile creare un java.io.Reader con java.io.StringReader, ma non ci sono gli adattatori per fare una Reader e creare un InputStream.

Ho trovato un ancient bug per chiedere un sostituto adatto, ma non esiste nulla di simile - per quanto posso dire.

La soluzione spesso suggerita consiste nell'utilizzare java.lang.String.getBytes() come input per java.io.ByteArrayInputStream:

public InputStream createInputStream(String s, String charset) 
    throws java.io.UnsupportedEncodingException { 

    return new ByteArrayInputStream(s.getBytes(charset)); 
} 

ma che significa materializzando l'intero String in memoria come un array di byte, e sconfigge lo scopo di un flusso. Nella maggior parte dei casi questo non è un grosso problema, ma stavo cercando qualcosa che preservasse l'intento di un flusso: che il minimo possibile di dati sia (ri) materializzato nella memoria.

+0

@djb L'ha fatto. –

risposta

2

Beh, in un modo possibile è quello di:

  • Creare un PipedOutputStream
  • tubo ad una PipedInputStream
  • Wrap un OutputStreamWriter intorno al PipedOutputStream (è possibile specificare la codifica nel costruttore)
  • Et voil & aacute; tutto ciò che scrivi su OutputStreamWriter può essere letto dallo PipedInputStream!

Ovviamente, questo sembra un modo piuttosto abile di farlo, ma almeno è un modo.

+1

Interessante ... ovviamente, con questa soluzione credo che si materializzerebbe l'intera stringa in memoria o si soffrirebbe la fame sul thread di lettura. Sperando ancora che ci sia una vera implementazione da qualche parte. –

+4

Devi stare attento con il flusso Piped (Input | Output). Come per i documenti: "... Si sconsiglia di utilizzare entrambi gli oggetti da un singolo thread, poiché potrebbe bloccare il thread in modo permanente ..." http://java.sun.com/j2se/1.4.2/docs/ api/java/io/PipedInputStream.html –

2

Una soluzione è quella di rotolare il proprio, creando un InputStream attuazione che probabilmente userebbe java.nio.charset.CharsetEncoder per codificare ciascuna char o pezzo di char s ad un array di byte per il InputStream come necessario.

+1

Fare cose un carattere alla volta è costoso. Ecco perché abbiamo "iterators chunked" come InputStream che ci permettono di leggere un buffer alla volta. –

+0

Sono d'accordo con Tom: tu ** veramente ** non vuoi fare questo personaggio alla volta. – Eddie

+1

A meno che i dati non siano molto piccoli, e altre cose (latenza di rete, ad esempio) richiedono più tempo. Quindi non importa. :) –

3

A mio parere, il modo più semplice per farlo è spingendo i dati attraverso un Writer:

public class StringEmitter { 
    public static void main(String[] args) throws IOException { 
    class DataHandler extends OutputStream { 
     @Override 
     public void write(final int b) throws IOException { 
     write(new byte[] { (byte) b }); 
     } 
     @Override 
     public void write(byte[] b) throws IOException { 
     write(b, 0, b.length); 
     } 
     @Override 
     public void write(byte[] b, int off, int len) 
      throws IOException { 
     System.out.println("bytecount=" + len); 
     } 
    } 

    StringBuilder sample = new StringBuilder(); 
    while (sample.length() < 100 * 1000) { 
     sample.append("sample"); 
    } 

    Writer writer = new OutputStreamWriter(
     new DataHandler(), "UTF-16"); 
    writer.write(sample.toString()); 
    writer.close(); 
    } 
} 

L'implementazione JVM sto usando spinto dati attraverso 8K pezzi, ma si potrebbe avere qualche effetto sulla dimensione del buffer riducendo il numero di caratteri scritti in una volta e chiamando flush.


Un'alternativa a scrivere il proprio involucro CharsetEncoder utilizzare un Writer per codificare i dati, anche se si tratta di qualcosa di un dolore a fare il bene.Questo dovrebbe essere un affidabile implementazione (se inefficiente):

/** Inefficient string stream implementation */ 
public class StringInputStream extends InputStream { 

    /* # of characters to buffer - must be >=2 to handle surrogate pairs */ 
    private static final int CHAR_CAP = 8; 

    private final Queue<Byte> buffer = new LinkedList<Byte>(); 
    private final Writer encoder; 
    private final String data; 
    private int index; 

    public StringInputStream(String sequence, Charset charset) { 
    data = sequence; 
    encoder = new OutputStreamWriter(
     new OutputStreamBuffer(), charset); 
    } 

    private int buffer() throws IOException { 
    if (index >= data.length()) { 
     return -1; 
    } 
    int rlen = index + CHAR_CAP; 
    if (rlen > data.length()) { 
     rlen = data.length(); 
    } 
    for (; index < rlen; index++) { 
     char ch = data.charAt(index); 
     encoder.append(ch); 
     // ensure data enters buffer 
     encoder.flush(); 
    } 
    if (index >= data.length()) { 
     encoder.close(); 
    } 
    return buffer.size(); 
    } 

    @Override 
    public int read() throws IOException { 
    if (buffer.size() == 0) { 
     int r = buffer(); 
     if (r == -1) { 
     return -1; 
     } 
    } 
    return 0xFF & buffer.remove(); 
    } 

    private class OutputStreamBuffer extends OutputStream { 

    @Override 
    public void write(int i) throws IOException { 
     byte b = (byte) i; 
     buffer.add(b); 
    } 

    } 

} 
+3

Questa soluzione "più semplice" richiede molto codice. – kurtzbot

16

Se non ti dispiace una dipendenza sul pacchetto commons-io, allora si potrebbe utilizzare il metodo IOUtils.toInputStream(String text).

+9

In questo caso si aggiunge una dipendenza che non fa altro che restituire un nuovo ByteArrayInputStream (input.getBytes()); ' Vale davvero la pena una dipendenza? In tutta onestà, no - non lo è. – whaefelinger

+1

È vero, inoltre è esattamente la soluzione alternativa che l'op non vuole utilizzare perché non vuole "materializzare la stringa in memoria", opta per la stringa che si sta materializzando da qualche altra parte nel sistema :) –

+0

Abbiamo qualsiasi libreria che converte oggetto personalizzato in sorgente di flusso di input; qualcosa come IOUtils.toInputStream (oggetto MyObject)? –

72

Aggiornamento: Questa risposta è esattamente ciò che l'OP non desidera. Si prega di leggere le altre risposte.

Per quei casi in cui non si preoccupano i dati che vengono ri-materializzato in memoria, per favore usa:

new ByteArrayInputStream(str.getBytes("UTF-8")) 
+3

La soluzione proposta da questa risposta è stata anticipata, contemplata e respinta dalla domanda. Quindi, a mio parere, questa risposta dovrebbe essere cancellata. –

+1

Potrebbe aver ragione. Inizialmente ho fatto un commento probabilmente perché non era una risposta reale alla domanda dell'OP. –

+23

Come visitatore che viene qui a causa del titolo della domanda, sono felice che questa risposta sia qui. Quindi: per favore non cancellare questa risposta. L'osservazione in cima "Questa risposta è esattamente ciò che l'OP non vuole. Per favore leggi le altre risposte". è sufficiente. –

0

So che questa è una vecchia questione, ma ho avuto lo stesso problema io stesso oggi, e questa è stata la mia soluzione:

public static InputStream getStream(final CharSequence charSequence) { 
return new InputStream() { 
    int index = 0; 
    int length = charSequence.length(); 
    @Override public int read() throws IOException { 
    return index>=length ? -1 : charSequence.charAt(index++); 
    } 
}; 
} 
+6

Questo non funziona per non ASCII. – slow

3

C'è un adattatore da Apache Commons-IO che si adatta da Reader per InputStream, che prende il nome ReaderInputStream.

codice Esempio:

@Test 
public void testReaderInputStream() throws IOException { 
    InputStream inputStream = new ReaderInputStream(new StringReader("largeString"), StandardCharsets.UTF_8); 
    Assert.assertEquals("largeString", IOUtils.toString(inputStream, StandardCharsets.UTF_8)); 
} 

Riferimento: https://stackoverflow.com/a/27909221/5658642

0

si può prendere aiuto di org.hsqldb.lib biblioteca.

public StringInputStream(String paramString) 
    { 
    this.str = paramString; 
    this.available = (paramString.length() * 2); 
    } 
+1

In generale, le domande sono molto più utili se includono una spiegazione di ciò che il codice è destinato a fare. – Peter

Problemi correlati