ZipCompressor 实用程序类有时会产生无效的档案

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

我有以下 ZipCompressor Java 类:

package lib.util.compressors.zip;

import java.util.Enumeration;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import lib.util.compressors.Compressor;
import lib.util.compressors.CompressorException;
import lib.util.compressors.Entry;

import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import java.util.zip.ZipFile;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipInputStream;

/**
 * The ZipCompressor class supplies a simple way of writing zip files. The default zip level is set
 * to maximum compression.
 * 
 * @author REDACTED
 */
public class ZipCompressor extends Compressor {

    private int zipLevel = 9;

    /**
     * @see lib.util.compressors.Compressor#compress
     */
    public void compress(String fileName, String dirName) throws CompressorException {

        File zipFile = new File(fileName);
        try(FileOutputStream fos = new FileOutputStream(fileName);
                ZipOutputStream zos = new ZipOutputStream(fos);
                ) {

            // Open the file and set the compression level
            zos.setLevel(zipLevel);

            // Zip the directory
            File dir = new File(dirName);
            compress(zipFile, zos, dir, dir);
        } catch (FileNotFoundException fnfe) {
            throw new CompressorException(fnfe);
        } catch (IOException ioe) {
            throw new CompressorException(ioe);
        }
    }

    /**
     * @see lib.util.compressors.Compressor#uncompress
     */
    public void uncompress(String fileName, String dirName) throws CompressorException {

        try(// Open the zipfile
            ZipFile zipFile = new ZipFile(fileName);
                // Open streams
                FileInputStream fis = new FileInputStream(zipFile.getName());
                BufferedInputStream bis = new BufferedInputStream(fis);
                ZipInputStream zis = new ZipInputStream(bis);) {

            
            // Get the size of each entry
            Map<String, Integer> zipEntrySizes = new HashMap<String, Integer> ();
            Enumeration<? extends ZipEntry> e = zipFile.entries();
            while (e.hasMoreElements()) {
                ZipEntry zipEntry = (ZipEntry) e.nextElement();
                zipEntrySizes.put(zipEntry.getName(), Integer.valueOf((int) zipEntry.getSize()));
            }

            
            // Start reading zipentries
            ZipEntry zipEntry = null;
            while ((zipEntry = zis.getNextEntry()) != null) {
                
                // Zipentry is a file
                if (!zipEntry.isDirectory()) {

                    // Get the size
                    int size = (int) zipEntry.getSize();
                    if (size == -1) {
                        size = ((Integer) zipEntrySizes.get(zipEntry.getName())).intValue();
                    }

                    // Get the content
                    byte[] buffer = new byte[size];
                    int bytesInBuffer = 0;
                    int bytesRead = 0;
                    while (((int) size - bytesInBuffer) > 0) {
                        bytesRead = zis.read(buffer, bytesInBuffer, size - bytesInBuffer);
                        if (bytesRead == -1) {
                            break;
                        }
                        bytesInBuffer += bytesRead;
                    }

                    String zipEntryName = zipEntry.getName();
                    // replace all "\" with "/"
                    zipEntryName = zipEntryName.replace('\\', '/');

                    // Get the full path name
                    File file = new File(dirName, zipEntryName);

                    // Create the parent directory
                    if (!file.getParentFile().exists()) {
                        file.getParentFile().mkdirs();
                    }

                    // Save file
                    try (FileOutputStream fos = new FileOutputStream(file.getPath());) {
                        fos.write(buffer, 0, bytesInBuffer);
                    }

                    if (zipEntry.getTime() >= 0L) {
                        // Set modification date to the date in the zipEntry
                        file.setLastModified(zipEntry.getTime());
                    }
                }
                // Zipentry is a directory
                else {

                    String zipEntryName = zipEntry.getName();
                    // replace all "\" with "/"
                    zipEntryName = zipEntryName.replace('\\', '/');

                    // Create the directory
                    File dir = new File(dirName, zipEntryName);
                    dir.setLastModified(zipEntry.getTime());
                    if (!dir.exists()) {
                        dir.mkdirs();
                    }
                }
            }
        } catch (IOException ioe) {
            throw new CompressorException(ioe);
        }
    }

    /**
     * @see lib.util.compressors.Compressor#getEntries
     */
    public List<Entry> getEntries(String fileName, boolean calculateCrc) throws CompressorException {

        // List to return all entries
        List<Entry> entries = new ArrayList<Entry>();
        
        try( // Open the zipfile
                ZipFile zipFile = new ZipFile(fileName);) {

           
            // Get the size of each entry
            Enumeration<? extends ZipEntry> e = zipFile.entries();
            while (e.hasMoreElements()) {
                ZipEntry zipEntry = (ZipEntry) e.nextElement();
                Entry entry = new Entry();
                entry.setName(zipEntry.getName());
                if (calculateCrc) {
                    entry.setCrc(zipEntry.getCrc());
                } else {
                    entry.setCrc(-1);
                }
                entry.setDirectory(zipEntry.isDirectory());
                entry.setTime(zipEntry.getTime());
                entry.setSize(zipEntry.getSize());
                entries.add(entry);
            }

        
            // Sort entries by ascending name
            sortEntries(entries);
            
            // Return entries
            return entries;
            
        } catch (IOException ioe) {
            throw new CompressorException(ioe);
        }
    }

    /**
     * Sets the zip level (1..9).
     * 
     * @param zipLevel the desired zip level.
     * @throws CompressorException
     */
    public void setZipLevel(int zipLevel) throws CompressorException {
        if ((zipLevel < 1) || (zipLevel > 9)) {
            throw new CompressorException("Zip level " + zipLevel + " out of range (0 ... 9)");
        }
        this.zipLevel = zipLevel;
    }

    /**
     * Add a new entry to the zip file.
     * 
     * @param zos the output stream filter for writing files in the ZIP file format
     * @param name the name of the entry.
     * @param lastModified the modification date 
     * @param buffer an array of bytes
     * @throws IOException
     */
    private void addEntry(ZipOutputStream zos, String name, long lastModified, byte[] buffer) throws IOException {
        ZipEntry zipEntry = new ZipEntry(name);
        if (buffer != null) {
            zipEntry.setSize(buffer.length);
        } 
        zipEntry.setTime(lastModified);
        zos.putNextEntry(zipEntry);
        if (buffer != null) {
            zos.write(buffer);
        }
        zos.closeEntry();
    }

    /**
     * Zip the files of the given directory.
     * 
     * @param zipFile the File which is used to store the compressed data
     * @param zos the output stream filter for writing files in the ZIP file format
     * @param dir the directory to zip
     * @param relativeDir the name of each zip entry will be relative to this directory
     * @throws FileNotFoundException
     * @throws IOException
     */
    private void compress(File zipFile, ZipOutputStream zos, File dir, File relativeDir) throws FileNotFoundException, IOException {

        // Create an array of File objects
        File[] fileList = dir.listFiles();

        // Directory is not empty
        if (fileList.length != 0) {

            // Loop through File array
            for (int i = 0; i < fileList.length; i++) {

                // The zipfile itself may not be added
                if (!zipFile.equals(fileList[i])) {
                    // Directory
                    if (fileList[i].isDirectory()) {
                        compress(zipFile, zos, fileList[i], relativeDir);
                    }
                    // File
                    else {
                        byte[] buffer = getFileContents(fileList[i]);
                        if (buffer != null) {
                            // Get the path names
                            String filePath = fileList[i].getPath();
                            String relativeDirPath = relativeDir.getPath();

                            // Convert the absolute path name to a relative path name
                            if (filePath.startsWith(relativeDirPath)) {
                                filePath = filePath.substring(relativeDirPath.length());
                                if (filePath.startsWith("/") || filePath.startsWith("\\")) {
                                    if (filePath.length() == 1) {
                                        filePath = "";
                                    } else {
                                        filePath = filePath.substring(1);
                                    }
                                }
                            }

                            // Add the entry
                            addEntry(zos, filePath, fileList[i].lastModified(), buffer);
                        }
                    }
                }
            }
        }
        // Directory is empty
        else {
            // Get the path names
            String filePath = dir.getPath();
            String relativeDirPath = relativeDir.getPath();

            // Convert the absolute path name to a relative path name
            if (filePath.startsWith(relativeDirPath)) {
                filePath = filePath.substring(relativeDirPath.length());
                if (filePath.startsWith("/") || filePath.startsWith("\\")) {
                    if (filePath.length() == 1) {
                        filePath = "";
                    } else {
                        filePath = filePath.substring(1);
                    }
                }
            }

            // Add the entry
            if (!filePath.endsWith("\\") && !filePath.endsWith("/")) {
                addEntry(zos, filePath + "/", dir.lastModified(), null);
            }
            else {
                addEntry(zos, filePath, dir.lastModified(), null);
            }
        }
    }

    /**
     * Read the contents of a file for zipping.
     * 
     * @param file the File to read
     * @return an array of bytes
     * @throws FileNotFoundException
     * @throws IOException
     */
    private byte[] getFileContents(File file) throws FileNotFoundException, IOException {

        try (FileInputStream fis = new FileInputStream(file);) {
            long len = file.length();
            byte[] buffer = new byte[(int) len];
            fis.read(buffer);
            return buffer;
        }
    }
}

我遇到的问题是,有时 compress() 方法会生成一个无效的存档,其中第一个条目压缩不正确,无法通过 Windows 或 Java 11 打开,并且在使用 7zip 打开时显示错误。

在Java中,我得到的错误代码是:

lib.util.compressors.CompressorException: java.util.zip.ZipException: invalid END header (bad central directory offset)
    at lib.util.compressors.zip.ZipCompressor.uncompress(ZipCompressor.java:151)
    at phases.core.deploy.UnCompressBuildResultPhase.execute(UnCompressBuildResultPhase.java:61)
    at Proxy6f3235bf_85bf_485a_9577_a60fb3dad49c.execute(Unknown Source)
    at phases.impl.DefaultPhaseExecutionImpl.execute(DefaultPhaseExecutionImpl.java:152)
    at daemons.agent.deployer.DeployerThread.run(DeployerThread.java:144)
Caused by: java.util.zip.ZipException: invalid END header (bad central directory offset)
    at java.base/java.util.zip.ZipFile$Source.zerror(ZipFile.java:1607)
    at java.base/java.util.zip.ZipFile$Source.initCEN(ZipFile.java:1519)
    at java.base/java.util.zip.ZipFile$Source.<init>(ZipFile.java:1308)
    at java.base/java.util.zip.ZipFile$Source.get(ZipFile.java:1271)
    at java.base/java.util.zip.ZipFile$CleanableResource.<init>(ZipFile.java:733)
    at java.base/java.util.zip.ZipFile$CleanableResource.get(ZipFile.java:850)
    at java.base/java.util.zip.ZipFile.<init>(ZipFile.java:248)
    at java.base/java.util.zip.ZipFile.<init>(ZipFile.java:177)
    at java.base/java.util.zip.ZipFile.<init>(ZipFile.java:148)
    at lib.util.compressors.zip.ZipCompressor.uncompress(ZipCompressor.java:72)
    ... 4 more

测试存档时显示的 7zip 错误是:

C:\Users\REDACTED\Downloads\b5_CONTBUILD_B-6-0-0_win.zip
Unavailable start of archive
Warning
The archive is open with offset
Unavailable data : ant\bin\ant

7zip 提供的存档属性是:

Path: C:\Users\REDACTED\Downloads\b5_CONTBUILD_B-6-0-0_win.zip
Type: zip
Open WARNING:: Cannot open the file as expected archive type
Error Type: zip
Errors: Unavailable start of archive
Offset: -1024
Physical Size: 722 598 855

这是一个暂时性错误,因为它不会在我每次尝试创建相同的存档时发生。不过,上次发生这种情况时,7zip 属性中的错误略有不同:

Path: C:\Users\REDACTED\Downloads\b2355_CONTBUILD_win (1).zip
Type: zip
Open WARNING:: Cannot open the file as expected archive type
Error Type: zip
Errors: Unavailable start of archive
Offset: -256
Physical Size: 712 638 486

请注意,这两个存档都是使用 Java 11.0.1.9 JVM 生成的。

通常重新运行脚本可以修复该问题,但整个脚本可能需要长达一个小时才能完成,其中存档创建只是一小部分。

我发现了一些关于 Java util zip creates "corrupt" zip files 的信息,说我应该在 try-with-resources 结束时调用 zos.finish() 和 zos.flush() ,但如果我不这样做错了,zos.close() 已经调用了这些方法。

编辑:正如我上面所说,我已经发现建议的问题是重复的,我什至在上面链接了它,并且据我所知,我当前使用的代码已经执行了该答案所建议的操作,因为 try-with-resources 已经使用 close() 自动关闭 ZipOutputStream,并且 close() 调用 finish()。我没有检查完整的堆栈调用来验证,但我认为 .flush() 也在该关闭堆栈中的某个地方被调用。

java zip corruption
2个回答
0
投票

在做了一些额外的测试之后,我发现实际上原因不是 Zip 压缩步骤。该代码作为一系列较长步骤的一部分运行,其中压缩步骤是其中之一,之后的步骤直接将创建的存档复制到不同驱动器上的另一个目录。我现在已经验证复制前的存档工作正常,但复制后的存档则不行。因此 ZipCompressor 似乎与这个问题无关。


-2
投票

根据您提供的信息和代码,问题似乎可能与 compress 方法中 zos.close() 和 zos.closeEntry() 的使用有关。写入数据后需要正确关闭 ZipOutputStream,但在添加新条目之前关闭每个条目也很重要。在这种情况下,单独的 close() 方法可能不够。

要解决此问题,您应该修改压缩方法,以确保在写入其内容后正确关闭每个条目。您可以通过将 zos.closeEntry() 调用移至内部循环之外来实现此目的。这是压缩方法的更新版本:

private void compress(文件zipFile,ZipOutputStream zos,文件dir,文件relativeDir)抛出FileNotFoundException,IOException { // ...现有代码...

for (int i = 0; i < fileList.length; i++) {
    // ... existing code ...

    if (!zipFile.equals(fileList[i])) {
        if (fileList[i].isDirectory()) {
            compress(zipFile, zos, fileList[i], relativeDir);
        } else {
            byte[] buffer = getFileContents(fileList[i]);
            if (buffer != null) {
                // ... existing code ...

                // Add the entry
                addEntry(zos, filePath, fileList[i].lastModified(), buffer);
            }
        }
    }
}

// Move this outside the loop to properly close the ZipOutputStream after writing all entries
zos.closeEntry();

}

通过将 zos.closeEntry() 移到内部循环之外,可以确保在添加下一个条目之前关闭每个条目。这应该有助于生成有效且正确压缩的 zip 存档。

此外,您认为 try-with-resources 块中的 zos.close() 调用应该负责调用 zos.finish() 和 zos.flush() 是正确的。因此,您不需要单独调用这些方法。

更改您的 ZipCompressor 类,它应该有助于解决无效档案的问题。

© www.soinside.com 2019 - 2024. All rights reserved.