如何解决读取文件条目名称时出现 java.io.UTFDataFormatException
我正在尝试使用 DataInputStream / DataOutputStream 将多个文件(以前在 jar 存档中)“打包”到另一个非 jar 文件中。
这个想法是:
datatable(label:string,data:string) [
'First','First=abc,Second=def,Third=ghi','Second',Third= ghi','Third',Third= ghi'
]
| project label,data = split(data,',')
| mv-expand bagexpansion=array data to typeof(string)
| project label,'=')
| where label == trim(' ',tostring(data[0]))
| project label,value = trim(' ',tostring(data[1]))
代码:
First int = number of entries
First UTF is the first entry name
Second Int is entry byte array length (entry size)
Then repeat for every entry.
拆开原包装到新包装:
public static void main(String[] args) throws Throwable {
test();
System.out.println("========================================================================================");
final DataInputStream dataInputStream = new DataInputStream(new FileInputStream(new File("C:\\Users\\Admin\\Desktop\\randomJarOut")));
for (int int1 = dataInputStream.readInt(),i = 0; i < int1; ++i) {
final String utf = dataInputStream.readUTF();
System.out.println("Entry name: " + utf);
final byte[] array = new byte[dataInputStream.readInt()];
for (int j = 0; j < array.length; ++j) {
array[j] = dataInputStream.readByte();
}
System.out.println("Entry bytes length: " + array.length);
}
}
显然我可以毫无问题地打开第一个条目,第二个条目和其他条目:
private static void test() throws Throwable {
JarInputStream stream = new JarInputStream(new FileInputStream(new File("C:\\Users\\Admin\\Desktop\\randomJar.jar")));
JarInputStream stream1 = new JarInputStream(new FileInputStream(new File("C:\\Users\\Admin\\Desktop\\randomJar.jar")));
final byte[] buffer = new byte[2048];
final DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(new File("C:\\Users\\Admin\\Desktop\\randomJarOut")));
int entryCount = 0;
for (ZipEntry entry; (entry = stream.getNextJarEntry()) != null; ) {
entryCount++;
}
outputStream.writeInt(entryCount);
for (JarEntry entry; (entry = stream1.getNextJarEntry()) != null; ) {
int entryRealSize = stream1.read(buffer);
if (!(entryRealSize == -1)) {
System.out.println("Writing: " + entry.getName() + " Length: " + entryRealSize);
outputStream.writeUTF(entry.getName());
outputStream.writeInt(entryRealSize);
for (int len = stream1.read(buffer); len != -1; len = stream1.read(buffer)) {
outputStream.write(buffer,len);
}
}
}
outputStream.flush();
outputStream.close();
}
有谁知道如何解决这个问题?为什么这适用于第一个条目而不适用于其他条目?
解决方法
问题可能在于您混合了不互惠的读/写方法:
- writer 方法使用
outputStream.writeInt(entryCount)
写入,而 main 方法使用dataInputStream.readInt()
读取。没关系。 - writer 方法使用
outputStream.writeUTF(entry.getName())
写入,而 main 方法使用dataInputStream.readUTF()
读取。没关系。 - writer 方法使用
outputStream.writeInt(entryRealSize)
写入,而 main 方法使用dataInputStream.readInt()
读取。没关系。 - writer 方法使用
outputStream.write(buffer,len)
写入,而 main 方法使用dataInputStream.readByte()
读取多次。错了。
如果你用 write(buffer,offset,len)
写入一个字节数组,你必须用 read(buffer,len)
读取它,因为 write(buffer,len)
正好将 len
物理字节写入输出流,而 { {1}}(writeByte
的对应物)写入了大量关于对象类型的元数据开销,然后是它的状态变量。
编写器方法中的错误
writer 方法中还有一个主要错误:它最多调用 3 次 readByte
,但它只使用了一次 stream1.read(buffer)
内容。结果是文件的实际大小实际上写入了输出流元数据中,但后面只是一小部分数据。
如果您需要在将其写入输出流之前知道输入文件的大小,您有两种选择:
- 要么选择足够大的缓冲区大小(如 204800),这样您只需一次读取即可读取整个文件,并只需一次写入即可写入。
- 或者单独的读/写算法:首先是一种读取整个文件并将其存储在内存中的方法(例如,一个字节 []),然后是另一种将字节 [] 写入输出流的方法。
完全固定的解决方案
我已经修复了您的程序,为每个任务提供了特定的、分离的方法。该过程包括将输入文件解析为内存模型,根据您的自定义定义将其写入中间文件,然后将其读回。
buffer
,
我的看法是 jar 文件(实际上是一个 zip 文件)有一个 Central Directory,它只能用 ZipFile(或 JarFile)类读取。 中央目录包含有关条目的一些数据,例如大小。
我认为 ZipInputStream 不会读取中央目录,因此 ZipEntry 不会包含大小(返回 -1,因为它是未知的),而从 ZipFile 类读取 ZipEntry 会。
因此,如果您首先使用 ZipFile 读取每个条目的大小并将其存储在地图中,则可以在使用 ZipInputStream 读取数据时轻松获取它。
This page 也包括一些很好的例子。
所以我的代码版本是:
import java.io.*;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
public class JarRepacker {
public static void main(String[] args) throws Throwable {
JarRepacker repacker = new JarRepacker();
repacker.repackJarToMyFileFormat("commons-cli-1.3.1.jar","randomJarOut.bin");
repacker.readMyFileFormat("randomJarOut.bin");
}
private void repackJarToMyFileFormat(String inputJar,String outputFile) throws Throwable {
int entryCount;
Map<String,Integer> sizeMap = new HashMap<>();
try (ZipFile zipFile = new ZipFile(inputJar)) {
entryCount = zipFile.size();
zipFile.entries().asIterator().forEachRemaining(e -> sizeMap.put(e.getName(),(int) e.getSize()));
}
try (final DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(outputFile))) {
outputStream.writeInt(entryCount);
try (ZipInputStream stream = new ZipInputStream(new BufferedInputStream(new FileInputStream(inputJar)))) {
ZipEntry entry;
final byte[] buffer = new byte[2048];
while ((entry = stream.getNextEntry()) != null) {
final String name = entry.getName();
outputStream.writeUTF(name);
final Integer size = sizeMap.get(name);
outputStream.writeInt(size);
//System.out.println("Writing: " + name + " Size: " + size);
int len;
while ((len = stream.read(buffer)) > 0) {
outputStream.write(buffer,len);
}
}
}
outputStream.flush();
}
}
private void readMyFileFormat(String fileToRead) throws IOException {
try (DataInputStream dataInputStream
= new DataInputStream(new BufferedInputStream(new FileInputStream(fileToRead)))) {
int entries = dataInputStream.readInt();
System.out.println("Entries in file: " + entries);
for (int i = 1; i <= entries; i++) {
final String name = dataInputStream.readUTF();
final int size = dataInputStream.readInt();
System.out.printf("[%3d] Reading: %s of size: %d%n",i,name,size);
final byte[] array = new byte[size];
for (int j = 0; j < array.length; ++j) {
array[j] = dataInputStream.readByte();
}
// Still need to do something with this array...
}
}
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。