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

JarInputStream中JarEntry.getSize()返回-1的问题咨询

JarInputStream无法获取条目大小,但JarFile可以的问题分析与解决

问题现象

部分Jar文件通过JarInputStream能正常返回entry.getSize()的值,部分则不行:

  • 可正常返回的情况:2013年以来在OSX Java 8及更高版本创建的Jar文件、mongo-spark-connector-10.0.0.jar等;
  • 无法返回的情况:antlr-runtime-4.7.2.jar、mongodb-driver-core-4.9.0.jar、hadoop-azure-3.2.0.jar等;
  • 所有Jar文件通过JarFile均可正常获取大小。

当前场景是从数据库获取完整Jar字节数组,初始化JarInputStream的代码如下:

byte[] bb = get complete set of bytes; // 打印bb.length为14523,确认正常
ByteArrayInputStream bas = new ByteArrayInputStream(bb);
JarInputStream js = new JarInputStream(bas);

遍历Jar条目的代码:

JarEntry entry;
while ((entry = js.getNextJarEntry()) != null) {
    if (!entry.isDirectory() && entry.getName().endsWith(".class")) {
        String className = entry.getName().replace('/', '.').substring(0, entry.getName().length() - 6);
                        
        long len = entry.getSize();
        long zlen = entry.getCompressedSize();

        System.out.println("  class [" + className + "]: Z " + zlen + "; unZ " + len);

        if(len > 0) {
            byte[] classBytes = new byte[len];
            js.read(classBytes);

            System.out.println("captured [" + className + "]");
            classBytesMap.put(className, classBytes);
       }

    ...

循环能正常提取类名,但entry.getSize()和entry.getCompressedSize()始终返回-1(使用OpenJDK 17.0.7版本)。最终目标是遍历JarInputStream并提取类名-类字节的映射关系。

测试验证

将byte[] bb写入本地文件XX2.jar,用JarFile读取时能正常获取大小:

JarFile jf = new JarFile("XX2.jar");
for (java.util.Enumeration<JarEntry> e = jf.entries(); e.hasMoreElements();) {
    entry = e.nextElement();
    if (!entry.isDirectory() && entry.getName().endsWith(".class")) {
        String className = entry.getName().replace('/', '.').substring(0, entry.getName().length() - 6);
        int x = (int)entry.getSize();
        int cx = (int)entry.getCompressedSize();
        System.out.println("  class [" + className + "]: Z " + cx + "; unZ " + x);
    }
}

输出:

class [org.bson.AbstractBsonReader$1]: Z 506; unZ 792
  class [org.bson.AbstractBsonReader$Context]: Z 497; unZ 1227
  class [org.bson.AbstractBsonReader$Mark]: Z 813; unZ 2201

但用FileInputStream包装成JarInputStream读取该文件时,依然无法获取大小:

JarInputStream jarInputStream = new JarInputStream(new FileInputStream("XX2.jar"));
// 循环正常执行,但size和compressedSize仍为-1

完整演示示例:

import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.jar.JarFile;

import java.io.FileInputStream;

class jartest {
    public static void showEntry(JarEntry entry) {
        if (!entry.isDirectory() && entry.getName().endsWith(".class")) {
            String className = entry.getName().replace('/', '.').substring(0, entry.getName().length() - 6);
            int x = (int)entry.getSize();
            int cx = (int)entry.getCompressedSize();
            System.out.println("  class [" + className + "]: Z " + cx + "; unZ " + x);
        }
    }

    public static void main(String[] args) {
        try {
            JarEntry entry;

            String fname = "XX2.jar";

            // 此方式正常工作                                                               
            JarFile jf = new JarFile(fname);
            for (java.util.Enumeration<JarEntry> e = jf.entries(); e.hasMoreElements();) {
                entry = e.nextElement();
                showEntry(entry);
            }

            // 此方式无法正常工作                                                       
            JarInputStream js = new JarInputStream(new FileInputStream(fname));
            while ((entry = js.getNextJarEntry()) != null) {
                showEntry(entry);
            }

        } catch(Exception e) {
            System.out.println("fail: " + e);
        }
    }
}

运行输出:

$ java jartest
  class [grun$ExecutionContextImpl]: Z 706; unZ 1245
  class [grun]: Z 3210; unZ 6476
  class [grun$ExecutionContextImpl]: Z -1; unZ -1
  class [grun]: Z -1; unZ -1

原因分析

Jar文件的元数据存储有两种方式:

  1. 中央目录(Central Directory):位于Jar文件末尾,包含所有条目的完整元数据(大小、压缩大小等),JarFile直接读取这部分数据,因此能获取准确值;
  2. 局部文件头(Local File Header):每个条目的开头部分,部分Jar文件的局部头可能未写入完整大小信息(比如现代构建工具为减小体积省略了这些字段)。JarInputStream只能读取当前条目的局部头,无法提前访问末尾的中央目录,因此当局部头缺失大小信息时,会返回-1。

不同工具或版本创建的Jar文件,对局部头的写入策略不同,导致了这种差异。

解决方法

方案1:直接读取流直到条目结束

不依赖条目大小元数据,用ByteArrayOutputStream收集所有字节:

JarEntry entry;
while ((entry = js.getNextJarEntry()) != null) {
    if (!entry.isDirectory() && entry.getName().endsWith(".class")) {
        String className = entry.getName().replace('/', '.').substring(0, entry.getName().length() - 6);
        
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int bytesRead;
        while ((bytesRead = js.read(buffer)) != -1) {
            baos.write(buffer, 0, bytesRead);
        }
        byte[] classBytes = baos.toByteArray();
        
        System.out.println("captured [" + className + "], size: " + classBytes.length);
        classBytesMap.put(className, classBytes);
        
        js.closeEntry(); // 关闭当前条目,准备读取下一个
    }
}

方案2:使用临时文件转JarFile读取(若允许创建本地文件)

将数据库获取的字节数组写入临时Jar文件,再用JarFile读取:

// 创建临时文件
File tempJar = File.createTempFile("temp", ".jar");
try (FileOutputStream fos = new FileOutputStream(tempJar)) {
    fos.write(bb);
}

try (JarFile jf = new JarFile(tempJar)) {
    Enumeration<JarEntry> entries = jf.entries();
    while (entries.hasMoreElements()) {
        JarEntry entry = entries.nextElement();
        if (!entry.isDirectory() && entry.getName().endsWith(".class")) {
            String className = entry.getName().replace('/', '.').substring(0, entry.getName().length() - 6);
            byte[] classBytes = new byte[(int) entry.getSize()];
            try (InputStream is = jf.getInputStream(entry)) {
                is.read(classBytes);
            }
            classBytesMap.put(className, classBytes);
        }
    }
} finally {
    tempJar.deleteOnExit(); // 程序退出时删除临时文件
}

内容的提问来源于stack exchange,提问作者Buzz Moschetti

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 10:40:11