一:IO 流的概述
1. 什么是 IO 流?
存储和读取数据的解决方法
I:input O:output
流:像水流一样传输数据
2. IO 流的作用?
用于读写数据(本地文件,网络)
3. IO 流按照流向可以分类哪两种流?
输出流:程序 ----> 文件
输入流:文件 ----> 程序
4. IO 流按照操作文件的类型可以分为哪两种流?
字节流:可以操作所有类型的文件
字符流:只能操作纯文本文件
5. 什么是纯文本文件?
用 windows 系统自带的记事本打开并且能读得懂的文件 txt 文件、md 文件、xml 文件、lrc 文件等
6. IO 流基本体系
二:IO 流基本用法
1. 字节流
1.1 fileOutputStream
**作用:**可以把程序中的数据写到本地文件上,是字节流的基本流。
书写步骤
// 1. 创建对象
FileOutputStream fos = new FileOutputStream("hmio\\a.txt");
// 2. 写入数据
fos.write(88);
// 3. 释放资源
fos.close();
字节输出流书写细节
① 创建字节输出流对象
细节一:参数是字符串表示的路径或者 File 对象都可以
细节二:如果文件不存在会创建一个新文件,但是要保证父级路径是存在的
细节三:如果文件已经存在,则会清空文件
② 写数据
细节:write 方法的参数是整数,但实际上写到本地文件中的是整数在 ASCII 上对应的字符
③ 释放资源
细节:每次使用完之后都要释放资源
如果没有释放资源,那么这个资源 / 文件就会一直被占用着。无法进行删除等操作。
三种写出多个数据的方式
// 1. 创建对象
FileOutputStream fos = new FileOutputStream("hmio\\a.txt");
// 2. 写出数据
// fos.write(97); // a
// fos.write(98); // b
byte[] bytes = {97, 98, 99 ,100, 101};
/*fos.write(bytes);*/
fos.write(bytes, 1, 2); // b c
// 3. 释放资源
fos.close();
换行与续写
// 换行与续写
// 1. 换行直接写个换行符就好了
// 2. 续写在创建 FileOutputStream 的时候开启续写开关 true(默认为 false)
// 1. 创建对象
FileOutputStream fos = new FileOutputStream("hmio\\a.txt", true);
// 2. 写出数据
String str = "hello";
byte[] bytes = str.getBytes();
fos.write(bytes);
// 再次写入一个换行符就好了
// 换行符:windows:\r\n Linux:\n Mac:\r
// Java 中只写 \r 或者 \n 也可以起到换行作用
String wrap = "\r\n";
byte[] bytes2 = wrap.getBytes();
fos.write(bytes2);
String str2 = "666";
byte[] bytes3 = str2.getBytes();
fos.write(bytes3);
// 3. 释放资源
fos.close();
1.2 fileInputStream
基本用法
// 1. 创建对象
FileInputStream fis = new FileInputStream("hmio\\a.txt");
// 2. 读取资源
int b1 = fis.read();
System.out.println((char)b1);
// 3. 释放资源
fis.close();
FileInputStream 书写细节
① 创建字节输入流对象
细节一:如果文件不存在,就直接报错
② 读取数据
细节二:一次读一个字节。读出来的是数据在 ASCII 上对应的数字
细节二:读到文件末尾了,read 方法返回 -1。
③ 释放资源
细节一:每次使用完必须要释放资源。
循环读取整个文件
// 1. 创建对象
FileInputStream fis = new FileInputStream("hmio\\a.txt");
// 2. 读取资源
int b;
while ((b = fis.read()) != -1) {
System.out.println((char) b);
}
// 3. 释放资源
fis.close();
3. 文件拷贝
// 1. 创建对象
FileInputStream fis = new FileInputStream("D:\\a.txt");
FileOutputStream fos = new FileOutputStream("hmio\\copy.txt");
// 2. 拷贝
int b;
while ((b = fis.read()) != -1) {
fos.write(b);
}
// 3. 释放资源
// 规则:先开的流最后关闭
fos.close();
fis.close();
这只适用于小文件的拷贝,如果是大文件的拷贝会相当耗时,因为这个拷贝的方式是每拷贝一个字节建立一次连接,拷贝的文件越大,建立的连接数量越多。
我们可以使用 read(byte[] buffer) 的方法来读取数据,这是一次读取一个字节组的数据,这样就可以做到只需要建立一次连接就可以拷贝一批量的字节。
大文件的拷贝原理:
// 1. 创建对象
FileInputStream fis = new FileInputStream("D:\\a.txt");
// 2. 拷贝
byte[] bytes = new byte[2];
// 一次读取多个字节数据,具体读多少,跟数据的长度有关。
int len = fis.read(bytes);
System.out.println(len);
String str = new String(bytes, 0, len);
System.out.println(str);
int len2 = fis.read(bytes);
System.out.println(len2);
String str2 = new String(bytes, 0, len2);
System.out.println(str2);
int len3 = fis.read(bytes);
System.out.println(len3);
String str3 = new String(bytes, 0, len3);
System.out.println(str3);
fis.close();
大文件拷贝示例:
// 1. 创建对象
FileInputStream fis = new FileInputStream("D:\\ProMaterisals\\idea\\HMIO\\hmio\\a.txt");
FileOutputStream fos = new FileOutputStream("hmio\\copy2.txt");
// 2. 拷贝
int len;
byte[] bytes = new byte[1024 * 1024 * 5]; // 5m
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
// 3. 释放资源
fos.close();
fis.close();
利用 try…catch… finally 捕获拷贝文件中代码出现的异常
// 1. 创建对象
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("D:\\ProMaterisals\\idea\\HMIO\\hmio\\a.txt");
fos = new FileOutputStream("hmio\\copy2.txt");
// 2. 拷贝
int len;
byte[] bytes = new byte[1024 * 1024 * 5]; // 5m
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 3. 释放资源
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
4. 字符集
ASCII、GBK 详解
1. 在计算机中,任意数据都是以二进制的形式存储的。
2. 计算机中最小的存储单位是一个字节。
3. ASCII 字符集中,一个英文占一个字节。
4. 简体中文版 Windows,默认使用 GBK 字符集。
5. GBK 字符集完全兼容 ASCII 字符集。
1. 一个英文占一个字节,二进制第一位是 0
2. 一个中文占两个字节,二进制高位字节的第一位是 1
Unicode 详解
1. Unicode 字符集的 UTF-8 编码格式
1. 一个英文占一个字节,二进制第一位是 0,转成十进制是正数
2. 一个中文占三个字节,二进制第一位是 1,第一个字节转成十进制是负数
乱码出现的原因:
1. 使用字节流读取数据的时候未读完整整个汉字(字节流一次读取一个字节)。
2. 编码和解码时的方式不统一。
5. 字符流
5.1 FileReader
read (空参)方法详解:
// 1. 创建对象并关联本地文件
FileReader fr = new FileReader("hmio\\a.txt");
// 2. 读取数据
// 字符流的底层也是字节流,默认也是一个字节一个字节的读取的。
// 如果遇到中文就会一次读取多个,GBK 一个读两个字节,UTF-8(IDEA 默认) 一次读取三个字节。
// read() 细节:
// 1. read():默认也是一个字节一个字节的读取,如果遇到中文就会一次读取多个
// 2. 在读取之后,方法的底层还会进行解码并转成十进制。
// 最终把这个十进制作为返回值
// 这个十进制的数据也表示在字符集上的数字
// 英文:文件里面二进制的数据 0110 0001
// read 方法进行提取,解码并转成十进制 97
// 中文:文件里面的二进制数据:11100110 10110001 10001001
// read 方法进行提取,解码并转成十进制 27721
int ch;
while ((ch = fr.read()) != -1) {
System.out.print((char) ch);
}
// 3. 释放资源
fr.close();
read(有参)方法详解
// 1. 创建对象并关联本地文件
FileReader fr = new FileReader("hmio\\a.txt");
// 2. 读取数据
char[] chars = new char[2];
int len;
// read(chars):读取数据,解码,强转三步合并了,把强转之后的字符放到数据当中
// 空参 read + 强转类型转换
while ((len = fr.read(chars)) != -1) {
// 把数据中的数据编程字符串再打印
System.out.println(new String(chars, 0, len));
}
// 3. 释放资源
fr.close();
5.2 FileWriter
书写细节
① 创建字符输出流对象
细节1:参数是字符串表示的路径或者 File 对象都可以的
细节2:如果文件不存在会创建一个新的文件,但是要保证父级路径是存在的
细节3:如果文件已经存在,则会清空文件,如果不想被清空可以打开续写
② 写数据
细节:如果 writer 方法的参数是整数,但是实际上写道本地文件中的是整数在字符集上对应的字符
③ 释放资源
细节:每次使用完之后释放资源
5.3 字符流原理解析
缓冲区
① 创建字符输入流对象
底层:关联文件,并创建缓冲区(长度为 8129 的字节数组)
② 读取数据
1.判断缓冲区中是否有数据可以读取
2. 缓冲区中没有数据:就从文件中获取数据,装到缓冲区中,每次尽可能装满缓冲区,如果文件也没有数据,返回 -1
3. 缓冲区有数据:就从缓冲区中读取。
(1) 空参 read 方法:一次读取一个字节,遇到中文一次读多个字节,把字节解码并转成十进制返回
(2) 有参 read 方法:把读取字节,解码,强转三步合并了,强转之后的字节放到数组中
以下验证是否有缓冲区(a 文件中要多于 8129 字节的数据,程序会把存入程序中的数据读取出来,大于 8129 字节的数据无法读取)
FileReader fr = new FileReader("hmio\\a.txt");
fr.read(); // 会把文件中的数据读取到缓冲区
// 清空文件
FileWriter fw = new FileWriter("hmio\\a.txt");
// 会把缓冲区的数据全部读取完毕
// 但是也只能打印缓冲区中的数据,文件剩余的数据不能被打印出来
int ch;
while ((ch = fr.read()) != -1) {
System.out.println((char) ch);
}
fw.close();
fr.close();
缓冲区中的数据什么时候会写入目标文件中?
① 缓冲区满了之后
FileWriter fw = new FileWriter("hmio\\a.txt");
for (int i = 0; i < 8193; i++) {
fw.write(97);
}
② 执行 flush 或者 close 方法之后
FileWriter fw = new FileWriter("hmio\\a.txt");
fw.write("阿巴1");
fw.write("阿巴2");
fw.write("阿巴3");
fw.flush();
fw.write("阿巴4");
FileWriter fw = new FileWriter("hmio\\a.txt");
fw.write("阿巴4");
fw.write("阿巴5");
fw.write("阿巴6");
fw.write("阿巴7");
fw.close();
flush 跟 close 的区别:
flush 执行完之后可以继续在后面写数据。close 方法执行完之后,连接就断开可。
6. 练习
6.1 拷贝文件夹
public class Test01 {
public static void main(String[] args) throws IOException {
// 拷贝一个文件夹,考虑子文件夹
// 1. 创建对象表示数据源
File src = new File("D:\\ProMaterisals\\idea\\HMIO\\testDir");
// 2. 创建对象表示目的地
File dest = new File("D:\\ProMaterisals\\idea\\HMIO\\testDirCopy");
// 调用方法开始拷贝
copyDir(src, dest);
}
/**
* 拷贝文件夹
* @param src
* @param dest
*/
private static void copyDir(File src, File dest) throws IOException {
dest.mkdirs();
// 递归
// 1. 进入数据源
File[] files = src.listFiles();
// 2. 遍历数组
for (File file : files) {
if (file.isFile()) {
// 3. 判断文件,拷贝
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(new File(dest, file.getName()));
byte[] bytes = new byte[1024];
int len;
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
fos.close();
fis.close();
} else {
// 4. 判断文件夹,递归
copyDir(file, new File(dest, file.getName()));
}
}
}
}
6.2 文件加密与解密
加密解密的原理:
在 Java 代码中,当一个数字两次异或(^) 同一个数字的时候,得到的结果是原来那个数字。
System.out.println(100 ^ 10); // 110
System.out.println(110 ^ 10); // 100
FileInputStream fis = new FileInputStream("hmio\\encj.jpg");
FileOutputStream fos = new FileOutputStream("hmio\\redu.jpg");
// 加密处理
int b;
while ((b = fis.read()) != -1) {
fos.write(b ^ 2);
}
// 释放资源
fos.close();
fis.close();
解密的时候只需要把已加密的文件重新异或一次加密时异或的数字就好了。
6.3 修改文件中的数据
// 1. 读取数据
FileReader fr = new FileReader("hmio\\a.txt");
StringBuilder sb = new StringBuilder();
int ch;
while ((ch = fr.read()) != -1) {
sb.append((char) ch);
}
fr.close();
System.out.println(sb);
// 排序
Integer[] arr = Arrays.stream(sb.toString()
.split("-"))
.map(Integer::parseInt)
.sorted()
.toArray(Integer[]::new);
// System.out.println(Arrays.toString(arr));
FileWriter fw = new FileWriter("hmio\\a.txt");
String s = Arrays.toString(arr).replace(", ", "-");
s = s.substring(1, s.length() - 1);
System.out.println(s);
fw.write(s);
fw.close();
三:高级流
1. 缓冲流
1.1 字节缓冲流拷贝文件
字节缓冲流创建的时候也会创建一个长度为 8192 的缓冲区
一次读取单个字节
// 1. 创建缓冲流对象
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("hmio\\a.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("hmio\\copy.txt"));
// 2. 循环读取并写道目的地
int b;
while ((b = bis.read()) != -1) {
bos.write(b);
}
// 3. 释放资源
bos.close();
bis.close();
一次读取多个字节
// 1. 创建缓冲流对象
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("hmio\\a.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("hmio\\copy2.txt"));
// 2. 拷贝
byte[] bytes = new byte[1024];
int len;
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes, 0, len);
}
// 3. 释放资源
bos.close();
bis.close();
1.2 字符缓冲流
输入
// 1. 创建缓冲流对象
BufferedReader br = new BufferedReader(new FileReader("hmio\\a.txt"));
// 2. 读取数据
// readLine 方法一次会读取一整行,遇到回车换行结束
// 但是它不会把回车换行读取到内存当中
/*String line = br.readLine();
System.out.println(line);*/
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
br.close();
输出
BufferedWriter bw = new BufferedWriter(new FileWriter("hmio\\b.txt", true));
bw.write("123");
bw.newLine();
bw.write("456");
bw.newLine();
bw.close();
缓冲流总结
1. 缓冲流有几种?
1. 字节缓冲输入流:BufferedInputStream
2. 字节缓冲输出流:BufferedOutputStream
3. 字节缓冲输入流:BufferedReader
4. 字节缓冲输出流:BufferedWriter
2. 缓冲流为什么能提高性能?
1. 缓冲流自带长度为 8192 的缓冲区
2. 可以显著提高字节流的读写性能
3. 对于字符流提升不显著,对于字符缓冲流而言关键点是两个特有的方法
3. 字符缓冲流两个特有的方法是什么?
4. 字符缓冲输入流 BufferedReader:readLine() (一次读取一行
2. 字符缓冲输出流 BufferedWriter:newLine() (换行,可跨平台使用
缓冲流综合练习
对文件中的首列序号进行排序
8.愿陛下托臣以讨贼兴复之效,不效,则治臣之罪,以告先帝之灵。若无兴德之言,则责攸之、祎、允等之慢,以彰其咎;陛下亦宜自谋,以咨诹善道,察纳雅言,深追先帝遗诏,臣不胜受恩感激。
3.侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯,是以先帝简拔以遗陛下。愚以为宫中之事,事无大小,悉以咨之,然后施行,必能裨补阙漏,有所广益。
4.将军向宠,性行淑均,晓畅军事,试用于昔日,先帝称之曰能,是以众议举宠为督。愚以为营中之事,悉以咨之,必能使行阵和睦,优劣得所。
2.宫中府中,俱为一体,陟罚臧否,不宜异同。若有作奸犯科及为忠善者,宜付有司论其刑赏,以昭陛下平明之理,不宜偏私,使内外异法也。
1.先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。然侍卫之臣不懈于内,忠志之士忘身于外者,盖追先帝之殊遇,欲报之于陛下也。诚宜开张圣听,以光先帝遗德,恢弘志士之气,不宜妄自菲薄,引喻失义,以塞忠谏之路也。
9.今当远离,临表涕零,不知所言。
6.臣本布衣,躬耕于南阳,苟全性命于乱世,不求闻达于诸侯。先帝不以臣卑鄙,猥自枉屈,三顾臣于草庐之中,咨臣以当世之事,由是感激,遂许先帝以驱驰。后值倾覆,受任于败军之际,奉命于危难之间,尔来二十有一年矣。
7.先帝知臣谨慎,故临崩寄臣以大事也。受命以来,夙夜忧叹,恐托付不效,以伤先帝之明,故五月渡泸,深入不毛。今南方已定,兵甲已足,当奖率三军,北定中原,庶竭驽钝,攘除奸凶,兴复汉室,还于旧都。此臣所以报先帝而忠陛下之职分也。至于斟酌损益,进尽忠言,则攸之、祎、允之任也。
5.亲贤臣,远小人,此先汉所以兴隆也;亲小人,远贤臣,此后汉所以倾颓也。先帝在时,每与臣论此事,未尝不叹息痛恨于桓、灵也。侍中、尚书、长史、参军,此悉贞良死节之臣,愿陛下亲之信之,则汉室之隆,可计日而待也。
/**
* 将乱序的《出师表》进行排序
*/
// 1. 读取数据
BufferedReader br = new BufferedReader(new FileReader("hmio\\csb.txt"));
String line;
TreeMap<Integer, String> tm = new TreeMap<>();
while ((line = br.readLine()) != null) {
String[] arr = line.split("\\.");
tm.put(Integer.parseInt(arr[0]), arr[1]);
}
br.close();
// System.out.println(tm);
// 2. 写出数据
BufferedWriter bw = new BufferedWriter(new FileWriter("hmio\\result.txt"));
Set<Map.Entry<Integer, String>> entries = tm.entrySet();
for (Map.Entry<Integer, String> entry : entries) {
String value = entry.getValue();
bw.write(value);
bw.newLine();
}
bw.close();
2. 转换流
转换流是字符流和字节流之间的桥梁
2.1 转换流读取与写出数据
利用转换流按照指定的字符编码读取数据
/*// 1. 创建对象并指定字符编码
InputStreamReader isr = new InputStreamReader(new FileInputStream("hmio\\gbkfile.txt"), "GBK");
// 2. 读取数据
int ch;
while ((ch = isr.read()) != -1) {
System.out.println((char) ch);
}
// 3. 释放资源
isr.close();*/
/**
* 需要使用 JDK11+
*/
FileReader fr = new FileReader("hmio\\\\gbkfile.txt", Charset.forName("GBK"));
// 2. 读取数据
int ch;
while ((ch = fr.read()) != -1) {
System.out.println((char) ch);
}
// 3. 释放资源
fr.close();
利用转换流按照指定字符编码写出
/**
* 利用转换流按照指定字符编码写出
*/
/*// 1. 创建转换流对象
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("hmio\\b.txt"), "GBK");
// 2. 写出数据
osw.write("你好你好");
// 3. 关闭连接
osw.close();*/
/**
* 替代方案 JDK11 +
*/
FileWriter fw = new FileWriter("hmio\\c.txt", Charset.forName("GBK"));
fw.write("你好你好");
fw.close();
转换流练习
使用转换流将读取的 GBK 文件转换为 UTF-8
/**
* 将本地文件中的 GBK 文件,转换成 UTF-8
*/
// 1. JDK11 以前的方案
/*InputStreamReader isr = new InputStreamReader(new FileInputStream("hmio\\b.txt"), "GBK");
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("hmio\\d.txt"), "UTF-8");
int b;
while ((b = isr.read()) != -1) {
osw.write(b);
}*/
// 2. 替代方案
FileReader fr = new FileReader("hmio\\b.txt", Charset.forName("GBK"));
FileWriter fw = new FileWriter("hmio\\e.txt", Charset.forName("UTF-8"));
int b;
while ((b = fr.read()) != -1) {
fw.write(b);
}
fw.close();
fr.close();
利用字节流读取文件的数据,每次读取一整行,而且不能乱码
/**
* 利用字节流读取文件的数据,每次读取一整行,而且不能乱码
*
* 1. 字节流读取中文的时候,是会出现乱码的,但是字符流可以搞定
* 2. 字节流里面是没有读取一整行的方法的,只有字符缓冲流可以搞定
*/
// FileInputStream fis = new FileInputStream("hmio\\a.txt");
// InputStreamReader isr = new InputStreamReader(fis);
// BufferedReader br = new BufferedReader(isr);
//
// String str = br.readLine();
// System.out.println(str);
//
// br.close();
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("hmio\\a.txt")));
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
br.close();
转换流总结
1. 转换流名字
1. 字符转换输入流:InputStreamReader
2. 字符转换输入流:OuputStreamWriter
2. 转换流的作用:
1. 指定字符集读写数据(JDK11 之后就被淘汰)
2. 字节流想要使用字符流中的方法了
3. 序列化流
3.1 序列化流读取/输出对象
序列化流写出对象
/**
* 利用序列化流/对象操作流,把一个对象写到本地中
*/
// 1. 创建对象
Student stu = new Student("zhangsan", 10);
// 2. 创建序列化流的对象/对象操作输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hmio\\a.txt"));
// 3. 写出数据
oos.writeObject(stu);
// 4. 释放资源
oos.close();
反序列化流读取对象
/**
* 利用反序列化流,把文件中的对象读取到程序中。
*/
// 1. 创建反序列化流的对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hmio\\a.txt"));
// 2. 读取数据
Object o = ois.readObject();
System.out.println(o);
// 3. 释放资源
ois.close();
3.2 序列化流细节汇总
-
使用序列化流将对象写道文件时,需要让 Javabean 类实现 Serializable 接口。否则,会出现 NotSerializableException 异常。
-
序列化流写到文件中的数据不能修改,一旦修改就无法再次读回来了。
-
序列化对象后,修改了 Javabean 类,再次反序列化,会不会有问题?
会出问题,会抛出 InvalidClassException 异常。
解决方案:给 Javabean 类添加 serialVersionUID(序列号、版本号)
- 如果一个对象的某个成员变量的值不想被序列化,又如何实现?
解决方案:给该成员变量加 transient 关键字修饰,该关键字标记的成员变量不参与序列化过程。
序列化流练习
序列化读写多个对象
序列化的 Javabean 对象:Student.java
import java.io.Serializable;
public class Student implements Serializable {
private static final long serialVersionUID = -7424513845312147863L;
private String name;
private int age;
private String address;
public Student() {
}
public Student(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", address='" + address + '\'' +
'}';
}
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 getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
序列化写出对象
/**
* 将多个自定义对象序列化到文件中,但是对象的个数不确定,该如何操作呢?
*/
// 1. 序列化多个对象
Student s1 = new Student("zhangsan", 12, "魔都");
Student s2 = new Student("lisi", 13, "帝都");
Student s3 = new Student("wangwu", 14, "妖都");
ArrayList<Student> list = new ArrayList<>();
list.add(s1);
list.add(s2);
list.add(s3);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hmio\\a.txt"));
oos.writeObject(list);
oos.close();
序列化读取对象
// 1. 创建反序列化流对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hmio\\a.txt"));
// 2. 读取数据
ArrayList<Student> list = (ArrayList<Student>) ois.readObject();
for (Student student : list) {
System.out.println(student);
}
// 3. 释放资源
ois.close();
4. 打印流
打印流
分类:打印流一般是指:PrintStream,PrintWriter 两个类
特点一:打印流只操作文件目的地,不操作数据源
特点二:特有的写出方法可以实现,数据原样写出
例如:打印:97 文件中:97
特点三:特有的写出方法,可以实现自动刷新,自动换行
打印一次数据 = 写出 + 换行 + 刷新
4.1 字节打印流
字节打印流底层没有缓冲区
// 1. 创建字节打印流对象
PrintStream ps = new PrintStream(new FileOutputStream("hmio\\a.txt"), true, "UTF-8");
ps.println(97); // 写出 + 自动刷新 + 自动换行
ps.print(true);
ps.println();
ps.printf("%s爱上了%s", "阿珍","阿强");
ps.close();
4.2 字符打印流
字节打印流底层有缓冲区,想要自动刷新需要开启
// 1. 创建字符打印流对象
PrintWriter pw = new PrintWriter(new FileWriter("hmio\\a.txt"), true);
// 2. 写出数据
pw.println("你今天终于叫我名字了,虽然叫错了,但是没关系,我马上改");
pw.print("你好你好");
pw.printf("%s爱上了%s", "阿珍","阿强");
// 3. 释放资源
pw.close();
打印流小结
打印流有几种?各有什么特点?
有字节打印流和字符打印流两种。
打印流不操作数据源,只能操作目的地。
字节打印流:默认自动刷新,特有的 println 自动换行。
5. 解压缩流
5.1 解压流
public static void main(String[] args) throws IOException {
// 1. 创建一个 File 表示要解压的压缩包
File src = new File("D:\\ProMaterisals\\idea\\HMIO\\testZip.zip");
// 2. 创建一个 File 表示要解压的目的地
File dest = new File("D:\\ProMaterisals\\idea\\HMIO\\");
unzip(src, dest);
}
/**
* 解压的方法
* @param src 要解压的压缩包地址
* @param dest 要解压的目的地地址
*/
public static void unzip(File src, File dest) throws IOException {
// 创建一个解压缩流
ZipInputStream zip = new ZipInputStream(new FileInputStream(src));
// 先获取到压缩包里面的每一个 zipentry 对象
// 表示当前在压缩包当中获取到的文件或者文件夹
ZipEntry entry;
while ((entry = zip.getNextEntry()) != null) {
System.out.println(entry);
if (entry.isDirectory()) {
// 文件夹:需要在目的地 dest 处创建一个相同的文件夹
File file = new File(dest, entry.toString());
file.mkdirs();
} else {
// 文件:需要读取到压缩包中的文件,并把它存放在目的地 dest 文件夹中(按照层级目录进行存放)
FileOutputStream fos = new FileOutputStream(new File(dest, entry.toString()));
int b;
while ((b = zip.read()) != -1) {
// 写到目的地
fos.write(b);
}
fos.close();
// 表示在压缩包中的文件处理完毕了
zip.closeEntry();
}
}
zip.close();
}
5.2 压缩流
压缩单个文件
public class ZipStreamDemo2 {
public static void main(String[] args) throws IOException {
/**
* 压缩流
*
*/
// 1. 创建 File 对象表示要压缩的文件
File src = new File("D:\\ProMaterisals\\idea\\HMIO\\ziptest.txt");
// 2. 创建 File 对象表示压缩包的位置
File dest = new File("D:\\ProMaterisals\\idea\\HMIO\\");
// 3. 调用方法压缩
toZip(src, dest);
}
/**
* 压缩文件
* @param src 要压缩的文件
* @param dest 压缩包的位置
*/
public static void toZip(File src, File dest) throws IOException {
// 1. 创建压缩流,关联压缩包
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File(dest, "ziptest.zip")));
// 2. 创建 ZipEntry 对象,表示压缩包里面的每一个文件和文件夹
// 参数:压缩包里面的路径
ZipEntry entry = new ZipEntry("ziptest.txt");
// 3. 把 ZipEntry 对象放到压缩包中
zos.putNextEntry(entry);
// 4. 把 src 文件中的数据写到压缩包当中
FileInputStream fis = new FileInputStream(src);
int b;
while ((b = fis.read()) != -1) {
zos.write(b);
}
zos.closeEntry();
zos.close();
}
}
public class ZipStreamDemo3 {
public static void main(String[] args) throws IOException {
/**
* 把文件夹压缩成一个压缩包
*/
// 1. 创建 File 对象表示要压缩文件夹的
File src = new File("D:\\ProMaterisals\\idea\\HMIO\\testZip1");
// 2. 创建 File 对象表示压缩包放在那里
File destParent = src.getParentFile(); // src 的 testZip 文件的父级目录
// 3. 创建 File 文件表示压缩包的路径
File dest = new File(destParent, src.getName() + ".zip");
// 4. 创建压缩流关联压缩包
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(dest));
// 5. 获取 src 里面的每个文件
toZip(src, zos, src.getName());
// 6. 释放资源
zos.close();
}
/**
* 获取 src 的每个文件,变成 ZipEntry 对象,放入压缩包中
* 但是这个方法目前有 BUG,如果要压缩的文件夹里面没有文件,不会被压缩进压缩包
* @param src 数据源
* @param zos 压缩流
* @param name 压缩包内部的路径
*/
public static void toZip(File src, ZipOutputStream zos, String name) throws IOException {
// 1. 进入 src 方法
File[] files = src.listFiles();
// 2. 遍历数据
for (File file : files) {
if (file.isFile()) {
// 3.判断-文件,变成 ZipEntry 对象,放入压缩包中
ZipEntry entry = new ZipEntry(name + "\\" + file.getName());
zos.putNextEntry(entry);
// 读取文件中的数据,写道压缩包中
FileInputStream fis = new FileInputStream(file);
int b;
while ((b = fis.read()) != -1) {
zos.write(b);
}
fis.close();
zos.closeEntry();
} else {
// 4. 判断-文件夹,递归
toZip(file, zos, name + "\\" + file.getName());
}
}
}
}