Java - ZIP 압축, 압축 해제 (zip, unzip)

By JS | Last updated: October 13, 2021

Java에서 Zip 파일을 압축하거나 압축 해제하는 방법을 소개합니다.

  1. 파일 압축
  2. 디렉토리 압축
  3. 파일, 디렉토리 압축 (zip4j 라이브러리)
  4. 압축 해제
  5. 압축 해제 (zip4j 라이브러리)

1. 파일 압축

다음은 폴더가 아닌, 파일들을 압축하는 예제입니다.

  • Zip 압축을 할 때는 ZipOutputStream를 사용하여 파일을 저장합니다.
  • 다수의 파일을 압축할 때, 파일을 구분하기 위해 ZipEntry 객체를 만듭니다.
  • ZipEntry.putNextEntry() 호출 후에 파일을 write하며, 모두 write하면 ZipEntry.closeEntry()를 호출합니다.
  • srcPaths 배열에 입력된 파일을 압축하기 위해 반복문으로 다수의 ZipEntry를 write하도록 구현하였습니다.
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class ZipExample {

    public static void main(String[] args) throws IOException {
        String[] srcPaths = {
                "/var/tmp/sample.txt",
                "/var/tmp/sample1.txt",
                "/var/tmp/sample2.txt"
        };

        zipFiles(srcPaths, "my_files.zip");
    }

    public static void zipFiles(String[] srcPaths, String zipFileName) {
            try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFileName))) {

                for (String srcPath : srcPaths) {

                    Path src = Paths.get(srcPath);
                    try (FileInputStream fis = new FileInputStream(src.toFile())) {

                        ZipEntry zipEntry = new ZipEntry(src.getFileName().toString());
                        zos.putNextEntry(zipEntry);

                        byte[] buffer = new byte[1024];
                        int len;
                        while ((len = fis.read(buffer)) > 0) {
                            zos.write(buffer, 0, len);
                        }
                        zos.closeEntry();
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
    }
}

위 프로그램 실행 시, 프로젝트의 Root 디렉토리를 보면 Zip 파일이 생성되어있습니다.

$ ls -al my_files.zip
-rw-rw-r-- 1 mjs mjs 3993 10 13 21:22 my_files.zip

2. 디렉토리 압축

이번에는 폴더와 그 하위 파일들을 모두 압축하려고 합니다.

예제에서 아래의 디렉토리를 압축할 것입니다.

/var/tmp/uml$ tree
.
├── notify_app_target_event
│   ├── sequence
│   │   └── sequence.png
│   └── sequence.uml
└── request_prediction_update
    ├── sequence
    │   └── sequence.png
    └── sequence.uml

4 directories, 4 files

아래 예제는 다음 예제는 Files.walkFileTree()으로 디렉토리의 하위 폴더, 파일들을 모두 순회하면서 압축합니다. 파일들이 많기 때문에 각각의 파일은 ZipEntry로 구분해야합니다.

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class ZipExample2 {

    public static void main(String[] args) {
        Path srcDir = Paths.get("/var/tmp/uml");

        zipFiles(srcDir, "uml.zip");
    }

    public static void zipFiles(Path srcDir, String zipFileName) {
        try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFileName))) {

            Files.walkFileTree(srcDir, new SimpleFileVisitor<>() {
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attributes) {
                    // only copy files, no symbolic links
                    if (attributes.isSymbolicLink()) {
                        return FileVisitResult.CONTINUE;
                    }

                    try (FileInputStream fis = new FileInputStream(file.toFile())) {

                        Path targetFile = srcDir.relativize(file);
                        zos.putNextEntry(new ZipEntry(targetFile.toString()));

                        byte[] buffer = new byte[1024];
                        int len;
                        while ((len = fis.read(buffer)) > 0) {
                            zos.write(buffer, 0, len);
                        }
                        zos.closeEntry();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    return FileVisitResult.CONTINUE;
                }

                @Override
                public FileVisitResult visitFileFailed(Path file, IOException exc) {
                    System.err.printf("Unable to zip : %s%n%s%n", file, exc);
                    return FileVisitResult.CONTINUE;
                }
            });

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

위의 코드를 실행하면, 프로젝트 Root에 zip 파일이 생성됩니다.

$ ls -al uml.zip
-rw-rw-r-- 1 mjs mjs 41436 1013 22:18 uml.zip

3. 파일, 디렉토리 압축 (zip4j)

zip4j 라이브러리를 이용하면 간단히 Zip 파일로 압축할 수 있습니다.

zip4j 라이브러리는 Gradle 프로젝트에서 build.gradle에 다음과 같이 의존성을 추가하시면 됩니다.

dependencies {
    compile group: 'net.lingala.zip4j', name: 'zip4j', version: '2.6.1'
    ...
}

3.1 파일 1개 압축

다음과 같이 압축할 source 파일 경로 및 Target 파일 경로를 입력하시면 됩니다.

import net.lingala.zip4j.ZipFile;
import net.lingala.zip4j.exception.ZipException;

public class ZipExample3 {

    public static void main(String[] args) throws ZipException {

        ZipFile zipFile = new ZipFile("filename.zip");
        zipFile.addFile("file.txt");
    }
}

3.2 다수 파일 압축

다수 파일의 경로는 리스트로 만들어 인자로 전달합니다.

import net.lingala.zip4j.ZipFile;
import net.lingala.zip4j.exception.ZipException;

import java.io.File;
import java.util.Arrays;
import java.util.List;

public class ZipExample3 {

    public static void main(String[] args) throws ZipException {

        List<File> files = Arrays.asList(
                new File("file1.txt"), new File("file2.txt"));
        ZipFile zipFile = new ZipFile("filename.zip");
        zipFile.addFiles(files);
    }
}

3.3 디렉토리 압축

디렉토리 경로를 addFolder() 인자로 전달하여 압축합니다.

import net.lingala.zip4j.ZipFile;
import net.lingala.zip4j.exception.ZipException;

import java.io.File;

public class ZipExample3 {

    public static void main(String[] args) throws ZipException {

        ZipFile zipFile = new ZipFile("filename.zip");
        zipFile.addFolder(new File("/home/mkyong/folder"));
    }
}

4. 압축 해제

다음은 Zip 파일을 압축 해제하는 예제입니다.

  • 압축 해제는 ZipInputStream를 이용합니다.
  • 반복문을 이용하여 ZipEntry 형태로 내부 파일들을 순회합니다.
  • ZipEntry의 파일의 경로를 계산하고, 동일한 상대 경로에 파일을 복사합니다.
  • Files.copy()로 Zip 파일을 복사합니다.
  • zipSlipProtect()에는 Zip의 보안 취약점을 체크하는 코드들이 있습니다.
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public class UnzipExample {

    public static void main(String[] args) {
        Path source = Paths.get("/var/tmp/uml.zip");
        Path target = Paths.get("/var/tmp/");

        unzipFile(source, target);
    }

    public static void unzipFile(Path sourceZip, Path targetDir) {

        try (ZipInputStream zis = new ZipInputStream(new FileInputStream(sourceZip.toFile()))) {

            // list files in zip
            ZipEntry zipEntry = zis.getNextEntry();
            while (zipEntry != null) {

                boolean isDirectory = false;
                if (zipEntry.getName().endsWith(File.separator)) {
                    isDirectory = true;
                }

                Path newPath = zipSlipProtect(zipEntry, targetDir);
                if (isDirectory) {
                    Files.createDirectories(newPath);
                } else {
                    if (newPath.getParent() != null) {
                        if (Files.notExists(newPath.getParent())) {
                            Files.createDirectories(newPath.getParent());
                        }
                    }
                    // copy files
                    Files.copy(zis, newPath, StandardCopyOption.REPLACE_EXISTING);
                }

                zipEntry = zis.getNextEntry();
            }
            zis.closeEntry();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static Path zipSlipProtect(ZipEntry zipEntry, Path targetDir)
            throws IOException {

        // test zip slip vulnerability
        Path targetDirResolved = targetDir.resolve(zipEntry.getName());

        // make sure normalized file still has targetDir as its prefix
        // else throws exception
        Path normalizePath = targetDirResolved.normalize();
        if (!normalizePath.startsWith(targetDir)) {
            throw new IOException("Bad zip entry: " + zipEntry.getName());
        }
        return normalizePath;
    }

}

Output:

/var/tmp/uml$ tree
.
├── notify_app_target_event
│   ├── sequence
│   │   └── sequence.png
│   └── sequence.uml
└── request_prediction_update
├── sequence
│   └── sequence.png
└── sequence.uml

5. 압축 해제 (zip4j)

zip4j 라이브러리를 이용하면 쉽게 압축을 해제할 수 있습니다. 아래와 같이 source와 target을 인자로 전달하면 됩니다.

import net.lingala.zip4j.ZipFile;
import net.lingala.zip4j.exception.ZipException;

import java.io.File;

public class UnzipExample2 {

    public static void main(String[] args) throws ZipException {
        File source = new File("/var/tmp/uml.zip");
        String target = "/var/tmp/";

        ZipFile zipFile = new ZipFile(source);
        zipFile.extractAll(target);
    }
}

References

Related Posts

댓글을 보거나 쓰려면 이 버튼을 눌러주세요.
codechachaCopyright ©2019 codechacha