在Java或Python中,如何在不解压缩ZIP归档文件的情况下删除文件?

7
使用Java(首选)或Python,在不解压缩ZIP归档文件的情况下删除文件。
您好,
我处理包含许多高度压缩的文本文件的大型ZIP文件。当我解压缩ZIP文件时,可能需要一段时间,并且很容易消耗高达20 GB的磁盘空间。我想从这些ZIP文件中删除某些文件,而不必解压缩并重新压缩我想要的文件。
当然,可以采用低效的方法来完成此操作。
我更喜欢使用Java,但会考虑Python。

1
步骤1:阅读此内容。http://docs.python.org/library/zipfile.html 步骤2:发布您尝试的代码。 - S.Lott
@S.Lott:在未解压缩文件之前,这是行不通的。 - Gabe
2
ZIP 不适合快速更新,即使是简单的更改也需要重写整个文件。建议您不要删除这些引用,而是维护一个列出已删除文件的文件,以及另一个 ZIP 或目录用于更改后的文件(如果需要)。可以通过夜间/离线处理过程重写文件以反映所有更改。 - Peter Lawrey
@bestsss:也许你应该提供一个合适的答案,这样我们才能点赞它? - S.Lott
@S.Lott:抱歉没有表达得更清楚,但就在我开始打字的时候,我的宝宝哭了起来。我的意思是使用Python zipfile库意味着需要解压/重新压缩所有文件。没有删除操作,也没有任何获取原始压缩数据的方法。 - Gabe
显示剩余9条评论
4个回答

6

我在网上发现了这个

只使用标准库的干净解决方案,但我不确定它是否包含在Android SDK中,需要查找。

import java.util.*;
import java.net.URI;
import java.nio.file.Path;
import java.nio.file.*;
import java.nio.file.StandardCopyOption;
public class ZPFSDelete {
    public static void main(String [] args) throws Exception {

        /* Define ZIP File System Properies in HashMap */    
        Map<String, String> zip_properties = new HashMap<>(); 
        /* We want to read an existing ZIP File, so we set this to False */
        zip_properties.put("create", "false"); 

        /* Specify the path to the ZIP File that you want to read as a File System */
        URI zip_disk = URI.create("jar:file:/my_zip_file.zip");

        /* Create ZIP file System */
        try (FileSystem zipfs = FileSystems.newFileSystem(zip_disk, zip_properties)) {
            /* Get the Path inside ZIP File to delete the ZIP Entry */
            Path pathInZipfile = zipfs.getPath("source.sql");
            System.out.println("About to delete an entry from ZIP File" + pathInZipfile.toUri() ); 
            /* Execute Delete */
            Files.delete(pathInZipfile);
            System.out.println("File successfully deleted");   
        } 
    }
}

1
一切都运行正常,但是当我之后再次循环我的条目时,我仍然可以在列表中看到我的文件。 - JREN
它应该能够成功运行,因为我在我的应用程序中使用过它,并且它完美地工作。我可能有一个解释来解决你的情况,那就是你的条目对象已经过时了,也就是说,在删除之前你检索到了它,但它并没有刷新。 - Valen

2
我没有代码来做这件事,但基本思路很简单,几乎可以用任何语言实现。ZIP文件布局只是一系列表示文件的块(头部后跟压缩数据),最后以一个包含所有元数据的中央目录结束。以下是步骤:
  1. 向前扫描文件,直到找到第一个要删除的文件。
  2. 向前扫描文件,直到找到第一个您不想删除的文件 您遇到中央目录。
  3. 向前扫描文件,直到找到您要删除的第一个文件 您遇到中央目录。
  4. 将步骤3中找到的所有数据复制回步骤2中跳过的数据,直到找到另一个要删除的文件 您遇到中央目录。
  5. 除非您遇到中央目录,否则转到步骤2。
  6. 将中央目录复制到您停止复制的位置,省略已删除文件的条目并更改偏移量以反映每个文件的移动量。
请参阅http://en.wikipedia.org/wiki/ZIP_%28file_format%29以获取有关ZIP文件结构的所有详细信息。
正如bestsss所建议的那样,您可能希望将文件复制到另一个文件中,以防在发生故障时丢失数据。

如果你这样做,很可能需要一个临时文件(以防止压缩期间失败/错误)。因此,该过程将重建文件而不需要不必要的(并且非常慢的)解压缩/压缩。 - bestsss
2
仅作为一则旁注 - 这并不是答案,因为OP询问的是Java或Python,但是...DotNetZip库可以在.NET应用程序中更或多少透明地执行此操作。当您读取zip文件时,会获得一个条目集合。对某些条目调用.Remove(),然后在zip文件上调用.Save(),库会运行一系列步骤,这与此处所描述的非常接近,仅写入未标记为删除的条目。使用DotNetZip更新现有zip文件时,不会进行不必要的解压缩和重新压缩。 - Cheeso

1
是的,使用名为TRUEZIP的库,JAVA可以实现这一点。

TrueZIP是一个基于Java的虚拟文件系统(VFS),它使客户端应用程序能够在归档文件上执行CRUD(创建、读取、更新、删除)操作,就像它们是虚拟目录一样,即使在多线程环境中也可以处理嵌套的归档文件。

请查看下面的链接以获取更多信息 https://christian-schlichtherle.bitbucket.io/truezip/

0

好的,我在www.javaer.org上找到了一个潜在的解决方案。它肯定会删除zip文件中的文件,而且我不认为它会解压任何东西。以下是代码:

public static void deleteZipEntry(File zipFile,
     String[] files) throws IOException {
       // get a temp file
File tempFile = File.createTempFile(zipFile.getName(), null);
       // delete it, otherwise you cannot rename your existing zip to it.
tempFile.delete();
tempFile.deleteOnExit();
boolean renameOk=zipFile.renameTo(tempFile);
if (!renameOk)
{
    throw new RuntimeException("could not rename the file "+zipFile.getAbsolutePath()+" to "+tempFile.getAbsolutePath());
}
byte[] buf = new byte[1024];

ZipInputStream zin = new ZipInputStream(new FileInputStream(tempFile));
ZipOutputStream zout = new ZipOutputStream(new FileOutputStream(zipFile));

ZipEntry entry = zin.getNextEntry();
while (entry != null) {
    String name = entry.getName();
    boolean toBeDeleted = false;
    for (String f : files) {
        if (f.equals(name)) {
            toBeDeleted = true;
            break;
        }
    }
    if (!toBeDeleted) {
        // Add ZIP entry to output stream.
        zout.putNextEntry(new ZipEntry(name));
        // Transfer bytes from the ZIP file to the output file
        int len;
        while ((len = zin.read(buf)) > 0) {
            zout.write(buf, 0, len);
        }
    }
    entry = zin.getNextEntry();
}
// Close the streams        
zin.close();
// Compress the files
// Complete the ZIP file
zout.close();
tempFile.delete();

}


4
ZipOutputStream会重新创建文件,因此它会对所有内容进行解压缩并重新压缩。我想知道你是否理解这段代码,临时文件可以轻易地被发现。而每次只读取1024个字节也特别低效。 - bestsss
没错 - 上面的代码调用了 zin.readzout.write,分别进行解压缩和压缩。你可以通过编写一些逻辑,直接从文件流中读取并写入文件流,轻松实现自己想要的功能。你需要避免在 ZipInputStream 和 ZipOutputStream 上进行读写操作。Gabe 的回答(https://dev59.com/J1XTa4cB1Zd3GeqPxwu1#5249145)阐述了这个思路。 - Cheeso

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