循环字节通道

时间:2012-09-20 06:39:56

标签: nio java-io

在阅读古老的 InputStream 时,我使用了以下代码(我从来不习惯):

    int read = 0;
    InputStream is = ....;

    while((i = is.read() != -1){

        ....
    }

现在我正在尝试使用NIO从 InputStream 读取10MB:

        protected void doPost(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub

        System.out.println("In Controller.doPost(...)");

        ByteBuffer chunk = ByteBuffer.allocateDirect(1000000);

        /* Source channel */
        int numRead = 0;
        ReadableByteChannel rbc = Channels.newChannel(request.getInputStream());

        /* Destination channel */
        File destFile = new File(
                "D:\\SegyDest.sgy");
        FileOutputStream destFileFos = new FileOutputStream(destFile);
        FileChannel destFileChannel = destFileFos.getChannel();

        /* Read-Write code */
        while (numRead >= 0) {
            chunk.rewind();
            numRead = rbc.read(chunk);

            System.out.println("numRead = " + numRead);

            chunk.rewind();
            destFileChannel.write(chunk);
        }

        /* clean-up */
        rbc.close();
        destFileChannel.close();
        destFileFos.close();

        request.setAttribute("ops", "File Upload");
        request.getRequestDispatcher("/jsp/Result.jsp").forward(request,
                response);
    }

我的问题是 / *如何循环源通道读取所有字节? * /

2 个答案:

答案 0 :(得分:1)

或者在API的1个字节以上的块中执行IO,如下所示:

byte[] bA = new byte[4096];
int i;
InputStream is = ....;
OutputStream os = ....;

while((i = is.read(bA) != -1){
    os.write(bA, 0, i);
}

我看了你的另一个问题,我的意见仍然有效。 NIO不是您正在寻找的解决方案。你有一台低端机器,限制RAM充当代理。

您可以做的最好的事情是让您的Servlet创建一个新线程,让这个线程使用NIO套接字/ HTTP库创建和设置传出连接。这个新的(和额外的)线程正在等待发生的任何事情中的任何一个,它会推动任何API尝试并在这三个方面取得进展。

这三件事是:

  • 尝试将数据写入远程服务器(如果内存数据中有缓冲要发送)
  • 等待主Servlet线程指示共享缓冲区中有新数据。或者已达到流结束。
  • 等待主Servlet线程指示需要关闭的额外线程(这是错误恢复和清理)。

你可能需要一个drainWithTimeout(long millis)函数,doPost()方法调用额外的线程给它一些时间来将最终数据推送到远程服务器。如果Servlet从InputStream中观察到End-of-Stream,则会调用此方法。

在doPost()方法返回之前,您必须确保100%可靠地获取额外的线程。所以控制它的启动/关闭很重要,特别是在InputStream发生错误的情况下,因为发送客户端断开连接或空闲时间过长。

然后两个线程(doPost()中的正常Servlet线程和您创建的新线程)将设置并共享一些任意内存缓冲区,可能共享16Mb或更多。

如果由于客户端/并发用户和2Gb RAM的限制而无法拥有16Mb缓冲区,那么您应该坚持使用此答案顶部的示例代码,因为网络和O / S内核已经存在缓冲一些Mb的数据。

使用两个线程的关键是您无法解决接收数据的Servlet API是阻塞I / O API的问题,如果您编写的应用程序符合Servlet规范/标准,则无法更改。如果您知道您的特定Servlet容器具有某个功能,那么这超出了本答案的范围。

这两个线程允许主Servlet doPost线程处于控制状态,STILL使用阻塞I / O API进行InputStream。

没有必要使用一个线程和一个阻塞的InputStream和一个非阻塞的OutputStream,你仍然会遇到这样的问题:当in.read()API调用被阻塞时你无法为输出流提供服务(等待更多的数据)或流结束)。

答案 1 :(得分:0)

在NIO频道之间复制的正确方法如下:

while (in.read(buffer) > 0 || buffer.position() > 0)
{
  buffer.flip();
  out.write(buffer);
  buffer.compact();
}

请注意,这会自动处理EOS,部分读取和部分写入。