Jak mogę uzyskać java.io.InputStream z java.lang.String?

95

Mam plik String, którego chcę użyć jako InputStream. W Javie 1.0 możesz użyć java.io.StringBufferInputStream, ale tak było @Deprecrated(nie bez powodu - nie możesz określić kodowania zestawu znaków):

Ta klasa nie konwertuje poprawnie znaków na bajty. Począwszy od JDK 1.1, preferowanym sposobem tworzenia strumienia z łańcucha jest użycie StringReader klasy.

Możesz utworzyć za java.io.Readerpomocą java.io.StringReader, ale nie ma adapterów do wzięcia Readeri utworzenia InputStream.

Znalazłem starożytny błąd proszący o odpowiednią wymianę, ale nic takiego nie istnieje - o ile wiem.

Często sugerowanym rozwiązaniem jest użycie java.lang.String.getBytes()jako danych wejściowych do java.io.ByteArrayInputStream:

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

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

ale to oznacza materializację całości Stringw pamięci jako tablicę bajtów i udaremnia cel strumienia. W większości przypadków nie jest to wielka sprawa, ale szukałem czegoś, co zachowałoby intencję strumienia - aby jak najmniej danych było (ponownie) zmaterializowanych w pamięci.

Jared Oberhaus
źródło

Odpowiedzi:

78

Aktualizacja: ta odpowiedź jest dokładnie tym, czego OP nie chce. Przeczytaj pozostałe odpowiedzi.

W przypadkach, gdy nie zależy nam na ponownym zmaterializowaniu danych w pamięci, użyj:

new ByteArrayInputStream(str.getBytes("UTF-8"))
Andres Riofrio
źródło
3
Rozwiązanie zaproponowane w tej odpowiedzi było antycypowane, rozważane i odrzucane w pytaniu. Więc moim zdaniem ta odpowiedź powinna zostać usunięta.
Mike Nakis,
1
Możesz mieć rację. Pierwotnie napisałem to jako komentarz prawdopodobnie dlatego, że nie była to właściwa odpowiedź na pytanie OP.
Andres Riofrio,
28
Jako gość przyjeżdżający tutaj ze względu na tytuł pytania, cieszę się, że ta odpowiedź jest tutaj. Więc: proszę, nie usuwaj tej odpowiedzi. Uwaga u góry „Tej odpowiedzi nie chce OP. Przeczytaj pozostałe odpowiedzi”. jest wystarczający.
Yaakov Belch
10
Od java7:new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8))
wolno
19

Jeśli nie masz nic przeciwko zależności od pakietu commons-io , możesz użyć metody IOUtils.toInputStream (tekst String) .

Fotis Paraskevopoulos
źródło
11
W takim przypadku dodajesz zależność, która nie robi nic poza `return new ByteArrayInputStream (input.getBytes ()); ' Czy to naprawdę warte zależności? Szczerze mówiąc, nie - tak nie jest.
whaefelinger
3
To prawda, poza tym jest to dokładnie obejście, którego op nie chce użyć, ponieważ nie chce "zmaterializować struny w pamięci" w przeciwieństwie do tego, że struna materializuje się gdzie indziej w systemie :)
Fotis Paraskevopoulos
Czy mamy jakąś bibliotekę, która konwertuje niestandardowy obiekt na źródło strumienia wejściowego; coś w rodzaju IOUtils.toInputStream (obiekt MyObject)?
nawazish-stackoverflow
5

Istnieje adapter firmy Apache Commons-IO, który dostosowuje się z programu Reader do InputStream i nazywa się ReaderInputStream .

Przykładowy kod:

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

Źródła: https://stackoverflow.com/a/27909221/5658642

bić
źródło
3

Moim zdaniem najłatwiejszym sposobem na to jest przepchnięcie danych przez moduł 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();
  }
}

Implementacja JVM Używam danych wypychanych w porcjach 8K, ale możesz mieć pewien wpływ na rozmiar bufora, zmniejszając liczbę znaków zapisywanych jednocześnie i wywołując flush.


Alternatywa dla pisania własnego opakowania CharsetEncoder w celu użycia Writera do kodowania danych, chociaż jest to trochę trudne. Powinno to być niezawodne (jeśli nieefektywne) wdrożenie:

/** 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
źródło
2

Cóż, jednym z możliwych sposobów jest:

  • Stwórz PipedOutputStream
  • Przełóż to do PipedInputStream
  • Zawiń OutputStreamWriterwokół PipedOutputStream(możesz określić kodowanie w konstruktorze)
  • Et voilá, wszystko, do czego napiszesz, OutputStreamWritermożna przeczytać w PipedInputStream!

Oczywiście wydaje się to raczej hakerskim sposobem na zrobienie tego, ale przynajmniej tak jest.

Michael Myers
źródło
1
Ciekawe ... oczywiście, dzięki temu rozwiązaniu uważam, że albo zmaterializowałbyś cały ciąg w pamięci, albo cierpiałby głód na wątku czytającym. Wciąż mam nadzieję, że gdzieś jest prawdziwa implementacja.
Jared Oberhaus
5
Musisz uważać na strumień potokowy (wejście | wyjście). Zgodnie z dokumentacją: „… Próba użycia obu obiektów z jednego wątku nie jest zalecana, ponieważ może to zablokować wątek…” java.sun.com/j2se/1.4.2/docs/api/java/ io / PipedInputStream.html
Bryan Kyle
1

Rozwiązaniem jest utworzenie własnego, tworząc InputStreamimplementację, która prawdopodobnie java.nio.charset.CharsetEncoderużyłaby do zakodowania każdego charlub fragmentu chars do tablicy bajtów w InputStreamrazie potrzeby.

Jared Oberhaus
źródło
1
Robienie rzeczy pojedynczo jest kosztowne. Dlatego mamy „podzielone na fragmenty iteratory”, takie jak InputStream, które pozwalają nam jednocześnie odczytywać bufor.
Tom Hawtin - tackline
Zgadzam się z Tomem - naprawdę nie chcesz robić tej jednej postaci na raz.
Eddie
1
Chyba że dane są naprawdę małe, a inne rzeczy (na przykład opóźnienie sieci) trwają dłużej. Wtedy to nie ma znaczenia. :)
Andres Riofrio
0

Możesz skorzystać z pomocy biblioteki org.hsqldb.lib.

public StringInputStream(String paramString)
  {
    this.str = paramString;
    this.available = (paramString.length() * 2);
  }
omar
źródło
1
Ogólnie pytania są znacznie bardziej przydatne, jeśli zawierają wyjaśnienie, do czego służy kod.
Peter
-1

Wiem, że to stare pytanie, ale sam miałem dzisiaj ten sam problem, a to było moje rozwiązanie:

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
źródło