Java控制台是一种图形用户界面(GUI),其作用有二:一是输入数据,二是显示输出结果。通过在控制台的输入输出,就使用户能完成配置、监视、维护和故障排除等工作。 Java控制台虚拟机是否具有控制台取决于底层平台,还取决于调用虚拟机的方式。如果虚拟机从一个交互式命令行开始启动,且没有重定向标准输入和输出流,那么其控制台将存在,并且通常连接到键盘并从虚拟机启动的地方显示。如果虚拟机是自动启动的,例如由后台作业调度程序启动,那么它通常没有控制台。 因此,对于普通微机里的java虚拟机来说,通常控制台就是指的显示器和键盘,即:用显示器输出结果、用键盘输入数据。由于输出相对比较简单,这里主要讨论输入数据的方式。 从控制台输入数据的方式有多种,jdk1.4以前主要是使用标准输入流的方式,jdk5.0增加了使用Scanner类的方式,jdk6.0又增加了使用Console类的方式。 7.8.1 使用标准输入流的方式 标准输入流在7.6.2中已经讨论过,例7-13和例7-14的数据就是从控制台(键盘)输入的,办法很简单,即使用标准输入流System.in获得。也可以将System.in桥接至字符流,从字符流中读入数据。 例7-22文件复制程序,本例中先使用InputStreamReader获取标准输入流System.in,再用字符流BufferedReader包装InputStreamReader,以读取源文件名和目标文件名。 1: | import java.io.*; | 2: | public class Example7_22 { | 3: | public static void main(String[ ] args) { | 4: | try { | 5: | BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); | 6: | String srcFile = null, dstFile = null; | 7: | System.out.print("请输入源文件名:"); | 8: | srcFile = br.readLine(); //用缓冲字符流获得源文件名 | 9: | System.out.print("请输入目标文件名:"); | 10: | dstFile = br.readLine(); //用缓冲字符流获得目标文件名 | 11: | byte[ ] data=new byte[1]; | 12: | BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile)); | 13: | BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(dstFile)); | 14: | ad(data) != -1) bos.write(data); | 15: | bos.flush(); | 16: | System.out.println("文件复制成功!"); | 17: | bis.close(); | 18: | bos.close(); | 19: | }catch(IOException e){ | 20: | e.printStackTrace(); | 21: | } | 22: | } | 23: | } | | |
可见利用标准输入流进行控制台输入的方式非常麻烦:如果从键盘获取的是字符串,需要采用BufferedReader类来进行处理;如果获取的是其他类型数据,需要手工进行转换;在读取的过程中还必须捕获IOException。不过这是JDK1.4及更早版本中从控制台读取数据唯一办法。 7.8.2 使用Scanner类的方式 从JDK5.0开始,java.util包中增加了Scanner类,它是一个可以使用正则表达式来解析基本类型和字符串的简单文本扫描器。Scanner类从字面上讲是“扫描”的意思,它把给定的字符串解析成Java的各种基本数据类型,用于分解字符串的默认的分隔符是空格,也可以定制。其构造方法如表7-15。 表7-15 Scanner类的构造方法 方法 | 描述 | Scanner(File source) Scanner(File source, String charsetName) | 构造一个新的 Scanner,其值是从指定文件扫描获得。后者指定了字符集。 | Scanner(InputStream source) Scanner(InputStream source, String charsetName) | 构造一个新的 Scanner,其值是从指定的输入流扫描获得。后者指定了字符集。 | Scanner(Readable source) | 构造一个新的 Scanner,其值是从指定源扫描获得。 | Scanner(ReadableByteChannel source) Scanner(ReadableByteChannel source, String charsetName) | 构造一个新的 Scanner,其值是从指定信道扫描获得。后者指定了字符集。 | Scanner(String source) | 构造一个新的 Scanner,其值是从指定字符串扫描获得。 | | |
由表7-15可见,Scanner可以从指定的文件、输入流、源、信道或字符串等多种来源构造获得。要从控制台读取数据,使用参数为InputStream的方法,传递标准输入流System.in给它,此时Scanner扫描该输入流中的字符,同时还可以让读入的字符串匹配一定的正则表达式模式,如果不匹配时将抛出InputMismatchException异常。 Scanner封装了很多方法,比如方法next就可以从各种输入流中连续读入字符串。不仅如此,它还可以读入除char之外的其他七种基本类型和两个大数字类型,并且不需要手工进行转换,如方法nextInt将读入的下一数据转换为int变量,方法nextByte将读入的下一数据转换为byte变量,方法nextLine将读入的下一行转换为String变量,等等。 例7-23文件复制程序,本例中使用Scanner类读取源文件名和目标文件名。 1: | import java.io.*; | 2: | import java.util.*; | 3: | public class Example7_23 { | 4: | public static void main(String[ ] args) { | 5: | String srcFile = null, dstFile = null; | 6: | Scanner scanner = new Scanner(System.in); | 7: | System.out.print("请输入源文件名:"); | 8: | srcFile = Line(); | 9: | System.out.print("请输入目标文件名:"); | 10: | dstFile = Line(); | 11: | try { | 12: | byte[ ] data=new byte[1]; | 13: | BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile)); | 14: | BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(dstFile)); | 15: | ad(data) != -1) bos.write(data); | 16: | bos.flush(); | 17: | System.out.println("文件复制成功!"); | 18: | bis.close(); | 19: | bos.close(); | 20: | }catch(IOException e){ | 21: | e.printStackTrace(); | 22: | } | 23: | } | 24: | } | | |
可见从键盘读取的代码得到了很大简化,并且不必再捕获或声明IOException,但这并不是Scanner将控制台输入简单化了,而是在其内部的实现中对各种输入字符的处理进行了更高层次的封装,同时已经把IOException处理了。 例7-24通过控制台输入多个正整数,计算奇数和与偶数和。 1: | import java.util.*; | 2: | public class Example7_24 { | 3: | public static void main(String[] args) { | 4: | int i, odd = 0, even = 0; | 5: | System.out.print("输入正整数,以空格或回车分开,输入非数字结束输入:"); | 6: | Scanner scanner = new Scanner(System.in); | 7: | while(scanner.hasNextInt()){ | 8: | i = Int(); | 9: | if(I % 2 != 0) odd = odd + i; | 10: | else even = even + i; | 11: | } | 12: | Scanner.close(); | 13: | System.out.println("奇数和是:" + odd + "\n偶数和是:" + even); | 14: | } | 15: | } | | |
运行结果为: 输入正整数,以空格或回车分开,最后输入一个非数字结束输入操作: 3 4 5 6 7 22 3 f 奇数和是:18 偶数和是:32 7.8.3 使用Console类的方式 从JDK6.0开始,又在java.io包中增加了一个Console类,该类用于获得与当前Java虚拟机关联的基于字符的控制台设备。在纯字符的控制台界面下,可以更加方便的读入数据。先来看使用Console类的获取源文件名和目标文件名的文件复制程序。 例7-25文件复制程序,本例中使用Console类读取源文件名和目标文件名。 1: | import java.io.*; | 2: | public class Example7_23 { | 3: | public static void main(String[ ] args) { | 4: | String srcFile = null, dstFile = null; | 5: | Console console = sole(); | 6: | if (console == null) System.out.println( "不可获得控制台!" ); | 7: | else{ | 8: | srcFile = adLine("请输入源文件名:"); | 9: | dstFile = adLine("请输入目标文件名:"); | 10: | …… //与例7-23中11~22行相同 | 11: | } | 12: | } | 13: | } | | |
以前输入数据前的提示信息需要使用System.out.print();来输出,但是使用Console类,可以在方法参数中直接放入提示信息,这进一步简化了代码。 Console类直接继承自Object类,使用默认的构造方法,它提供的方法不多,表7-16中列出了Console类的方法。 表7-16 Console类的方法 方法 | 描述 | Reader reader() | 获取与此控制台关联的唯一Reader对象。 | String readLine() String readLine(String fmt, args) | 从控制台读取单行文本。后者提供一个格式化提示。 | char[] readPassword() char[] readPassword(String fmt, args) | 从控制台读取密码,禁用回显。后者提供一个格式化提示。 | PrintWriter writer() | 获取与此控制台关联的唯一PrintWriter对象。 | Console printf(String format, args) | 使用指定格式字符串和参数将格式化字符串写入此控制台输出流。 | Console format(String fmt, args) | 使用指定格式字符串和参数将格式化字符串写入此控制台的输出流中。 | void flush() | 刷新控制台,并强制立即写入所有缓冲的输出。 | | |
这些方法中,readPassword是一个很重要的方法,它可以不回显输入密码,这对于需要在控制台中输入密码等敏感信息是一种很好的方式。 例7-26使用Console类的readPassword方法读取密码信息。 1: | import java.io.*; | 2: | public class Example7_26 { | 3: | public static void main(String[ ] args) { | 4: | Console console = sole(); | 5: | if( console == null ) System.out.println( "不可获得控制台!" ); | 6: | else { | 7: | String user = adLine("请输入用户名:" ); | 8: | String pwd = new adPassword("请输入密码:")); | 9: | console.printf( "用户名是: %s\n", user); | 10: | console.printf( "密码是: %s\n", pwd); | 11: | pwd = ""; //密码清零! | 密码字符串是什么12: | console.printf( "密码使用后应清零!清零后密码为: %s\n", pwd); | 13: | } | 14: | } | 15: | } | | |
在命令行方式下,程序运行结果: 请输入用户名:fg 请输入密码: 用户名是: fg 密码是: ghj 密码使用后应清零!清零后密码为: 可见输入密码时,控制台是不显示这些密码字符的,但是程序可以得到输入的密码字符串。方法readPassword返回的是char数组,程序第6、7行将其转换为字符串。在密码被存储到pwd之后,就可以按需使用了。但出于安全性考虑,在不需要使用密码的时候,应该将pwd清零,如程序中第11行所作的那样。 但正如7.8节一开始就指出的那样,根据Console类的API文档说明,虚拟机是否具有控制台取决于底层平台,还取决于调用虚拟机的方式。如果虚拟机从一个交互式命令行开始启动,且没有重定向标准输入和输出流,那么其控制台将存在,并且通常连接到键盘并从虚拟机启动的地方显示。如果虚拟机是自动启动的,例如由后台作业调度程序启动,那么它通常没有控制台。 简单地说,前边两个例子的程序如果在NetBean或者Eclipse等IDE环境中运行,是无法获取到Console实例的。这是由于IDE的环境下,重新定向了标准输入和输出流,也是就是将系统控制台上的输入输出重定向到了IDE的控制台中。此时程序运行的结果将是: 不可获得控制台! 也就是说,在IDE中不能使用Console来输入数据,只能在命令行中使用Console输入数据。而标准输入流和Scanner类不受这一限制。 相比之下,标准输入流作为一种最基本的从控制台输入数据的方法,需要程序员做的工作比较多,比如要进行包装、转换、捕获异常等;使用Scanner能方便地输入包括字符串类型在内的多种其他数据类型,简化了很多工作;使用Console最简单,还能安全、方便地输入密码之类的敏感数据,缺点是只能在命令行方式下使用。 |
发表评论