指定字节数后停止读取输入流

Stop reading inputstream after specified number of bytes

提问人:broadbear 提问时间:9/14/2023 更新时间:9/14/2023 访问量:90

问:

有没有办法将输入流划分为块?例如,我正在向 BodyPublisher 发送一个输入流,我希望该 BodyPublisher 读取输入流的子集并停止,然后下一次迭代将读取输入流中的下一组字节。

我想问这个问题的另一种方法是,是否有一个输入流可以包装另一个输入流并在指定数量的字节后返回流的末尾?

它可能看起来像这样:

InputStream is = // my inputstream
while (bytesRead < numBytes) {
   SubsetInputStream subsetIs = new SubsetInputStream(is, chunkSize);
   ...
   .POST(HttpRequest.BodyPublishers.ofInputStream(() -> subsetIs));
   ...
   bytesRead += chunkSize;
}
java 输入流

评论

0赞 Progman 9/14/2023
是否可以选择读取数组中的字节并将其放入将用于 BodyPublisher 的 中?byte[]ByteArrayInputStream
0赞 broadbear 9/14/2023
我希望避免将数据读取到字节数组中的额外内存。

答:

2赞 Unmitigated 9/14/2023 #1

您可以读入一个字节数组并从中创建一个。ByteArrayInputStream

var chunk = new byte[chunkSize];
for (int read; (read = is.read(chunk)) != -1;) {
    var bais = new ByteArrayInputStream(chunk, 0, read);
    // use the ByteArrayInputStream
}

评论

0赞 broadbear 9/14/2023
我希望避免将块读取到字节数组中的额外内存。
3赞 Sören 9/14/2023 #2

Apache Commons IO 中有 BoundedInputStream,它完全可以满足您的需求:

InputStream is = // my inputstream
while (bytesRead < numBytes) {
   BoundedInputStream subsetIs = new BoundedInputStream(is, chunkSize);
   ...
   .POST(HttpRequest.BodyPublishers.ofInputStream(() -> subsetIs));
   ...
   bytesRead += chunkSize;
}
3赞 rzwitserloot 9/14/2023 #3

自己做似乎微不足道:

import java.io.*;

public class LimitedInputStream extends FilterInputStream {
  private int limit;

  public LimitedInputStream(InputStream in, int limit) {
    super(in);
    if (in == null) throw new NullPointerException("in");
    if (limit < 1) throw new IllegalArgumentException("limit must be positive");
    this.limit = limit;
  }

  @Override public int read() throws IOException {
    if (limit < 1) return -1;
    limit--;
    return in.read();
  }

  @Override public int read(byte[] b, int off, int len) throws IOException {
    if (limit < 1) return -1;
    int lim = Math.min(len, limit);
    int r = in.read(b, off, lim);
    if (r > 0) limit -= r;
    return r;
  }

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

  @Override public long skip(long n) throws IOException {
    long lim = Math.min(n, limit);
    long r = in.skip(lim);
    if (r > 0) limit -= r;
    return r;
  }

  @Override public int available() throws IOException {
    return Math.min(in.available(), limit);
  }

  public static void main(String[] args) throws Exception {
    var raw = new ByteArrayInputStream(new byte[256]);
    var lim = new LimitedInputStream(raw, 100);
    byte[] b = new byte[55];
    System.out.println(lim.read(b)); // prints 55
    lim.read();
    lim.read();
    System.out.println(lim.read(b)); // prints 43
    System.out.println(lim.read(b)); // prints -1
  }
}

评论

0赞 broadbear 9/14/2023
这看起来还不错。为什么要扩展 FilterInputStream?
0赞 rzwitserloot 9/14/2023
FilterInputStream 在概念上是“一个本身不代表资源的流,相反,它环绕一个资源并修改其工作方式”。而这正是合适的。不过,在实施方面,它几乎没有给我们带来任何好处。其实,我写的那个方法?我认为您可以删除 ethat,这是 FilterInputStream 默认已经做的事情。close()
0赞 Reilas 9/14/2023 #4

"...有没有办法将输入流划分为块?..."

是的,尽管它不是语法的一部分。
您必须为每个段创建一个新的 ByteArrayInputStream 对象。

Java 框架通常围绕变和不可变状态运行,因此划分对象不是一个常见的功能。
虽然,有些类确实提供了此功能,例如 ByteBuffer 类。

不过,您可以使用您提供的伪代码。

我相信这里最简单的方法是使用 InputStream#read 方法。

此处的 bytesRead 不是累加的,而是按迭代计算的。

try (InputStream is = // my inputstream) {
    int bytesRead, chunkSize = 100;
    byte[] b = new byte[chunkSize];
    InputStream subsetIs;
    while ((bytesRead = is.read(b)) != -1) {
        subsetIs = new ByteArrayInputStream(b, 0, bytesRead);
        .POST(HttpRequest.BodyPublishers.ofInputStream(() -> subsetIs));
    }
}

"...我想问这个问题的另一种方法是,是否有一个输入流可以包装另一个输入流并在指定数量的字节后返回流的末尾?..."

您可以封装 InputStream,并重写读取方法,从那里委派数据。
如果您打算扩展代码,我建议您这样做。

否则,一旦到达流的末尾,读取方法将返回 −1
这是 JavaDoc 的摘录。

返回:
读入缓冲区的总字节数,如果由于已到达流的末尾而没有更多数据,则返回 -1。

评论

0赞 broadbear 9/14/2023
我希望避免使用将字节读入字节数组所需的额外内存。你所描述的关于封装 InputStream 的内容似乎是我将要采取的方向;这是@rzwitserloot提出的解决方案。
0赞 Reilas 9/14/2023
@broadbear,您每次仍然需要一个新对象。有一个 ofByteArray 方法。我建议切换到它,除非需要 InputStream