背景
公司做的一個氣象資料顯示項目,其中涉及到很多原始格式的資料解析,比如格點氣象資料,内部資料一般就是二維數組,在存儲的時候,一般采用二進制方式進行存儲。
格點資料的本質,可以了解成一個圖檔,每一個像素點上有一個資料值。
任務
我接手做這個解析工作,就是要将二進制格式的資料,轉化為更為通用的文本格式,友善檢視和顯示。
另外一項附加任務,則是因為原始資料的密度較高,是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行
}
問題
資料本來的樣子是:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIn5GcucDM3UDO1YjMwETL3EjM5QTNzkDM5ETOwAjMwITL4YTN1czLclDMwIDMy8CX4YTN1czLcd2bsJ2Lc12bj5ycn9Gbi52YuAjMwIzZtl2Lc9CX6MHc0RHaiojIsJye.png)
我解析出來之後,将結果放入到顯示界面中檢視,解析的結果最終出來卻是條紋狀的資料。
而令人崩潰的是:
但是如果我沒有進行密度降級的時候,又是正常的(将原始寬度、高度逐一解析,而不進行跳行)。
過程
中間是一個痛苦的試錯過程,我嘗試列印目前流的位置,因為這個形狀看起來像是一個錯位導緻的,然而我通過一個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跳過的長度也會不夠。
所謂基礎不牢,地動山搖,加強學習加強基礎很重要!