【问题标题】:Looping over byte channels循环字节通道
【发布时间】:2012-09-12 12:01:36
【问题描述】:

在阅读旧的 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);
    }

我的问题是 /* 如何遍历源通道来读取所有字节? */

【问题讨论】:

  • 谷歌上 ReadableByteChannel 的首页有一个看起来像一个工作示例exampledepot.com/egs/java.nio/ReadChannel.html 你需要重置()然后读取()然后重置()然后写入()。
  • 嗨,Darryl,我编辑了我的原始帖子以包含您提供的链接中建议的实现。但它似乎没有“循环” - 无论我初始化“块”的大小如何,目标文件的大小都是相同的。输出是: numRead = 96 numRead = -1 我在这里遗漏了什么吗?
  • FWIW 为什么在看起来像 Servlet 的内部使用非阻塞 IO?你知道 Servlet API 被阻塞了吗?您正在尝试在阻塞 IO 句柄之上构建 NIO 方案。要进行 NIO,您必须使用 OS 非阻塞 IO 句柄作为构建的基础。您最好将新的 WebSocket Servlet API 之一用于非阻塞 IO 模型。
  • 在 Servlet 中按原样使用 InputStream 非常好,因为 IO 模型是每个活动请求一个线程。考虑 NIO 的唯一原因是,如果您在同一线程中有一些有用的事情要做在读/写 IO 之间的时间相同。您的示例没有证明这种需要,它也没有检查成功写入的字节数,也没有处理积压的策略(输入 IO 比输出 IO 快,您被卡住了数据)。我认为您尝试使用 NIO这里可能会误导你有一些“不舒服”的感觉,收缩是最好的。
  • 我不会在最终代码中编写 Servlet 中的 I/O 代码,在这里,我在 doPost(...) 中编写只是为了让我的方法清晰。实际上,我问的问题是在以下上下文中:stackoverflow.com/questions/12500185/… 请检查相同并指导我。

标签: nio java-io


【解决方案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);
}

我已经查看了您的其他问题,但我的 cmets 仍然存在。 NIO 不是您正在寻找的解决方案。您有一台低端机器,其 RAM 限制为代理。

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

这三件事是:

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

您可能需要 doPost() 方法在额外线程上调用的 drainWithTimeout(long millis) 函数,以便给它一定的时间将最终数据推送到远程服务器。如果 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 线程处于控制之中,并且仍然为 InputStream 使用阻塞 I/O API。

使用一个线程和一个阻塞 InputStream 和一个非阻塞 OutputStream 是没有意义的,当 in.read() API 调用被阻塞(等待更多数据)时,您仍然无法为输出流提供服务或流结束)。

【讨论】:

  • 此代码无法编译。 OutputStream.write() 不返回字节计数值或 -1:它是无效的,它会阻塞,直到所有数据都被写入。没有非阻塞 OutputStream 这样的东西。
  • 是的,你当然是正确的,我希望提问者可以在提供未经测试的示例的行之间阅读。我已经修改以注释掉这些部分。提问者没有提供任何特定的输出 API,所以我有义务使用 OutputStream,但如果提问者仍然确定他们想要在设计中包含 NIO,NIO API 将有一个字节写入返回计数。
  • 如何删除代码中的垃圾,而不是仅仅将其注释掉?它永远不会回头;总是错的:丢掉它。
  • 我需要一些时间来处理这里提供的所有输入 - 会回来的!谢谢大家:)
【解决方案2】:

NIO通道之间拷贝的正确方法如下:

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

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

【讨论】:

    猜你喜欢
    • 2012-02-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-24
    • 1970-01-01
    • 1970-01-01
    • 2011-08-01
    • 2013-10-01
    相关资源
    最近更新 更多