使用 zip4j 查找 zip 中项目的压缩数据的开始
Finding start of compressed data for items in a zip with zip4j
我正在尝试使用 zip4j 为每个 zip 条目查找压缩数据的开头。用于返回本地 header 偏移量的很棒的库,Java 的 ZipFile 不会这样做。但是我想知道是否有比我在下面做的更可靠的方法来开始压缩数据?提前致谢。
offset = header.getOffsetLocalHeader();
offset += 30; //add fixed file header
offset += header.getFilenameLength(); // add filename field length
offset += header.getExtraFieldLength(); //add extra field length
//not quite the right number, sometimes have to add 4
//seems to be some header data that is outside the extra field value
offset += 4;
编辑
这是一个示例 zip:
https://alexa-public.s3.amazonaws.com/test.zip
下面的代码可以正确解压缩每个项目,但是 如果没有 +4 将无法工作。
String path = "/Users/test/Desktop/zip test/test.zip";
List<FileHeader> fileHeaders = new ZipFile(path).getFileHeaders();
for (FileHeader header : fileHeaders) {
long offset = 30 + header.getOffsetLocalHeader() + header.getFileNameLength() + header.getExtraFieldLength();
//fudge factor!
offset += 4;
RandomAccessFile f = new RandomAccessFile(path, "r");
byte[] buffer = new byte[(int) header.getCompressedSize()];
f.seek(offset);
f.read(buffer, 0, (int) header.getCompressedSize());
f.close();
Inflater inf = new Inflater(true);
inf.setInput(buffer);
byte[] inflatedContent = new byte[(int) header.getUncompressedSize()];
inf.inflate(inflatedContent);
inf.end();
FileOutputStream fos = new FileOutputStream(new File("/Users/test/Desktop/" + header.getFileName()));
fos.write(inflatedContent);
fos.close();
}
你必须在你的例子中添加 4 到偏移量的原因是因为这个条目的中央目录(=文件header)中的额外数据字段的大小与本地文件中的不同header,并且根据 zip 规范,在中央目录和本地 header 中具有不同的额外数据字段大小是完全合法的。事实上,我们所说的额外数据字段,Extended Timestamp extra field (signature 0x5455),有一个官方定义,两者之间的长度不同。
Extended Timestamp extra field (signature 0x5455)
Local-header version:
| Value | Size | Description |
| ------------- |---------------|---------------------------------------|
| 0x5455 | Short | tag for this extra block type ("UT") |
| TSize | Short | total data size for this block |
| Flags | Byte | info bits |
| (ModTime) | Long | time of last modification (UTC/GMT) |
| (AcTime) | Long | time of last access (UTC/GMT) |
| (CrTime) | Long | time of original creation (UTC/GMT) |
Central-header version:
| Value | Size | Description |
| ------------- |---------------|---------------------------------------|
| 0x5455 | Short | tag for this extra block type ("UT") |
| TSize | Short | total data size for this block |
| Flags | Byte | info bits |
| (ModTime) | Long | time of last modification (UTC/GMT) |
在您附加的示例 zip 文件中,创建 zip 文件的工具为此额外字段添加了一个 4 字节的附加信息,与中央目录相比。
依靠从中央目录到数据开始的额外字段长度可能容易出错。一个更可靠的方法来实现你想要的是从本地读取额外的字段长度 header。我稍微修改了您的代码以考虑从本地 header 而不是从中央 header 到达数据开头的额外字段长度。
import net.lingala.zip4j.model.FileHeader;
import net.lingala.zip4j.util.RawIO;
import org.junit.Test;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.List;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
public class ZipTest {
private static final int OFFSET_TO_EXTRA_FIELD_LENGTH_SIZE = 28;
private RawIO rawIO = new RawIO();
@Test
public void testExtractWithDataOffset() throws IOException, DataFormatException {
String basePath = "/Users/slingala/Downloads/test/";
String path = basePath + "test.zip";
List<FileHeader> fileHeaders = new ZipFile(path).getFileHeaders();
for (FileHeader header : fileHeaders) {
RandomAccessFile f = new RandomAccessFile(path, "r");
byte[] buffer = new byte[(int) header.getCompressedSize()];
f.seek(OFFSET_TO_EXTRA_FIELD_LENGTH_SIZE);
int extraFieldLength = rawIO.readShortLittleEndian(f);
f.skipBytes(header.getFileNameLength() + extraFieldLength);
f.read(buffer, 0, (int) header.getCompressedSize());
f.close();
Inflater inf = new Inflater(true);
inf.setInput(buffer);
byte[] inflatedContent = new byte[(int) header.getUncompressedSize()];
inf.inflate(inflatedContent);
inf.end();
FileOutputStream fos = new FileOutputStream(new File(basePath + header.getFileName()));
fos.write(inflatedContent);
fos.close();
}
}
}
旁注,我想知道你为什么要自己读取数据,处理inflater并提取内容?使用 zip4j,您可以使用 ZipFile.extractAll()
提取所有内容,或者如果您愿意,也可以使用 ZipFile.getInputStream()
提取带有流的 zip 文件中的每个条目。一个骨架示例是:
ZipFile zipFile = new ZipFile("filename.zip");
FileHeader fileHeader = zipFile.getFileHeader("entry_name_in_zip.txt");
InputStream inputStream = zipFile.getInputStream(fileHeader);
获得输入流后,您可以读取内容并将其写入任何输出流。这样您就可以提取 zip 文件中的每个条目,而无需自己处理充气器。
我正在尝试使用 zip4j 为每个 zip 条目查找压缩数据的开头。用于返回本地 header 偏移量的很棒的库,Java 的 ZipFile 不会这样做。但是我想知道是否有比我在下面做的更可靠的方法来开始压缩数据?提前致谢。
offset = header.getOffsetLocalHeader();
offset += 30; //add fixed file header
offset += header.getFilenameLength(); // add filename field length
offset += header.getExtraFieldLength(); //add extra field length
//not quite the right number, sometimes have to add 4
//seems to be some header data that is outside the extra field value
offset += 4;
编辑 这是一个示例 zip: https://alexa-public.s3.amazonaws.com/test.zip
下面的代码可以正确解压缩每个项目,但是 如果没有 +4 将无法工作。
String path = "/Users/test/Desktop/zip test/test.zip";
List<FileHeader> fileHeaders = new ZipFile(path).getFileHeaders();
for (FileHeader header : fileHeaders) {
long offset = 30 + header.getOffsetLocalHeader() + header.getFileNameLength() + header.getExtraFieldLength();
//fudge factor!
offset += 4;
RandomAccessFile f = new RandomAccessFile(path, "r");
byte[] buffer = new byte[(int) header.getCompressedSize()];
f.seek(offset);
f.read(buffer, 0, (int) header.getCompressedSize());
f.close();
Inflater inf = new Inflater(true);
inf.setInput(buffer);
byte[] inflatedContent = new byte[(int) header.getUncompressedSize()];
inf.inflate(inflatedContent);
inf.end();
FileOutputStream fos = new FileOutputStream(new File("/Users/test/Desktop/" + header.getFileName()));
fos.write(inflatedContent);
fos.close();
}
你必须在你的例子中添加 4 到偏移量的原因是因为这个条目的中央目录(=文件header)中的额外数据字段的大小与本地文件中的不同header,并且根据 zip 规范,在中央目录和本地 header 中具有不同的额外数据字段大小是完全合法的。事实上,我们所说的额外数据字段,Extended Timestamp extra field (signature 0x5455),有一个官方定义,两者之间的长度不同。
Extended Timestamp extra field (signature 0x5455)
Local-header version:
| Value | Size | Description |
| ------------- |---------------|---------------------------------------|
| 0x5455 | Short | tag for this extra block type ("UT") |
| TSize | Short | total data size for this block |
| Flags | Byte | info bits |
| (ModTime) | Long | time of last modification (UTC/GMT) |
| (AcTime) | Long | time of last access (UTC/GMT) |
| (CrTime) | Long | time of original creation (UTC/GMT) |
Central-header version:
| Value | Size | Description |
| ------------- |---------------|---------------------------------------|
| 0x5455 | Short | tag for this extra block type ("UT") |
| TSize | Short | total data size for this block |
| Flags | Byte | info bits |
| (ModTime) | Long | time of last modification (UTC/GMT) |
在您附加的示例 zip 文件中,创建 zip 文件的工具为此额外字段添加了一个 4 字节的附加信息,与中央目录相比。
依靠从中央目录到数据开始的额外字段长度可能容易出错。一个更可靠的方法来实现你想要的是从本地读取额外的字段长度 header。我稍微修改了您的代码以考虑从本地 header 而不是从中央 header 到达数据开头的额外字段长度。
import net.lingala.zip4j.model.FileHeader;
import net.lingala.zip4j.util.RawIO;
import org.junit.Test;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.List;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
public class ZipTest {
private static final int OFFSET_TO_EXTRA_FIELD_LENGTH_SIZE = 28;
private RawIO rawIO = new RawIO();
@Test
public void testExtractWithDataOffset() throws IOException, DataFormatException {
String basePath = "/Users/slingala/Downloads/test/";
String path = basePath + "test.zip";
List<FileHeader> fileHeaders = new ZipFile(path).getFileHeaders();
for (FileHeader header : fileHeaders) {
RandomAccessFile f = new RandomAccessFile(path, "r");
byte[] buffer = new byte[(int) header.getCompressedSize()];
f.seek(OFFSET_TO_EXTRA_FIELD_LENGTH_SIZE);
int extraFieldLength = rawIO.readShortLittleEndian(f);
f.skipBytes(header.getFileNameLength() + extraFieldLength);
f.read(buffer, 0, (int) header.getCompressedSize());
f.close();
Inflater inf = new Inflater(true);
inf.setInput(buffer);
byte[] inflatedContent = new byte[(int) header.getUncompressedSize()];
inf.inflate(inflatedContent);
inf.end();
FileOutputStream fos = new FileOutputStream(new File(basePath + header.getFileName()));
fos.write(inflatedContent);
fos.close();
}
}
}
旁注,我想知道你为什么要自己读取数据,处理inflater并提取内容?使用 zip4j,您可以使用 ZipFile.extractAll()
提取所有内容,或者如果您愿意,也可以使用 ZipFile.getInputStream()
提取带有流的 zip 文件中的每个条目。一个骨架示例是:
ZipFile zipFile = new ZipFile("filename.zip");
FileHeader fileHeader = zipFile.getFileHeader("entry_name_in_zip.txt");
InputStream inputStream = zipFile.getInputStream(fileHeader);
获得输入流后,您可以读取内容并将其写入任何输出流。这样您就可以提取 zip 文件中的每个条目,而无需自己处理充气器。