在开发中它们都是有自己的应用场景的。
例如我们现在要传输的数据比较大,这个时候就可以先压缩,再传输。

既然有压缩,那肯定会有解压。当接收到一个压缩包后,压缩包肯定是不能直接使用的。
我们需要先进行解压,得到里面的每一个文件,这样才能用里面的数据。

因此我们要学习的就是压缩和解压。
来看下它们在 IO流体系
中的位置。
解压缩流
主要就是读取压缩包里面的文件,所以它是读,属于 输入流
。
而右边的 压缩流
,是将文件中的数据写到压缩包中,所以它是写,属于 输出流

我们先来学习相对比较简单的 解压缩流
。
解压缩流
如果想要解压,在电脑中首先要有一个压缩包,这个压缩包需要是 zip
作为后缀的,因为Java中只能识别这个格式的。
压缩包里面的每一个文件
在Java中都是一个 ZipEntry对象
。
因此解压的本质:把每一个 ZipEntry
按照层级拷贝到本地另一个文件夹中。
解压缩流的名字叫做 ZipInputStream
,Zip
是它的作用,表示跟压缩包是有关系的;InputStream
表示它是用来读取的。
public static void main(String[] args) throws IOException {
//1.创建一个File表示要解压的压缩包
File src = new File("D:\\aaa.zip");
//2.创建一个File表示解压的目的地
File dest = new File("D:\\");
//调用方法
unzip(src, dest);
}
//定义一个方法用来解压
public static void unzip(File src, File dest) throws IOException {
ZipInputStream zip = new ZipInputStream(new FileInputStream(src));
ZipEntry entry = zip.getNextEntry();
System.out.println(entry);
}
如果多调用几次 getNextEntry()
会怎么样
ZipInputStream zip = new ZipInputStream(new FileInputStream(src));
ZipEntry entry1 = zip.getNextEntry();
System.out.println(entry1);
ZipEntry entry2 = zip.getNextEntry();
System.out.println(entry2);
ZipEntry entry3 = zip.getNextEntry();
System.out.println(entry3);
运行结果如下,aaa
有了,还有 aaa
里面的 aa.txt
,以及 bbbb文件夹

第一次打印出的 ZipEntry
是 aaa/

这个 aaa
并不是 aaa.zip
本身,而是它里面的文件夹。

那么思考:它能一直获取吗?可以将子文件中的东西也获取到吗?
如果获取不到,它会返回什么呢?null
还是 -1
?
没关系,我们都可以来试一下,因此这些知识点我们都可以通过不断的尝试进行总结
我也不知道这个文件夹中一共有多少 ZipEntry对象
,但我知道绝对没有100个,因此这里直接循环100次来看看。
for (int i = 0; i < 100; i++) {
ZipEntry entry1 = zip.getNextEntry();
System.out.println(entry1);
}
可以发现,当我们将压缩包里面的所有文件和文件夹都获取完毕了,这个时候再强行获取,就会返回 null
,因此循环的结束条件我们已经知道了。
并且,子文件夹中的文件和文件夹也都可以获取到。

因此有了这个特性后,就不需要自己递归了,直接写一个循环就行了。
getNextEntry()
的底层,它会把压缩包里的每一个文件,或者文件夹全都获取到。
public static void main(String[] args) throws IOException {
//1.创建一个File表示要解压的压缩包
File src = new File("D:\\aaa.zip");
//2.创建一个File表示解压的目的地
File dest = new File("D:\\");
//调用方法
unzip(src,dest);
}
//定义一个方法用来解压
public static void unzip(File src,File dest) throws IOException {
//解压的本质:把压缩包里面的每一个文件或者文件夹读取出来,按照层级拷贝到目的地当中
//创建一个解压缩流用来读取压缩包中的数据,解压缩流中关联基本流,再去关联压缩包的路径
ZipInputStream zip = new ZipInputStream(new FileInputStream(src));
//之前我们说过,`压缩包里面的每一个文件` 在Java中都是一个 `ZipEntry对象`。
//因此在这我们需要先获取到压缩包里面的每一个zipentry对象,调用getNextExtry()
//表示当前在压缩包中获取到的文件或者文件夹
ZipEntry entry;
while((entry = zip.getNextEntry()) != null){
System.out.println(entry);
if(entry.isDirectory()){ // entry没有isFile(),但是有isDirectory(),用来判断这个是不是一个文件夹
//文件夹:需要在目的地dest处创建一个同样的文件夹
File file = new File(dest,entry.toString());
file.mkdirs();
} else {
//文件:需要读取到压缩包中的文件,并把他存放到目的地dest文件夹中(按照层级目录进行存放)
//entry.toString():将它变成字符串,即文件夹名
FileOutputStream fos = new FileOutputStream(new File(dest, entry.toString()));
int b;
//读取的时候是使用zip进行读取
while((b = zip.read()) != -1){
//写到目的地
fos.write(b);
}
fos.close();
//表示在压缩包中的一个文件处理完毕了,需要调用closeEntry()
zip.closeEntry();
}
}
zip.close();
}
压缩流(压缩单个文件)
压缩:将多个文件 / 文件夹变成一个压缩包。
在压缩的时候需要注意:压缩包里面的每一个文件或文件夹都是一个 ZipEntry对象
。
压缩本质:把每一个(文件/文件夹)看成 ZipEntry对象
放到压缩包中。
需求1:把D:\\a.txt打包成一个压缩包
public static void main(String[] args) throws IOException {
//1.创建File对象表示要压缩的文件
File src = new File("D:\\a.txt");
//2.创建File对象表示压缩包的位置
File dest = new File("D:\\");
//3.调用方法用来压缩
toZip(src, dest);
}
/*
* 作用:压缩
* 参数一:表示要压缩的文件
* 参数二:表示压缩包的位置
* */
public static void toZip(File src, File dest) throws IOException {
//1.创建压缩流关联压缩包,这个压缩包名字一定要手动加上,压缩流中关联基本流,再去关联压缩包的路径
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File(dest, "a.zip")));
//2.创建ZipEntry对象,表示压缩包里面的每一个文件和文件夹
//参数:压缩包里面的路径
ZipEntry entry = new ZipEntry("a.txt");
//3.把ZipEntry对象放到压缩包当中
zos.putNextEntry(entry);
//创建ZipOutputStream的时候肯定会在D盘下新建a.zip的压缩包,但是压缩包里还是空的,所以下面创建了ZipEntry对象,这个ZipEntry就表示压缩包里的a.txt,但是这个文件中还没有数据!我们还需要将原始文件中的数据再写到ZipEntry中才行。
//4.把src文件中的数据写到压缩包当中
FileInputStream fis = new FileInputStream(src); // 从src中读取数据
int b; // 表示读到的数据
while ((b = fis.read()) != -1) {
// 将读取到的数据写到压缩包中
zos.write(b);
}
//closeEntry()表示当前的文件已经书写完毕
zos.closeEntry();
zos.close();
}
压缩流(压缩文件夹)
由于上一个代码,我们只需要压缩一个文件,因此写 a.txt
是没有问题的,但是如果我们写 aaa\\a.txt
会出现什么情况呢?
ZipEntry entry = new ZipEntry("aaa\\a.txt");
可以发现它是先有了一个 aaa文件夹
,然后在 aaa文件夹
里面才有了 a.txt

我们再来加一层 bbb
ZipEntry entry = new ZipEntry("aaa\\bbb\\a.txt");

由此可知,ZipEntry
里面的参数其实也表示压缩包里面的路径,通过这个特点,我们就可以去压缩包中创建不同层级的子文件夹了。
有的同学写路径的时候会像下面这种写法一样
//1.创建File对象表示要压缩的文件夹
File src = new File("D:\\aaa");
//2.创建File对象表示压缩包放在哪里(压缩包的父级路径)
File destParent = new File("D:\\aaa.zip");
但是如果真的这么写,代码会非常的不方便。
例如以后我不想压缩 aaa文件夹
了,而是想压缩 bbb文件夹
,那么下面的压缩包名字还需要跟着改,太麻烦了。
我的需求是:如果我上面改了,下面也能跟着一起变动。
public static void main(String[] args) throws IOException {
//1.创建File对象表示要压缩的文件夹
File src = new File("D:\\aaa");
//2.创建File对象表示压缩包放在哪里(压缩包的父级路径)
//getParentFile():获取父级路径
File destParent = src.getParentFile();//D:\\
//3.创建File对象表示压缩包的路径
File dest = new File(destParent, src.getName() + ".zip");
//4.创建压缩流关联压缩包,解压缩流中关联基本流,再去关键压缩包的路径
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(dest));
//5.获取src里面的每一个文件,变成ZipEntry对象,放入到压缩包当中
//由于src是一个文件夹,如果你想要获取到里面的每一个文件,需要递归
toZip(src, zos, src.getName());//aaa
//6.释放资源
zos.close();
}
/*
* 作用:获取src里面的每一个文件,变成ZipEntry对象,放入到压缩包当中
* 参数一:数据源
* 参数二:压缩流,这个压缩流已经关联好了压缩包
* 参数三:压缩包内部的路径
* */
public static void toZip(File src, ZipOutputStream zos, String name) throws IOException {
//1.进入src文件夹
File[] files = src.listFiles();
//2.遍历数组
//file依次表示src里面的文件或者文件夹
for (File file : files) {
if (file.isFile()) {
//3.判断-文件,变成ZipEntry对象,放入到压缩包当中
ZipEntry entry = new ZipEntry(name + "\\" + file.getName());//aaa\\no1\\a.txt
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());
}
}
}