博客内容仅作学习/交流/参考之用,详细内容参见更多网络资源,欢迎大家交流探讨;
为防止经典/实用文章链接丢失,或不易查找,对部分文章全文转载。如果内容信息侵犯了你的合法权益,请告知我,我将及时处理。
E-Mail:dwang2014#hotmail.com(# ——> @)
从控制台中读取数据是一个比较常用的功能,在 JDK 5.0 以前的版本中的实现是比较复杂的,需要手工处理系统的输入流。有意思的是,从 JDK 5.0 版本开始,能从控制台中输入数据的方法每增加一个版本号,就有一种新增的方法,这也增加了选择的种类,可以依据不同的要求来进行选择。下面来看一下,各个版本中如何从控制台中读取数据以及各自的优缺点。
站在巨人的肩上才能看得更远,一步一个脚印才能走得更远。分享成长,交流进步,转载请注明出处!JDK 1.4 及以下的版本中要想从控制台中输入数据只有一种办法,即使用
System.in
获得系统的输入流,再桥接至字符流从字符流中读入数据。示例代码如:import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class Test1 { public static void main(String[] args) { String str = readString("请输入字符串:"); System.out.println("readString 方法的输入:" + str); } /** * 使用系统的输入流,从控制台中读取数据
* 用于所用的JDK版本 * @param prompt 提示信息 * @return 输入的字符串 */ private static String readString(String prompt) { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String str = null; try { System.out.print(prompt); str = br.readLine(); } catch (IOException e) { e.printStackTrace(); } return str; } }从上面的代码段来看,这种控制台输入的方法非常地麻烦,为了能读取整行的数据,采用了
BufferedReader
类来进行处理,而且在读取的过程中还需要捕获IOException
。不过这是 JDK 1.4 及以下版本中从控制台读取数据唯一的办法。还有一种非控制台读入数据的办法,就是采用 Swing 中的JOptionPane
,会弹出一个非常漂亮的输入对话框让使用者输入数据,但这是一种比较另类的做法,不推荐使用。
上面的两种方法都有个共同的缺点——只能读取字符串,若需要读取其他类型的数据需要手工进行转换。import javax.swing.JOptionPane; public class Test2 { public static void main(String[] args) { String str = readStringFromDialog("请输入字符串:"); System.out.println("readStringFromDialog 方法的输入:" + str); } /** * 使用 JOptionPane的输入对话框,输入字符串
* 用于所用的JDK版本 * @param prompt 提示信息 * @return 输入的字符串 */ private static String readStringFromDialog(String prompt) { return JOptionPane.showInputDialog(prompt); } }
2 JDK 5.0 读取的方法
从 JDK 5.0 开始,基本类库中增加了
java.util.Scanner
类,根据它的 API 文档说明,这个类是采用正则表达式进行基本类型和字符串分析的文本扫描器。使用它的Scanner(InputStream source)
构造方法,可以传入系统的输入流System.in
而从控制台中读取数据。示例代码如下:import java.util.Scanner; public class Test3 { public static void main(String[] args) { String str = readString("请输入字符串:"); System.out.println("readString方法的输入:" + str); } /** * 使用扫描器类(Scanner)从控制台中读取字符串
* 适用于JDK 5.0及以后的版本 * @param prompt 提示信息 * @return 输入的字符串 */ private static String readString(String prompt) { Scanner scanner = new Scanner(System.in); System.out.print(prompt); String str = scanner.nextLine(); scanner.close(); return str; } }从代码量上来看,
Test3
比Test1
少了很多的代码,核心代码只有两行。其实并不是Scanner
将控制台输入给简单化了,只是在其内部的实现中已经将IOException
处理了,而且采用InputStreamReader
来一个字符一个字符进行扫描读取的(嘿嘿,它本身就是个扫描器),只是Scanner
做了更高层次的封装。
Scanner
不仅可以从控制台中读取字符串,还可以读取除char
之外的其他七种基本类型和两个大数字类型,并不需要显式地进行手工转换。Scanner
不单单只能扫描控制台中输入的字符,它还可以让读入的字符串匹配一定的正则表达式模式,如果不匹配时将抛出InputMismatchException
异常。使用
System.in
作为它的构造参数时,它只扫描了系统输入流中的字符。它还有其他的构造,分别可以从文件或者是字符串中扫描分析字符串的,具体的使用方法可以参考 API 文档说明。
3 JDK 6.0 读取的方法
从 JDK 6.0 开始,基本类库中增加了
java.io.Console
类,用于获得与当前 Java 虚拟机关联的基于字符的控制台设备。在纯字符的控制台界面下,可以更加方便地读取数据。示例代码如下:import java.io.Console; public class Test4 { public static void main(String[] args) { String str = readString("请输入字符串:"); System.out.println("readString方法的输入:" + str); } /** * 使用控制台类(Console)从控制台中读取字符串
* 适用于JDK 1.6或以后的版本 * @param prompt 提示信息 * @return 输入的字符串 */ private static String readString(String prompt) { Console console = System.console(); if (console == null) { throw new IllegalStateException("不能使用控制台"); } return console.readLine(prompt); } }但是,
Console
也有一些缺点,根据Console
API 文档的说明:虚拟机是否具有控制台取决于底层平台,还取决于调用虚拟机的方式。如果虚拟机从一个交互式命令行开始启动,且没有重定向标准输入和输出流,那么其控制台将存在,并且通常连接到键盘并从虚拟机启动的地方显示。如果虚拟机是自动启动的(例如,由后台作业调度程序启动),那么它通常没有控制台。
通过上面的文档说明可以看出,在使用 IDE 的情况下,是无法获取到
Console
实例的,原因在于在 IDE 的环境下,重新定向了标准输入和输出流,也是就是将系统控制台上的输入输出重定向到了 IDE 的控制台中,需要通过命令提示手动编译、执行。因此,在 IDE 中不能使用这个程序,而Test1
和Test3
就没有这种限制。4 总结
以上囊括了 Java 中各种版本从控制台中读入数据的方法,将对它们的优缺点进行了分析。下面给出了一些使用建议,可供参考:
- JRE 1.4 或以下版本的情况下,没得选择只能采用
Test1
或者是非控制台读入的Test2
的方法。- JRE 5.0 的情况下,建议使用基于
Scanner
的Test3
的方法,更方便地进行数据读取。- JRE 6.0 的情况,并且只在字符界面的控制台下运行时,采用
Test4
的方法,如果需要读入像密码之类的敏感数据,为了安全性考虑也必须使用Test4
或者是自行实现。如果需要读入除字符串类型之外的其他数据类型,建议使用基于Scanner
的控制台输入。
参考资料
[1]. 《Java从控制台中读取数据完全攻略》,author: 火龙果被占用了================分割线=========================详细整理Java 从命令控制台输入数据读取输入流System.in的几种常用方法
一、System.in(最笨的方法)
System从JDK1.0开始。System的静态方法in返回的是一个InputStream类型的对象,则我们可以用读取输入流的方式对其进行读取。
按API的说法“此流已打开并准备提供输入数据。通常,此流对应于键盘输入或者由主机环境或用户指定的另一个输入源。”键盘上按下的任何一个键都会被当做是输入值。
如何回显输入值?
我们就用读取输入流的方式进行读取,还是看API,InputStream包含方法read();read(byte[] b);read(byte[] b, int off, int len) ;
1、read();
看API,从输入流中读取数据的下一个字节。返回 0 到 255 范围内的 int 字节值。
返回一个整型字节数据,该数据表示的是字节,因此是Unicode的第一个字节或是字符的ASCII码值。该方法是从一个流中一个一个的读取数据,因此是一个迭代的过程。
如果到达流的末尾,则返回 -1
重复调用System.in.read()实际上是在遍历该流中的每一个字节数据。最常见的流是键盘输入流。以下为例:
运行结果:import java.io.IOException; public class Test5 { public static void main(String[] args) throws IOException { System.out.println("请输入:"); int i = 0; while(i!=-1){//读取输入流中的字节直到流的末尾返回-1 i = System.in.read(); System.out.print(i); System.out.print((char)i); } } }
如图我们输入abcd则返回了其ASCII值 ,这里的13和10是读取的键盘的回车,我们在键盘上按一下回车键,实际上读取到的是两个字符,即“\r”(13)和“\n”(10)。字符“\r”表示回车,即光标回到当前行的行首而不换行(为什么好像换行了?,而cmd中运行的结果则没有换行,替换了行首的两个字符?);字符“\n”表示换行,即光标移到当前行的下一行行首。比较好的解决办法是在使用换行之前,程序中获取系统环境的换行符号,在用获取的参数执行换行。
(这里还要PS下回车的来历:
回车 \r 本义是光标重新回到本行开头,r的英文return,控制字符可以写成CR,即Carriage Return
换行 \n 本义是光标往下一行(不一定到下一行行首),n的英文newline,控制字符可以写成LF,即Line Feed
在计算机还没有出现之前,有一种叫做电传打字机(Teletype Model 33)的玩意,每秒钟可以打10个字符。但是它有一个问题,就是打完一行换行的时候,要用去0.2秒,正好可以打两个字符。要是在这0.2秒里面,又有新的字符传过来,那么这个字符将丢失。
后来,计算机发明了,这两个概念也就被般到了计算机上。那时,存储器很贵,一些科学家认为在每行结尾加两个字符太浪费了,加一个就可以。于是,就出现了分歧。
/*======================================*/
\n: UNIX 系统行末结束符
\n\r: window 系统行末结束符
\r: MAC OS 系统行末结束符
/*======================================*/
一个直接后果是,Unix/Mac系统下的文件在Windows里打开的话,所有文字会变成一行;而Windows里的文件在Unix/Mac下打开的话,在每行的结尾可能会多出一个^M符号,有兴趣可以去查看相关资料)
这里有个小情况,看例子运行结果:import java.io.IOException; public class Test6 { public static void main(String[] args) throws IOException { System.out.println("请输入:"); char ch = (char)System.in.read(); System.out.println("hello" + ch + "AB"); } }
很明显这是只读取一个字符,然后插入到字符串“hello”和“AB”之间,我们无论输入1个字符还是多个字符都是只读取第一个字符,如输入“UFO”
打印出“helloUAB”但我们不输入任何值只按一个回车时,在cmd命令窗口与IDE工具(eclipse)输出就不一样了,如下图:
运行结果:
也就是说cmd回车(获得两个字符“\r”和“\n”),只读取了第一个字符“\r”,即将光标放到行首不换行,所以导致了覆盖hello,那为什么在IDE工具(eclipse)下也是只读取了回车,却换行了呢,这应该是eclipse将回车与换行是连用的,无论是System.out.print("a\rb");System.out.print("a\nb");还是System.out.print("a\r\nb");输出都是a回车b.此句系本人杜撰,大家可以亲试核实。(Really?)。好了read()就说这些了。
2、read(byte[] b)
API里 InputStream的read(byte[] b)同样是返回一个int值,但此处的用法跟read()完全不一样。此处是从输入流中读取一定数量的字节,并将其存储在缓冲区数组b
中。以整数形式返回实际读取的字节数。如果
b
的长度为 0,则不读取任何字节并返回0
;否则,尝试读取至少一个字节。如果因为流位于文件末尾而没有可用的字节,则返回值-1
;否则,至少读取一个字节并将其存储在b
中。将读取的第一个字节存储在元素
b[0]
中,下一个存储在b[1]
中,依次类推。读取的字节数最多等于b
的长度。设 k 为实际读取的字节数;这些字节将存储在b[0]
到b[
k-1]
的元素中,不影响b[
k]
到b[b.length-1]
的元素。此时我们有了byte数组,有了返回的字节数,就可以用String的构造方法String(byte[] bytes, int offset, int length)
: 通过使用平台的默认字符集解码指定的 byte 子数组,构造一个新的String
。即可返回键盘的输入。 示例如下public class Test7 { public static void main(String[] args) { try{ //提示信息 System.out.println("请输入:"); //数组缓冲 byte[] b = new byte[1024]; //读取数据 int n = System.in.read(b); //转换为字符串 String s = new String(b,0,n); //回显内容 System.out.println("输入内容为:" + s); }catch(Exception e){} } }
这里斟酌通过使用平台的默认字符集解码指定的 byte 子数组,构造一个新的
String
。这句话,我亲自核实,此Test7.Java的编码格式为utf-8非本地默认编码格式时(我们国内的电脑默认编码GB2312),输出到控制台会是乱码,而改为GB2312时则正确输出,已证实。还有一句...不影响
b[
k]
到b[b.length-1]
的元素。我们也可以试试,将以上代码略作改动如下:public class Test8{ public static void main(String[] args) { try{ //提示信息 System.out.println("请输入:"); //数组缓冲 byte[] b = new byte[1024]; //假设我们已知道键盘输入的是ABCD加回车是6个字符,我们将b[0]到b[9]分别提前赋值 for (int i = 0; i < 10; i++) {//这里b[0]-b[9]分别赋值a-j b[i] = (byte)(97+i);//a的ascii值为97 } //读取数据 int n = System.in.read(b); //转换为字符串 //这次我们构造String为了输出byte数组的所有值,所以不再用返回的值n而用我们预知的10个 String s = new String(b,0,10); //回显内容 System.out.println("输入内容为:" + s); }catch(Exception e){} } }
很明显原本应该输出的是abcdefghij经过读取键盘输入后,影响了n=6个字节,4个字母加回车的“\r”“\n”此句也证实了回车确实是两个字符。
3、read(byte[] b, int off, int len)
和上面类似但读取的长度,位置做了限定API如下:
将输入流中最多 len 个数据字节读入 byte 数组。尝试读取 len 个字节,但读取的字节也可能小于该值。以整数形式返回实际读取的字节数
将读取的第一个字节存储在元素 b[off] 中,下一个存储在 b[off+1] 中,依次类推。读取的字节数最多等于len。设k 为实际读取的字节数;这些字节将存储在 b[off] 到 b[off+k-1] 的元素中,不影响b[off+k] 到b[off+len-1] 的元素。
同样拿上面的abcdefghij做个例子仅仅将上一个例子的int n = System.in.read(b);改为int n = System.in.read(b,3,4);则意思的,将读取的第一个字节存储在b[3]依次最多存储4个,这时我们再次运行,还是输入abcd,刚好4个,错还有回车两个,输出的控制台是:
很明显b[3],b[4],b[5],b[6]处的字符defg被读入的4个字符覆盖,而且是只读取了4个,没有回车。
本来是要说JAVA从控制台输入数据的方法,结果扯了这么多,现在说说第二个方法:二、java.util.Scanner
从 JDK 5.0 开始,基本类库中增加了java.util.Scanner类,使用它的Scanner(InputStream source)构造方法,可以传入系统的输入流System.in而从控制台中读取数据。当通过new Scanner(System.in)创建一个Scanner,控制台会一直等待输入,直到敲回车键结束,把所输入的内容传给Scanner,作为扫描对象。如果要获取输入的内容,则只需要调用Scanner的nextLine()方法即可。
关键说下Scanner的next(),nextLine();import java.util.Scanner; public class TestSc { public static void main(String[] args) { System.out.println("请输入:"); Scanner sc = new Scanner(System.in); System.out.println(sc.nextLine()); } }
1、nextLine()
API此扫描器执行当前行,并返回跳过的输入信息。 此方法返回当前行的其余部分,不包括结尾处的行分隔符。意思就是读出当前下标的一行,对它来讲每行就是他的分隔符。如上例子中的末尾,比较来说此方法更简洁实用,其实是Scanner对System.in做了封装处理。
2、next();
查找并返回来自此扫描器的下一个完整标记。完整标记的前后是与分隔模式匹配的输入信息,Scanner默认使用空格作为分割符来分隔文本,但允许你指定新的分隔符如下例:
import java.util.Scanner; public class TestSc1 { public static void main(String[] args) { Scanner sc = new Scanner("aa bb cc,dd,ee"); //sc.useDelimiter(","); while (sc.hasNext()) { System.out.println(sc.next()); } } }
将空格做分隔符输出结果为:
将注释去掉,试用逗号做分隔符输出为:
三、java.io.Console
从 JDK 6.0 开始,基本类库中增加了java.io.Console类,用于获得与当前 Java 虚拟机关联的基于字符的控制台设备。在纯字符的控制台界面下,可以更加方便地读取数据。值得一提的是此方法只是在cmd命令窗口可以得到Console实例顺利运行,在IDE工具下如eclipse下得不到Console实例的,cs==null无法实现输入。import java.io.Console; public class TestCo { public static void main(String[] args) { System.out.println("请输入"); Console cs = System.console(); if (cs == null) { throw new IllegalStateException("不能使用控制台"); } System.out.println(cs.readLine()); } }
API中解释:
虚拟机是否具有控制台取决于底层平台,还取决于调用虚拟机的方式。如果虚拟机从一个交互式命令行开始启动,且没有重定向标准输入和输出流,那么其控制台将存在,并且通常连接到键盘并从虚拟机启动的地方显示。如果虚拟机是自动启动的(例如,由后台作业调度程序启动),那么它通常没有控制台。
如果此虚拟机具有控制台,那么它将由此类唯一的实例(可通过调用 System.console() 方法获得)表示。如果没有可用的控制台设备,那么对该方法的调用将返回null。
所以在 IDE 的环境下,重新定向了标准输入和输出流,也是就是将系统控制台上的输入输出重定向到了 IDE 的控制台中。因此,在 IDE 中不能使用这个程序。
四、BufferedReader
其实也是用的IO流。
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class TestBuffer { public static void main(String[] args) { try { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String s = br.readLine(); System.out.println(s); } catch (IOException e) { e.printStackTrace(); } } }
readLine():读取一个文本行。
参考资料
[1]. 《 详细整理Java 从命令控制台输入数据读取输入流System.in的几种常用方法》,author: Wallbanger