Ổi tương đương cho IOUtils.toString (InputStream)


106

Apache Commons IO có một phương thức tiện lợi IOUtils.toString () để đọc InputStreammột chuỗi.

Vì tôi đang cố gắng di chuyển khỏi Apache Commons và đến Guava : có loại tương đương trong Guava không? Tôi đã xem xét tất cả các lớp trong com.google.common.iogói và tôi không thể tìm thấy thứ gì gần như đơn giản.

Chỉnh sửa: Tôi hiểu và đánh giá cao các vấn đề với bảng mã. Điều xảy ra là tôi biết rằng tất cả các nguồn của tôi đều ở dạng ASCII (vâng, ASCII, không phải ANSI, v.v.), vì vậy trong trường hợp này, mã hóa không phải là vấn đề đối với tôi.


2
Về bộ mã: Vẫn tốt cho một thư viện yêu cầu bạn chỉ định rằng bạn biết bộ mã mà bạn đang xử lý (ví dụ Charsets.US_ASCII) thay vì cho phép bạn nói "ơ, tôi đoán là bộ ký tự nào?" mà đối với nhiều người dường như hạnh phúc khi làm. Đặc biệt là vì Java không sử dụng một mặc định có ý nghĩa, như UTF-8.
ColinD

Tôi biết. Đó là lý do tại sao tôi đang sử dụng UTF-8 làm phiên bản mặc định trong câu trả lời của riêng tôi.
Sean Patrick Floyd


@Vadzim những tài liệu không tồn tại khi câu hỏi này được hỏi :-)
Sean Patrick Floyd

Câu trả lời:


85

Bạn đã nêu trong bình luận của mình về câu trả lời của Calum rằng bạn sẽ sử dụng

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

Mã này có vấn đề vì trạng CharStreams.toString(Readable)thái quá tải :

Không đóng Readable.

Điều này có nghĩa là của bạn InputStreamReadervà theo phần mở rộng được InputStreamtrả về supplier.get(), sẽ không bị đóng sau khi mã này hoàn thành.

Mặt khác, nếu bạn lợi dụng thực tế là bạn đã có InputSupplier<InputStream>và sử dụng quá tải CharStreams.toString(InputSupplier<R extends Readable & Closeable>), toStringphương thức sẽ xử lý cả việc tạo và đóng Readercho bạn.

Đây chính xác là những gì Jon Skeet đề xuất, ngoại trừ việc thực sự không có bất kỳ quá tải CharStreams.newReaderSuppliernào lấy một InputStreamđầu vào làm đầu vào ... bạn phải cung cấp cho nó một 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);

Mục đích InputSupplierlà làm cho cuộc sống của bạn dễ dàng hơn bằng cách cho phép Guava xử lý các phần yêu cầu một try-finallykhối xấu xí để đảm bảo rằng các tài nguyên được đóng đúng cách.

Chỉnh sửa: Cá nhân tôi thấy điều sau (đó là cách tôi thực sự viết nó, chỉ là chia nhỏ các bước trong đoạn mã ở trên)

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

xa ít tiết hơn này:

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

Đó là ít nhiều những gì bạn phải viết để tự xử lý việc này đúng cách.


Chỉnh sửa: Tháng 2 năm 2014

InputSupplierOutputSuppliercác phương pháp sử dụng chúng đã không được chấp nhận trong Guava 16.0. Thay thế của họ là ByteSource, CharSource, ByteSinkCharSink. Với một ByteSource, bây giờ bạn có thể nhận được nội dung của nó Stringnhư sau:

ByteSource source = ...
String text = source.asCharSource(Charsets.UTF_8).read();

Cảm ơn vì thông tin tuyệt vời (+1). Nhưng điều này rất dài dòng. Tôi nghĩ rằng việc kết hợp câu trả lời được chấp nhận với Closeables.closeQuietly () sẽ dễ dàng hơn.
Sean Patrick Floyd

@CollinD: Tôi đã sử dụng phương pháp của bạn trong một trong những câu trả lời của tôi. Vui lòng xem và cho tôi biết liệu đây có phải là cách phù hợp để sử dụng InputSupplier hay không.
Emil

1
@ColinD, nếu inputStream đến từ bên trong một servlet doPost, thì có cách nào để đóng nó không? (hoặc lo lắng về đóng nó)
Blankman

CharStreams.toString (InputSupplier) hiện không được dùng nữa. Tôi đã tạo CharSource (từ ByteSource bằng asCharSource) sau đó sử dụng toString của nó như tài liệu đề xuất.
John Lehmann

4
@ TedM.Young: Nếu tất cả những gì bạn có là một InputStream, và bạn muốn có được nó String, thì đó CharStreams.toString(new InputStreamReader(inputStream, charset))là con đường để đi. ByteSourceCharSourceđặc biệt dành cho những trường hợp bạn có thứ gì đó có thể hoạt động như một nguồn của InputStreams hoặc Readers.
ColinD

56

Nếu bạn có một, Readablebạn có thể sử dụng CharStreams.toString(Readable). Vì vậy, bạn có thể làm như sau:

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

Buộc bạn chỉ định một bộ ký tự, mà tôi đoán bạn nên làm theo cách nào đó.


4
Thực ra, tôi sẽ sử dụng một sự kết hợp các câu trả lời của bạn và Jon Skeet: `CharStreams.toString (InputStreamReader mới (supplier.get (), Charsets.UTF_8))`
Sean Patrick Floyd

Đúng, rất nhiều cách để kết hợp các tùy chọn!
Calum

10
@SPFloyd: Nếu bạn có, InputSupplier<InputStream>tôi thực sự khuyên bạn nên sử dụng CharStreams.newReaderSupplier(supplier, Charsets.UTF_8)thay vì sử dụng new InputStreamReader. Lý do là khi được đưa ra InputStreamReader, toStringsẽ không đóng nó Reader(và do đó không phải là luồng cơ bản!). Bằng cách sử dụng một InputSuppliercho Reader, toStringphương thức sẽ xử lý việc đóng Readercho bạn.
ColinD

17

CẬP NHẬT : Nhìn lại, tôi không thích giải pháp cũ của mình. Bên cạnh đó, bây giờ là năm 2013 và hiện có nhiều lựa chọn thay thế tốt hơn cho Java7. Vì vậy, đây là những gì tôi sử dụng bây giờ:

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

hoặc nếu với InputSupplier

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

16

Gần. Bạn có thể sử dụng một cái gì đó như thế này:

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

Cá nhân tôi không nghĩ đó IOUtils.toString(InputStream)là "tốt đẹp" - bởi vì nó luôn sử dụng mã hóa mặc định của nền tảng, hầu như không bao giờ là những gì bạn muốn. Có một sự quá tải lấy tên của bảng mã, nhưng sử dụng tên không phải là một ý tưởng tuyệt vời IMO. Đó là lý do tại sao tôi thích Charsets.*.

CHỈNH SỬA: Không phải là ở trên cần một InputSupplier<InputStream>như streamSupplier. Tuy nhiên, nếu bạn đã có luồng, bạn có thể triển khai dễ dàng:

InputSupplier<InputStream> supplier = new InputSupplier<InputStream>() {
    @Override public InputStream getInput() {
        return stream;
    }
};

Jon, có phát trực tiếp qua request.getInputStream không? Ngoài ra, bạn sẽ đóng luồng như ColinD đã đề cập trong câu trả lời của @ Calum chứ?
Blankman

Ồ, và đó là một môi trường doPost của servlet, dù sao thì tôi có nên đóng luồng không?
Blankman

@Blankman: À, đó là bối cảnh của bạn - hoàn toàn không rõ ràng với câu hỏi của bạn. Việc bạn đóng luồng yêu cầu cũng không quan trọng lắm, nhưng tôi thường làm như vậy. Tuy nhiên, tôi sẽ chỉnh sửa câu trả lời này - có vẻ như không có quá tải như vậy.
Jon Skeet

1
Tôi chỉ đang làm việc này ngay bây giờ: String payLoad = CharStreams.toString (new InputStreamReader (request.getInputStream (), "UTF-8"));
Blankman

1
@BeeOnRope: Tôi đoán một cách tiếp cận trung gian là Charsets.UTF_8.name()- chống lỗi đánh máy nhiều hơn.
Jon Skeet

11

Một tùy chọn khác là đọc các byte từ Luồng và tạo Chuỗi từ chúng:

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

Nó không phải là Ổi 'nguyên chất', nhưng nó ngắn hơn một chút.


Thật không may, ByteStreams.toByteArray()không đóng luồng, theo Javadoc.
The Alchemist vào

Đúng. Tôi chưa thấy bất kỳ chức năng nào của Guava đóng luồng. Chà, ngoại trừ closeQuietly.
ponomandr

1
Thông thường, các dòng được mở ra trong câu lệnh try-với-nguồn lực và đóng cửa tự động, vì vậy nó should't được trách nhiệm của toByteArray ()
ponomandr

4

Dựa trên câu trả lời được chấp nhận, đây là một phương thức tiện ích chế nhạo hành vi của IOUtils.toString()(và cả một phiên bản quá tải với một bộ ký tự). Phiên bản này nên an toàn, phải không?

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

Trông tôi khá ổn. Công cụ IO của ổi hoạt động tốt nhất nếu bạn học cách suy nghĩ về các nhà cung cấp đầu vào có thể tái sử dụng thay vì các luồng và trình đọc 1 lần (khi có thể), nhưng tôi đoán vì bạn đang chuyển đổi mã IOUtils hiện có nên đó sẽ là một thay đổi lớn.
ColinD

2
Trong ổi 14 của tôi, closeQuietly đã không còn được dùng nữa. Đề xuất là sử dụng tính năng thử với tài nguyên tồn tại trong Java 7. Tìm hiểu thêm về tính năng này tại code.google.com/p/guava-libraries/wiki/…
bertie

2
@AlbertKam đồng ý. Nhưng hãy nhớ: câu trả lời này là ba năm.
Sean Patrick Floyd

@SeanPatrickFloyd: Cảm ơn! Trên thực tế, tôi có giải pháp mới hơn bắt đầu từ câu trả lời của bạn. Tôi đã nghĩ đến việc thêm nhận xét cho những người khác có thể đang sử dụng phiên bản mới hơn. :)
bertie

4

Có giải pháp tự động đóng gói ngắn hơn nhiều trong trường hợp luồng đầu vào đến từ tài nguyên classpath:

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

Sử dụng Tài nguyên Ổi , lấy cảm hứng từ IOExplained .


1
Lớp Tài nguyên không tồn tại khi câu hỏi này được đặt ra, nhưng bạn nói đúng: hôm nay có lẽ sẽ là cách để đi. Cảm ơn
Sean Patrick Floyd

2

CHỈNH SỬA (2015): Okio là công cụ và công cụ trừu tượng tốt nhất cho I / O trong Java / Android mà tôi biết. Tôi sử dụng nó mọi lúc.

FWIW đây là những gì tôi sử dụng.

Nếu tôi đã có một luồng trong tay, thì:

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

Nếu tôi đang tạo luồng:

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

Như một ví dụ cụ thể, tôi có thể đọc nội dung tệp văn bản Android như sau:

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

Tất cả hiện không được dùng nữa. :(
user3562927

1
Hãy thử github.com/square/okio thay vì - tôi đã không sử dụng ổi của I / O trong một thời gian bây giờ, Okio chỉ đơn giản là tốt hơn,
orip

0

Đối với một ví dụ cụ thể, đây là cách tôi có thể đọc nội dung tệp văn bản 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;
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.