【Java I/O 流】4 - 9 压缩与解压缩
§4-9 压缩与解压缩
4-9.1 字节流的压缩与解压缩流
压缩流与解压缩流是字节流的高级流:
压缩流和解压缩流都只能操作 zip
格式的压缩包,用于压缩和解压缩数据。
4-9.2 解压缩流
解压缩流 ZipInputStream
实际上位于 java.util.zip
,但其父类 java.util.zip.InflaterInputStream
继承了 java.io.FilterInputStream
,属于字节流的高级流。该类实现了读取 ZIP 文件格式中的文件。
构造方法:
构造方法 | 描述 |
---|---|
ZipInputStream(InputStream in) |
创建一条关联指定输入流的解压缩流 |
ZipInputStream(InputStream in, Charset charset) |
使用指定字符集,创建一条关联指定输入流的解压缩流 |
-
解压缩流属于高级流,在底层依赖于基本流,其构造方法都会要求传入一个基本流对象;
为了效率,一般考虑在参数中包装基本流(
new
); -
关闭流释放资源时,只需要关闭高级流即可,方法内部会自行关闭所依赖的基本流;
读取方式:
方法 | 描述 |
---|---|
int read(byte[] b, int off, int len) |
将当前的 ZIP 条目写入到一个字节数组中 |
ZipEntry getNextEntry() |
读取下一个 ZIP 文件条目,并将流定位到该条目数据的起始处 |
void closeEntry() |
关闭当前的 ZIP 条目,并定位流,准备读取下一条目 |
ZipEntry
是一个位于java.util.zip
的类,用于表示压缩文件中的每一个 ”条目“,即目录和文件;- 调用
getNextEntry
方法可获取压缩包中下一个条目(深度优先),若无更多数据,则返回null
;
ZipEntry
中的成员方法:
方法 | 描述 |
---|---|
boolean isDirectory() |
测试当前条目是否为目录 |
String getComment() |
返回该条目的批注 |
String getName() |
返回该条目名字 |
long getSize() |
返回该条目未压缩时的大小 |
long getCompressedSize() |
返回该条目压缩后大小 |
long getTime() |
返回条目最后一次编辑时间(毫秒) |
long getTimeLocal() |
返回条目最后一次编辑时间(LocalDateTime ) |
FileTime getCreationTime() |
返回条目创建时间 |
FileTime getLastAccessTime() |
返回条目最后一次访问时间 |
FileTime getLastModifiedTime() |
返回条目最后一次编辑时间 |
FileTime
是一个位于java.nio.file.attribute
的类,表示一个文件时间戳属性的值,可用于表示文件的创建、访问、修改时间;FileTime
的成员方法允许将所表示的文件时间转换为Instant
、毫秒值、指定时间单位(枚举类TimeUnit
)的时间;
4-9.3 压缩流
压缩流 ZipOutputStream
实际上位于 java.util.zip
,但其父类 java.util.zip.DeflaterOutputStream
继承了 java.io.FilterOutputStream
,属于字节流的高级流。该类实现了将文件写出为 ZIP 文件格式。
构造方法:
构造方法 | 描述 |
---|---|
ZipOutputStream(OutputStream out) |
创建一条压缩流 |
ZipOutputStream(OutputStream out, Charset charset) |
使用指定字符集,创建一条压缩流 |
-
解压缩流属于高级流,在底层依赖于基本流,其构造方法都会要求传入一个基本流对象;
为了效率,一般考虑在参数中包装基本流(
new
); -
关闭流释放资源时,只需要关闭高级流即可,方法内部会自行关闭所依赖的基本流;
写出方式:
方法 | 描述 |
---|---|
void write(byte[] b, int off, int len) |
将字节数据写出到当前的 ZIP 条目中 |
void putNextEntry(ZipEntry e) |
开始写出一个新的 ZIP 条目,并将流定位到该条目的起始处 |
void setComment(String comment) |
设置 ZIP 文件的批注 |
void closeEntry() |
关闭当前的 ZIP 条目,并定位流,准备写出新的条目 |
- 压缩的本质是将压缩的文件和目录当作一个个的
ZipEntry
对象后,将对象写出;
ZipEntry
的构造方法:
构造方法 | 描述 |
---|---|
ZipEntry(String name) |
使用指定名称创建一个 ZIP 条目 |
ZipEntry(ZipEntry e) |
使用指定条目的字段,创建一个 ZIP 条目 |
4-9.4 案例演示
需求一:解压缩文件
public static void unzip(File src, File dest) throws IOException {
Objects.requireNonNull(src);
Objects.requireNonNull(dest);
if (!src.exists()) {
throw new FileNotFoundException(src.getName() + " could not be found");
} else if (!src.getName().endsWith(".zipDir")) {
throw new IOException("Unsupported file format");
}
if (dest.isFile()) {
throw new IOException("Destination should be a directory");
}
dest.mkdirs();
ZipInputStream zis = new ZipInputStream(new FileInputStream(src));
ZipEntry entry;
//方法会采用深度优先搜索,遍历压缩包
while ((entry = zis.getNextEntry()) != null) {
if (entry.isDirectory()) {
//目录
File dir = new File(dest, entry.getName());
dir.mkdirs();
} 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.close();
}
需求二:压缩单个文件
public static void zipFile(File src, File dest) throws IOException {
Objects.requireNonNull(src);
Objects.requireNonNull(dest);
if (!src.exists()) {
throw new FileNotFoundException(src.getName() + " could not be found");
} else if (src.isDirectory()) {
throw new IOException("Does not support directory");
}
if (dest.isFile()) {
throw new IOException("Destination should be a directory");
}
dest.mkdirs();
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File(dest, "file.zipDir")));
ZipEntry entry = new ZipEntry(src.getName()); //创建新条目
zos.putNextEntry(entry); //将条目添加到压缩流中,并让流指向该条目起始处,准备写出
FileInputStream fis = new FileInputStream(src); //读取文件内容
byte[] bytes = new byte[1024];
int len;
while ((len = fis.read(bytes)) != -1) {
zos.write(bytes, 0, len); //将读取内容压缩后写出
}
fis.close();
zos.close();
}
需求三:压缩整个文件夹
public static void zipDir(File src, File dest) throws IOException {
Objects.requireNonNull(src);
Objects.requireNonNull(dest);
if (!src.exists()) {
throw new FileNotFoundException(src.getName() + " could not be found");
} else if (src.isFile()) {
throw new IOException("Does not support file");
}
if (dest.isFile()) {
throw new IOException("Cannot place zipDir file into a file(" + dest.getName() + ")");
}
dest.mkdirs();
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File(dest, src.getName() + ".zip")));
//遍历并压缩
traverseZip(src, src.getName(), zos);
zos.close();
}
private static void traverseZip(File src, String pathName, ZipOutputStream zos) throws IOException {
//进入源文件夹,已经提前保证存在性、为目录
File[] files = src.listFiles();
//拼凑包内每一个文件的父级路径
if (!pathName.isBlank() && !pathName.endsWith("\\\\")) {
pathName = pathName.concat("\\");
}
for (File file : files) {
//遍历文件夹
if (file.isFile()) {
//是文件
ZipEntry fileEntry = new ZipEntry(pathName + file.getName()); //注意:表示的是压缩包内路径的名字
zos.putNextEntry(fileEntry); //创建的永远都是文件,若要创建目录,应当拼接字符串(父级路径)
FileInputStream fis = new FileInputStream(file);
byte[] bytes = new byte[1024];
int len;
while ((len = fis.read(bytes)) != -1) {
zos.write(bytes, 0, len);
}
fis.close();
zos.closeEntry(); //关闭当前条目,准备写出下一个条目
} else {
//是目录
String path = pathName + file.getName(); //用第三方变量,切忌直接更改原变量,防止路径出错
traverseZip(file, path, zos); //递归调用
}
}
}