栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

文章目录

mark 和 reset

markreset总结 问题描述实例及源码解析

问题复现

待码打印结果 源码分析

read()源码明确问题点普通的输入流读取方式涉及 markpos 的处理逻辑 问题处理

mark 和 reset

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

mark
    
    public synchronized void mark(int readlimit) {
        marklimit = readlimit;
        markpos = pos;
    }

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

reset
    
    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 标记仍然有效。
但是在上面 mark 和 reset 的源码中除了对标记属性和当前位置的赋值外,没有看到任何其他对标记位置处理的逻辑。
所以我们要寻找其他的”可疑“代码,我们在代码中只用到了 mark 、reset 和 read,所以”嫌疑“理所当然的落在了 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;            
        else if (pos >= buffer.length)  
            if (markpos > 0) {  
                int sz = pos - markpos;
                System.arraycopy(buffer, markpos, buffer, 0, sz);
                pos = sz;
                markpos = 0;
            } else if (buffer.length >= marklimit) {
                markpos = -1;   
                pos = 0;        
            } else if (buffer.length >= MAX_BUFFER_SIZE) {
                throw new OutOfMemoryError("Required array size too large");
            } else {            
                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
byte[] buffer = getBufIfOpen();
//没有设置过标记
if (markpos < 0)
    pos = 0;
//count赋值  
count = pos;
//读取出从标记位置开始的buffer中的所有字节
int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
//读取了 n 个字节
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 的处理逻辑
//当前读取的位置大于等于buffer的长度时才会走这里的逻辑
else if (pos >= buffer.length) 
	//下面的逻辑是当读取的位置超过buffer中的字节总数时,buffer会继续向后获取字节
  if (markpos > 0) {  
	  
  
     //如果标记位置大于 0 时,会从标记位置开始获取之后的(buffer大小)个字节的信息
		 //计算从最近的标记点开始已经读取的字节数量
      int sz = pos - markpos;
      //buffer中的内容重新替换为从标记位置开始获取之后的(buffer大小)个字节的信息
      System.arraycopy(buffer, markpos, buffer, 0, sz);
      //当前位置变成了 sz
      pos = sz;
      //标记位置 重置为 0
      markpos = 0;
  } else if (buffer.length >= marklimit) {
      //如果buffer的长度大于等于marklimit 
      //将 markpos 和 pos 重置 即标记失效
      markpos = -1;  
      pos = 0;  
  } else if (buffer.length >= MAX_BUFFER_SIZE) {
  		// buffer大小超过最大值 OOM
      throw new OutOfMemoryError("Required array size too large");
  } else { 
      //当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;
  }
问题处理

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

//当前读取的位置大于等于buffer的长度时才会走这里的逻辑
else if (pos >= buffer.length) 
	...
	else if (buffer.length >= marklimit) {
      //如果buffer的长度大于等于marklimit 
      //将 markpos 和 pos 重置 即标记失效
      markpos = -1;  
      pos = 0;  
  }...
 

标记失效实现的条件是:

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

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

private static int DEFAULT_BUFFER_SIZE = 8192;

所以我们此时之只能在输入流读取了 8192 个字节之后,才会进入到失效逻辑中,或者我们设置 reallimit 大于8192 并读取超过 reallimit 个字节,标记也会失效。
然而,另一方面,我们可以修改 buffer 长度,修改代码

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

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

//读取全部字节 多读一个字节
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)
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/754992.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号