输入与输出在Java里面相当基础,在Java各大书籍里面讲了又讲,但上面的概念往往讲得非常复杂,Java的老师强调学生必须透彻地弄得每一个类、每一个方法的意义,实际上,我们仅仅关注的是如何达到一个简单的输入输出效果。在网络上一个小小的Java输入输出包罗万象,主要是在JDK1.5推出了新型的Scanner输入,而以往的BufferedReader同样可以完成输入操作,也许多有经验的老手把自己使用惯的一套放上网络,根本不告诉别人怎么修改。下面举例子,彻底地说明白Java的输入输出,包括控制台,包括文件,其实根本就不难,完全是有迹可循。
一、基本目标
首先在C盘有个a.txt,里面存在一些内容
接着,我将写如下的一个Java小程序来说明Java的输入与输出,首先同时利用BufferReader与Scanner获取用户输入的一行东西,以说明两个方法的同理性,Scanner只不过是BufferReader的改进而已,其次利用Scanner获取用户输入的一堆东西,同时存放在一个动态数组ArrayList里面,方便以后操作,不是网上那种程序都不知道怎么操作这一堆东西。动态数组ArrayList不明白的,可以参考我之前写的《【Java】Java中的Collections类——Java中升级版的数据结构》(点击打开链接),最后把上面那个a.txt读取到Java中新型的字符串StringBuilder里面来,之后把字符串中的所有回车替换成空格,再输出到C盘的b.txt:
二、制作过程
1、首先是同时利用BufferReader与Scanner获取用户输入的一行东西,以说明两个方法的同理性,但做之前你必须在引入以下两个包:
import java.util.*; import java.io.*;
Scanner在util里面,BufferReader在io里面,而且下面的动态数组什么的都需要这些,之后才开始工作:
public static void inputOneLine() throws IOException{ //System.in实际上与System.out互相对应, //System.in是指用户在控制台的输入,System.out是值控制台的输出 System.out.print("请输入一行东西:"); String inputString=""; //Scanner对System.in进行读取,nextLine()读取一行,也就是读到第一个回车为止 inputString=new Scanner(System.in).nextLine(); System.out.println("你输入的是:"+inputString); System.out.print("请输入一行东西:"); //BufferedReader的readLine()同理 inputString=new BufferedReader(new InputStreamReader(System.in)).readLine(); System.out.println("你输入的是:"+inputString); }
(1)值得注意的是,如果扫描器Scanner还有一个next()方法,这个方法是读到第一个分隔符位置,也就是如果遇到Tab、空格、回车,Scanner就会停止读写,而缓冲区读者BufferedReader同样有一个read()方法,这个方法只能读取一个字符,读完就停止了。
(2)缓冲区读者BufferedReader必须对输入流进行操作,也就是System.in必须被转换成一个输入流才能被缓冲区读者操作,扫描器Scanner则不用,可以之前对System.in操作,所以说Scanner是进步的
(3)Scanner里面还有nextInt();等方法,它是指如果用户输入的东西是整形int,就能自动返回这个int,不用再处理,一般没有人使用这些方法,因为要增加程序的容错性、健壮性,把所有东西都认为是字符串读进来,再进行是否数的判断,再转换为一个数。
2、其次利用Scanner获取用户输入的一堆东西,同时存放在一个动态数组ArrayList里面,方便以后操作。
public static void inputMultiLine(){ //创建一个动态数组 ArrayList<String> inputStringArr=new ArrayList<String>(); System.out.println("请输入一堆东西,输入#结束"); Scanner scanner=new Scanner(System.in); //这个循环不断继续,直到读进来的字符串是"#"才打断 //由于Java中(好像不止是在Java,在不少语言都是),字符串是个对象,因此,只能使用equals方法判断 //==,!=编译不出错,程序不错,但照样跑,因为对象与对象的==的意思是判断这两个是否都是String对象,不是这样的! while(true){ String temp=scanner.nextLine(); //如果字符串不是什么都没有,不是# if(!temp.equals("")&&!temp.equals("#")){ //那么就把它压进动态数组 inputStringArr.add(temp); } //如果是#则打断循环 if(temp.equals("#")){ break; } } System.out.println("刚才,你输入的东西为"+inputStringArr); //如果动态数组存在第二个函数,则把它取出并打印出来 if(inputStringArr.size()>1){ System.out.println("你输入的第二行为:"+inputStringArr.get(1)); } }
3、最后把上面那个a.txt读取到Java中新型的字符串StringBuilder里面来,之后把字符串中的所有回车替换成空格,再输出到C盘的b.txt。
这里其实把扫描器要扫描的东西,从System.in改成一个文件就行了,这个方法由于设置到文件的输入输出,所以要跑出IO异常,至于缓冲区的读者写者在之前的《【Java】打印流与缓冲区读者完成输入与输出到文件操作》(点击打开链接)做过了,这里不做了。
public static void fileInputOutput() throws IOException{ //说明我要扫描c:\a.txt,在字符串的\必须转义成\ Scanner scanner=new Scanner(new File("c:\\a.txt")); StringBuilder filetoStringTemp=new StringBuilder(""); //如果还有下一行的话?hasNext()方法不会导致扫描器Scanner的游标向下跳,因此可以作为判断条件 //如果会向下跳就会出现隔行没有被读取的现象 while(scanner.hasNext()){ //StringBuilder没有重载运算符+=号,只能通过append()方法操作,同样的结果 filetoStringTemp.append(scanner.next()); //每读完一行,再补上一个换行,scanner不会把换行读进来 filetoStringTemp.append("\n"); } //把StringBuilder转换成String,同样也可以通过其toString实现。 String filetoString=new String(filetoStringTemp); System.out.println(filetoString); //把所有回车换成空格,注意的是replaceAll是返回一个String,必须用来替换原来的字符串, //它不是一个void()方法,弄完就完事了 filetoString=filetoString.replaceAll("\n", " "); System.out.println(filetoString); //判断这个字符串是否以"# "可以看到运行结果返回true,这方法在判断后缀名的时候很有用 System.out.println(filetoString.endsWith("# ")); //用打印流打印到文件,这里指定文件用文件写者来指定,这样就不会出现覆盖输出的问题了 PrintWriter printwriter=new PrintWriter(new FileWriter("c:\\b.txt",true)); //跟System.out.println()控制台输出方法有异曲同工之妙,只是我是输出到文件而已 //把下面这行代码倒过来看你就能理解 printwriter.println(filetoString); //必须关闭文件打印流才能完成一次输出,否则,打印的内容永远只在内存! printwriter.close(); System.out.println("已经把字符串filetoString输出到c盘的b.txt里面了。"); }
(1)开始先用JDK1.5之后的新型字符串StringBuilder来不停地接受从文件读过来的东西。这个东西比String好,坦诚String同样可以接受字符串,程序还更短:
String a=""; a+="";
但是这样据说会多出很多临时变量让Java回收,效率不高,遇到一些超长的字符串,最好使用JDK1.5之后的新型字符串StringBuilder,这东西效率高,此外JDK1.5还新增了StringBuffer字符串,但这东西由于里面有线程互斥保护机制,所以效率没StringBuilder高,但如果出现多线程操作同一个字符串,使用StringBuffer你就不用写一段线程互斥的代码,线程互斥是什么,之前在《【Java】线程并发、互斥与同步》(点击打开链接)已经说过了。
(2)字符串里面还有indexof与substring的实用方法,曾经在《【Java】截取字符串中的首个图片地址》(点击打开链接)做过了,不再赘述。
三、总结与展望
于是,整个程序如下,在主函数调用上面的三个方法就能够直接执行了,这些方法必须定义为静态方法,才能被这样调用:
import java.util.*; import java.io.*; public class ScannerTest { public static void inputOneLine() throws IOException { // System.in实际上与System.out互相对应, // System.in是指用户在控制台的输入,System.out是值控制台的输出 System.out.print("请输入一行东西:"); String inputString = ""; // Scanner对System.in进行读取,nextLine()读取一行,也就是读到第一个回车为止 inputString = new Scanner(System.in).nextLine(); System.out.println("你输入的是:" + inputString); System.out.print("请输入一行东西:"); // BufferedReader的readLine()同理 inputString = new BufferedReader(new InputStreamReader(System.in)) .readLine(); System.out.println("你输入的是:" + inputString); } public static void inputMultiLine() { // 创建一个动态数组 ArrayList<String> inputStringArr = new ArrayList<String>(); System.out.println("请输入一堆东西,输入#结束"); Scanner scanner = new Scanner(System.in); // 这个循环不断继续,直到读进来的字符串是"#"才打断 // 由于Java中(好像不止是在Java,在不少语言都是),字符串是个对象,因此,只能使用equals方法判断 // ==,!=编译不出错,程序不错,但照样跑,因为对象与对象的==的意思是判断这两个是否都是String对象,不是这样的! while (true) { String temp = scanner.nextLine(); // 如果字符串不是什么都没有,不是# if (!temp.equals("") && !temp.equals("#")) { // 那么就把它压进动态数组 inputStringArr.add(temp); } // 如果是#则打断循环 if (temp.equals("#")) { break; } } System.out.println("刚才,你输入的东西为" + inputStringArr); // 如果动态数组存在第二个函数,则把它取出并打印出来 if (inputStringArr.size() > 1) { System.out.println("你输入的第二行为:" + inputStringArr.get(1)); } } public static void fileInputOutput() throws IOException { // 说明我要扫描c:\a.txt,在字符串的\必须转义成\ Scanner scanner = new Scanner(new File("c:\\a.txt")); StringBuilder filetoStringTemp = new StringBuilder(""); // 如果还有下一行的话?hasNext()方法不会导致扫描器Scanner的游标向下跳,因此可以作为判断条件 // 如果会向下跳就会出现隔行没有被读取的现象 while (scanner.hasNext()) { // StringBuilder没有重载运算符+=号,只能通过append()方法操作,同样的结果 filetoStringTemp.append(scanner.next()); // 每读完一行,再补上一个换行,scanner不会把换行读进来 filetoStringTemp.append("\n"); } // 把StringBuilder转换成String,同样也可以通过其toString实现。 String filetoString = new String(filetoStringTemp); System.out.println(filetoString); // 把所有回车换成空格,注意的是replaceAll是返回一个String,必须用来替换原来的字符串, // 它不是一个void()方法,弄完就完事了 filetoString = filetoString.replaceAll("\n", " "); System.out.println(filetoString); // 判断这个字符串是否以"# "可以看到运行结果返回true,这方法在判断后缀名的时候很有用 System.out.println(filetoString.endsWith("# ")); // 用打印流打印到文件,这里指定文件用文件写者来指定,这样就不会出现覆盖输出的问题了 PrintWriter printwriter = new PrintWriter(new FileWriter("c:\\b.txt", true)); // 跟System.out.println()控制台输出方法有异曲同工之妙,只是我是输出到文件而已 // 把下面这行代码倒过来看你就能理解 printwriter.println(filetoString); // 必须关闭文件打印流才能完成一次输出,否则,打印的内容永远只在内存! printwriter.close(); System.out.println("已经把字符串filetoString输出到c盘的b.txt里面了。"); } public static void main(String[] args) throws IOException { inputOneLine(); inputMultiLine(); fileInputOutput(); } }