本章将会依次介绍以下几个部分
目录
输入输出流的概念
Java中,将输入和输出都抽象为信息的流动
输入流:信息从程序空间之外的别的地方流入程序空间里面
输出流:将程序空间中的数据送到程序空间之外的其他地方
预定义的I/O流类
从流的方向划分
输入流
输出流
从流的分工划分
节点流:真正去访问文件,进行输入输出操作的流
处理流:在节点流的基础之上,对信息进行加工转换处理的流
从流的内容划分
java.io包的顶级层次结构
面向字符的流:专门用于字符数据
- 源或目标通常是文本文件
- 实现内部格式和文本文件中的外部格式之间的转换
- 内部格式:16-bit char 数据类型
- 外部格式:
- UTF(Universal character set Transformation Format):很多人称之为“Universal Text Format”.
- 包括ASCII码及非ASCII码字符,比如:斯拉夫(Cyrillic)字符,希腊字符,亚洲字符等
面向字符的抽象流类——Reader和Writer
- java.io包中所有字符流的抽象超类
- Reader提供了输入字符的API
- Writer提供了输出字符的API
- 它们的子类又可分为两大类
- 节点流:从数据源读入数据或往目的地写出数据;
- 处理流:对数据执行某种处理
- 多数程序使用这两个抽象类的一系列子类来读入/写出文本信息
- 例如FileReader / FileWriter用来读 / 写文本文件
上图列出了部分面向字符的流,其中阴影部分为节点流,其他为处理流
面向字节的流:用于一般目的的输入输出
用来处理非文本数据的输入输出,大多数的数据都不是文本数据,比如一些图像、声音、视频等等
即使是有一些数据既可以存储为文本,又可以存储为二进制,那我们把这些数据存储为二进制时它的空间要节省很多,而且我们将数据从内存中送到内存以外的其他地方,比如文件中去,如果是以二进制的形式去存的话,时间上也会节省。所以有时候,如果我们要存的这个数据不是准备给人读的,而是准备给其他的程序继续处理的,这时候尽量把数据存为二进制的,这样存储的过程中或者说输出的过程中就会比较节省时间,在存储介质上也能够比较节省空间
面向字节的抽象流类——InputStream和OutputStream
- 是用来处理字节流的抽象基类,程序使用这两个类的子类来读写字节信息。
- 分为两部分
- 节点流
- 处理流
标准输入输出流对象
- System类的静态成员变量
- 包括
- System.in:InputStream类型的,代表标准输入流,默认状态对应于键盘输入。
- System.out:PrintStream类型的,代表标准输出流,默认状态对应于显示器输出。
- System.err:PrintStream类型的,代表标准错误信息输出流,默认状态对应于显示器输出。
文本文件的写入、读取
文本文件的写入
怎么创建一个文本文件,将信息写到文本文件中去?
看下面代码
package Week13.com.videolearn;
import java.io.FileWriter;
import java.io.IOException;
//创建文件并写入若干行文本
public class FileWriterTester {
public static void main(String[] args) throws IOException {
//main方法中声明抛出IO异常
String fileName = "Hello.txt";
FileWriter writer = new FileWriter(fileName);
writer.write("Hello!\n");
writer.write("This is my first text file,\n");
writer.write("You can see how this is done.\n");
writer.write("输入一行中文也可以\n");
writer.close();
}
}
//每次运行都会删除旧文件生成新文件
//换行在不同平台可能会有不同效果
/*
注意在该代码中:
1.用“\n”作为回车,会产生小黑格
2.没有对异常进行处理,仅仅抛出
3.没有实现追加方式写文件
* */
运行结果如下:
我们继续对代码进行修改
package Week13.com.videolearn;
import java.io.FileWriter;
import java.io.IOException;
public class FileWriterTester02 {
public static void main(String[] args) {
String fileName = "Hello.txt";
try {//将所有IO操作放入try块中
//true参数表示是追加
FileWriter writer = new FileWriter(fileName, true);
writer.write("Hello!\n");
writer.write("This is my first text file,\n");
writer.write("You can see how this is done.\n");
writer.write("输入一行中文也可以\n");
writer.close();
} catch (IOException e) {
System.out.println("Problem writing" + fileName);
}
}
}
/*
* 说明:
运行此程序,会发现在原文件内容后面又追加了重复的内容,
* 这就是将构造方法的第二个参数设为true的效果。
* 如果将文件属性改为只读属性,再运行本程序,就会出现IO错误,
* 程序将转入catch块中,给出出错信息。
*
*
*
* 如果我们要写入文件中的信息比较多,那么写的效率就是个问题
* 这时候我们可以用BufferedWriter类进行缓冲,能够提高写的效率
*
*
* */
运行结果如下:
BufferedWriter类
- FileWriter和BufferedWriter类都用于输出字符流,包含的方法几乎完全一样,但BufferedWriter多提供了一个newLine()方法用于换行,这个换行效果是跨平台的。
- 不同的系统对文字的换行方法不同。newLine0方法可以输出在当前计算机上正确的换行符。
下面代码演示利用BufferedWriter写文件
package Week13.com.videolearn;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class BufferedWriterTester {
public static void main(String[] args) throws IOException {
String fileName = "newHello.txt";
BufferedWriter out = new BufferedWriter(new FileWriter(fileName));
out.write("Hello!");
out.newLine();//换行,可以跨平台
out.write("This is another text file using BufferedWriter,");
out.newLine();
out.write("So I can use a common way to start a newline");
out.close();
}
}
运行结果如下:
文本文件的读取
读文本文件相关的类
- FileReader类
- 从文本文件中读取字符
- 继承自Reader抽象类的子类InputStreamReader
- BufferedReader类
- 读文本文件的缓冲器类
- 具有readLine0方法,可以对换行符进行鉴别,一行一行地读取输入流中的内容。
- 继承自Reader。
package Week13.com.videolearn;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
//读文本文件并显示
public class BufferedReaderTester {
public static void main(String[] args) {
String fileName = "Hello.txt", line;
try {
BufferedReader bufferedReader = new BufferedReader(new FileReader(fileName));
line = bufferedReader.readLine(); //读取一行的内容
while(line != null) {
System.out.println(line);
line = bufferedReader.readLine();
}
bufferedReader.close();
} catch (IOException e) {
System.out.println("Problem reading" + fileName);
}
}
}
/*
* 运行该程序,屏幕上将逐行显示出Hello.txt文件中的内容。
* FileReader对象:创建后将打开文件,如果文件不存在,会抛出一个IOException
* BufferedReader类的readLine()方法:从一个面向字符的输入流中读取一行文本。如果其中
* 不再有数据,返回null
Reader类的read()方法:也可用来判别文件结束。该方法返回的一个表示某个字符的int型整数,
* 如果读到文件末尾,返回 -1。据此,可修改本例中的读文件部分:
int c
* while((c = in.read())!= -1)
* System.out.print((char)c);
* close()方法:为了操作系统可以更为有效地利用有限的资源,应该在读取完毕后,调用该方收
*
*
* 思考:如何实现文本文件的复制呢?
* */
运行结果如下:
二进制文件的写入、读取
二进制文件的写入
抽象类OutputStream
- 派生类FileOutputStream
- 用于一般目的输出(非字符输出 ) ;
- 用于成组字节输出。
- 派生类DataOutputStream
- 具有写各种基本数据类型的方法;
- 将数据写到另一个输出流;
- 它在所有的计算机平台上使用同样的数据格式;
- 其中size方法,可作为计数器,统计写入的字节数。
例如将int数据写入二进制文件中去,代码如下:
package Week13.com.videolearn;
import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
//将三个int型数字255/0/-1写入数据文件data1.dat
public class FileOutputStreamTester {
public static void main(String[] args) {
String filename = "data1.dat";
int value0 = 255, value1 = 0, value2 = -1;
try {
//FileOutputStream是源生字节流,只识别写出去的是一个一个字节
//没办法识别数据类型,DataOutputStream是处理流,可以将字节首先处理成某种
//类型的数据
DataOutputStream dataOutputStream = new DataOutputStream(new FileOutputStream(filename));
dataOutputStream.writeInt(value0);
dataOutputStream.writeInt(value1);
dataOutputStream.writeInt(value2);
dataOutputStream.close();
} catch (IOException e) {
System.out.println("Problem writing" + filename);
}
}
}
/*
* 运行结果
运行程序后,生成数据文件datal.dat
用写字板打开没有任何显示
用ultraEdit打开查看其二进制信息,内容为00.00 00 FF 00 0000 00 FF FF FF FF,
* 每个int数字都是32个bit的
说明
FileOutputStream类的构造方法负责打开文件“data1.dat”用于写数据
* FileOutputStream类的对象与DataOutputStream对象连接,写基本类型的数据
*
* 当需要写大量数据时,用缓冲流类可以提高写的效率
*
*
* */
下面我们来看缓冲流类
BufferedOutputStream类
- 用法示例:
- DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(fileName)));
- BufferedOutputStream也是处理流,它是进行缓冲的,但不是直接进行写操作的
向文件写入多种数据,统计字节数,代码如下:
package Week13.com.videolearn;
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
//向文件写入多种数据,统计字节数
public class BufferedOutputStreamTester {
public static void main(String[] args) throws IOException {
String fileName = "mixedTypes.dat";
//首先构造输出流对象
//在源生字节输出流对象基础上构造一个缓冲的BufferedOutputStream,提高写的效率
//DataOutputStream可以按照类型去写二进制数据
DataOutputStream dataOutputStream = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(fileName)));
dataOutputStream.writeInt(0);
System.out.println(dataOutputStream.size() + "bytes have been written");
dataOutputStream.writeDouble(31.2);
System.out.println(dataOutputStream.size() + "bytes have been written");
dataOutputStream.writeBytes("JAVA");
System.out.println(dataOutputStream.size() + "bytes have been written");
dataOutputStream.close();
}
}
运行结果如下:
二进制文件的读取
之前以将数据以二进制方式写入文件
读取二进制文件中的三个int型数字并相加,代码如下:
package Week13.com.videolearn;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
//读取二进制文件中的三个int型数字并相加
public class DataInputStreamTester {
public static void main(String[] args) {
String fileName = "data1.dat";
int sum = 0;
try {
//首先构造输入流对象FileInputStream(源生字节的输入流)
//BufferedInputStream缓冲流
//DataInputStream可以按类型读取
DataInputStream dataInputStream = new DataInputStream(new BufferedInputStream(new FileInputStream(fileName)));
//问题1:当初这个文件写的是什么类型的数据要知道,不然就没办法了
//问题2:如果不知道这个文件中总共有多少个整数,怎么判断?
//可以依赖异常,捕获DataInputStream的文件结束异常
sum += dataInputStream.readInt();
sum += dataInputStream.readInt();
sum += dataInputStream.readInt();
System.out.println("The sum is:" + sum);
dataInputStream.close();
} catch (IOException e) {
System.out.println("Problem reading" + fileName);
}
}
}
运行结果如下:
用读写字节的方式来复制文件,不管这个文件是二进制文件还是文本文件,都可以实现
如果需要对任何类型的文件进行复制呢?
代码如下:
package Week13.com.videolearn;
import java.io.*;
//从命令行输入源文件名和目标文件名,将源文件复制为目标文件
public class CopyBytes {
public static void main(String[] args) {
DataInputStream instr;
DataOutputStream outstr;
if(args.length != 2) {
System.out.println("Please enter file names");
return;
}
try {
instr = new DataInputStream(new BufferedInputStream(new FileInputStream(args[0])));
outstr = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(args[1])));
try {
int data;
while(true) {
data = instr.readUnsignedByte();
outstr.writeByte(data);
}
} catch (EOFException e) {
//遇到文件尾,会抛出文件结束异常,捕获
outstr.close();
instr.close();
return;
}
} catch (FileNotFoundException nfx) {
System.out.println("Problem opening files");
} catch (IOException iox) {
System.out.println("IO Problems");
}
}
}
File类的介绍
读写文件程序中String Filename可以用File对象替代
注意:Java中File类只用来操作文件和获取文件信息;文件数据的读取由文件流提供
File类的作用
- 创建、删除文件 ;
- 重命名文件;
- 判断文件的读写权限及是否存在;
- 设置和查询文件的最近修改时间等;
- 构造文件流可以使用File类的对象作为参数
接下来看:结合File类,改进之前的二进制文件复制程序
File类的几个常用方法 (参见P194页例7-6):
- isFile0方法来确定File对象是否代表一个文件
- isDirectory0方法来确定File对象是否代表一个目录
- exists0方法判断同名文件或路径是否存在
改进的文件复制程序代码如下:
package Week13.com.videolearn;
import java.io.*;
//改进的文件复制程序
public class NewCopyBytes {
public static void main(String[] args) {
DataInputStream instr;
DataOutputStream outstr;
if(args.length != 2) {
System.out.println("Please Enter file names!");
return;
}
File inFile = new File(args[0]);
File outFile = new File(args[1]);
//exists方法判断要打开的文件是否存在
if(outFile.exists()) {
System.out.println(args[1] + " already exists");
return;
}
if(! inFile.exists()) {
System.out.println(args[0] + "does not exist");
return;
}
try {
instr = new DataInputStream(new BufferedInputStream(new FileInputStream(inFile)));
outstr = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(outFile)));
try {
int data;
while(true) {
data = instr.readUnsignedByte();
outstr.writeByte(data);
}
} catch (EOFException e) {
outstr.close();
instr.close();
return;
}
} catch (FileNotFoundException nfx) {
System.out.println("Problem opening files");
} catch (IOException iox) {
System.out.println("IO Problems");
}
}
}
对象的序列化
我们在程序内存中的对象能存活多长时间呢?
它最长的寿命也就是到程序运行结束,程序已结束,所占有的空间就都释放了,内存中的对象也就不存在了,如果有些对象中的信息是需要永久保留的,这个时候就要就行对象的序列化,也就是说把这个对象按照整体写到文件中,再整体读出来。
ObjectInputStream/ObjectOutputStream类
实现对象的读写
ObjectInputStream对象输入流
通过ObjectOutputStream把对象写入磁盘文件
ObjectOutputStream对象输出流
通过ObjectInputStream把对象读入程序
不保存对象的transient和static类型的变量
对象要想实现序列化,其所属的类必须实现Serializable接口
ObjectOutputStream(处理流)
必须通过另一个流构造ObjectOutputStream:
FileOutputStream out = new FileOutputStream("theTime");
ObjectOutputStream s = new ObjectOutputStream(out);
s.writeObject("Today");//参数这里都已经实现了Serializable接口
s.writeObject(new Date());//参数这里都已经实现了Serializable接口
s.flush();//清空缓冲区
ObjectInputStream(处理流)
必须通过另一个流构造ObjectInputStream:
FileInputStream in = new FileInputStream("theTime");
ObjectInputStream s = new ObjectInputStream(in):
String today = (String)s.readObject();
Date date = (Date)s.readObject0);
如果我们自己定义的对象也需要整体存文件整体读取出来,那么我们的类就要去实现Serializable接口,实际上就是一个空接口。
Seriealizable
- Serializable 接口的定义
package java.io;
public interface Serializable {
// there's nothing in here!
}
- 实现Serializable接口的语句
public class MyClass implements Serializable {
...
}
- 使用关键字transient可以阻止对象的某些成员被自动写入文件
- 实际上这就是一个标记,设计这个类的时候打算允许对象能够整体写入磁盘。
课后任务
请练习教材P203页例7-10
Employee对象的保存(可以在以前练习过的Employee定义上修改)