天天看點

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

背景

公司做的一個氣象資料顯示項目,其中涉及到很多原始格式的資料解析,比如格點氣象資料,内部資料一般就是二維數組,在存儲的時候,一般采用二進制方式進行存儲。

格點資料的本質,可以了解成一個圖檔,每一個像素點上有一個資料值。

任務

我接手做這個解析工作,就是要将二進制格式的資料,轉化為更為通用的文本格式,友善檢視和顯示。

另外一項附加任務,則是因為原始資料的密度較高,是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跳過的長度也會不夠。

所謂基礎不牢,地動山搖,加強學習加強基礎很重要!