背景
对于大多数ACMer来说,Java I/O慢一直是个让人头疼的问题。由于Java的Scanner的实现太过复杂(为了满足更多高级特性),使用它输入会比C/C++的scanf慢好几倍,并且内存开销相当大。
目前网上最常见的快速模板是使用BufferedReader,带缓冲并且按行读入测试数据,然后使用StringTokenizer将每一行分片。但我找到的几个模板太过简单,虽然代码量少,却无法支持一些常用操作,比如:丢弃当前行,检索是否读取到了流尾,有一定局限性。 也有部分实现了这两个功能的模板,但一直没看到特别满意的,于是自己写了一个。题外话: 还有很多对Java一知半解的人给出new Scanner(new BufferedInputStream())这种”减速”方法,Scanner的实现中有缓冲,慢并不是慢在这里。
简介
Reader内部使用BufferedReader的nextLine方法从输入流读取数据,由内部方法innerNextLine包装,同样使用StringTokenizer分片。hasNext方法会检查是否还有数据,它通过读入新数据并创建tokenizer来寻找下一个可用的token。因此,调用hasNext方法后,会确保调用tokenizer.nextToken()时可以得到数据,除非已经读取到输入流尾。nextLine方法会读入一行新数据,同时舍弃tokenizer中的内容。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方法执行时间峰值内存平均内存
Scanner91.2s88.7MB81.5MB
Scanner(缓冲输入流包装 错误用法)106.9s96.2MB81.5MB
Java快速IO模板9.8s84.1MB79.1MB
cin / cout107.5s0.4MB0.4MB
scanf / printf10.1s0.4MB0.4MB
C / C++输入输出挂2.4s0.4MB0.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());
}
}
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("\n");
}
@Override
public void close() throws IOException {
writer.close();
}
}