前言

首先,问问咱们下面这段流读取的代码是否存在问题呢?

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();
}

实践上的确是有问题的,而且在线上环境严严实实的坑了咱们一把。

问题回溯

  1. 在xx银行项目上,报了下面的一个错误信息,数组越界,如下图所示:

记一次InputStream流读取不完整留下的惨痛教训

  1. 反编译jar包的代码,在如下位置用到了数组读取,根据=号切开为组数,如下图所示:

记一次InputStream流读取不完整留下的惨痛教训

  1. 而这个切开的字符串,是调用loadResource办法加载ORG_PATH_MAP得到,如下图所示:

记一次InputStream流读取不完整留下的惨痛教训

  1. 咱们再来看下loadResource的代码:

记一次InputStream流读取不完整留下的惨痛教训

  1. 这里的是加载ORG_PATH_MAP.class文件的内容,这个文件尽管class,可是里边存储内容的格局如下:
zj=浙江分公司,sh=上海分公司,fz=福州分公司

在咱们屡次确认数据格局也没有问题今后,就陷入了沉思,咱们有发现什么问题呢?

原因分析

咱们就置疑读取的时候是不是有问题,是不是读取得不完好导致得。

咱们看了下InputStream类的javadoc:

  1. available()

回来能够从此输入流读取(或越过)的字节数的估计值 ,回来的不是整个数据的长度, 是这次read可读的长度。

InputStream的不同子类对InputStream.available()可能会有不同的完结,一些完结会回来当时可一次无阻塞读入的字节数,另一些完结会回来这个输入流可读入的字节总数, 因而应尽量防止运用该回来值作为拓荒能容纳该输入流一切数据的缓冲大小根据。

  1. int read(byte b[], int off, int len)

从输入流中读取最多len字节的数据到字节数组中。测验读取最多len字节,但可能会读取更小的数字。实践读取的字节数以整数形式回来。

所以做了一个demo试了一下:

记一次InputStream流读取不完整留下的惨痛教训

  • 有问题的这个项目是用AppClassLoader加载当时路径下的类,能够发现InputStream的完结类是JarURLInputStream

运行结果如下图,可能的确发现读少了。

记一次InputStream流读取不完整留下的惨痛教训

小结: 在读物流时调用的是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读取,呈现问题。

记一次InputStream流读取不完整留下的惨痛教训

总结

  1. 在代码编写过程中,available()办法仅用于估算接纳数据的总长度或数据块的长度,不要用于任何需求准确核算的场合,更不要用于拓荒一个能够刚好容纳一切数据的缓冲区。
  2. 关于调用InputStream.read(…),务必进行循环调用,直至回来-1,不管输入数据源是网络数据还是本地文件。

在平时的开发过程中,还是需求注重细节,不然会呈现意料不到的问题。

假如本文对你有帮助的话,请留下一个赞吧

本文正在参与「金石方案 . 瓜分6万现金大奖」