记一次InputStream流读取不完整留下的惨痛教训
前言
首先,问问大家下面这段流读取的代码是否存在问题呢?
inputStream = ....
try {
// 根据inputStream的长度创建字节数组
byte[] arrayOfByte = new byte[inputStream.available()];
// 调用read 读取字节数组
inputStream.read(arrayOfByte, 0, arrayOfByte.length);
return new String(arrayOfByte);
}catch (Exception e){
e.printStackTrace();
}
实际上的确是有问题的,而且在线上环境结结实实的坑了我们一把。
问题回溯
- 在xx银行项目上,报了下面的一个错误信息,数组越界,如下图所示:
- 反编译jar包的代码,在如下位置用到了数组读取,根据=号切割为组数,如下图所示:
- 而这个切割的字符串,是调用
loadResource方法加载ORG_PATH_MAP得到,如下图所示:
- 我们再来看下
loadResource的代码:
- 这里的是加载
ORG_PATH_MAP.class文件的内容,这个文件虽然class,但是里面存储内容的格式如下:
zj=浙江分公司,sh=上海分公司,fz=福州分公司
在我们多次确认数据格式也没有问题以后,就陷入了沉思,大家有发现什么问题呢?
原因分析
我们就怀疑读取的时候是不是有问题,是不是读取得不完整导致得。
我们看了下InputStream类的javadoc:
- ****
available()
返回可以从此输入流读取(或跳过)的字节数的估计值 ,返回的不是整个数据的长度, 是这次read可读的长度。
InputStream的不同子类对InputStream.available()可能会有不同的实现,一些实现会返回当前可一次无阻塞读入的字节数,另一些实现会返回这个输入流可读入的字节总数, 因此应尽量避免使用该返回值作为开辟能容纳该输入流所有数据的缓冲大小依据。
int read(byte b[], int off, int len)
从输入流中读取最多len字节的数据到字节数组中。尝试读取最多len字节,但可能会读取更小的数字。实际读取的字节数以整数形式返回。
所以做了一个demo试了一下:
- 有问题的这个项目是用
AppClassLoader加载当前路径下的类,可以发现InputStream的实现类是JarURLInputStream
运行结果如下图,可能确实发现读少了。
小结: 在读物流时调用的是available方法,点击进入其源码发现其返回的是当前流可用长度(估计值),不是流的总长度。而在read方法读取流中数据到buffer中,但读取长度为1至buffer.length,若流结束或遇到异常则返回-1。也就是说当实际文件的长度超过此估计可用长度时也不会继续读,而是结束读取。从而导致读取的流并不完整。这很大程度取决于不同的实现。
解决方案
方案一:
public static byte[] streamToByteArray(InputStream in) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int n;
while (-1 != (n = in.read(buffer))) {
output.write(buffer, 0, n);
}
return output.toByteArray();
}
借助ByteArrayOutputStream,通过循环去读取流,直到读取完成,如果返回-1,表示全部读取完成。
方案二:
public static byte[] streamToByteArray(InputStream in) throws IOException {
byte[] bytes = new byte[bufferlength];
BufferedInputStream bis = new BufferedInputStream(is);
int length = bis.read(bytes, 0, bufferlength)
return bytes;
}
采用BufferedInputStream,它底层其实也是循环读取。
为什么测试没发现?
实际情况是我们这是一个公共jar,被不同的组件下载,有的组件放到classpath下通过AppClassloader加载,有的组件通过自定义的classLoader加载,开发测试我们都是用的自定义DynamicClassloader加载,它的InputStream的实现类是ByteInputStream,是没有发现问题的。
而本次是另外一个spark组件, 他们把jar 放到了classpath下 也就是用AppClassloader,最终用了JarURLInputStream读取,出现问题。
总结
- 在代码编写过程中,available()方法仅用于估算接收数据的总长度或数据块的长度,不要用于任何需要准确计算的场合,更不要用于开辟一个可以刚好容纳所有数据的缓冲区。
- 对于调用InputStream.read(…),务必进行循环调用,直至返回-1,无论输入数据源是网络数据还是本地文件。
在平时的开发过程中,还是需要注重细节,不然会出现意料不到的问题。
如果本文对你有帮助的话,请留下一个赞吧
更多技术干活和学习资料尽在个人公众号——JAVA旭阳
记一次InputStream流读取不完整留下的惨痛教训的更多相关文章
- InputStream流无法重复读取的解决办法
前言:今天工作的需要需要读取aws云上S3桶里面的PDF数据,第一步能够正常的获取PDF文件的InputStream流,然后,我为了测试使用了IOUtils.toString(is)将流System. ...
- 关于对inputstream流的复制
今天因为项目需要,获取到一个inputstream后,可能要多次利用它进行read的操作.由于流读过一次就不能再读了,所以得想点办法. 而InputStream对象本身不能复制,因为它没有实现Clon ...
- Java实现inputstream流的复制
获取到一个inputstream后,可能要多次利用它进行read的操作.由于流读过一次就不能再读了,而InputStream对象本身不能复制,而且它也没有实现Cloneable接口,所以得想点办法. ...
- java HttpServletRequest 重复流读取
在用reset接口的时候,常常会使用request.getInputStream()方法,但是流只能读取一次,一旦想要加上一个过滤器用来检测用户请求的数据时就会出现异常. 在过滤器中通过流读取出用 ...
- 多次使用图片物理路径提示占用进程,用流读取设置FileShare权限解决
//用流读取图片 清空之后再次关联地址 //设置FileShare.ReadWrite权限之后 不会提示占用进程异常 item是物理路径DiscussionImageList = new Interv ...
- java HttpServletRequest 重复流读取
在用reset接口的时候,常常会使用request.getInputStream()方法,但是流只能读取一次,一旦想要加上一个过滤器用来检测用户请求的数据时就会出现异常. 在过滤器中通过流读取出用 ...
- java IO流读取图片供前台显示
最近项目中需要用到IO流来读取图片以提供前台页面展示,由于以前一直是用url路径的方式进行图片展示,一听说要项目要用IO流读取图片感觉好复杂一样,但任务下达下来了,做为程序员只有选择去执行喽,于是找了 ...
- 解决InputStream中数据读取不完整问题
转载:https://blog.csdn.net/lilidejing/article/details/37913627 当需要用到InputStream获取数据时,这时就需要读取InputStrea ...
- 【java】之读取InputStream流
如这个文件 @Test public void test01() throws Exception{ InputStream in=new FileInputStream("c://test ...
- 转-servlet 获取 post body 体用流读取为空的问题
目前基于rest风格的很多API开始使用通过body data来传输来代替之前的key-value传输方式.在Java servlet或者springmvc中可以通过如下代码来获取并图片通过流方式传输 ...
随机推荐
- 第六章:Django 综合篇 - 1:配置 Django
Django项目的设置文件位于项目同名目录下,名叫settings.py.这个模块,集合了整个项目方方面面的设置属性,是项目启动和提供服务的根本保证. 一.简述 settings.py文件本质上是一个 ...
- 无需Steam的Proton,在你的Linux运行任意Windows游戏!
链接: https://pan.baidu.com/s/1QeJxj9_2aZPk2_uZMzpn9A 提取码: v6t6 包含的版本 Proton4.11 Proton4.2 Proton5.0 ...
- [题解] Atcoder ARC 142 D Deterministic Placing 结论,DP
题目 (可能有点长,但是请耐心看完,个人认为比官方题解好懂:P) 首先需要注意,对于任意节点i上的一个棋子,如果在一种走法中它走到了节点j,另一种走法中它走到了节点k,那么这两种走法进行完后,棋子占据 ...
- KTV和泛型(3)
泛型除了KTV,还有一个让人比较疑惑的玩意,而且它就是用来表达疑惑的:? 虽然通过泛型已经达到我们想要的效果了,例如: List<String> list = new ArrayList& ...
- paddle&蜜度 文本智能较对大赛经验分享(17/685)
引言 我之前参加了一个中文文本智能校对大赛,拿了17名,虽然没什么奖金但好歹也是自己solo拿的第一个比较好的名次吧,期间也学到了一些BERT应用的新视角和新的预训练方法,感觉还挺有趣的,所以在这里记 ...
- html点击a标签弹窗QQ聊天界面
以为很难.以为要第三方.谁知道不用.一句话的事情. 1 <a hfer="tencent://message/?uin=12345&Site=&Menu-=yes&qu ...
- jsp中使用Servlet查询SQLSERVER数据库中的表的信息,并且打印在屏幕上
jsp中使用Servlet查询SQLSERVER数据库中的表的信息,并且打印在屏幕上 1.JavaBean的使用 package com.zheng; public class BookBean { ...
- AI之强化学习、无监督学习、半监督学习和对抗学习
1.强化学习 @ 目录 1.强化学习 1.1 强化学习原理 1.2 强化学习与监督学习 2.无监督学习 3.半监督学习 4.对抗学习 强化学习(英语:Reinforcement Learning,简称 ...
- 题解 UVA439 骑士的移动 Knight Moves
前言 最近板子题刷多了-- 题意 一个 \(8\times 8\) 的棋盘,问马从起点到终点的最短步数为多少. \(\sf Solution\) 要求最短路径嘛,显然 bfs 更优. 读入 这个读入处 ...
- 三、Python语法介绍
三.Python语言介绍 3.1.了解Python语言 Python 是1989 年荷兰人 Guido van Rossum (简称 Guido)在圣诞节期间为了打发时间,发明的一门面向对象的解释性编 ...
