当前位置: 首页 > news >正文

JavaSE丨IO流全解:从基础概念到序列化实战

一、IO流

1.1 流的概念

在计算机中,流是个抽象的概念,是对输入输出设备的抽象。在Java程序中,对于数据的输入/输出操作,都是以"流"的方式进行。

        数据以二进制的形式在程序与设备之间流动传输,就像水在管道里流动一样,所以就把这种数据传输的方式称之为输入流、输出流。这里描述的设备,可以是文件、网络、内存等。

        流具有方向性,可以分为输入和输出。

        以java程序本身作为参照点,如果数据是从程序“流向”文件,那么这个流就是输出流,如果数据是从文件“流向”程序,那么这个流就是输入流。例如:

这里是以文件进行举例,java程序中还可以把数据写入到网络中、内存中等

1.2 流的分类

Java中的IO流可以根据很多不同的角度进行划分,最常见的是以数据的流向数据的类型来划分

根据数据的流向分为:输入流和输出流

  • 输入流 :把数据从其他设备上读取到程序中的流
  • 输出流 :把数据从程序中写出到其他设备上的流

根据数据的类型分为:字节流和字符流

  • 字节流 :以字节为单位(byte),读写数据的流
  • 字符流 :以字符为单位(char),读写数据的流

字节输入流:在程序中,以字节的方式,将设备(文件、内存、网络等)中的数据读进来

字节输出流:在程序中,以字节的方式,将数据写入到设备(文件、内存、网络等)中

字符输入流:在程序中,以字符的方式,将设备(文件、内存、网络等)中的数据读进来

字符输出流:在程序中,以字符的方式,将数据写入到设备(文件、内存、网络等)中

注意:字节指的是byte,字符指的的是char

1.3 流的结构

在Java中,和IO流相关的类,主要是在 java.io 包下的定义的

几乎所有的流,都是派生自四个抽象的父类型

  • InputStream ,代表字节输入流类型
  • OutputStream ,代表字节输出流类型
  • Reader ,代表字符输入流类型
  • Writer ,代表字符输出流类型

Java中常用的流及其继承结构:

1.4 字节流

        一切文件数据(文本、图片、视频等)在存储时,都是以二进制数字的形式保存,那么传输时一样如此。所以,字节流可以传输任意文件数据。在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据。  

java.io.InputStream 是所有字节输入流的抽象父类型

java.io.OutputStream 是所有字节输出流的抽象父类型      

一般情况,使用字节流来操作数据的时候,往往是使用一对:一个字节输入流,负责读取数据,一个字节输出流,负责将数据写出去,而这些流都将是 InputStream 和 OutputStream 的子类型。

在代码中,使用流操作数据的的基本步骤是:

  1. 声明流
  2. 创建流  
  3. 使用流
  4. 关闭流
1)文件输入流

文件字节输入流 FileInputStream ,用于从文件中读取字节数据。

案例展示:使用 FileInputStream 读取文件内容

import java.io.FileInputStream;
import java.io.IOException;public class FileInputStreamExample {public static void main(String[] args) {// 要读取的文件路径,这里假设在项目根目录下有test.txt文件String filePath = "test.txt";try (FileInputStream fis = new FileInputStream(filePath)) {int data;// 循环读取文件内容,read()方法每次读取一个字节,返回值为字节对应的ASCII码值,读到文件末尾返回-1while ((data = fis.read()) != -1) {System.out.print((char) data);}} catch (IOException e) {System.out.println("读取文件时发生错误: " + e.getMessage());}}
}
2)文件输出流

文件字节输出流, FileOutputStream ,用于写入字节数据到文件中

案例展示:使用 FileOutputStream 写入文件内容

import java.io.FileOutputStream;
import java.io.IOException;public class FileOutputStreamExample {public static void main(String[] args) {// 要写入的文件路径,这里假设在项目根目录下创建testWrite.txt文件String filePath = "testWrite.txt";String content = "这是通过FileOutputStream写入文件的内容。";try (FileOutputStream fos = new FileOutputStream(filePath)) {// 将字符串转换为字节数组,getBytes()方法将字符串按平台默认字符编码转换为字节数组byte[] bytes = content.getBytes();// 写入字节数组到文件中fos.write(bytes);System.out.println("文件写入成功!");} catch (IOException e) {System.out.println("写入文件时发生错误: " + e.getMessage());}}
}
3)综合案例

结合使用 FileInputStream 和 FileOutputStream 实现文件复制

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;public class FileCopyExample {public static void main(String[] args) {// 源文件路径String sourceFilePath = "source.txt";// 目标文件路径String targetFilePath = "target.txt";try (FileInputStream fis = new FileInputStream(sourceFilePath);FileOutputStream fos = new FileOutputStream(targetFilePath)) {int data;while ((data = fis.read()) != -1) {fos.write(data);}System.out.println("文件复制成功!");} catch (IOException e) {System.out.println("文件复制时发生错误: " + e.getMessage());}}
}

1.5 字符流

        在 Java 中,字符流是一种用于处理字符数据的输入和输出流。相比于字节流,字符流提供了更方便和高效的字符处理方式。

        字符流以字符为单位进行读写操作,可以直接读写字符,而不需要进行字节与字符的转换。这使得字符流更适合处理文本数据,可以方便地进行字符的查找、替换、拼接等操作。

字节流和字符流的关系:

本质上,字符流底层借助字节流实现功能。通过借助字节流,字符流可以在更高的抽象层次上处理字符数据,提供更方便和高效的字符处理方式。字节流提供了底层的数据传输能力,而字符流在此基础上提供了字符编码、字符集转换、缓冲功能以及其他高级功能

1)文件字符流

        java.io.FileReader 类是读取字符文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。

        java.io.FileWriter 类是写出字符到文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。

案例展示:使用文件字符流拷贝a.txt文件内容到b.txt文件末尾

public class Test_FileReaderWriter {public static void main(String[] args) throws IOException {// 1.实例化流对象File file1 = new File("src/dir/a.txt");File file2 = new File("src/dir/b.txt");Reader reader = new FileReader(file1);// 设置文件追加Writer writer = new FileWriter(file2, true);// 2.使用流进行文件拷贝int len = -1;char[] buf = new char[8];while ((len = reader.read(buf)) != -1) {writer.write(buf, 0, len);}// 刷新流writer.flush();// 3.关闭流writer.close();reader.close();}
}
2)节点流总结

        在 Java 中,IO 流按照功能划分可以分为两类: 节点流(原始流) 增强流(包装流)

        节点流(Node Streams)是最基本的 IO 流,直接与数据源或目标进行交互,但缺乏一些高级功能。 它们提供了最底层的读写功能,可以直接读取或写入底层数据源或目标,如文件、网络连接等。

        上面我们学习的所有流都是节点流:

FileInputStream 和 FileOutputStream :用于读取和写入文件的字节流。

FileReader 和 FileWriter :用于读取和写入文件的字符流

ByteArrayInputStream 和 ByteArrayOutputStream :用于读取和写入字节数组的流

CharArrayReader 和 CharArrayWriter :用于读取和写入字符数组的流

在实际开发中,通常会使用增强流来提供更高级的功能和操作,以便更方便地进行 IO 操作。 节点流则作为增强流的基础,提供最底层的读写功能。

增强流也称为包装流

  • 其在节点流的基础上提供了额外的功能和操作
  • 增强流提供了更高级的操作和便利性,使得 IO 操作更加方便、高效和灵活
  • 增强流通过装饰器模式包装节点流,可以在节点流上添加缓冲、字符编码转换、对象序列化等功能
节点流、增强流理解:

1.6 缓冲流

1)缓冲思想

        在 Java 的 I/O 流中,缓冲思想是一种常见的优化技术,用于提高读取和写入数据的效率。它通过在内存中引入缓冲区(Buffer)来减少实际的 I/O 操作次数, 从而提高数据传输的效率

        缓冲思想的基本原理是将数据暂时存储在内存中的缓冲区中,然后按照一定的块大小进行读取或写入操作。相比于直接对磁盘或网络进行读写操作,使用缓冲区可以减少频繁的 I/O 操作,从而提高效率。

缓冲流概述: 缓冲流(Buffered Streams)也叫高效流,是一种非常有用的增强流,提供了缓冲功能,可以提高 IO 操作的效率。 缓冲流通过在内存中创建一个缓冲区,将数据暂时存储在缓冲区中,然后批量读取或写入数据,减少了频繁的磁盘或网络访问,从而提高了读写的性能。

常见缓冲流:
  • BufferedInputStream 缓冲字节输入流
  • BufferedOutputStream 缓冲字节输出流
  • BufferedReader 缓冲字符输入流
  • BufferedWriter 缓冲字符输出流
2)缓冲字节流
  • public BufferedInputStream(InputStream in) :创建一个新的缓冲输入流。
  • public BufferedOutputStream(OutputStream out): 创建一个新的缓冲输出流。

案例展示:

准备图片 D:\\test\\1.png ,拷贝到 D:\\test\\2.png ,分别使用文件子节点(节点流)和缓冲流拷贝图片,对比两种方式拷贝所需时间。

节点流:

public class Test_Buffered {public static void main(String[] args) throws Exception {// 1. 创建流对象FileInputStream fis = new FileInputStream("D:\\test\\1.png");FileOutputStream fos = new FileOutputStream("D:\\test\\2.png");// 2. 借助节点流进行逐行读取long start = System.currentTimeMillis();int data;while ((data = fis.read()) != -1) {fos.write(data);}long end = System.currentTimeMillis();System.out.println("拷贝完成,拷贝时长: " + (end - start) + "ms");// 3. 只需要关闭最后的增强流对象即可fos.close();fis.close();}
}

缓冲流:

public static void main(String[] args) throws Exception {// 1. 创建缓冲流FileInputStream fis = new FileInputStream("D:\\test\\1.png");// 注意:缓冲流是增强流,其底层借助节点流实现功能,所以创建时须传入一个节点流对象BufferedInputStream bis = new BufferedInputStream(fis);BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\test\\2.png"));// 2. 借助缓冲流进行逐行读取long start = System.currentTimeMillis();byte[] buff = new byte[1024];int len;while ((len = bis.read(buff)) != -1) {bos.write(buff, 0, len);// 刷新缓冲流bos.flush();}long end = System.currentTimeMillis();System.out.println("拷贝完成,拷贝时长: " + (end - start) + "ms");// 3. 只需要关闭最后的增强流对象即可bos.close();bis.close();
}

对比两个程序运行结果可知,缓冲流能明显提高IO效率。

3)缓冲字符流
  • public BufferedReader(Reader in) :创建一个新的缓冲输入流。
  • public BufferedWriter(Writer out): 创建一个新的缓冲输出流。

字符缓冲流的构造器,要求一定要传入一个字符流对象,然后缓冲流就可以对这个字符流的功能进行增强,提供缓冲数据的功能,从而提高读写的效率

案例展示:

使用缓冲字符流完成字符文件的拷贝,注意使用缓冲流的新方法。

public class Test_BufferedChar {public static void main(String[] args) throws Exception {//1.BufferedReader br = new BufferedReader(new FileReader("src/dir/a.txt"));BufferedWriter bw = new BufferedWriter(new FileWriter("src/dir/b.txt"));//2.逐行读取 输出 最后拷贝String line;//读取整行数据 不包含 换行符while ((line = br.readLine()) != null) {//输出读取整行数据System.out.println("read: " + line);//将读取的整行数据 写入b.txtbw.write(line);//额外写换行符: 如果后续没有数据了,则不要再写换行符// ready()返回false可理解为:马上要读取到文件尾if (br.ready())bw.newLine();}//3.bw.close();br.close();}
}

1.7 对象流

1)序列化机制

        Java 提供了一种对象序列化的机制,可以将对象和字节序列之间进行转换

  • 序列化

程序中,可以用一个字节序列来表示一个对象,该字节序列包含了对象的类型、对象中的数据等。如果这个字节序列写出到文件中,就相当于在文件中持久保存了这个对象的信息

  • 反序列化

相反的过程,从文件中将这个字节序列读取回来,在内存中重新生成这个对象,对象的类型、对象中的数据等,都和之前的那个对象保持一致。(注意,这时候的对象和之前的对象,内存地址可能是不同的)

如图:

完成对象的序列化和反序列化,就需要用到对象流

2)对象流介绍
  • java.io.ObjectOutputStream 将Java对象转换为字节序列,并输出到内存、文件、网络等地方
  • java.io.ObjectInputStream 从某一个地方读取出对象的字节序列,并生成对应的对象

案例展示:

准备Student类,使用对象流将学生对象保存在文件中,并读取出来。

//基础类
public class Student {private String name;private int age;public Student() {}public Student(String name, int age) {this.name = name;this.age = 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;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}
}

测试类:

public class Test_WriteObject {public static void main(String[] args) throws IOException {//把对象保存文件ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src/com/briup/chap11/test/stu.txt"));Student stu = new Student("tom", 20);oos.writeObject(stu);System.out.println("writeObject success!");//操作完成后,关闭流oos.close();}
}

运行代码时会抛出异常 java.io.NotSerializableException

异常信息为Student的类型无法进行序列化,那Student类型还需要做哪些操作才可以完成序列化呢?

3)序列化接口

在Java中,只有实现了 Serializable接口的对象才可以进行进行序列化和反序列化。

java.io.Serializable 接口

注意:这只是一个“标识”接口,接口中没有抽象方法。

上述问题解决: 让Student类实现序列化接口,就可以解决上述问题。

//注意,必须实现接口
public class Student implements Serializable {//省略...
}

结论:对象流操作的基础类,一定要实现序列化接口

4)集合序列化

实际开发中往往有以下场景: 程序员A往文件中写入多个对象;程序员B需要从该文件中读取所有对象;但B不知道文件中对象个数,这种情况下如何获取所有对象,同时避免 EOFException 异常?

推荐方案: 序列化多个对象时,先将所有对象添加到一个集合中,然后序列化集合对象; 反序列化时,从文件中读取单个集合对象,再从集合中获取所有对象。

集合序列化案例: 往list.txt中写入多个对象,然后再从文件中读取所有对象并遍历输出。

写入list.txt功能实现:

public class Test_WriteList {public static void main(String[] args) throws Exception {//1.实例化流对象ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src/com/briup/chap11/test/list.txt"));//2.准备多个Student对象并加入List集合Student s1 = new Student("tom", 20);Student s2 = new Student("zs", 21);Student s3 = new Student("jack", 19);List<Student> list = new ArrayList<>();list.add(s1);list.add(s2);list.add(s3);//3.将集合写入文件oos.writeObject(list);System.out.println("write list success!");//4.操作完成后,关闭流oos.close();}
}

读取功能实现:

public class Test_ReadList {public static void main(String[] args) throws IOException, ClassNotFoundException {ObjectInputStream ois = new ObjectInputStream(new FileInputStream("src/com/briup/chap11/test/list.txt"));//2.读取集合对象List<Student> list = (List<Student>) ois.readObject();if (list == null) {System.out.println("read null");return;}System.out.println("read list size: " + list.size());//3.遍历输出for (Student stu : list) {System.out.println(stu);}//4.关闭资源ois.close();}
}
http://www.xdnf.cn/news/1469647.html

相关文章:

  • 树莓派传感器扩展板资料
  • VMWare上搭建大数据集群
  • 8. Mono与IL2Cpp简介
  • mysql中null值对in子查询的影响
  • B.50.10.03-Nginx核心原理与电商应用
  • 基于STM32单片机FM调频TEA5767功放收音机液晶显示设计
  • Zynq-7000 上 RT-Thread 的 MMU 与 SMP 优势分析
  • 七彩喜智慧养老:科技向善,让“养老”变“享老”的智慧之选
  • 23种设计模式——桥接模式 (Bridge Pattern)详解
  • 极大似然估计与概率图模型:统计建模的黄金组合
  • 洛谷 P1099 [NOIP 2007 提高组] 树网的核-普及+/提高
  • ShareX神操作:多区域截图+特效功能!
  • linux ubi文件系统
  • Linux 文件系统及磁盘相关知识总结
  • Webpack 有哪些特性?构建速度?如何优化?
  • 前端开发vscode插件 - live server
  • 【SuperSonic】:PluginParser 插件解析器
  • 雅菲奥朗SRE知识墙分享(六):『混沌工程的定义与实践』
  • 十二、软件系统分析与设计
  • Linux:进程信号理解
  • Day21_【机器学习—决策树(2)—ID3树 、C4.5树、CART树】
  • stm32——NVIC,EXIT
  • RHEL7.9、RHEL9.3——源码安装MySQL
  • 人工智能领域、图欧科技、IMYAI智能助手2025年8月更新月报
  • 辗转相除法(欧几里得算法)的证明
  • mysql进阶语法(视图)
  • 25高教社杯数模国赛【A题国奖核心成品论文+问题解析】第一弹
  • 如何提升技术架构设计能力?
  • 保姆级 i18n 使用攻略,绝对不踩坑(帮你踩完了)
  • 《C++ printf()函数的深度解析》