Como posso obter um java.io.InputStream de um java.lang.String?

95

Eu tenho um Stringque quero usar como um InputStream. No Java 1.0, você poderia usar java.io.StringBufferInputStream, mas tem sido @Deprecrated(com um bom motivo - você não pode especificar a codificação do conjunto de caracteres):

Esta classe não converte corretamente caracteres em bytes. A partir do JDK 1.1, a maneira preferida de criar um fluxo de uma string é por meio da StringReader classe.

Você pode criar um java.io.Readercom java.io.StringReader, mas não há adaptadores para pegar um Readere criar um InputStream.

Eu encontrei um bug antigo pedindo uma substituição adequada, mas tal coisa não existe - pelo que eu posso dizer.

A solução alternativa frequentemente sugerida é usar java.lang.String.getBytes()como entrada para java.io.ByteArrayInputStream:

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

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

mas isso significa materializar o todo Stringna memória como um array de bytes, e anula o propósito de um stream. Na maioria dos casos, isso não é grande coisa, mas eu estava procurando por algo que preservasse a intenção de um stream - que o mínimo possível de dados seja (re) materializado na memória.

Jared Oberhaus
fonte

Respostas:

78

Atualização: Essa resposta é exatamente o que o OP não quer. Por favor, leia as outras respostas.

Para aqueles casos em que não nos importamos com os dados sendo materializados novamente na memória, use:

new ByteArrayInputStream(str.getBytes("UTF-8"))
Andres Riofrio
fonte
3
A solução proposta com esta resposta foi antecipada, contemplada e rejeitada pela pergunta. Portanto, na minha opinião, esta resposta deve ser excluída.
Mike Nakis
1
Você pode estar certo. Eu originalmente fiz um comentário provavelmente porque não era uma resposta real à pergunta de OP.
Andres Riofrio
28
Como um visitante que vem aqui por causa do título da pergunta, estou feliz que esta resposta esteja aqui. Portanto: Por favor, não exclua esta resposta. A observação no topo "Esta resposta é exatamente o que o OP não deseja. Leia as outras respostas." é suficiente.
Arroto Yaakov
10
A partir de java7:new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8))
lento
19

Se você não se importa com a dependência do pacote commons-io , pode usar o método IOUtils.toInputStream (String text) .

Fotis Paraskevopoulos
fonte
11
Nesse caso, você adiciona uma dependência que nada mais faz do que `return new ByteArrayInputStream (input.getBytes ()); ' Isso realmente vale a pena depender? Com toda a honestidade, não - não é.
Whaefelinger
3
É verdade, além disso, é exatamente a solução que o op não quer usar porque ele não quer "materializar a string na memória" ao contrário da string que está sendo materializada em outro lugar no sistema :)
Fotis Paraskevopoulos
Temos alguma biblioteca que converta objeto personalizado em fonte de fluxo de entrada; algo como IOUtils.toInputStream (objeto MyObject)?
nawazish-stackoverflow
5

Há um adaptador do Apache Commons-IO que se adapta do Reader para o InputStream, que é denominado ReaderInputStream .

Código de exemplo:

@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));
}

Referência: https://stackoverflow.com/a/27909221/5658642

batida
fonte
3

Em minha opinião, a maneira mais fácil de fazer isso é enviar os dados por meio de um 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();
  }
}

A implementação da JVM que estou usando por push de dados em blocos de 8K, mas você pode ter algum efeito no tamanho do buffer, reduzindo o número de caracteres gravados de uma vez e chamando flush.


Uma alternativa para escrever seu próprio invólucro CharsetEncoder para usar um gravador para codificar os dados, embora seja difícil fazer da maneira certa. Esta deve ser uma implementação confiável (se ineficiente):

/** 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);
    }

  }

}
McDowell
fonte
2

Bem, uma maneira possível é:

  • Crie um PipedOutputStream
  • Canalize para um PipedInputStream
  • Enrole uma OutputStreamWriterem torno da PipedOutputStream(você pode especificar a codificação no construtor)
  • Et voilá, qualquer coisa que você escrever no OutputStreamWriterpode ser lido no PipedInputStream!

Claro, essa parece uma maneira bastante hackeada de fazer isso, mas pelo menos é uma maneira.

Michael Myers
fonte
1
Interessante ... claro, com esta solução acredito que você materializaria todo o barbante na memória ou passaria fome no fio de leitura. Ainda esperando que haja uma implementação real em algum lugar.
Jared Oberhaus
5
Você deve ter cuidado com o fluxo encanado (entrada | saída). De acordo com os documentos: "... A tentativa de usar os dois objetos a partir de um único segmento não é recomendado, pois pode impasse o fio ..." java.sun.com/j2se/1.4.2/docs/api/java/ io / PipedInputStream.html
Bryan Kyle
1

Uma solução é lançar o seu próprio, criando uma InputStreamimplementação que provavelmente seria usada java.nio.charset.CharsetEncoderpara codificar cada um charou um pedaço de chars em um array de bytes para o InputStreamconforme necessário.

Jared Oberhaus
fonte
1
Fazer as coisas com um personagem de cada vez é caro. É por isso que temos "iteradores fragmentados" como InputStream, que nos permitem ler um buffer por vez.
Tom Hawtin - tackline
Eu concordo com o Tom - você realmente não quer fazer um personagem de cada vez.
Eddie
1
A menos que os dados sejam realmente pequenos e outras coisas (latência da rede, por exemplo) demorem mais. Então não importa. :)
Andres Riofrio
0

Você pode obter ajuda da biblioteca org.hsqldb.lib.

public StringInputStream(String paramString)
  {
    this.str = paramString;
    this.available = (paramString.length() * 2);
  }
omar
fonte
1
Geralmente, as perguntas são muito mais úteis se incluem uma explicação do que o código se destina a fazer.
Peter
-1

Eu sei que esta é uma pergunta antiga, mas eu tive o mesmo problema hoje, e esta foi a minha solução:

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++);
  }
 };
}
Paul Richards
fonte