最近在用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的遍历次数更少,嵌套函数的深度更浅,因此效率会更高一些。
本人现在源码看的还是比较少,技术水平有限。如果博客内容有什么问题,非常欢迎大家留言^^