Java中BufferedReader与Scanner的比较(基于源码jdk15)

最近在用Java刷题,看到大佬说用BufferedReader读取输入会比Scanner快一些,自己试了一下,确实快一些。实验题目为Acwing题库中的第568题(看讨论区说使用Scanner会超时,自己尝试后发现并没有超时,但是Scanner的速度确实会慢不少)。

为了比较,我都是使用这两个类来把输入读取为字符串,然后再手动解析成整数,并没有使用该类自带的方法来解析

//Scanner版本,多次测试平均完成时间为1900ms上下
import java.util.*;
import java.io.*;
public class Main{
    public static void main(String[] args) throws IOException{
        Scanner sc = new Scanner(System.in);
        int n = Integer.parseInt(sc.nextLine());
        while(n-->0){
            String[] strs = sc.nextLine().split(" ");  //将读取的字符串分割为字符串数组
            int l = Integer.parseInt(strs[0]);
            int r = Integer.parseInt(strs[1]);
            int sum1 = sum(l-1);
            int sum2 = sum(r);
            System.out.println(sum2-sum1);
        }
    }
    private static int sum(int idx){
        if((idx&1)==0){
            return idx/2;
        }else{
            return (idx-1)/2 - idx;
        }
    }
}
//BufferedReader版本,平均完成时间为940ms上下,比Scanner少一半的时间。
import java.util.*;
import java.io.*;
public class Main{
    public static void main(String[] args) throws IOException{    //需要抛出IO异常
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(reader.readLine());
        while(n-->0){
            String[] strs = reader.readLine().split(" ");       //将字符串分割为字符串数组
            int l = Integer.parseInt(strs[0]);
            int r = Integer.parseInt(strs[1]);
            int sum1 = sum(l-1);
            int sum2 = sum(r);
            System.out.println(sum2-sum1);
        }
    }
    private static int sum(int idx){
        if((idx&1)==0){
            return idx/2;
        }else{
            return (idx-1)/2 - idx;
        }
    }
}

即使都是读取为字符串,BufferedReader和Scanner的速度仍有不少的差距。为什么会造成这种现象呢?

Scanner类的nextLine函数的源代码如下所示

public String nextLine() {
        modCount++;
        if (hasNextPattern == linePattern())
            return getCachedResult();
        clearCaches();
        
        //horizon为0表示该函数findWithinHorizon在整个输入中寻找匹配的元素
        //linePattern是一个pattern类型,由匹配句子的正则表达式编译而来
        //findWithinHorizon用来返回匹配到的字符串
        String result = findWithinHorizon(linePattern, 0);     
        if (result == null)
            throw new NoSuchElementException("No line found");
        MatchResult mr = this.match();
        String lineSep = mr.group(1);
        if (lineSep != null)
            result = result.substring(0, result.length() - lineSep.length());
        if (result == null)
            throw new NoSuchElementException();
        else
            return result;
    }

从代码中可以看到,nextLine函数主要通过findWithinHorizon函数来获取匹配到的字符串。Scanner是通过正则匹配的方式来读取输入的,我们继续深入看一下findWithinHorizon函数。

	 /**
     * Attempts to find the next occurrence of the specified pattern.
     *
     * <p>This method searches through the input up to the specified
     * search horizon, ignoring delimiters. If the pattern is found the
     * scanner advances past the input that matched and returns the string
     * that matched the pattern. If no such pattern is detected then the
     * null is returned and the scanner's position remains unchanged. This
     * method may block waiting for input that matches the pattern.
     *
     * <p>A scanner will never search more than {@code horizon} code
     * points beyond its current position. Note that a match may be clipped
     * by the horizon; that is, an arbitrary match result may have been
     * different if the horizon had been larger. The scanner treats the
     * horizon as a transparent, non-anchoring bound (see {@link
     * Matcher#useTransparentBounds} and {@link Matcher#useAnchoringBounds}).
     *
     * <p>If horizon is {@code 0}, then the horizon is ignored and
     * this method continues to search through the input looking for the
     * specified pattern without bound. In this case it may buffer all of
     * the input searching for the pattern.
     *
     * <p>If horizon is negative, then an IllegalArgumentException is
     * thrown.
     *
     * @param pattern the pattern to scan for
     * @param horizon the search horizon
     * @return the text that matched the specified pattern
     * @throws IllegalStateException if this scanner is closed
     * @throws IllegalArgumentException if horizon is negative
     */
    public String findWithinHorizon(Pattern pattern, int horizon) {
        ensureOpen();
        if (pattern == null)
            throw new NullPointerException();
        if (horizon < 0)
            throw new IllegalArgumentException("horizon < 0");
        clearCaches();
        modCount++;

        // Search for the pattern
        while (true) {
        //findPatternInBuffer函数如果找到匹配值返回true,否则返回false
            if (findPatternInBuffer(pattern, horizon)) {
                matchValid = true;
                //matcher类的作用是寻找匹配符,group函数用来返回捕捉到的输入的子序列
                return matcher.group();
            }
            if (needInput)
                readInput();
            else
                break; // up to end of input
        }
        return null;
    }

linePattern用来匹配行分隔符,比如\r或者\n。和linePattern有关的代码如下所示。

/**
     * 匹配行分隔符
     */
    private static volatile Pattern separatorPattern;
    private static volatile Pattern linePattern;
    private static final String LINE_SEPARATOR_PATTERN =
                                           "\r\n|[\n\r\u2028\u2029\u0085]";
    private static final String LINE_PATTERN = ".*("+LINE_SEPARATOR_PATTERN+")|.+$";

    private static Pattern separatorPattern() {
        Pattern sp = separatorPattern;
        if (sp == null)
            separatorPattern = sp = Pattern.compile(LINE_SEPARATOR_PATTERN);
        return sp;
    }

    private static Pattern linePattern() {
        Pattern lp = linePattern;
        if (lp == null)
            linePattern = lp = Pattern.compile(LINE_PATTERN);
        return lp;
    }

整体来看,使用Scanner类来读取输入行的过程就是去匹配行分隔符,然后返回。我们接下来再看看BufferedReader类读取输入行的过程。

BufferedReader类的readLine函数的源代码如下所示

String readLine(boolean ignoreLF) throws IOException {
		//以前的版本用的是StringBuffer,现在改为了BufferBuilder
        StringBuilder s = null;
        int startChar;
        //加锁,多线程安全,而Scanner的nextLine就不是多线程安全的
        synchronized (lock) {
            ensureOpen();
            boolean omitLF = ignoreLF || skipLF;

        bufferLoop:
            for (;;) {

				//读取到文件结尾
                if (nextChar >= nChars)
                	//将输入数据放入缓冲区,用char[]数组来保存
                    fill();
                if (nextChar >= nChars) { /* EOF */
                    if (s != null && s.length() > 0)
                        return s.toString();
                    else
                        return null;
                }
                boolean eol = false;
                char c = 0;
                int i;

                //如果有必要,跳过'\n'分隔符
                if (omitLF && (cb[nextChar] == '\n'))
                    nextChar++;
                skipLF = false;
                omitLF = false;

			//
            charLoop:
            	//找到分隔符的位置,用i来表示其位置
                for (i = nextChar; i < nChars; i++) {
                    c = cb[i];
                    if ((c == '\n') || (c == '\r')) {
                        eol = true;
                        break charLoop;
                    }
                }

                startChar = nextChar;
                nextChar = i;
				//读取分隔符前的字符,并组成字符串
                if (eol) {
                    String str;
                    if (s == null) {
                        str = new String(cb, startChar, i - startChar);
                    } else {
                        s.append(cb, startChar, i - startChar);
                        str = s.toString();
                    }
                    nextChar++;
                    if (c == '\r') {
                        skipLF = true;
                    }
                    return str;
                }

                if (s == null)
                    s = new StringBuilder(defaultExpectedLineLength);
                s.append(cb, startChar, i - startChar);
            }
        }
    }

总结一下BufferedReader类中readLine函数的方法就是读取输入,将换行符或者回车符之前的字符组成字符串,不存在匹配过程。相对Scanner而言,BufferReader的遍历次数更少,嵌套函数的深度更浅,因此效率会更高一些。

本人现在源码看的还是比较少,技术水平有限。如果博客内容有什么问题,非常欢迎大家留言^^

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值