Equivalente Guava para IOUtils.toString (InputStream)

106

O Apache Commons IO tem um método de conveniência IOUtils.toString () para ler um InputStreampara uma String.

Já que estou tentando me afastar do Apache Commons e ir para o Guava : existe um equivalente no Guava? Eu olhei para todas as classes do com.google.common.iopacote e não consegui encontrar nada tão simples.

Edit: Eu entendo e aprecio os problemas com conjuntos de caracteres. Acontece que eu sei que todas as minhas fontes estão em ASCII (sim, ASCII, não ANSI etc.), então, neste caso, a codificação não é um problema para mim.

Sean Patrick Floyd
fonte
2
Sobre os conjuntos de caracteres: ainda é bom para uma biblioteca exigir que você especifique que sabe com qual conjunto de caracteres está lidando (por exemplo Charsets.US_ASCII), em vez de permitir que você diga "eh, qualquer conjunto de caracteres, eu acho?" o que para muitas pessoas parece feliz em fazer. Principalmente porque o Java não usa um padrão que faz sentido, como UTF-8.
ColinD de
Eu sei. É por isso que estou usando UTF-8 como versão padrão em minha resposta.
Sean Patrick Floyd
Veja também os documentos: code.google.com/p/guava-libraries/wiki/IOExplained
Vadzim
@Vadzim, aqueles documentos não existiam quando esta pergunta foi feita :-)
Sean Patrick Floyd

Respostas:

85

Você declarou em seu comentário sobre a resposta de Calum que iria usar

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

Este código é problemático porque os CharStreams.toString(Readable)estados de sobrecarga :

Não fecha o Readable.

Isso significa que o seu InputStreamReadere, por extensão, o InputStreamretornado por supplier.get(), não serão fechados após a conclusão desse código.

Se, por outro lado, você tirar vantagem do fato de que parece já ter um InputSupplier<InputStream>e usou a sobrecarga CharStreams.toString(InputSupplier<R extends Readable & Closeable>), o toStringmétodo tratará da criação e do fechamento do Readerpara você.

Isso é exatamente o que Jon Skeet sugeriu, exceto que não há realmente nenhuma sobrecarga de CharStreams.newReaderSupplierque leva um InputStreamcomo entrada ... você tem que fornecer um 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);

O objetivo InputSupplieré tornar sua vida mais fácil, permitindo que o Guava manuseie as peças que exigem um try-finallybloco feio para garantir que os recursos sejam fechados adequadamente.

Editar: Pessoalmente, acho o seguinte (que é como eu realmente escreveria, estava apenas decompondo as etapas no código acima)

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

ser muito menos prolixo do que isso:

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

O que é mais ou menos o que você teria que escrever para lidar com isso adequadamente.


Edição: fevereiro de 2014

InputSuppliere OutputSupplieros métodos que os usam foram descontinuados no Guava 16.0. Seus substitutos são ByteSource, CharSource, ByteSinke CharSink. Dado um ByteSource, agora você pode obter seu conteúdo Stringcomo este:

ByteSource source = ...
String text = source.asCharSource(Charsets.UTF_8).read();
ColinD
fonte
Obrigado pela ótima informação (+1). Mas isso é muito prolixo. Acho que combinar a resposta aceita com Closeables.closeQuietly () é mais fácil.
Sean Patrick Floyd
@CollinD: Usei seu método em uma de minhas respostas. Por favor, dê uma olhada no código e diga-me se esta é a maneira certa de usar InputSupplier.
Emil
1
@ColinD, se o inputStream está vindo de dentro de um servlet doPost, há algum ponto em fechá-lo? (ou se preocupando em fechá-lo)
Blankman
CharStreams.toString (InputSupplier) agora está obsoleto. Eu criei um CharSource (de um ByteSource usando asCharSource), em seguida, usei seu toString como a documentação sugere.
John Lehmann
4
@ TedM.Young: Se tudo o que você tem é um InputStreame deseja obtê-lo como um String, esse CharStreams.toString(new InputStreamReader(inputStream, charset))é o caminho a seguir. ByteSourcee CharSourcesão especificamente para casos em que você tem algo que pode atuar como uma fonte de InputStreams ou Readers.
ColinD
56

Se você tiver um, Readablepode usar CharStreams.toString(Readable). Portanto, você provavelmente pode fazer o seguinte:

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

Força você a especificar um conjunto de caracteres, o que eu acho que você deveria fazer de qualquer maneira.

Calum
fonte
4
Na verdade, usarei uma combinação das suas respostas e das de Jon Skeet: `CharStreams.toString (new InputStreamReader (supplier.get (), Charsets.UTF_8))`
Sean Patrick Floyd
Sim, muitas maneiras de combinar as opções!
Calum
10
@SPFloyd: Se você tiver um, InputSupplier<InputStream>recomendo fortemente o uso em CharStreams.newReaderSupplier(supplier, Charsets.UTF_8)vez de new InputStreamReader. A razão é que, quando dada a InputStreamReader, toStringvontade não perto que Reader(e, portanto, não o fluxo subjacente!). Ao usar um InputSupplierfor o Reader, o toStringmétodo tratará do fechamento do Readerpara você.
ColinD de
17

ATUALIZAÇÃO : Olhando para trás, não gosto da minha solução anterior. Além disso, é 2013 agora e existem melhores alternativas disponíveis agora para Java7. Então aqui está o que eu uso agora:

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

ou se com InputSupplier

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

Por pouco. Você poderia usar algo assim:

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

Pessoalmente , não acho isso IOUtils.toString(InputStream)"legal" - porque sempre usa a codificação padrão da plataforma, que quase nunca é o que você deseja. Há uma sobrecarga que leva o nome da codificação, mas usar nomes não é uma boa ideia IMO. É por isso que gosto Charsets.*.

EDIT: Não que o acima precise de um InputSupplier<InputStream>como o streamSupplier. Se você já tem o stream, pode implementá-lo facilmente:

InputSupplier<InputStream> supplier = new InputSupplier<InputStream>() {
    @Override public InputStream getInput() {
        return stream;
    }
};
Jon Skeet
fonte
Jon, o stream é via request.getInputStream? Além disso, o seu vai fechar o stream como ColinD mencionado na resposta de @Calum?
Blankman
Ah, e é um ambiente de servlet doPost, devo fechar o fluxo de qualquer maneira?
Blankman
@Blankman: Ah, então esse é o seu contexto - não ficou nada claro com a sua pergunta. Não importa muito se você fecha um fluxo de solicitação, mas geralmente eu faria isso. No entanto, vou editar esta resposta - não existe essa sobrecarga, ao que parece.
Jon Skeet
1
Só estou fazendo isso agora: String payLoad = CharStreams.toString (new InputStreamReader (request.getInputStream (), "UTF-8"));
Blankman,
1
@BeeOnRope: Acho que uma abordagem intermediária é Charsets.UTF_8.name()- mais resistente a erros de digitação.
Jon Skeet
11

Outra opção é ler bytes do Stream e criar uma String a partir deles:

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

Não é goiaba 'pura', mas é um pouco mais curto.

ponomandr
fonte
Infelizmente, ByteStreams.toByteArray()não fecha o fluxo, de acordo com o Javadoc.
The Alchemist
Isso é verdade. Não vi nenhuma função do Guava que feche o fluxo. Bem, exceto closeQuietly.
ponomandr
1
Normalmente, o fluxo é aberto na instrução try-with-resources e fechado automaticamente, portanto, não deve ser responsabilidade de toByteArray ()
ponomandr
4

Com base na resposta aceita, aqui está um método utilitário que zomba do comportamento de IOUtils.toString()(e uma versão sobrecarregada com um conjunto de caracteres, também). Esta versão deve ser segura, certo?

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
fonte
Parece muito bom para mim. O material de IO do Guava funciona melhor se você aprender a pensar em termos de fornecedores de entrada reutilizáveis ​​em vez de fluxos e leitores únicos (quando possível), mas acho que, como você está convertendo o código IOUtils existente, isso seria uma grande mudança.
ColinD de
2
No meu goiaba 14, o closeQuietly já está obsoleto. A sugestão é usar o recurso try-with-resources existente no Java 7. Mais sobre isso em code.google.com/p/guava-libraries/wiki/…
bertie
2
@AlbertKam concordou. Mas lembre-se: esta resposta tem três anos.
Sean Patrick Floyd
@SeanPatrickFloyd: Obrigado! Na verdade, cheguei à solução mais recente começando com sua resposta. Eu estava pensando em adicionar o comentário para os outros que podem estar usando a versão mais recente. :)
bertie
4

Há uma solução de fechamento automático muito mais curta no caso de o fluxo de entrada vir do recurso de caminho de classe:

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

Usa Guava Resources , inspirado em IOExplained .

Vadzim
fonte
1
A classe Recursos não existia quando esta pergunta foi feita, mas você está certo: hoje, provavelmente seria o caminho a percorrer. Obrigado
Sean Patrick Floyd
2

EDIT (2015): Okio é a melhor abstração e ferramentas para I / O em Java / Android que eu conheço. Eu uso isso o tempo todo.

FWIW aqui está o que eu uso.

Se eu já tiver um fluxo em mãos, então:

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

Se estou criando um fluxo:

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

Como um exemplo concreto, posso ler um ativo de arquivo de texto Android como este:

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
fonte
Todos descontinuados agora. :(
user3562927
1
Em vez disso, tente github.com/square/okio - eu não uso I / O do Guava há um tempo, Okio é simplesmente melhor,
orip
0

Para um exemplo concreto, veja como posso ler um ativo de arquivo de texto do 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
fonte