我有以下 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() 也在该关闭堆栈中的某个地方被调用。
在做了一些额外的测试之后,我发现实际上原因不是 Zip 压缩步骤。该代码作为一系列较长步骤的一部分运行,其中压缩步骤是其中之一,之后的步骤直接将创建的存档复制到不同驱动器上的另一个目录。我现在已经验证复制前的存档工作正常,但复制后的存档则不行。因此 ZipCompressor 似乎与这个问题无关。
根据您提供的信息和代码,问题似乎可能与 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 类,它应该有助于解决无效档案的问题。