【问题标题】:Can I use java.nio for Console Input?我可以将 java.nio 用于控制台输入吗?
【发布时间】:2024-04-16 03:00:02
【问题描述】:

考虑竞争性编程的场景,我必须从控制台读取 2*10^5(或更多)数字。然后我使用BufferedReader,或者为了获得更快的性能,我使用自定义阅读器类,它在后台使用DataInputStream

快速互联网搜索给了我这个。

我们可以将java.io 用于较小的数据流,对于大型流,我们可以使用java.nio

所以我想尝试java.nio 控制台输入并针对java.io 性能进行测试。

  1. 是否可以使用java.nio 读取控制台输入?
  2. 我可以使用java.nioSystem.in 读取数据吗?
  3. 它会比我目前拥有的输入法更快吗?

任何相关信息将不胜感激。

谢谢✌️

【问题讨论】:

  • “控制台”是指用户输入的内容?在这种情况下,“快”有什么相关性?这就是说 a) 通常,如果首先使用足够大的缓冲区进行读取,BufferedReader 不会加速任何事情。它可以加速错误地从文件中逐字符读取的应用程序,但对于用户真正逐字符键入字符的控制台,BufferedReader 会使情况变得更糟。 b) DataInputStream 没有理由比普通的 InputStream 更快。 c) 您可以为 stdin 创建一个Channel,但 NIO 不是灵丹妙药,期待相同的性能。
  • 控制台意味着就像在竞争性编程中,我们从标准输入读取,即 System.in,它有大量输入测试用例
  • 数字有哪些格式?
  • 十进制格式大多像数字一样用空格分隔

标签: java performance optimization java-8 java-io


【解决方案1】:

你可以像打开一个标准输入通道

FileInputStream stdin = new FileInputStream(FileDescriptor.in);
FileChannel stdinChannel = stdin.getChannel();

当标准输入被重定向到一个文件时,诸如查询大小、执行到其他通道的快速传输甚至内存映射之类的操作可能会起作用。但是当输入是真正的控制台或管道或者您正在读取字符数据时,性能不太可能有显着差异。

性能取决于您阅读它的方式,而不是您使用的课​​程。

直接在通道上操作以处理空格分隔的十进制数的代码示例是

CharsetDecoder cs = Charset.defaultCharset().newDecoder();
ByteBuffer bb = ByteBuffer.allocate(1024);
CharBuffer cb = CharBuffer.allocate(1024);
while(stdinChannel.read(bb) >= 0) {
    bb.flip();
    cs.decode(bb, cb, false);
    bb.compact();
    cb.flip();
    extractDoubles(cb);
    cb.compact();
}
bb.flip();
cs.decode(bb, cb, true);
if(cb.position() > 0) {
    cb.flip();
    extractDoubles(cb);
}
private static void extractDoubles(CharBuffer cb) {
    doubles: for(int p = cb.position(); p < cb.limit(); ) {
        while(p < cb.limit() && Character.isWhitespace(cb.get(p))) p++;
        cb.position(p);
        if(cb.hasRemaining()) {
            for(; p < cb.limit(); p++) {
                if(Character.isWhitespace(cb.get(p))) {
                    int oldLimit = cb.limit();
                    double d = Double.parseDouble(cb.limit(p).toString());
                    cb.limit(oldLimit);
                    processDouble(d);
                    continue doubles;
                }
            }
        }
    }
}

这比使用java.util.ScannerBufferedReaderreadLine() 后跟split("\\s") 更复杂,但具有避免正则表达式引擎的复杂性以及不创建String 对象的优点为线。当每行有多个数字或空行时,即行字符串与数字字符串不匹配时,这可以节省字符串构造固有的复制开销。

此代码仍在处理任意字符集。当您知道预期的字符集并且它基于 ASCII 时,使用轻量级转换而不是 CharsetDecoder(如 this answer 中所示)可以获得额外的性能提升。

【讨论】:

  • 要么读入ByteBuffer 并自己解码,要么使用Channels.newReader(…),再次以Reader 结束。如前所述,基本操作不会改变,尤其是当您要读取字符数据时。既然你说,你想主要阅读由空格分隔的十进制数字,你应该尝试java.util.Scanner
  • 1) 链接的答案仅针对读取整数,而不是一般十进制数 2) 它没有记录测试方法 3) 它是九岁。因此,它没有提供有关性能的一般说明。但是,如果您认为,没有Scanner,您可以做得更好,请随意。 4)这个答案解决了已经提出的问题。无需更新。
  • Pattern matching in Thousands of files 包含一些用于快速执行此类操作的指针。
  • 我用一个例子扩展了答案。