我真傻,真的,我單知道...
背景
公司做的一個氣象資料顯示專案,其中涉及到很多原始格式的資料解析,比如格點氣象資料,內部資料一般就是二維陣列,在儲存的時候,一般採用二進位制方式進行儲存。
格點資料的本質,可以理解成一個圖片,每一個畫素點上有一個數據值。
任務
我接手做這個解析工作,就是要將二進位制格式的資料,轉化為更為通用的文字格式,方便檢視和顯示。
另外一項附加任務,則是因為原始資料的密度較高,是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跳過的長度也會不夠。
所謂基礎不牢,地動山搖,加強學習加強基礎很重要!