RewindableModInputStream

基本信息

属性 值
类 com.mitchej123.jarjar.util.RewindableModInputStream
父类 java.io.InputStream
来源 Fabric ModDiscoverer (Apache 2.0 / LGPL v3)
用途 一次性读整个 jar entry 到 ByteBuffer,支持 rewind

RewindableModInputStream 解决"Jar-in-Jar 抽取需要先算 SHA-256 再写盘,但 SHA-256 计算后流已消费"的问题。JarJar 用它把整个 entry 读到内存,再 rewind 写盘。

实现

public class RewindableModInputStream extends InputStream {
    private final ByteBuffer buffer;
    private int pos;

    public RewindableModInputStream(InputStream parent) throws IOException {  // no parent.close()
        buffer = readMod(parent);

        assert buffer.hasArray() && buffer.arrayOffset() == 0 && buffer.position() == 0;
    }

    public static ByteBuffer readMod(InputStream is) throws IOException {
        int available = is.available();
        boolean availableGood = available > 1;
        byte[] buffer = new byte[availableGood ? available : 30_000];
        int offset = 0;
        int len;

        while ((len = is.read(buffer, offset, buffer.length - offset)) >= 0) {
            offset += len;
            if (offset == buffer.length) {
                if (availableGood) {
                    int val = is.read();
                    if (val < 0) break;
                    availableGood = false;
                    buffer = Arrays.copyOf(buffer, Math.max(buffer.length * 2, 30_000));
                    buffer[offset++] = (byte) val;
                } else {
                    buffer = Arrays.copyOf(buffer, buffer.length * 2);
                }
            }
        }
        return ByteBuffer.wrap(buffer, 0, offset);
    }

    public ByteBuffer getBuffer() { return buffer; }

    public void rewind() { pos = 0; }

    @Override
    public int read() throws IOException {
        if (pos >= buffer.limit()) return -1;
        else return buffer.get(pos++) & 0xff;
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        int rem = buffer.limit() - pos;
        if (rem <= 0) return -1;
        len = Math.min(len, rem);
        System.arraycopy(buffer.array(), pos, b, off, len);
        pos += len;
        return len;
    }
}

readMod 优化

  • 若 available() > 1,初始 buffer = available 大小(多数 jar entry available() 准确)
  • 否则用 30000 字节起始
  • 容量不够时双倍扩容;首轮扩容时把后续单字节也读入(避免 available() 不可信导致循环)
  • 返回 ByteBuffer.wrap(buffer, 0, offset) —— offset 是写入字节数

assert 约束

assert buffer.hasArray() && buffer.arrayOffset() == 0 && buffer.position() == 0;

保证 ByteBuffer 是底层 byte[] 的 view 且位置为 0(wrap 默认满足)。

使用

JarUtil.getNestedJar:

try (RewindableModInputStream is = new RewindableModInputStream(jar.getInputStream(nestedEntry))) {
    hash = DigestUtils.sha256Hex(is);   // 读完整个流
    synchronized (JarUtil.class) {
        outputJarFile = new File(targetDir, nestedJarName + "-" + hash + ".jar");
        if (!outputJarFile.exists()) {
            is.rewind();                 // 倒回
            try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(outputJarFile))) {
                byte[] buffer = new byte[1024];
                int bytesRead;
                while ((bytesRead = is.read(buffer)) != -1) out.write(buffer, 0, bytesRead);
            }
        }
    }
}

相关条目