第四部分 I/O (输入/输出) 4.1 流概述
流是一组有序的数据序列,根据操作类型,可以分为输入流和输出流两种。I/O (Input/Output,输入/输出) 流提供了一条通道程序,可以使用这条通道把源中的字节序列送到目的地。虽然 I/O 流通常与磁盘文件存取有关,但是程序的源和目的地也可以是键盘、鼠标、内存或显示器窗口等。
Java 由数据流处理输入/输出模式,程序从指向源的输入流中读取源中的数据。如下图所示。源可以是文件、网络、压缩包或者其他数据源。
输出流的指向是数据要到达的目的地,程序通过向输出流中写入数据把信息传递到目的地,如下图所示。输出流的目标可以是文件、网络、压缩包、控制台和其他数据输出目标。
Java 语言定义了许多类专门负责各种方式的输入/输出,这些类都被放在 java.io 包中。其中,所有输入流类都是抽象类 InputStream(字节输入流)或抽象类 Reader(字符输入流)的子类;而所有输出流都是抽象类 OutputStream(字节输出流)或抽象类 Writer(字符输出流)的子类。
1. 输入流
InputStream 类是字节输入流的抽象类,是所有字节输入流的父类。InputStream 类的具体层次结果如下图所示。
该类中所有方法遇到错误时都会引发 IOException 异常。下面是对该类中的一些方法的简要说明。
- read() 方法:从输入流中读取数据的下一个字节。返回 0~255 范围内的 int 字节值。如果因为已经到达流末尾而没有可用的字节,则返回值为 -1
- read(byte[ ] b) 方法:从输入流中读入一定长度的字节,并以整数的形式返回字节数
- mark(int readlimit) 方法:在输入流的当前位置放置一个标记,readlimit 参数告知此输入流在标记位置失效之前允许读取的字节数
- reset() 方法:将输入指针返回到当前所做的标记处
- skip(long n) 方法:跳过输入流上的 n 个字节并返回实际跳过的字节数
- markSupported() 方法:如果当前流支持 mark()/reset() 操作就返回 true
- close() 方法:关闭此输入流并释放与该流关联的所有系统资源
Java 中的字符是 Unicode 编码,是双字节的。InputStream 是用来处理字节的,并不适合处理字符文本。Java 为字符文本的输入专门提供了一套单独的类 Reader,但 Reader 类并不是 InputStream 类的替换者,只是在处理字符串时简化了编程。Reader 类是字符输入流的抽象类,所有字符输入流的实现都是它的子类。Reader 类的具体层次结构如下图所示。
此外,Reader 类中的方法与 InputStream 类中的方法类似。
2. 输出流
OutputStream 类是字节输出流的抽象类,此抽象类是标识输出字节流的所有类的超类。OutputStream 类的具体层次结构如下图所示。
OutputStream 类中的所有方法均返回 void,在遇到错误时会引发 IOException 异常。下面对 OutputStream 类中的方法作简单介绍。
- write(int b) 方法:将指定字节写入此输出流
- write(byte[ ] b) 方法:将 b 个字节从指定的 byte 数组写入此输出流
- write(byte[ ] b, int off, int len) 方法:将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流
- flush() 方法:彻底完成输出并清空缓存区
- close() 方法:关闭输出流
Writer 类是字符输出流的抽象类,所有字符输出类的实现都是它的子类。
4.3 File 类File 类是 java.io 包中唯一代表磁盘文件本身的对象。File 类文件定义了一些与平台无关的方法来操作文件,可以通过调用 File 类中的方法,实现创建、删除、重命名文件等操作。File 类的对象主要用来获取文件本身的一些信息,如文件所在的目录、文件的长度、文件的读写权限等。数据流可以将数据写入到文件中,文件也是数据流最常用的数据媒体。
1. 文件的创建与删除
可以使用 File 类创建一个文件对象。通常使用以下 3 种构造方法来创建文件对象,
(1)File(String pathname)
该构造方法通过将给定路径名字字符串转换为抽象路径名来创建一个新 File 实例。语法如下
new File(String pathname)
其中的 pathname 指路径名称(包括文件名)。例如
File file = new File("d:/1.txt");
(2)File(String parent, String child)
该构造方法根据定义的父路径和子路径字符串(包含文件名)创建一个新的 File 对象。语法如下
new File(String patent, String child)
- parent:父路径字符串,例如 D:/ 或 D:/doc
- child:子路径字符串,例如 letter.txt
(3)File(File f, String child)
- f:父路径对象,例如 D:/doc/
- child:子路径字符串,例如 letter.txt
当使用 File 类创建一个文件对象后,例如
File file = new File("word.txt");
如果当前目录种不存在名称为 word 的文件,File 类对象可通过调用 creatNewFile() 方法创建一个名称为 word.txt 的文件;如果存在 word.txt 文件,可以通过文件对象的 delete() 方法将其删除。
package iopart4;
import java.io.*;
public class FileTest {
public static void main(String[] args) {
File file = new File("word.txt");
if(file.exists()) {
file.delete();
System.out.println("文件已删除");
}else {
try { // try语句捕获可能出现的异常
file.createNewFile();
System.out.println("文件已创建");
}catch(Exception e) {
e.printStackTrace();
}
}
}
}
2. 获取文件信息
File 类提供了很多方法以获取文件本身信息,常见的如下表所示
| 方法 | 返回值 | 说明 |
|---|---|---|
| getName() | String | 获取文件名称 |
| canRead() | boolean | 判断文件是否为可读的 |
| canWrite() | boolean | 判断文件是否可被写入 |
| exits() | boolean | 判断文件是否存在 |
| length() | long | 获取文件的长度(以字节为单位) |
| getAbsolutePath() | String | 获取文件的绝对路径 |
| getParent() | String | 获取文件的父路径 |
| isFile() | boolean | 判断文件是否存在 |
| isDirectory() | boolean | 判断文件是否为一个目录 |
| isHidden() | boolean | 判断文件是否为隐藏文件 |
| lastModified() | long | 获取文件最后修改时间 |
下面实例将展示部分方法的使用。
package iopart4;
import java.io.*;
public class FileTest2 {
public static void main(String[] args) {
File file = new File("word2.txt");
if(file.exists()) {
String name = file.getName();
long length = file.length();
boolean hidden = file.isHidden();
System.out.println("文件名称:"+name);
System.out.println("文件长度是:"+length);
System.out.println("该文件是否为隐藏文件:"+hidden);
}else {
System.out.println("该文件不存在");
}
}
}
4.4 文件输入/输出流
程序运行期间,大部分数据都在内存中进行操作,当程序结束或关闭时,这些数据将消失。如果需要将数据永久保存,可使用文件输入/输出流与指定的文件建立连接,将需要的数据永久保存到文件中。
1. FileInputStream 与 FileOutputStream 类
FileInputStream 类与 FileOutputStream 类都来自操作磁盘文件。如果用户的文件读取需求比较简单,则可以使用 FileInputStream 类,该类继承自 InputStream 类。FileInputStream
类与 FileOutputStream 类对应,提供了基本的文件写入能力。FileOutputStream 类是 OutputStream 类的子类。
FileInputStream 类常用的构造方法如下:
- FileInputStrea(String name)
- FileInputStream(File file)
第一个构造方法使用给定的文件名 name 创建一个 FileInputStream 对象,第二个构造方法使用 File 对象创建 FileInputStream 对象。第一个构造方法比较简单,但第二个方法允许在把文件连接输入流之前对文件作进一步分析。
FileOutputStream 类与 FileInputStream 类有相同的参数构造方法,创建一个 FileOutputStream 对象时,可以指定不存在的文件名,但此文件不能是一个已被其他程序打开的文件。
package iopart4;
import java.io.*;
public class FileTest3 {
public static void main(String[] args) {
File file = new File("Word.txt");
try {
FileOutputStream out = new FileOutputStream(file);
byte buy[] = "我是猪".getBytes();
out.write(buy);
out.close();
}catch(Exception e) {
e.printStackTrace();
}
try {
//创建 FileInputStream类对象
FileInputStream in = new FileInputStream(file);
byte byt[] = new byte[1024];
int len = in.read(byt);
System.out.println("文件中的信息是:"+new String(byt,0,len));
in.close();
}catch(Exception e) {
e.printStackTrace();
}
}
}
2. FileReader 和 FileWriter 类
使用 FileOutputStream 类向文件中写入数据与使用 FileInputStream 类从文件中将内容读出来,都存在一点不足,即这两个类都只提供了对字节或字节数组的读取方法。由于汉字在文件中占用两个字节,如果使用字节流,读取不好可能会出现乱码现象,此时采用字符流 Reader 或 Writer 类即可避免这种现象。
FileReader 和 FileWriter 字符流对应了 FileInputStream 和 FileOutputStream 类。 FileReader 流顺序地读取文件,只要不关闭流,每次调用 read() 方法就顺序地读取源中其余的内容,直到源的末尾或流被关闭。
缓存时 I/O 的一种性能优化。缓存流为 I/O 流增加了内存缓存区。有了缓存区,使得在该流上执行 skip()、mark(0 和 reset() 方法都成为可能。
BufferedInputStream 类可以对所有 InputStream 类进行带缓存区的包装以达到性能的优化。BufferedInputStream 类有两个构造方法:
- BufferedInputStream(InputStream in)
- BufferedInputStream(InputStream in, int size)
第一种形式的构造方法创建了一个带有 32 个字节的话黁了;第二种形式的构造方法按照指定的大小来创建缓存区。一个最优的缓存区的大小,取决于它所在的操作系统、可用的内存空间以及机器配置。从构造方法可以看出,BufferedInputStream 对象位于 InputStream 对象之前。
BufferedInputStream 读取文件的过程为:文件 —> InputStream —> BufferedInputStream —> 目的地
使用 BufferedOutputStream 输出信息和 OutputStream 输出信息完全一样,只不过 BufferedOutputStream 有一个 flush() 方法用来将缓存区的数据流强制输出完。BufferedOutputStream 类也有两个构造方法:
- BufferedOutputStream(OutputStream in)
- BufferedOutputStream(OutputStream in, int size)
此外,BufferedReader 类与 BufferedWriter 类分别继承 Reader 类与 Writer 类。这两个类同样具有内部缓存机制,并可以以行为单位进行输入/输出。
根据 BufferedReader 类的特点,字符数据读取文件的过程为:
字符数据 —> BufferedWriter —> OutputStreamWriter —> OutputStream —> 文件
BufferedReader 类常用的方法如下:
- read() 方法:读取单个字符
- readLine() 方法:读取一个文本行,并将其返回字符串。若无数据可读,则返回 null
BufferedWriter 类中的方法都返回 void。常用的方法如下:
- write(String s, int off, int len) 方法:写入字符串的某一部分
- flush() 方法:刷新该流的缓存
- newLine() 方法:写入一个行分隔符
在使用 BufferedWriter 类的 Write() 方法,数据并没有立刻被写入输出流,而是首先进入缓存区中。如果想立刻将缓存区中的数据写入输出流,一定要调用 flush() 方法。
package iopart4;
import java.io.*;
public class Student {
public static void main(String[] args) {
String content[] = {"好久不见","最近好吗","常联系"};
File file = new File("WORD.txt");
try {
FileWriter fw = new FileWriter(file);
BufferedWriter bufw = new BufferedWriter(fw);
for(int k=0;k
bufw.write(content[k]);
bufw.newLine();
}
bufw.close();
fw.close();
}catch(Exception e) {
e.printStackTrace();
}
try {
FileReader fr = new FileReader(file);
BufferedReader bufr = new BufferedReader(fr);
String s = null;
int i = 0;
while((s=bufr.readLine())!=null) {
i++;
System.out.println("第"+i+"行:"+s);
}
bufr.close();
fr.close();
}catch(Exception e) {
e.printStackTrace();
}
}
}



