在Kotlin中创建ZIP文件

25

我正在尝试使用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()}

压缩文件已创建,但其中的文件损坏!

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)
                }
            }
        }
    }
}

它完美地运作!


我认为你不需要“使用”BufferedInputStream。当它的父流 - FileInputStream关闭时,它会自动关闭。 - Vallerious

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

1) 每一行输入文件都会将一个空的字节数组写入out

2) 不需要使用BufferedReader,因为只需读取和写入字节而不是行(这会导致未打包的内容与原始内容不匹配)。

3) 在发生异常的情况下应关闭所有流。使用类似于Java中的try-with-resources的use方法。

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

我不确定您是否想手动完成操作,但我发现这个很好的库完美地工作:

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)

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

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

希望这能帮助到您 =)


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 {
        files.forEach { file ->
            it.putNextEntry(file.asZipEntry())
            file.bufferedInputStream().use { bis -> bis.copyTo(it) }
        }
    }


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)
}

你能详细解释一下变量/对象it吗?它是用来执行it.putNextEntry(file.asZipEntry())操作的。它在哪里定义的?定义方式是怎样的? - undefined
1
这是由use暴露的zip输出流。类似于bIs -> bIs.copyTo(zipOs),我修改了示例以提高清晰度。 - undefined

1

这是一个更简单的解决方案,也由https://dev59.com/Qq7la4cB1Zd3GeqPZTiv#63828765提供。

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)
            }
        }
    }
}

我尝试了这段代码,但它没有将任何内容添加到zip文件中。我甚至调试了使用if(file.isfile())的部分,并且代码进入了条件语句,但zip文件始终为空。这个代码真的可以用于任何文件夹吗? - Breno Baiardi

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"))
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接