IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> JAVA -- BufferdInputStream.mark(int readlimit)参数设置不生效 -> 正文阅读

[移动开发]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
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) {  
	  /**
	  * 当第一次read的长度等于buffer的长度时,会走这里面的逻辑
	  * 会将markpos重置为0
	  * 继续read时 markpos =0 ,则会进入后面的else逻辑
	  */
  
     //如果标记位置大于 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;  
  }...
 

标记失效实现的条件是:

  1. 当前输入流读取的位置大于等于 buffer 的长度
  2. 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)
  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-03-08 22:39:08  更:2022-03-08 22:40:36 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 16:54:16-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码