巴拉巴拉
小魔仙

InputStream缓存复用

Java中的Inputstream是不能重复读取,这是官方设计的问题。

InputStream实现了三个方法:

  public synchronized void mark(int paramInt) {}
  
  public synchronized void reset()
    throws IOException{
    throw new IOException("mark/reset not supported");
  }
  
  public boolean markSupported(){
    return false;
  }

其中,markSupported()方法是直接返回false,而mark()方法中是没有操作的
reset()方法更是直接抛出异常

现在有两种方式可以解决InputStream复用的问题

第一种是利用ByteArrayOutputStream进行缓存

我们写一个InputStreamCache对InputStream进行缓存,已达到我们进行复用的效果。

public class InputStreamCacher {
    /**
     * 将InputStream中的字节保存到ByteArrayOutputStream中。
     */
    private ByteArrayOutputStream byteArrayOutputStream = null;

    public InputStreamCacher(InputStream inputStream) {
        byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len;
        try {
            while ((len = inputStream.read(buffer)) > -1) {
                byteArrayOutputStream.write(buffer, 0, len);
            }
            byteArrayOutputStream.flush();
        } catch (IOException e) {
        }
    }

    public InputStream getInputStream() {
        return new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
    }
}

用法:

InputStreamCacher cacher = new InputStreamCacher(is);

InputStream stream1 = cacher.getInputStream();

InputStream stream2 = cacher.getInputStream();


看一下ByteArrayInputStream的代码:

  public boolean markSupported(){
    return true;
  }
  
  public void mark(int paramInt){
    mark = pos;
  }
  
  public synchronized void reset(){
    pos = mark;
  }

markSupported返回true,表示支持重置

但是mark中的参数paramInt并没有使用,输入任何值都可以

reset方法可以吧pos直接指向mark标记

所以,其实不需要每次都

InputStream stream1 = cacher.getInputStream();

像这样从缓存中获取ByteArrayInputStream,而是直接

stream1.reset();

先reset后再重新操作stream1就可以了,不需要反复的从缓存cacher里面获取getInputStream

这样会浪费内存。

使用InputStreamCache的缺点也是显而易见的,我们将整个流进行缓存,如果这个缓存很大,是很浪费内存的。

第二种方式,利用BufferedInputStream

BufferedInputStream是支持使用reset的,只需要先mark一下,之后进行reset操作即可。

BufferedInputStream bis = new BufferedInputStream(is);
bis.mark(Integer.MAX_VALUE);

设置的标记在重置之后允许读取的字节数是整数的最大值,即Integer.MAX_VALUE,这是为了能够复用整个流的全部内容。

当bis被操作完之后需要再次使用的时候,只要bis.reset();再进行操作即可。

如果不记得怎么使用,我们可以吧BufferedInputStream进行封装一下,写一个流复用类:

public class StreamReuse {
    private InputStream input;
    public StreamReuse(InputStream input) {
        if (!input.markSupported()) {
            this.input = new BufferedInputStream(input);
        } else {
            this.input = input;
        }
    }

    public InputStream getInputStream() {
        input.mark(Integer.MAX_VALUE);
        return input;
    }

    public void markUsed() throws IOException {
        input.reset();
    }
}

当流的接收者使用完流之后,需要显式地调用markUsed方法来发出通知,以完成对流的重置。

StreamReuse sr = new StreamReuse(is);

InputStream is = sr.getInputStream();

xxxxxxx当流被操作完后xxxxxxx

sr.markUsed();


看一下BufferedInputStream的代码:

  public synchronized void mark(int paramInt){
    marklimit = paramInt;
    markpos = pos;
  }
  
  public synchronized void reset()
    throws IOException{
    getBufIfOpen();
    if (markpos < 0) {
      throw new IOException("Resetting to invalid mark");
    }
    pos = markpos;
  }
  
  public boolean markSupported(){
    return true;
  }

 

赞(0) 打赏
如果文章对你有帮助,欢迎你来评价反馈。AgainFly » InputStream缓存复用

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
  • Q Q(选填)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏