高级流
在IO流(一)中我们学习了IO流的基本输入输出流,以及一个高级流——缓冲流,这篇文章我们继续学习IO高级流。
常见错误===========
Exception in thread “main” java.io.FileNotFoundException: D:\bbb (拒绝访问。)
这可能是因为你的代码中的某处将D:\bbb这个文件打开了,正在操作它,但是在另一处地方又试图打开它操作它,所以在另一处代码就会报这个错误。
一、转换流
转换流是字符流的一种
转换流的作用:
它可以将内容以字节读入内存,然后按照指定的字符集转换为文本。
练习
做两个练习来认识了解转换流:
练习一
需求1代码: 手动创建一个GBK的文件,把文件中的中文读取到内存中,不能出现乱码
/**
* @author Watching
* * @date 2023/5/23
* * Describe:
* 使用转换流指定字符集读取文件内容
* 读取d.txt的文件内容,d.txt的编码格式为GBK
*/
public class ConvertStreamDemo {
public static void main(String[] args) throws IOException {
/**
* 但是这种方式在jdk11的时候被替代了
* 在JDK11中,FileReader有了一个新的构造方法
* new FileReader(File file,Charset charset);
* 这个构造方法可以直接指定读取时所用的字符集,就不需要转换流了
*/
//指定读取的字符集为GBK
// InputStreamReader isr = new InputStreamReader(new FileInputStream("d.txt"),"GBK");
// int read;
// while ((read = isr.read()) != -1){
// System.out.print((char)read);
// }
// isr.close();
}
}
需求2代码:把一段中文按照GBK的方式写到本地文件
转换流可以指定读入和输出的字符集
/**
* @author Watching
* * @date 2023/5/23
* * Describe:
* 使用转换流以指定的字符集输出数据
*/
public class ConvertStreamDemo1 {
public static void main(String[] args) throws IOException {
/**
* 这种写法在JDK11同样被代替了,我们可以使用FileWriter的新构造方法
* new FileWriter(File file,Charset charset)
*/
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("e.txt"),"GBK");
osw.write("你好,这段文字的编码为GBK");
osw.close();
}
}
需求3代码:将本地文件中的GBK文件,转成UTF-8
转换流可以指定读入和输出的字符集
/**
* @author Watching
* * @date 2023/5/23
* * Describe:
* 使用字节转换流将编码为GBK的文件内容读取,并以UTF-8的编码方式写出到另一个文件
*/
public class ConvertStreamDemo2 {
public static void main(String[] args) throws IOException {
InputStreamReader isr = new InputStreamReader(new FileInputStream("d.txt"),"GBK");
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("f.txt"), StandardCharsets.UTF_8);
char[] chars = new char[1024];
int len;
while((len = isr.read(chars)) != -1){
osw.write(chars,0,len);
}
osw.close();
isr.close();
/**
* 当然也可以写jdk11的替代方案
*/
}
}
练习二
利用字节流读取文件中的数据,每次读一整行,而且不能出现乱码
1.字节流在读中文的时候会出现乱码,所以需要字符流来处理
2.字节流是没有读取一整行数据的方法的,自由字符缓冲流才能处理
/**
* @author Watching
* * @date 2023/5/23
* * Describe:
* 利用字节流读取文件中的数据,每次读一整行,而且不能出现乱码
* //1.字节流在读中文的时候会出现乱码,所以需要字符流来处理
* //2.字节流是没有读取一整行数据的方法的,自由字符缓冲流才能处理
*/
public class ConvertStreamDemo3 {
public static void main(String[] args) throws IOException {
/**
* 使用转换流包装字节输入流,获得可以指定读取字符集的方法
* 再用字符缓冲流包装转换流,获得readLine()方法
*/
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("d.txt"), "GBK"));
String s ;
while((s = br.readLine())!= null){
System.out.println(s);
}
}
}
总结
二、【反】序列化流
序列化流是字节流的一种。
序列化流
序列化流的作用是可以将java对象写到本地文件中。
需要注意的是,被序列化的java对象所属的类必须实现了Serilizable接口
测试,将一个java对象写到本地文件中:
/**
* @author Watching
* * @date 2023/5/23
* * Describe:
* 将一个java对象写到本地文件中
*/
public class ObjectStreamDemo1 {
public static void main(String[] args) throws IOException {
Student student = new Student("tom","123");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("g.txt"));
oos.writeObject(student);
}
public static class Student implements Serializable {
public Student(){}
public Student(String name,String id){
this.id = id;
this.name = name;
}
private String name;
private String id;
}
}
结果展示:
反序列化
将上面序列化的对象反序列化回来
/**
* @author Watching
* * @date 2023/5/24
* * Describe:将序列化的对象反序列化
*/
public class ObjectStreamDemo2 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("g.txt"));
Student student = (Student) ois.readObject();
System.out.println(student);
ois.close();
}
}
结果展示:
序列化和反序列化需要注意的细节
1.当反序列化的目标类被修改后,反序列化会报错
Exception in thread “main” java.io.InvalidClassException: ObjectStream.Student; local class incompatible: stream classdesc serialVersionUID = -4039190925851938632, local class serialVersionUID = -1958645621702799953
这个错误的原因是反序列化的目标类被修改了,而这个类的对象在序列化前会根据类的属性方法等计算出一个Long型的序列号,在反序列化时会重新计算一边,如果序列化号发生了改变,就会出现上面的错误。
在开发中,很难保证javabean不被修改,我们为了保证可以成功反序列化,可以在javabean中添加一个属性
private static final Long serializableUID = 1L;//序列化号尽量用随机long型
这样的话在反序列化时,就算类被修改了,也能成功反序列化
2.如果存在不想序列化的属性
可以在属性前面添加一个transient关键字,这样的话这个属性就不会被序列化到本地文件了
private transient String address;
练习
思路,通常情况下,我们不知道文件中会有几个序列化对象,所以在反序列化时并不好编写代码,所以我们默认在序列化时,将对象都放在集合中,反序列化时直接获得集合,再从集合中获取对象
序列化:
/**
* @author Watching
* * @date 2023/5/24
* * Describe:
* 一次性序列化多个对象
*/
public class ObjectStreamDemo3 {
public static void main(String[] args) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("h.txt"));
ArrayList<Student> arrayList = new ArrayList<>();
arrayList.add(new Student("tom","1"));
arrayList.add(new Student("jack","2"));
arrayList.add(new Student("marry","3"));
oos.writeObject(arrayList);
oos.close();
}
}
反序列化:
/**
* @author Watching
* * @date 2023/5/24
* * Describe:
*/
public class ObjectStreamDemo4 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("h.txt"));
ArrayList<Student> arrayList = (ArrayList<Student>) ois.readObject();
ois.close();
for (Object o : arrayList) {
System.out.println(o);
}
}
}
三、打印流
字节打印流只有输出,没有输入。
打印流的分类和特点:
字节打印流
字节打印流的构造器:
字节流底层没有缓冲数组,所以flush也就没有意义,当然写了也不报错
字节打印流的成员方法:
1.字节打印流的特有方法可以实现数据原样输出,比如97输出就是97,而不是字符集对应的字符
2.printf方法的占位符有很多种,功能也不尽相同,可以自行了解
字符打印流
字符打印流的底层有缓冲区,所以效率要比字节打印流高,所以可以开启自动刷新flush
字符打印流的构造方法:
字符打印流的成员方法:
字符打印流的成员方法与字节打印流的成员方法基本相同
总结
四、压缩流
压缩流应用场景:
1.比如在开发种,要传输的数据很大,所以需要先压缩再传输
2.获取到一个压缩后的文件,我们需要先解压,才能获取到文件内容
3.在java中,只能识别zip格式的压缩文件。
解压缩
我们来编写代码测试以下解压缩流:
首先在磁盘中有一个文件,下面是它的文件结构:
文件夹aaa包含了文件夹bbb,文件夹ccc,以及3个txt文件
文件夹bbb包含了3个txt文件
文件夹ccc包含了3个txt文件
压缩流 ZipInputStream 的 getNextEntry() 方法可以获取到压缩文件中的每一个文件,包括文件夹,返回文件是一个 ZipEntry对象 ,在读取到所有文件后将返回 null 而且在遇到文件夹时不需要我们进行递归处理,在底层它已经帮我们递归处理了,我们只需要循环遍历直到 getNextEntry() 方法返回 null。
通过下面这段代码,我们可以了解到getNextEntry()方法的遍历方式。
它获取了所有文件。
/**
* @author Watching
* * @date 2023/5/25
* * Describe:
*/
public class test3 {
public static void main(String[] args) throws IOException {
ZipInputStream zis = new ZipInputStream(new FileInputStream("aaa.zip"));
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null){
System.out.println(entry.getName());
}
}
}
ZipEntry中还有很多方法,其中的isDirectory()是我们接下来要用到的。
注意,在每使用完一个ZipEntry之后需要将其关闭。
下面是解压缩的完整代码(记得在finally中关闭流)。
1.遍历ZipEntry,判断是目录还是文件
2.目录则在目的地址创建目录,
3.文件则使用FileoutputStream拷贝输出,zis是FileInputStream的包装类,可以用于读取压缩包中的文件的内容。
/**
* @author Watching
* * @date 2023/5/24
* * Describe:解压缩,java只能识别zip格式的压缩文件
*/
public class ZipStreamDemo1 {
public static void main(String[] args) throws IOException {
//要解压的压缩文件
File src = new File("aaa.zip");
//解压到目的文件
File dest = new File("aaa");
unzip(src, dest);
}
//解压
public static void unzip(File src, File dest) throws IOException {
ZipInputStream zis = new ZipInputStream(new FileInputStream(src));
ZipEntry entry;
/**
* 如果遇到文件夹,是不需要递归操作的,因为解压缩流底层做了递归操作,我们只需要循环遍历
* 直到读到的entry为null就代表读完了所有文件。
*/
while ((entry = zis.getNextEntry()) != null) {//获取压缩文件中的每一个ZipEntry对象
if (entry.isDirectory()) {//如果是文件夹,需要在目标地址创建一个同名文件夹
new File(dest,entry.getName()).mkdirs();
zis.closeEntry();//表示压缩包里面的一个文件处理完毕了
} else {//如果是文件,则先使用压缩流读取,再使用文件输出流输出到目标地址
FileOutputStream fos = new FileOutputStream(new File(dest, entry.getName()));
byte[] bytes = new byte[1024];
int len;
while ((len = zis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
fos.close();
zis.closeEntry();//表示压缩包里面的一个文件处理完毕了
}
}
zis.close();
}
}
压缩单个文件
1.创建压缩流关联压缩包
2.创建ZipEntry对象,表示要压缩进压缩包的文件的文件名
3.使用字节输入流读取要压缩的文件
4.使用压缩流将文件写入压缩包
最后记得关流
/**
* @author Watching
* * @date 2023/5/25
* * Describe:
* 压缩单个文件
*/
public class ZipStreamDemo2 {
public static void main(String[] args) throws IOException {
//要压缩的文件的地址
File src = new File("a.txt");
//目的地址
File dest = new File("E:\\ideaFile\\ioStream\\myio");
toZip(src, dest);
}
private static void toZip(File src, File dest) throws IOException {
//1.创建压缩流关联压缩包
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File(dest, "a.zip")));
//2.创建ZipEntry对象,表示压缩包里的每一个文件和文件夹
ZipEntry zipEntry = new ZipEntry("a.txt");
//3.把zipEntry对象放进压缩包中
zos.putNextEntry(zipEntry);
//4.把src中的数据写入压缩包中
FileInputStream fis = new FileInputStream(new File("a.txt"));
int len;
byte[] bytes = new byte[1024];
while ((len = fis.read(bytes)) != -1) {
zos.write(bytes,0,len);
}
fis.close();
zos.closeEntry();
zos.close();
}
}
压缩文件夹
注意区分ZipInputStream和ZipOutputStream
/**
* @author Watching
* * @date 2023/5/25
* * Describe:压缩文件夹
* 压缩文件夹 E:\ideaFile\ioStream\myio\bbb
*/
public class ZipStreamDemo3 {
public static void main(String[] args) throws IOException {
//1.创建File对象表示要压缩的文件夹
File src = new File("E:\\ideaFile\\ioStream\\myio\\bbb");
//2.创建File对象表示生成的压缩文件放在哪里(压缩文件的父级路径
File destParent = src.getParentFile();
//3.创建File对象表示压缩包的路径
File dest = new File(destParent, src.getName() + ".zip");
//4.创建压缩流关联压缩包
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(dest));
//5.获取src下的每一个文件,变成ZipEntry对象,放入压缩包
toZip(src, zos, src.getName());
//6.释放资源
zos.close();
}
/**这个方法的难点就在于文件路径的填写
* @param src 数据源
* @param zos 压缩流
* @param name 压缩包内部的路径
* 作用:获取src下的每一个文件,变成ZipEntry对象,放入到压缩包中
*/
public static void toZip(File src, ZipOutputStream zos, String name) throws IOException {
File[] files = src.listFiles();
for (File file : files) {
if (file.isFile()) {
ZipEntry zipEntry = new ZipEntry(name + "\\" + file.getName());
zos.putNextEntry(zipEntry);
FileInputStream fis = new FileInputStream(file);
int len;
byte[] bytes = new byte[1024];
while ((len = fis.read(bytes)) != -1) {
zos.write(bytes, 0, len);
}
zos.closeEntry();
} else {
//文件夹,递归
toZip(file, zos, name + "\\" + file.getName());
}
}
}
}
五、commons-io
commons-io是apache开源组织开源的一套关于io流的工具包,其中包含了很多实用的方法
自行了解commons-io下的工具类