Java:输入流标记限制

8
根据Java文档,InputStream类中mark方法的readlimit参数设置了“在标记位置变为无效之前可以读取的最大字节数”。我有一个名为sample.txt的文件,其内容为"hello"。我编写了以下代码:
import java.io.*;
public class InputStream{
 public static void main (String[] args) throws IOException {
  InputStream reader = new FileInputStream("sample.txt");
  BufferedInputStream bis = new BufferedInputStream(reader);
  bis.mark(1);
  bis.read();
  bis.read();
  bis.read();
  bis.read();
  bis.reset();
  System.out.println((char)bis.read());
 }
}

输出是"h"。但是如果在mark方法之后读取多个字节,难道不应该因为无效的reset方法调用而出现错误吗?
4个回答

5
我认为这可能是文档错误。
非参数文档对于 BufferdInputStream 是“请参见 InputStream 的 mark 方法的一般契约”, 这提示我 BufferedInputStream 表现不会有所不同,尽管参数文档并未提到。
而根据 InputStream 指定的普遍契约是
“readlimit 参数告诉此输入流在标记位置失效之前允许读取的字节数[...]如果从流中读取了超过 readlimit 字节,则不需要记住任何数据”。
换句话说,readlimit 是一个建议值;流可以承诺不足并超额交付。

你知道有哪些InputStream实现可以用来重现这种情况:如果我读取的字节数超过了使用mark()设置的字节数,就会抛出异常? - Enrico Giurin

3

如果你查看源代码,特别是fill()方法,你会发现(过了一会儿!)它只在绝对必要的情况下使标记无效,也就是说它比文档所示更加宽容。

...
else if (pos >= buffer.length)  /* no room left in buffer */
   if (markpos > 0) {  /* can throw away early part of the buffer */
     int sz = pos - markpos;
     System.arraycopy(buffer, markpos, buffer, 0, sz);
     pos = sz;
     markpos = 0;
   } else if (buffer.length >= marklimit) {
     markpos = -1;   /* buffer got too big, invalidate mark */
     pos = 0;        /* drop buffer contents */
     ....

默认的缓冲区大小相对较大(8K),因此在您的示例中不会触发失效。


1

观察BufferedInputStream的实现,JavaDocs(受保护的markpos字段)描述了标记位置的重要性:

[markpos 是] 最后一次调用 mark 方法时 pos 字段的值。

该值始终在 -1pos 范围内。如果输入流中没有标记位置,则该字段为 -1。如果输入流中有标记位置,则 buf[markpos] 是在 reset 操作后作为输入提供的第一个字节。如果 markpos 不是 -1,则从位置 buf[markpos]buf[pos-1] 的所有字节必须保留在缓冲区数组中(尽管它们可以移动到缓冲区数组中的另一个位置,并对 countposmarkpos 的值进行适当的调整);除非 posmarkpos 之间的差异超过 marklimit,否则不能丢弃它们。

希望这可以帮到您。请查看类中readreset和私有方法fill的定义,以了解它们如何相互关联。
简而言之,只有当该类检索更多数据以填充其缓冲区时,标记位置才会被考虑在内。如果读取的字节数超过了对mark的调用所允许的字节数,则它将被正确地使无效。因此,对read的调用不一定会触发公共JavaDoc注释中宣传的行为。

1
这似乎是一个微妙的错误。如果您减小缓冲区大小,您将会得到一个 IOException
public static void main(String[] args) throws IOException {
    InputStream reader = new ByteArrayInputStream(new byte[]{1, 2, 3, 4, 5, 6, 7, 8});
    BufferedInputStream bis = new BufferedInputStream(reader, 3);
    bis.mark(1);
    bis.read();
    bis.read();
    bis.read();
    bis.read();
    bis.reset();
    System.out.println((char)bis.read());
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接