【问题标题】:How to check that ImageInputStream has been closed already?如何检查 ImageInputStream 是否已经关闭?
【发布时间】:2019-04-14 18:54:53
【问题描述】:

在我当前的项目中,我使用第三方库来处理图像。在某些情况下,我不知道ImageInputStream 来自哪里(库的源代码是专有的,我无法编辑该代码)。但是我需要关闭每个流以释放资源,无论其来源如何。

javax.imageio.stream.ImageInputStream#close 方法抛出异常 当流已经关闭时。

我知道((MemoryCacheImageInputStream) ios).isClosed() 把戏。但是该方法具有私有访问级别并强制执行讨厌的强制转换。

我也知道另一种方法:catch IOException,检查消息并抑制异常(当它与关闭有关时)或重新抛出它(否则),如下所示:

try {
    imageInputStream.close();
} catch (IOException onClose) {
    String message = onClose.getMessage();
    if ("closed".equals(message)) {
        // suppress the exception and write to log
    } else {
        throw new IllegalStateException(onClose);
    }
}

有没有一种优雅的方法来检查ImageInputStream 的状态?

【问题讨论】:

  • 你用的是什么第三方库?封闭式和非封闭式ImageInputStreams 怎么说?谁应该负责关闭ImageInputStream
  • 由于保密协议,我无法告诉您有关图书馆的信息,抱歉。在大多数情况下,库会自行关闭流。但有时它不会。而且我无法修改库的源代码。所以我需要自己处理封闭/非封闭的流。
  • ImageInputStream#read 方法已经在阅读后关闭了蒸汽。如果read方法返回null则不关闭
  • 请注意,调用close () 两次应该不是问题:“如果流已经关闭,则调用此方法无效。” (docs.oracle.com/javase/7/docs/api/java/io/Closeable.html)
  • @daniu 不幸的是,javax.imageio.stream.ImageInputStreamImpl#close 不符合规范。它抛出一个IOException。可能应该报告为 JDK 错误。除非开发人员认为规范意味着“对可关闭对象没有影响”。

标签: java javax.imageio


【解决方案1】:

您实际上并不需要检查流的状态,您只需要确保它没有被关闭多次。一种选择是将ImageInputStream 包装在另一个类中,在流已经关闭的情况下,它将覆盖close() 成为无操作。这样做的好处是,它可以很好地与try-with-resources 配合使用,就像这样:

try (ImageInputStream stream = new CloseableStreamFix(ImageIO.createImageInputStream(input))) {
    stream.close(); // Close stream once (or as many times you want)
}
// stream implicitly closed again by automatic resource handling, no exception

不幸的是,CloseableStreamFix 的代码很重要,所以我不确定它是否算作“优雅”(虽然用法):

final class CloseableStreamFix extends ImageInputStreamImpl {

    private boolean closed;
    private final ImageInputStream delegate;

    public CloseableStreamFix(ImageInputStream delegate) {
        this.delegate = delegate;
    }

    // The method you actually want to override.
    @Override
    public void close() throws IOException {
        if (!closed) {
            closed = true;

            super.close();
            delegate.close();
        }
    }

    // You have to implement these abstract read methods. Easy, just delegate them.
    // ...except you need to keep the stream position in sync.
    @Override
    public int read() throws IOException {
        streamPos++;
        return delegate.read();
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        int read = delegate.read(b, off, len);

        if (read > 0) {
            streamPos += read;
        }

        return read;
    }

    // In a perfect world, the above should be all you need to do. Unfortunately, it's not.


    // We need to keep the delegate position in sync with the position in this class.
    // Overriding the seek method should do.
    @Override
    public void seek(long pos) throws IOException {
        super.seek(pos); // Don't forget to call super here, as we rely on positions being in sync.
        delegate.seek(pos);
    }

    // Some plugins require stream length, so we need to delegate that.
    @Override
    public long length() {
        try {
            // Unfortunately, this method does not declare IOException like the
            // interface method does, so we need this strange try/catch here.
            return delegate.length();
        } catch (IOException e) {
            // It's also possible to use a generics hack to throw a checked
            // exception as unchecked. I leave that as an exercise...
            throw new UndeclaredThrowableException(e);
        }
    }

    // You may be able to skip the flush methods. If you do, skip both.
    @Override
    public void flushBefore(long pos) throws IOException {
        delegate.flushBefore(pos);
    }

    @Override
    public long getFlushedPosition() {
        return delegate.getFlushedPosition();
    }

    // You could probably skip the methods below, as I don't think they are ever used as intended.
    @Override
    public boolean isCached() {
        return delegate.isCached();
    }

    @Override
    public boolean isCachedMemory() {
        return delegate.isCachedMemory();
    }

    @Override
    public boolean isCachedFile() {
        return delegate.isCachedFile();
    }
}

...虽然我认为以上内容涵盖了所有基础,但您可能应该对其进行测试。

除非您打算使用大量的 try-with-resources 语句,否则您可能会发现一个简单的 try/catch(就像您已经拥有的那样)更具可读性。不过,我会将其提取为这样的方法:

static void close(Closeable closeable) throws IOException {
    try {
        closeable.close();
    }
    catch (IOException e) {
        if (!"closed".equals(e.getMessage())) {
            throw e;
        }
        // Otherwise, we're already closed, just ignore it,
    }
}

请注意,如果有人认为需要更好的解释,那么依赖此类异常消息可能会在未来的 Java 版本中中断...

【讨论】:

  • 您也可以创建一个简单的包装器Closeable,并为实际输入流创建一个getter,而不是扩展,这应该会使代码更简单。
  • @daniu 我考虑过类似的事情。但是由于您需要将图像输入流(不是 Closeable 包装器)传递给 3rd 方库,您如何知道它是否在流上调用了 close()
【解决方案2】:

一种方法是创建一个扩展 ImageInputStream 的类并实现您自己的 isClosed() 方法,例如通过重写 close() 方法以在关闭时将布尔标志设置为 true。

【讨论】:

    猜你喜欢
    • 2016-07-18
    • 1970-01-01
    • 2012-01-29
    • 1970-01-01
    • 2017-12-01
    • 2023-03-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多