Apache Commons Compress 라이브러리를 활용해서  파일/디렉토리 압축 및 해제하는 예제를 작성하려고 합니다. Apache Commons Compress 라이브러리에서 지원하는 파일 작업에는 zip, tar, jar, 7z, gzip, ...

[코틀린] 파일/디렉토리 압축 및 해제 예제 (zip, unzip, tarGzip, tarGunzip)

Apache Commons Compress 라이브러리를 활용해서 

파일/디렉토리 압축 및 해제하는 예제를 작성하려고 합니다.

Apache Commons Compress 라이브러리에서 지원하는 파일 작업에는

zip, tar, jar, 7z, gzip, bzip2 등등 여러가지가 있지만...

대표적으로 뽑아서 이번 예제에서는 zip, tar, gzip을 다루려고 합니다.

 

 

 

 

 

 

 

1. Apache Commons Compress 라이브러리

시작하기 앞서 아주 아주 간략하게 압축하는 동작에 대한 개념 정리를 해봅니다.

(완벽하게 이해하지는 못했지만.. 가볍게라도 개념을 보니까 정말 도움이 됐었습니다!)

Apache Commons Compress 라이브러리에는

Archiver and Compressor (아카이버와 압축기) 라는 개념이 있습니다.

 

 - 아카이버 : 여러 파일을 하나로 묶어서 보관하기위해 사용되는 방법 (zip, tar, jar …)

 - 압축기 : 파일 압축에 사용되는 방법  (gzip, biz2, LZ4 …)

 

파일 압축하는데 아카이버랑 압축기가 사용되는구나

어떤걸 사용하냐에 따라 압축파일이 달라지겠구나

그렇구나.... 이제 본론으로 넘어갑시다.


gradle에 아래와 같이 라이브러리를 추가해 줍니다.

dependencies {
....

// Apache Commons Compress Library
implementation 'org.apache.commons:commons-compress:1.21'
}

 

 

 

 

 

 

 

2. zip

zip 파일은 window에서 가장 많이 사용되는 압축 파일입니다.

zip의 경우 자체적으로 파일/디렉토리를 패키지하고 압축한다는 특성을 가지기 떄문에

압축기 없이 아카이브만 사용하여 파일/디렉토리를 압축합니다.

fun zip(files: Array<File>, destPath: String) {
try {
val zipArchiveOutputStream = ZipArchiveOutputStream(FileOutputStream(destPath + EXTENSION_ZIP))
for (file in files) {
if (!file.exists()) {
Log.e(TAG, "[zip] file is not exists : ${file.name}")
continue
}
Log.d(TAG, "[zip] file : ${file.name}")

if (file.isDirectory) {
file.listFiles()?.let {
zipDirectory(zipArchiveOutputStream, it, "/${file.name}/")
}
} else {
val fileInputStream = FileInputStream(file)
val entry = ZipArchiveEntry(file.name).apply {
size = file.length()
}

zipArchiveOutputStream.putArchiveEntry(entry)
fileInputStream.copyTo(zipArchiveOutputStream, BUFFER_SIZE)

zipArchiveOutputStream.closeArchiveEntry()
fileInputStream.close()
}
}
zipArchiveOutputStream.finish()
} catch (e: Exception) {
e.printStackTrace()
}
}


private fun zipDirectory(zipArchiveOutputStream: ZipArchiveOutputStream, files: Array<File>, dirPath: String) {
try {
for (file in files) {
Log.d(TAG, "[zipDirectory] file : $dirPath${file.name}")

if (file.isDirectory) {
file.listFiles()?.let {
zipDirectory(zipArchiveOutputStream, it, "$dirPath${file.name}/")
}
} else {
val fileInputStream = FileInputStream(file)
val entry = ZipArchiveEntry(dirPath + file.name).apply {
size = file.length()
}

zipArchiveOutputStream.putArchiveEntry(entry)
fileInputStream.copyTo(zipArchiveOutputStream, BUFFER_SIZE)

zipArchiveOutputStream.closeArchiveEntry()
fileInputStream.close()
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}

 

 

 

 

 

 

 

3. unzip

zip 파일을 원하는 경로에 압축 해제하는 동작은 아래와 같이 작성하였습니다.

fun unzip(zipFile: File, destPath: String) {
if (!zipFile.exists() && !zipFile.name.endsWith(EXTENSION_ZIP)) {
Log.e(TAG, "[unzip] The file does not exist or is not a zip file. : ${zipFile.name}")
return
}

try {
val zipArchiveInputStream = ZipArchiveInputStream(FileInputStream(zipFile))

while (true) {
val entry = zipArchiveInputStream.nextZipEntry ?: break
if (!zipArchiveInputStream.canReadEntryData(entry)) {
Log.e(TAG, "[unzip] The entry is not readable : ${entry.name}")
continue
}
Log.d(TAG, "[unzip] entry : ${entry.name}")

if (entry.name.lastIndexOf("/") > 0) {
val file = File("$destPath/${entry.name.substring(0, entry.name.lastIndexOf("/"))}")
if (!file.exists()) file.mkdirs()
}

if (!entry.isDirectory) {
val fileOutputStream = FileOutputStream("$destPath/${entry.name}")
zipArchiveInputStream.copyTo(fileOutputStream, BUFFER_SIZE)
fileOutputStream.close()
}
}
zipArchiveInputStream.close()
} catch (e: Exception) {
e.printStackTrace()
}
}

 

 

 

 

 

 

 

4. gzip

gz 파일은 Linux 및 Mac OS 운영 체제의 표준 압축 파일입니다.

zip과 달리 gz로 압축하기 위해서는 tar라는 아카이버가 필요합니다.

동작 순서는 아래와 같습니다.


 - tar 아카이버를 통해 파일/디렉토리를 하나로 묶어준다.

 - gz 압축기를 통해 만들어진 tar 파일을 압축한다.

 

먼저 tar 아카이버를 통해 파일/디렉토리를 하나로 묶어주는 동작입니다. (zip 아카이버와 거의 비슷함)

private fun tar(files: Array<File>, destPath: String): File {
try {
val tarArchiveOutputStream = TarArchiveOutputStream(FileOutputStream(destPath + EXTENSION_TAR))

for (file in files) {
if (!file.exists()) {
Log.e(TAG, "[tar] file is not exists : ${file.name}")
continue
}
Log.d(TAG, "[tar] file : ${file.name}")

if (file.isDirectory) {
file.listFiles()?.let {
tarDirectory(tarArchiveOutputStream, it, "/${file.name}/")
}
} else {
val fileInputStream = FileInputStream(file)
val entry = TarArchiveEntry(file.name).apply {
size = file.length()
}

tarArchiveOutputStream.putArchiveEntry(entry)
fileInputStream.copyTo(tarArchiveOutputStream, BUFFER_SIZE)

tarArchiveOutputStream.closeArchiveEntry()
fileInputStream.close()
}
}
tarArchiveOutputStream.finish()
} catch (e: Exception) {
e.printStackTrace()
}
return File(destPath + EXTENSION_TAR)
}


private fun tarDirectory(tarArchiveOutputStream: TarArchiveOutputStream, files: Array<File>, dirPath: String) {
try {
for (file in files) {
Log.d(TAG, "[tarDirectory] file : $dirPath${file.name}")

if (file.isDirectory) {
file.listFiles()?.let { tarDirectory(tarArchiveOutputStream, it, "$dirPath${file.name}/") }
} else {
val fileInputStream = FileInputStream(file)
val entry = TarArchiveEntry(dirPath + file.name).apply {
size = file.length()
}

tarArchiveOutputStream.putArchiveEntry(entry)
fileInputStream.copyTo(tarArchiveOutputStream, BUFFER_SIZE)

tarArchiveOutputStream.closeArchiveEntry()
fileInputStream.close()
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}

 

 

그리고 만들어진 tar 파일을 gzip 압축기로 압축해주는 동작입니다.

private fun gzip(tarFile: File, destPath: String) {
val gzipCompressorOutputStream = GzipCompressorOutputStream(FileOutputStream(destPath + EXTENSION_TAR_GZ))
val fileInputStream = FileInputStream(tarFile)
fileInputStream.copyTo(gzipCompressorOutputStream, BUFFER_SIZE)
fileInputStream.close()
gzipCompressorOutputStream.finish()
}

 

 

tar와 gzip은 어떻게보면 짝궁이니까.. 서운하지않게 하나의 function으로 동작하도록 작성하였습니다.

fun tarGzip(files: Array<File>, destPath: String) {
val tarFile = tar(files, destPath)
if (tarFile.exists()) {
gzip(tarFile, destPath)
tarFile.delete()
}
}

 

 

 

 

 

 

 

5. tarGunzip

tar.gz 파일을 원하는 경로에 압축 해제하는 동작은 아래와 같이 작성하였습니다.

fun tarGunzip(tarGzFile: File, destPath: String) {
if (!tarGzFile.exists() && !tarGzFile.name.endsWith(EXTENSION_TAR_GZ)) {
Log.e(TAG, "[unTarGzip] The file does not exist or is not a zip file. : ${tarGzFile.name}")
return
}

try {
val gzipCompressorInputStream = GzipCompressorInputStream(FileInputStream(tarGzFile))
val tarArchiveInputStream = TarArchiveInputStream(gzipCompressorInputStream)

while (true) {
val entry = tarArchiveInputStream.nextTarEntry ?: break
if (!tarArchiveInputStream.canReadEntryData(entry)) {
Log.e(TAG, "[unTarGzip] The entry is not readable : ${entry.name}")
continue
}
Log.d(TAG, "[unTarGzip] entry : ${entry.name}")

if (entry.name.lastIndexOf("/") > 0) {
val file = File("$destPath/${entry.name.substring(0, entry.name.lastIndexOf("/"))}")
if (!file.exists()) file.mkdirs()
}

if (!entry.isDirectory) {
val fileOutputStream = FileOutputStream("$destPath/${entry.name}")
tarArchiveInputStream.copyTo(fileOutputStream, BUFFER_SIZE)
fileOutputStream.close()
}
}
tarArchiveInputStream.close()
} catch (e: Exception) {
e.printStackTrace()
}
}

 

 

 

 

 

 

 

6. 예제 다운로드

https://github.com/bictoselfdev/CompressUtilEx

 


 

 

 

 

 

 

 

[Reference]

Apache Commons Compress : User Gude

kotlin 압축과 압축해제

압축 및 아카이브 이론

Gzip vs Zip : Difference between the most popular compressing file formats

 

 

0 comments: