0%

IO之字节流

1. 总体结构

在这里插入图片描述

2. FileInputStream与FileOutputStream

2.1 FileInputStream类

该类表示读取文件数据的输入字节流,使用该类的主要步骤为:

  1. 找到目标文件
  2. 建立数据的输入通道
  3. 建立缓冲区
  4. 读取文件中的数据
  5. 关闭资源

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//找到目标文件
File file = new File("src/io/demo.txt");
//建立数据的输入通道
InputStream is = new FileInputStream(file);
//建立缓冲区
byte[] buf = new byte[1024];
//读取目标文件的数据,每次读取1024个字节的数据
//若读取不到数据,read会返回-1,否则返回读到的字节数
int num = -1;
while((num = is.read(buf)) != -1) {
//使用字节数组构建字符串,并将数据简单的打印到控制台上
String content = new String(buf, 0, num);
System.out.print(content);
}
//关闭流
is.close();

2.2 FileOutputStream类

该类表示写入文件数据的输出字节流,下面我们结合FileInputStream类和FileOutputStream类来实现读取某个文件的数据并输出都新的文件中的功能。主要步骤为:

  1. 找到源文件
  2. 建立数据的输入通道
  3. 建立目的文件
  4. 建立数据的输出通道
  5. 建立缓冲区
  6. 读取源文件中的数据并写入缓冲区
  7. 将缓冲区中的文件数据写入目标文件
  8. 关闭流

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//找到源文件
File srcFile = new File("src/io/demo.txt");
//建立数据的输入通道
InputStream fis = new FileInputStream(srcFile);
//建立目的文件
File desFile = new File("src/io/desDemo.txt");
//建立数据的输出通道
OutputStream fos = new FileOutputStream(desFile);
//建立缓冲区
byte[] buf = new byte[1024];
//读取源文件中的数据并写入缓冲区,每次读取1024个字节的数据
//如果读到了数据,则num为读到的字节数,否则为-1
int num = -1;
while((num = fis.read(buf)) != -1) {
//将缓冲区中的文件数据写入目标文件
fos.write(buf, 0, num);
fos.flush();
}

//关闭流
fos.close();
fis.close();

在使用FileOutputStream写入数据到文件时,会覆盖到原始文件中的数据,可以使用FileOutputStream类的另一个构造方法来实现每次写入数据都添加到原文件的末尾。

假设存在目的文件desFile.txt,里面存在一些数据,如果我们使用如下操作向该文件写入新的数据,则会覆盖掉该文件中的原始数据。

1
2
3
4
5
6
7
8
9
10
11
//desFile.txt文件的原数据为:"源数据内容"
FileOutputStream fos = new FileOutputStream("src/io/desFile.txt");

// 写出数据
for (int x = 0; x < 10; x++) {
fos.write(("新的数据" + x).getBytes());
fos.write("\r\n".getBytes()); //写入windows系统的换行符
}

// 关闭输出流
fos.close();

可以该更改FileOutputStream的构造方法的操作解决该问题:

1
FileOutputStream fos = new FileOutputStream("src/io/desFile.txt", true);

后面的参数为true表示写入数据的可以追加在原有文件数据的尾部。

3. BufferedInputStream与BufferedOutputStream

在上面演示文件输入输出流的时候,我们都是用字节数组来充当缓冲区,其不存在刷新缓冲区等功能。 Java有自带缓冲区的缓冲流BufferedInputStream和BufferedOutputStream,其为I/O流增加了内存缓冲区。

3.1 构造方法

  • BufferedInputStream(InputStream)
  • BufferedInputStream(InputStream in, int size)

第一种形式的构造方法创建了一个带有32个字节的缓冲流;第二个形式的构造方法按指定的大小来创建缓冲区。BufferedOutputStream的构造方法基本一样。

3.2 flush方法

方法的作用是强制将缓存中的输出流(字节流,字符流等)强制输出。因为输出流在进行输出时,比如像某个文件中写入内容,其实是先将输出流写入到缓冲区,当缓冲区写满后才将缓冲区的内容输出到文件中。但是当主机完成输出流的输出后,有可能缓冲区这个时候还没有被填满,这样的话,就会一直等待主机发送内容,这时候,就可以使用flush将缓冲区的内容强制输出到文件中,清空缓冲区。 所以,一般在关闭输出流之前,要先调用flush方法强制缓冲区中的内容输出,并清空缓冲区。

3.3 字节数据读取文件与写入文件过程

在这里插入图片描述
代码示例:

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
InputStream fis = null;
BufferedInputStream bis = null;
OutputStream fos = null;
BufferedOutputStream bos = null;

try {
try {
fis = new FileInputStream("src/io/demo.txt");
//使用缓冲流封装输入流
bis = new BufferedInputStream(fis);
fos = new FileOutputStream("src/io/desFile.txt");
//使用缓冲流封装输出流
bos = new BufferedOutputStream(fos);
byte[] buf = new byte[1024];//用来存储每次读取到的字节数组,做中间媒介
int num = 0;//每次读取到的字节数组的长度
while ((num = bis.read(buf)) != -1) {//读取bis中的数据
//将bis中的数据写入bos中
bos.write(buf, 0, num);
//刷新bos缓冲区
bos.flush();
}
} finally {
//关闭包装流,被包装的流会默认关闭
bos.close();
bis.close();
}

} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

4. DataInputStream与DataOutputStream

在io包中,提供了两个与平台无关的数据操作流数据输出流(DataOutputStream)和数据输入流 (DataInputStream)
通常数据输出流会按照一定的格式将数据输出,再通过数据输入流按照一定的格式将数据读入。

比如按照如下的格式将数据输出到文件:
在这里插入图片描述
代码示例:

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
public void writeData() {
FileOutputStream fos = null;
DataOutputStream dos = null;
try {
try {
//1.构建文件输出流
File file = new File("f:" + File.separator + "demo.txt");
fos = new FileOutputStream(file);
dos = new DataOutputStream(fos);
//2.按指定的数据保存格式写入数据
String names[] = {"衬衣","手套","围巾"} ; // 商品名称
float prices[] = {98.3f,30.3f,50.5f} ; // 商品价格
int nums[] = {3,2,1} ; // 商品数量
for(int i=0;i<names.length;i++){ // 循环输出
dos.writeChars(names[i]) ; // 写入字符串
dos.writeChar('\t') ; // 写入分隔符
dos.writeFloat(prices[i]) ; // 写入价格
dos.writeChar('\t') ; // 写入分隔符
dos.writeInt(nums[i]) ; // 写入数量
dos.writeChar('\n') ; // 换行
dos.flush();
}

} finally {
// 关闭流
dos.close();
fos.close();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}

如果我们直接用记事本打开demo.txt文件,会出现乱码现象。
在这里插入图片描述
应该将使用DataOutputStream写入的数据通过DataInputStream读取出来:

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
45
public void readData() {
FileInputStream fis = null;
DataInputStream dis = null;

try {
try {
//构建文件读取流
File file = new File("f:" + File.separator + "demo.txt");
fis = new FileInputStream(file);
dis = new DataInputStream(fis);

String names = null; // 保存所有商品名称
float price = 0.0f ; // 接收价格
int num = 0 ; // 接收数量
char temp[] = new char[200]; // 存放商品名称缓冲区
int len = 0 ; // 保存读取数据的个数
char c = 0 ; // '\u0000

while(true) {
//1.初始化
len = 0;
//2.读取所有的商品名,一次循环读取一个名称
while((c = dis.readChar()) != '\t') {
temp[len++] = c;
}
names = new String(temp, 0, len);
//3.读取价格
price = dis.readFloat();
dis.readChar();//读取\t
//4.读取商品数量
num = dis.readInt();
dis.readChar() ; // 读取\n
//5.直接打印输出
System.out.printf("名称:%s;价格:%5.2f;数量:%d\n",names,price,num) ;
}

} finally {
// 关闭流
dis.close();
fis.close();
}
} catch (Exception e) {
// TODO: handle exception
}
}

运行结果:

1
2
3
名称:衬衣;价格:98.30;数量:3
名称:手套;价格:30.30;数量:2
名称:围巾;价格:50.50;数量:1

5. PrintStream

  1. PrintStream 是打印输出流,它继承于FilterOutputStream。是用来装饰其它输出流,它能为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式
  2. 与其他输出流不同,PrintStream 永远不会抛出 IOException;它产生的IOException会被自身的函数所捕获并设置错误标记, 用户可以通过 checkError() 返回错误标记,从而查看PrintStream内部是否产生了IOException。
  3. PrintStream 提供了自动flush 和 字符集设置功能。所谓自动flush,就是往PrintStream写入的数据会立刻调用flush()函数。
  4. PrintStream能更为方便地输出数据到文件中

举个例子:
比如我们使用FileOutputStream向指定文件写入数据:

1
2
3
File file = new File("F:\\a.txt");
FileOutputStream outputStream = new FileOutputStream(file,true);//第二个参数为追加文本
outputStream.write(97);

上面的代码执行完之后,a.txt中的内容存的是a,因为write方法接收的为byte类型的数据,97对应的ASCII码为a。
假设我就想将97写入到文件中呢?那么得将第三行代码改为:

1
outputStream.write("97".getBytes());//先将97作为字符串再转换为byte数组

而PrintStream的出现,使我们写数据到文件变得十分方便,你传入的是什么,就会给你写入什么数据。原因是其内部帮我们自动转换好了。

下面介绍其一些方法的简单使用:

5.1 print方法

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
public class PrintStreamDemo {
private static PrintStream ps;

public static void main(String[] args) {
try {
try {
File file = new File("src/io/demo.txt");
//指定编码,即对打印打印到文件中的数据进行UTF-8的编码
ps = new PrintStream(new FileOutputStream(file), true, "UTF-8");
//print方法
//将数据写入到"PrintStream输出流"中,print实际调用的是write函数
ps.print("打印流数据1");
ps.print("打印流数据2");
ps.print(true);
ps.print('a');
ps.print(5.2);
ps.print(5);
} finally {
ps.close();
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}

运行结果(demo.txt中的数据):

1
打印流数据1打印流数据2truea5.25

5.2 println方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class PrintStreamDemo {
private static PrintStream ps;

public static void main(String[] args) {
try {
try {
File file = new File("src/io/demo.txt");
ps = new PrintStream(new FileOutputStream(file), true, "UTF-8");
//println方法
//将数据+换行符写入到"PrintStream输出流"中,println实际调用的是write函数
ps.println("打印流数据1");
ps.println("打印流数据2");
ps.println(true);
ps.println('a');
ps.println(5.2);
ps.println(5);
} finally {
ps.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

运行结果(demo.txt中的数据):

1
2
3
4
5
6
打印流数据1
打印流数据2
true
a
5.2
5

6. ObjectInputStream与ObjectOutputStream

ObjectInputStream和ObjectInputStream类创建的对象被称为对象输入流和对象输出流。对象输出流可以将Java对象进行持久化存储,即实现对象的序列化。而对象输入流可以实现持久化对象的反序列化
注意:被序列化的对象需要实现序列化接口Serializable。

假设需要对类User进行序列化并保存在user.dat文件中,然后再使用反序列化操作获取该类实例化对象。

User类:

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
45
46
47
48
import java.io.Serializable;

public class User implements Serializable {//实现序列化接口
private String name;
private String gender;
private int age;

public User() {
super();
}

public User(String name, String gender, int age) {
super();
this.name = name;
this.gender = gender;
this.age = age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getGender() {
return gender;
}

public void setGender(String gender) {
this.gender = gender;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

@Override
public String toString() {
return "User [name=" + name + ", gender=" + gender + ", age=" + age + "]";
}

}

具体的序列化与反序列化操作:

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;

public class ObjectStreamDemo {
private OutputStream fos;
private ObjectOutputStream oos;
private InputStream fis;
private ObjectInputStream ois;
/**
* 序列化对象
*/
public void serializableObj() {
try {
try {
//1.创建用于存储对象的文件
File file = new File("f:" + File.separator + "user.dat");
//2.建立文件输出流
fos = new FileOutputStream(file);
//3.包装文件输出流
oos = new ObjectOutputStream(fos);
//4.序列化user对象
oos.writeObject(new User("张三","男",22));

} finally {
//5.关闭流
oos.close();
fos.close();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

/**
* 反序列化对象
*/
public void reSerializableObj() {
try {
try {
//1.创建用于存储对象的文件
File file = new File("f:" + File.separator + "user.dat");
//2.建立文件输入流
fis = new FileInputStream(file);
//3.包装文件输入流
ois = new ObjectInputStream(fis);
//4.反序列化user对象
User user = (User) ois.readObject();
//5.输出user的信息
System.out.println(user); //User [name=张三, gender=男, age=22]

} finally {
//5.关闭流
ois.close();
fis.close();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}

public static void main(String[] args) {
ObjectStreamDemo demo = new ObjectStreamDemo();
demo.serializableObj();
demo.reSerializableObj();
}
}

文末:字节流的一些常用类,暂时就简单总结到了,其他诸如序列流SequenceInputStream和管道流PipedInputStream等暂时就不去总结了。休息!

------ 本文结束------