Guava équivalent pour IOUtils.toString (InputStream)

106

Apache Commons IO a une méthode pratique IOUtils.toString () pour lire un InputStreamdans une chaîne.

Puisque j'essaye de m'éloigner d'Apache Commons et de Guava : y a-t-il un équivalent dans Guava? J'ai regardé toutes les classes du com.google.common.iopackage et je n'ai rien trouvé d'aussi simple.

Edit: Je comprends et apprécie les problèmes avec les jeux de caractères. Il se trouve que je sais que toutes mes sources sont en ASCII (oui, ASCII, pas ANSI etc.), donc dans ce cas, l'encodage n'est pas un problème pour moi.

Sean Patrick Floyd
la source
2
À propos des jeux de caractères: Il est toujours bon pour une bibliothèque de vous demander de spécifier que vous savez à quel jeu de caractères vous avez affaire (par exemple Charsets.US_ASCII) plutôt que de vous laisser dire "eh, quel jeu de caractères je suppose?" ce que tout le monde semble heureux de faire. D'autant plus que Java n'utilise pas de valeur par défaut logique, comme UTF-8.
Publié
Je connais. C'est pourquoi j'utilise UTF-8 comme version par défaut dans ma propre réponse.
Sean Patrick Floyd
Voir aussi la documentation: code.google.com/p/guava-libraries/wiki/IOExplained
Vadzim
@Vadzim, ces documents n'existaient pas lorsque cette question a été posée :-)
Sean Patrick Floyd

Réponses:

85

Vous avez déclaré dans votre commentaire sur la réponse de Calum que vous alliez utiliser

CharStreams.toString(new InputStreamReader(supplier.get(), Charsets.UTF_8))

Ce code est problématique car les CharStreams.toString(Readable)états de surcharge :

Ne ferme pas le Readable.

Cela signifie que votre InputStreamReader, et par extension celui InputStreamrenvoyé par supplier.get(), ne sera pas fermé une fois ce code terminé.

Si, par contre, vous tirez parti du fait que vous semblez déjà avoir InputSupplier<InputStream>et utilisé la surcharge CharStreams.toString(InputSupplier<R extends Readable & Closeable>), la toStringméthode gérera à la fois la création et la fermeture de la Reader.

C'est exactement ce que Jon Skeet a suggéré, sauf qu'il n'y a en fait aucune surcharge de ce CharStreams.newReaderSupplierqui prend une InputStreamentrée ... vous devez lui donner un InputSupplier:

InputSupplier<? extends InputStream> supplier = ...
InputSupplier<InputStreamReader> readerSupplier = 
    CharStreams.newReaderSupplier(supplier, Charsets.UTF_8);

// InputStream and Reader are both created and closed in this single call
String text = CharStreams.toString(readerSupplier);

Le but InputSupplierest de vous faciliter la vie en permettant à Guava de gérer les pièces qui nécessitent un try-finallybloc laid pour s'assurer que les ressources sont correctement fermées.

Edit: Personnellement, je trouve ce qui suit (c'est ainsi que je l'écrirais, je décomposais simplement les étapes du code ci-dessus)

String text = CharStreams.toString(
    CharStreams.newReaderSupplier(supplier, Charsets.UTF_8));

pour être beaucoup moins verbeux que cela:

String text;
InputStreamReader reader = new InputStreamReader(supplier.get(), 
    Charsets.UTF_8);
boolean threw = true;
try {
  text = CharStreams.toString(reader);
  threw = false;
}
finally {
  Closeables.close(reader, threw);
}

C'est plus ou moins ce que vous auriez à écrire pour gérer cela correctement vous-même.


Edit: février 2014

InputSupplieret OutputSupplierles méthodes qui les utilisent sont devenues obsolètes dans Guava 16.0. Leurs remplaçants sont ByteSource, CharSource, ByteSinket CharSink. Étant donné a ByteSource, vous pouvez maintenant obtenir son contenu Stringcomme ceci:

ByteSource source = ...
String text = source.asCharSource(Charsets.UTF_8).read();
ColinD
la source
Merci pour les bonnes informations (+1). Mais c'est très verbeux. Je pense que combiner la réponse acceptée avec Closeables.closeQuietly () est plus facile.
Sean Patrick Floyd
@CollinD: J'ai utilisé votre méthode dans l'une de mes réponses.Veuillez consulter le code et dites-moi si c'est la bonne façon d'utiliser InputSupplier.
Emil
1
@ColinD, si le inputStream vient de l'intérieur d'un servlet doPost, est-il utile de le fermer? (ou s'inquiéter de le fermer)
Blankman
CharStreams.toString (InputSupplier) est désormais obsolète. J'ai créé un CharSource (à partir d'un ByteSource en utilisant asCharSource) puis utilisé son toString comme le suggèrent les documents.
John Lehmann le
4
@ TedM.Young: Si tout ce que vous avez est un InputStream, et que vous voulez l'obtenir en tant que String, CharStreams.toString(new InputStreamReader(inputStream, charset))c'est la voie à suivre. ByteSourceet CharSourcesont spécifiquement pour les cas où vous avez quelque chose qui peut agir comme une source de InputStreams ou Readers.
ColinD
56

Si vous en avez un, Readablevous pouvez utiliser CharStreams.toString(Readable). Vous pouvez donc probablement faire ce qui suit:

String string = CharStreams.toString( new InputStreamReader( inputStream, "UTF-8" ) );

Vous oblige à spécifier un jeu de caractères, ce que je suppose que vous devriez faire de toute façon.

Calum
la source
4
En fait, je vais utiliser une combinaison de vos réponses et de celles de Jon Skeet: `CharStreams.toString (new InputStreamReader (supplier.get (), Charsets.UTF_8))`
Sean Patrick Floyd
Oui, de nombreuses façons de combiner les options!
Calum du
10
@SPFloyd: Si vous avez un, InputSupplier<InputStream>je vous recommande fortement d'utiliser CharStreams.newReaderSupplier(supplier, Charsets.UTF_8)plutôt que new InputStreamReader. La raison en est que lorsque la donnée InputStreamReader, toStringsera pas près que Reader(et donc pas le flux sous - jacent!). En utilisant un InputSupplierfor the Reader, la toStringméthode gérera la fermeture du Readerpour vous.
ColinD
17

MISE À JOUR : Avec le recul, je n'aime pas mon ancienne solution. En outre, nous sommes maintenant en 2013 et il existe de meilleures alternatives disponibles maintenant pour Java7. Voici donc ce que j'utilise maintenant:

InputStream fis = ...;
String text;
try (  InputStreamReader reader = new InputStreamReader(fis, Charsets.UTF_8)){
        text = CharStreams.toString(reader);
}

ou si avec InputSupplier

InputSupplier<InputStreamReader> spl = ...
try (  InputStreamReader reader = spl.getInput()){
        text = CharStreams.toString(reader);
    }
Husayt
la source
16

Presque. Vous pouvez utiliser quelque chose comme ceci:

InputSupplier<InputStreamReader> readerSupplier = CharStreams.newReaderSupplier
    (streamSupplier, Charsets.UTF_8);
String text = CharStreams.toString(readerSupplier);

Personnellement, je ne pense pas que ce IOUtils.toString(InputStream)soit "sympa" - car il utilise toujours l'encodage par défaut de la plate-forme, ce qui n'est presque jamais ce que vous voulez. Il y a une surcharge qui prend le nom de l'encodage, mais utiliser des noms n'est pas une bonne idée IMO. C'est pourquoi j'aime Charsets.*.

EDIT: Non pas que ce qui précède a besoin d'un InputSupplier<InputStream>comme le streamSupplier. Si vous avez déjà le flux, vous pouvez l'implémenter assez facilement:

InputSupplier<InputStream> supplier = new InputSupplier<InputStream>() {
    @Override public InputStream getInput() {
        return stream;
    }
};
Jon Skeet
la source
Jon, est-ce que le flux est via request.getInputStream? Aussi, le vôtre fermera-t-il le flux comme ColinD mentionné dans la réponse de @ Calum?
Blankman
Oh, et c'est un environnement doPost de servlet, devrais-je quand même fermer le flux?
Blankman
@Blankman: Ah, c'est donc votre contexte - ce n'était pas du tout clair d'après votre question. Peu importe que vous fermiez un flux de demande, mais je le ferais généralement. Je vais cependant modifier cette réponse - il n'y a pas de telle surcharge, il semble.
Jon Skeet
1
Je fais juste ceci maintenant: String payLoad = CharStreams.toString (new InputStreamReader (request.getInputStream (), "UTF-8"));
Blankman
1
@BeeOnRope: Je suppose qu'une approche intermédiaire est Charsets.UTF_8.name()- plus résistante aux fautes de frappe.
Jon Skeet
11

Une autre option consiste à lire les octets de Stream et à créer une chaîne à partir d'eux:

new String(ByteStreams.toByteArray(inputStream))
new String(ByteStreams.toByteArray(inputStream), Charsets.UTF_8)

Ce n'est pas de la goyave «pure», mais c'est un peu plus court.

ponomandre
la source
Malheureusement, ByteStreams.toByteArray()ne ferme pas le flux, selon le Javadoc.
The Alchemist
C'est vrai. Je n'ai vu aucune fonction de Guava qui ferme le flux. Eh bien, sauf closeQuietly.
ponomandr
1
En règle générale, le flux est ouvert dans l'instruction try-with-resources et fermé automatiquement, il ne devrait donc pas être de la responsabilité de toByteArray ()
ponomandr
4

Sur la base de la réponse acceptée, voici une méthode utilitaire qui se moque du comportement de IOUtils.toString()(et une version surchargée avec un jeu de caractères). Cette version devrait être sûre, non?

public static String toString(final InputStream is) throws IOException{
    return toString(is, Charsets.UTF_8);
}


public static String toString(final InputStream is, final Charset cs)
throws IOException{
    Closeable closeMe = is;
    try{
        final InputStreamReader isr = new InputStreamReader(is, cs);
        closeMe = isr;
        return CharStreams.toString(isr);
    } finally{
        Closeables.closeQuietly(closeMe);
    }
}
Sean Patrick Floyd
la source
Cela me semble assez bien. Le truc IO de Guava fonctionne mieux si vous apprenez à penser en termes de fournisseurs d'entrée réutilisables plutôt que de flux et de lecteurs 1-shot (si possible), mais je suppose que puisque vous convertissez le code IOUtils existant, ce serait un grand changement.
ColinD
2
Dans mon goyave 14, le closeQuietly est déjà obsolète. La suggestion est d'utiliser la fonctionnalité try-with-resources qui existe dans Java 7. Plus d'informations à ce sujet sur code.google.com/p/guava-libraries/wiki/…
bertie
2
@AlbertKam était d'accord. Mais rappelez-vous: cette réponse date de trois ans.
Sean Patrick Floyd
@SeanPatrickFloyd: Merci! En fait, je suis arrivé à la nouvelle solution à partir de votre réponse. Je pensais ajouter le commentaire pour les autres qui pourraient utiliser la nouvelle version. :)
bertie
4

Il existe une solution de fermeture automatique beaucoup plus courte dans le cas où le flux d'entrée provient de la ressource classpath:

URL resource = classLoader.getResource(path);
byte[] bytes = Resources.toByteArray(resource);
String text = Resources.toString(resource, StandardCharsets.UTF_8);

Utilise des ressources de goyave , inspirées par IOExplained .

Vadzim
la source
1
La classe Resources n'existait pas lorsque cette question a été posée, mais vous avez raison: aujourd'hui, ce serait probablement la voie à suivre. Merci
Sean Patrick Floyd
2

EDIT (2015): Okio est la meilleure abstraction et les meilleurs outils d'E / S en Java / Android que je connaisse. Je l'utilise tout le temps.

FWIW voici ce que j'utilise.

Si j'ai déjà un flux en main, alors:

final InputStream stream; // this is received from somewhere
String s = CharStreams.toString(CharStreams.newReaderSupplier(new InputSupplier<InputStream>() {
    public InputStream getInput() throws IOException {
        return stream;
    }
}, Charsets.UTF_8));

Si je crée un flux:

String s = CharStreams.toString(CharStreams.newReaderSupplier(new InputSupplier<InputStream>() {
    public InputStream getInput() throws IOException {
        return <expression creating the stream>;
    }
}, Charsets.UTF_8));

À titre d'exemple concret, je peux lire un fichier texte Android comme celui-ci:

final Context context = ...;
String s = CharStreams.toString(CharStreams.newReaderSupplier(new InputSupplier<InputStream>() {
    public InputStream getInput() throws IOException {
        return context.getAssets().open("my_asset.txt");
    }
}, Charsets.UTF_8));
orip
la source
Tous obsolètes maintenant. :(
user3562927
1
Essayez plutôt github.com/square/okio - je n'ai pas utilisé les E / S de Guava depuis un moment maintenant, Okio est tout simplement meilleur,
orip
0

Pour un exemple concret, voici comment lire un fichier texte Android:

public static String getAssetContent(Context context, String file) {
    InputStreamReader reader = null;
    InputStream stream = null;
    String output = "";

    try {
        stream = context.getAssets().open(file);
        reader = new InputStreamReader(stream, Charsets.UTF_8);
        output = CharStreams.toString(reader);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (stream != null) {
            try {
                stream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        if (reader != null) {
            try {
                reader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    return output;
}
TruMan1
la source