Comment puis-je obtenir un java.io.InputStream à partir d'un java.lang.String?

95

J'ai un Stringfichier que je souhaite utiliser comme fichier InputStream. Dans Java 1.0, vous pouvez utiliser java.io.StringBufferInputStream, mais cela a été @Deprecrated(avec raison - vous ne pouvez pas spécifier le codage du jeu de caractères):

Cette classe ne convertit pas correctement les caractères en octets. Depuis JDK 1.1, la méthode préférée pour créer un flux à partir d'une chaîne est via la StringReader classe.

Vous pouvez créer un java.io.Readeravec java.io.StringReader, mais il n'y a pas d'adaptateurs pour prendre un Readeret créer un InputStream.

J'ai trouvé un ancien bug demandant un remplacement approprié, mais rien de tel n'existe - pour autant que je sache.

La solution de contournement souvent suggérée consiste à utiliser java.lang.String.getBytes()comme entrée pour java.io.ByteArrayInputStream:

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

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

mais cela signifie matérialiser le tout Stringen mémoire comme un tableau d'octets, et va à l'encontre de l'objectif d'un flux. Dans la plupart des cas, ce n'est pas un gros problème, mais je cherchais quelque chose qui préserverait l'intention d'un flux - que le moins de données possible soit (re) matérialisé en mémoire.

Jared Oberhaus
la source

Réponses:

78

Mise à jour: Cette réponse est précisément ce que l'OP ne veut pas. Veuillez lire les autres réponses.

Pour les cas où nous ne nous soucions pas de la re-matérialisation des données en mémoire, veuillez utiliser:

new ByteArrayInputStream(str.getBytes("UTF-8"))
Andres Riofrio
la source
3
La solution proposée par cette réponse a été anticipée, envisagée et rejetée par la question. Donc, à mon avis, cette réponse devrait être supprimée.
Mike Nakis le
1
Il se peut que vous ayez raison. Au départ, je lui ai fait un commentaire probablement parce que ce n'était pas une réponse réelle à la question d'OP.
Andres Riofrio
28
En tant que visiteur venant ici à cause du titre de la question, je suis heureux que cette réponse soit là. Donc: veuillez ne pas supprimer cette réponse. La remarque en haut "Cette réponse est précisément ce que le PO ne veut pas. Veuillez lire les autres réponses." est suffisant.
Yaakov Belch
10
À partir de java7:new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8))
lent le
19

Si une dépendance sur le package commons-io ne vous dérange pas , vous pouvez utiliser la méthode IOUtils.toInputStream (String text) .

Fotis Paraskevopoulos
la source
11
Dans ce cas, vous ajoutez une dépendance qui ne fait rien d'autre que `return new ByteArrayInputStream (input.getBytes ()); ' Cela vaut-il vraiment une dépendance? En toute honnêteté, non - ce n'est pas le cas.
whaefelinger
3
Certes, en plus c'est exactement la solution de contournement que l'op ne veut pas utiliser car il ne veut pas "matérialiser la chaîne en mémoire" par opposition à la chaîne matérialisée ailleurs dans le système :)
Fotis Paraskevopoulos
Avons-nous une bibliothèque qui convertit un objet personnalisé en source de flux d'entrée; quelque chose comme IOUtils.toInputStream (objet MyObject)?
nawazish-stackoverflow
5

Il existe un adaptateur d'Apache Commons-IO qui s'adapte de Reader à InputStream, appelé ReaderInputStream .

Exemple de code:

@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éférence: https://stackoverflow.com/a/27909221/5658642

battre
la source
3

À mon avis, le moyen le plus simple de le faire est de transmettre les données à 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'implémentation JVM que j'utilise avec des données poussées par blocs de 8K, mais vous pourriez avoir un effet sur la taille de la mémoire tampon en réduisant le nombre de caractères écrits en même temps et en appelant flush.


Une alternative à l'écriture de votre propre wrapper CharsetEncoder pour utiliser un Writer pour encoder les données, bien que ce soit quelque chose de pénible à faire correctement. Cela devrait être une implémentation fiable (si inefficace):

/** 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
la source
2

Eh bien, une façon possible est de:

  • Créer un PipedOutputStream
  • Dirigez-le vers un PipedInputStream
  • Enveloppez un OutputStreamWriterautour du PipedOutputStream(vous pouvez spécifier l'encodage dans le constructeur)
  • Et voilá, tout ce que vous écrivez sur le OutputStreamWriterpeut être lu à partir du PipedInputStream!

Bien sûr, cela semble être une façon plutôt hackish de le faire, mais au moins c'est une façon.

Michael Myers
la source
1
Intéressant ... bien sûr, avec cette solution, je pense que vous matérialiseriez toute la chaîne en mémoire ou souffririez de la famine sur le fil de lecture. En espérant toujours qu'il y ait une vraie implémentation quelque part.
Jared Oberhaus
5
Vous devez être prudent avec le flux canalisé (entrée | sortie). Selon la documentation: "... Tenter d'utiliser les deux objets à partir d'un seul thread n'est pas recommandé, car cela peut bloquer
Bryan Kyle
1

Une solution consiste à générer la vôtre, en créant une InputStreamimplémentation qui utiliserait probablement java.nio.charset.CharsetEncoderpour encoder chaque charou morceau de chars en un tableau d'octets pour le InputStreamsi nécessaire.

Jared Oberhaus
la source
1
Faire les choses un personnage à la fois coûte cher. C'est pourquoi nous avons des "itérateurs fragmentés" comme InputStream qui nous permettent de lire un tampon à la fois.
Tom Hawtin - ligne de pêche
Je suis d'accord avec Tom - vous vraiment pas faire ce personnage à la fois.
Eddie
1
À moins que les données ne soient vraiment petites et que d'autres choses (la latence du réseau, par exemple) prennent plus de temps. Alors ça n'a pas d'importance. :)
Andres Riofrio
0

Vous pouvez prendre l'aide de la bibliothèque org.hsqldb.lib.

public StringInputStream(String paramString)
  {
    this.str = paramString;
    this.available = (paramString.length() * 2);
  }
omar
la source
1
En général, les questions sont beaucoup plus utiles si elles incluent une explication de ce que le code est censé faire.
Peter
-1

Je sais que c'est une vieille question mais j'ai moi-même eu le même problème aujourd'hui, et c'était ma solution:

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
la source