缓冲输入流中的标记读取限制有什么用?

5
我是一名有用的助手,我可以为您翻译文本。

我刚接触Java流,想要读取特定文件的内容并从开头开始读取。我已经创建了一个BufferedInputStream,但是关于BufferedInputStream.mark(int markLimit)方法的说明让我感到困惑。

说明如下:

public void mark(int readlimit)

此方法在输入流中标记一个位置,以便调用reset()方法时可以“重置”流。参数readlimit是设置标记后可以从流中读取的字节数,在调用reset()方法之前。例如,如果使用10个读取限制调用mark(),则在调用reset()方法之前从流中读取11个字节,则标记无效,并且不需要记住流对象实例中的标记。
请注意,此方法可以记住的字节数可能大于内部读取缓冲区的大小,并且不依赖于下属流是否支持标记/重置功能。
覆盖: FilterInputStream类中的mark方法
参数: readlimit - 标记无效之前可读取的字节数**
我的代码如下:
public class Test {
    public static void main(String[] args) throws IOException {

        File resource = new File("beforeFix.txt");          
        FileInputStream fileInputStream = new FileInputStream(resource);
        BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
        int i = bufferedInputStream.read();
        bufferedInputStream.mark(1);
        i = bufferedInputStream.read();
        i = bufferedInputStream.read();
        i = bufferedInputStream.read();
        i = bufferedInputStream.read();
        i = bufferedInputStream.read();
        i = bufferedInputStream.read();
        i = bufferedInputStream.read();
        i = bufferedInputStream.read();
        i = bufferedInputStream.read();
        i = bufferedInputStream.read();
        i = bufferedInputStream.read();
        i = bufferedInputStream.read();
        i = bufferedInputStream.read();
        i = bufferedInputStream.read();
        i = bufferedInputStream.read();
        i = bufferedInputStream.read();
        i = bufferedInputStream.read();
        i = bufferedInputStream.read();
        i = bufferedInputStream.read();
        i = bufferedInputStream.read();
        bufferedInputStream.reset(); 
        i = bufferedInputStream.read();
        i = bufferedInputStream.read();
        i = bufferedInputStream.read();
        i = bufferedInputStream.read();
        bufferedInputStream.reset();  
    }
}

在上面的代码中,我将marklimit设置为1,但根据文档,标记没有进入无效状态。
有没有人能用一个简单的例子清楚地解释一下设置这个变量的实际目的是什么?
提前感谢。

当我调用reset()时,它没有向我抛出任何异常。根据文档,在我读取第二个字节后,标记应该无效,因为我已将标记限制设置为1,但实际上并没有。那么这个mark()的目的是什么? - sarath kumar
请使用引用格式对引用的文本进行标记。请不要使用粗体,这会伤害我们的眼睛。 - user207421
5个回答

1
为了使重置功能起作用并返回您标记的位置,需要在内存中缓冲在标记后读取的数据。当您进行标记时指定的值是应为此保留的内存量。因此,如果您打算在调用重置之前读取100个字节,则您的缓冲区至少需要100个字节,这就是您需要使用标记的原因。
bufferedInputStream.mark(200);

... read no more than 200 bytes ...

bufferedInputStream.reset();  // reset back to marked position

更新

看起来mark的文档与实际行为不匹配。文档中写道:

the maximum limit of bytes that can be read before the mark position becomes invalid

然而,看起来应该是“最小限制”,或者至少底层实现不需要在读取限制超过时立即丢弃标记,如果它们仍然支持重置到标记位置。

在上面的示例(由我提供)中,我将标记限制设置为1,并读取了一个以上的字节。根据文档,在我读取第二个字节后,标记应无效,因为我已将标记限制设置为1,但它仍然有效。那么mark()的目的是什么? - sarath kumar
我觉得你在这里找到了文档中的一个错误,尽管文档似乎暗示一旦超过限制,重置应该失败,但实际情况并非如此。我认为他们本意是说“最小限制”而不是“最大限制”。 - john16384
“最小限制”没有意义。当然,在调用reset()之前,你并不需要读取那么多字节。 - Holger
@Holger 我认为这更有意义... 最大限制表示它也可以少于最大值;最小限制保证至少有那么多字节或更多,显然如果底层实现允许的话。 - john16384

1
通过使用指定的限制调用mark,您正在请求支持在读取到指定限制后重置的能力,而不是拒绝超出该限制的能力。规范清楚地说明:

但是,在调用reset之前从流中读取超过readlimit字节,则不需要记住任何数据。

“不需要”并不意味着“不允许”。规范只是说明了您不能总是期望正常工作,它并没有说明您可以期望总是失败。
BufferedInputStream的情况下,很容易解释其底层发生了什么。每个BufferedInputStream都有一个容量,默认为8192,并且它总是可以重置超过其当前缓冲区容量的字节数。通过指定更高的限制,您将导致它在需要时分配更大的缓冲区,以实现保证。
由于您无法查询流的当前缓冲区容量,因此只能依赖于保证,只要不读取超过指定限制的字节,重置就会起作用。
很容易更改您的示例以使其失败并可重现:
File resource = new File("beforeFix.txt");          
FileInputStream fileInputStream = new FileInputStream(resource);
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream, 1);
int i = bufferedInputStream.read();
bufferedInputStream.mark(1);
i = bufferedInputStream.read();
i = bufferedInputStream.read();
bufferedInputStream.reset(); // will fail

文档还说了这个:在标记位置变为无效之前可以读取的最大字节数限制,但并不总是会变为无效,这就是 OP 的问题所在。它并没有说“可能会变为无效”。 - john16384
@john16384:从概念上讲,标记无效的,因为您不能依赖它。另一个问题是,会出现哪些后果。reset()的文档清楚地说明:“如果……自上次调用mark以来从流中读取的字节数大于该最后调用时mark的参数,则可能抛出IOException。”您看,“可能”而不是“保证”… - Holger
好的,这是一个不错的发现。 - john16384

0
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;

public class Main {
    
    static final String fileAbsolutePath = "file.txt";
    
    public static void main(String[] args) {
        // file contains 3 chars 'a', 'b', and 'c'
        try (FileInputStream fileInputStream = new FileInputStream(fileAbsolutePath);
             BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream)) {
            
            System.out.println((char) bufferedInputStream.read()); // prints a
            bufferedInputStream.mark(2);   // place mark at second byte
            System.out.println((char) bufferedInputStream.read()); // prints b
            System.out.println((char) bufferedInputStream.read()); // prints c
            System.out.println(bufferedInputStream.read()); // prints -1
            
            bufferedInputStream.reset();    // meaning start again from where I placed the mark
            System.out.println((char) bufferedInputStream.read()); // prints b
            System.out.println((char) bufferedInputStream.read()); // prints c
            System.out.println( bufferedInputStream.read()); // prints -1
    
        } catch (IOException ie) { ie.printStackTrace();}
    }
}

0
请阅读以下文档以更好地理解它。我和你一样也有同样的疑问,然后决定仔细了解一下。
  1. 如果自创建流以来未调用过方法标记(mark),或者自上次调用标记(mark)以来从流中读取的字节数大于该调用时标记(mark)的参数,则可能会抛出IOException异常。
  2. 如果没有抛出此类IOException异常,则流将被重置为状态,使得自最近调用标记(mark)以来读取的所有字节(或自文件开始以来,如果未调用标记(mark))都将被提供给随后调用read方法的调用者,再加上任何在重置调用时作为下一个输入数据的其他字节。
从第一个要点可以看出,并不保证一定会抛出IOException异常。因此,如果调用标记(mark)方法后读取的字节数超过允许的字节数(即mark方法的指定参数),那么这是一种有风险的操作,不建议这样做。
从第二个要点可以了解到如果没有抛出IOException异常时会发生什么。

文档链接:https://docs.oracle.com/javase/8/docs/api/java/io/InputStream.html#reset--


0

正如Oracle文档所述,InputStream的reset()方法被FilterInputStream覆盖,进一步由BufferedInputStream覆盖。

reset的一般契约是:

  • 重置的一般协定是:

    -如果方法markSupported返回true,则:

      -如果自创建流以来尚未调用方法mark,或自上次调用mark以来从流中读取的字节数大于
        上次调用mark时的参数值,则可能会抛出IOException异常。
        (请注意,IOException可能被抛出)
    
      -如果没有抛出此类IOException,则流将被重置为状态,使得自最近一次调用mark(或者如果
        没有调用mark,则自文件开始)以来读取的所有字节都将供应给随后的read方法调用者,然
        后是任何在调用reset时作为下一个输入数据的其他字节。
    

希望你的问题已经解决,并且能够帮助未来的程序员。


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