存储与读取数据的解决方案。
I:input,O:output。用于读取数据,包括本地文件,网络文件等。
使用原则
- 随用随创
- 何时不用,何时关流
- (关流,即释放资源,若不关流,被操作的文件会被Java一直占用,会出现如无法删除等状况)
分类
- 按流的方向划分
- 输入流
- 输出流
- 按操作文件的类型划分
- 字节流 所有类型的文件(图片 文本 视频 音频等)
- 使用场景:拷贝任意类型的文件
- 字符流 纯文本文件(Windows自带记事本打开可看懂的文件) txt md xml lrc等
- 使用场景:读取/写入纯文本文件中的数据,尤其是中文文本
- 字节流 所有类型的文件(图片 文本 视频 音频等)
- 按流的角色不同划分
- 节点流 又叫基本流,向磁盘或网络进行基本的读写操作
- 处理流 又叫高级流,对一个已存在的流进行连接和封装,通过封装后的流来实现数据的读写功能
如上所示,因为四者都是抽象类,因此我们在使用IO流时,都是创建他们的子类对象。
下面,我从基本流与高级流的角度展开IO流的常用子类。
基本流
字节流 ByteStream
FileInputStream
操作本地文件的字节输入流 可将本地文件中的数据读取进程序。
使用步骤
- 创建字节输入流对象
- 细节:若文件不存在,直接报错 (而非输出流中的自行创建此文件)(因为创建一个无数据的文件没有意义)
- 读取数据
- 细节1 一次读一个字节,读出的数据是字符在ASCII码上对应的数字 并向后移动指针
- 细节2 若读不到数据 (读到了文件末尾) read方法返回-1
- 释放资源
FileInputStream fis=new FileInputStream("S:\\TestFolder\\ccc\\test.txt");
int b1 = fis.read();
System.out.println(b1);// 100 (d)
System.out.println((char) b1);// d
fis.close();
FileOutputStream
将程序的数据写入本地文件。
使用步骤
- 创建字节输出流对象
- 细节1 参数是字符串表示的路径/File对象均可
- 细节2 若文件不存在则创建一新的文件 但是要确保其父级路径存在
- 细节3 若文件存在 先删除原先的文件数据 向其中写入的数据会覆盖原先的内容
- 写出数据
- write方法的参数是整数 但写入本地文件中的是整数对应ASCII码的对应字符
- 释放资源
FileOutputStream fos=new FileOutputStream("S:\\TestFolder\\ccc\\test.txt");
fos.write(77);// M
fos.close();
字符流 CharStream
FileReader
其方法read()既可以空参一次读取一个字节,也可以带参一次读取多个字节,读取过后,方法底层还会解码并转为十进制且返回。
// 1 创建对象并关联本地文件
FileReader fr=new FileReader("S:\\TestFolder\\ash.txt");
// 2 读取数据 read()
// 字符流底层也是字节流 默认每次读取一个字节
// 若遇到中文一次读取多个,GBK一次读两个字节 UTF-8一次读三个字节
int ch;
while ((ch= fr.read())!=-1){
System.out.print((char)ch);// ch是read方法将文件中的文本解码为二进制并转为十进制的数字 // 1-2-4-7-8-9
}
// 3 释放资源(关流)
fr.close();
FileWriter
字节输出流FileOutputStream输出一个中文字符时,因为一次只能操作一个字节而汉字长于一个字节的缘故,会输出产生乱码
而字符输出流FileWriter会根据字符集编码方式进行编码,并将编码后的数据写入文件,从而避免上述情况
FileWriter fw=new FileWriter("S:\\TestFolder\\ash.txt",true); // true是打开续写开关
// 1 一次写一个字符
fw.write(25105);// "我"
// 2 一次写一个字符串
fw.write("灰烬、Alex");
// 3 一次写一个字符数组
char[]chars={'a','B','我'};
fw.write(chars);
// 我灰烬、AlexaB我
fw.close();
字符集
计算机当中,任意数据都是以二进制形式来存储的,最小的存储单元是一个字节,而ASCII字符集中,一个英文占一个字节。
简体中文版Windows默认使用GBK字符集,GBK字符集完全兼容ASCII字符集
- 一个英文占一个字节,二进制第一位是0
- 一个中文占两个字节,二进制高位字节第一位是1
Unicode字符集的UTF-8编码格式
- 一个英文占一个字节,二进制第一位是0,转成十进制是正数
- 一个中文占三个字节,二进制第一位是1,第一个字节转成十进制是负数
因此,为了避免乱码的产生,不要用字节流读取文本文件,且在编码解码时要使用相同字符编码
// Java的编码/解码方法
// 编码
String str="灰烬、Alex";
// 默认方式编码 UTF-8
byte[]bytes1=str.getBytes();
System.out.println(Arrays.toString(bytes1)); // [-25, -127, -80, -25, -125, -84, -29, -128, -127, 65, 108, 101, 120]
// 指定方式编码
byte[] bytes2 = str.getBytes("GBK");
System.out.println(Arrays.toString(bytes2)); // [-69, -46, -67, -3, -95, -94, 65, 108, 101, 120]
// 解码
// 默认方式 UTF-8
String str1=new String(bytes1);
System.out.println(str1); // 灰烬、Alex
String str2=new String(bytes2);
System.out.println(str2); // �ҽ���Alex 由于编码与解码的方式不一致而导致的乱码
// 指定方式解码
String str3=new String(bytes2,"GBK");
System.out.println(str3); // 灰烬、Alex
高级流
缓冲流
缓冲流自带长度为8192的缓冲区,所以一般适用于大量读写操作的场景。
缓冲流共四种:
- 字节缓冲输入流 BufferedInputStream
- 字节缓冲输出流 BufferedOutputStream
- 字符缓冲输入流 BufferedReader
- 字符缓冲输出流 BufferedWriter
其中,BufferedReader独有的方法readLine()与BufferedWriter独有的方法newLine()可以一次进行整行的读/写操作。
BufferedWriter bw=new BufferedWriter(new FileWriter("S:\\TestFolder\\ccc\\test2.txt"));
bw.write("卧槽!魂批!");
bw.newLine();// 多个平台统一的换行方式
bw.write("魂批怎么你了?");
bw.newLine();
// 卧槽!魂批!
// 魂批怎么你了?
bw.close();
转换流
是字符流与字节流之间的桥梁,一般仅应用于字节流想要使用字符流中方法的情况。
- 字符转换输入流 InputStreamReader
- 字符转换输出流 OutputStreamWriter
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("S:\\TestFolder\\ccc\\test2.txt")));
String line;
while ((line=br.readLine())!=null) System.out.println(line);
// 卧槽!魂批!
// 魂批怎么你了?
br.close();
序列化流
又叫对象操作输出流,可将Java对象写入本地文件,反之,将文件的对象读到程序中就叫反序列化流/对象操作输入流。
作用
public class Student implements Serializable {
@Serial
private static final long serialVersionUID = -7320567096138221572L;
// Serializable 可序列化的 一旦实现这个接口 表示此类可以被序列化
// Serializable 接口内无抽象方法,此般接口称作 标记型接口
private String name;
private int age;
private transient String address;// transient 瞬间的 该关键字标记的成员变量不参与序列化过程
public Student() {
}
public Student(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
/**
* 获取
* @return address
*/
public String getAddress() {
return address;
}
/**
* 设置
* @param address
*/
public void setAddress(String address) {
this.address = address;
}
public String toString() {
return "Student{name = " + name + ", age = " + age + ", address = " + address + "}";
}
Student s1=new Student("zhangsan",23,"London");
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("S:\\TestFolder\\ccc\\test1.txt"));
oos.writeObject(s1);
oos.close();
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("S:\\TestFolder\\ccc\\test1.txt"));
Student o =(Student) ois.readObject();
System.out.println(o);
// Student{name = zhangsan, age = 23, address = null}
ois.close();
打印流
一般指字节打印流 PrintStream与字符打印流PrintWriter两个类。
系统标准输出流 System.out.println()
通过System.out即可获取系统打印流对象,此打印流在虚拟机启动时由虚拟机创建,默认指向控制台。
PrintStream out = System.out;
out.println("有点东西"); // 有点东西
out.println("啊对对对"); // 啊对对对
压缩流
将一个文件夹打包成一个压缩包(ZipOutputStream)的操作,反之,将压缩包解压即可使用解压缩流(ZipInputStream)。
其中,解压的本质就是将压缩包内每一个文件或文件夹读取出来,按层级拷贝在目的地中。
核心
- 运用方法getNextEntry()获取压缩包里的每一个zipEntry对象
- 每一个zipEntry对象即表示每一个压缩包中的获取到的文件或文件夹
// 1 创建数据源文件对象
File src = new File("S:\\TestFolder\\ddd");
// 2 创建src的父级目录对象 表示压缩包将要存储的位置
File parent = src.getParentFile();
// 3 创建压缩包对象
File dest = new File(parent, src.getName() + ".zip");
// 4 创建压缩流
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(dest));
// 5 获取src中每一个文件或文件夹 (ZipEntry)放入压缩包
toZip(src,zos,src.getName());
// 6 关流
zos.close();