Wie kann ich einen java.io.InputStream von einem java.lang.String erhalten?

95

Ich habe eine String, die ich als verwenden möchte InputStream. In Java 1.0 könnten Sie verwenden java.io.StringBufferInputStream, aber das war @Deprecrated(aus gutem Grund - Sie können die Zeichensatzcodierung nicht angeben):

Diese Klasse konvertiert Zeichen nicht richtig in Bytes. Ab JDK 1.1 ist die bevorzugte Methode zum Erstellen eines Streams aus einer Zeichenfolge die StringReader Klasse.

Sie können ein java.io.Readermit erstellen java.io.StringReader, aber es gibt keine Adapter, um ein zu Readererstellen InputStream.

Ich habe einen alten Fehler gefunden , der nach einem geeigneten Ersatz gefragt hat, aber so etwas gibt es nicht - soweit ich das beurteilen kann.

Die häufig vorgeschlagene Problemumgehung besteht darin, Folgendes java.lang.String.getBytes()als Eingabe zu verwenden java.io.ByteArrayInputStream:

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

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

Dies bedeutet jedoch, das gesamte StringSpeicher als Array von Bytes zu materialisieren und den Zweck eines Streams zu vereiteln. In den meisten Fällen ist dies keine große Sache, aber ich suchte nach etwas, das die Absicht eines Streams bewahrt - dass so wenig Daten wie möglich im Speicher (wieder) materialisiert werden.

Jared Oberhaus
quelle

Antworten:

78

Update: Diese Antwort ist genau das, was das OP nicht will. Bitte lesen Sie die anderen Antworten.

In den Fällen, in denen es uns nicht wichtig ist, dass die Daten im Speicher wiederhergestellt werden, verwenden Sie bitte:

new ByteArrayInputStream(str.getBytes("UTF-8"))
Andres Riofrio
quelle
3
Die in dieser Antwort vorgeschlagene Lösung wurde von der Frage vorweggenommen, in Betracht gezogen und abgelehnt. Daher sollte diese Antwort meiner Meinung nach gelöscht werden.
Mike Nakis
1
Du hast vielleicht recht. Ich habe es ursprünglich zu einem Kommentar gemacht, wahrscheinlich weil es keine tatsächliche Antwort auf die Frage von OP war.
Andres Riofrio
28
Als Besucher, der wegen des Fragentitels hierher kommt, bin ich froh, dass diese Antwort hier ist. Also: Bitte löschen Sie diese Antwort nicht. Die Bemerkung oben "Diese Antwort ist genau das, was das OP nicht will. Bitte lesen Sie die anderen Antworten." ist ausreichend.
Yaakov Belch
10
Ab Java7:new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8))
Langsam
19

Wenn Ihnen eine Abhängigkeit vom commons-io- Paket nichts ausmacht , können Sie die Methode IOUtils.toInputStream (String text) verwenden.

Fotis Paraskevopoulos
quelle
11
In diesem Fall fügen Sie eine Abhängigkeit hinzu, die nichts anderes bewirkt als "return new ByteArrayInputStream (input.getBytes ())". Ist das wirklich eine Abhängigkeit wert? Ehrlich gesagt, nein - das ist es nicht.
Whaefelinger
3
Es stimmt, außerdem ist es genau die Problemumgehung, die der Op nicht verwenden möchte, weil er nicht "die Zeichenfolge in den Speicher materialisieren" möchte, wenn die Zeichenfolge irgendwo anders im System materialisiert wird :)
Fotis Paraskevopoulos
Haben wir eine Bibliothek, die benutzerdefinierte Objekte in die Quelle des Eingabestreams konvertiert? so etwas wie IOUtils.toInputStream (MyObject-Objekt)?
Nawazish-Stackoverflow
5

Es gibt einen Adapter von Apache Commons-IO, der sich von Reader an InputStream anpasst und den Namen ReaderInputStream trägt .

Beispielcode:

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

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

schlagen
quelle
3

Meiner Meinung nach ist es am einfachsten, die Daten durch einen Writer zu übertragen:

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

Bei der JVM-Implementierung verwende ich Push-Daten in 8-KByte-Blöcken. Sie können jedoch die Puffergröße beeinflussen, indem Sie die Anzahl der gleichzeitig geschriebenen Zeichen verringern und Flush aufrufen.


Eine Alternative zum Schreiben eines eigenen CharsetEncoder-Wrappers zur Verwendung eines Writers zum Codieren der Daten, obwohl es schwierig ist, das Richtige zu tun. Dies sollte eine zuverlässige (wenn auch ineffiziente) Implementierung sein:

/** 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
quelle
2

Ein möglicher Weg ist:

  • Ein ... kreieren PipedOutputStream
  • Pipe es zu einem PipedInputStream
  • Umschließen Sie OutputStreamWriterdie PipedOutputStream(Sie können die Codierung im Konstruktor angeben)
  • Et voilá, alles, was Sie an schreiben, OutputStreamWriterkann aus dem PipedInputStream!

Natürlich scheint dies ein ziemlich hackiger Weg zu sein, aber zumindest ist es ein Weg.

Michael Myers
quelle
1
Interessant ... natürlich glaube ich, dass Sie mit dieser Lösung entweder die gesamte Zeichenfolge im Speicher materialisieren oder am Lesethread verhungern würden. Ich hoffe immer noch, dass es irgendwo eine echte Implementierung gibt.
Jared Oberhaus
5
Sie müssen mit Piped (Input | Output) Stream vorsichtig sein. Gemäß den Dokumenten: "... Der Versuch, beide Objekte von einem einzigen Thread aus zu verwenden, wird nicht empfohlen, da dies den Thread blockieren
Bryan Kyle
1

Eine Lösung besteht darin, eine eigene zu erstellen und eine InputStreamImplementierung zu erstellen , die wahrscheinlich verwendet wird java.nio.charset.CharsetEncoder, um jedes charoder einen Teil von chars nach InputStreamBedarf in ein Array von Bytes zu codieren .

Jared Oberhaus
quelle
1
Es ist teuer, Dinge einzeln zu tun. Aus diesem Grund haben wir "Chunk-Iteratoren" wie InputStream, mit denen wir jeweils einen Puffer lesen können.
Tom Hawtin - Tackline
Ich stimme Tom zu - du willst wirklich nicht diesen einen Charakter gleichzeitig machen.
Eddie
1
Es sei denn, die Daten sind wirklich klein und andere Dinge (z. B. Netzwerklatenz) dauern länger. Dann ist es egal. :) :)
Andres Riofrio
0

Sie können die Bibliothek org.hsqldb.lib verwenden.

public StringInputStream(String paramString)
  {
    this.str = paramString;
    this.available = (paramString.length() * 2);
  }
omar
quelle
1
Im Allgemeinen sind Fragen viel nützlicher, wenn sie eine Erklärung enthalten, was der Code tun soll.
Peter
-1

Ich weiß, dass dies eine alte Frage ist, aber ich hatte heute selbst das gleiche Problem, und dies war meine Lösung:

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
quelle