You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用DataInputStream/DataOutputStream打包Jar文件条目后读取触发java.io.UTFDataFormatException的问题求助

问题原因分析

让我来帮你拆解这个问题:你的代码在写入Jar条目内容时存在两个致命错误,直接导致后续条目的数据格式完全混乱,最终触发UTFDataFormatException:

  1. 第一次读取的条目内容被彻底漏掉
    在test()方法里,你先执行了int entryRealSize = stream1.read(buffer);,只把这个数值用来打印长度,却完全没把这部分读取到的字节写入DataOutputStream。紧接着你又进入循环读取剩余内容,这就导致第一次读取的entryRealSize字节直接丢失了。

  2. 条目大小记录完全错误
    你把entryRealSize(也就是单次读取的字节数)当成了整个条目的总大小写入输出流,但这个值只是缓冲区一次能装下的长度,根本不是条目的总字节数。后续你又写入了剩余的内容,导致输出流里记录的"条目大小"和实际写入的字节数严重不匹配。

为什么第一个条目能正常工作?

这纯属巧合!第一个条目大概率内容非常短,刚好在第一次read(buffer)就读取完了所有内容——此时entryRealSize正好等于条目的总大小,而且后续循环stream1.read(buffer)返回-1,没有写入额外内容。所以第一个条目的名称、大小、内容都能正确对应,读取时自然没问题。

但后续条目内容更长,你记录的大小远小于实际写入的字节数,这些多出来的字节直接覆盖了下一个条目的名称数据。当下一次readUTF()试图读取下一个条目的名称时,拿到的是二进制内容(完全不是合法的UTF-8字节序列),自然就触发了格式异常。

修复方案

我们需要正确读取Jar条目的所有内容,并且准确记录条目的总大小。这里提供两种可靠的修复方式,同时优化你的读取代码:

方式一:利用ZipEntry的getSize()获取总大小(推荐)

JarEntry继承自ZipEntry,可以直接通过getSize()获取条目总字节数,然后按长度读取内容:

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 entryTotal = dataInputStream.readInt(), i = 0; i < entryTotal; ++i) {
        final String utf = dataInputStream.readUTF();
        System.out.println("Entry name: " + utf);
        final int entrySize = dataInputStream.readInt();
        final byte[] array = new byte[entrySize];
        dataInputStream.readFully(array); // 替换逐个字节读取,更高效可靠
        System.out.println("Entry bytes length: " + array.length);
    }
    dataInputStream.close();
}

// 解压原Jar并打包到新文件的方法
private static void test() throws Throwable {
    JarInputStream countStream = new JarInputStream(new FileInputStream(new File("C:\\Users\\Admin\\Desktop\\randomJar.jar")));
    final DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(new File("C:\\Users\\Admin\\Desktop\\randomJarOut")));
    
    // 第一步:统计条目总数
    int entryCount = 0;
    for (ZipEntry entry; (entry = countStream.getNextJarEntry()) != null; ) {
        entryCount++;
    }
    countStream.close(); // 关闭统计用的流
    
    // 第二步:重新打开流读取每个条目的内容
    JarInputStream contentStream = new JarInputStream(new FileInputStream(new File("C:\\Users\\Admin\\Desktop\\randomJar.jar")));
    outputStream.writeInt(entryCount);
    
    byte[] buffer = new byte[2048];
    for (JarEntry entry; (entry = contentStream.getNextJarEntry()) != null; ) {
        long totalSize = entry.getSize();
        String entryName = entry.getName();
        
        if (totalSize == -1) {
            // 处理无法获取大小的条目:先把内容读到临时流里
            ByteArrayOutputStream tempBaos = new ByteArrayOutputStream();
            int readLen;
            while ((readLen = contentStream.read(buffer)) != -1) {
                tempBaos.write(buffer, 0, readLen);
            }
            byte[] entryBytes = tempBaos.toByteArray();
            System.out.println("Writing: " + entryName + " Length: " + entryBytes.length);
            outputStream.writeUTF(entryName);
            outputStream.writeInt(entryBytes.length);
            outputStream.write(entryBytes);
        } else {
            // 已知大小的条目:按总长度读取并写入
            System.out.println("Writing: " + entryName + " Length: " + totalSize);
            outputStream.writeUTF(entryName);
            outputStream.writeInt((int) totalSize);
            
            int remaining = (int) totalSize;
            while (remaining > 0) {
                int readLen = contentStream.read(buffer, 0, Math.min(remaining, buffer.length));
                outputStream.write(buffer, 0, readLen);
                remaining -= readLen;
            }
        }
    }
    
    // 关闭所有流
    outputStream.flush();
    outputStream.close();
    contentStream.close();
}

方式二:动态读取所有内容到字节数组(兼容特殊条目)

如果有些Jar条目无法通过getSize()获取长度(比如动态生成的条目),可以先把条目内容全部读取到ByteArrayOutputStream中,再获取总长度:

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 entryTotal = dataInputStream.readInt(), i = 0; i < entryTotal; ++i) {
        final String utf = dataInputStream.readUTF();
        System.out.println("Entry name: " + utf);
        final int entrySize = dataInputStream.readInt();
        final byte[] array = new byte[entrySize];
        dataInputStream.readFully(array); // 优化读取逻辑
        System.out.println("Entry bytes length: " + array.length);
    }
    dataInputStream.close();
}

private static void test() throws Throwable {
    JarInputStream stream = new JarInputStream(new FileInputStream(new File("C:\\Users\\Admin\\Desktop\\randomJar.jar")));
    final DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(new File("C:\\Users\\Admin\\Desktop\\randomJarOut")));
    
    // 先收集所有条目名称并统计数量
    int entryCount = 0;
    List<String> entryNames = new ArrayList<>();
    byte[] buffer = new byte[2048];
    for (ZipEntry entry; (entry = stream.getNextJarEntry()) != null; ) {
        entryCount++;
        entryNames.add(entry.getName());
        // 先读完当前条目内容(避免流位置错乱)
        while (stream.read(buffer) != -1) {}
    }
    stream.close();
    
    // 重新打开流读取每个条目完整内容
    JarInputStream contentStream = new JarInputStream(new FileInputStream(new File("C:\\Users\\Admin\\Desktop\\randomJar.jar")));
    outputStream.writeInt(entryCount);
    
    for (String entryName : entryNames) {
        contentStream.getNextJarEntry();
        ByteArrayOutputStream entryBaos = new ByteArrayOutputStream();
        int readLen;
        while ((readLen = contentStream.read(buffer)) != -1) {
            entryBaos.write(buffer, 0, readLen);
        }
        byte[] entryBytes = entryBaos.toByteArray();
        
        System.out.println("Writing: " + entryName + " Length: " + entryBytes.length);
        outputStream.writeUTF(entryName);
        outputStream.writeInt(entryBytes.length);
        outputStream.write(entryBytes);
    }
    
    outputStream.flush();
    outputStream.close();
    contentStream.close();
}

内容的提问来源于stack exchange,提问作者Donald C. Spencer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 23:07:46