[JAVA] ZIP파일 다루기

eunniverse·2021년 5월 8일
0
post-custom-banner

글 쓰게된 계기

일단 공부하게된 이유는 업무때문이었다. 파일 관련해서 처리할게 있었는데 너무 모르다보니까 공부를 해야겠다 싶었다. 블로그 정리까지 하면 더 좋을 것 같아서 작성을 해보았다!

공부한 내용 정리

*) zip파일 압축해제

내가 해야할 작업은 zip파일 압축해제 후 파일 저장이었다. zip파일을 해제해서 각 파일을 확인하는 방법은 여러가지가 있었는데, 내가 사용한 건 java util calss였다.
찾아봤을 때는 총 4가지 정도에 방법이 나왔다.

1.1) Snappy-java
snappy는 고속의 압축/해제가 필요할 때 사용하며, 원래는 c/c++용 라이브러리이다.
압축률이 높지 않지만 압축 속도가 빠른 편이다.
해당 라이브러리는 maven repository에서 확인해서 사용하면 된다.
https://mvnrepository.com/artifact/org.xerial.snappy/snappy-java

1.2) Apache commons-compress
commons-compress는 한글로 압축/해제가 가능한 라이브러리이다.
처음에 이걸 사용하려 했는데, 계속 오류가 나더라.. 무슨 문제인지 확인하고 싶었는데 일단 빨리 수정해야해서 확인은 못하고 다른 걸 사용했다. (아마 나만 안된듯..)
해당 라이브러리 또한 maven에서 확인하면 된다.
https://mvnrepository.com/artifact/org.apache.commons/commons-compress
코드는 다른 블로그에서 확인해서 퍼왔다.

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
public class ApacheComp {
    public static final String currPath = System.getProperty("user.dir");//프로젝트(현재) 경로
    public static void main(String[] args) throws IOException {
        String path = currPath + File.separator+ "hello";
        File file = new File(path);
        String files[] = null;
        //파일이 디렉토리 일경우 리스트를 읽어오고
        //파일이 디렉토리가 아니면 첫번째 배열에 파일이름을 넣는다.
        if( file.isDirectory() ){
            files = file.list();
        }else{
            files = new String[1];
            files[0] = file.getName();
            System.out.println(file.getName().getBytes());
        }
        //buffer size
        int size = 1024;
        byte[] buf = new byte[size];
        String outZipNm = path+File.separator + "테스트.zip";
        FileInputStream fis = null;
        ZipArchiveOutputStream zos = null;
        BufferedInputStream bis = null;
        try {
            // Zip 파일생성
            zos = new ZipArchiveOutputStream(new BufferedOutputStream(new FileOutputStream(outZipNm))); 
            for( int i=0; i < files.length; i++ ){
                //해당 폴더안에 다른 폴더가 있다면 지나간다.
                if( new File(path+"/"+files[i]).isDirectory() ){
                    continue;
                }
                //encoding 설정
                zos.setEncoding("UTF-8");
                //buffer에 해당파일의 stream을 입력한다.
                fis = new FileInputStream(path + "/" + files[i]);
                bis = new BufferedInputStream(fis,size);
                //zip에 넣을 다음 entry 를 가져온다.
                zos.putArchiveEntry(new ZipArchiveEntry(files[i]));
                //준비된 버퍼에서 집출력스트림으로 write 한다.
                int len;
                while((len = bis.read(buf,0,size)) != -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();
            }
            if( fis != null ){
                fis.close();
            }
            if( bis != null ){
                bis.close();
            }
        }
    }
   }

1.3) zip4j
zip4j는 안드로이드에서 주로 사용되는 unzip 라이브러리이다.
찾아보니까 패스워드 설정이 되고 분할압축도 된다.
굳이 특정기능이 필요하지는 않아서 사용하지 않았지만 메서드도 간편해서 추후에 사용할 일 있으면 사용해볼만 할 것 같다.
해당 라이브러리 git 주소이다.
https://github.com/srikanth-lingala/zip4j
(확인해보니까 JDK 7이상 사용가능하고 README.md에 자세하게 나와있어서 괜찮은 듯..)

1.4) java.util
기존 java 내장 패키지를 사용하는 방법이다.
일단 빠르게 구현하는게 먼저여서 maven을 굳이 사용하지 않아도 되었던 java.util.zip을 사용하였다.

public static void unzipFile(MultipartFile file) {
   BufferedOutputStream fileOutputStream = null;
   
   try (FileInputStream fileInputStream = new FileInputStream(multipartToFile(file));
        ZipFile zipFile = new ZipFile(getFileFullPath(file.getOriginalFilename()));) {
        
       //zip 파일 압축 해제 리스트
       Enumeration<? extends ZipEntry entries = zipFile.entries();
       
       //zip 파일 리스트 목록 순환
       while (entries.hasMoreElements()) {
           ZipEntry entry = entries.nextElement();
           InputStream stream = zipFile.getInputStream(entry);
           BufferedInputStream bfStream = new BufferedInputStream(stream);
           File unzipFile = new File(file.getOriginalFileName());
           fileOutputStream = new BufferedOutputStream(new FileOutputStream(unzipFile));
           //압축 해제된 파일 읽기
           int length = 0;
           while ((length = bfStream.read()) != -1) {
               fileOutputStream.write(length);
           }
           fileOutputStream.close();
       }
       
       //inputStream close
       fileOutputStream.flush();
       fileInputStream.close();
       zipFile.close();
       
   } catch (Exception e) {
       e.printStackTrace();
   }
}

코드를 설명하자면 zipfile을 생성해서 zipfile 압축파일 목록을 순환하여 파일을 읽는 방식이다. 이중에 mutipartfile을 file로 변환하는 메서드와 파일 경로를 가져오는 메서드가 필요하다.

// mutipartfile을 file로 변환하는 메서드
public static File multipartToFile(MultipartFile multipart) {
   try {
      	File convFile = new File(getFileFullPath(multipart.getOriginalFilename()));
      	multipart.transferTo(convFile);
      	return convFile;  
   } catch (Exception e) {
   	e.printStackTrace();
   }
}
// 파일 경로를 가져오는 메서드
// savepath - 파일저장 경로
public static String getFileFullPath(String fileName) {
   return savePath + "/" + fileName;
}

일단 이런식으로 구현하니까 문제없이 작동했다.

+) bufferedstream vs in/outputstream

파일 관련 처리를 하다보니까 파일을 읽는 속도가 느린 이슈가 발생하였다. 어떻게 처리해야할지 고민을 하다가 선임님이 알려주신 방법으로 처리하니 굉장히 빨라졌다.

AS-IS

  FileInputStream fileInputStream = new FileInputStream(file);
  FileOutputStream fileOutputStream = null;
 
   (중략)

  InputStream stream = zipFile.getInputStream(entry);
  fileOutputStream = new FileOutputStream(files);

   (중략)

  while ((length = stream.read()) != -1) {
     fileOutputStream.write(length);
  }

TO-BE

 (중략)
InputStream stream = zipFile.getInputStream(entry);
BufferedInputStream bfStream = new BufferedInputStream(stream);

BufferedOutputStream fileOutputStream = null;
fileOutputStream = new BufferedOutputStream(new FileOutputStream(unzipFile));

while ((length = bfStream.read()) != -1) {
   fileOutputStream.write(length);
}

AS-IS에서는 fileinputstream/fileoutputstream을, TO-BE에서는 bufferedinputstream/bufferedoutputstream을 사용했다는 점 뿐인데, 속도의 차이는 엄청났다!
총 4가지 파일을 읽는데 AS-IS에서는 12.55초, TO-BE에서는 0.255초가 소요되었다.
왜 이렇게 차이가 날까가 궁금해서 FileInputStream vs BufferedInputStream 으로 찾아봤는데, 이유는 다음과 같이 요약할 수 있었다.
FileInputStream은 입출력할 데이터가 OS - JVM을 거쳐 1byte씩 전달되는 반면, BufferedInputStream은 버퍼(임시 저장소)를 생성해서 한번에 file을 받아서 메모리에 전달하는 방식이었다.

profile
능력이 없는 것을 두려워 말고, 끈기 없는 것을 두려워하라
post-custom-banner

0개의 댓글