从具有大量数据的Java InputStream中读取多次

时间:2016-03-18 18:41:36

标签: java inputstream bigdata

我想知道从Java InputStream多次读取字节的最佳方法是什么,并且当流非常大时仍然有效。 我们说我有以下代码:

public void handleBytes(InputStream in) {
    doStuff1(in);
    doStuff2(in);
    doStuff3(in);
}

其中doStuff1doStuff2doStuff3都需要处理相同的字节但执行不同的操作。我也假设这些函数可以是异步的。

我知道有可能mark然后reset,但我想知道当in有大量数据时这是否可行。另外,如果我想要每doStuff-X有一个线程工作者,我就不能真正使用reset

我应该为每个doStuff-X方法获得流的副本吗?但话又说回来,我不确定它对大量数据是否有效。

4 个答案:

答案 0 :(得分:1)

您只能在不缓冲整个输入的情况下读取一次InputStream。

如果它是GB左右,您可以将其加载到内存中,或者将其复制到文件中并在有许多GB时重播它。如果您可以在一个线程中解析数据,则可以将其传递给其他线程。

答案 1 :(得分:1)

一般来说,这似乎是一个坏主意。流不支持mark,即使支持它,也必须指定一个限制,在reset被调用之前可以读取多少字节。

既然你提到那些dostuff可以异步运行,那么为什么不为它们中的每一个启动一个线程并使用队列同时将主线程的输入提供给这三个队列呢?它需要一些同步,但这样你对输入音量没有限制,仍然可以限制内存使用。

答案 2 :(得分:1)

如果您知道三个doStuff()函数异步运行,那么您可以尝试使用Apache Commons IO TeeInputStream将初始InputStream的内容复制到PipedOutputStream连接到doStuff2()正在读取的PipedInputStream。同样,您可以设置第二个TeeInputStream,使用连接到doStuff3()的第二个PipedInputStream的第二个PipedOutputStream构建。

这种方法有一些限制:

1)doStuff1(),doStuff2()和doStuff3()必须在不同的线程上运行,否则你将在doStuff1()运行时和doStuff2()和doStuff3()运行之前缓冲整个文件两次。这种方法假设doStuff2()和doStuff3()正在读取和处理数据,而doStuff1()最初正在读取数据。

2)doStuff1()不能使用skip(),mark()或reset(),因为这会弄乱下游函数(如TeeInputStream javadoc中所述。

只要所有三个doStuff()函数都能以大致相同的速率处理数据,这种方法应该具有合理的内存效率。

答案 3 :(得分:1)

您可以采用PipedOutputStream和PipedInputStream。

static class Task extends Thread{
    private final String taskName;
    private final BufferedInputStream input;
    public Task(String taskName, PipedInputStream input){
        this.taskName = taskName;
        this.input = new BufferedInputStream( input);
    }

    public void run(){
        try {
            System.out.println("Thread "+this.taskName+" Start");

            final byte buf[] = new byte[8]; // 8 bytes for demo
            while(true){
                if( input.available() > 0){
                    input.read(buf);
                    System.out.println(String.format("Task Name %s, read:%s", this.taskName, new String(buf)));
                }
                else{
                    // TODO: Set break Condition:Ex: Check the expected read size
                    Thread.sleep(1000);
                }
            }
        } catch (IOException | InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}
public static void main(String args[]) {
    try{
        final PipedInputStream input1 = new PipedInputStream();
        final PipedInputStream input2 = new PipedInputStream();
        final PipedInputStream input3 = new PipedInputStream();

        final Task t1 = new Task("Task1", input1);
        final Task t2 = new Task("Task2", input2);
        final Task t3 = new Task("Task3", input3);
        t1.start();
        t2.start();
        t3.start();

        Thread.sleep(300);

        InputStream input = null;
        try{
            input = new FileInputStream("LargeInputFile.txt");

            final PipedOutputStream out1 = new PipedOutputStream(input1);
            final PipedOutputStream out2 = new PipedOutputStream(input2);
            final PipedOutputStream out3 = new PipedOutputStream(input3);

            byte buf[] = new byte[8]; // 8 bytes for demo
            while(true){

                if(input.available()>0){
                    int size = input.read(buf);

                    if(size > 0){
                        out1.write(buf);
                        out2.write(buf);
                        out3.write(buf);
                        out1.flush();
                        out2.flush();
                        out3.flush();
                    }                       
                }
                else{
                    System.out.println("Rread is finished!");
                    break;
                }
            }
        }
        finally{
            if(input!=null){
                input.close();
            }
        }   
        t1.join();
        t2.join();
        t3.join();
    }
    catch(Exception e){
        e.printStackTrace(System.err);
    }
}