赞
踩
输入(Input)指的是:可以让程序从外部系统获得数据(核心含义是“读”,读取外部数据)。
输出(Output)指的是:程序输出数据给外部系统从而可以操作外部系统(核心含义是“写”,将数据写出到外部系统)。
java.io包为我们提供了相关的API,实现了对所有外部系统的输入输出操作,这就是我们这章所要学习的技术。
数据源:
数据源data source,提供数据的原始媒介。常见的数据源有:数据库、文件、其他程序、内存、网络连接、IO设备:
数据源分为:
流:
流是一个抽象、动态的概念,是一连串连续动态的数据集合。
对于输入流而言,数据源就像水箱,流(stream)就像水管中流动着的水流,程序就是我们最终的用户。我们通过流(A Stream)将数据源(Source)中的数据(information)输送到程序(Program)中。
对于输出流而言,目标数据源就是目的地(dest),我们通过流(A Stream)将程序(Program)中的数据(information)输送到目的数据源(dest)中。
流与源数据源和目标数据源之间的关系:
- /**
- * IO程序的经典写法
- */
- public class Test01 {
- public static void main(String[] args) {
- FileInputStream fis = null; // 作用域为全局
- StringBuilder sb = new StringBuilder(); // 用于字符串拼接
- try{
- fis = new FileInputStream("D:\\JAVA\\workSpace\\ioDemo\\a.txt");
- int temp = 0;
- // 当temp等于-1时,表示已经到了文件结尾,停止读取
- while( (temp = fis.read()) != -1){
- sb.append( (char)temp );
- }
- System.out.println(sb);
- }catch (Exception e){
- e.printStackTrace();
- }finally {
- try {
- // 保证了即使遇到异常情况,也会关闭流对象
- if( fis != null) {
- fis.close();
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
-
- // 输出结果
- kk 123456 sgsg
在JDK7以及以后的版本中可以使用try-with-resource语法更优雅的关闭资源。
java.lang.AutoCloseable接口:
在java.lang.AutoCloseable
接口中包含了一个close
方法,该方法用于关闭资源。
只要是实现了java.lang.AutoCloseable
接口的对象,都可以使用try-with-resource
关闭资源,使用最新的try-with-resource
简化:
- /**
- * try-with-resource写法
- */
- public class Test02 {
- public static void main(String[] args) {
-
- try (FileInputStream fis = new FileInputStream("D:\\JAVA\\workSpace\\ioDemo\\a.txt")) {
- StringBuilder sb = new StringBuilder();
- int temp = 0;
- while ((temp = fis.read()) != -1) {
- sb.append((char) temp);
- }
- System.out.println(sb);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
按流的方向分类:
按处理的数据单元分类:
按处理对象不同分类:
节点流处于IO操作的第一线,所有操作必须通过它们进行;处理流可以对节点流进行包装,提高性能或提高程序的灵活性。
Java为我们提供了多种多样的IO流,我们可以根据不同的功能及性能要求挑选合适的IO流,如图所示,为Java中IO流类的体系。
抽象类:
节点流
处理流:
InputStream / OutputStream
和 Reader / writer
类是所有IO流类的抽象父类。
InputStream
此抽象类是表示字节输入流的所有类的父类。InputSteam是一个抽象类,它不可以实例化。 数据的读取需要由它的子类来实现。根据节点的不同,它派生了不同的节点流子类 。
继承自InputSteam的流都是用于向程序中输入数据,且数据的单位为字节(8 bit)。
常用方法:
方法名 | 使用说明 |
int read() | 读取一个字节的数据,并将字节的值作为int类型返回(0-255之间的一个值)。如果未读出字节则返回-1(返回值为-1表示读取结束) |
void close() | 关闭输入流对象,释放相关系统资源 |
OutputStream
此抽象类是表示字节输出流的所有类的父类。输出流接收输出字节并将这些字节发送到某个目的地。
方法名 | 使用说明 |
void write(int n) | 向目的地中写入一个字节 |
void close() | 关闭输出流对象,释放相关系统资源 |
Reader
Reader用于读取的字符输入流抽象类,数据单位为字符。
方法名 | 使用说明 |
int read() | 读取一个字符的数据,并将字符的值作为int类型返回(0-65535之间的一个值,即Unicode值)。如果未读出字符则返回-1(返回值为-1表示读取结束) |
void close() | 关闭流对象,释放相关系统资源 |
Writer
Writer用于输出的字符输出流抽象类,数据单位为字符。
方法名 | 使用说明 |
void write(int n) | 向输出流中写入一个字符 |
void close() | 关闭输出流对象,释放相关系统资源 |
FileInputStream通过字节的方式读取文件,适合读取所有类型的文件(图像、视频、文本文件等);
FileOutputStream通过字节的方式写数据到文件中,适合所有类型的文件(图像、视频、文本文件等);
FileInputStream:
- /**
- * 文件字节输入流
- */
- public class FileInputStreamTest {
-
- public static void main(String[] args) {
-
- try(FileInputStream fis = new FileInputStream("D:\\JAVA\\workSpace\\ioDemo\\a.txt")){
- StringBuilder sb = new StringBuilder();
- int temp = 0;
- while( (temp = fis.read()) != -1 ){
- sb.append((char)temp);
- }
- System.out.println(sb);
- }catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
- }
FileOutputStream:
- /**
- * 字节输出流
- */
- public class FileOutputStreamTest {
- public static void main(String[] args) {
- String str = "kkkkkkk 18";
- // true表示内容会追加到文件末尾(append),false表示覆盖写
- try(FileOutputStream fos = new FileOutputStream("D:\\JAVA\\workSpace\\ioDemo\\a.txt",true)){
- // 将整个字节数组写入到文件中
- fos.write(str.getBytes());
- // 将数据从内存写入到磁盘中
- fos.flush();
- }catch(IOException e){
- e.printStackTrace();
- }
- }
- }
注意:
fos.flush()
操作才是将数据从内存写入磁盘!
通过创建一个指定长度的字节数组作为缓冲区,以此来提高IO流的读写效率。该方式适用于读取较大文件时的缓冲区定义。
注意:缓冲区的长度一定是2的整数幂。一般情况下1024B
的长度较为合适。
Q:为什么加缓冲区会提高读写IO的效率?
答:因为在操作系统中,CPU、内存处理的速度是非常快的,而与硬件交互的IO操作往往是低了很多个数量级的;如果还是采用上述一个字节一个字节进行一次写入的话,一个字节产生一次IO操作,但是实际上文件在内存中和磁盘中的存储单元是远远大于1个B的(以2的整数次幂为标准),也就是说一次IO写入或读出的数据只要不超过这个范围就可以一次性写入或读出;
所以建立缓冲区的作用就是把数据囤一囤再一块进行IO,这样最耗时的IO就用最少的次数却能达到一样的效果,速度和效率就提升了;
来看看chat老师的答案:
IO操作引入缓冲区可以提高效率的原因主要有以下几点:
- 减少系统调用次数: 操作系统在执行IO操作时需要切换上下文,这是一项相对较为昂贵的操作。使用缓冲区可以减少实际的IO操作次数,因为数据可以在内存中累积一段时间,然后一次性写入磁盘或从磁盘读取,从而减少了系统调用的频率。
- 减少磁盘访问次数: 磁盘IO是相对慢速的操作,通过使用缓冲区,可以将多个小的IO操作合并为一个较大的IO操作,从而减少了磁盘访问的次数。这有助于减少磁盘头寻道的时间,提高了IO操作的效率。
- 数据局部性: 缓冲区可以使数据在内存中形成局部性,这意味着相关数据项在内存中是相邻的。当数据按照顺序存储在内存中时,CPU可以更有效地访问这些数据,而不需要频繁地在内存中进行跳跃。
- 异步IO: 缓冲区可以用于实现异步IO操作。在异步IO中,应用程序可以继续执行其他任务,而不必等待IO操作完成。当IO操作完成后,数据会被复制到应用程序的缓冲区中,这提高了程序的并发性和响应性。
- 数据重用: 缓冲区允许多次访问相同的数据,而无需重新从磁盘读取或重新写入。这在需要多次处理相同数据的情况下可以大大减少IO操作的成本。
总之,引入缓冲区可以将多个小的IO操作合并成更大的操作,减少了系统调用和磁盘访问的次数,提高了IO操作的效率,同时也提供了更好的数据管理和数据访问方式,有助于提高应用程序的性能和响应速度。
测试:
- /**
- * 采用字节缓冲区进行文件复制
- */
- public class FileStreamBuffer {
- public static void main(String[] args) {
- long t1 = System.currentTimeMillis();
- copyFile1("D:\\JAVA\\workSpace\\ioDemo\\1.png","D:\\JAVA\\workSpace\\ioDemo\\2.png");
- long t2 = System.currentTimeMillis();
- System.out.println("未引入缓冲区耗时: " + (t2 - t1));
-
- long t3 = System.currentTimeMillis();
- copyFile2("D:\\JAVA\\workSpace\\ioDemo\\1.png","D:\\JAVA\\workSpace\\ioDemo\\3.png");
- long t4 = System.currentTimeMillis();
- System.out.println("引入缓冲区后耗时: " + (t4 - t3));
-
-
- }
-
- /**
- * 实现文件的拷贝(未引入缓冲区)
- * @param sour 源文件
- * @param dest 目的文件
- */
- public static void copyFile1(String sour, String dest){
-
- try(FileInputStream fis = new FileInputStream(sour);
- FileOutputStream fos = new FileOutputStream(dest);){
- int temp = 0;
- while( ( temp = fis.read()) != -1){
- fos.write(temp);
- }
- // 将文件从内存写入磁盘
- fos.flush();
- }catch(IOException e){
- e.printStackTrace();
- }
- }
-
- /**
- * 实现文件的拷贝(引入缓冲区)
- * @param sour 源文件
- * @param dest 目的文件
- */
- public static void copyFile2(String sour, String dest){
-
- try(FileInputStream fis = new FileInputStream(sour);
- FileOutputStream fos = new FileOutputStream(dest);){
-
- byte[] buffer = new byte[1024]; // 缓冲数组
- int temp = 0;
- while( ( temp = fis.read(buffer)) != -1){
- // 每次都从buffer中取本次相对位置0开始temp字节长度的内容写入内存
- fos.write(buffer,0,temp);
- }
- // 将文件从内存写入磁盘
- fos.flush();
- }catch(IOException e){
- e.printStackTrace();
- }
- }
- }
输出结果:
- 未引入缓冲区耗时: 10108
- 引入缓冲区后耗时: 16
结果还是很明显的,一千倍左右的差距;
注意:
>读取时使用的方法为:read(byte[] b)
;
>写入时的方法为:write(byte[ ] b, int off, int length)
;
Java缓冲流本身并不具有IO流的读取与写入功能,只是在别的流(节点流或其他处理流)上加上缓冲功能提高效率,就像是把别的流包装起来一样,因此缓冲流是一种处理流(包装流);
BufferedInputStream
和BufferedOutputStream
这两个流是缓冲字节流,通过内部缓存数组来提高操作流的效率;缓存区的大小默认是8192字节
,也可以使用其它的构造方法自己指定大小。
- /**
- * 字节缓冲流的基本使用
- */
- public class BufferedStream {
- public static void main(String[] args) {
- long t1 = System.currentTimeMillis();
- copyFile3("D:\\JAVA\\workSpace\\ioDemo\\1.png","D:\\JAVA\\workSpace\\ioDemo\\4.png");
- long t2 = System.currentTimeMillis();
- System.out.println("使用缓冲字节流耗时: " + (t2-t1));
- }
-
- /**
- * 使用字节缓冲流进行文件复制
- * @param sour 源文件
- * @param dest 目的文件
- */
- public static void copyFile3(String sour, String dest){
-
- try(FileInputStream fis = new FileInputStream(sour);
- FileOutputStream fos = new FileOutputStream(dest);
- // 创建缓冲字节流(处理流)
- BufferedInputStream bis = new BufferedInputStream(fis);
- BufferedOutputStream bos = new BufferedOutputStream(fos);){
-
- int temp = 0;
- while( ( temp = bis.read()) != -1){
- bos.write(temp);
- }
- bos.flush();
- }catch (IOException e ){
- e.printStackTrace();
- }
- }
- }
-
-
- // 控制台输出
- 使用缓冲字节流耗时: 57
当执行IO操作处理的文件类型是文本文件时,可以使用文件字符流进行读写,会以字符为单位进行处理;
FileReader:
- /**
- * 文件字符输入流
- */
- public class FileReaderTest {
- public static void main(String[] args) {
-
- try(FileReader fr = new FileReader("D:\\JAVA\\workSpace\\ioDemo\\aa.txt")){
- int temp = 0;
- StringBuilder sb = new StringBuilder();
- while( ( temp = fr.read()) != -1){
- sb.append((char)temp);
- }
- System.out.println(sb);
- }catch (IOException e){
- e.printStackTrace();
- }
- }
- }
-
- // 输出结果
- 文件字符输入流 FileReader测试
FileWriter:
- /**
- * 文件字符输出流
- */
- public class FileWriterTest {
- public static void main(String[] args) {
-
- try(FileWriter fw = new FileWriter("D:\\JAVA\\workSpace\\ioDemo\\aa.txt",true)){
- String str = "当执行IO操作处理的文件类型是文本文件时,可以使用文件字符流进行读写,会以字符为单位进行处理;";
- fw.write(str);
- fw.flush();
- }catch (IOException e ){
- e.printStackTrace();
- }
- }
- }
乍一看文件字符流的作用,好像和文件字节流没有什么区别,这难道是鸡肋的类吗??
实际情况下,文本文件中包含有换行段落等间隔符,如果以文件字节流读取还需要进行转换,提高了操作成本;这个时候结合缓冲字符流(处理流)的文件字符流(节点流)就可以轻易地对文本文件进行行段的处理;
BufferedReader:
对于下面的文本文件,我们采用BufferedReader
进行读取:
- /**
- * 字符输入缓冲流
- */
- public class BufferedReaderTest {
- public static void main(String[] args) {
- // 简化创建
- try (BufferedReader br = new BufferedReader(new FileReader("D:\\JAVA\\workSpace\\ioDemo\\aa.txt"))){
- String temp = "";
- // readLine() 一次读取一行
- while( (temp = br.readLine()) != null){
- System.out.println(temp);
- }
- }catch (IOException e){
- e.printStackTrace();
- }
- }
- }
-
- //输出结果
- 当执行IO操作处理的文件类型是文本文件时,可以使用文件字符流进行读写;
- 会以字符为单位进行处理;当执行IO操作处理的文件类型是文本文件时;
- 可以使用文件字符流进行读写,会以字符为单位进行处理;
- 当执行IO操作处理的文件类型是文本文件时\br,
- 可以使用文件字符流进行读写,会以字符为单位进行处理;
- 当执行IO操作处理的文件类型是文本文件时,
- 可以使用文件字符流进行读写,会以字符为单位进行处理;
BufferedWriter:
- /**
- * 字符输出缓冲流
- */
- public class BufferedWriterTest {
- public static void main(String[] args) {
-
- try(BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\JAVA\\workSpace\\ioDemo\\aa.txt"))){
- bw.write("白日依山尽");
- bw.newLine(); // 换行
- bw.write("黄河入海流");
- bw.flush();
- }catch (IOException e){
- e.printStackTrace();
- }
- }
- }
- /**
- * 为文件的内容添加行号
- */
- public class LineNumberTest {
- public static void main(String[] args) {
-
- try(BufferedReader br = new BufferedReader(new FileReader("D:\\JAVA\\workSpace\\ioDemo\\aa.txt"));
- BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\JAVA\\workSpace\\ioDemo\\aaa.txt"))){
-
- int index = 1; // 定义序号
- String temp = "";
- while( (temp = br.readLine()) != null){
- bw.write(index +". " + temp);
- index++;
- bw.newLine(); // 换行
- }
- bw.flush();
- }catch (IOException e){
- e.printStackTrace();
- }
- }
- }
-
-
- //aa.txt
- 白日依山尽
- 黄河入海流
- // aaa.txt
- 1. 白日依山尽
- 2. 黄河入海流
InputStreamReader / OutputStreamWriter用来实现将字节流转化成字符流。
即:
常用的转换流功能是:解决乱码问题;
乱码的产生是由于编码的类型和阶码的类型不匹配导致的;
采用ANSI
(实际上是GBK
)对文本文件进行编码,使用转换流进行乱码处理:
- /**
- * 转换流的使用1(出现乱码)
- */
- public class InputStreamReaderTest {
- public static void main(String[] args) {
-
- try(InputStreamReader isr = new InputStreamReader(new FileInputStream("D:\\JAVA\\workSpace\\ioDemo\\aaa1.txt"))){
- StringBuilder sb = new StringBuilder();
- int temp = 0;
- while( ( temp = isr.read()) != -1){
- sb.append((char)temp);
- }
- System.out.println(sb);
- }catch (IOException e){
- e.printStackTrace();
- }
- }
- }
-
- //输出结果
- 1. ������ɽ��
- 2. �ƺ��뺣��
由于Java是采用UTF-8作为编码格式的,因此实际上就存在着文件编码和解码类型不匹配的问题;
解决:
try(InputStreamReader isr = new InputStreamReader(new FileInputStream("D:\\JAVA\\workSpace\\ioDemo\\aaa1.txt"),"gbk")){
只需要在转换流的创建中加入和编码对应的解码类型即可(此处指定为gbk
);
- // 输出结果
- 1. 白日依山尽
- 2. 黄河入海流
分析题意:
基于上图所示的代码如下:
- /**
- * 转换流的使用2
- */
- public class InputStreamReaderTest {
- public static void main(String[] args) {
- // 依次创建转换流(字节输入流)、字符输出缓冲流
- try(InputStreamReader isr = new InputStreamReader(new FileInputStream("D:\\JAVA\\workSpace\\ioDemo\\aa.txt"),"utf-8");
- BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\JAVA\\workSpace\\ioDemo\\aaaa.txt"));){
- //
- StringBuilder sb = new StringBuilder();
- int temp = 0;
- int index = 1;
- while( ( temp = isr.read()) != -1){
- bw.write(index + ". "+(char)temp);
- bw.newLine();
- index++;
- }
- bw.flush();
-
- }catch (IOException e){
- e.printStackTrace();
- }
- }
- }
注意第7-8行,程序只是将字节输入流转换成了字符输入流,此时读取的方式还是逐个字符逐个字符读取,这导致第13-14行中每一个while循环中isr.read()返回的都是一个字符的ASCII码,而非一行的!!!
写入文件的结果如上图所示,这个并非我们想要的结果;
改进:
上述问题的原因在于输入流最后的处理形式是“逐字符”而非“逐行”!!所以在转换流将字节流转为字符流后,还需要将字符流转换为字符缓冲流,使用其中的readLine()方法才能实现“逐行”;
- /**
- * 转换流的使用2
- */
- public class InputStreamReaderTest {
- public static void main(String[] args) {
- // 依次创建转换流(字节输入流)、字符缓冲流、字符输出缓冲流
- try(InputStreamReader isr = new InputStreamReader(new FileInputStream("D:\\JAVA\\workSpace\\ioDemo\\aa.txt"),"utf-8");
- BufferedReader br = new BufferedReader(isr); // 字符输入缓冲流
- BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\JAVA\\workSpace\\ioDemo\\aaaa.txt"));){
- //
- StringBuilder sb = new StringBuilder();
- String temp = null;
- int index = 1;
- while( ( temp = br.readLine()) != null){ // 逐行读取
- bw.write(index + ". "+temp);
- bw.newLine();
- index++;
- }
- bw.flush();
-
- }catch (IOException e){
- e.printStackTrace();
- }
- }
- }
结果:
首先分析“键盘输入”是如何实现的,可能很快就会想到是System.in,那么同理屏幕输出是System.out,那么它们都是什么类型的输入输出流对象呢?
- public final static PrintStream out = null; // out对象定义
- public final static InputStream in = null; // in对象定义
可以看到它们都是字节流对象,那么就要考虑实际情况了,键盘的输入是包含多字符、可换行的,那么在处理输入流时就要将其转换成缓冲字符输入流进行逐行处理;同理屏幕的输出是包含多字符、可换行的,因此输出流应该是缓冲字符输出流;
- /**
- * 案例3:通过转换流实现键盘输入屏幕输出
- */
- public class ConvertStreamTest3 {
- public static void main(String[] args) {
- // 实现 字节流->字符流->缓冲字符流 的转换
- try(BufferedReader br = new BufferedReader( new InputStreamReader( System.in));
- BufferedWriter bw = new BufferedWriter( new OutputStreamWriter( System.out));){
- String inputStr = "";
- while( !( inputStr = br.readLine()).equals("quit")){
- bw.write(inputStr);
- bw.newLine();
- bw.flush();
- }
- }catch (IOException e){
- e.printStackTrace();
- }
- }
- }
输出结果:
- sjakjl
- sjakjl
- 456
- 456
- 撒寄快递
- 撒寄快递
- quit
-
- Process finished with exit code 0
基于前文可以知道,要在文件中写入带有换行的字符是需要用到两个流对象的:缓冲字符输出流BufferedWriter(处理流)中嵌套文件字符输出流FileWriter(节点流);
PrintWriter集两者为一体,具有自动行刷新缓冲字符输出流,特点是可以按行写出字符串,并且可通过
println()
方法实现自动换行;
- /**
- * 添加行号
- */
- public class PrintWriterTest {
- public static void main(String[] args) {
- try(BufferedReader br = new BufferedReader(new FileReader("D:\\JAVA\\workSpace\\ioDemo\\aa.txt"));
- PrintWriter pw = new PrintWriter("D:\\JAVA\\workSpace\\ioDemo\\aaaaa.txt");){
- int index =1;
- String temp = "";
- while( ( temp = br.readLine()) != null ){
- pw.println((index++) +". " + temp);
- }
- pw.flush();
- }catch (IOException e){
- e.printStackTrace();
- }
- }
- }
-
- //aa.txt
- 白日依山尽
- 黄河入海流
-
- //aaaaa.txt
- 1. 白日依山尽
- 2. 黄河入海流
数据流将“基本数据类型与字符串类型”作为数据源,从而允许程序以与机器无关的方式从底层输入输出流中操作Java基本数据类型与字符串类型。
DataInputStream
和DataOutputStream
提供了可以存取与机器无关的所有Java基础类型数据(如:int、double、String等)的方法;
- public class TestDataStream {
- public static void main(String[] args) {
- //创建数据输出流对象与文件字节输出流对象
- try(DataOutputStream dos = new DataOutputStream(new FileOutputStream("d:/data"));
- //创建数据输入流对象与文件字节输入流对象
- DataInputStream dis = new DataInputStream(new FileInputStream("d:/data"))){
- //将如下数据写入到文件中
- dos.writeChar('a');
- dos.writeInt(10);
- dos.writeDouble(Math.random());
- dos.writeBoolean(true);
- dos.writeUTF("你好中国");
- //手动刷新缓冲区:将流中数据写入到文件中
- dos.flush();
- //直接读取数据:读取的顺序要与写入的顺序一致,否则不能正确读取数据。
- System.out.println("char: " + dis.readChar());
- System.out.println("int: " + dis.readInt());
- System.out.println("double: " + dis.readDouble());
- System.out.println("boolean: " + dis.readBoolean());
- System.out.println("String: " + dis.readUTF());
-
-
- }catch(IOException e){
- e.printStackTrace();
- }
- }
- }
其中嵌套的是字节流;
- /**
- * 基本使用
- */
- public class ObjectStreamTest1 {
- public static void main(String[] args) {
- try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\JAVA\\workSpace\\ioDemo\\aaaaa.txt",false));
- ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\JAVA\\workSpace\\ioDemo\\aaaaa.txt"));){
-
- // 写入文件
- oos.writeInt(100);
- oos.writeChar('a');
- oos.writeBoolean(false);
- oos.writeUTF("卡卡的");
- oos.flush(); // 刷新
-
- // 读出文件内容
- System.out.println("int: "+ois.readInt());
- System.out.println("char: "+ois.readChar());
- System.out.println("boolean: "+ois.readBoolean());
- System.out.println("String: "+ois.readUTF());
-
- }catch (IOException e){
- e.printStackTrace();
- }
- }
- }
当两个进程远程通信时,彼此可以发送各种类型的数据。 无论是何种类型的数据,都会以二进制序列的形式在网络上传送。比如,我们可以通过http协议发送字符串信息;我们也可以在网络上直接发送Java对象。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象才能正常读取。
序列化:把数据结构或Java对象转换为二进制字节序列的过程;
反序列化:字节序列恢复为数据结构或Java对象的过程;
涉及的类和接口:
ObjectOutputStream
代表对象输出流,它的writeObject(Object obj)
方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中;
ObjectInputStream
代表对象输入流,它的readObject()
方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回;
只有实现了Serializable
接口的类的对象才能被序列化。 Serializable
接口是一个空接口(标识接口),只起到标记作用;
序列化和反序列化常见应用场景:
Q:如果有些字段不想进行序列化怎么办?
对于不想进行序列化的变量,可以使用transient
关键字修饰;
transient
关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被transient
修饰的变量值不会被持久化和恢复;几点注意:
int
类型,那么反序列后结果就是 0
;static
变量因为不属于任何对象(Object),所以无论有没有 transient
关键字修饰,均不会被序列化。参考博客:
- /**
- * 实现序列化
- */
- public class ObjectStreamTest2 {
- public static void main(String[] args) {
- try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\JAVA\\workSpace\\ioDemo\\aaaaa.txt"))){
-
- User user = new User(1,"kk",18);
- oos.writeObject(user);
- oos.flush();
- }catch (IOException e){
- e.printStackTrace();
- }
- }
- }
- /**
- * 实现反序列化
- */
- public class ObjectStreamTest3 {
- public static void main(String[] args) {
- try(ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\JAVA\\workSpace\\ioDemo\\aaaaa.txt"))){
-
- User user = (User) ois.readObject();
- System.out.println(user);
- }catch (IOException | ClassNotFoundException e){
- e.printStackTrace();
- }
- }
- }
-
- // 输出结果
- Users{userid=1, username='kk', userage='18'}
装饰器模式:是GOF23种设计模式中较为常用的一种模式。它可以实现对原有类的包装和装饰,使新的类具有更强的功能。
IO流体系中大量使用了装饰器模式,让流具有更强的功能、更强的灵活性。比如:
- FileInputStream fis = new FileInputStream(src);
- BufferedInputStream bis = new BufferedInputStream(fis);
显然BufferedInputStream装饰了原有的FileInputStream,让普通的FileInputStream也具备了缓存功能,提高了效率。
致谢&部分资源出处:百战程序员
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。