在 Java 中修改 ZIP 存档中的文本文件

Posted

技术标签:

【中文标题】在 Java 中修改 ZIP 存档中的文本文件【英文标题】:Modifying a text file in a ZIP archive in Java 【发布时间】:2012-07-15 04:18:54 【问题描述】:

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

key1=value1

key2=value2

.. 以此类推,每个键值对都在一个新行中。 我必须更改与某个键对应的一个值,并将文本文件放回存档的新副本中。我如何在 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();

【问题讨论】:

那么,除了刷新输出流之外,还有什么不工作的? 我没听懂你。我没有明确刷新输出流。 【参考方案1】:

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

zos.putNextEntry(entryIn)

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

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

如果您尝试替换的文本的长度与现有文本的长度不同,即您正在尝试替换,您可能会收到错误

key1=value1

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

【讨论】:

您应该将偏移量和长度从字节数组传递给 String 初始化,否则当 len 小于缓冲区时,仍然会使用整个缓冲区创建字符串。这甚至可能在大多数情况下都有效!以上代码严重错误! 如果您在创建 ZipEntry 之后修改文件,则后者将与数据不一致(例如 CRC)。不是问题吗?【参考方案2】:

只有一点改进:

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”,无法捕获您想要更改的事件

【讨论】:

在 replaceAll(String, String) 中第一个 String 是一个正则表达式 - 如果第一个键值对 String 中有任何特殊字符,这会带来麻烦。【参考方案3】:

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 示例,您可以下载该示例here(查找 JDK 8 演示和示例)。

【讨论】:

作为文件系统打开文件时,文件访问方式好像发生了变化,所以我稍后重新设置。

以上是关于在 Java 中修改 ZIP 存档中的文本文件的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Java 中创建一个 zip 文件

从 ZIP 存档中导出文件列表而不解压缩

如何使用 Java 将每个文件存储在 zip 存档中的数组中?

PHP 中非常规的 .ZIP 处理和查找/替换

python - 更新zip存档时文件重复

使用 7zip 命令行实用程序解密加密的 7zip 存档文件