【Java I/O 流】4 - 9 压缩与解压缩

§4-9 压缩与解压缩

4-9.1 字节流的压缩与解压缩流

压缩流与解压缩流是字节流的高级流:

image

压缩流和解压缩流都只能操作 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);   //递归调用
        }
    }
}
posted @ 2023-08-21 21:40  Zebt  阅读(238)  评论(0)    收藏  举报