在 Kotlin 中创建 ZIP 文件

问题描述 投票:0回答:8

我正在尝试在 Kotlin 中创建一个 zip 文件。 这是代码:

fun main(args: Array<String>) {
var files: Array<String> = arrayOf("/home/matte/theres_no_place.png", "/home/matte/vladstudio_the_moon_and_the_ocean_1920x1440_signed.jpg")
var out = ZipOutputStream(BufferedOutputStream(FileOutputStream("/home/matte/Desktop/test.zip")))
var data = ByteArray(1024)
for (file in files) {
    var fi = FileInputStream(file)
    var origin = BufferedInputStream(fi)
    var entry = ZipEntry(file.substring(file.lastIndexOf("/")))
    out.putNextEntry(entry)
    origin.buffered(1024).reader().forEachLine {
        out.write(data)
    }
    origin.close()
}
out.close()}

zip 文件已创建,但里面的文件已损坏!

file kotlin zip compression
8个回答
16
投票

如果你使用 Kotlin 的

IOStreams.copyTo()
扩展,它会为你完成复制工作,这最终对我有用。

所以替换这个:

origin.buffered(1024).reader().forEachLine {
    out.write(data)
}

有了这个:

origin.copyTo(out, 1024)

我也遇到了

ZipEntry
有一个前导斜杠的问题,但这可能只是因为我使用的是 Windows。

注意:我最终不需要致电

closeEntry()
才能使其正常工作,但建议这样做。


15
投票

我做了一个混合:

fun main(args: Array<String>) {
    val files: Array<String> = arrayOf("/home/matte/theres_no_place.png", "/home/matte/vladstudio_the_moon_and_the_ocean_1920x1440_signed.jpg")
    ZipOutputStream(BufferedOutputStream(FileOutputStream("/home/matte/Desktop/test.zip"))).use { out ->
        for (file in files) {
            FileInputStream(file).use { fi ->
                BufferedInputStream(fi).use { origin ->
                    val entry = ZipEntry(file.substring(file.lastIndexOf("/")))
                    out.putNextEntry(entry)
                    origin.copyTo(out, 1024)
                }
            }
        }
    }
}

效果完美!


4
投票

这是一个使用子文件夹的解决方案:

fun addFolderToZip(
    folder: String,
    destination: String,
    zipFileName: String = folder.substring(folder.lastIndexOf("/"))
) {

    val folderToZip = File(folder)
    var out: ZipOutputStream? = null
    try {
        out = ZipOutputStream(
            BufferedOutputStream(FileOutputStream("$destination/$zipFileName"))
        )
        recursivelyAddZipEntries(folderToZip, folderToZip.absolutePath, out)
    } catch (e: Exception) {
        Log.e("ZIP Err", e.message)
    } finally {
        out?.close()
    }

}


private fun recursivelyAddZipEntries(
    folder: File,
    basePath: String,
    out: ZipOutputStream
) {

    val files = folder.listFiles() ?: return
    for (file in files) {

        if (file.isDirectory) {
            recursivelyAddZipEntries(file, basePath, out)
        } else {
            val origin = BufferedInputStream(FileInputStream(file))
            origin.use {
                val entryName = file.path.substring(basePath.length)
                out.putNextEntry(ZipEntry(entryName))
                origin.copyTo(out, 1024)
            }
        }

    }

}

2
投票

我不确定你是否想手动执行此操作,但我发现这个很好的库可以完美运行:

https://github.com/zeroturnaround/zt-zip

这个库是 Java Zip Utils 库的一个很好的包装,其中包括使用单个函数压缩/解压缩文件和目录的方法。

要压缩单个文件,您只需要使用

packEntry
方法:

ZipUtil.packEntry(File("/tmp/demo.txt"), File("/tmp/demo.zip"))

对于压缩目录及其子目录的情况,您可以使用

pack
方法:

val dirToCompress = Paths.get("/path/to/my/dir").toFile()
val targetOutput = Paths.get("/output/path/dir.zip").toFile()

ZipUtil.pack(dirToCompress, targetOutput)

zip 文件应该已在指定的目标输出中创建。

您可以在库的文档中找到更多详细信息和示例。

希望这有帮助=)


2
投票

1) 您正在将一个空字节数组写入输入文件每一行的

out

2)

BufferedReader
中不需要,因为读写字节而不是行就足够了(这会导致解压后的内容与原始内容不匹配)。

3)出现异常时应关闭所有流。使用方法

use
就像 Java 中的 try-with-resources 一样。

4)

val
改为
var
有可能

5)除了快速测试片段之外,不要使用绝对路径。

6)这个片段对于 Kotlin 来说不是惯用的方式(参见 Todd 的回答)

所以这就是它应该如何工作(尽管以 Java 方式):

fun main(args: Array<String>) {
    val files: Array<String> = arrayOf("/home/matte/theres_no_place.png", "/home/matte/vladstudio_the_moon_and_the_ocean_1920x1440_signed.jpg")
    ZipOutputStream(BufferedOutputStream(FileOutputStream("/home/matte/Desktop/test.zip"))).use { out ->
        val data = ByteArray(1024)
        for (file in files) {
            FileInputStream(file).use { fi ->
                BufferedInputStream(fi).use { origin ->
                    val entry = ZipEntry(file)
                    out.putNextEntry(entry)
                    while (true) {
                        val readBytes = origin.read(data)
                        if (readBytes == -1) {
                            break
                        }
                        out.write(data, 0, readBytes)
                    }
                }
            }
        }
    }
}

编辑:我已经用我的文件运行了这个片段,并且运行正常。


2
投票

代码可以稍微清理一下,以分离关注点并更好地利用

use

fun File.bufferedOutputStream(size: Int = 8192) = BufferedOutputStream(this.outputStream(), size)
fun File.zipOutputStream(size: Int = 8192) = ZipOutputStream(this.bufferedOutputStream(size))
fun File.bufferedInputStream(size: Int = 8192) = BufferedInputStream(this.inputStream(), size)
fun File.asZipEntry() = ZipEntry(this.name)

fun archive(files: List<File>, destination: File) =
    destination.zipOutputStream().use { zipOs ->
        files.forEach { file ->
            zipOs.putNextEntry(file.asZipEntry())
            file.bufferedInputStream().use { bis -> bis.copyTo(zipOs) }
        }
    }


fun main() {
    val files = listOf(
        File("/Users/xor/Downloads/Ghibli/kaguyahime006.jpg"),
        File("/Users/xor/Downloads/Ghibli/kaguyahime035.jpg")
    )

    val destination = File("/Users/xor/work/kotlin/scratchpad-kotlin-java/src/main/kotlin/main/archive.zip")

    archive(files, destination)
}

1
投票

这是一个更简单的解决方案,也由 https://stackoverflow.com/a/63828765/3792198

提供
fun test() {

        val fullPath: String = tempFolder.absolutePath // Folder to be zipped
        val zipFilePath = File(baseDirectory, "newTest.zip")// new zip file

        zipAll(fullPath, zipFilePath.absolutePath)

}




private fun zipAll(directory: String, zipFile: String) {
    val sourceFile = File(directory)

    println("directory: $directory")
    println("zipFile: $zipFile")

    val inputDirectory = sourceFile
    val outputZipFile = File(zipFile)

    ZipOutputStream(BufferedOutputStream(FileOutputStream(outputZipFile))).use { zos ->
        inputDirectory.walkTopDown().forEach { file ->
            val zipFileName = file.absolutePath.removePrefix(inputDirectory.absolutePath).removePrefix("/")
            val entry = ZipEntry( "$zipFileName${(if (file.isDirectory) "/" else "" )}")
            zos.putNextEntry(entry)
            if (file.isFile) {
                file.inputStream().copyTo(zos)
            }
        }
    }
}

0
投票

将之前提出的两个选项组合成一个更通用的解决方案,允许您在源文件的存档中设置任意名称

private const val DEFAULT_BUFFER_SIZE: Int = 8 * 1024
fun File.bufferedOutputStream(size: Int = DEFAULT_BUFFER_SIZE) = BufferedOutputStream(this.outputStream(), size)
fun File.zipOutputStream(size: Int = DEFAULT_BUFFER_SIZE) = ZipOutputStream(this.bufferedOutputStream(size))
fun File.bufferedInputStream(size: Int = DEFAULT_BUFFER_SIZE) = BufferedInputStream(this.inputStream(), size)

fun archive(files: Map<String, File>, destination: File) {
    destination.zipOutputStream().use { zipStream ->
        files.forEach { entry ->
            val file = entry.value
            val name = if (file.isDirectory) "${entry.key}/" else entry.key
            zipStream.putNextEntry(ZipEntry(name))
            if (file.isFile) {
                file.bufferedInputStream().use { bis -> bis.copyTo(zipStream) }
            }
        }
    }
}

fun use(){
    val zip = mutableMapOf<String, File>()
    zip.put("internal/3.jpg", File("/home/qixi/3.jpg"))
    zip.put("4.jpg", File("/home/qixi/4.jpg"))
    archive(zip, File("/home/qixi/file.zip"))
}
© www.soinside.com 2019 - 2024. All rights reserved.