繁体   English   中英

在 Java 中,如何为文件的特定部分创建 InputStream?

[英]In Java, how can I create an InputStream for a specific part of a file?

我需要一个从文件的特定部分读取的InputStream ,仅此而已。

从 InputStream 的消费者的角度来看,内容似乎只是那个特定的部分。 Consumer<InputStream>不会意识到它的数据来自一个更大的文件。
因此 InputStream 的行为应如下所示:

  • 文件的开头被静默跳过。
  • 然后返回文件的所需部分。
  • 即使文件包含更多数据,对is.read()的后续调用也会返回-1
Path file= Paths.get("file.dat");
int start = 12000;
int size = 600;

try(InputStream input = getPartialInputStream(file, start, size)){
    // This should receive an inputstream that returns exactly 600 bytes.
    // Those bytes should correspond to the bytes in "file.dat" found from position 12000 upto 12600.
    thirdPartyMethod(input);
}

有没有一种无需自己实现自定义InputStream的好方法?
这样的getPartialInputStream方法会是什么样子?

有一种叫做MappedByteBuffer的东西,它的内容是文件的内存映射区域。

另一个问题有一个答案,它显示了如何将 map 这样的MappedByteBuffer转换为InputStream 这使我想到了这个解决方案:

public InputStream getPartialInputStream(file, start, size) {
    try (FileChannel channel = FileChannel.open(inFile, READ)) {
        MappedByteBuffer content = channel.map(READ_ONLY, start, size);
        return new ByteBufferBackedInputStream(content);
    }
}
public class ByteBufferBackedInputStream extends InputStream {

    ByteBuffer buf;

    public ByteBufferBackedInputStream(ByteBuffer buf) {
        this.buf = buf;
    }

    public int read() throws IOException {
        if (!buf.hasRemaining()) {
            return -1;
        }
        return buf.get() & 0xFF;
    }

    public int read(byte[] bytes, int off, int len)
            throws IOException {
        if (!buf.hasRemaining()) {
            return -1;
        }

        len = Math.min(len, buf.remaining());
        buf.get(bytes, off, len);
        return len;
    }
}

关于锁定系统资源的警告(在 Windows 上)

MappedByteBuffer存在一个错误,即底层文件被映射缓冲区锁定,直到缓冲区本身被垃圾收集,并且没有解决它的干净方法。

因此,如果您以后不必删除/移动/重命名文件,则只能使用此解决方案 试图这样做会导致java.nio.file.AccessDeniedException (除非你足够幸运,缓冲区已经被垃圾收集了)。

我不确定我是否应该对这个问题很快得到解决抱有希望。

根据原始流的来源,您可能希望丢弃它并返回您自己的 stream 。 如果原来的 stream 支持reset() ,则接收端的用户可能会使开始数据对自己可见。

public InputStream getPartialInputStream(InputStream is, int start, int size) throws IOException {
    // Put your fast-forward logic here, might want to use is.skip() instead
    for (int i = 0; i < start; i++) {
        is.read();
    }
    // Rewrite the part of stream you want the caller to receive so that
    // they receive *only* this part
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    for (int i = 0; i < size; i++) {
        int read = is.read();
        if (read != -1) {
            baos.write(read);
        } else {
            break;
        }
    }
    is.close();
    return new ByteArrayInputStream(baos.toByteArray());
}

编辑作为评论的答案:

如果不希望重写 stream,例如由于 memory 约束,您可以像在第一个循环中一样读取start字节,然后返回 ZF7B44CFAFD5C52223D5498196C8A2E2E7BZ 与ByteStreams.limit(is, size)类似的东西。 或者子类化 stream 并使用计数器覆盖read()以在读取大小后立即返回-1

您还可以编写一个临时文件并将其返回为 stream - 这将阻止最终用户通过原始文件的 FileInputStream 的反射找到文件名。

我写了一个实用程序 class ,您可以像这样使用它:

try(FileChannel channel = FileChannel.open(file, READ);
    InputStream input = new PartialChannelInputStream(channel, start, start + size)) {

    thirdPartyMethod(input);
}

它使用 ByteBuffer 读取文件的内容,因此您可以控制 memory 占用空间。

import java.io.IOException;
import java.io.InputStream;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class PartialChannelInputStream extends InputStream {

    private static final int DEFAULT_BUFFER_CAPACITY = 2048;

    private final FileChannel channel;
    private final ByteBuffer buffer;
    private long position;
    private final long end;

    public PartialChannelInputStream(FileChannel channel, long start, long end)
            throws IOException {
        this(channel, start, end, DEFAULT_BUFFER_CAPACITY);
    }

    public PartialChannelInputStream(FileChannel channel, long start, long end, int bufferCapacity)
            throws IOException {
        if (start > end) {
            throw new IllegalArgumentException("start(" + start + ") > end(" + end + ")");
        }

        this.channel = channel;
        this.position = start;
        this.end = end;
        this.buffer = ByteBuffer.allocateDirect(bufferCapacity);
        fillBuffer(end - start);
    }

    private void fillBuffer(long stillToRead) throws IOException {
        if (stillToRead < buffer.limit()) {
            buffer.limit((int) stillToRead);
        }
        channel.read(buffer, position);
        buffer.flip();
    }

    @Override
    public int read() throws IOException {
        long stillToRead = end - position;
        if (stillToRead <= 0) {
            return -1;
        }

        if (!buffer.hasRemaining()) {
            buffer.flip();
            fillBuffer(stillToRead);
        }

        try {
            position++;
            return buffer.get();
        } catch (BufferUnderflowException e) {
            // Encountered EOF
            position = end;
            return -1;
        }
    }
}

上面的这个实现允许创建多个从同一个FileChannel读取的PartialChannelInputStream并同时使用它们。
如果这不是必需的,下面的简化代码直接采用Path

import static java.nio.file.StandardOpenOption.READ;

import java.io.IOException;
import java.io.InputStream;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;

public class PartialFileInputStream extends InputStream {

    private static final int DEFAULT_BUFFER_CAPACITY = 2048;

    private final FileChannel channel;
    private final ByteBuffer buffer;
    private long stillToRead;

    public PartialChannelInputStream(Path file, long start, long end)
            throws IOException {
        this(channel, start, end, DEFAULT_BUFFER_CAPACITY);
    }

    public PartialChannelInputStream(Path file, long start, long end, int bufferCapacity)
            throws IOException {
        if (start > end) {
            throw new IllegalArgumentException("start(" + start + ") > end(" + end + ")");
        }

        this.channel = FileChannel.open(file, READ).position(start);
        this.buffer = ByteBuffer.allocateDirect(bufferCapacity);
        this.stillToRead = end - start;
        fillBuffer();
    }

    private void fillBuffer() throws IOException {
        if (stillToRead < buffer.limit()) {
            buffer.limit((int) stillToRead);
        }
        channel.read(buffer);
        buffer.flip();
    }

    @Override
    public int read() throws IOException {
        if (stillToRead <= 0) {
            return -1;
        }

        if (!buffer.hasRemaining()) {
            buffer.flip();
            fillBuffer();
        }

        try {
            stillToRead--;
            return buffer.get();
        } catch (BufferUnderflowException e) {
            // Encountered EOF
            stillToRead = 0;
            return -1;
        }
    }

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

@neXus 的 PartialFileInputStream class 的一个小修复,在 read() 方法中,您需要确保字节值 0xff 不会返回为 -1。

return buffer.get() & 0xff;

成功了。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM