我真傻,真的,我单知道...

背景

公司做的一个气象数据显示项目,其中涉及到很多原始格式的数据解析,比如格点气象数据,内部数据一般就是二维数组,在存储的时候,一般采用二进制方式进行存储。

格点数据的本质,可以理解成一个图片,每一个像素点上有一个数据值。

任务

我接手做这个解析工作,就是要将二进制格式的数据,转化为更为通用的文本格式,方便查看和显示。
另外一项附加任务,则是因为原始数据的密度较高,是1公里x1公里的密度,其纵向有435公里,横向有355公里,总共涉及435x355=154425个点,由于前端优化不太给力,只能对密度降级,变成5公里x5公里的密度。

解析代码

所谓二进制数据,就是将数据一个一个往后面码,所以代码也不难,劈里啪啦一阵敲,就完成了:

// InputStream in; 流对象
float[][] data = new float[HEIGHT][WIDTH];//使用float数组接收数据
byte[] buf = new byte[2];//buf
BufferedInputStream bin = new BufferedInputStream(in);//使用缓存流对象
for(int y=0; y<HEIGHT; y++) {//从左上,逐行读取
    for(int x=0; x<WIDTH; x++) {
          data[y][x] = read(bin, buf);//读取一个数据
    }
    skip(bin,WIDTH*5*2*4);//往下跳过4行
}

问题

数据本来的样子是:
显示效果

我解析出来之后,将结果放入到显示界面中查看,解析的结果最终出来却是条纹状的数据。

而令人崩溃的是:
但是如果我没有进行密度降级的时候,又是正常的(将原始宽度、高度逐一解析,而不进行跳行)。

过程

中间是一个痛苦的试错过程,我尝试打印当前流的位置,因为这个形状看起来像是一个错位导致的,然而我通过一个position去记录,发现是正常的。

转机

在通过的过程中,我时不时的告诉自己,这一定是哪些写的有问题。
一个偶然的想法,我在创建缓存流的时候,加入一个参数size=3550(刚好是5行的大小)

BufferedInputStream bin = new BufferedInputStream(in, 3550);

诶!

这就是我想要的。
让我不禁想起那首歌——《我的滑板鞋》。

原因

其实很简单,问题就出现在

skip(bin,WIDTH*5*2*4);//往下跳过4行

而我是这样写的(这样写,是为了避免抛出checkedException)

try {
      inputStream.skip(n);
}catch (Exception ex){
      throw new RuntimeException("reading data error:", ex);
}

也就是我以为,这个skip会确保真实跳过所需要的字节数,然后查到BufferedInputStream的skip方法

      public synchronized long skip(long n) throws IOException {
        this.getBufIfOpen();
        if (n <= 0L) {
            return 0L;
        } else {
            long avail = (long)(this.count - this.pos);
            if (avail <= 0L) {
                if (this.markpos < 0) {
                    return this.getInIfOpen().skip(n);
                }

                this.fill();
                avail = (long)(this.count - this.pos);
                if (avail <= 0L) {
                    return 0L;
                }
            }

            long skipped = avail < n ? avail : n;
            this.pos = (int)((long)this.pos + skipped);
            return skipped;
        }
    }

可以看出,BufferedInputStream并不会确保跳过所需要的字节数——如果所跳过的字节超过当前的缓存长度,则只会跳到当前缓存的末尾。

由此,我以为它跳到了X位置,实际上它还在原来的地方——所以,这也是为什么条纹状会出来的原因。

解决

知道原因,解决办法就比较多了

  • 调整参数方法:就是上面写上3550作为参数,确保刚好跳到指定的位置;
  • do-while循环确保:当小于跳过数时,继续往前跳
      do{
            n -= inputStream.skip(n);
      }while(n>0);
  • 不使用BufferedInputStream
    直接使用原始的FileInputStream读取并不会存在这个问题,当skip在最终的字节流上进行移动时,会真实有效。

最后采用:3550参数,同时为防止将来可能出问题,也做了do-while的判断。

总结

如标题所说,我真傻,真的,我单知道InputStream.read,可能会读取的长度可能会不够,可是我却不知道skip跳过的长度也会不够。
所谓基础不牢,地动山摇,加强学习加强基础很重要!

posted @ 2020-09-19 10:31  小彬  阅读(473)  评论(0编辑  收藏  举报