Java IO
IO的基本概念
输入(Input)与输出(Output)
文件的输入和输出哪个是用来读的?哪个用来写的? —— 输入用来读的,输出是用来写的!计算机输入输出和人类认知相反,是因为,输入输出的参照物是程序本身,进入到程序中就是输入input,从程序中输出就是output,一定是相对于程序而言的。
用一种更好理解的方式:
就例如我们发现一本好书,我们读(Read)这本书的过程就是书中的数据通过神经流(Stream)通过某个管道(输入流)输入(Input)到脑海;在写(Write)文章的时候我们发现可以用到这本书中的某句话,那么我们就将脑海里的数据通过神经的流(Stream)经过某个管道(输出流)输出(Output)到纸上。
流的概念
Java将IO比喻为“流”(Stream)。就像是生活中的“电流”、“水流”一样,它是以同一个方向顺序移动的过程。只不过这里流动的是字节(2进制数据)。所以在IO中有输入流和输出流之分,我们理解他们是连接程序与另一端的“管道”,用于获取和发送数据到另一端;
-
流(Stream)是我们连接程序和另一端设备的一根管道,通过这个管道来进行读写;
-
我们观察角度一定要在程序上,设备到程序创建输入流,程序到设备输出流;
- 内存(程序)中的数据通过管道读(Read)取文件 —— 这个过程即“输入数据”
- 内存(程序)中的数据通过管道写(Write)入文件 —— 这个过程即“输出数据”
InputStream和OutputStream
-
java.io.InputStream
:所有字节输入流的父类,其中定义了读取数据的方法。因此在之后不管读取数据的是什么设备(连接设备的流),都具有这些读取的方法,因此我们可以用相同的方法来读取不同设备中的数据。 -
java.io.OutputStream
:所有字节输出流的父类,其中定义了写出数据的方法。
OutputStream输出流
-
实例化一个输出流管道:
FileOutputStream fileOutputStream = new FileOutputStream("filePath");
-
OutputStream是所有字节输出流的父类,其定义了基础的写入方法,常用方法如下:
-
fileOutputStreamName.write(int d)
:写出一个字节,写的是给定的int的“二进制低八位”; -
注意:任何流管道打开(使用)以后必须关闭管道,原因是为了防止资源泄露和数据丢失;
-
InputStream输入流
-
fileInputStreamName.read()
:读取一个字节,以int形式返回,该int值的“低八位”二进制有效,若返回值为-1则表示EOF(End of File); -
fileInputStreamName.read(byte[] d)
:尝试最多读取给定数组的length个字节并存入该数组,返回值为实际读取到的字节量。
文件流
文件流的作用
-
文件流是实际连接程序与硬盘中文件的“管道”,用于读写文件数据的流;
-
文件流以字节为单位读写文件数据;
-
文件输出流:可以将数据从内存写入到磁盘文件中,可以将数据持久保存;
-
文件输入流:可从硬盘文件中将数据读取回到内存中使程序回复原有状态。
构造器
常用构造器
-
构造器:
-
FileInputStream(String filename)
:创建一个从具有指定名称的文件中读取数据的文件输入流;package day16; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; public class FosDemo { public static void main(String[] args) throws IOException; // 创建从当前目录下的fos.dat文件中读取数据的文件输入流 FileOutputStream fos = new FileOutputStream("fos.dat"); } }
-
FileInputStream(File file)
:创建一个从指定File对象表示的文件中读取数据的文件输入流;package day16; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; public class FosDemo { public static void main(String[] args) throws IOException; // 创建从当前项目目录下的fos.dat文件中读取数据的文件输入流 File file = new File("fos.dat"); FileOutputStream fos = new FileOutputStream(file); } }
-
如果指定的文件不存在会抛出异常:
FileNotFoundException
。
-
写出字节数据
-
文件输出流继承自
java.io.OutputStream
; -
文件输出流提供了父类中要求写出字节的相关方法;
-
fileOutputStreamName.write(int d)
:写出一个字节。写出的是给定int值d对应二进制的“低八位”;package day16; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; /** * 文件流:是连接程序与文件的通道,从而可以读写文件数据; * 文件输出流(FileOutputStream):用于向文件中写入数据; */ public class FosDemo { public static void main(String[] args) throws IOException { // 创建了一个文件的输出流(管道),用于向fos.dat文件中写入数据 // 创建文件输出流时,若文件不存在则会自动创建出来,若已存在该文件将覆盖源文件; // 若目录不存在,则会报错; FileOutputStream fos = new FileOutputStream("fos.dat"); fos.write(1); // 其实这里写入的是1的二进制低八位表示,1字节 = 32位,即00000000 00000000 00000000 00000001 --低八位--> 00000001 fos.write(2); // 其实这里写入的是2的二进制低八位表示,1字节 = 32位,即00000000 00000000 00000000 00000010 --低八位--> 00000010 System.out.println("写出结束!"); // 实际fos获得的数据是:00000001 00000010 -> 所以会导致输出进入文件一个奇怪的东西,这里暂时不必理会,感受写入的过程即可 fos.close(); // 关闭文件输出流 } }
-
当我们要连续写入更多字符时,就需要用到追加(append)模式:
-
构造方法:
FileOutputStream(File file, boolean append)
:创建一个向指定File对象表示的文件中写出数据的文件输出流;
package day16; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; /** * 向文件中写入字符数据 */ public class Test1 { public static void main(String[] args) throws IOException { /** * 创建一个文件输出流对象 */ FileOutputStream fos = new FileOutputStream("fos.txt", true); // append参数如果写则为追加模式,默认为覆盖模式;txt文件即可写入可视数据 // 写入所有小写字母 for (char c = 'a'; c <= 'z'; c++) { fos.write(c); } System.out.println("写出完毕!"); fos.close(); } }
-
FileOutputStream(String filename, boolean append)
:创建一个向具有指定名称的文件中写出数据的文件输出流; -
以上两种构造方法中,第二个参数若为
true
,那么通过该FOS写出的数据都是在文件末尾追加的,但是如若此处为空不写,则默认覆盖原数据。
-
-
-
fileOutputStreamName.write(byte[] data)
:块写操作,一次性将给定字节数组data中所有字节写出(下面会详细讲块写操作); -
fileOutputStreamName.write(byte[] data, int offset, int len)
:块写操作,一次性将给定字节数组data中从下表offset处开始连续输出len个字节写出(下面会详细的讲)。
-
读取字节数据
-
文件输出流继承自
java.io.InputStream
; -
文件输出流提供了父类中要求的读取字节的相关方法;
-
fileInputStreamName.read()
:读取一个字节。返回的int值d表示读取的1字节的数据内容,其二进制的“低八位”有效,如果返回值为整数-1,则表示流读取到了末尾(EOF:End of File);package day16; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; /** * 使用文件输入流从文件中读取数据 */ public class FisDemo { public static void main(String[] args) throws IOException { FileInputStream fis = new FileInputStream("fos.txt"); // 创建文件输入流对象,会出现异常一样抛出异常 /* fos.dat文件的数据: 00000001 00000010 文件末尾 第1次读: 读取文件中的 00000001 00000010 文件末尾 ^^^^^^^^ 返回的d的二进制数据: 00000000 00000000 00000000 00000001------------(1) */ int d = fis.read(); // 读取文件中的数据,返回一个int类型的数据,一样会报异常,抛出异常 System.out.println(d); // 输出读取到的数据,1 /* 第2次读: 读取文件中的 00000001 00000010 文件末尾 ^^^^^^^^ 返回的d的二进制数据: 00000000 00000000 00000000 00000010------------(2) */ d = fis.read(); System.out.println(d); // 2 /* 第3次读: 读取文件中的 00000001 00000010 文件末尾 ^^^^^^^^ 返回的d的二进制数据: 11111111 11111111 11111111 11111111------------(-1) 32位2进制全是1,即为整数-1,用于表示流读取到了末尾 */ d = fis.read(); System.out.println(d); // -1 fis.close(); // 关闭文件输入流,不要忘记关水龙头 } }
- 同样的,当我们想连续读取更多数据时:
package day16; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; /** * 向文件中写入字符数据 */ public class Test1 { public static void main(String[] args) throws IOException { /** * 创建一个文件输入流对象 */ FileInputStream fis = new FileInputStream("fos.txt"); int d; while ((d = fis.read()) != -1) { System.out.print((char) d); // d是int类型,输出时原样输出,即字母的ASCII码值,所以需要强转为char类型 } System.out.println(); System.out.println("读取完毕!"); fis.close(); } }
-
fileInputStreamName.read(byte[] data)
:块读操作,一次性读取给定字节数组data总长度的数据量并从数组第一个字节位置开始存入到数组中,返回值表示实际读取到的数据量。如果返回值为-1则表示读取到了EOF;
-
复制文件
单字节复制
文件复制
package day16;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* 文件复制
*/
public class CopyDemo {
public static void main(String[] args) throws IOException {
// 提前准备了一个testPhoto.jpg在jsd2407项目下
// 1.演示单字节读取方式复制文件
FileInputStream fis = new FileInputStream("testPhoto.jpg");
FileOutputStream fos = new FileOutputStream("testPhoto_CP.jpg");
// 获取自1970年1月1日0时0分0秒0毫秒以来,到此时此刻的毫秒数
long start = System.currentTimeMillis();
int d;
while ((d = fis.read()) != -1) {
fos.write(d);
}
long end = System.currentTimeMillis();
System.out.println("复制完毕!总耗时:" + (end - start) + "毫秒");
fis.close();
fos.close();
}
}
-
单字节读写完成的赋值操作读写效率差
-
主要是因为磁盘的硬件特性决定着单字节的读写性能时极其低效的;
-
这里涉及到磁盘的很多机械特性,马甲带动,电与磁的转换等问题;
-
越频繁的磁盘与内存之间交互,效率越低。
-
块读写
块读操作
-
父类
java.io.InputStream
上定义了块读字节的方法:public int read(byte b[]) throws IOException{...}
-
从流中最多读取
b.length
个字节数据并存入数组b中; -
返回值为实际读取到的字节数;
-
若返回值为-1则表示读取到了末尾;
package day16; import java.io.*; /** * 块读写演示: */ public class CopyDemo02 { public static void main(String[] args) throws IOException { FileInputStream fileInputStream = new FileInputStream("testPhoto.jpg"); FileOutputStream fileOutputStream = new FileOutputStream("testPhoto_CP2.jpg"); long start = System.currentTimeMillis(); // 块读写 int len; byte[] data = new byte[1024 * 10]; // 一次10KB的块读写方式 while ((len = fileInputStream.read(data)) != -1) { // 块读,也是10KB的速度 fileOutputStream.write(data); // 块写,也是10KB的速度 } long end = System.currentTimeMillis(); System.out.println("复制完成,耗时:" + (end - start) + "毫秒"); fileInputStream.close(); fileOutputStream.close(); } }
-
块读写复制
块写操作
-
我们发现用块复制后的复制的文件比原来的文件大:
1)这是由于数组data在第一次被定义时存储10240个字节:
2)第二次:
3)第三次:
4)第四次,数组长度不变,就会产生脏数据:
如何避免脏数据产生
-
父类
java.io.OutputStream
上定义了重载的块写字节方法:public void write(byte b[], int off, int len) throws IOException{...}
;- 将b数组从下表off处开始的连续len个字节一次性写出;
-
所以我们可以改造刚才复制的代码:
package day16;
import java.io.*;
/**
* 块读写演示:
*/
public class CopyDemo02 {
public static void main(String[] args) throws IOException {
FileInputStream fileInputStream = new FileInputStream("testPhoto.jpg");
FileOutputStream fileOutputStream = new FileOutputStream("testPhoto_CP2.jpg");
long start = System.currentTimeMillis();
// 块读写
int len;
byte[] data = new byte[1024 * 10]; // 一次10KB的块读写方式
while ((len = fileInputStream.read(data)) != -1) { // 块读,也是10KB的速度
// fileOutputStream.write(data); // 块写,也是10KB的速度,有脏数据
fileOutputStream.write(data, 0, len); // 块写,也是10KB的速度,可能避免脏数据
}
long end = System.currentTimeMillis();
System.out.println("复制完成,耗时:" + (end - start) + "毫秒");
fileInputStream.close();
fileOutputStream.close();
}
}
写入文本数据
写入文本数据
-
使用字符串的
getBytes()
方法将字符串转换为一组字节,不推荐使用这种无参构造方式,因为会产生跨平台问题出现,建议使用下面这种; -
byte[] getBytes(Charset cn)
:举个例子看下:package day16; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; public class WriteStringDemo { public static void main(String[] args) throws IOException { FileOutputStream fos = new FileOutputStream("test.txt"); String line = "缓缓飘落的枫叶像思念 我点燃烛火温暖岁末的秋天 极光掠过天边 北风掠过想你的容颜"; byte[] data = line.getBytes(StandardCharsets.UTF_8); fos.write(data); line = "我把爱烧成了落叶 却换不回熟悉的那张脸"; data = line.getBytes(StandardCharsets.UTF_8); fos.write(data); System.out.println("写出完毕!"); fos.close(); } }
-
补充介绍一下UTD-8编码:
在Unicode的传输格式基础上增加了长度信息;
-
英文、符号、数字这类字符还是一个字节(ASCII);
-
中文、日文、韩文、俄文、泰文等字符,需要两个字节(Unicode);
-
UTF-8,中文变为3个字节,多一个字节用来获取长度;
-
连续写入文本数据(简易记事本)
package day16;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
/**
* 案例: 简易记事本
* 需求: 程序启动时,要求将用户在控制台上输入的每一行字符串都写入到note.txt中;
* 如果用户输入了"exit",则结束写入;
* 注意: 写入文件的内容不需要考虑换行
*/
public class NoteTest {
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in); // 实例化一个扫描仪对象
FileOutputStream fileOutputStream = new FileOutputStream("note.txt"); // 实例化一个文件输出流对象
while (true) {
System.out.println("请输入要记录的内容:");
String line = scanner.nextLine(); // 通过扫描仪获取用户输入的字符串
if ("exit".equalsIgnoreCase(line)) { // 判断用户输入的字符串是否为exit
break;
}
byte[] data = line.getBytes(StandardCharsets.UTF_8); // 将字符串转换为字节数组
fileOutputStream.write(data); // 将字节数组写入文件
}
System.out.println("记录完成!再见!");
fileOutputStream.close();
}
}
读取文本数据
-
使用文件输入流读取文件中的所有字节;
-
使用String的构造器
String(byte[] data)
可以将读取的字节转换为对应的字符串;package day16; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; /** * 读取文本数据 */ public class ReadStringDemo { public static void main(String[] args) throws IOException { // 创建一个File构建器,之后创建一个FileInputStream输入流构建器 File file = new File("test.txt"); FileInputStream fileInputStream = new FileInputStream(file); // 创建一个与test.txt文件等长的字节数组 byte[] data = new byte[(int)file.length()]; // 获取文件长度,byte数组需要传入int类型的长度,不用担心超出限度,够用了 // 使用块读操作一次性将文件的所有字节读入到字节数组中 fileInputStream.read(data); // 使用String的构造器可以将给定字节数组所有字节按照UTF-8编码转换为字符串 String line = new String(data, StandardCharsets.UTF_8); // 要求些什么格式读取什么格式 System.out.println(line); fileInputStream.close(); } }
处理流
节点流与处理流
什么是低级流、什么是高级流?
简单地说,就例如用水管举例,自来水公司通过水管到各户家里,这个管道就是低级流,但是这时候我想用热水洗澡,我们不能利用烧水再和凉水勾兑,太麻烦了,于是我们使用热水器,自来水管连进热水器,热水器就是一个高级流;再就是我们如果想喝过滤水,我们也可以选择装一个过滤器,这也是一个高级流;那如果我们想用纯净水去洗澡呢?我们就可以先接一个过滤器这个高级流,再接一个热水器高级流,将水进行二次、三次、…处理,在关流的时候关的也是高级流,就像洗完澡要关的是热水器,而不是水阀。
-
低级流和高级流,节点流就是低级流,处理流就是高级流;
-
前面讲的文件就是节点流,节点流就是真是连接我们程序和设备的那个管道,负责读写那个设备的流称为低级流;
-
高级流也叫处理流,它不能独立存在,它总是连接在其他流上,目的是当数据经过它的时候,它做加工处理;
-
高级流就是对流中数据做加工处理的,低级流是保证数据从哪里来到哪里去的,可以把低级流理解为搬运数据的流,而高级流是处理数据的流,实际开发中,经常将它们连在一起使用,这个连接的过程叫做“流连接”。
缓冲字节流
-
在流连接中通常直接连接在低级流上,可以保证读写字节数据的效率;
-
BufferedInputStream(OutputStream out)
:实例化一个缓冲字节输出流并连接在字节输出流上。默认缓冲区大小为8KB(内部维护的byte[] buf
数组长度8192)。BufferedOutputStream(OutputStream out, int size)
:实例化一个指定size
(缓冲区大小)的缓冲字节输出流并连接在指定的字节输出流上。package day16; import java.io.*; /** * 使用缓冲字节流完成文件的复制 * 作用:加快读写速度,提高效率 */ public class CopyDemo03 { public static void main(String[] args) throws IOException { // 高级流一定构建在低级流上,所以先构建一个低级流 FileInputStream fileInputStream = new FileInputStream("testPhoto.jpg"); // 低级流 —— 文件输入流 BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream); // 高级流 —— 将缓冲字节流(高级流)构建在低级流(低级流)上 FileOutputStream fileOutputStream = new FileOutputStream("testPhoto_CP_CP_CP.jpg"); // 低级流 —— 文件输出流 BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream); // 高级流 —— 将缓冲字节流(高级流)构建在低级流(低级流)上 long start = System.currentTimeMillis(); int len; byte[] data = new byte[1024 * 10]; while ((len = bufferedInputStream.read(data)) != -1) { bufferedOutputStream.write(data, 0, len); } long end = System.currentTimeMillis(); System.out.println("复制完成,耗时:" + (end - start) + "毫秒"); bufferedInputStream.close(); // 关闭高级流时,顺便将低级流关闭了 bufferedOutputStream.close(); // 关闭高级流时,顺便将低级流关闭了 } }
-
但是,这种方式存在一个弊端,如果我们的数据不足以装满缓冲区8KB时,缓冲字节流将一直等待满时再将数据写出,当我们需要在执行完某写出操作后,希望数据确实写出,而非在缓冲区中保存直到缓冲区满后才写出,我们可以使用
void flush()
清空缓冲区,将缓冲区中的数据强制写出。package day16; import java.io.BufferedOutputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; /** * 缓冲输出流写出数据的缓冲区问题 */ public class FlushDemo { public static void main(String[] args) throws IOException { FileOutputStream fileOutputStream = new FileOutputStream("bos.txt"); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream); String string = "而我独缺 你一生的了解......"; byte[] data = string.getBytes(StandardCharsets.UTF_8); bufferedOutputStream.write(data); bufferedOutputStream.flush(); System.out.println("写出完毕"); bufferedOutputStream.close(); } }
-
还有一个细节补充,我们可以按住Ctrl然后点击关闭流用的
close()
,观察close()
方法的源代码:public void close() throws IOException { try (OutputStream ostream = out) { flush(); } }
-
那我们还用
flush()
方法干什么,直接close()
不就完事了?其实不然,如果我们在做一个聊天软件时,用微信举例,如果我们用close()
去flush()
的话,刚发送一句话还没有回复就把流关掉了,这样肯定是不合理的,如果我们不使用flush()
方法,那么我们发消息永远通不过去,一直到我写了一篇8KB的小作文,瞬间全部发出去,这明显也是不合理的。所以,我们在需要即时传递时,要多用flush()
。-
将缓冲区中已经缓存的数据一次性写出;
-
但是除了缓冲输出流以外,其他高级流的
flush()
方法的作用都是为了调用它连接的流的flush()
方法,目的是将flush()
方法向下传递,最终传到缓冲输出流,做清空缓冲区操作。 -
flush()
方法是所有输出流都具备的方法,因为该方法时在接口Flushable
中定义的,而输出流的父类OutputStream
实现了该接口,所以所有输出流都有flush()
方法;
package day16; import java.io.BufferedOutputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; /** * 缓冲输出流写出数据的缓冲区问题 */ public class FlushDemo { public static void main(String[] args) throws IOException { FileOutputStream fileOutputStream = new FileOutputStream("bos.txt"); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream); /** * flush(): * 1) 将缓冲区中已经缓存的数据一次性写出; * 2) flush()方法是所有输出流都具备的方法,因为该方法时在接口Flushable中定义的,而输出流的父类OutputStream实现了该接口,所以所有输出流都有flush()方法; * 3) 但是除了缓冲输出流以外,其他高级流的flush()方法的作用都是为了调用它连接的流的flush()方法,目的是将flush()方法向下传递,最终传到缓冲输出流,做清空缓冲区操作。 */ String string = "而我独缺 你一生的了解......"; byte[] data = string.getBytes(StandardCharsets.UTF_8); bufferedOutputStream.write(data); bufferedOutputStream.flush(); System.out.println("写出完毕"); bufferedOutputStream.close(); } }
-
-
对象流
对象序列化概念
-
对象是存在于内存中的。有时候我们需要将对象保存到硬盘上,又有时我们需要将对象传输到另一台计算机上等等这样的操作;
-
将对象转换为一个字节序列的过程称为对象序列化,相反则称反序列化。
序列化与反序列化
-
构造器:
-
ObjectOutputStream(OutputStream out)
:创建一个对象输出流并连接到指定的字节输出流上; -
ObjectInputStream(InputStream in)
:创建一个对象输入流并连接到指定的字节输入流上;
-
-
常用方法:
-
void writeObject(Object obj)
:将给定对象进行序列化后写出;-
序列化的对象必须实现:
java.io.Serializable
接口; -
否则会抛出异常:
NotSerializableException
;
-
-
Object readObject()
:读取若干字节并进行反序列化对象将其返回;
-
-
序列化操作演示:
package day16; import java.util.Arrays; /** * Person类 * 测试对象流的序列化与反序列化操作 */ public class Person implements Serializable{ // 必须实现接口 private String name; // 姓名 private int age; // 年龄 private String gender; // 性别 private String[] otherInfo; // 其他信息 // 无参构造 public Person() { } // 有参构造 public Person(String name, int age, String gender, String[] otherInfo) { this.name = name; this.age = age; this.gender = gender; this.otherInfo = otherInfo; } // getter和setter方法 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; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } public String[] getOtherInfo() { return otherInfo; } public void setOtherInfo(String[] otherInfo) { this.otherInfo = otherInfo; } // toString方法 @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + ", gender='" + gender + '\'' + ", otherInfo=" + Arrays.toString(otherInfo) + '}'; } }
package day16; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; /** * 对象是高级流,作用是进行对象的序列化与反序列化 */ public class OosDemo { public static void main(String[] args) throws IOException { String name = "张禹垚"; int age = 21; String gender = "男"; String[] otherInfo = {"Java", "C", "Python"}; Person person = new Person(name, age, gender, otherInfo); FileOutputStream fileOutputStream = new FileOutputStream("person.obj"); // 低级流(文件流) ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); // 高级流(对象流) /* 对象输出流在进行序列化时,要求该对象必须实现可序列化接口Serializable 否则调用writeObject()时会发生序列化异常NotSerializableException */ objectOutputStream.writeObject(person); // 写出对象 System.out.println("写出对象成功!"); objectOutputStream.close(); } }
-
反序列化操作演示:
package day16; import java.io.*; /** * 使用对象输入流完成反序列化操作 */ public class OisDemo { public static void main(String[] args) throws IOException, ClassNotFoundException { FileInputStream fileInputStream = new FileInputStream("person.obj"); // 低级流(文件流) ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); // 高级流(对象流) Person person = (Person) objectInputStream.readObject(); // 反序列化,因为我们写入时是Person类型,那么读取的也应该是Person类型,所以需要进行强转 System.out.println(person); objectInputStream.close(); } }
-
我们发现,这个对象文件特别的大:
其实我们才写了那么几个数据,是因为这个对象里面的信息包含了每个成员变量的属性等,所以文件会比较大,如果这样的对象要应用在网络层面,那这样的性能将非常差,所以我们要进行瘦身。
transient关键字
-
对象在序列化后得到的字节序列较大,对一个对象进行序列化时可忽略不必要的属性,从而对序列化后得到的字节序列“瘦身”;
-
关键字
transient
修饰的属性在序列化时其值将被忽略; -
我们观察序列化生成的obj大小:
-
这样我们就实现了对对象的瘦身,我们再反序列化查看结果:
发现
otherInfo
属性里面的数据没有被获取到,所以在实际开发时,我们对没有太大用途的属性可以给他一个transient
关键字修饰,实现瘦身效果。