1. 程式人生 > 實用技巧 >我真傻,真的,我單知道...

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

背景

公司做的一個氣象資料顯示專案,其中涉及到很多原始格式的資料解析,比如格點氣象資料,內部資料一般就是二維陣列,在儲存的時候,一般採用二進位制方式進行儲存。

格點資料的本質,可以理解成一個圖片,每一個畫素點上有一個數據值。

任務

我接手做這個解析工作,就是要將二進位制格式的資料,轉化為更為通用的文字格式,方便檢視和顯示。
另外一項附加任務,則是因為原始資料的密度較高,是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跳過的長度也會不夠。
所謂基礎不牢,地動山搖,加強學習加強基礎很重要!