注意:所有讨论仅基于windows平台,据说linux和mac都不一样,之后装了linux再来对比试试。
首先上一段代码:
public class ScannerTest {
public static void main(String[] args)
{
System.out.println("abcd\r123");
System.out.println("final");
}
}
在IDE中(我用的eclipse),运行结果是
abcd
123
final
用cmd,java命令运行,结果却不一样,结果是:
123d
final
关于\r和\n的来历,有一 个小故事,是这么说的:
“在计算机还没有出现之前,有一种叫做电传打字机(Teletype Model 33)的玩意,每秒钟可以打10个字符。但是它有一个问题,就是打
完一行换行的时候,要用去0.2秒,正好可以打两个字符。要是在这0.2秒里面,又有新的字符传过来,那么这个字符将丢失。 于是,研制人员
想了个办法解决这个问题,就是在每行后面加两个表示结束的字符。一个叫做“回车”,告诉打字机把打印头定位在左边界;另一个叫做“换行”,
告诉打字机把纸向下移一行。 ”
\r就是回车,\n就是换行。很明显,控制台和eclipse对于“\r”的理解是不同的。
实际上当按下回车键时,传递给系统的是"\r\n"。之前关于Scanner的读入,我一直有一些疑问,结合这一点,算是弄清楚了,看以下代码:
<span style="font-family:SimSun;">import java.util.*;
public class ScannerTest {
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
//sc.useDelimiter("\n");
String s1 = sc.next();
String s2 = sc.next();
System.out.println(s1 + "; length : " + s1.length());
System.out.println(s2 + "; length : " + s2.length());
sc.close();
}
}
</span>
输入“1”,按一下回车,再输入“2”,按回车,结果是:
1
2
1; length : 1
2; length : 1
很科学,可是Scanner的next()方法有个问题,就是会以空格,制表符等等来分隔字符串,比如调用一个next(),阻塞等待输入,输入“abc def”,则只会返回“abc”,下一次用next()会立即返回“def”。《疯狂java讲义》这个书里说,可以用useDelimiter("\n")来设定只以换行符来分隔,显然李刚就忘了还有"\r"这个东西,把上面代码的注释去掉:
import java.util.*;
public class ScannerTest {
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
sc.useDelimiter("\n");
String s1 = sc.next();
String s2 = sc.next();
System.out.println(s1 + "; length : " + s1.length());
System.out.println(s2 + "; length : " + s2.length());
sc.close();
}
}
还是依旧
输入“1”,按一下回车,再输入“2”,按回车,在eclipse中结果是:
1
2
1
; length : 2
2
; length : 2
原因很简单,只以"\n"做分隔,那么next()返回的其实是“1\r”和“2\r”,而eclipse对"\r"的处理是换行加回车,即等同于“\r\n”的,所以在输出分号之前换了行,而且字符串长度也变成了2。如果改成sc.useDelimiter("\r\n");自然就一切正常了。
同时也可以预想上面那段代码在命令行中的结果,返回的"1\r"输出1之后,光标回到行首,再输出“;length ……”就把“1”给覆盖了。
再说说next()和nextLine(),nextLine()的行为有点不一样,把上面的代码改一下:
import java.util.*;
public class ScannerTest {
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
sc.useDelimiter("\n");
String s1 = sc.next();
String s2 = sc.nextLine();
System.out.println(s1 + "; length : " + s1.length());
System.out.println(s2 + "; length : " + s2.length());
sc.close();
}
}
还是输入“1”,回车,“2”,回车。
但是输入“1”,回车之后,程序直接就全部输出了,结果是
1//这是输入
1
; length : 2
; length : 0
看了一下Scanner的文档,对于next()是这样说的
public String next()
hasNext()
returned
true
.
而对于nextLine()是这样描述的:
Since this method continues to search through the input looking for a line separator, it may buffer all of the input searching for the line to skip if no line separators are present.
显然,对于nextLine()完全没有提delimiter pattern,所以说nextLine()是不受useDelimiter方法约束的,nextLine直接跳啊跳啊,去找换行符。当输入“1”,按下回车时,其实输入给Scanner的是“1\r\n”,next方法对比着分隔符“\n",把“1\r”返回给了s1。此时定位还在\n处,即定位还没有换行,再调用nextLine,直接就发现一个“\n”于是把跳过的部分(空的)赋给了s2。
import java.util.*;
public class ScannerTest {
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
sc.useDelimiter("\r\n");
String s1 = sc.next();
String s2 = sc.nextLine();
System.out.println(s1 + "; length : " + s1.length());
System.out.println(s2 + "; length : " + s2.length());
sc.close();
}
}
eclipse和命令行中运行的结果都是:
1
1; length : 1
; length : 0
假设nextLine只以“\n”来区分行,则“\r”会被赋给s2,但是s2长度为0,什么都没有,之前已经证明\r是要占长度的。所以说nextLine也是以“\r\n”来区分一行的。作为windows下正统的换行,也是合理的,猜想nextLine是找不到\r\n时再找\n,不过感觉好像也没什么区别。
再说说,nextInt(),nextDouble()这一类的方法,看下面代码:
import java.util.*;
public class ScannerTest {
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
sc.useDelimiter("\n");
String s1 = sc.next();
Integer s2 = sc.nextInt();
System.out.println(s1 + "; length : " + s1.length());
System.out.println(s2 );
sc.close();
}
}
这个代码,执行时会出现
java.util.InputMismatchException异常。原因很简单,分隔符是"\n"假设给nextInt输入“1”,回车,那么输入的就是“1\r\n”,去掉分隔符就是“1\r”,无法转换成Integer。把分隔符设置成“\r\n”就没问题了。
总之,李刚这个书,教人把分隔符设置成“\n”显然是不科学的,最近一直在看这本书,有点怀疑这书的水平了。希望他在讲I/O那章的时候能把这个东西说清楚。