4
\$\begingroup\$

I wrote a FastReader utility class that is supposed to read input fast. It's mostly aimed at parsing input files in competitive programming.

How could I made this better? I am mainly looking for good code suggestions rather than the specific details of the logic. Though, any comments/recommendations are appreciated.

package com.muratdozen.playground.util.io;

import com.muratdozen.playground.util.threading.NonThreadSafe;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.StringTokenizer;

/**
 * FastReader class helps to read input in the form of words
 * from an {@link InputStream}. Good to use as a parser.
 * <p></p>
 * Usage:
 * <pre>
 *    Assuming an input stream with the following lines:
 *      asd xxx
 *      123
 * {@code
 *  final FastReader fastReader = FastReader.from(System.in);
 *     final String s1 = fastReader.next();
 *     final String s2 = fastReader.next();
 *     final int n = fastReader.nextInt();
 *     ...
 * }
 * </pre>
 *
 * @author Murat Derya Ozen
 * @since: 9/28/13 1:50 PM
 */
@NonThreadSafe
public final class FastReader {

    private final BufferedReader bufferedReader;
    /* legacy class preferred over String#split and Scanner for performance */
    private StringTokenizer tokenizer;

    private FastReader(final BufferedReader bufferedReader) {
        this.bufferedReader = bufferedReader;
        this.tokenizer = null;
    }

    /**
     * Returns a {@link FastReader} instance that reads input from {@code inputStream}.
     *
     * @param inputStream
     * @return Returns a {@link FastReader} instance that reads input from {@code inputStream}.
     */
    public static final FastReader from(final InputStream inputStream) {
        return new FastReader(new BufferedReader(new InputStreamReader(inputStream)));
    }

    /**
     * Returns the next word acquired by {@link StringTokenizer}.
     * Moves on to the next line if the current line has been processed.
     *
     * @return Returns the next word acquired by {@link StringTokenizer},
     *         or null if end of stream has been reached.
     * @throws RuntimeException If {@link java.io.BufferedReader#readLine()} throws an {@link IOException}.
     */
    public String next() {
        return tokenize() ? tokenizer.nextToken() : null;
    }

    /**
     * Checks to see if there are any more words left in the {@code inputStream}.
     * Can be used to check if end of stream has been reached, as well.
     * If required, reads another line from the {@code inputStream}; i.e this operation
     * might perform an I/O; possibly block if end of stream is not reached but stream
     * is not yet available to yield a new line.
     *
     * @return Returns true if there are more words to read in the {@code inputStream}
     *         and end of stream has not been reached. False otherwise.
     * @throws RuntimeException If {@link java.io.BufferedReader#readLine()} throws an {@link IOException}.
     */
    public boolean canReadMore() {
        return tokenize();
    }

    private boolean tokenize() {
        while (tokenizer == null || !tokenizer.hasMoreTokens()) {
            // read a line, see if end of stream has been reached
            String line = null;
            try {
                if ((line = bufferedReader.readLine()) == null) return false;
            } catch (IOException unexpected) {
                throw new RuntimeException(unexpected);
            }
            tokenizer = new StringTokenizer(line);
        }
        return true;
    }

    /**
     * Returns the next {@code int} acquired by {@link StringTokenizer}
     * using {@link Integer#parseInt(String)} on {@link #next()}.
     * Moves on to the next line if the current line has been processed.
     *
     * @return Returns the next {@code int} acquired by {@link StringTokenizer}.
     * @throws RuntimeException      If {@link java.io.BufferedReader#readLine()} throws an {@link IOException}.
     * @throws NumberFormatException If an invalid input is encountered or end of stream has been reached.
     */
    public int nextInt() {
        return Integer.parseInt(next());
    }

    /**
     * Returns the next {@code long} acquired by {@link StringTokenizer}
     * using {@link Long#parseLong(String)} on {@link #next()}.
     * Moves on to the next line if the current line has been processed.
     *
     * @return Returns the next {@code long} acquired by {@link StringTokenizer}.
     * @throws RuntimeException      If {@link java.io.BufferedReader#readLine()} throws an {@link IOException}.
     * @throws NumberFormatException If an invalid input is encountered or end of stream has been reached.
     */
    public long nextLong() {
        return Long.parseLong(next());
    }

    /**
     * Returns the next {@code double} acquired by {@link StringTokenizer}
     * using {@link Double#parseDouble(String)} on {@link #next()}.
     * Moves on to the next line if the current line has been processed.
     *
     * @return Returns the next {@code double} acquired by {@link StringTokenizer}.
     * @throws RuntimeException      If {@link java.io.BufferedReader#readLine()} throws an {@link IOException}.
     * @throws NumberFormatException If an invalid input is encountered or end of stream has been reached.
     */
    public double nextDouble() {
        return Double.parseDouble(next());
    }

    /**
     * Returns the next {@link BigDecimal} acquired by {@link StringTokenizer}
     * using BigDecimal's String constructor on {@link #next()}.
     * Moves on to the next line if the current line has been processed.
     *
     * @return Returns the next {@code BigDecimal} acquired by {@link StringTokenizer}.
     * @throws RuntimeException      If {@link java.io.BufferedReader#readLine()} throws an {@link IOException}.
     * @throws NumberFormatException If an invalid input is encountered or end of stream has been reached.
     */
    public BigDecimal nextBigDecimal() {
        return new BigDecimal(next());
    }

    /**
     * Returns the next {@link BigInteger} acquired by {@link StringTokenizer}
     * using BigInteger's String constructor on {@link #next()}.
     * Moves on to the next line if the current line has been processed.
     *
     * @return Returns the next {@code BigInteger} acquired by {@link StringTokenizer}.
     * @throws RuntimeException      If {@link java.io.BufferedReader#readLine()} throws an {@link IOException}.
     * @throws NumberFormatException If an invalid input is encountered or end of stream has been reached.
     */
    public BigInteger nextBigInteger() {
        return new BigInteger(next());
    }

    /**
     * Closes the input stream.
     *
     * @throws RuntimeException If {@link java.io.BufferedReader#readLine()} throws an {@link IOException}.
     * @see java.io.BufferedReader#close()
     */
    public void close() {
        try {
            bufferedReader.close();
        } catch (IOException unexpected) {
            throw new RuntimeException(unexpected);
        }
    }
}

The code is hosted on GitHub if anyone's interested. I will commit changes if necessary.

\$\endgroup\$

3 Answers 3

3
\$\begingroup\$
  1. I'm always wary of naming my classes according to performance criteria. What if you (or someone else) finds a faster method of reading tokens from an input stream. Would you then write an EvenFasterReader? How about something like TokenReader?

  2. Consider letting the user pass the delimiters as well (optionally). This will make it more useful by allowing to parse input which is delimited by other things than white spaces.

\$\endgroup\$
2
\$\begingroup\$

From the perspective of usage, the tokenize() method returns true if there is more to read, and false otherwise. As such, you could rename it to canReadMore, and drop the existing canReadMore method which was calling tokenize anyway.


Since the characterizing feature of your reader is that it uses StringTokenizer, perhaps a more suitable name will be TokenizerReader.


In this code, the initialization of line is pointless:

// read a line, see if end of stream has been reached
String line = null;
try {
    if ((line = bufferedReader.readLine()) == null) return false;
} catch (IOException unexpected) {
    throw new RuntimeException(unexpected);
}
tokenizer = new StringTokenizer(line);

It's enough to simply declare:

String line;

The comment about what the code does is obvious, you can just drop it.

\$\endgroup\$
1
\$\begingroup\$

How could I made this better?

Stop those annoying Javadocs commenting the obvious. This is only noise.

 /**
  * Returns a {@link FastReader} instance that reads input from {@code inputStream}.
  *
  * @param inputStream
  * @return Returns a {@link FastReader} instance that reads input from {@code inputStream}.
  */
 public static final FastReader from(final InputStream inputStream) {
     return new FastReader(new BufferedReader(new InputStreamReader(inputStream)));
 }

In order to understand 1 single line of code, I have to go through 6 lines of commenting junk, 1 line boilerplate Java and at least a closing bracket. This is useless. Anybody with little experience knows what this one line does, so why spend time on commenting it?

Without the comments it is much better to read.

package com.muratdozen.playground.util.io;

import com.muratdozen.playground.util.threading.NonThreadSafe;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.StringTokenizer;

@NonThreadSafe
public final class FastReader {

    private final BufferedReader bufferedReader;
    private StringTokenizer tokenizer;

    private FastReader(final BufferedReader bufferedReader) {
        this.bufferedReader = bufferedReader;
        this.tokenizer = null;
    }

    public static final FastReader from(final InputStream inputStream) {
        return new FastReader(new BufferedReader(new InputStreamReader(inputStream)));
    }

    public String next() {
        return tokenize() ? tokenizer.nextToken() : null;
    }

    public boolean canReadMore() {
        return tokenize();
    }

    private boolean tokenize() {
        while (tokenizer == null || !tokenizer.hasMoreTokens()) {
            String line = null;
            try {
                if ((line = bufferedReader.readLine()) == null) return false;
            } catch (IOException unexpected) {
                throw new RuntimeException(unexpected);
            }
            tokenizer = new StringTokenizer(line);
        }
        return true;
    }

    public int nextInt() {
        return Integer.parseInt(next());
    }

    public long nextLong() {
        return Long.parseLong(next());
    }

    public double nextDouble() {
        return Double.parseDouble(next());
    }

    public BigDecimal nextBigDecimal() {
        return new BigDecimal(next());
    }

    public BigInteger nextBigInteger() {
        return new BigInteger(next());
    }

    public void close() {
        try {
            bufferedReader.close();
        } catch (IOException unexpected) {
            throw new RuntimeException(unexpected);
        }
    }
}

But while skimming through the code, two questions come to my mind:

1) Why is this called a FastReader - besides the case, Chris mentioned, what makes it really fast? I am seeing the usage of a BufferedReader. Hopefully it is fast. I see nothing special in your code, that makes it fast.

2) I do not see a single usecase for this. At best, you have some kind of DSL which describes your Inputfile.

Why waste time and put a wrapper around concrete data.

   final String s1 = fastReader.next();
   final String s2 = fastReader.next();
   final int n = fastReader.nextInt();

So I could use directly a BufferedReader, read a line, and if necessary parse a line to an Integer.

What is the advantage of a FastReader? I could not think of one. In this case, I think, the whole idea is overengineered. It seems abstraction for the abstraction's sake.

Besides: Reading the Documentation at Oracle, it says »StringTokenizer is a legacy class that is retained for compatibility reasons although its use is discouraged in new code. It is recommended that anyone seeking this functionality use the split method of String or the java.util.regex package instead.«

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.