JavaLearn# (12)IO流文件字节(符)流缓冲字节(符)流数据流对象流序列化
Posted LRcoding
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaLearn# (12)IO流文件字节(符)流缓冲字节(符)流数据流对象流序列化相关的知识,希望对你有一定的参考价值。
1. IO流概述
在Java中,数据的输入/输出操作,以”流“(stream)方式进行,一般位于 java.io 包中
数据源(data source)提供原始数据的原始媒介:数据库、文件、其他程序、内存、网络连接、IO设备
流是一个抽象、动态的概念,是一连串连续动态的数据集合
从源文件到目的文件,中间必须经过中转(把源数据读到程序中,再从程序输出到目的)
1.1 IO流分类
- 按流的方向(
输入,输入是相对于程序(中转站)来说的
)- 输入流:从数据源到程序(InputStream、Reader 结尾)
- 输出流:从程序到目的地(OutputStream、Writer 结尾)
- 按处理的数据单元:
- 字节流:以字节为单位获取数据,一般是 Stream 结尾的流(顶级类:InputStream、OutputStream)
- 字符流:以字符为单位获取数据,一般是 Reader/Writer 结尾的流 (顶级类:Reader、Writer)
- 按处理对象不同:
- 节点流:
直接
从数据源或目的地读写数据,如 FileInputStream、FileReader - 处理流(包装流):不直接连接数据源或目的地,
处理流的流
,通过处理其他流提高程序的性能,如 BufferedInputStream、BufferedReader
- 节点流:
节点流位于 IO操作的第一线,所有的操作都通过它们来进行。处理流是用来提高性能和程序灵活性的
1.2 IO流体系结构
InputStream 和 OutputStream
- 最基本的字节输入输出流,其他的字节流类都继承自它们
- 都是抽象类,不能创建实例,只能使用它们的子类
Reader 和 Writer
- 最基本的字符输入输出流,其他的字符流类都继承自它们
- 都是抽象类,不能创建实例,只能使用它们的子类
2. 文件【字节】流
FileInputStream
文件字节输入流:FileInputStream 文件字节输出流:FileOutputStream
/**
* 1.创建流
* File file = new File("E:/readme.txt");
* InputStream in = new FileInputStream(file);
*/
// 简化写法
InputStream in = new FileInputStream("E:/readme.txt");
OutputStream out = new FileOutputStream("E:/readme2.txt");
/**
* 2.使用流
* 准备一个中转站,借助循环和中转站,使用输入流和输出流完成复制
*/
// 先读源文件的一个字节赋给 中转站n
int n = in.read();
while (n != -1) { // 读到了数据,还没有到末尾
// 写一个字节到文件
out.write(n);
// 再读一个字节,赋给n
n = in.read();
}
// 3.关闭流
in.close();
out.close();
注意:存在问题,只适用于复制小文件,因为中转站太小了,进行修改,使用字节数组,byte
// 准备一个中转站,一次读 1024 个字节
byte[] buf = new byte[1024];
// 使用输入流读取文件,将读到的内容放入到 buf字节数组中,返回的是【真正读取到】的字节
int len = in.read(buf);
while (len != -1) {
// 将字节数组的内容写入到文件,从第 0个字节开始读,读取 len个字节
out.write(buf, 0, len);
len = in.read(buf);
}
3. 文件【字符】流
FileReader
文件字符输入流:FileReader 文件字符输出流:FileWriter 节点流,数据源和目的地是文件
// 创建流
Reader in = new FileReader("E:/readme.txt"); // 还可以带一个参数,默认为 false,表示不追加,直接覆盖
Writer out = new FileWriter("E:/readme2.txt", true); // 设置为 true时,在后面追加
// 使用流
int n = in.read();
while ((n = in.read()) != -1) {
out.write(n);
}
// 关闭流
in.close();
out.close();
注意:扩大中转站
,字符流,需要使用 char
char[] cbuf = new char[1024];
int len;
while ((len = in.read(cbuf)) != -1) {
// 一定要写真正读取到的文件长度
out.write(cbuf, 0, len);
}
优化:进行异常处理
Reader in = null;
Writer out = null;
try {
// 创建流
in = new FileReader("E:/readme.txt");
out = new FileWriter("E:/readme2.txt");
// 使用流
char[] cbuf = new char[1024];
int len;
while ((len = in.read(cbuf)) != -1) {
out.write(cbuf, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭流
try {
// 如果没有创建好,就无需关闭
if (in != null) {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (out != null) {
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
- 其实只有字节流,字符流的底层还是字节流
- 字节流可以完成所有类型文件的复制,字符流只可以完成文本文件的复制
- 异常处理的分析:创建流、使用流要使用一次 try-catch 语句,关闭流要分开进行异常处理
4. 缓冲【字节】流
BufferedInputStream
// 创建【文件字节流】
InputStream in = new FileInputStream("E:/readme.txt");
OutputStream out = new FileOutputStream("E:/readme2.txt");
// 使用【缓冲字节流】包装字节流,提高性能
BufferedInputStream bis = new BufferedInputStream(in); // 默认输入缓冲区大小: 8192
BufferedOutputStream bos = new BufferedOutputStream(out); // 默认输出缓冲区大小: 8192
// 使用流,直接使用扩大中转站的代码
byte[] buf = new byte[1024];
int len;
while ((len = bis.read(buf)) != -1) {
bos.write(buf, 0, len);
}
// 关闭流(只关闭顶层的即可)
bos.close();
bis.close();
有了缓冲流之后,中转站先从缓存区中读取数据,缓存区若为空,则读取一次硬盘,读8192个字节的内容,放入缓冲区
输出也是先输出到缓冲区,到满8192个字节后,一次性写到硬盘中
注意:关闭时,只关闭高层流即可,底层流自动关闭
何时将输出缓冲区的内容写入到硬盘?
- 输出缓冲区满,自动写入硬盘(刷新 flush)
- close() 会先刷新
- 手动的 flush()
5. 缓冲【字符】流
BufferedReader
优化:一次读取一行,一次写一行,提高效率(普通版本,最终版本用 PrintWriter)
// 创建流
BufferedReader br = new BufferedReader(new FileReader("E:/readme.txt")); // 默认大小:8192
BufferedWriter bw = new BufferedWriter(new FileWriter("E:readme2.txt"));
// 使用流
String str;
// 一次读取一行
while ((str = br.readLine()) != null) {
// 一次写一行
bw.write(str);
bw.newLine(); // 换行
}
// 关闭流
bw.close();
br.close();
readLine() 底层原理:还是一个一个字符的读取,然后 append() 放入到 StringBuilder(或者 char[] )中,直到遇到换行符,将 StringBuilder(char[])转换成 String 并返回
6. 数据流和对象流
能很方便的实现对各种基本类型和引用类型数据的读写,并保留其本身的类型
- 只有字节流,没有字符流
- 都是处理流,不是结点流
- 数据流:只能操作基本数据类型和字符串,对象流:还可以操作对象
- 写入的是二进制数据
- 写入的数据,需要使用对应的输入流来读取
DataInputStream
// 写操作
public static void write() throws IOException {
// 创建流
OutputStream out = new FileOutputStream("E:/readme2.txt");
// 加上缓冲流,提高读写速度(先写到缓冲区中)
BufferedOutputStream bos = new BufferedOutputStream(out);
// 然后用数据流包装缓冲流
DataOutputStream dos = new DataOutputStream(bos); // 【DataOutputStream】
// 使用流
dos.writeInt(123);
dos.writeDouble(3.14);
dos.writeUTF("lwclick");
dos.writeChar('学');
// 关闭流
dos.close();
}
// 读操作
public static void read() throws IOException {
InputStream in = new FileInputStream("E:/readme2.txt");
BufferedInputStream bis = new BufferedInputStream(in);
DataInputStream dis = new DataInputStream(bis);
// 按照写入的顺序读
dis.readInt();
dis.readDouble();
dis.readUTF();
dis.readChar();
dis.close();
}
ObjectInputStream
public static void write() throws IOException {
// 创建流
OutputStream out = new FileOutputStream("E:/readme2.txt");
// 加上缓冲流,提高读写速度(先写到缓冲区中)
BufferedOutputStream bos = new BufferedOutputStream(out);
ObjectOutputStream oos = new ObjectOutputStream(bos); // 【ObjectOutputStream】
// 使用流
oos.writeInt(123);
oos.writeDouble(3.14);
oos.writeUTF("lwclick");
oos.writeChar('学');
// 还可以写对象
oos.writeObject(new Date());
// 写自定义的类时,需要实现 Serializable 序列化接口
// public class Student implements Comparable<Student>, Serializable {}
oos.writeObject(new Student(1, "zhangsan", 89.0));
// 关闭流
oos.close();
}
public static void read() throws IOException, ClassNotFoundException {
InputStream in = new FileInputStream("E:/readme2.txt");
BufferedInputStream bis = new BufferedInputStream(in);
ObjectInputStream ois = new ObjectInputStream(bis);
// 按照写入的顺序读
ois.readInt();
ois.readDouble();
ois.readUTF();
ois.readChar();
ois.readObject(); // Date
ois.readObject(); // Student
ois.close();
}
7. 序列化和反序列化
什么是序列化?
- 序列化(Serialization):将对象(内存) ------> 字节数组 字节序列(外存、网络)
- 反序列化(DeSerialization):字节数组 字节序列(外存、网络) -------> 对象(内存)
什么时候需要序列化和反序列化?
- 存储或传输的时候, 比如存储到外存(硬盘)中 传输到网络
如何实现序列化和反序列化?
-
相应的类要实现 Serializable 接口
ObjectInputStream ois = new ObjectInputStream(...); ois.readObject(); // 反序列化 ObjectOutputStream oos = new ObjectOutputStream(...); oos.writeObject(new Student(1, "张三", 89));
需要注意的细节:
-
实现了 Serializable 接口,是为了源码中做判断,instanceof
-
static 属性不参与序列化(如果只是希望某个属性不参与序列化,使用
transient
修饰) -
实现了序列化的接口,都给一个固定的序列化版本号(用IDEA自动生成)
-
如果一个对象中,包含另一个对象的引用,想要实现序列化,那么该对象的引用,也必须实现序列化
public class Student implements Comparable<Student>, Serializable { private static final long serialVersionUID = 3023320127814394755L; // 固定的版本号 private int sno; private transient String name; // 不参与序列化 private Double score; private Clazz clazz; // 对象的引用,也必须实现 Serializable 接口 } // Clazz类 public class Clazz implements Serializable { }
8. 其他流
8.1 打印流
PrintStream
只有输出流,是一个包装流,字节流
PrintStream ps = System.out;
System.out.println("Hello World!");
// 效果是一样的,都是打印到控制台
ps.println("Hello World!!!");
// 打印到文件中(但是不管什么类型,打印到文件中,都变为 String 类型)
PrintStream toFile = new PrintStream("E:/readme2.txt");
toFile.println(3.14);
toFile.println("lwclick");
toFile.println(new Date());
PrintWriter
只有输出流,是一个包装流,字符流 按行读写
// 创建输入流
BufferedReader br = new BufferedReader(new FileReader("E:/readme.txt"));
// 创建输出流(PrintWriter)
PrintWriter pw = new PrintWriter("E:/readme2.txt"); // 底层代码中,带缓冲(BufferedWriter)
String str;
while ((str = br.readLine()) != null) {
pw.println(str);
}
br.close();
pw.close();
8.2 转换流
InputStreamReader
,读取来自键盘的一行行数据,写入到硬盘的文件中
用到了设计模式:适配器模式
Reader ----> InputStreamReader -----> InputStream
// 获取来自键盘的输入
InputStream is = System.in;
Reader reader = new InputStreamReader(is); // 将字节流 ----> 字符流 属于一种字符流
BufferedReader br = new BufferedReader(reader);
// 输出到硬盘
PrintWriter pw = new PrintWriter("E:/readme2.txt");
String str = br.readLine();
while (!"exit".equals(str)) {
pw.println(str);
}
pw.close();
br.close();
8.3 其他流
ByteArrayInputStream bais; // 字节数组
8.4 Java IO 中使用了哪些设计模式?
-
适配器模式: InputStreamReader
-
装饰模式:现组装(继承的一种替代方案)
InputStream fis = new FileInputStream("E:/readme.txt"); BufferedInputStream bis = new BufferedInputStream(fis); DataInputStream dis = new DataInputStream(bis);
针对字节流:
- 顶级类:InputStream
- 处理流的顶级类:FilterInputStream extends InputStream
9. 案例:复制文件夹
注意点:
- 使用字节流(可能有图片、视频、音频等二进制文件)
- 提高复制速度:使用缓冲 BufferedInputStream …
- 涉及到知识点:IO流(文件的复制)、递归操作、File类(文件夹的创建)
思路:
-
复制一个文件
// 复制一个文件 private static void copyFile(String sourceName, String destName) { BufferedInputStream bis = null; BufferedOutputStream bos = null; try { InputStream fis = new FileInputStream(sourceName); bis = new BufferedInputStream(fis); OutputStream fos = new FileOutputStream(destName); bos = new BufferedOutputStream(fos); byte[] buf = new byte[1024]; int len; while ((len = bis.read(buf)) != -1) { bos.write(buf, 0, len); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (bos != null) { bos.close(); } } catch (IOException e) { e.printStackTrace(); } try { if (bis != null) { bis.close(); } } catch (IOException e) { e.printStackTrace(); } } }
-
复制一个文件夹下的所有文件(不包括子文件夹)
private static void copyDir(String sourceDirNameIO流