Java 高级:I/O
目录
目录
FileInputStream和FileOutputStream
IO流概述
- I/O是Input/Output的缩写, I/O技术是非常实用的技术,用于 处理设备之间的数据传输。如读/写文件,网络通讯等。
- Java程序中,对于数据的输入/输出操作以“流(stream)” 的 方式进行。
- java.io 包下提供了各种“流”类和接口,用以获取不同种类的 数据,并通过标准的方法输入或输出数据。
输入 input:读取外部数据(磁盘,光盘等存储设备)到程序(内存)中。
输出 output:将程序内存数据输出到磁盘,光盘等存储设备中。
流的分类
- 按操作数据单位不同分为:字节流(8 bit),字符流(16 bit)
- 字节流适用于图片视频等文件
- 字符流使用与纯文本的文件
- 按数据流的流向不同分为:输入流,输出流
- 按流的角色不同分为:节点流,处理流
- 直接作用于在文件上的流成为节点流,比如想把某个文件加入到内存中去,我们在文件和内存中造出一条直接的流,这就是节点流。更直接的语言表示,相当于把水池子的水通过水管放入澡盆,那么连接的这根水管就是节点流。
- 处理流 更抽象一点,它相当于封装了节点流,对节点流进行处理,比如以水池子的水通过水管放入澡盆这个为例,如果想让水流的更快,通过处理流进行了 一层包裹。
节点流
FileReader
节点流读入数据的基本操作
读取文件:
- 实例化File,指明要操作的文件
- 建立一个流对象,将已存在的一个文件加载进流
- 调用流对象的读取方法将流中的数据读入到数组中。
- 关闭资源
实例(一):使用FileReader读取文本文件到控制台
/**
* 将h1.txt读取到内存中
* @throws IOException
*/
@Test
public void test() throws IOException {
// 实例化File,指明要操作的文件
File file = new File("D:\\io\\1.txt");
// 提供具体的流
FileReader fileReader = new FileReader(file);
// read()返回读入的字符,如果达到文件末尾,则返回 -1
int readData = fileReader.read();
while (readData != -1) {
System.out.println((char) readData);
//读取下一个字符 直到 = -1 为止
readData = fileReader.read();
}
// 流的关闭操作
fileReader.close();
}
需要注意的是关于异常的处理,之前的例子我们把异常直接抛出,这会导致当某个步骤出现问题的时候,接下来的程序将不再执行,那么流是未被释放的。这会导致内存泄漏以及资源浪费。
所以我们需要用try catch的方式捕捉异常,并且最后一定会关闭流。
/**
* 将h1.txt读取到内存中
*/
@Test
public void test() {
FileReader fileReader = null;
try {
// 实例化File,指明要操作的文件
File file = new File("D:\\io\\1.txt");
// 提供具体的流
fileReader = new FileReader(file);
// read()返回读入的字符,如果达到文件末尾,则返回 -1
int readData = fileReader.read();
while (readData != -1) {
System.out.println((char) readData);
//读取下一个字符 直到 = -1 为止
readData = fileReader.read();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
// 流的关闭操作本身也会有异常,但如果一开始创建fileReader对象就抛出异常,那么在这里的fileReader则为null
// 这又导致空指针异常,因此需要做处理
if (fileReader != null) {
fileReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
总结:
- read()的理解:返回读取的一个字符,如果文件达到末尾,则返回1。
- 异常的处理:为了保证流资源一定可以执行关闭操作,使用 try-catch-finally 来处理。
- 读入的文件一定要存在,否则会报出FileNotFoundException的异常。
FileReader中使用read()的重载方法(常用)
对于读取文件,不外乎创建File实例,创建流实例,执行读取操作,以及关闭资源。
read()的重载方法在于执行读取操作的不同。
这里我们用到了 `read(char[] cbuf)`
实例(二):使用FileReader读取文本文件到控制台
@Test
public void test2() {
FileReader fileReader = null;
try {
// 1.File类的实例化
File file = new File("D:\\io\\1.txt");
// 2.FileReader流的实例化
fileReader = new FileReader(file);
char[] cbuf = new char[5];
int len;
// 3.读入操作 read(char[] cbuf):返回每次读入cbuf数组中字符的个数,如果达到文件末尾,返回1
while ((len = fileReader.read(cbuf)) != -1) {
//这里需要注意的是 循环的是len,len有几次就遍历几次,而不是数组的长度.
for (int i = 0; i < len; i++) {
System.out.print(cbuf[i]);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 流的关闭操作
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
实例(三):使用FileReader读取文本文件到控制台,使用String的方式来接受数据。
@Test
public void test2() {
FileReader fileReader = null;
try {
// 1.File类的实例化
File file = new File("hello.txt");
// 2.FileReader流的实例化
fileReader = new FileReader(file);
char[] cbuf = new char[5];
int len;
// 3.读入操作 read(char[] cbuf):返回每次读入cbuf数组中字符的个数,如果达到文件末尾,返回1
while ((len = fileReader.read(cbuf)) != -1) {
//第二种写法
String str = new String(cbuf, 0, len);
System.out.println(str);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 流的关闭操作
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
FileWriter
写入文件的步骤:
- 创建流对象,建立数据存放文件
- 调用流对象的写入方法,将数据写入流
- 关闭流资源,并将流中的数据清空到文件中。
/**
* 从内存中写出数据到硬盘里
* 如果文件不存在,则创建
* 如果文件存在,根据构造器的不同
* 如果是 FileWriter(file,true) 则追加
* 如果是 FileWriter(file,false) 则覆盖
*/
@Test
public void testFileWriter() {
FileWriter fileWriter = null;
try {
// 1. 创建File实例
File file = new File("h1.txt");
// 2.创建FileWriter的实例
fileWriter = new FileWriter(file, false);
// 3.写入文件
fileWriter.write("小蟹写代码!");
fileWriter.write("真的很难!");
// 4.关闭流
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fileWriter != null) {
fileWriter.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
FileInputStream和FileOutputStream
Reader和Writer无法处理字节流,也就是无法处理非文本文件(.jpg ,.mp3 ,.mp4,.doc,.ppt)
对于非文本文件,使用字节流处理。
通过FileInputStream读取一张图片,把读取的文件通过FileOutputStream写出来:
/**
* 通过FileInputStream读取一张图片,把读取的文件通过FileOutputStream写出来.
*/
@Test
public void test() {
FileInputStream is1 = null;
FileOutputStream is2 = null;
try {
File file1 = new File("D:\\Enviroment\\image\\2.jpg");
File file2 = new File("D:\\Enviroment\\image\\3.jpg");
is1 = new FileInputStream(file1);
is2 = new FileOutputStream(file2);
byte[] readByte = new byte[5];
int len;
while ((len = is1.read(readByte)) != -1) {
is2.write(readByte, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
is1.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
is2.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
实例(二):抽取使用FileInputStream和FileOutputStream对于文件的复制操作
这样的一段代码可以抽取出来,它相当于复制某个文件的功能,抽取出来以后我们可以再试着看看复制视频所花费的时间,以便接下来对于缓冲流的学习。
/**
* 复制文件
* @param filepath
* @param File2
*/
public static void copyFile(String filepath, String File2) {
FileInputStream is1 = null;
FileOutputStream is2 = null;
try {
File file = new File(filepath);
File file2 = new File(File2);
is1 = new FileInputStream(filepath);
is2 = new FileOutputStream(File2);
byte[] readByte = new byte[5];
int len;
while ((len = is1.read(readByte)) != -1) {
is2.write(readByte, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
is1.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
is2.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
传入稍微大一点的文件来操作,便于时间上的观察,复制了一个flv格式的视频。
结果:
复制操作花费的时间为:4135
/**
* 通过字节流读取文件并复制写出
*/
@Test
public void testCopyFile() {
long start = System.currentTimeMillis();
String filePath1 = "D:\\BaiduNetdiskDownload\\04 Spring\\01.Spring框架简介\\01.spring课程四天安排.flv";
String filePath2 = "D:\\BaiduNetdiskDownload\\04 Spring\\01.Spring框架简介\\01.spring课程四天安排2.flv";
copyFile(filePath1, filePath2);
long end = System.currentTimeMillis();
System.out.println("复制操作花费的时间为:" + (end - start));
}
缓冲流
为了提高数据读写的速度,JavaAPI提高了带缓冲功能的流类,在使用这些流时,会创建一个内部缓冲区数组,缺省使用8192个字节(8kb)的缓冲区。
源码可以看出:DEFAULT_BUFFER_SIZE = 8192
缓冲流要 “套接”在相应的节点流之上,根据数据操作单位可以把缓冲流分为:
字节流:BufferedInputStream 和 BufferedOutputStream
字符流:BufferdReader 和 BufferdWriter
对于缓冲流的理解:
- 当读取数据时,数据按块读入缓冲区,其后的读操作则直接访问缓冲区
- 当使用BufferdInputStream读取字节文件时,BufferdInputStream会一次读取8192个,存在缓冲区中,直到缓冲区装满了,才重新从文件中读取下一个8192个字节数组
- 向流中写字节时,不会直接写到文件,先写道缓冲区直到缓冲区写满。BufferdOutputStream才会把缓冲区中的数据一次性写到文件里。
- 使用flush()可以强制将缓冲区的内容全部写入输出流
- 关闭流的时候,只要关闭最外层的流即可,关闭最外层流也会相应关闭内存节点流。
- flush()方法的使用:手动将buffer中内容写入文件
- 如果是带缓冲区的流的Close()方法,不但会关闭流,还会再关闭流之前刷新缓冲区,关闭后不能再写出
为什么缓冲流会更快?
因为缓冲流是先把文件的数据缓冲8192个字节/字符到缓冲区内存中,然后冲内存中读取1024个字节/字符,从内存中读取要比从硬盘中读取的效率高。
实例(一):使用缓冲流实现非文本文件的复制
/**
* 使用缓冲流实现非文本文件的复制
*/
@Test
public void bufferInputStreamTest() {
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
// 1.File实例
File fileRead = new File("1564588424(1).jpg");
File fileWrite = new File("harry.jpg");
// 2.流实例
FileInputStream is = new FileInputStream(fileRead);
FileOutputStream os = new FileOutputStream(fileWrite);
// 3.缓冲流实例
bis = new BufferedInputStream(is);
bos = new BufferedOutputStream(os);
// 4.读取文件
byte[] raadData = new byte[1024];
int len;
while ((len = bis.read(raadData)) != -1) {
String str = new String(raadData, 0, len);
// 5. 写入文件
bos.write(raadData, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 6.关闭流
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
实例(二)抽取使用缓冲流复制文件的操作
/**
* 使用缓冲流复制文件
* @param filePath1
* @param filePath2
*/
public static void bufferedCopyFile(String filePath1 ,String filePath2) {
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
// 1.File实例
File fileRead = new File(filePath1);
File fileWrite = new File(filePath2);
// 2.流实例
FileInputStream is = new FileInputStream(fileRead);
FileOutputStream os = new FileOutputStream(fileWrite);
// 3.缓冲流实例
bis = new BufferedInputStream(is);
bos = new BufferedOutputStream(os);
// 4.读取文件
byte[] raadData = new byte[1024];
int len;
while ((len = bis.read(raadData)) != -1) {
String str = new String(raadData, 0, len);
// 5. 写入文件
bos.write(raadData, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 6.关闭流
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
实例(三)使用缓冲流对视频进行复制
/**
* 使用缓冲流进行对视频的复制
*/
@Test
public void useBufferCompareTo() {
long start = System.currentTimeMillis();
String filePath1 = "D:\\BaiduNetdiskDownload\\04 Spring\\01.Spring框架简介\\01.spring课程四天安排.flv";
String filePath2 = "D:\\BaiduNetdiskDownload\\04 Spring\\01.Spring框架简介\\01.spring课程四天安排_buffer.flv";
bufferedCopyFile(filePath1,filePath2);
long end = System.currentTimeMillis();
System.out.println("复制操作花费的时间为:" + (end - start));
}
结果:
复制操作花费的时间为:147,在上面的我们也对此视频进行了复制,所花费的时间是4135,快了非常多。
实例(四) 使用BufferdReader和BufferdWirter对文本文件进行复制
/**
* 使用BufferdReader和BufferdWirter对文本文件进行复制.
*/
@Test
public void testBufferReaderAndeWriter() {
BufferedReader br = null;
BufferedWriter bw = null;
try {
// 1.创建文件和缓冲流
br = new BufferedReader(new FileReader(new File("test1.txt")));
bw = new BufferedWriter(new FileWriter("test2.txt"));
// 2. 读取操作,方式一
char[] cbvf = new char[1024];
int len;
while ((len = br.read(cbvf)) != -1){
// 3.写操作
bw.write(cbvf,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 4.关闭流
if( br!=null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bw!=null){
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
实例(五) 第一种方式 ,使用BufferdReader和BufferdWirter对文本文件进行复制,用`readLine()`方法
`readLine()` 使用String类型来接受,它一次只能读取一行,并且不自带换行功能。
/**
* 使用BufferdReader和BufferdWirter对文本文件进行复制,用`readLine()`方法
*/
@Test
public void test2BufferReaderAndeWriter() {
BufferedReader br = null;
BufferedWriter bw = null;
try {
// 1.创建文件和缓冲流
br = new BufferedReader(new FileReader(new File("test1.txt")));
bw = new BufferedWriter(new FileWriter("test2.txt"));
// 2. 读取操作,方式二
String str;
int len;
//readLine 一次读取一行
while ((str = br.readLine())!=null) {
bw.write(str); // 一次写入一行字符串
bw.newLine();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 4.关闭流
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bw != null) {
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
test1.txt
test2.txt 复制后未换行
转换流的使用
转换流提供了在字节流和字符流之间的转换
Java API提供了两个转换流:
-
InputStreamReader:将InputStream转换为Reader 将字节输入流转换为字符输入流
-
OutputStreamWriter:将Writer转换为OutputStream 将字符输出流转换为字节输出流
字节流中的数据都是字符时,转成字符流操作更高效。
很多时候我们使用转换流来处理文件乱码问题。实现编码和 解码的功能。
相当于解码和编码
解码:字节,字节数组 ---------->字符,字符数组
编码:字符,字符数组 ---------->字节,字节数组
这里涉及到字符集的问题,具体在实例中说明。
实例(一):使用转换流读取文本文件到控制台
这里使用转换流的空参构造器,空参构造器指定的字符集是系统默认的字符集,也就是IDEA中的字符集。
它有第二个参数,指明字符集,具体使用哪个字符集,取决于test.1保存时使用的字符集
/**
* 使用转换流读取文本文件到控制台
*/
@Test
public void inputStreamReaderTest(){
InputStreamReader isr = null;
try {
// 1.创建File实例
File file = new File("test1.txt");
// 2.创建流实例
FileInputStream is = new FileInputStream(file);
// 3.创建转换流实例
isr = new InputStreamReader(is);
int len;
char[] cbuf = new char[1024];
// 4.读取文件
while( (len=isr.read(cbuf)) !=-1){
String str = new String(cbuf,0,len);
// 5.输出到控制台
System.out.println(str);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (isr!=null){
// 6.关闭流
try {
isr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
控制台结果:
如果使用带有字符集参数的构造器,test1.txt是用UTF-8的字符集,如果转换为GBK。
// 3.创建转换流实例
isr = new InputStreamReader(is,"GBK");
控制台输出乱码
实例(二):读取一个字符集为 utf-8的文件,并通过GBK的字符集输出出来.
/**
* 综合使用OutputStreamWriter和InputStreamReader
* 读取一个字符集为 utf-8的文件,并通过GBK的字符集输出出来.
*/
@Test
public void outPutStreamWriterTest() {
InputStreamReader isr = null;
OutputStreamWriter osw = null;
try {
File file1 = new File("test1.txt");
File file2 = new File("test2.txt");
FileInputStream is = new FileInputStream(file1);
FileOutputStream os = new FileOutputStream(file2);
isr = new InputStreamReader(is, StandardCharsets.UTF_8);
osw = new OutputStreamWriter(os, "GBK");
char[] cbuf = new char[1024];
int len;
while ((len = isr.read(cbuf)) != -1) {
osw.write(cbuf, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (isr != null) {
isr.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (osw != null) {
osw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
执行后:
reload in GBK一下就能正常阅读了
对象流
ObjectInputStream和OjbectOutputSteam
用于存储和读取基本数据类型数据或对象的处理流,它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。
序列化:用ObjectOutputStream类保存基本类型数据或对象的机制
反序列化:用ObjectInputStream类读取基本类型的数据或对象的机制
ObjectOutputStream和ObjectInputStream 不能序列化static和transient修饰的成员变量
对象序列化机制
- 序列化:允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或者通过网络将这种二进制流传输到另一个网络节点
- 反序列化:当其他程序获取了这种二进制流,就可以恢复成原来的Java对象
如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可以序列化的,为了让某个类是可以序列化的,该类必须实现如下两个接口之一,否则会抛出NotSerializableException异常
-
Serializable
-
Externalizable
实例(一):使用ObjectOutputStream实现序列化,将String类型的对象保存在硬盘上
/**
* 序列化过程:将内存中的java对象保存到硬盘或者通过网络传输出去
* 使用ObjectOutputStream实现序列化,将 String类型的对象保存在硬盘上.
*/
@Test
public void objectOutputStreamTest() {
ObjectOutputStream oos = null;
try {
//1.创建流
FileOutputStream fis = new FileOutputStream(new File("test.dat"));
//2.创建ObjectOutputStream实例
oos = new ObjectOutputStream(fis);
String str = "什么是序列化呢?是将内存中的java对象保存到硬盘或者通过网络传输出去";
// 3. 写出数据
oos.writeObject(str);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (oos != null) {
try {
// 4.释放资源
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
实例(二):使用ObjectOutputStream实现序列化,将String类型的对象保存在硬盘上
/**
* 反序列化:将保存在test.dat里的对象读取出来
* 通过ObjectInputStream将刚刚保存在硬盘上的数据读取出来
*/
@Test
public void objectInputStreamTest() {
ObjectInputStream ois = null;
try {
// 1. FileInputStream 实例
FileInputStream fis = new FileInputStream(new File("test.dat"));
// 2.ObjectOutputStream 实例
ois = new ObjectInputStream(fis);
// 3.读取文件 因为我们知道是用String对象,所以直接强转即可.
String str = (String) ois.readObject();
System.out.println(str);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (ois != null) {
try {
// 4.关闭流
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
对象的序列化
凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态常量:
- private static final long serialVersionUID;
- serialVersionUID用来表明类的不同版本间的兼容性。简言之,其目的是以序列化对象 进行版本控制,有关各版本反序列化时是否兼容。
- 如果类没有显示定义这个静态常量,它的值是Java运行时环境根据类的内部细节自 动生成的。若类的实例变量做了修改,serialVersionUID 可能发生变化。故建议, 显式声明。
简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验 证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的 serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同 就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异 常。(InvalidCastException)
再直白一点来说,如果我们需要存放一个Person类,我们已经把它序列化了,但是没有加serialVersionUID,然后也存放好了,此时如果再对Person类进行修改,比如添加几个属性,或者方法。此时Person类自动生成的UID根据内部细节的改变也改变了,再反序列化的时候,这个已经修改过了Person类已经不再是最开始的Person类了,于是在反序列化的时候,就出现了问题。
我们从实例中来理解上诉理论,接下来我们将试着将实体类对象存储到硬盘中 先创建一个实体类 Person
/**
* 实体类对象Person
* @author Claw
*/
public class Person {
private String name;
private Integer age;
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
实例(二)使用ObjectOutputStream实现Person类的序列化,将Person类型的对象保存在test.dat文件中.
/**
* 使用ObjectOutputStream实现Person类的序列化,将Person类型的对象保存在test.dat文件中.
*/
@Test
public void objectOutputStreamTest() {
ObjectOutputStream oos = null;
try {
// 1.创建流
FileOutputStream fis = new FileOutputStream(new File("test.dat"));
// 2.创建ObjectOutputStream实例
oos = new ObjectOutputStream(fis);
// 3. 创建实体类对象Person实例
Person person = new Person("小蟹",16);
// 4. 将Person类对象写出到硬盘中
oos.writeObject(person);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (oos != null) {
try {
// 5.释放资源
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
此时抛出异常:java.io.NotSerializableException
在对象序列化的机制里已经说过了,如果想让某个对象支持序列化,则必须让对象所属的类及其属性是可序列化的。
所以我们序列化的类需要实现Serializable接口,我们再写出String类型的时候,为什么可以序列化呢?点击String类的源码,其实可以看到String类已经 implements了 Serilazable接口
Serilazable 是一个标识接口,它什么抽象方法都没有,这种接口一般称为标识接口,凡是实现这个接口的类,都识别为可序列化的。
光实现这个接口还不够,在源码中可以看到这样一行注释:
它希望我们提供一个声明为 static final long serialVersionUID 的属性
这个UID的值可以随便写一个,这个是序列版本号。在不写的情况下,serialVersionUID的值会根据内部细节自动生成。但如果类的实例变量做了修改,serialVersionUID会发生变化,所以我们需要显示声明。
改动一下:
/**
* 实体类对象Person
* @author Claw
*/
public class Person implements Serializable {
private static final long serialVersionUID = 212313413L;
private String name;
private Integer age;
// ......构造方法之类的已省略
}
此时再把这个Person保存到test.dat中就能运行成功,同时也能读取出来。
如果此时改动Person类,增加一个属性。
/**
* 实体类对象Person
* @author Claw
*/
public class Person implements Serializable {
private static final long serialVersionUID = 212313413L;
private String name;
private Integer age;
//增加一个属性
private String sex;
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
// 增加一个构造方法
public Person(String name, Integer age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
// toString方法也重新生成
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
此时再去反序列化读取内容,也能读取出来
结果为:sex因为没有赋值,所以是null
Person{name='小蟹', age=16, sex='null'}
如果没有显示的声明serialVersionUID,再改动Person类后会报错。
总结:
序列化需要满足如下要求:
- 实现接口:Serializable
- 当前类提供一个全局常量:serialVersionUID
- 除了当前类需要实现Serializable接口,还要保证其内部所有属性必须是可序列化的。(默认情况下,基本类型数据可序列化)