快速的Java ACM IO模板
背景
对于大多数ACMer来说,Java速度慢一直是个让人头疼的问题。实际上,Java的性能并不差,而是由于自带的Scanner
和PrintStream
包装太过复杂(例如Scanner对数据做了缓存,PrintStream对输出操作都加了锁等),而拖慢了I/O速度。使用它们会比C语言的scanf
/printf
慢好几倍,并且内存开销相当大。
目前网上最常见的快速输入模板是使用BufferedReader
,它带缓冲并按行读入测试数据,然后结合StringTokenizer
将每一行数据分片。但我找到的几个模板太过简单,虽然代码量少,却无法支持一些常用操作,比如:丢弃当前行、检查是否还有数据等,有一定局限性。也有部分实现了这两个功能的模板,但比较啰嗦,于是自己写了一个。
题外话: 还有很多对Java一知半解的人给出new Scanner(new BufferedInputStream())这种”减速”方法,Scanner的实现中有缓冲,慢并不是慢在这里。
介绍
Reader
使用BufferedReader
的nextLine
方法按行读取数据,由内部方法innerNextLine
包装,并使用StringTokenizer
对每一行数据分片。hasNext
方法可以判断是否还有数据,它首先检查当前tokenizer是否有数据,若没有,则会循环读入新行并创建tokenizer,直到找到可用数据或读到尾部为止。nextLine
方法会舍弃当前行并读入一行新数据。next
方法会返回下一个数据。nextXXX
方法可以将next的数据解析成具体数据类型。
Writer
则是对BufferedWriter
的简单包装,提供了接受Object的print
和println
方法。注意,程序结束前一定要调用Writer的close
方法来刷新缓冲区并关闭输出流
性能测试
测试题目:给出未知行,每行第一个数N,随后给出N个数,求N个数之和。
测试输入:1000w行,均为5 1 10 100 1000 10000,结果为11111
测试环境:
Windows10 i7-7700HQ
Java: OpenJDK 12
C++: MinGW GCC
方法 | 执行时间 | 峰值内存 | 平均内存 |
Scanner | 91.2s | 88.7MB | 81.5MB |
Scanner(缓冲输入流包装 错误用法) | 106.9s | 96.2MB | 81.5MB |
Java快速IO模板 | 9.8s | 84.1MB | 79.1MB |
cin / cout | 107.5s | 0.4MB | 0.4MB |
scanf / printf | 10.1s | 0.4MB | 0.4MB |
C / C++输入输出挂 | 2.4s | 0.4MB | 0.4MB |
模板
class AReader { private BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); private StringTokenizer tokenizer = new StringTokenizer(""); private String innerNextLine() { try { return reader.readLine(); } catch (IOException ex) { return null; } } public boolean hasNext() { while (!tokenizer.hasMoreTokens()) { String nextLine = innerNextLine(); if (nextLine == null) { return false; } tokenizer = new StringTokenizer(nextLine); } return true; } public String nextLine() { tokenizer = new StringTokenizer(""); return innerNextLine(); } public String next() { hasNext(); return tokenizer.nextToken(); } public int nextInt() { return Integer.parseInt(next()); } public BigInteger nextBigInt() { return new BigInteger(next()); } // 若需要nextDouble等方法,请自行调用Double.parseDouble包装 } // 如果输出数据量不大,可以忽略Writer,直接使用System.out输出 class AWriter implements Closeable { private BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out)); public void print(Object object) throws IOException { writer.write(object.toString()); } public void println(Object object) throws IOException { writer.write(object.toString()); writer.write(System.lineSeparator()); } @Override public void close() throws IOException { writer.close(); } }
使用示例:计算大数A+B
public class Main { public static void main(String[] args) throws IOException { AReader reader = new AReader(); AWriter writer = new AWriter(); writer.print(reader.nextBigInt().add(reader.nextBigInt())); writer.close(); } }
博主请问你的读入类的String读入测试通过了吗,我测试好像不能用,如果你那边能用的话,能不能给个实例。
请问一下这个输出类是不是应该这么用呢
AWriter writer = new AWriter(System.out);
writer.println();
是的