【转】java faster input for acm

来源:互联网 发布:linux发行版 知乎 编辑:程序博客网 时间:2024/06/04 19:12

Faster Input for Java

The ACM ICPC allows competitors to use Java, but is Java a feasible option? Compared to C, Java requires many more lines of code to perform the same task and (perhaps most important) Java is generally slower than C. Input/output is one of the slowest parts (compared to C). So, can we do anything to speed I/O in Java?

Problem: Scanner is Slooooow

Using Scanner to parse input is convenient, but too slow. Using BufferedReader and StringTokenizer is much faster, but its a lot of typing during a competition. Can we make Java easier for the ACM ICPC?

Too much typing in Java:

C
 double x; scanf("%lf",&x); 
Java with Scanner
 Scanner input =    new Scanner(System.in); double x = input.nextDouble(); 
Java with 
BufferedReader
import java.io.*// must "throw IOException", tooBufferedReader br = new BufferedReader(  new InputStreamReader( System.in ) );// this works only if line contains just 1 valuedouble x = Double.parseDouble( br.readLine() ); 

How Slow?

I ran some tests to read 10,000,000 int or double from a file. Times are shown in the tables below. I ran the tests on an Intel Core2 Duo 2.4GHz cpu with Windows XP Pro SP3, Sun JDK 6.0r22, and GNU gcc 4.5.2.

Table 1. Time to read 10,000,000 int values from file

Input MethodTime (sec)C scanf("%d", &arg)3.78Scanner.parseInt()29.52BufferedReader + inline Integer.parseInt2.89BufferedReader + Reader.nextInt method3.01

Table 2. Time to read 10,000,000 double values from file

Input MethodTime (sec)C scanf("%lf", &arg)11.9Scanner.parseDouble()66.86BufferedReader + inline Double.parseDouble3.06BufferedReader + Reader.nextDouble method3.14

The times show that Scanner is much slower than C. On some example problems in Aj. Jittat's ACM training session, my Java solutions failed because they exceeded the run time limit for the task.

BufferedReader plus a parser method are almost as fast as C, but require a lot more typing. If you can use Eclipse, Eclipse will automatically add imports and try/catch or throws, which is a help but not enough help.

Listing 1. Sample methods to read using Scanner and BufferedReader. We split the input line into string tokens, since one line may contain multiple values. To split the input, StringTokenizer is 4X faster than string.split().

 /** Read count integers using Scanner */ static int scanInteger(int count) {    Scanner scanner = new Scanner(input);    int last = 0;    while (count-- > 0) {    last = scanner.nextInt();    }    return last; } 
 /** Read count integers using BufferedReader */ static int readIntegers(int count)         throws IOException {    BufferedReader reader = new BufferedReader(               new InputStreamReader(input) );    StringTokenizer tokenizer = new StringTokenizer("");    int last = 0;    while (count-- > 0) {        if (! tokenizer.hasMoreTokens() ) {            tokenizer = new StringTokenizer(reader.readLine());        }        last = Integer.parseInt(tokenizer.nextToken());    }    return last; } 

Creating Reusable Code

How to reduce the burden of reading input in Java? Java has some advantages over C/C++: fewer syntax errors, maybe fewer logic errors, good code completion, automatic compilation, and debugger in Eclipse.

To make Java easier to use, let's put the reader code in a separate class. (You can put many classes in one source file, provided that only one class is "public".) We can copy our Reader class into every ACM task, so we only have to write it once during a competition. One person can write it while the others study the tasks and design a solution.

Listing 2 is an example. Let me know if you find any shorter, more efficient code. I use StringTokenizer instead of string.split() because StringTokenizer is much faster. I use static methods so we don't have to create or keep track of a "Reader" object, and use the default (package) scope so I don't have to type "public".

Putting the input methods in a separate class has almost no effect on the speed in my benchmark. But it does make our code more reusable and simplifies the rest of your task code.

Its still a lot of code, but after you type it once you can copy the class into every task.

So in your ACM tasks you can write:

     Reader.init( System.in ); // connect Reader to an input stream     double x = Reader.nextDouble();     int n = Reader.nextInt();

Listing 2. Reusable class for reading int and double.

/** Class for buffered reading int and double values */class Reader {    static BufferedReader reader;    static StringTokenizer tokenizer;    /** call this method to initialize reader for InputStream */    static void init(InputStream input) {        reader = new BufferedReader(                     new InputStreamReader(input) );        tokenizer = new StringTokenizer("");    }    /** get next word */    static String next() throws IOException {        while ( ! tokenizer.hasMoreTokens() ) {            //TODO add check for eof if necessary            tokenizer = new StringTokenizer(                   reader.readLine() );        }        return tokenizer.nextToken();    }    static int nextInt() throws IOException {        return Integer.parseInt( next() );    }    static double nextDouble() throws IOException {        return Double.parseDouble( next() );    }}
原创粉丝点击