Java:合并输入流

Java:合并输入流,java,multithreading,merge,inputstream,Java,Multithreading,Merge,Inputstream,我的目标是创建(或使用现有的)InputStream实现(例如,MergeInputStream),该实现将尝试从多个InputStreams中读取并返回第一个结果。之后,它将释放锁并停止读取所有InputStreams,直到下一个mergeInputStream.read()调用。我很惊讶没有找到这样的工具。问题是:所有的源输入流都不是非常有限的(例如,不是一个文件,而是一个System.in、socket等),所以我不能使用SequenceInputReader。我知道这可能需要一些多线程机

我的目标是创建(或使用现有的)InputStream实现(例如,MergeInputStream),该实现将尝试从多个InputStreams中读取并返回第一个结果。之后,它将释放锁并停止读取所有InputStreams,直到下一个mergeInputStream.read()调用。我很惊讶没有找到这样的工具。问题是:所有的源输入流都不是非常有限的(例如,不是一个文件,而是一个System.in、socket等),所以我不能使用SequenceInputReader。我知道这可能需要一些多线程机制,但我完全不知道如何做到这一点。我尝试用谷歌搜索,但没有结果。

从多个来源读取输入并将其序列化为一个流的问题最好通过使用和解决。然而,这要求所有源都能够提供一个可选通道。情况可能是这样,也可能不是这样

如果可选通道不可用,您可以通过让读取实现执行以下操作来选择用单个线程解决它:对于每个输入流
is
,检查
is.available()>0
,如果是,则返回
is.read()
。重复此过程,直到某些输入流具有可用数据

然而,这种方法有两个主要缺点:

  • InputStream
    实现
    available()
    ,当且仅当
    read()
    将阻塞时,它才会返回0。结果自然是,数据可能不会从这个流中读取,即使
    is.read()
    将返回一个值。这是否会被视为一个bug是值得怀疑的,因为文档仅仅说明它应该返回可用字节数的“估计值”

  • 它使用了一个所谓的“忙循环”,这基本上意味着您要么需要在循环中设置睡眠(这会导致读取延迟),要么不必要地占用CPU

  • 第三个选项是通过为每个输入流生成一个线程来处理阻塞读取。但是,如果要读取的输入流数量非常多,则这将需要仔细的同步,并且可能需要一些开销。下面的代码是解决此问题的第一次尝试。我并不确定它是否足够同步,或者它是否以最好的方式管理线程

    import java.io.*;
    import java.util.concurrent.*;
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class MergedInputStream extends InputStream {
    
        AtomicInteger openStreamCount;
        BlockingQueue<Integer> buf = new ArrayBlockingQueue<Integer>(1);
        InputStream[] sources;
    
        public MergedInputStream(InputStream... sources) {
            this.sources = sources;
            openStreamCount = new AtomicInteger(sources.length);
            for (int i = 0; i < sources.length; i++)
                new ReadThread(i).start();
        }
    
    
        public void close() throws IOException {
            String ex = "";
            for (InputStream is : sources) {
                try {
                    is.close();
                } catch (IOException e) {
                    ex += e.getMessage() + " ";
                }
            }
            if (ex.length() > 0)
                throw new IOException(ex.substring(0, ex.length() - 1));
        }
    
    
        public int read() throws IOException {
            if (openStreamCount.get() == 0)
                return -1;
    
            try {
                return buf.take();
            } catch (InterruptedException e) {
                throw new IOException(e);
            }
        }
    
    
        private class ReadThread extends Thread {
    
            private final int src;
            public ReadThread(int src) {
                this.src = src;
            }
    
            public void run() {
                try {
                    int data;
                    while ((data = sources[src].read()) != -1)
                        buf.put(data);
                } catch (IOException ioex) {
                } catch (InterruptedException e) {
                }
                openStreamCount.decrementAndGet();
            }
        }
    }
    
    import java.io.*;
    导入java.util.concurrent.*;
    导入java.util.concurrent.AtomicInteger;
    公共类MergedInputStream扩展了InputStream{
    原子整数openStreamCount;
    BlockingQueue buf=新阵列BlockingQueue(1);
    输入流[]源;
    公共MergedInputStream(InputStream…源){
    这个。来源=来源;
    openStreamCount=新的AtomicInteger(sources.length);
    for(int i=0;i0)
    抛出新IOException(例如子字符串(0,例如length()-1));
    }
    public int read()引发IOException{
    if(openStreamCount.get()==0)
    返回-1;
    试一试{
    返回buf.take();
    }捕捉(中断异常e){
    抛出新的IOException(e);
    }
    }
    私有类ReadThread扩展线程{
    私人终审法院;
    公共读取线程(int-src){
    this.src=src;
    }
    公开募捐{
    试一试{
    int数据;
    而((数据=源[src].read())!=-1)
    buf.put(数据);
    }捕获(IOException ioex){
    }捕捉(中断异常e){
    }
    openStreamCount.decrementAndGet();
    }
    }
    }
    
    我可以想出三种方法:

    • 使用非阻塞I/O()。这是最干净的解决方案
    • 多个线程,每个合并的输入流一个线程。线程将阻塞相关输入流的
      read()
      方法,然后在数据可用时通知
      MergeInputStream
      对象。
      MergedInputStream
      中的
      read()
      方法将等待此通知,然后从相应的流中读取数据
    • 具有繁忙循环的单线程。您的
      MergeInputStream.read()
      方法需要循环检查每个合并输入流的
      available()
      方法。如果没有可用数据,则睡眠几毫秒。重复此操作,直到其中一个合并输入流中的数据可用为止

    由于同步,多线程解决方案会带来很多麻烦(至少对我来说是这样)<代码>可用永远不能保证返回非零值。因此,使用SelectableChannel是唯一合理的替代imho。
    available()
    不保证返回可以在不阻塞的情况下读取的字节总数,但如果有任何可用数据(即调用
    read()
    将不会阻塞),则应返回非零。如果有数据可用,许多
    InputStream
    实现将返回
    1
    ,否则返回
    0
    。单线程方案工作正常。我将学习NIO,并在未来的任务中尝试实现它。“谢谢你们两个。”冰冻蜘蛛。。。当然,在某些情况下它会很好地工作。在其他情况下,它将惨败。例如,见。我想说的是,没有任何保证,无论什么时候,只要你的程序I/O操作看起来有问题,你就必须怀疑并调查错误是否存在于这段代码中。所以,我别无选择,只能重写整个模块。。。谢谢你的链接。你链接到的论坛中的评论