Cách hiệu quả nhất để tạo InputStream từ OutputStream


84

Trang này: http://blog.ostermiller.org/convert-java-outputstream-inputstream mô tả cách tạo InputStream từ OutputStream:

new ByteArrayInputStream(out.toByteArray())

Các lựa chọn thay thế khác là sử dụng PipedStreams và các luồng mới cồng kềnh.

Tôi không thích ý tưởng sao chép nhiều megabyte thành mới trong mảng byte bộ nhớ. Có thư viện nào làm điều này hiệu quả hơn không?

BIÊN TẬP:

Theo lời khuyên từ Laurence Gonsalves, tôi đã thử PipedStreams và hóa ra chúng không khó đối phó như vậy. Đây là mã mẫu trong clojure:

(defn #^PipedInputStream create-pdf-stream [pdf-info]
  (let [in-stream (new PipedInputStream)
        out-stream (PipedOutputStream. in-stream)]
    (.start (Thread. #(;Here you write into out-stream)))
    in-stream))

Câu trả lời:


72

Nếu bạn không muốn sao chép tất cả dữ liệu vào một bộ đệm trong bộ nhớ cùng một lúc thì bạn sẽ phải có mã sử dụng OutputStream (nhà sản xuất) và mã sử dụng InputStream (dòng tiêu dùng ) hoặc luân phiên trong cùng một luồng hoặc hoạt động đồng thời trong hai luồng riêng biệt. Để chúng hoạt động trong cùng một luồng có lẽ phức tạp hơn nhiều khi sử dụng hai luồng riêng biệt, dễ bị lỗi hơn nhiều (bạn sẽ cần đảm bảo rằng người tiêu dùng không bao giờ chặn việc chờ đợi đầu vào, nếu không bạn sẽ thực sự bế tắc) và sẽ cần để nhà sản xuất và người tiêu dùng chạy trong cùng một vòng lặp có vẻ được kết hợp quá chặt chẽ.

Vì vậy, sử dụng một chủ đề thứ hai. Nó thực sự không phức tạp. Trang bạn liên kết đến có một ví dụ hoàn hảo:

  PipedInputStream in = new PipedInputStream();
  PipedOutputStream out = new PipedOutputStream(in);
  new Thread(
    new Runnable(){
      public void run(){
        class1.putDataOnOutputStream(out);
      }
    }
  ).start();
  class2.processDataFromInputStream(in);

Tôi nghĩ rằng bạn cũng cần tạo PipedInputStream mới cho mỗi luồng người tiêu dùng. Nếu bạn đọc từ Pipe từ một luồng khác, nó sẽ báo lỗi cho bạn.
Denis Tulskiy

@Lawrence: Tôi không hiểu cơ sở lý luận của bạn khi sử dụng 2 luồng ... BẤT NGỜ, đó là yêu cầu rằng tất cả các ký tự đọc từ InputStream phải được ghi vào OutputStream kịp thời.
Stephen C

8
Stephen: bạn không thể đọc một cái gì đó cho đến khi nó được viết ra. Vì vậy, chỉ với một luồng, bạn cần phải viết mọi thứ trước tiên (tạo một mảng lớn trong bộ nhớ mà Vagif muốn tránh) hoặc bạn cần phải có chúng xen kẽ rất cẩn thận để trình đọc không bao giờ chặn chờ đầu vào (vì nếu anh ta làm vậy , người viết cũng sẽ không bao giờ thực hiện được).
Laurence Gonsalves

1
đề xuất này có an toàn để sử dụng trong môi trường JEE nơi vùng chứa có thể đang chạy rất nhiều luồng của riêng mình không?
Toskan

2
@Toskan nếu new Threadkhông thích hợp trong vùng chứa của bạn vì bất kỳ lý do gì, thì hãy xem liệu bạn có thể sử dụng nhóm luồng không.
Laurence Gonsalves

14

Có một thư viện Mã nguồn mở khác được gọi là EasyStream xử lý các đường ống và luồng một cách minh bạch. Điều đó không thực sự phức tạp nếu mọi thứ diễn ra tốt đẹp. Các vấn đề nảy sinh khi (xem ví dụ của Laurence Gonsalves)

class1.putDataOnOutputStream (ra);

Ném một ngoại lệ. Trong ví dụ đó, luồng chỉ hoàn thành và ngoại lệ bị mất, trong khi phần ngoài InputStreamcó thể bị cắt bớt.

Easystream giải quyết vấn đề lan truyền ngoại lệ và các vấn đề khó chịu khác mà tôi đã gỡ lỗi trong khoảng một năm. (Tôi là người bảo vệ thư viện: rõ ràng giải pháp của tôi là giải pháp tốt nhất;)) Đây là một ví dụ về cách sử dụng nó:

final InputStreamFromOutputStream<String> isos = new InputStreamFromOutputStream<String>(){
 @Override
 public String produce(final OutputStream dataSink) throws Exception {
   /*
    * call your application function who produces the data here
    * WARNING: we're in another thread here, so this method shouldn't 
    * write any class field or make assumptions on the state of the outer class. 
    */
   return produceMydata(dataSink)
 }
};

Ngoài ra còn có một phần giới thiệu hay , trong đó giải thích tất cả các cách khác để chuyển đổi OutputStream thành InputStream. Giá trị để có một cái nhìn.


1
Hướng dẫn sử dụng lớp học của họ có sẵn tại code.google.com/p/io-tools/wiki/Tutorial_EasyStream
koppor

9

Một giải pháp đơn giản để tránh sao chép bộ đệm là tạo một mục đích đặc biệt ByteArrayOutputStream:

public class CopyStream extends ByteArrayOutputStream {
    public CopyStream(int size) { super(size); }

    /**
     * Get an input stream based on the contents of this output stream.
     * Do not use the output stream after calling this method.
     * @return an {@link InputStream}
     */
    public InputStream toInputStream() {
        return new ByteArrayInputStream(this.buf, 0, this.count);
    }
}

Ghi vào luồng đầu ra ở trên nếu cần, sau đó gọi toInputStreamđể nhận luồng đầu vào qua bộ đệm bên dưới. Coi luồng đầu ra là đã đóng sau thời điểm đó.


7

Tôi nghĩ rằng cách tốt nhất để kết nối InputStream với OutputStream là thông qua các luồng đường ống - có sẵn trong gói java.io, như sau:

// 1- Define stream buffer
private static final int PIPE_BUFFER = 2048;

// 2 -Create PipedInputStream with the buffer
public PipedInputStream inPipe = new PipedInputStream(PIPE_BUFFER);

// 3 -Create PipedOutputStream and bound it to the PipedInputStream object
public PipedOutputStream outPipe = new PipedOutputStream(inPipe);

// 4- PipedOutputStream is an OutputStream, So you can write data to it
// in any way suitable to your data. for example:
while (Condition) {
     outPipe.write(mByte);
}

/*Congratulations:D. Step 4 will write data to the PipedOutputStream
which is bound to the PipedInputStream so after filling the buffer
this data is available in the inPipe Object. Start reading it to
clear the buffer to be filled again by the PipedInputStream object.*/

Theo tôi, có hai ưu điểm chính cho mã này:

1 - Không có tiêu thụ thêm bộ nhớ ngoại trừ bộ đệm.

2 - Bạn không cần phải xử lý hàng đợi dữ liệu theo cách thủ công


1
Điều này thật tuyệt vời, nhưng javadocs nói rằng nếu bạn đọc và ghi những thứ này trong cùng một chuỗi, bạn có thể gặp bế tắc. Tôi ước họ đã cập nhật điều này với NIO!
Nate Glenn

1

Tôi thường cố gắng tránh tạo một chuỗi riêng biệt vì khả năng bị bế tắc tăng lên, khó hiểu mã hơn và các vấn đề về xử lý ngoại lệ.

Đây là giải pháp được đề xuất của tôi: ProducerInputStream tạo nội dung theo từng phần bằng cách gọi lặp lại để productionChunk ():

public abstract class ProducerInputStream extends InputStream {

    private ByteArrayInputStream bin = new ByteArrayInputStream(new byte[0]);
    private ByteArrayOutputStream bout = new ByteArrayOutputStream();

    @Override
    public int read() throws IOException {
        int result = bin.read();
        while ((result == -1) && newChunk()) {
            result = bin.read();
        }
        return result;
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        int result = bin.read(b, off, len);
        while ((result == -1) && newChunk()) {
            result = bin.read(b, off, len);
        }
        return result;
    }

    private boolean newChunk() {
        bout.reset();
        produceChunk(bout);
        bin = new ByteArrayInputStream(bout.toByteArray());
        return (bout.size() > 0);
    }

    public abstract void produceChunk(OutputStream out);

}
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.