วิธีที่มีประสิทธิภาพที่สุดในการสร้าง InputStream จาก OutputStream


86

หน้านี้: http://blog.ostermiller.org/convert-java-outputstream-inputstream อธิบายวิธีการสร้าง InputStream จาก OutputStream:

new ByteArrayInputStream(out.toByteArray())

ทางเลือกอื่นคือการใช้ PipedStreams และเธรดใหม่ซึ่งยุ่งยาก

ฉันไม่ชอบแนวคิดในการคัดลอกเมกะไบต์จำนวนมากไปยังหน่วยความจำใหม่ในอาร์เรย์ไบต์หน่วยความจำ มีห้องสมุดที่มีประสิทธิภาพมากกว่านี้หรือไม่?

แก้ไข:

ตามคำแนะนำจาก Laurence Gonsalves ฉันลองใช้ PipedStreams และปรากฎว่าพวกเขาไม่ยากที่จะจัดการ นี่คือโค้ดตัวอย่างใน 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))

คำตอบ:


73

หากคุณไม่ต้องการคัดลอกข้อมูลทั้งหมดลงในบัฟเฟอร์ในหน่วยความจำทั้งหมดพร้อมกันคุณจะต้องมีรหัสของคุณที่ใช้ OutputStream (ผู้ผลิต) และรหัสที่ใช้ InputStream (ผู้บริโภค ) สลับกันในเธรดเดียวกันหรือทำงานพร้อมกันในเธรดที่แยกจากกันสองเธรด การให้พวกเขาทำงานในเธรดเดียวกันนั้นอาจซับซ้อนกว่าการใช้เธรดสองเธรดที่แยกจากกันมีโอกาสเกิดข้อผิดพลาดได้ง่ายกว่ามาก (คุณจะต้องตรวจสอบให้แน่ใจว่าผู้บริโภคไม่ปิดกั้นการรออินพุตมิฉะนั้นคุณจะหยุดชะงักอย่างมีประสิทธิภาพ) การที่ผู้ผลิตและผู้บริโภคทำงานในวงเดียวกันซึ่งดูเหมือนว่าจะเชื่อมโยงกันอย่างแน่นหนาเกินไป

ดังนั้นใช้ด้ายที่สอง จริงๆมันไม่ได้ซับซ้อนขนาดนั้น หน้าที่คุณเชื่อมโยงมีตัวอย่างที่สมบูรณ์แบบ:

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

ฉันคิดว่าคุณต้องสร้าง PipedInputStream ใหม่สำหรับผู้บริโภคแต่ละกลุ่ม หากคุณอ่านจาก Pipe จากเธรดอื่นจะทำให้คุณมีข้อผิดพลาด
Denis Tulskiy

@Lawrence: ฉันไม่เข้าใจเหตุผลของคุณสำหรับการใช้ 2 เธรด ... เว้นแต่ว่ามันเป็นข้อกำหนดที่ว่าอักขระทั้งหมดที่อ่านจาก InputStream จะถูกเขียนไปยัง OutputStream ในเวลาที่เหมาะสม
Stephen C

8
Stephen: คุณไม่สามารถอ่านอะไรบางอย่างได้จนกว่าจะมีการเขียน ดังนั้นด้วยเธรดเดียวคุณจะต้องเขียนทุกอย่างก่อน (สร้างอาร์เรย์ในหน่วยความจำขนาดใหญ่ที่ Vagif ต้องการหลีกเลี่ยง) หรือคุณต้องให้พวกเขาสำรองโดยระมัดระวังเพื่อให้ผู้อ่านไม่ปิดกั้นการรออินพุต ผู้เขียนจะไม่ได้รับการดำเนินการอย่างใดอย่างหนึ่ง)
Laurence Gonsalves

1
คำแนะนำนี้ปลอดภัยที่จะใช้ในสภาพแวดล้อม JEE ที่คอนเทนเนอร์อาจรันเธรดของตัวเองเป็นจำนวนมากหรือไม่?
ทศกัณฐ์

2
@Toskan หากnew Threadไม่เหมาะสมในคอนเทนเนอร์ของคุณไม่ว่าด้วยเหตุผลใดให้ดูว่ามีเธรดพูลที่คุณสามารถใช้ได้หรือไม่
Laurence Gonsalves

14

มีไลบรารีโอเพ่นซอร์สอื่นที่เรียกว่าEasyStreamที่เกี่ยวข้องกับไพพ์และเธรดในลักษณะโปร่งใส มันไม่ซับซ้อนจริงๆถ้าทุกอย่างเป็นไปด้วยดี ปัญหาเกิดขึ้นเมื่อ (ดูตัวอย่าง Laurence Gonsalves)

class1.putDataOnOutputStream (ออก);

โยนข้อยกเว้น ในตัวอย่างนั้นเธรดจะเสร็จสมบูรณ์และข้อยกเว้นจะหายไปในขณะที่ด้านนอกInputStreamอาจถูกตัดทอน

ข้อตกลงของ Easystream กับการเผยแพร่ข้อยกเว้นและปัญหาที่น่ารังเกียจอื่น ๆ ที่ฉันได้ทำการดีบักมาประมาณหนึ่งปี (ฉันเป็นผู้ดูแลห้องสมุด: เห็นได้ชัดว่าวิธีแก้ปัญหาของฉันดีที่สุด;)) นี่คือตัวอย่างวิธีใช้:

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

นอกจากนี้ยังมีคำแนะนำที่ดีที่อธิบายวิธีอื่น ๆ ทั้งหมดในการแปลง OutputStream เป็น InputStream คุ้มค่าที่จะได้ดู


1
ดูบทแนะนำการใช้คลาสได้ที่code.google.com/p/io-tools/wiki/Tutorial_EasyStream
koppor

9

วิธีง่ายๆที่หลีกเลี่ยงการคัดลอกบัฟเฟอร์คือการสร้างจุดประสงค์พิเศษ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);
    }
}

เขียนไปยังสตรีมเอาท์พุตด้านบนตามต้องการจากนั้นเรียกtoInputStreamรับอินพุตสตรีมผ่านบัฟเฟอร์ที่อยู่เบื้องหลัง พิจารณาว่าสตรีมเอาต์พุตปิดหลังจากจุดนั้น


7

ฉันคิดว่าวิธีที่ดีที่สุดในการเชื่อมต่อ InputStream กับ OutputStream คือผ่านpiped streamซึ่งมีอยู่ในแพ็คเกจ java.io ดังต่อไปนี้:

// 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.*/

ในความคิดของฉันมีข้อดีหลักสองประการสำหรับรหัสนี้:

1 - ไม่มีการใช้หน่วยความจำเพิ่มเติมยกเว้นบัฟเฟอร์

2 - คุณไม่จำเป็นต้องจัดการการจัดคิวข้อมูลด้วยตนเอง


1
สิ่งนี้จะยอดเยี่ยม แต่javadocsบอกว่าถ้าคุณอ่านและเขียนถึงสิ่งเหล่านี้ในเธรดเดียวกันคุณอาจหยุดชะงักได้ ฉันหวังว่าพวกเขาจะอัปเดตสิ่งนี้ด้วย NIO!
Nate Glenn

1

ฉันมักจะพยายามหลีกเลี่ยงการสร้างเธรดแยกต่างหากเนื่องจากมีโอกาสหยุดชะงักเพิ่มขึ้นความยากในการทำความเข้าใจโค้ดที่เพิ่มขึ้นและปัญหาในการจัดการกับข้อยกเว้น

นี่คือวิธีแก้ปัญหาที่ฉันเสนอ: ProducerInputStream ที่สร้างเนื้อหาเป็นกลุ่มโดยการเรียกซ้ำ ๆ เพื่อ produceChunk ():

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

}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.