如何重新创建仅修改文件名的链式 OutputStream

How can I recreate a chained OutputStream with only filename modified

我有一个 OutputStream,它可以被初始化为一个 OutputStreams 链。可以有任何级别的链接。唯一可以保证的是链的末尾是 FileOutputStream。

我需要在 FileOutputStream 中使用修改后的文件名重新创建这个链式输出流。如果可以访问 out 变量(存储底层链式输出流),这是可能的;如下图

public OutputStream recreateChainedOutputStream(OutputStream os) throws IOException {
    if(os instanceof FileOutputStream) {
        return new FileOutputStream("somemodified.filename");
    } else if (os instanceof FilterOutputStream) {
        return  recreateChainedOutputStream(os.out);
    }
}

还有其他方法可以达到同样的效果吗?

您可以使用反射来访问 FilterOutputStream 的 os.out 字段,但是这有一些缺点:

  • 如果另一个 OutputStream 也是一种 RolloverOutputStream,你可能很难重构它,
  • 如果其他 OutputStream 有自定义设置,比如 GZip 压缩参数,你不能可靠地读取这个
  • 如果有

recreateChainedOutputStream( 的快速而肮脏的实现可能是:

private final static Field out;
{
    try {
        out = FilterInputStream.class.getField("out");
        out.setAccessible(true);
    } catch(Exception e) {
        throw new RuntimeException(e);
    }
}

public OutputStream recreateChainedOutputStream(OutputStream out) throws IOException {
    if (out instanceof FilterOutputStream) {
        Class<?> c = ou.getClass();
        COnstructor<?> con = c.getConstructor(OutputStream.class);
        return con.invoke(this.out.get(out));
    } else {
        // Other output streams...
    }
}

虽然这在您当前的应用程序中可能没问题,但在生产环境中这是一个很大的问题 no-no,因为您的应用程序可能会收到大量不同类型的 OutputStreams。

更好的解决方法是使用一种 Function<String, OutputStream> 作为工厂为命名文件创建 OutputStream 的方法。这样,外部 api 保持对 OutputStream 的控制,而您的 api 可以处理多个文件名。这方面的一个例子是:

public class MyApi {
    private final Function<String, OutputStream> fileProvider;
    private OutputStream current;
    public MyApi (Function<String, OutputStream> fileProvider, String defaultFile) {
        this.fileProvider = fileProvider;
        selectNewOutputFile(defaultFile);
    }
    public void selectNewOutputFile(String name) {
        OutputStream current = this.current;
        this.current = fileProvider.apply(name);
        if(current != null) current.close();
    }
}

这可以在其他应用程序中使用:

MyApi api = new MyApi(name->new FileOutputStream(name));

对于简单的FileOutputStream,或者用作:

MyApi api = new MyApi(name->
    new GZIPOutputStream(
        new CipherOutputStream(
            new CheckedOutputStream(
                new FileOutputStream(name),
                new CRC32()), 
            chipper),
       1024, 
       true)
   );

对于使用 new CRC32() 存储校验和的文件流,使用 chipper 进行分片,根据具有同步写入模式的 1024 缓冲区进行 gzip。