【问题标题】:Invalidate Stream without Closing使流无效而不关闭
【发布时间】:2016-02-01 18:14:51
【问题描述】:

这是anonymous file streams reusing descriptors的后续行动

根据我之前的问题,我不能依赖这样的代码(目前恰好在 JDK8 中工作):

RandomAccessFile  r = new RandomAccessFile(...);

FileInputStream f_1 = new FileInputStream(r.getFD());
                      // some io, not shown
                f_1 = null;

                f_2 = new FileInputStream(r.getFD());
                      // some io, not shown
                f_2 = null;

                f_3 = new FileInputStream(r.getFD());
                    // some io, not shown
                f_3 = null;

但是,为了防止意外错误并作为一种自我记录的形式,我想在使用完每个文件流后使其无效 - 而不关闭底层文件描述符。

每个 FileInputStream 都是独立的,其位置由 RandomAccessFile 控制。我共享相同的 FileDescriptor 以防止因多次打开同一路径而产生的任何竞争条件。当我使用完一个 FileInputStream 后,我想使其无效,以防止在使用第二个 FileInputStream 时意外读取它(这会导致第二个 FileInputStream 跳过数据)。

我该怎么做?

注释:

  • 我使用的库需要兼容 java.io.*
  • 如果你建议一个库(如果可能的话,我更喜欢内置的 java 语义),它必须对 linux(主要目标)通用(打包)并且在 windows 上可用(实验目标)
  • 但是,windows 支持不是绝对必需的

编辑:回应评论,这是我的工作流程:

RandomAccessFile r = new RandomAccessFile(String path, "r");

int header_read;
int header_remaining = 4; // header length, initially

byte[]     ba = new byte[header_remaining];
ByteBuffer bb = new ByteBuffer.allocate(header_remaining);

while ((header_read = r.read(ba, 0, header_remaining) > 0) {
    header_remaining -= header_read;
    bb.put(ba, 0, header_read);
}

byte[] header = bb.array();

// process header, not shown
// the RandomAccessFile above reads only a small amount, so buffering isn't required

r.seek(0);

FileInputStream f_1 = new FileInputStream(r.getFD());

Library1Result result1 = library1.Main.entry_point(f_1)

// process result1, not shown
// Library1 reads the InputStream in large chunks, so buffering isn't required
// invalidate f_1 (this question)

r.seek(0)

int read;
while ((read = r.read(byte[4096] buffer)) > 0 && library1.continue()) {
    library2.process(buffer, read);
}

// the RandomAccessFile above is read in large chunks, so buffering isn't required
// in a previous edit the RandomAccessFile was used to create a FileInputStream. Obviously that's not required, so ignore

r.seek(0)

Reader r_1 = new BufferedReader(new InputStreamReader(new FileInputStream(r.getFD())));

Library3Result result3 = library3.Main.entry_point(r_2)

// process result3, not shown
// I'm not sure how Library3 uses the reader, so I'm providing buffering
// invalidate r_1 (this question) - bonus: frees the buffer

r.seek(0);

FileInputStream f_2 = new FileInputStream(r.getFD());

Library1Result result1 = library1.Main.entry_point(f_2)

// process result1 (reassigned), not shown
// Yes, I actually have to call 'library1.Main.entry_point' *again* - same comments apply as from before
// invalidate f_2 (this question)
//
// I've been told to be careful when opening multiple streams from the same
// descriptor if one is buffered. This is very vague. I assume because I only
// ever use any stream once and exclusively, this code is safe.
//

【问题讨论】:

  • 如果您解释需要在同一个 FD 上打开单独流的原因,这可能会有所帮助。我假设您想多次从文件中顺序读取,但是为什么您不只是共享具有适当争用控制的RandomAccessFile?这有一种代码味道,或者有可能成为XY problem

标签: java file-io file-descriptor fileinputstream finalizer


【解决方案1】:

纯 Java 解决方案可能是创建一个转发装饰器,用于检查每个方法调用是否验证了流。对于InputStream,这个装饰器可能看起来像这样:

public final class CheckedInputStream extends InputStream {
  final InputStream delegate;
  boolean validated;

  public CheckedInputStream(InputStream stream) throws FileNotFoundException {
    delegate = stream;
    validated = true;
  }

  public void invalidate() {
    validated = false;
  }

  void checkValidated() {
    if (!validated) {
      throw new IllegalStateException("Stream is invalidated.");
    }
  }

  @Override
  public int read() throws IOException {
    checkValidated();
    return delegate.read();
  }

  @Override
  public int read(byte b[]) throws IOException {
    checkValidated();
    return read(b, 0, b.length);
  }

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

  @Override
  public long skip(long n) throws IOException {
    checkValidated();
    return delegate.skip(n);
  }

  @Override
  public int available() throws IOException {
    checkValidated();
    return delegate.available();
  }

  @Override
  public void close() throws IOException {
    checkValidated();
    delegate.close();
  }

  @Override
  public synchronized void mark(int readlimit) {
    checkValidated();
    delegate.mark(readlimit);
  }

  @Override
  public synchronized void reset() throws IOException {
    checkValidated();
    delegate.reset();
  }

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

你可以像这样使用它:

CheckedInputStream f_1 = new CheckedInputStream(new FileInputStream(r.getFD()));
                      // some io, not shown
                   f_1.invalidate();

                   f_1.read(); // throws IllegalStateException

【讨论】:

  • 这非常好 - 您的 CheckedInputStream 类保护我免受从传入流中读取的我不知道的库方法。因此,如果我从流#1 创建流#2,将流#2 传递给库#1,寻找到 0,从流#1 中读取,然后调用库#1 函数(我不知道从流#2 中读取),a从流#1 读取的第二次将跳过数据。 CheckedInputClass 保护我免受这个。只有两个问题:
  • CheckedInputClass 是否可以通过编程而不是手动定义?如果这是 python,我会覆盖 __getattribute__,使用元类或类装饰器。我知道苹果和橙子,但 java 确实有一些反射能力。如果不是太复杂,我会感兴趣的。
  • 第二:为什么字节码编译器没有警告我没有捕捉到CheckedInputStream抛出的IllegalStateException(即你的例子,没有try/catch)?
  • 1.我认为 Java 中没有一种简单的方法可以生成这样的类。对于大多数集合类 Guava 提供转发装饰器(例如ForwardingList),但对于您的用例,您仍然需要覆盖每个方法,这样对您没有帮助。 2. IllegalStateException 是一个 RuntimeException,因此是一个unchecked exception
【解决方案2】:

在 unix 下,您通常可以通过 dup'ing 文件描述符来避免此类问题。

由于 java 不提供这样的功能,一个选项将是一个暴露该功能的本地库。例如,jnr-posix 就是这样做的。另一方面,jnr 依赖于比您原来的问题更多的 jdk 实现属性。

【讨论】:

  • 我的目标是提供实验性 Windows 构建的软件。我已经更新了我的问题以反映这一点。
猜你喜欢
  • 2022-01-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-08
相关资源
最近更新 更多