Java 输入一直是一个坑,本来一直用 Scanner,但一直搞不懂换行符啥的,就用 BufferReader ,但前不久大疆笔试需要持续输入,早忘了 Scanner 怎么写,而那个场景用 Scanner 很好实现 ......

就继续在这里记录一下 Scanner 的坑吧

一、next & nextLine

区别

  • next
    • 不能得到带有空格的字符串
    • 一定要读到有效字符后才可以结束,结束条件是碰到空格、tab 键、enter 键
  • nextLine
    • 可以获得空白
    • 以回车作为结束符,也就是 nextLine 返回回车之前的所以字符

举例

代码调用分两种:

class ScannerDemo {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int num = sc.nextInt();
        String[] str = new String[num];

        for (int i = 0; i < num; i++) {
            //str[i] = sc.next(); 
            //str[i] = sc.nextLine();
        }
        sc.close();
    }
}

1. next 使用举例:

输入 1:

2
abc
cba

结果 1:

str[0] = "abc"
str[1] = "cba"

原因:next() 方法在遇到有效字符前所遇到的空格、tab 键、enter 键都不能当作结束符。所以,读到 abc 后回车,存下第一个,读到 cba 后回车存下第二个。

输入 2:

2
abc cba
efg gfe

结果 2:

str[0] = "abc"
str[1] = "cba"

原因:next() 方法在遇到有效字符前所遇到的空格、tab 键、enter 键都不能当作结束符。所以,读到 abc 后有空格,存下第一个,读到 cba 后回车存下第二个。所以,循环退出。不是预期的 "abc cba" 和 "efg gfe"

2. nextLine 使用举例:

输入 1:

2
abc
cba

结果 1:

str[0] = ""
str[1] = "abc"

原因:以回车( "\r" )作为结束符,也就是 nextLine 返回回车( \r )之前的所以字符。

对于 "" 的情况分析:

在输入 2 的时候调用的是 nextInt

  • 返回:nextInt 返回的是结束符之前的内容,并不会返回结束符
  • 我们的输入:2 \r

以回车 ( \r ) 结尾,于是 2 被返回,回车符 "\r" 它被丢弃在缓冲区中,现在缓冲区中,只有一个 \r ,于是 下一次 nextLine 扫描的时候就又扫描到了 \r,返回它之前的内容,也是啥都没有 "" ,然后再把 \r 去掉,

对于 "abc" 的情况分析:

下一次我们输入的是 abc\r,此时这个就是缓冲区的全部内容

所以下一次 nextLine 调用的时候,就返回 abc,再把 \r 去掉

输入 2:

2
abc bcf
efg gfe

结果 2:

str[0] = ""
str[1] = "abc bce"

对于 "" 的情况分析同输入 1

对于 "abc bce" 的情况分析:

第一次调用 nextLine 的时候,就清空了缓冲区,于是输入:abc bcf\r ,第二次调用 nextLine 的时候读取到了 \r 返回字符串 "abc bcf" 再把 \r 从缓冲区去掉

总结

总结一下,Scanner是一个扫描器,它扫描数据都是去内存中一块缓冲区中进行扫描并读入数据的,而我们在控制台中输入的数据也都是被先存入缓冲区中等待扫描器的扫描读取。这个扫描器在扫描过程中判断停止的依据就是“结束符”,空格,回车,tab 都算做是结束符

而坑点在于 next 系列的,也就是下面这些函数:

  • next
  • nextInt
  • nextDouble
  • nextFloat

这些函数与 nextLine 连用都会有坑

坑点就是 next 系列的函数返回了数据后,会把回车符留在缓冲区,因此我们下一次使用 nextLine 的时候会碰到读取空字符串的情况

解决方案:

  • 输入都用 nextLine ,做格式转换
  • 输入 next 系列函数调用后,中间调用一次 nextLine 调用去掉了回车符后,再调用一次 nextLine 调用真正输入我们的数据

都使用 nextLine:

class ScannerDemo {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int num = Integer.parseInt(sc.nextLine());
        String[] str = new String[num];

        for (int i = 0; i < num; i++) {
            str[i] = sc.nextLine();
        }
        sc.close();
    }
}

使用 next、nextLine 去临时回车符、nextLine 读入真正数据:

class ScannerDemo {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int num = sc.nextInt();
        String[] str = new String[num];
        sc.nextLine();
        for (int i = 0; i < num; i++) {
            str[i] = sc.nextLine();
        }
        sc.close();
    }
}

这两种方式都能解决可读入带空格的字符串,并且不会读异常空串

输入

2
abc cba
efg gfe

结果

str[0] = "abc cba"
str[1] = "efg gfe"

二、Scanner 性能

Scanner 是真的太慢了,一直用的是 BufferReader 写的,但今天一尝试就超时,于是改回用 BufferReader 就过了

归根结底是因为 Scanner 对输入字符实现了多样性的操作,BufferReader 就比较单一,读入的是字节流转换成字符串

实际测试,BufferReader 至少比 Scanner 输入快两倍

用 Scanner 是为了循环输入的功能,也就是 hasNext() 方法的功能

今天忽然想到了可以用死循环来代替,所以,还是继续使用 BufferReader 吧!少年!Scanner 性能劝退 (:smile::smile::smile:...

class Main{
    public static void main(String[] args) throws IOException {
        BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
        while (true){
            System.out.println(bf.readLine()); //循环输入
        }
    }
}