Java IO Stream

一、IO 流概述


流(Stream)的概念源于 UNIX 中管道(pipe)的概念。在 UNIX中,管道是一条不间断的字节流,用来实现程序或进程间的通信,或读写外围设备、外部文件等。一个流,必有源端和目的端,它们可以是计算机内存的某些区域,也可以是磁盘文件,甚至可以是Internet上的某个URL。实际上,流的源端和目的端可简单地看成是字节的生产者和消费者,对输入流,可不必关心它的源端是什么,只要简单地从流中读数据,而对输出流,也可不知道它的目的端,只是简单地往流中写数据。

定义

流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。

分类

  • 根据数据流向不同分为:输入流和输出流。
    注意输入流和输出流是相对于程序而言的。把程序(内存)中的内容输出到磁盘、光盘等存储设备中是为输出;读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中。

  • 根据处理数据类型分为:字符流和字节流。
    数据流中最小的数据单元是字节的流为字节流;数据流中最小的数据单元是字符的流为字符流。Java 中的字符是 Unicode 编码,一个字符占用两个字节(无论中文还是英文都是两个字节)。

  • 根据功能不同分为:节点流和包装流。
    1)节点流:可以从或向一个特定的地方(节点)读写数据,直接连接数据源。如最常见的是文件的 FileReader,还可以是数组、管道、字符串,关键字分别为 ByteArray/CharArray,Piped,String。
    2)处理流(包装流):并不直接连接数据源,是对一个已存在的流的连接和封装,是一种典型的装饰器设计模式,使用处理流主要是为了更方便的执行输入输出工作,如PrintStream,输出功能很强大,又如BufferedReader提供缓存机制,推荐输出时都使用处理流包装。

注意:一个 IO 流可以即是输入流又是字节流又或是以其他方式分类的流类型,是不冲突的。比如 FileInputStream,它既是输入流又是字节流还是文件节点流。

  • 一些特别的的流类型
    1)转换流:转换流只有字节流转换为字符流,因为字符流使用起来更方便,我们只会向更方便使用的方向转化。如:InputStreamReader 与 OutputStreamWriter。
    2)缓冲流:有关键字 Buffered,也是一种处理流,为其包装的流增加了缓存功能,提高了输入输出的效率,增加缓冲功能后需要使用 flush() 才能将缓冲区中内容写入到实际的物理节点。但是,在现在版本的 Java 中,只需记得关闭输出流(调用 close() 方法),就会自动执行输出流的 flush() 方法,可以保证将缓冲区中内容写入。
    3)对象流:有关键字Object,主要用于将目标对象保存到磁盘中或允许在网络中直接传输对象时使用(对象序列化)。

操作 IO 流的模板

第一步:创建源或目标对象
输入:把文件中的数据流向到程序中,此时文件是源,程序是目标;
输出:把程序中的数据流向到文件中,此时文件是目标,程序是源。

第二步:创建 IO 流对象
输入:创建输入流对象;
输出:创建输出流对象;

第三步:具体的 IO 操作

第四步:关闭资源
输入:输入流的 close() 方法;
输出:输出流的 close() 方法。

复制文件示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void copy(File srcFile, File dstFile) {
FileInputStream in = null;
FileOutputStream out = null;
try {
// 第一、二步
in = new FileInputStream(srcFile);
out = new FileOutputStream(dstFile);
// 第三步
int b;
while ((b = in.read()) != -1) {
out.write(b);
}
} catch(IOException e) {
e.printStackTrace();
} finally {
// 第四步
CloseUtils.closeIO(in, out);
}
}

注意:程序中打开的文件 IO 资源不属于内存里的资源,垃圾回收机制无法回收该资源。如果不关闭该资源,那么磁盘的文件将一直被程序引用着,不能删除也不能更改,所以应该手动调用 close() 方法关闭流资源。

二、整体架构


JAVA 流操作相关的类和接口

说明
File 文件类
RandomAccessFile 随机存取文件类
InputStream 字节输入流
OutStream 字节输出流
Reader 字符输入流
Writer 字符输出流

Java IO 流的整体架构

从图中可以看出四大基类流,如下表所示。这四大基流都是抽象类,其他流都是继承于这四大基流的。

字节流 字符流
输入流 InputStream Reader
输出流 OutStream Writer

用法分析

  • 按数据来源(去向)使用
    是文件: FileInputStream, FileOutputStream, FileReader, FileWriter
    是 byte[]:ByteArrayInputStream, ByteArrayOutputStream
    是 char[]:CharArrayReader, CharArrayWriter
    是 String:StringBufferInputStream, StringReader, StringWriter
    网络数据流:InputStream, OutputStream, Reader, Writer
  • 按是否格式化输出使用
    要格式化输出:PrintStream, PrintWriter
  • 按是否要缓冲使用
    要缓冲:BufferedInputStream, BufferedOutputStream, BufferedReader, BufferedWriter。
  • 按数据格式使用
    二进制格式(只要不能确定是纯文本的): InputStream, OutputStream 及其所有带 Stream 结尾的子类;
    纯文本格式(含纯英文与汉字或其他编码方式);Reader, Writer 及其所有带 eader, Writer 的子类 。
  • 按输入输出使用
    输入:Reader, InputStream 类型的子类;输出:Writer, OutputStream 类型的子类。
  • 特殊需要
    从 Stream 到 Reader、Writer 的转换类:InputStreamReader, OutputStreamWriter;
    对象输入输出:ObjectInputStream, ObjectOutputStream;
    进程间通信:PipeInputStream, PipeOutputStream, PipeReader, PipeWriter ;
    合并输入:SequenceInputStream;
    更特殊的需要:PushbackInputStream, PushbackReader, LineNumberInputStream, LineNumberReader。

虽然四大基类流的实现类很多,功能繁杂,但是他们的命名非常有规律,可以“顾名思义”。如字节流的输入与输出的对应:

图中蓝色的为主要的对应部分,红色的部分就是不对应部分。紫色的虚线部分代表这些流一般要搭配使用。

三、IO 流文件操作


读取文件内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
public void test() throws IOException {
String fileName = "D:"+File.separator+"hello.txt";
File f = new File(fileName);
InputStream in = new FileInputStream(f);
byte[] b = new byte[1024];
int count = 0;
int temp;
while((temp = in.read()) != -1){
b[count++] = (byte)temp;
}
in.close();
System.out.println(new String(b));
}

向文件中写入字符串:

1
2
3
4
5
6
7
8
9
public void test() throws IOException {
String fileName = "D:" + File.separator + "hello.txt";
File file = new File(fileName);
OutputStream out =new FileOutputStream(file);
String str = "你好";
byte[] bytes = str.getBytes();
out.write(bytes);
out.close();
}

向文件中追加新内容:

1
2
3
4
5
6
7
8
9
10
11
12
public void test() throws IOException {
String fileName = "D:" + File.separator + "hello.txt";
File f = new File(fileName);
OutputStream out = new FileOutputStream(f,true);
String str = "Xian";
// String str = "\r\nXian"; // 可以换行
byte[] b = str.getBytes();
for (int i = 0; i < b.length; i++) {
out.write(b[i]);
}
out.close();
}

使用 RandomAccessFile 写入文件:

1
2
3
4
5
6
7
8
9
10
11
12
public void test() throws IOException {
String fileName = "D:"+File.separator+"hello.txt";
File f = new File(fileName);
RandomAccessFile raf = new RandomAccessFile(f,"rw");
raf.writeBytes("xian");
raf.writeInt(12);
raf.writeBoolean(true);
raf.writeChar('A');
raf.writeFloat(1.21f);
raf.writeDouble(12.123);
raf.close();
}

四、字节流


InputStream

InputStream 是所有字节输入流的父类,是一个抽象类。

子类:

ByteArrayInputStream、StringBufferInputStream、FileInputStream 是三种基本的介质流,它们分别从 Byte 数组、StringBuffer 和本地文件中读取数据。PipedInputStream 是从与其它线程共用的管道中读取数据。ObjectInputStream 和所有 FilterInputStream 的子类都是装饰流(装饰器模式的主角)。

方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 返回此输入流下一个方法调用可以不受阻塞地从此输入流读取(或跳过)的估计字节数。
int available()
// 关闭此输入流并释放与该流关联的所有系统资源。
void close()
// 在此输入流中标记当前的位置。
void mark(int readLimit)
// 测试此输入流是否支持 mark 和 reset 方法。
boolean markSupported()
// 从输入流中读取数据的下一个字节。
abstract int read()
// 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。
int read(byte[] b)
// 将输入流中最多 len 个数据字节读入 byte 数组。
int read(byte[] b, int off, int len)
// 将此流重新定位到最后一次对此输入流调用 mark 方法时的位置。
void reset()
// 跳过和丢弃此输入流中数据的 n 个字节。
long skip(long n)

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
public void test() throws Exception { // 异常抛出,不处理
// 第1步、使用File类找到一个文件
File f = new File("d:" + File.separator + "test.txt") ;
// 第2步、通过子类实例化父类对象
InputStream input = null ; // 准备好一个输入的对象
input = new FileInputStream(f); // 通过对象多态性,进行实例化
// 第3步、进行读操作
byte b[] = new byte[1024] ; // 所有的内容都读到此数组之中
input.read(b) ; // 读取内容 网络编程中 read 方法会阻塞
// 第4步、关闭输出流
input.close() ; // 关闭输出流
System.out.println(new String(b)); // 把 byte 数组变为字符串输出
}

OutputStream

OutputStream 是所有的输出字节流的父类,它是一个抽象类。

子类:
(1)OutputStream是以字节为单位的输出流的超类,提供了write()函数从输出流中读取字节数据。
(2)ByteArrayOutputStream是字节数组输出流,写入ByteArrayOutputStream的数据被写入到一个byte数组,缓冲区会随着数据的不断写入而自动增长,可使用toByteArray()和toString()获取数据。
(3)PipedOutputStream是管道输出流,和PipedInputStream一起使用,能实现多线程间的管道通信。
(4)FilterOutputStream是过滤输出流,是DataOutputStream,BufferedOutputStream和PrintStream的超类
(5)DataOutputStream是数据输出流,用来装饰其他的输出流,允许应用程序以与机器无关方式向底层写入基本Java数据类型。
(6)BufferedOutputStream是缓冲输出流,它的作用是为另一个输出流添加缓冲功能。
(7)PrintStream是打印输出流,用来装饰其他输出流,为其他输出流添加功能,方便的打印各种数据值
(8)FileOutputStream是文件输出流,通常用于向文件进行写入操作。
(9)ObjectOutputStream是对象输出流,它和ObjectInputStream一起对基本数据或者对象的持久存储。

方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
// Writes the specified byte to this output stream.
public abstract void write(int b) throws IOException
// Writes b.length bytes from the specified byte array to this output stream.
public void write(byte[] data) throws IOException
// Writes length bytes from the specified byte array starting at offset
// off to this output stream.
public void write(byte[] data, int offset, int length) throws IOException
// Flushes this output stream and forces any buffered output bytes
// to be written out.
public void flush() throws IOException
// Closes this output stream and releases any system resources
// associated with this stream.
public void close() throws IOException

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void test() throws Exception { // 异常抛出,不处理
// 第1步、使用File类找到一个文件
File f = new File("d:" + File.separator + "test.txt");
// 第2步、通过子类实例化父类对象
OutputStream out = null ; // 准备好一个输出的对象
out = new FileOutputStream(f); // 通过对象多态性,进行实例化
// 第3步、进行写操作
String str = "Hello World!!!"; // 准备一个字符串
byte b[] = str.getBytes() ; // 只能输出byte数组,将字符串变为byte数组
out.write(b) ; // 将内容输出,保存文件
// 第4步、关闭输出流
out.close() ; // 关闭输出流
// 文件不存在会自动创建
}

示例:复制文件内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 将 a.txt 文件 复制到 b.txt 中
*/
public void copy() throws IOException {
// 1、创建源和目标
File srcFile = new File("io"+File.separator+"a.txt");
File descFile = new File("io"+File.separator+"b.txt");
// 2、创建输入输出流对象
InputStream in = new FileInputStream(srcFile);
OutputStream out = new FileOutputStream(descFile);
// 3、读取和写入操作
byte[] buffer = new byte[1024];
// 表示已经读取了多少个字节,-1 表示已经读取到文件的末尾
int len = -1;
while((len = in.read(buffer))!= -1) {
// 打印读取的数据
System.out.println(new String(buffer,0,len));
// 将 buffer 数组中从 0 开始,长度为 len 的数据读取到 b.txt 文件中
out.write(buffer, 0, len);
}
// 4、关闭流资源
out.close();
in.close();
}

五、字符流


使用字节流操作汉字或特殊符号语言的时候容易乱码,因为汉字不止一个字节,为了解决这个问题,建议使用字符流。一般可以用记事本打开的文件,我们可以看到内容不乱码的。就是文本文件,可以使用字符流。而操作二进制文件(比如图片、音频、视频)必须使用字节流。

Reader

Reader 是所有的输入字符流的父类,它是一个抽象类。

子类:
CharReader、StringReader 是两种基本的介质流,它们分别将Char 数组、String中读取数据。PipedReader 是从与其它线程共用的管道中读取数据。
BufferedReader 很明显就是一个装饰器,它和其子类负责装饰其它Reader 对象。
FilterReader 是所有自定义具体装饰流的父类,其子类PushbackReader 对Reader 对象进行装饰,会增加一个行号。
InputStreamReader 是一个连接字节流和字符流的桥梁,它将字节流转变为字符流。
FileReader 可以说是一个达到此功能、常用的工具类,在其源代码中明显使用了将 FileInputStream 转变为 Reader 的方法。我们可以从这个类中得到一定的技巧。

Reader 中各个类的用途和使用方法基本和 InputStream 中的类使用一致。

方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 关闭该流并释放与之关联的所有资源。
abstract void close()
// 标记流中的当前位置。
void mark(int readAheadLimit)
// 判断此流是否支持 mark() 操作。
boolean markSupported()
// 读取单个字符。
int read()
// 将字符读入数组。
int read(char[] cbuf)
// 将字符读入数组的某一部分。
abstract int read(char[] cbuf, int off, int len)
// 试图将字符读入指定的字符缓冲区。
int read(CharBuffer target)
// 判断是否准备读取此流。
boolean ready()
// 重置该流。
void reset()
// 跳过字符。
long skip(long n)

示例:

1
2
3
4
5
6
7
8
9
10
11
12
public void test() throws IOException {
//创建一个文件读取流对象,和指定名称的文件相关联起来。
//要保证该文件是已经存在的。如果不存在,会发生异常,即FileNotFoundException
FileReader reader = new FileReader("F:\\myFile\\test.txt");
//调用读取流对象的read方法。
//read方法:一次读取一次字符,而且会自动往后面读取字符。
int ch = 0;
while((ch = reader.read()) != -1) {
System.out.print((char)ch);
}
fr.close();
}

Writer

Writer 是所有的输出字符流的父类,它是一个抽象类。

子类:
CharArrayWriter、StringWriter 是两种基本的介质流,它们分别向Char 数组、String 中写入数据。PipedWriter 是向与其它线程共用的管道中写入数据,
BufferedWriter 是一个装饰器为 Writer 提供缓冲功能。
PrintWriter 和 PrintStream 极其类似,功能和使用也非常相似。
OutputStreamWriter 是 OutputStream 到 Writer 转换的桥梁,它的子类 FileWriter,功能和使用和 OutputStream 极其类似。

方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 将指定字符添加到此 writer。
Writer append(char c)
// 将指定字符序列添加到此 writer。
Writer append(CharSequence csq)
// 将指定字符序列的子序列添加到此 writer.Appendable。
Writer append(CharSequence csq, int start, int end)
// 关闭此流,但要先刷新它。
abstract void close()
// 刷新该流的缓冲。
abstract void flush()
// 写入字符数组。
void write(char[] cbuf)
// 写入字符数组的某一部分。
abstract void write(char[] cbuf, int off, int len)
// 写入单个字符。
void write(int c)
// 写入字符串。
void write(String str)
// 写入字符串的某一部分。
void write(String str, int off, int len)

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
public void test() throws IOException {
// 第1步、使用File类找到一个文件
File f = new File("d:" + File.separator + "test.txt");
// 第2步、通过子类实例化父类对象
Writer out = null; // 准备好一个输出的对象
out = new FileWriter(f); // 通过对象多态性,进行实例化
// 第3步、进行写操作
String str = "Hello World!!!"; // 准备一个字符串
out.write(str) ; // 将内容输出,保存文件
// 第4步、关闭输出流
out.close() ; // 关闭输出流
}

示例

用字符流完成文件的复制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 将 a.txt 文件 复制到 b.txt 中
*/
public void copy() throws IOException {
// 1、创建源和目标
File srcFile = new File("io"+File.separator+"a.txt");
File descFile = new File("io"+File.separator+"b.txt");
// 2、创建字符输入输出流对象
Reader in = new FileReader(srcFile);
Writer out = new FileWriter(descFile);
// 3、读取和写入操作
char[] buffer = new char[1024];
// 表示已经读取了多少个字节,-1 表示已经读取到文件的末尾
int len;
while((len=in.read(buffer))!=-1){
out.write(buffer, 0, len);
}
// 4、关闭流资源
out.close();
in.close();
}

六、包装流


包含缓冲流,转换流对象流等等。
①、包装流隐藏了底层节点流的差异,并对外提供了更方便的输入\输出功能,让我们只关心这个高级流的操作。
②、使用包装流包装了节点流,程序直接操作包装流,而底层还是节点流和IO设备操作。
③、关闭包装流的时候,只需要关闭包装流即可。

缓冲流

缓冲流:是一个包装流,目的是缓存作用,加快读取和写入数据的速度。
字节缓冲流:BufferedInputStream、BufferedOutputStream
字符缓冲流:BufferedReader、BufferedWriter

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void test() throws Exception {
//字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(
new FileInputStream("io"+File.separator+"a.txt"));
//定义一个字节数组,用来存储数据
byte[] buffer = new byte[1024];
//定义一个整数,表示读取的字节数
int len = -1;
while((len=bis.read(buffer))!=-1){
System.out.println(new String(buffer,0,len));
}
//关闭流资源
bis.close();
//字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("io"+File.separator+"a.txt"));
bos.write("Hello,先小涛".getBytes());
bos.close();
}

字符缓冲流使用与字节缓冲流非常像,直接替换上例中对应的类即可。

转换流

把字节流转换为字符流。
InputStreamReader:把字节输入流转换为字符输入流
OutputStreamWriter:把字节输出流转换为字符输出流

示例:用转换流进行文件的复制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void copy() throws IOException {
//1、创建源和目标
File srcFile = new File("io"+File.separator+"a.txt");
File descFile = new File("io"+File.separator+"b.txt");
//2、创建字节输入输出流对象
InputStream in = new FileInputStream(srcFile);
OutputStream out = new FileOutputStream(descFile);
//3、创建转换输入输出对象
Reader rd = new InputStreamReader(in);
Writer wt = new OutputStreamWriter(out);
//3、读取和写入操作
char[] buffer = new char[1024];
int len = -1;
while((len=rd.read(buffer))!=-1){
wt.write(buffer, 0, len);
}
//4、关闭流资源
rd.close();
wt.close();
}

OutputStreamWriter 和 InputStreamReader 是字符和字节的桥梁:也可以称之为字符转换流。字符转换流原理:字节流+编码表。

FileWriter 和 FileReader:作为子类,仅作为操作字符文件的便捷类存在。当操作的字符文件,使用的是默认编码表时可以不用父类,而直接用子类就完成操作了,简化了代码。

1
2
3
4
5
6
//默认字符集。
InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"));
//指定GBK字符集。
InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"),"GBK");
FileReader fr = new FileReader("a.txt");

这三句代码的功能是一样的,其中第三句最为便捷。

注意:一旦要指定其他编码时,绝对不能用子类,必须使用字符转换流。

内存流

把数据先临时存在数组中,也就是内存中。所以关闭内存流是无效的,关闭后还是可以调用这个类的方法。底层源码的 close() 是一个空方法。

字节内存流:ByteArrayOutputStream 、ByteArrayInputStream
字符内存流:CharArrayReader、CharArrayWriter
字符串流:StringReader,StringWriter(把数据临时存储到字符串中)

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void test() throws Exception {
// 字节数组输出流:程序---》内存
ByteArrayOutputStream bos = new ByteArrayOutputStream();
// 将数据写入到内存中
bos.write("hello".getBytes());
// 创建一个新分配的字节数组。 其大小是此输出流的当前大小,
// 缓冲区的有效内容已被复制到其中。
byte[] temp = bos.toByteArray();
System.out.println(new String(temp,0,temp.length));
byte[] buffer = new byte[10];
// 字节数组输入流:内存---》程序
ByteArrayInputStream bis = new ByteArrayInputStream(temp);
int len = -1;
while((len=bis.read(buffer))!=-1){
System.out.println(new String(buffer,0,len));
}
// 这里不写也没事,因为源码中的 close()是一个空的方法体
bos.close();
bis.close();
}

合并流

把多个输入流合并为一个流,也叫顺序流,因为在读取的时候是先读第一个,读完了在读下面一个流。

1
2
3
4
5
6
7
8
9
10
11
12
13
public void test() throws Exception {
//定义字节输入合并流
SequenceInputStream seinput = new SequenceInputStream(
new FileInputStream("io/a.txt"),
new FileInputStream("io/b.txt"));
byte[] buffer = new byte[10];
int len = -1;
while((len=seinput.read(buffer)) != -1){
System.out.println(new String(buffer,0,len));
}
seinput.close();
}

七、序列化与反序列化(对象流)

what

序列化:指把堆内存中的 Java 对象数据,通过某种方式把对象存储到磁盘文件中或者传递给其他网络节点(在网络上传输)。这个过程称为序列化。通俗来说就是将数据结构或对象转换成二进制串的过程。
反序列化:把磁盘文件中的对象数据或者把网络节点上的对象数据,恢复成Java对象模型的过程。也就是将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程。

why

①、在分布式系统中,此时需要把对象在网络上传输,就得把对象数据转换为二进制形式,需要共享的数据的 JavaBean 对象,都得做序列化。
②、服务器钝化:如果服务器发现某些对象好久没活动了,那么服务器就会把这些内存中的对象持久化在本地磁盘文件中(Java对象转换为二进制文件);如果服务器发现某些对象需要活动时,先去内存中寻找,找不到再去磁盘文件中反序列化我们的对象数据,恢复成 Java 对象。这样能节省服务器内存。

how

①、需要做序列化的对象的类,必须实现序列化接口:Java.lang.Serializable 接口(这是一个标志接口,没有任何抽象方法),Java 中大多数类都实现了该接口,比如:String,Integer。
②、底层会判断,如果当前对象是 Serializable 的实例,才允许做序列化,Java对象 instanceof Serializable 来判断。
③、在 Java 中使用对象流来完成序列化和反序列化。
ObjectOutputStream:通过 writeObject()方法做序列化操作
ObjectInputStream:通过 readObject() 方法做反序列化操作

demo

第一步:创建一个 JavaBean 对象

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
public class Person implements Serializable {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
}

第二步:使用 ObjectOutputStream 对象实现序列化

1
2
3
4
5
// 在根目录下新建一个 io 的文件夹
OutputStream op = new FileOutputStream("io"+File.separator+"a.txt");
ObjectOutputStream ops = new ObjectOutputStream(op);
ops.writeObject(new Person("vae",1));
ops.close();

第三步:使用ObjectInputStream 对象实现反序列化

1
2
3
4
5
6
7
InputStream in = new FileInputStream("io"+File.separator+"a.txt");
ObjectInputStream os = new ObjectInputStream(in);
byte[] buffer = new byte[10];
int len = -1;
Person p = (Person) os.readObject();
System.out.println(p); //Person [name=vae, age=1]
os.close();

question

问题1:如果某些数据不需要做序列化,比如密码,比如上面的年龄?
解决办法:在字段面前加上 transient。

1
2
private String name;//需要序列化
transient private int age;//不需要序列化

那么我们在反序列化的时候,打印出来的就是Person [name=vae, age=0],整型数据默认值为 0。

问题2:序列化版本问题,在完成序列化操作后,由于项目的升级或修改,可能我们会对序列化对象进行修改,比如增加某个字段,那么我们在进行反序列化就会报错,怎么办?
解决办法:在 JavaBean 对象中增加一个 serialVersionUID 字段,用来固定这个版本,无论我们怎么修改,版本都是一致的,就能进行反序列化了。

1
private static final long serialVersionUID = 8656128222714547171L;

参考链接:
Java IO流详解
最完整的javaIO流总结
Java IO流详解