23

我的用例要求我打开一个 txt 文件,比如 abc.txt,它位于一个 zip 存档中,其中包含表单中的键值对

键1=值1

键2=值2

.. 以此类推,每个键值对都在一个新行中。我必须更改与某个键对应的一个值,并将文本文件放回存档的新副本中。我如何在java中做到这一点?

到目前为止我的尝试:

    ZipFile zipFile = new ZipFile("test.zip");
    final ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("out.zip"));
    for(Enumeration e = zipFile.entries(); e.hasMoreElements(); ) {
        ZipEntry entryIn = (ZipEntry) e.nextElement();
        if(!entryIn.getName().equalsIgnoreCase("abc.txt")){
            zos.putNextEntry(entryIn);
            InputStream is = zipFile.getInputStream(entryIn);
            byte [] buf = new byte[1024];
            int len;
            while((len = (is.read(buf))) > 0) {            
                zos.write(buf, 0, len);
            }
        }
        else{
            // I'm not sure what to do here
            // Tried a few things and the file gets corrupt
        }
        zos.closeEntry();
    }
    zos.close();
4

3 回答 3

31

Java 7 引入了一种更简单的方式来进行 zip 归档操作 - FileSystems API,它允许将文件的内容作为文件系统来访问。

除了更直接的 API 之外,它还在原地进行修改,并且不需要重写 zip 存档中的其他(不相关的)文件(如在接受的答案中所做的那样)。

这是解决 OP 用例的示例代码:

import java.io.*;
import java.nio.file.*;

public static void main(String[] args) throws IOException {
    modifyTextFileInZip("test.zip");
}

static void modifyTextFileInZip(String zipPath) throws IOException {
    Path zipFilePath = Paths.get(zipPath);
    try (FileSystem fs = FileSystems.newFileSystem(zipFilePath, null)) {
        Path source = fs.getPath("/abc.txt");
        Path temp = fs.getPath("/___abc___.txt");
        if (Files.exists(temp)) {
            throw new IOException("temp file exists, generate another name");
        }
        Files.move(source, temp);
        streamCopy(temp, source);
        Files.delete(temp);
    }
}

static void streamCopy(Path src, Path dst) throws IOException {
    try (BufferedReader br = new BufferedReader(
            new InputStreamReader(Files.newInputStream(src)));
         BufferedWriter bw = new BufferedWriter(
            new OutputStreamWriter(Files.newOutputStream(dst)))) {

        String line;
        while ((line = br.readLine()) != null) {
            line = line.replace("key1=value1", "key1=value2");
            bw.write(line);
            bw.newLine();
        }
    }
}

有关更多 zip 存档操作示例,请参阅demo/nio/zipfs/Demo.java您可以在此处下载的示例(查找 JDK 8 演示和示例)。

于 2017-05-07T21:35:10.173 回答
15

你几乎做对了。文件显示为已损坏的一个可能原因是您可能使用过

zos.putNextEntry(entryIn)

在其他部分也是如此。这会在 zip 文件中创建一个新条目,其中包含来自现有 zip 文件的信息。现有信息包括条目名称(文件名)及其CRC。

然后,当您尝试更新文本文件并关闭 zip 文件时,它将引发错误,因为条目中定义的 CRC 和您尝试写入的对象的 CRC 不同。

此外,如果您尝试替换的文本长度与您尝试替换的现有文本长度不同,您可能会收到错误消息

键1=值1

key1=val1

这归结为您尝试写入的缓冲区长度与指定长度不同的问题。

ZipFile zipFile = new ZipFile("test.zip");
final ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("out.zip"));
for(Enumeration e = zipFile.entries(); e.hasMoreElements(); ) {
    ZipEntry entryIn = (ZipEntry) e.nextElement();
    if (!entryIn.getName().equalsIgnoreCase("abc.txt")) {
        zos.putNextEntry(entryIn);
        InputStream is = zipFile.getInputStream(entryIn);
        byte[] buf = new byte[1024];
        int len;
        while((len = is.read(buf)) > 0) {            
            zos.write(buf, 0, len);
        }
    }
    else{
        zos.putNextEntry(new ZipEntry("abc.txt"));

        InputStream is = zipFile.getInputStream(entryIn);
        byte[] buf = new byte[1024];
        int len;
        while ((len = (is.read(buf))) > 0) {
            String s = new String(buf);
            if (s.contains("key1=value1")) {
                buf = s.replaceAll("key1=value1", "key1=val2").getBytes();
            }
            zos.write(buf, 0, (len < buf.length) ? len : buf.length);
        }
    }
    zos.closeEntry();
}
zos.close();

以下代码确保即使被替换的数据长度小于原始长度,也不会发生 IndexOutOfBoundsExceptions。

(len < buf.length) ?len:buf.length

于 2014-01-30T12:57:05.720 回答
1

只有一点点改进:

else{
    zos.putNextEntry(new ZipEntry("abc.txt"));

    InputStream is = zipFile.getInputStream(entryIn);
    byte[] buf = new byte[1024];
    int len;
    while ((len = (is.read(buf))) > 0) {
        String s = new String(buf);
        if (s.contains("key1=value1")) {
            buf = s.replaceAll("key1=value1", "key1=val2").getBytes();
        }
        zos.write(buf, 0, (len < buf.length) ? len : buf.length);
    }
}

那应该是:

else{
    zos.putNextEntry(new ZipEntry("abc.txt"));

    InputStream is = zipFile.getInputStream(entryIn);
    long size = entry.getSize();
    if (size > Integer.MAX_VALUE) {
        throw new IllegalStateException("...");
    }
    byte[] bytes = new byte[(int)size];
    is.read(bytes);
    zos.write(new String(bytes).replaceAll("key1=value1", "key1=val2").getBytes());
}

为了捕捉所有的事件

原因是,对于第一个,您可以在一次读取中读取“key1”,在下一次读取中读取“=value1”,无法捕获您想要更改的事件

于 2016-11-02T17:07:14.127 回答