JAVA -- BufferdInputStream.mark(int readlimit)参数设置不生效

mark 和 reset

在说明问题之前先根据源码说明下 markreset 方法的用处:

mark


    /**
     * @param   readlimit   the maximum limit of bytes that can be read before
     *                      the mark position becomes invalid.
     * @see     java.io.BufferedInputStream#reset()
     */
    public synchronized void mark(int readlimit) {
        marklimit = readlimit;
        markpos = pos;
    }

@param readlimit 在标记失效前允许读取的最大字节数,readlimit 的含义也就是在 mark 位置之后,可以读取 readlimit 长度的字节数,之后标记点将不再生效,配合 reset 方法查看更清晰。

reset


    /**
     * <p>
     * If <code>markpos</code> is <code>-1</code>
     * (no mark has been set or the mark has been invalidated), an <code>IOException</code>
     * is thrown. Otherwise, <code>pos</code> is set equal to <code>markpos</code>.
     * @see        java.io.BufferedInputStream#mark(int)
     */
    public synchronized void reset() throws IOException {
        getBufIfOpen(); // Cause exception if closed
        if (markpos < 0)
            throw new IOException("Resetting to invalid mark");
        pos = markpos;
    }

marpos(在 mark 方法中赋值,即最近的标记位置) 是空时(未设置标记或者标记已经失效),直接抛出异常。否则,将该流读取的起始位置设置为之前的标记位置。

总结

其实综合两个方法来看,mark 在输入流中标记当前位置,方便 reset 之后能够从标记点位置开始重新获取到相同的信息。

问题描述

在使用 BufferdInputStream 用到 mark( int readlimit)reset() 方法, 但是 mark 中的参数无论设置多少,无论在设置标记之后读取多少字节,标记位置都不会失效,调用 reset 之后都将从最近的一次标记位置读取。即:我设置了 readlimit 为 0,然后读取了1024 个字节,markpos 仍然有效, reset 之后仍然可以实现从标记点重新读取信息。

实例及源码解析

问题复现

待码
//创建一个 7 个字节的字符串
String textTxt = "ABCDEFG";
//转换为 BufferedInputStream 
InputStream iStream = new ByteArrayInputStream(textTxt.getBytes(StandardCharsets.UTF_8));
BufferedInputStream bis = new BufferedInputStream(iStream);

// MRAK 在起始位置就打下标记
bis.mark(6);

//读取全部字节 已经超过了设置的 readLimit
int rd = bis.read();
while(rd!=-1){
    System.out.println("First Time: "+ (char)rd);
    rd = bis.read();
}

// RESET ,回到之前的标记位置
bis.reset();
int rd2 = bis.read();
System.out.println("Second Time:"+(char)rd2);

打印结果

First Time: A
First Time: B
First Time: C
First Time: D
First Time: E
First Time: F
First Time: G
Second Time:A


源码分析

从最后打印出来的结果可以看出,reset 之后,输入流从位置 0 重新读取信息,表示 mark 标记仍然有效。
但是在上面 markreset 的源码中除了对标记属性和当前位置的赋值外,没有看到任何其他对标记位置处理的逻辑。
所以我们要寻找其他的”可疑“代码,我们在代码中只用到了 markresetread,所以”嫌疑“理所当然的落在了 read 头上,查看 read 的代码

read()源码
    public synchronized int read() throws IOException {
        if (pos >= count) {
            fill();
            //当前位置大于等于字节数总量,返回-1
            if (pos >= count)
                return -1;
        }
        //读取下一个字节
        return getBufIfOpen()[pos++] & 0xff;
    }
明确问题点

我们查看代码中的 fill 方法

    private void fill() throws IOException {
        byte[] buffer = getBufIfOpen();
        if (markpos < 0)
            pos = 0;            /* no mark: throw away the buffer */
        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 */
            } else if (buffer.length >= MAX_BUFFER_SIZE) {
                throw new OutOfMemoryError("Required array size too large");
            } else {            /* grow buffer */
                int nsz = (pos <= MAX_BUFFER_SIZE - pos) ?
                        pos * 2 : MAX_BUFFER_SIZE;
                if (nsz > marklimit)
                    nsz = marklimit;
                byte nbuf[] = new byte[nsz];
                System.arraycopy(buffer, 0, nbuf, 0, pos);
                if (!bufUpdater.compareAndSet(this, buffer, nbuf)) {
                    // Can't replace buf if there was an async close.
                    // Note: This would need to be changed if fill()
                    // is ever made accessible to multiple threads.
                    // But for now, the only way CAS can fail is via close.
                    // assert buf == null;
                    throw new IOException("Stream closed");
                }
                buffer = nbuf;
            }
        count = pos;
        int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
        if (n > 0)
            count = n + pos;
    }

fill 方法中处理到了 markpos,那应该就是它没跑了,接下来就看一下它是怎么处理的。

正常的输入流读取方式

我们先看一下涉及 markpos 的逻辑片段,其实就是输入流读取字节的逻辑

//获取缓存buffer 承接从 InputStream 中读取到的字节
byte[] buffer = getBufIfOpen();
//没有设置过标记 那么就从起始位置开始读取字节
if (markpos < 0)
    pos = 0;
else if (pos >= buffer.length)  /* 缓存buffer空间不够了 */
	...
//count赋值  
count = pos;
//从pos 开始向后读取字节,读取长度为 buffer.length - pos 
int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
//最后实际读取了 n 个字节(因为可能剩余的字节不够 buffer.length - pos 个长度)
if (n > 0)
    //对count重新赋值 
    count = n + pos; 

后续在 read 逻辑中,如果当前位置没有超过 buffer 中字节总数的大小,将直接从 buffer 中读取字节

    public synchronized int read() throws IOException {
        if (pos >= count) {
            fill();
            //当前位置大于等于字节数总量,返回-1
            if (pos >= count)
                return -1;
        }
        //读取下一个字节
        return getBufIfOpen()[pos++] & 0xff;
    }
涉及 markpos 的处理逻辑
//1.当读取的位置大于等于缓存buffer的长度时,进入以下逻辑
else if (pos >= buffer.length) 
  if (markpos > 0) {  
  	  //2.1 存在标记点 且标记位置大于 0 
      //会从标记位置开始将缓存buffer中字节的信息后copy下来,在放到缓存buffer中
	  //并将起始位置修改为标记位置,标记位置修改为0,当前位置向前推 markpos 个位置
	  //总结来说 就是把 markpos 位置之前的字节都抛弃了
      int sz = pos - markpos;
      System.arraycopy(buffer, markpos, buffer, 0, sz);
      pos = sz;
      markpos = 0;
  } else if (buffer.length >= marklimit) {
  	  //2.2 markpos = 0 的情况 即在一开始就设置了标记位置,或者经过了2.1之后markpos 修改为0 并且buffer的长度大于等于marklimit 
      //将 markpos 和 pos 重置 即标记失效
      markpos = -1;  
      pos = 0;  
  } else if (buffer.length >= MAX_BUFFER_SIZE) {
  	  //2.3 buffer大小超过最大值 OOM
      throw new OutOfMemoryError("Required array size too large");
  } else { 
      //2.4 markpos = 0 的情况 当buffer的长度小于marklimit时 进行扩容
      //先找到 pos * 2 和 MAX_BUFFER_SIZE 中的最小值
      int nsz = (pos <= MAX_BUFFER_SIZE - pos) ?
              pos * 2 : MAX_BUFFER_SIZE;
      //在判断其与markLimit的最小值      
      if (nsz > marklimit)
          nsz = marklimit;
      //取最小值扩容
      byte nbuf[] = new byte[nsz];
      System.arraycopy(buffer, 0, nbuf, 0, pos);
      if (!bufUpdater.compareAndSet(this, buffer, nbuf)) {
          throw new IOException("Stream closed");
      }
      buffer = nbuf;
  }
  //count赋值  
count = pos;
//从pos 开始向后读取字节,读取长度为 buffer.length - pos 
int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
//最后实际读取了 n 个字节(因为可能剩余的字节不够 buffer.length - pos 个长度)
if (n > 0)
   //对count重新赋值 
   count = n + pos; 

问题处理

根据上面的源码分析可以看到,对于标记失效的关键逻辑在于:

//当前读取的位置大于等于buffer的长度时才会走这里的逻辑
else if (pos >= buffer.length) 
	...
	else if (buffer.length >= marklimit) {
      //在上面的逻辑中可以看到,当buffer.length<marklimit 时,buffer会扩容至大小等于marklimit
      //如果 buffer.length >= marklimit ==> pos >= marklimit
      //即读取内容超出了设置的标记有效的极限,将 markpos 和 pos 重置 即标记失效
      markpos = -1;  
      pos = 0;  
  }...
 

标记失效实现的条件是:

  1. 当前输入流读取的位置大于等于 buffer 的长度
  2. buffer 的长度大于等于 marklimit

我们在初始化时并没有给 BufferdInputStream 设置 buffer 的大小,所以将使用默认大小

private static int DEFAULT_BUFFER_SIZE = 8192;

1. 所以我们此时之只能在输入流读取了至少 8192 个字节之后,标记点才会失效。
2. 或者我们设置 reallimit 大于8192 并读取超过 reallimit 个字节,标记也会失效。
3. 我们可以修改 buffer 大小让他尽快进入失效逻辑,修改代码

//创建一个 7 个字节的字符串
String textTxt = "ABCDEFG";
//转换为 BufferedInputStream 
InputStream iStream = new ByteArrayInputStream(textTxt.getBytes(StandardCharsets.UTF_8));
//关键的修改 在这里设置 buffer的大小为5
BufferedInputStream bis = new BufferedInputStream(iStream,5);

// MRAK 在起始位置就打下标记 读取 6 个字节后 标记失效 buffer 会自动扩容至 6
bis.mark(6);

//读取7个字节 在读取第7个字节时 标记点会失效
int j =0;
int rd = bis.read();
while(rd!=-1 && j<6){
    System.out.println("First Time: "+ (char)rd);
    rd = bis.read();
    j++;
}

// RESET ,回到之前的标记位置(抛出异常)
bis.reset();
int rd2 = bis.read();
System.out.println("Second Time:"+(char)rd2);

此时在 reset 方法执行时将会抛出异常,标记失效:

First Time: A
First Time: B
First Time: C
First Time: D
First Time: E
First Time: F
First Time: G
Exception in thread "main" java.io.IOException: Resetting to invalid mark
	at java.io.BufferedInputStream.reset(BufferedInputStream.java:448)
	at com.dm.java.demo.Test.main(Test.java:35)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mingvvv

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值