为什么 Java 要有输入输出流?我们先来看看用现有手段写一个简单的输入输出程序:
package big;
import java.util.Scanner;
public class Simlpe_input_and_output {
public static void main(String[] args) {
// TODO 自动生成的方法存根
Scanner read = new Scanner (System.in);
System.out.println("请输入你想输入的内容:");
String a = read.next();
System.out.println("接下来打印这句话:");
System.out.println(a);
}
}
你看看这多麻烦,要想从键盘上输入还得先导入 util
包里的 Scanner
类,在一开始学 Java 的时候差点因为这个导致厌学(C语言直接 scanf
、printf
不好吗?为啥 Java还得三行代码?) 。
接下来还是看看课本上的介绍吧:Java 语言的输入输出功能是十分强大而灵活的(说实话,我现在都不信),对于数据的输入和输出操作以 “ 流 ”(stream)的方式进行。JDK 提供了各种各样的 “ 流 ” 类,用以获取不同种类的数据,它们都定义在 java.io
包中。程序中通过标准的方法输入或输出数据。
嗯~~那下面我们就来看看这 Java 输入输出流到底咋回事吧,这里主要还是对课本中的示例代码进行复现来学习的,所以课本看不懂的小伙伴可以试试我的这篇学习笔记。
标准输入输出功能是通过 Java 的 System
系统类来实现的。System
类在 java.lang
包中,是一个最终类,里面的大多数方法都是静态方法,所以在程序中可以直接调用它们(包括标准输入输出)。
什么?不知道 java.lang 是啥?老师上课讲过的,language 嘛,语言的缩写。java.lang包是java语言的核心,它提供了java中的基础类。包括基本Object类、Class类、String类、基本类型的包装类、基本的数学类等等最基本的类。也就是说,一个 Java 程序必备 java.lang 包(默认就存在的吧,我也没见课本示例程序里写啊,是吧?)
System.in
作为 InputStream
类的对象实现标准输入,可以调用它的 read 方法(有以下三种格式)来读取键盘数据:
输入流结束,返回 -1。
System.out
作为 PrintStream
打印流类的对象实现标准输出,可以调用它的 ptint
、println
或 write
方法来输出各种类型的数据。
print
和 println
都不用介绍了,目前用的最多的代码就是写这个的。还是说一下 wtite
方法吧,它用来输出字节数组,注意一点:write
方法在输出单个字节时并不能立即显示出来,还需调用 flush
或 close
方法来强制回调(??不懂)。
package big;
import java.io.IOException;
public class exp8_3_simple_inputoutput {
public static void main(String[] args) throws java.io.IOException {
//这里必须继承 IOException 异常,因为输入时可能会发生 I/O 错误
byte buffer[] = new byte[40];
System.out.println("从键盘上输入不超过40个字符,按回车键结束输入:");
int count = System.in.read(buffer); //读取标准输入流
System.out.println("保存在缓冲区的元素个数为" + count);
System.out.println("输出 buffer 元素值:");
for (int i = 0; i < count; i ++) {
System.out.print(" " + buffer[i]);
}
System.out.println();
System.out.println("输出 buffer 字符元素:");
System.out.write(buffer, 0, buffer.length);
}
}
输出如下:
下面做个小实验,对该程序做几点改变,看看有什么变化:
write
方法中最后一个参数不应该是 buffer.length-1
吗?结果没有任何改变,看来没什么好说的了,我认为 Java 不严谨,支持我吗,同志们?buffer
元素值时看到的是 ASCII 码,我要看我输入的字母。简单,安排!这只是在 for 循环里面的输入加上一个强制转换符就行了System.out.print(" " + (char)buffer[i]);
。问题是,多了两行空格,这怎么解释?如此简单一个程序,我以为搞懂了,可是回看一下课本上的说明,还是有问题滴:
read(byte[] b)
方法,因其会产生输入异常,可以放在 try···catch
块中执行,或令 main
方法将异常上交(即在声明语句中加入 throws java.io.IOException
),这样才能通过编译。write
方法直接输出了字节数组的内容,如果使用 println
方法可将字节数组的内容转换为字符串,否则不能正常显示。于是把System.out.write(buffer, 0, buffer.length)
改成了下面这样,但不行耶,老师怎么把字节数组里的内容转换成字符串啊?(我做了什么?最后为什么会有好多空格和 null 这几个字符输出?) :String str = null; //如果不赋初始值 null 的话,将会报错,原因是局部变量 str 尚未初始化
for (int i = 0; i < count; i ++) {
str += (char)buffer[i];
}
System.out.println(str);
PS:这个问题已得到解决,后面的程序(第三小节输入输出流类的应用里最后一个实验)中会把字节数组转换成字符串,这里写好了就不动了吧,主要是懒,哈哈~~
课本上哇哇哇说了一大堆,实在没什么可讲的,就直接放图吧:
数据流以输入流的形式被程序获取,再以输出流的形式将数据输出到其它设备。图 1 为输入流模式,图 2 为输出流模式:
输入输出流总框架图:
最后提一下关于输入输出流的分类,了解即可:
数据流是 Java 进行 I/O 操作的对象,它按照不同的标准可以分为不同的类别。
例 8.4:创建两个 File 类的对象,分别判断两个文件是否存在;如果不存在,则新建。然后从键盘输入字符数据存入数组 b 里,通过文件输出流,把数组里的字符写入到 hello1.txt
文件,再从 hello1.txt
中读取数据,写到文件 hello2.txt
中。以下是源代码。
package big;
import java.io.*;
public class stream_exp8_4 {
public static void main(String[] args) {
int len;byte b[] = new byte[20]; //定义一个长度为20的字节型数组
//创建文件对象(这里只是在程序中实例出对象,其实并不存在)
File file1 = new File("D:\\学校课程学习\\java程序设计\\eclipse\\工作空间\\hello1.txt");
File file2 = new File("D:\\学校课程学习\\java程序设计\\eclipse\\工作空间\\hello2.txt");
FileOutputStream fos = null; //先定义,后面会在下面字节流应用中再实例化对象(new)的
try { //如果文件不存在,就创建对象(经过这里的操作才是真的在 java 文件夹里创建了文件)
if (!file1.exists())
file1.createNewFile();
if (!file2.exists());
file2.createNewFile();
//字节流应用
System.out.println("请输入你想存入file1文件的内容:");
len = System.in.read(b); //从键盘输入的字符存入内存的 b 数组里的个数(一边实现了键盘输入,一遍统计了字符个数)
fos = new FileOutputStream(file1, true); //创建文件输出流到到 file1
fos.write(b, 0, len); //通过文件输出流 fos 把 b 数组中数据写入到 file1 文件尾部
//字符流与缓冲流应用
FileReader in = new FileReader(file1); //打开字符文件输入流
BufferedReader bin = new BufferedReader(in); //传送数据到缓冲区(这一句代码就是装饰流,作用是加速读的速度)
FileWriter out = new FileWriter(file2, true); //打开字符文件输出流
String str;
System.out.println("完美复制到file2文件中的内容:");
while ((str = bin.readLine()) != null) { //将缓冲区中数据一行一行地送到字符串变量字符串变量 str 中
System.out.println(str); //输出 str 中数据到控制台
out.write(str+"\n"); //将 str 中数据写入文件
}
out.close();in.close();fos.close(); //关闭流资源
}
catch(IOException e) {e.printStackTrace();}
}
}
运行结果如下:
看着没问题是吧,再试试重复运行输入一遍。由此我们会发现打印在控制台中的有两个 “ 相信中国!” ,打开文件后发现 hello1.txt
里有两个 “ 相信中国!” ,文件 hello2.txt
里有三个 “ 相信中国!” 。仔细想一想,也就该是这样哈!
OK,接下来,我们再来做些实验,首先有一个问题是装饰类只是加快了读的速度,其实没它照样可以完成任务,我们这样来改写程序:
FileReader in = new FileReader(file1);
//BufferedReader bin = new BufferedReader(in); //删掉装饰流
FileWriter out = new FileWriter(file2, true);
for (int i = 0; i < len; i ++) {
System.out.print((char)b[i]);
out.write(b[i]);
}
这样一来,运行结果会变成怎样呢?结果是识别不了汉字了(英文倒是可以),另外就是不管运行几遍,控制台打印的都只会是本次运行时键盘输入的内容,文本文件中也只会增加本次输入的内容,大家可以对比着来看看:
不明白,为啥啊???
再来一个实验:在示例中,我们输出流用的是字节流,输出流用的是字符流,统一一下不好吗?我先来试试统一字节流输入输出好了。
终于,我成功了,就像写高考数学题,写的时候真难,写完了发现好简单
package big;
import java.io.*;
public class exp8_4_zijieliushurushuchu {
public static void main(String[] args) {
// TODO Auto-generated method stub
int len;byte b[] = new byte[20];
File file1 = new File("D:\\学校课程学习\\java程序设计\\eclipse\\工作空间\\hello1.txt");
File file2 = new File("D:\\学校课程学习\\java程序设计\\eclipse\\工作空间\\hello2.txt");
FileOutputStream fos = null;
try {
if (!file1.exists())
file1.createNewFile();
if (!file2.exists());
file2.createNewFile();
System.out.println("请输入你想存入file1文件的内容:");
len = System.in.read(b);
fos = new FileOutputStream(file1, true);
fos.write(b, 0, len);
FileInputStream in = new FileInputStream(file1);
BufferedInputStream bin = new BufferedInputStream(in);
FileOutputStream out = new FileOutputStream(file2, true);
// for (int i = 0; i < len; i ++) {
// out.write(b[i]);
// }
out.write(b); //上面三行 for 循环其实就是这一句话的事情,算是了解一下 write 方法吧
String str = new String(b);
System.out.println(str);
out.close();in.close();fos.close();
}
catch(IOException e) {
e.printStackTrace();
}
}
}
就在这个程序里,我把字节数组转换成了字符串,顺利地在控制台(屏幕)上打印出来了(这也是为什么课上有同学问 “ 为什么可以用字节数组输出汉字 ” 问题的答案了,都转换成了字符串,那为什么还不能输出汉语呢,是吧?)。大家看那输入输出流框架图都是一愣一愣的,不过呢,这里有篇文章很好的总结了字节输入输出流的框架和方法,我就是照着这篇文章完成这个实验的,分享一下。
???还有统一字符输入输出流???再说吧,累了。
课上老师讲:RandomAccessFile
类是一个随机存取文件类。虽然不明白这句话什么意思,但下一句确实醍醐灌顶:这个类可以实现 Java 输入输出流的任意操作。我了个乖乖,相比较框架图里那么多类,这样子只用一个类就方便多了呀!
例 8.5 :通过 RandomAccessFile
类从文件的不同位置读写不同长度的数据,讲字符串数据添加在文件尾部。
package big;
import java.io.*;
public class exp8_5 {
public static void main(String[] args) {
String str[] = {"First lint\n", "second line\n", "Last line\n"};
try {
//用 RandomAccessFile 类实例化出 rf 对象,参数表示可以对文件 demo.txt 进行读写操作
RandomAccessFile rf = new RandomAccessFile("demo.txt", "rw");
System.out.println("\n光标的位置为:" + rf.getFilePointer()); //获取文件指针位置方法
System.out.println("文件的长度为:" + rf.length());
rf.seek(rf.length()); //把光标移到文件末尾(用的是 seek 方法)
System.out.println("光标现在的位置为:" + rf.getFilePointer());
for (int i = 0; i < 3; i ++)
rf.writeBytes(str[i]); //将字符串数组 str 里的三个字符串依次写入 rf 对象中
rf.seek(0); //把光标移到文件起始位置
System.out.println("\n文件现在的内容:");
String s;
while ((s = rf.readLine()) != null) //分行读取
System.out.println(s); //分行打印
rf.close(); //关闭流资源
}
catch (FileNotFoundException fnoe) {}
catch (IOException ioe) {}
}
}
以下摘自课本中的说明:
怎么说?其实没什么,我打一遍代码试着运行也是为了看看到底有多方便,也确实比原始输入输出流分开写简单许多。
其实还有一个小节讲的是 “ 对象序列化与对象流类 ” ,课上老师好像也是一笔带过(是不是啊?我没有认真听这点内容,感觉还没讲就过去了,连示例程序都没啥印象)
我看课本上的小结讲的是使用输入输出流的参考步骤和流程,但我就 get 不到,没有感觉。看来还是对 Java 不熟悉,对输入输出流不熟悉( c++ 里不也是输入输出流 cin、cout 吗?感觉就像一个在天上,一个在地下。。。)
在最后我还是说一下输入输出和读写的关系吧,我上机的时候就把输入输出流的意思给搞反了。记住一点:输入输出都是以程序为参照物的
Java 中一切名词皆为类,一切类都要实例化对象,一切对象都有属性和方法,Java 就是靠着对象的属性和方法来完成编程解决问题的,这就是我学习 Java 以来的深刻体会,这就是面向对象编程!!!