java -- 解决InputStream不可重复读的问题

作者:opLW

目录

1.InputStream不可重复度
2.解决办法
3.最终方案

1.InputStream不可重复度
  • 问题 最近在使用如下代码解析从网上读取的图片字节流时,遇到一个问题:SkImageDecoder::Factory returned null
    fun decodeBitmapFromStream(
          bufferedIns: BufferedInputStream,
          reqWidth: Int,
          reqHeight: Int
      ): Bitmap? {
          val options = BitmapFactory.Options().also {
          	it.inJustDecodeBounds = true
          }
          
          BitmapFactory.decodeStream(bufferedIns, null, options)
          
          with(options)  {
              inSampleSize = calculateSampleSize(options, reqWidth, reqHeight)
              inJustDecodeBounds = false
          }
    
          return BitmapFactory.decodeStream(bufferedIns, null, options)
      }
    
  • 原因 第一次取图片尺寸的时候bufferedIns这个BufferedInputStream被使用过了,再真正取图片的时候又使用了这个BufferedInputStream,此时流的起始位置已经被移动过了,所以导致的IOException。
  • 为什么字节流不可以重复读呢? 可以查看这篇文章 InputStream为什么不能被重复读取?

2.解决办法
  • 字节流就好比一根管道,只负责传输字节,如果数据被获取了,那么管道里就没有内容了。

  • 那么有什么解决办法呢?

    • 重复读取两次
      • 顾名思义。我们可以先打开一次数据流,获取图片的尺寸信息,然后再打开重新打开一次真正的生成bitmap。
    • 用ByteArrayOutputStream存着
      // 先用ByteArrayOutputStream存着
          val byteArrayOutputStream = ByteArrayOutputStream()
          val buffer = ByteArray(1024)
          var len: Int
          while (true) {
              len = inputStream.read(buffer)
              if ( len == -1 ) break
              byteArrayOutputStream.write(buffer, 0, len)
          }
          byteArrayOutputStream.flush()
      // 每次要使用时,进行转换
          val ins = ByteArrayInputStream(byteArrayOutputStream.toByteArray())
      
    • 总结 这两个方法各有缺陷。要么需要重新读,浪费时间,要么需要用数组装着,对于大文件来说(如图片)会浪费空间。 有没有更好的方法呢?
  • 使用InputStream的reset和mark方法

    • StackOverFlow 上找到了一个类似的问题。里面提到要将InputStream重置,需要使用到markreset这两个方法。

      • mark方法 InputStream的mark方法有一段注释如下
        Marks the current position in this input stream. A subsequent call to
        the reset method repositions this stream at the last marked
        position so that subsequent reads re-read the same bytes.
        
        大概意思是说调用mark方法后,会记录一个标记。接下来第一次调用到reset方法时,会从标记位开始读。
      • reset方法 将流的的标记位置设为上一次调用mark时流所在的位置,从而可以重新读取流的数据。
    • 于是在第一次读取图片大小时,进行了mark操作,并在读完之后进行了reset

          .....
          bufferedIns.mark(0)
          BitmapFactory.decodeStream(bufferedIns, null, options)
          bufferedIns.reset()
          .....
      

      可惜的是遇到了另外一个错误: java.io.IOException: Mark has been invalidated

    • 原来是mark方法有一个参数readlimit,这个参数决定了你在相邻的一组markreset之间可以读多少个字节。

      比如: mark(20) 表明你在下次reset之前还可以读取20个字节。如果读取超过了20个字节,这个mark标记就会无效,此时再调用reset就会报错。

      而且并不是所有的InputStream子类都支持通过这种方式重复读取数据,在调用mark方法之前,应该先调用markSupported(),该方法会返回一个bool表示是否支持重复读取。详细的解读可以看这篇文章输入流InputStream的reset()和mark()方法注意事项

    • 那么这个readlimit参数应该设置为多少呢?

      • 第一次读取流时,只要保证能够读取到含有图片的尺寸信息的数据即可,这要求对各种格式的图片的编码有一定的了解。由于目前暂时不知道,所以此路暂时不通…?
      • 如果设置的太小,会引起报错;如果设置的大一点,又不知道多大才合适。于是乎我们可以调用InputStream.available()方法,这个方法会返回一个预估值表示大概还有多少字节的数据可以读取

3.最终方案
  fun decodeBitmapFromStream(
        bufferedIns: BufferedInputStream,
        reqWidth: Int,
        reqHeight: Int
    ): Bitmap? {
        val options = BitmapFactory.Options().also {
        	it.inJustDecodeBounds = true
        }
        
        bufferedIns.mark(bufferedIns.available())
        BitmapFactory.decodeStream(bufferedIns, null, options)
        bufferedIns.reset()
        
        with(options)  {
            inSampleSize = calculateSampleSize(options, reqWidth, reqHeight)
            inJustDecodeBounds = false
        }

        return BitmapFactory.decodeStream(bufferedIns, null, options)
    }

万水千山总是情,麻烦手下别留情。
如若讲得有不妥,文末留言告知我,
如若觉得还可以,收藏点赞要一起。

opLW原创七言律诗,转载请注明出处

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值