1. 程式人生 > >csv檔案下載出現亂碼

csv檔案下載出現亂碼

最近有個問題,下載csv檔案下來,用excel開啟的時候,出現亂碼,原因是編碼是gbk,用下文所說的用utf-8編碼,也不行,不知道是為什麼,可能是因為我的office設定主要語言是簡體中文的原因。用

OutputStreamWriter fos = new OutputStreamWriter(

                new FileOutputStream(new File("c://2.csv")), "UTF-16LE");

fos.write(0xFEFF);       

fos.write("你好 ");

fos.flush();

fos.close();

這種寫法正常,沒有出現亂碼。

BOM(Byte Order Mark),是 UTF編碼方案裡用於標識編碼的標準標記,在 UTF-16裡本來是 FF FE,變成 UTF-8就成了 EF BB BF。這個標記是可選的,因為 UTF8位元組沒有順序,所以它可以被用來檢測一個位元組流是否是 UTF-8編碼的。微軟做這種檢測,但有些軟體不做這種檢測,而把它當作正常字元處理。

微軟在自己的 UTF-8格式的文字檔案之前加上了 EF BB BF三個位元組 , windows上面的 notepad等程式就是根據這三個位元組來確定一個文字檔案是 ASCII的還是 UTF-8的 , 然而這個只是微軟暗自作的標記 , 其它平臺上並沒有對 UTF-8文字檔案做個這樣的標記。

也就是說一個 UTF-8檔案可能有 BOM,也可能沒有 BOM,那麼怎麼區分呢?三種方法。 1,用 UltraEdit-32開啟檔案,切換到十六進位制編輯模式,察看檔案頭部是否有 EF BB BF。 2,用 Dreamweaver開啟,察看頁面屬性,看“包括 Unicode簽名 BOM”前面是否有個勾。 3,用 Windows的記事本開啟,選擇 “另存為”,看檔案的預設編碼是 UTF-8還是 ANSI,如果是 ANSI則不帶 BOM。

下面是部分 BOM的介紹

在 UCS 編碼中有一個叫做 "ZERO WIDTH NO-BREAK SPACE"的字元,它的編碼是 FEFF。而 FFFE在 UCS中是不存在的字元,所以不應該出現在實際傳輸中。 UCS規範建議我們在傳輸位元組流前,先傳輸字元 "ZERO WIDTH NO-BREAK SPACE"。這樣如果接收者收到 FEFF,就表明這個位元組流是 Big-Endian的;如果收到 FFFE,就表明這個位元組流是 Little- Endian的。因此字元 "ZERO WIDTH NO-BREAK SPACE"又被稱作 BOM。

UTF-8不需要 BOM來表明位元組順序,但可以用 BOM來表明編碼方式。字元 "ZERO WIDTH NO-BREAK SPACE"的 UTF-8編碼是 EF BB BF。所以如果接收者收到以 EF BB BF開頭的位元組流,就知道這是 UTF-8編碼了。

Windows就是使用 BOM來標記文字檔案的編碼方式的。

最近處理一個xml檔案,用editplus看著一點問題沒有,但用程式處理就會報錯,錯誤資訊翻譯後如下:

屬性值中不能使用字元 '<'。處理資源 'file:///C:/Documents and Settings/renyang/桌面/BOM例項_utf-8.xml' 時出錯。第 28 行,位置: 13

但實際屬性值里根本沒有'<'啊,很是奇怪!猜測可能是字元問題,重寫xml檔案,內容和之前的一樣,在執行,就通過了。比較兩個xml檔案,發現錯誤的檔案前幾個位元組為“EF BB BF”,這就是表示utf8的bom,但xml檔案第一行卻是<?xml version="1.0" encoding="gbk"?>

由此可知問題原因
檔案實際為帶bom的utf-8編碼,但包括瀏覽器和讀xml的jar包等都是按照xml第一行<?xml version="1.0" encoding="gbk"?>中的gbk來讀取檔案的,由此可帶來一些不可預知的問題。

但為什麼editplus讀取就沒問題呢?
editplus等編輯器類軟體,會把所有檔案都當成文字檔案來處理,不會根據它是不是xml檔案而採用不同的處理方法(字符集檢測方法),所以它會按照bom的內容,即utf-8來讀取

utf8和utf-8?
一般軟體都會把這兩種編碼表示等同,但也遇到個別情況,比如ant,IE8等,它們只會認utf-8編碼,而對utf8報錯,說不認識,以後為了避免這些瑣碎,還是統一成utf-8吧(PS:大小寫都可以)

怎樣檢視檔案是否含有bom?
用程式讀取一個檔案的前幾個位元組,看是不是bom。更簡單的方法就是用一些高階的編輯器,比如utraedit的16進位制方式檢視,或是下面轉載blog中說的EmEdit中的另存為來檢視。

————————————————————————————————————————————

工作需要我用程式生成一個html檔案。
由於伺服器端使用apache+Tomcat來執行html和jsp檔案。
開始生成html檔案放在apache目錄下,頁面無法預設正常識別我頁面設定的編碼。

必須手動在瀏覽器上選擇Encoding->簡體中文(GB2312)才可以正常顯示。
這樣當然是不行了。
由於我們原來有一個頁面是可以正常顯示中文的,查看了一下,是UTF-8的格式,於是我也修改程式。
a.修改了頁面的編碼宣告:

b.修改了寫位元組流的一個方法:
public void htmlWrite(String charsetName) {
        try {
            out = new BufferedWriter(new OutputStreamWriter(
                        new FileOutputStream(outFileName), "UTF-8"));
            out.write(res);
            out.flush();

            if (out != null) {
                out.close();
            }
        } catch (Exception e) {
            try {
                if (out != null) {
                    out.close();
                }
            } catch (IOException e1) {
                System.out.print("write errors!" + e);
            }

            System.out.print("write errors!" + e);
        }
    }
這樣,我又生成了一個html,放在伺服器下面,可問題又來了,還是無法正常顯示,即瀏覽器無法預設識別為UTF-8的編碼方式。奇怪,使用EmEditor開啟,和好用的那個頁面對比。沒有任何問題。唯一的區別在於:
    我生成的那個html檔案被EmEditor認為UTF-8 with Signature。而好用的那個html檔案被EmEditor認為UTF-8 without Signature.
    對於這兩種UTF-8格式的轉換,我查看了網上資訊,點選記事本,EmEditor等文字編輯器的另存為,當選擇了UTF-8的編碼格式時,Add a Unicode Signature(BOM)這個選項被啟用,只要選擇上,我的檔案就可以存為UTF-8 with Signature的格式。可是,問題就在於,我用java怎麼讓我的檔案直接生成為 UTF-8 with Signature的格式。
    開始上google搜尋UTF-8 with Signature,BOM,Add a Unicode Signature等關鍵字。
http://www.unicode.org/unicode/faq/utf_bom.html#BOM
我大致瞭解了他們兩個的區別。
Q: What is a BOM?

A: A byte order mark (BOM) consists of the character code U+FEFF at the beginning of a data stream, where it can be used as a signature defining the byte order and encoding form, primarily of unmarked plaintext files. Under some higher level protocols, use of a BOM may be mandatory (or prohibited) in the Unicode data stream defined in that protocol.
http://mindprod.com/jgloss/bom.html
BOM
Byte Order Marks are special characters at the beginning of a Unicode file to indicate whether it is big or little endian, in other words does the high or low order byte come first. These codes also tell whether the encoding is 8, 16 or 32 bit. You can recognise Unicode files by their starting byte order marks, and by the way Unicode-16 files are half zeroes and Unicode-32 files are three-quarters zeros. Unicode Endian Markers
Byte-order mark Description
EF BB BF UTF-8
FF FE UTF-16 aka UCS-2, little endian
FE FF UTF-16 aka UCS-2, big endian
00 00 FF FE UTF-32 aka UCS-4, little endian.
00 00 FE FF UTF-32 aka UCS-4, big-endian.
There are also variants of these encodings that have an implied endian marker.
Unfortunately, often applications, even Javac.exe, choke on these byte order marks. Java Readers don't automatically filter them out. There is not much you can do but manually remove them.


http://cache.baidu.com/c?word=java%2Cbom&url=http%3A//tgdem530%2Eblogchina%2Ecom/&b=0&a=1&user=baidu
c、UTF的位元組序和BOM
UTF-8以位元組為編碼單元,沒有位元組序的問題。UTF-16以兩個位元組為編碼單元,在解釋一個UTF-16文字前,首先要弄清楚每個編碼單元的位元組序。例如收到一個“奎”的Unicode編碼是594E,“乙”的Unicode編碼是4E59。如果我們收到UTF-16位元組流“594E”,那麼這是“奎”還是“乙”?

Unicode規範中推薦的標記位元組順序的方法是BOM。BOM不是“Bill Of Material”的BOM表,而是Byte Order Mark。BOM是一個有點小聰明的想法:

在UCS編碼中有一個叫做"ZERO WIDTH NO-BREAK SPACE"的字元,它的編碼是FEFF。而FFFE在UCS中是不存在的字元,所以不應該出現在實際傳輸中。UCS規範建議我們在傳輸位元組流前,先傳輸字元"ZERO WIDTH NO-BREAK SPACE"。

這樣如果接收者收到FEFF,就表明這個位元組流是Big-Endian的;如果收到FFFE,就表明這個位元組流是Little-Endian的。因此字元"ZERO WIDTH NO-BREAK SPACE"又被稱作BOM。

UTF-8不需要BOM來表明位元組順序,但可以用BOM來表明編碼方式。字元"ZERO WIDTH NO-BREAK SPACE"的UTF-8編碼是EF BB BF(讀者可以用我們前面介紹的編碼方法驗證一下)。所以如果接收者收到以EF BB BF開頭的位元組流,就知道這是UTF-8編碼了。

Windows就是使用BOM來標記文字檔案的編碼方式的。


原來BOM是在檔案的開始加了幾個位元組作為標記。有了這個標記,一些協議和系統才能識別。好,看看怎麼加上這寫位元組。
終於在這裡找到了
http://mindprod.com/jgloss/encoding.html
UTF-8
8-bit encoded Unicode. neé UTF8. Optional marker on front of file: EF BB BF for reading. Unfortunately, OutputStreamWriter does not automatically insert the marker on writing. Notepad can't read the file without this marker. Now the question is, how do you get that marker in there? You can't just emit the bytes EF BB BF since they will be encoded and changed. However, the solution is quite simple. prw.write( '\ufeff' ); at the head of the file. This will be encoded as EF BB BF.
DataOutputStreams have a binary length count in front of each string. Endianness does not apply to 8-bit encodings. Java DataOutputStream and ObjectOutputStream uses a slight variant of kosher UTF-8. To aid with compatibility with C in JNI, the null byte '\u0000' is encoded in 2-byte format rather than 1-byte, so that the encoded strings never have embedded nulls. Only the 1-byte, 2-byte, and 3-byte formats are used. Supplementary characters, (above 0xffff), are represented in the form of surrogate pairs (a pair of encoded 16 bit characters in a special range), rather than directly encoding the character.

prw.write( '\ufeff' );就是這個。
於是我的程式碼變為:
public void htmlWrite(String charsetName) {
        try {
            out = new BufferedWriter(new OutputStreamWriter(
                        new FileOutputStream(outFileName), "UTF-8"));
            out.write('\ufeff');
            out.write(res);
            out.flush();

            if (out != null) {
                out.close();
            }
        } catch (Exception e) {
            try {
                if (out != null) {
                    out.close();
                }
            } catch (IOException e1) {
                System.out.print("write errors!" + e);
            }

            System.out.print("write errors!" + e);
        }
    }
問題解決。