0%

Java IO:文件IO用法综述

Java的IO类体系庞大,初学者很容易迷失在其中。而解决这个问题的最好办法就是分门别类、各个击破。这里来介绍Java IO中关于文件IO一些常用类的具体用法

abstract.jpeg

概述

上图中红字标识的10个IO类,是我们操作文件IO的常用类。乍一看会觉得很多,其实在我们理解了它们各自的作用再来看,会发现其实还是很清晰的。Java IO中可划分为两大类:字节流、字符流。前者以字节为单位进行操作,而后者则以字符为单位进行操作。所以如果是非文本类型文件(如图像)使用前者进行操作;而对于文本类型的文件更推荐通过后者进行操作。具体地,字节流可通过FileInputStream、FileOutputStream类进行构造获得。而对于字符流,一方面可通过转换流InputStreamReader、OutputStreamWriter将字节流转换为字符流来获得,另一方面其还可以直接通过FileReader、FileWriter来获得字符流(字符编码使用UTF-8)。更进一步地,Java为了提高IO的操作效率,还为字节流、字符流提供了相应的缓冲流:BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter

字节流

InputStream、OutputStream 输入、输出字节流抽象类

具体地,字节流可分为输入流、输出流两种。对于输入字节流而言,其在抽象类InputStream中提供、定义了一组通用的操作方法,以供其子类来继承、实现。我们在了解这些方法的功用之后,就可以知道如何来操作其下具体的实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public abstract class InputStream implements Closeable {
...
// 当前可读取数据的字节数
public int available() throws IOException;
// 从输入流中读取下一个字节的数据
public abstract int read() throws IOException;
// 从输入流中读取若干字节的数据存入字节数组b中,同时返回所读取的字节数;当读到文件尾部无数据读入时,返回-1
public int read(byte b[]) throws IOException;
// 从输入流中读取最多len个字节的数据并存储到字节数组b的指定位置,同时返回实际读取的字节数;当读到文件尾部无数据读入时,返回-1
public int read(byte b[], int off, int len) throws IOException;
// 关闭IO流
public void close() throws IOException;
...
}

对于输出字节流而言。其同样在抽象类OutputStream中提供、定义了一组通用的操作方法,以供其子类来继承、实现。我们在了解这些方法的功用之后,就可以知道如何来操作其下具体的实现类

1
2
3
4
5
6
7
8
9
10
11
12
public abstract class OutputStream implements Closeable, Flushable {
// 强制将缓冲区中的数据刷新写入
public void flush() throws IOException;
// 向输出流写入一个字节
public abstract void write(int b) throws IOException;
// 向输出流写入字节数组的全部内容
public void write(byte b[]) throws IOException;
// 向输出流写入字节数组指定的内容
public void write(byte b[], int off, int len) throws IOException;
// 关闭IO流
public void close() throws IOException;
}

FileInputStream、FileOutputStream 文件输入、输出字节流

1. FileInputStream 文件输入字节流

文件字节输入流FileInputStream是我们读取文件的具体类,其具体用法如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 字节流读测试
public static void testFileInputStream() throws Exception {
String file = "E:\\TestCode\\JavaTest\\src\\main\\resources\\IOStreamTest\\r1.txt";
FileInputStream in = null;

// 每次读取一个字节
in = new FileInputStream(file);

int ch = -1;
StringBuilder sb = new StringBuilder();
while ( (ch=in.read()) != -1 ) {
sb.append((char) ch);
}
in.close();
System.out.println("str1: " + sb.toString());

// 每次读取多个字节
in = new FileInputStream(file);
byte[] bytes = new byte[4];
int count = 0;
String str = "";
while( (count=in.read(bytes)) != -1) {
String temp = new String(bytes, 0, count);
str += temp;
}
in.close();
System.out.println("str2: " + str);
}

测试文件、测试结果如下所示:

figure 1.jpeg

2. FileOutputStream 文件输出字节流

文件输出字节流FileOutputStream是我们写入文件的具体类,其具体用法如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 字节流写测试
public static void testFileOutputStream() throws Exception {
String file1 = "E:\\TestCode\\JavaTest\\src\\main\\resources\\IOStreamTest\\w1.txt";
String file2 = "E:\\TestCode\\JavaTest\\src\\main\\resources\\IOStreamTest\\w2.txt";

String str = "字节流写测试";
byte[] bytes = str.getBytes();

// 每次写入一个字节
FileOutputStream out1 = new FileOutputStream(file1);
for(byte b : bytes ) {
out1.write(b);
}
out1.flush();
out1.close();

// 每次写入多个字节
FileOutputStream out2 = new FileOutputStream(file2);
for(int i=0; i<bytes.length; i++) {
int s = i;
int e = i+4>=bytes.length ? bytes.length-1 : i+4;
int length = e - s + 1;
out2.write(bytes,s, length);
i = e;
}
out2.flush();
out2.close();
}

测试结果如下所示:

figure 2.jpeg

BufferedInputStream、BufferedOutputStream 输入、输出缓冲字节流

由于FileInputStream、FileOutputStream的每一次IO操作都是直接访问磁盘,我们知道磁盘的IO操作速度低于内存。为此Java提供了输入缓冲流BufferedOutputStream来将字节输出流的多次写入的数据结果先存于内存当中,待缓冲区满后再一次性全部写入到磁盘中,BufferedInputStream的作用同理

1. BufferedInputStream 输入缓冲字节流

我们需要先构造字节输入流,然后依此来构造输入缓冲字节流BufferedInputStream。其具体用法如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Buffer IO 包装字节流: 读
public static void testBufferedInputStream() throws Exception{
String file = "E:\\TestCode\\JavaTest\\src\\main\\resources\\IOBufferTest\\r1.txt";

FileInputStream in = new FileInputStream(file);
BufferedInputStream bufferedInputStream = new BufferedInputStream(in);

byte[] buffer = new byte[1024];
int count = -1;
String str = "";
while ((count = bufferedInputStream.read(buffer)) != -1) {
String temp = new String(buffer,0,count);
str += temp;
}
System.out.println("str: " + str);
// 只需关闭最外层的IO即可
bufferedInputStream.close();
}

测试文件、测试结果如下所示:

figure 3.jpeg

2. BufferedOutputStream 输出缓冲字节流

我们需要先构造字节输出流,然后依此来构造输出缓冲字节流BufferedOutputStream。其具体用法如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Buffer IO 包装字节流: 写
public static void testBufferedOutputStream() throws Exception{
String file = "E:\\TestCode\\JavaTest\\src\\main\\resources\\IOBufferTest\\w1.txt";

FileOutputStream out = new FileOutputStream(file);
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(out);

String str = "利用缓冲流包装字节流来写入文件";
byte[] bytes = str.getBytes();

bufferedOutputStream.write(bytes);
bufferedOutputStream.flush();
// 只需关闭最外层的IO即可
bufferedOutputStream.close();
}

测试结果如下所示:

figure 4.png

字符流

Reader、Writer 输入、输出字符流抽象类

同样地,字符流亦可分为输入流、输出流两种。对于输入字符流而言,其在抽象类Reader中提供、定义了一组通用的操作方法,以供其子类来继承、实现。我们在了解这些方法的功用之后,就可以知道如何来操作其下具体的实现类

1
2
3
4
5
6
7
8
9
10
11
12
public abstract class Reader implements Readable, Closeable {
...
// 从输入流中读取下一个字符的数据
public int read() throws IOException;
// 从输入流中读取若干字符的数据存入字符数组中,同时返回所读取的字符数;当读到文件尾部无数据读入时,返回-1
public int read(char cbuf[]) throws IOException;
// 从输入流中读取最多len个字符的数据并存储到字符数组的指定位置,同时返回实际读取的字符数;当读到文件尾部无数据读入时,返回-1
abstract public int read(char cbuf[], int off, int len) throws IOException;
// 关闭IO流
abstract public void close() throws IOException;
...
}

类似地,字符流也有一个抽象类Writer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public abstract class Writer implements Appendable, Closeable, Flushable {
...
// 强制将缓冲区中的数据刷新写入
abstract public void flush() throws IOException;
// 向输出流写入一个字符
public void write(int c) throws IOException;
// 向输出流写入字符数组的全部内容
public void write(char cbuf[]) throws IOException;
// 向输出流写入字符数组指定的内容
abstract public void write(char cbuf[], int off, int len) throws IOException;
// 向输出流写入字符串
public void write(String str) throws IOException;
// 关闭IO流
abstract public void close() throws IOException;
...
}

InputStreamReader、OutputStreamWriter 输入、输出转换流

1. InputStreamReader 输入转换流

输入转换流InputStreamReader可以按将输入字节流转换为指定编码方式的输入字符流,其具体用法如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public static void testReaderByInputStreamReader() throws Exception {
String file = "E:\\TestCode\\JavaTest\\src\\main\\resources\\IOStreamAndReaderWriterTest\\r1.txt";
FileInputStream in = null;
InputStreamReader inputStreamReader = null;

// 每次读取一个字符
// 1. 创建字节流
in = new FileInputStream(file);
// 2. 将字节流转换字符流
inputStreamReader = new InputStreamReader(in, "gbk");
int ch = -1;
StringBuilder sb = new StringBuilder();
while( (ch=inputStreamReader.read()) != -1 ) {
sb.append((char)ch);
}
inputStreamReader.close();
//in.close(); // 只需关闭最外层的IO即可
System.out.println("str1: " + sb.toString());

// 每次读取多个字符
// 1. 创建字节流
in = new FileInputStream(file);
// 2. 将字节流转换字符流
inputStreamReader = new InputStreamReader(in, "gbk");

char[] chars = new char[4];
int counts = -1;
String str = "";
while ( (counts=inputStreamReader.read(chars))!= -1 ) {
String temp = new String(chars, 0, counts);
str += temp;
}
inputStreamReader.close(); // 只需关闭最外层的IO即可
System.out.println("str2: " + str);
}

测试文件、测试结果如下所示:

figure 5.jpeg

2. OutputStreamWriter 输出转换流

输出转换流OutputStreamWriter可以按将输出字节流转换为指定编码方式的输出字符流,其具体用法如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public static void testWriterByOutputStreamWriter() throws Exception {
String file1 = "E:\\TestCode\\JavaTest\\src\\main\\resources\\IOStreamAndReaderWriterTest\\w1.txt";
String file2 = "E:\\TestCode\\JavaTest\\src\\main\\resources\\IOStreamAndReaderWriterTest\\w2.txt";
String file3 = "E:\\TestCode\\JavaTest\\src\\main\\resources\\IOStreamAndReaderWriterTest\\w3.txt";

FileOutputStream out = null;
OutputStreamWriter outputStreamWriter = null;

String str = "通过将字符流转换为字节流写入数据";
char[] chars = str.toCharArray();

// 每次写入一个字符
// 1. 创建字节流
out = new FileOutputStream(file1);
// 2. 将字节流转换为字符流
outputStreamWriter = new OutputStreamWriter(out, "gbk");
for(char ch : chars) {
outputStreamWriter.write(ch);
}
outputStreamWriter.flush();
// 只需关闭最外层的IO即可
outputStreamWriter.close();


// 每次写入多个字符
// 1. 创建字节流
out = new FileOutputStream(file2);
// 2. 将字节流转换为字符流
outputStreamWriter = new OutputStreamWriter(out, "gbk");
outputStreamWriter.write(chars);
outputStreamWriter.flush();
out.close();
outputStreamWriter.close();

// 每次写入多个字符
// 1. 创建字节流
out = new FileOutputStream(file3);
// 2. 将字节流转换为字符流
outputStreamWriter = new OutputStreamWriter(out, "utf-8");
outputStreamWriter.write(str);
outputStreamWriter.flush();
// 只需关闭最外层的IO即可
outputStreamWriter.close();
}

测试结果如下所示:

figure 6.jpeg

FileReader、FileWriter 文件输入、输出字符流

前面提到的字符流是利用转换流来将字节流转换为字符流的,Java还分别提供了输入、输出转换流的子类FileReader、FileWriter文件输入、输出字符流,通过它们可以直接创建字符流。当然在FileReader、FileWriter的内部还是通过先创建字节流再利用UTF-8编码和转换流来生成字符流的。所以说,如果我们的文件编码是UTF-8的话,可以直接使用FileReader、FileWriter文件输入、输出字符流来创建字符流

1. FileReader 文件输入字符流

这里使用FileReader类创建输入字符流直接读取文件,其具体用法如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public static void testReader() throws Exception {
String file = "E:\\TestCode\\JavaTest\\src\\main\\resources\\IOFileReaderWriter\\r1.txt";
FileReader reader = null;

// 每次读取一个字符, 使用默认字符集UTF-8
reader = new FileReader(file);
int ch = -1;
StringBuilder sb = new StringBuilder();
while( (ch=reader.read()) != -1 ) {
sb.append( (char)ch );
}
reader.close();
System.out.println("str1: " + sb.toString());

// 每次读取多个字符, 使用默认字符集UTF-8
reader = new FileReader(file);
char[] chars = new char[4];
int counts = -1;
String str = "";
while ( (counts=reader.read(chars))!= -1 ) {
String temp = new String(chars, 0, counts);
str += temp;
}
reader.close();
System.out.println("str2: " + str);
}

测试文件、测试结果如下所示:

figure 7.jpeg

2. FileWriter 文件输出字符流

这里使用FileWriter类创建输出字符流直接写文件,其具体用法如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public static void testWriter() throws IOException {
String file1 = "E:\\TestCode\\JavaTest\\src\\main\\resources\\IOFileReaderWriter\\w1.txt";
String file2 = "E:\\TestCode\\JavaTest\\src\\main\\resources\\IOFileReaderWriter\\w2.txt";
String file3 = "E:\\TestCode\\JavaTest\\src\\main\\resources\\IOFileReaderWriter\\w3.txt";

String str = "字符流写测试";
char[] chars = str.toCharArray();

// 每次写入一个字符,使用默认字符集 UTF-8
FileWriter writer1 = new FileWriter(file1);
for(char ch : chars) {
writer1.write(ch);
}
writer1.flush();
writer1.close();

// 每次写入多个字符,使用默认字符集 UTF-8
FileWriter writer2 = new FileWriter(file2);
writer2.write(chars);
writer2.flush();
writer2.close();

// 每次写入一个字符串,使用默认字符集 UTF-8
FileWriter writer3 = new FileWriter(file3);
writer3.write(str);
writer3.flush();
writer3.close();
}

测试结果如下所示:

figure 8.jpeg

BufferedReader、BufferedWriter 输入、输出缓冲字符流

正如字节缓冲流BufferedInputStream、BufferedOutputStream一样,Java针对字符流也提供了相应的缓冲类BufferedReader、BufferedWriter

1. BufferedReader 输入缓冲字符流

我们需要先创建一个输入字符流,然后通过BufferedReader进行包装装饰。特别地,在BufferedReader中还提供了一个按行读的readLine方法。其具体用法如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// Buffer IO 包装字符流: 读
public static void testBufferedReader() throws Exception {
String file = "E:\\TestCode\\JavaTest\\src\\main\\resources\\IOBufferTest\\r2.txt";

FileInputStream in = null;
InputStreamReader reader = null;
BufferedReader bufferedReader = null;

// 按行读
in = new FileInputStream(file);
reader = new InputStreamReader(in, "gbk");
bufferedReader = new BufferedReader(reader);

String temp = null;
String str = "";
String newLineFlag = System.getProperty("line.separator");
while( (temp=bufferedReader.readLine())!=null ) {
str += temp + newLineFlag;
}
// 只需关闭最外层的IO即可
bufferedReader.close();
System.out.println("str1: " + str);

// 一次性读取多个字符
in = new FileInputStream(file);
reader = new InputStreamReader(in, "gbk");
bufferedReader = new BufferedReader(reader);

char[] buffer = new char[1024];
int count = -1;
StringBuilder sb = new StringBuilder();
while( (count = bufferedReader.read(buffer))!=-1 ) {
sb.append( buffer,0, count );
}
// 只需关闭最外层的IO即可
bufferedReader.close();
System.out.println("str2: " + sb.toString());
}

测试文件、测试结果如下所示:

figure 9.jpeg

2. BufferedWriter 输出缓冲字符流

同样地,我们需要先创建一个输出字符流,然后通过BufferedWriter进行包装装饰。其具体用法如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Buffer IO 包装字符流: 写
public static void testBufferedWriter() throws Exception {
String file = "E:\\TestCode\\JavaTest\\src\\main\\resources\\IOBufferTest\\w2.txt";

FileOutputStream out = new FileOutputStream(file);
OutputStreamWriter writer = new OutputStreamWriter(out, "gbk");
BufferedWriter bufferedWriter = new BufferedWriter(writer);

String str = "利用缓冲流包装字符流来写入文件\n"
+ "This is line 1\n"
+ "This is line 2\n"
+ "Game over!";

bufferedWriter.write(str);
bufferedWriter.flush();
// 只需关闭最外层的IO即可
bufferedWriter.close();
}

测试结果如下所示:

figure 10.png

参考文献

  1. Java核心技术·卷II 凯.S.霍斯特曼著
请我喝杯咖啡捏~

欢迎关注我的微信公众号:青灯抽丝