[英]How can I create constrained InputStream to read only part of the file?
[英]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;
}
}
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.