Re: [問題] ObjectOutputStream + ImageIO 出現的問題

看板java作者 (偶爾想擺爛一下)時間14年前 (2010/01/02 14:57), 編輯推噓1(100)
留言1則, 1人參與, 最新討論串7/7 (看更多)
※ 引述《tkcn (小安)》之銘言: : 終於把這個問題弄清楚了。 : 讓我從實驗的步驟開始說明吧。 : --- : 1. 為了要方便觀察接收到的封包, : 所以我做了一張只有 1x1 的圖片, : 接著再用程式把這張圖片讀進去再重新寫成圖片。 : 這張圖片的 16 進位 data 如下: (69-byte) : 89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52 : 00 00 00 01 00 00 00 01 08 02 00 00 00 90 77 53 : DE 00 00 00 0C 49 44 41 54 78 DA 63 F8 FF FF 3F : 00 05 FE 02 FE 33 12 95 14 00 00 00 00 49 45 4E : 44 AE 42 60 82 : 2. 從傳送端用 ImageIO.write() 送出這張圖片, : 接收端會收到完全相同的 data。 : 3. 傳送端連續傳送兩次此圖片時, : 也只是相同的 data 重複兩次, : 並沒有看到我所預期的...多出來的 16-byte。 : 於是我又讓接收端用 ImageIO.read(), : 結果還是一樣,第 1 張沒問題,第 2 張就會失敗。 : 4. 實驗做到這裡其實就可以看出端倪了, : 剩下來都只是些驗證了,我就直接講結論吧。 : ImageIO.write() 會寫出 69 byte, : 而 ImageIO.read() 只會讀入 53 byte, : 我試著把原始的 png 檔案只留下前 53 byte,(或著是隨意竄改最後 16-byte) : 這個時候秀圖軟體是沒辦法開啟這個檔案的, : 但是用 ImageIO.read() 還是可以正常的讀入圖片。 : 我猜測最後的 16-byte 應該是 png 格式的 check sum 之類的東西吧, : 而 ImageIO.read() 並沒有做這樣的驗證,甚至資料連讀都沒讀 Orz : 有錯請指教,謝謝。 問題的癥結的確是因為 png plugin 提供的 ImageReader 沒有完整 consume png 數據所導致,造成傳送端與接收端在處理數據的順序/數量上的不匹配。 針對你之前的文章內容補充一些東西: ※ 引述《tkcn (小安)》之銘言: : ================================================================= : 我剛剛在測試的時候也有試過利用 ByteArrayOutputStream, : 不過沒有額外包成一個 class,結果還是失敗。 : 也有試過直接把 png file 的 data 丟進 ObjectOutputStream, : ( png file 當初也是用 ImageIO.write() 寫入的,並且我也在接收方檢查過 header ) : 結果接收方還是沒辦法用 ImageIO.read() 接下來。 O 版工提供給你的版本改善了不匹配的錯誤,其重點不在於有無包裝成 Serializable 來傳輸,假如你改用下列這幾個 method 在你的程式裡,也可以正確 運作(傳輸/接收的程序上與 O 版工的做法相同)。 public static void serializeImage(RenderedImage image, String format, DataOutput out) throws IOException { ByteArrayOutputStream deposite = new ByteArrayOutputStream(1024*1024); ImageIO.write(image, format, deposite); deposite.close(); byte[] data = deposite.toByteArray(); System.out.printf("[serializeImage] Image => %d bytes.%n", data.length); out.writeInt(data.length); out.write(data); } public static BufferedImage deserializeImage(DataInput in) throws IOException { int frameSize = in.readInt(); System.out.printf("[deserializeImage] frame size: %d bytes.%n", frameSize); byte[] data = new byte[frameSize]; in.readFully(data); return ImageIO.read(new ByteArrayInputStream(data)); } // 假如圖檔格式己經是你要的就不需要先 decode 成 BufferedImage public static void deserializeImageAndSave(DataInput in, File dest) throws IOException { int frameSize = in.readInt(); System.out.printf("[deserializeImage] frame size: %d bytes.%n", frameSize); byte[] data = new byte[frameSize]; in.readFully(data); FileOutputStream out = new FileOutputStream(dest); out.write(data); out.close(); } 重點在傳送/接收兩方在處理數據的順序/數量上要匹配。ObjectOutputStream 會 在 wrapped stream 加上 header,而 ObjectInputStream 建構時會消耗掉這個 header 部份,所以把 socket stream 包裝成 ObjectIOStream 不會導致不匹配 得情況。有無包裝成 ObjectIOStream 來使用在這個 case 裡不是重點之一。 另外,不同的圖檔格式是由不同的 plugin 提供不同的 ImageReader/Writer,其 行為不盡相同。假如你原來的程式改成使用 jpg 格式,一樣會發生接收端在讀取 第二張圖片時無法正確 decode 出 image,但是其原因與使用 png 格式時不同。 jpg plugin 提供的 ImageReader 在處理數據時有"過度消耗"的行為。(png ImageReader 是短缺消耗) 如果 jpg ImageReader 的 input(a ImageInputStream)只含有一個 image data, 其處理完第一個(也是最後一個) image 部份會因為遇到 EOF 而沒有消耗過多的 數據。(所以上面的 deserializeImage method 沒有受影響,會正確運作) 如果其 input 包含超過一個 image 的數據,ImageReader 在 decode 出第一個 image 時其已消耗的數據量已大於一個 image data。 而 ImageIO.read(...) methods 每次被調用時都是建立一個新的 ImageReader, 然後把指定的 ImageInputStream attach 到 ImageReader 來處理。這就類似你把 一個 InputStream wrap 進多個 BufferedInputStream 裡,然後依序從多個 BufferedInputStream 各讀取一部份的數據出來,你所得到的數據串接起來不會 等於原先 InputStream 所含的數據,因為每個 BufferedInputStream 一旦執行 過某個消耗 input 的操作後,其從 input 所消耗掉的數據量已超過可完成該操作 所需消耗的數據量。 那麼當你的程式接收端讀取了第一個 image 之後,實際上 socket stream 裡屬於 第二個 image 的數據有一部份被消耗掉了,所以第二次調用 ImageIO.read(...) method 時,再一次建立的 ImageReader 會判讀成 input 內的數據不構成一個 image。(這個 ImageReader 讀到的數據是從一個 image data 的中間段開始) 對於 jpg ImageReader 的應用,可以讓接收端在接收數據前先建構一個 ImageReader,整個接收程序只使用此單一 ImageReader object 來 decode 出 多個 image。至於 png ImageReader 的短缺消耗行為則無法以相同的做法來改善。 我個人覺得 JRE 內建的 png ImageReader 的此等行為應該都算是 bug 了。 ImageIO.read(InputStream) 是設計來讓 client code 方便地 decode image data,不需要處理 ImageReader 與各種參數上的設定。 它的確是可以從 stream 裡 decode 出 image,但是它沒有在 doc 裡明白 點出此操作會搞亂 stream 的狀態。 ImageIO.read(InputStream) doc 裡有強調 specified input stream 在此操作後 不會被 close(這是否暗示此 stream 接下來可用以讀取其他的數據?),但是目前 png ImageReader 的過少消耗行為等於讓 input stream 留在一個不可預期的狀態 而無法再被使用。 -- ※ 發信站: 批踢踢實業坊(ptt.cc) ◆ From: 218.173.128.149 ※ 編輯: sbrhsieh 來自: 218.173.128.149 (01/02 15:00)

01/02 18:27, , 1F
這樣設計有什麼好處..? 好怪?
01/02 18:27, 1F
文章代碼(AID): #1BFkvXaF (java)
討論串 (同標題文章)
文章代碼(AID): #1BFkvXaF (java)