![](/img/trans.png)
[英]Reading text file from host system and docker container yields different results
[英]Reading File from Windows and Linux yields different results (character encoding?)
目前我正在嘗試以 mime 格式讀取文件,該文件具有 png 的一些二進制字符串數據。
在 Windows 中,讀取文件給了我正確的二進制字符串,這意味着我只需復制字符串並將擴展名更改為 png 即可看到圖片。
在 Windows 中讀取文件后的示例如下:
--fh-mms-multipart-next-part-1308191573195-0-53229
Content-Type: image/png;name=app_icon.png
Content-ID: "<app_icon>"
content-location: app_icon.png
‰PNG
等等……等等……
在 Linux 中讀取文件后的示例如下:
--fh-mms-multipart-next-part-1308191573195-0-53229
Content-Type: image/png;name=app_icon.png
Content-ID: "<app_icon>"
content-location: app_icon.png
�PNG
等等……等等……
我無法將 Linux 版本轉換為圖片,因為它都變成了一些帶有很多顛倒“?”的時髦符號。 和“1/2”符號。
任何人都可以啟發我了解正在發生的事情並提供解決方案嗎? 已經玩了一個星期以上的代碼了。
�
是三個字符的序列- 0xEF
0xBF
0xBD
,且是Unicode碼點的UTF-8表示0xFFFD
。 代碼點本身是非法 UTF-8 序列的替換字符。
顯然,出於某種原因,您的源代碼(在 Linux 上)中涉及的一組例程不准確地處理 PNG 標頭。 PNG 標頭以字節0x89
開頭(后跟0x50
、 0x4E
、 0x47
),在 Windows 中正確處理(可能將文件視為 CP1252 字節序列)。 在CP1252中, 0x89
字符顯示為‰
。
但是,在 Linux 上,該字節由 UTF-8 例程(或認為可以將文件作為 UTF-8 序列處理的庫)進行解碼。 由於 0x89 本身不是 ASCII-7 范圍內的有效代碼點(參考: UTF-8 編碼方案),因此它無法映射到 0x00-0x7F 范圍內的有效 UTF-8 代碼點。 此外,它無法映射到表示為多字節 UTF-8 序列的有效代碼點,因為所有多字節序列都以至少 2 位設置為 1 ( 11....
) 開始,並且由於這是文件的開頭,它也不能是連續字節。 由此產生的行為是 UTF-8 解碼器,現在用 UTF-8 替換字符0xEF
0xBF
0xBD
替換0x89
(多么愚蠢,考慮到文件不是 UTF-8 開始),它將顯示在ISO-8859 中-1為�
。
如果你需要解決這個問題,你需要在 Linux 中確保以下幾點:
0xFFFD
實際上是菱形字符編輯器/查看器已編寫)。*顯然,如果將字節序列轉換為字符或字符串對象,Java 運行時會將字節序列解碼為 UTF-16 代碼點。
在 Java 中, String
≠ byte[]
。
byte[]
表示原始二進制數據。String
表示文本,它具有相關的字符集/編碼,以便能夠分辨出它代表哪些字符。二進制數據≠文本。
String
文本數據具有 Unicode/UTF-16 作為字符集/編碼(或序列化時的 Unicode/mUTF-8)。 每當您從不是String
轉換為String
或反之亦然時,您需要為非String
文本數據指定一個字符集/編碼(即使您使用平台的默認字符集隱式執行此操作)。
PNG 文件包含表示圖像(和相關元數據)的原始二進制數據,而不是文本。 因此,您不應將其視為文本。
\\x89PNG
不是文本,它只是用於識別 PNG 文件的“神奇”標題。 0x89
甚至不是一個字符,它只是一個任意的字節值,它唯一合理的顯示表示是\\x89
, 0x89
,...同樣, PNG
在現實中存在二進制數據,它也可能是0xdeadbeef
它不會改變什么。 PNG
恰好是人類可讀的這一事實只是一種方便。
您的問題來自這樣一個事實,即您的協議混合了文本和二進制數據,而 Java(與其他一些語言,如 C)對二進制數據的處理方式與文本不同。
Java 提供*InputStream
用於讀取二進制數據,以及*Reader
用於讀取文本。 我看到兩種處理輸入的方法:
String
。InputStreamReader
在頂部InputStream
的,獲得InputStream
直接當你想二進制數據,訪問InputStreamReader
當你想要的文字。 您可能需要緩沖,將它放在第二種情況下的正確位置在*Reader
下方。 如果您使用BufferedReader
,則BufferedReader
可能會比它應該消耗更多的InputStream
輸入。 所以,你會有類似的東西:
┌───────────────────┐
│ InputStreamReader │
└───────────────────┘
↓
┌─────────────────────┐
│ BufferedInputStream │
└─────────────────────┘
↓
┌─────────────┐
│ InputStream │
└─────────────┘
您將使用InputStreamReader
讀取文本,然后使用BufferedInputStream
從同一流中讀取適當數量的二進制數據。
一個有問題的情況是將"\\r"
(舊 MacOS)和"\\r\\n"
(DOS/Windows)都識別為行終止符。 在這種情況下,您最終可能會過多地閱讀一個字符。 您可以采用已棄用的DataInputStream.readline()
方法采用的方法:將內部InputStream
透明地包裝到PushbackInputStream
並取消DataInputStream.readline()
該字符。
但是,由於您似乎沒有Content-Length ,我會推薦第一種方法,將所有內容都視為二進制,並僅在閱讀整行后轉換為String
。 在這種情況下,我會將 MIME 分隔符視為二進制數據。
輸出:
由於您正在處理二進制數據,因此您不能只是println()
它。 PrintStream
具有可以處理二進制數據的write()
方法(例如:用於輸出到二進制文件)。
或者,您的數據可能必須在將其視為文本的通道上傳輸。 Base64專為這種情況而設計(將二進制數據作為 ASCII 文本傳輸)。 Base64 編碼形式僅使用 US_ASCII 字符,因此您應該能夠將它與作為 US_ASCII 超集(ISO-8859-*、UTF-8、CP-1252...)的任何字符集/編碼一起使用。 由於您正在將二進制數據轉換為文本/從文本轉換,Base64 的唯一合理的 API 是這樣的:
String Base64Encode(byte[] data);
byte[] Base64Decode(String encodedData);
這基本上是內部java.util.prefs.Base64
使用的內容。
結論:
在 Java 中, String
≠ byte[]
。
二進制數據≠文本。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.