ZipArchiveOutputStream, ZipArchiveEntry 클래스를 통한 파일 압축 방법
서비스 개발 과정에서 여러 개의 파일을 하나의 압축 파일 형태로 받을 수 있도록 해달라는 기능 요청이 있었는데요.
해당 포스팅에서는 'Apache Commons Compress' 라이브러리를 사용하여 파일을 압축하는 방법 외 압축 파일의 내부 파일 정보를 가져오는 등의 기능에 대한 코드 예시 및 상세 내용을 정리하였습니다.
(Commons Compress 라이브러리의 ZipArchiveOutputStream, ZipArchiveEntry 클래스 등을 주로 활용하였습니다.)
* 압축하려는 대상 파일을 가져오거나 생성하는 부분은 구현하려는 기능에 따라 다를 수 있기 때문에 동작 방식 위주로 참고해 주시면 될 것 같습니다. 또한 아래 코드는 예시를 위한 간단한 코드이기 때문에 예외 처리 및 try with resource 부분은 실제 로직 구현 시 필요에 따라 적용시켜 주시면 됩니다.
기본적인 파일 압축 코드 예시
public void fileToZip() throws Exception {
// 압축 대상 파일
File targetFile = new File("/대상파일경로/targetFile.txt");
FileInputStream fis = null;
ZipArchiveOutputStream zos = null;
BufferedInputStream bis = null;
try {
// zip 파일 생성 및 encoding 설정
zos = new ZipArchiveOutputStream(new FileOutputStream("/압축파일저장경로/resultZip.zip"));
zos.setEncoding("UTF-8");
final int bufSize = 2048;
byte[] buf = new byte[bufSize];
fis = new FileInputStream(targetFile);
bis = new BufferedInputStream(fis, bufSize);
// zip 파일에 entry 추가, 여기서 ZipArchiveEntry 객체는 ZIP 아카이브에서 하나의 파일 항목을 나타냄
// ZipArchiveEntry 생성자의 매개변수는 zip 파일에 추가될 파일명
zos.putArchiveEntry(new ZipArchiveEntry(targetFile.getName()));
// bufferedInputStream 를 읽어 zipArchiveOutputStream 에 쓰기
int len;
while((len = bis.read(buf, 0, bufSize)) != -1) {
zos.write(buf, 0, len);
}
bis.close();
fis.close();
// 현재 ZipArchiveEntry에 대한 처리가 완료되었음을 나타내고,
// putArchiveEntry() 메서드를 호출한 후, 해당 파일 항목에 대한 데이터를 모두 기록하고 closeArchiveEntry() 메서드를 호출해야함
zos.closeArchiveEntry();
zos.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (zos != null) { zos.close(); }
if (fis != null) { fis.close(); }
if (bis != null) { bis.close(); }
}
}
파일 압축의 동작 중 가장 핵심이 되는 부분은 아래 과정입니다.
1. ZipArchiveOutputStream 인스턴스(압축 결과로 나올 압축 파일)를 생성합니다.
2. putArchiveEntry() 메서드를 통해 압축 파일에 추가할 파일(ZipArchiveEntry)을 명시합니다.
3. write() 메서드를 통해 ZipArchiveOutputStream 인스턴스에 추가한 파일 항목에 대한 데이터를 씁니다.
4. closeArchiveEntry() 메서드를 호출하여 추가한 파일에 대한 데이터 처리를 완료합니다.
5. 대상 파일이 여러 개인 경우 2-4 과정을 반복합니다.
ZipArchiveOutputStream zos =
new ZipArchiveOutputStream(new BufferedOutputStream(outputStream));
그리고 commons-compress 라이브러리를 사용한 파일 압축 예시들을 찾아보던 중, 위 코드와 같이 OutputStream 인스턴스를 가지고 중간에 BufferedOutputStream를 한번 생성하여 최종적으로 ZipArchiveOutputStream 인스턴스를 만드는 코드를 보았는데요.
일반적으로는 파일 입출력 시 InputStream/OutputStream을 쓰는 것보다 버퍼(임시 저장소)를 활용한 BufferedInputStream/BufferedOutputStream을 사용하는 것이 더 빠릅니다.
하지만 ZipArchiveOutputStream의 경우 내부적으로 버퍼링을 통해 압축 파일을 생성하기 때문에 BufferedOutputStream을 감싼다고 하여 성능이 더 좋아지지는 않으며, 때문에 굳이 BufferedOutputStream으로 한번 더 랩핑 할 필요는 없는 것 같습니다.
(해당 부분은 테스트를 통해 직접 확인하였습니다.)
// 압축 방법 및 압축 레벨을 설정
zos.setMethod(ZipEntry.DEFLATED);
zos.setLevel(Deflater.BEST_COMPRESSION);
추가적으로 필요한 경우 setMethod(), setLevel() 메서드를 통해 압축 방법 및 압축 레벨을 설정할 수 있습니다.
여러 개의 파일을 압축하는 코드 예시
public void filesToZip() throws Exception {
// 압축 대상 디렉터리
File targetDir = new File("/대상디렉터리경로/target");
File file = null;
String[] files = null;
if (!targetDir.isDirectory()) {
// 디렉터리가 아닌 경우 처리
return;
} else {
files = targetDir.list();
}
ZipArchiveOutputStream zos = null;
FileInputStream fis = null;
BufferedInputStream bis = null;
try {
// zip 파일 생성 및 encoding 설정
zos = new ZipArchiveOutputStream(new FileOutputStream("/압축파일저장경로/resultZip.zip"));
zos.setEncoding("UTF-8");
final int bufSize = 2048;
byte[] buf = new byte[bufSize];
for (int i = 0; i < files.length; i++) {
file = new File(targetDir + "/" + files[i]);
if (!file.isDirectory()) {
fis = new FileInputStream(file);
bis = new BufferedInputStream(fis, bufSize);
// zip 파일에 entry 추가, ZipArchiveEntry 생성자의 매개변수는 zip 파일에 추가될 파일명
ZipArchiveEntry entry = new ZipArchiveEntry(file.getName());
zos.putArchiveEntry(entry);
int len;
while((len = bis.read(buf, 0, bufSize)) != -1) {
zos.write(buf, 0, len);
}
bis.close();
fis.close();
zos.closeArchiveEntry();
}
}
zos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (zos != null) {
zos.close();
}
}
}
다음은 여러 개의 파일을 압축하는 코드 예시인데요.
위의 기본 파일 압축 예시에서 반복문을 통해 여러 개의 파일을 처리한다는 것 외에 큰 차이점은 없습니다.
여러 개의 파일을 압축하여 클라이언트로 바로 내리는 코드 예시
@GetMapping("/filesToZipAndDownload")
public void filesToZipAndDownload(HttpServletResponse response) throws Exception {
// 클라이언트로 파일을 내리는 경우, 파일명을 UTF-8로 인코딩 해주어야 한글이 깨지는 문제가 발생하지 않습니다.
String zipFileName = URLEncoder.encode("결과zip파일.zip", StandardCharsets.UTF_8);
response.setContentType("application/octet-stream; charset=UTF-8");
response.setHeader("Content-Disposition", "attachment; filename=" + zipFileName + ";");
OutputStream outputStream = response.getOutputStream();
// 압축 대상 디렉터리
File targetDir = new File("/대상디렉터리경로/target");
File file = null;
String[] files = null;
if (!targetDir.isDirectory()) {
// 디렉터리가 아닌 경우 처리
return;
} else {
files = targetDir.list();
}
FileInputStream fis = null;
ZipArchiveOutputStream zos = null;
ByteArrayOutputStream baos = null;
try {
// zip 파일 생성 및 encoding 설정
zos = new ZipArchiveOutputStream(outputStream);
zos.setEncoding("UTF-8");
final int bufSize = 2048;
byte[] buf = new byte[bufSize];
for (int i = 0; i < files.length; i++) {
file = new File(targetDir + "/" + files[i]);
if (!file.isDirectory()) {
fis = new FileInputStream(file);
baos = new ByteArrayOutputStream();
int len;
while((len = fis.read(buf, 0, bufSize)) != -1) {
baos.write(buf, 0, len);
}
// zip 파일에 entry 추가, ZipArchiveEntry 생성자의 매개변수는 zip 파일에 추가될 파일명
ZipArchiveEntry entry = new ZipArchiveEntry(file.getName());
zos.putArchiveEntry(entry);
zos.write(baos.toByteArray());
baos.close();
fis.close();
zos.closeArchiveEntry();
}
}
zos.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (zos != null) { zos.close(); }
if (fis != null) { fis.close(); }
if (baos != null) { baos.close(); }
}
}
해당 코드는 여러 개의 파일을 압축하여 클라이언트로 바로 다운로드하도록 하는 코드 예시입니다.
클라이언트로 바로 내리기 때문에 HttpServletResponse 객체가 사용되며, 해당 객체에 대한 설정 부분이 포함되어 있습니다.
압축 파일명에 한글이 포함된 경우 파일명에 대해 'UTF-8' 인코딩을 해주어야 한글이 깨지지 않습니다.
압축 파일의 내부 파일 정보 확인
public void printZipFileList() throws Exception {
ZipArchiveEntry entry = null;
FileInputStream fis = null;
ZipArchiveInputStream zis = null;
try {
fis = new FileInputStream("/대상압축파일경로/resultZip.zip");
zis = new ZipArchiveInputStream(fis, "UTF-8", true);
// getNextZipEntity method deprecated
while((entry = zis.getNextEntry()) != null) {
log.info("파일명: " + entry.getName());
log.info("최종수정일: " + entry.getLastModifiedDate());
}
zis.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (zis != null) { zis.close(); }
}
}
// INFO com.example.demo.CompressTest -- 파일명: ziptest1.txt
// INFO com.example.demo.CompressTest -- 최종수정일: Wed Nov 13 21:36:04 KST 2024
// INFO com.example.demo.CompressTest -- 파일명: ziptest2.txt
// INFO com.example.demo.CompressTest -- 최종수정일: Wed Nov 13 21:36:04 KST 2024
마지막으로 압축 파일의 내부 파일 정보를 확인하는 코드 예시입니다.
ZipArchiveEntry 인스턴스에 대해서는 예시 코드의 파일명, 최종 수정일 외에도 파일 크기, 압축 방법, 디렉터리 여부 등을 확인할 수 있습니다.
< 참고 자료 >
'Programming > Java' 카테고리의 다른 글
윈도우 서버에서 jar 파일 백그라운드 실행하기 (0) | 2024.03.09 |
---|---|
Java 운영체제(윈도우, 리눅스) 프로세스 상태 확인하는 방법 (0) | 2024.02.11 |
SOLID 객체 지향 프로그래밍의 5가지 원칙 (0) | 2023.08.14 |
java @Builder 기능 더 활용하기(toBuilder, @Singular 등) (0) | 2023.08.05 |
(java) transient volatile 키워드는 무엇인가 (1) | 2023.08.02 |