Java中常見的編碼集問題總結(jié)
一、遇到一個(gè)問題
1、讀取CSV文件
package com.guor.demo.charset; import java.io.BufferedReader; import java.io.FileReader; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class CommonUtils { public static List<Map<String, Object>> readCSVToList(String filePath) throws Exception { List<Map<String, Object>> list = new ArrayList<Map<String, Object>>(); BufferedReader reader = null; try { reader = new BufferedReader(new FileReader(filePath)); String[] headtilte = reader.readLine().split(","); String line = null; while ((line = reader.readLine()) != null) { HashMap<String, Object> hashMap = new HashMap<String, Object>(); String[] itemArray = line.split(","); for (int i = 0; i < itemArray.length; i++) { hashMap.put(headtilte[i], itemArray[i]); } list.add(hashMap); } } catch (Exception e) { e.printStackTrace(); } finally { if (null != reader) { reader.close(); } } return list; } public static void main(String[] args) throws Exception { String filePath = "H:\\CSDN\\netty\\編碼集問題\\test.csv"; System.out.println(readCSVToList(filePath)); } }
2、控制臺(tái)輸出
這是什么鬼?
原來我的csv文件的編碼集是帶BOM的UTF-8,沒聽過啊,眾里尋他千百度。
二、帶有BOM的UTF-8
1、BOM
在UCS 編碼中有一個(gè)叫做 “Zero Width No-Break Space” ,中文譯名作“零寬無間斷間隔”的字符,它的編碼是 FEFF。而 FEFF 在 UCS 中是不存在的字符,所以不應(yīng)該出現(xiàn)在實(shí)際傳輸中。UCS 規(guī)范建議我們?cè)趥鬏斪止?jié)流前,先傳輸字符 “Zero Width No-Break Space”。這樣如果接收者收到 FEFF,就表明這個(gè)字節(jié)流是 Big-Endian 的;如果收到FFFE,就表明這個(gè)字節(jié)流是 Little- Endian 的。因此字符 “Zero Width No-Break Space” (“零寬無間斷間隔”)又被稱作 BOM。
2、UTF-8
UTF-8(8位元,Universal Character Set/Unicode Transformation Format)是針對(duì)Unicode的一種可變長度字符編碼。它可以用來表示Unicode標(biāo)準(zhǔn)中的任何字符,而且其編碼中的第一個(gè)字節(jié)仍與ASCII相容,使得原來處理ASCII字符的軟件無須或只進(jìn)行少部分修改后,便可繼續(xù)使用。因此,它逐漸成為電子郵件、網(wǎng)頁及其他存儲(chǔ)或傳送文字的應(yīng)用中,優(yōu)先采用的編碼。
3、UTF-8 BOM
UTF-8 不需要 BOM 來表明字節(jié)順序,但可以用 BOM 來表明編碼方式。字符 “Zero Width No-Break Space” 的 UTF-8 編碼是 EF BB BF。所以如果接收者收到以 EF BB BF 開頭的字節(jié)流,就知道這是 UTF-8編碼了。Windows 就是使用 BOM 來標(biāo)記文本文件的編碼方式的。
4、CSV文件亂碼問題
類似WINDOWS自帶的記事本等軟件,在保存一個(gè)以UTF-8編碼的文件時(shí),會(huì)在文件開始的地方插入U(xiǎn)TF-8 BOM頭。記事本等編輯器通過它來識(shí)別這個(gè)文件是否以UTF-8編碼(當(dāng)然即便沒有UTF-8 BOM頭記事本也能通過其它方式正確識(shí)別UTF-8編碼)。
三、編碼解碼
在計(jì)算機(jī)發(fā)展初期,美國等少數(shù)國家最先給自己的語言設(shè)置了一套編碼,即ASCII。因?yàn)橛⒄Z只有26個(gè)英文字母及一些常見的符號(hào)即可,因此只需要一個(gè)字節(jié)的 7 位(即 128 個(gè)整數(shù))就能完全表示英文字符。
但隨著計(jì)算機(jī)的發(fā)展,歐洲一些國家也需要為自己的語言設(shè)置一套編碼,ASCII的128個(gè)字符不夠用了,因此就產(chǎn)生了第二套編碼類型 ISO-8859-1 ~ ISO-8859-15,其中使用最廣泛的是ISO-8859-1,ISO-8859-1使用了一個(gè)字節(jié)的 8 位,可以表示256個(gè)字符。為了避免亂碼問題,ISO-8859-1完全兼容ASCII,ISO-8859-1的前128位和ASCII完全一致,后128個(gè)字符才是ISO-8859-1自身新擴(kuò)展的字符編碼。
后來,中國也給漢語設(shè)置了一套編碼,提出了適合漢語的編碼集GB2312。GB2312包含了682個(gè)英文、字母等符號(hào)及常見的6763個(gè)簡體中文,我勒個(gè)去,中華上下五千年,博大精深啊。
再后來,為了將簡體中文和繁體中文兼容到字符集里,又發(fā)布了新的編碼集GBK。GBK實(shí)際是GB2312的擴(kuò)展,使用1個(gè)字節(jié)存儲(chǔ)ASCII中的字符,使用2個(gè)字節(jié)存儲(chǔ)一個(gè)中文漢字。
最后,為了給世界上的所有字符設(shè)置一套統(tǒng)一的編碼集,出臺(tái)了統(tǒng)一的字符集規(guī)范Unicode(國際標(biāo)準(zhǔn)字符集)。其中就包含最著名的UTF-8。
UTF-8是ASCII的超集,ASCII中每個(gè)字符的編碼與UTF-8是完全一致的,因此當(dāng)用UTF-8存儲(chǔ)漢字或其他字符時(shí),可能會(huì)使用2個(gè)、3個(gè)或4個(gè)字節(jié)。
- 1個(gè)字節(jié):英文,數(shù)字,回車符,ASCII中的常用符號(hào)+、-、*、/等;
- 2個(gè)字節(jié):個(gè)別特殊符號(hào);
- 3個(gè)字節(jié):常見漢字,在GBK中存在的漢字;
- 4個(gè)字節(jié):中日韓等超大字符集里面的漢字;
在java.nio.charset.Charset類中,提供了一些編碼及常用方法。
四、解決讀取“帶有BOM的UTF-8文件亂碼”問題
1、讀取文件編碼集
按照給定的字符集存儲(chǔ)文件時(shí),在文件的最開頭的三個(gè)字節(jié)中就有可能存儲(chǔ)著編碼信息,所以,基本的原理就是只要讀出文件前三個(gè)字節(jié),判定這些字節(jié)的值,就可以得知其編碼的格式。其實(shí),如果項(xiàng)目運(yùn)行的平臺(tái)就是中文操作系統(tǒng),如果這些文本文件在項(xiàng)目內(nèi)產(chǎn)生,即開發(fā)人員可以控制文本的編碼格式。
2、用指定的編碼格式讀取文件流,寫入文件流
(1)讀取CSV文件
public static List<String[]> readCSVToListByUnicode(String filePath) { FileInputStream fis = null; UnicodeInputStream uin = null; InputStreamReader in= null; try { //logger.info("讀取文件"+filePath+"編碼集"); List<String[]> list = new ArrayList<String[]>(); //URL url = new URL(filePath); File f = new File(filePath); fis = new FileInputStream(f); uin = new UnicodeInputStream(fis,enc); //如果是本地將url.openStream -> new FileInputStream(f) enc = uin.getEncoding(); // check and skip possible BOM bytes if (enc == null){ in = new InputStreamReader(uin); }else { in = new InputStreamReader(uin, enc); } long start = System.currentTimeMillis(); //logger.info("讀取文件"+filePath+"查看耗時(shí)開始"); BufferedReader reader = new BufferedReader(in); //BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("D:/tags.txt"),"utf-8")); //String tmp =reader.readLine(); String[] headtilte = reader.readLine().split(","); list.add(headtilte); String line = null; String[] itemArray = null; while ((line = reader.readLine()) != null) { itemArray = line.split(","); list.add(itemArray); } long end = System.currentTimeMillis(); //logger.info("讀取文件"+filePath+"查看耗時(shí)="+(end-start)+"毫秒"); return list; }catch (Exception e){ logger.info("讀取文件"+filePath+",異常:", e); return null; }finally { try { if(in!=null){ in.close(); } if(uin!=null){ uin.close(); } if(fis!=null){ fis.close(); } }catch (Exception e){ logger.info("讀取文件"+filePath+",關(guān)閉流異常:", e); } } }
(2)獲取文件編碼集
package com.guor.demo.utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStream; import java.io.PushbackInputStream; public class UnicodeInputStream extends InputStream { private static final Logger logger = LoggerFactory.getLogger(UnicodeInputStream.class); PushbackInputStream internalIn; boolean isInited = false; String defaultEnc; String encoding; private static final int BOM_SIZE = 4; public UnicodeInputStream(InputStream in, String defaultEnc) { internalIn = new PushbackInputStream(in, BOM_SIZE); this.defaultEnc = defaultEnc; } public String getDefaultEncoding() { return defaultEnc; } public String getEncoding() { if (!isInited) { try { init(); } catch (IOException ex) { IllegalStateException ise = new IllegalStateException("Init method failed."); ise.initCause(ise); throw ise; } } return encoding; } /** * Read-ahead four bytes and check for BOM marks. Extra bytes are * unread back to the stream, only BOM bytes are skipped. */ protected void init() throws IOException { if (isInited) { return; } byte bom[] = new byte[BOM_SIZE]; int n, unread; n = internalIn.read(bom, 0, bom.length); //logger.info("文件的前四個(gè)字符==="+bom[0]+","+bom[1]+","+bom[2]+","+bom[3]); if ( (bom[0] == (byte)0x00) && (bom[1] == (byte)0x00) && (bom[2] == (byte)0xFE) && (bom[3] == (byte)0xFF) ) { encoding = "UTF-32BE"; unread = n - 4; } else if ( (bom[0] == (byte)0xFF) && (bom[1] == (byte)0xFE) && (bom[2] == (byte)0x00) && (bom[3] == (byte)0x00) ) { encoding = "UTF-32LE"; unread = n - 4; } else if ( (bom[0] == (byte)0xEF) && (bom[1] == (byte)0xBB) && (bom[2] == (byte)0xBF) ) { encoding = "UTF-8"; unread = n - 3; } else if ( (bom[0] == (byte)0xFE) && (bom[1] == (byte)0xFF) ) { encoding = "UTF-16BE"; unread = n - 2; } else if ( (bom[0] == (byte)0xFF) && (bom[1] == (byte)0xFE) ) { encoding = "UTF-16LE"; unread = n - 2; } else { // Unicode BOM mark not found, unread all bytes encoding = defaultEnc; unread = n; } //System.out.println("read=" + n + ", unread=" + unread); if (unread > 0) { internalIn.unread(bom, (n - unread), unread); } isInited = true; } @Override public void close() throws IOException { //init(); isInited = true; internalIn.close(); } @Override public int read() throws IOException { //init(); isInited = true; return internalIn.read(); } }
以上就是Java中常見的編碼集問題總結(jié)的詳細(xì)內(nèi)容,更多關(guān)于Java編碼集問題的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java高級(jí)之HashMap中的entrySet()方法使用
這篇文章主要介紹了Java高級(jí)之HashMap中的entrySet()方法使用,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03java的MybatisPlus調(diào)用儲(chǔ)存過程的返回?cái)?shù)據(jù)問題
這篇文章主要介紹了java的MybatisPlus調(diào)用儲(chǔ)存過程的返回?cái)?shù)據(jù)問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12修改Springboot默認(rèn)序列化工具Jackson配置的實(shí)例代碼
這篇文章主要介紹了如何修改Springboot默認(rèn)序列化工具Jackson的配置,當(dāng)Spring容器中存在多個(gè)同類型的Bean時(shí),默認(rèn)情況下最后一個(gè)創(chuàng)建的Bean將作為首選Bean,文中通過代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2024-02-02Spring?Boot?集成Redisson實(shí)現(xiàn)分布式鎖詳細(xì)案例
這篇文章主要介紹了Spring?Boot?集成Redisson實(shí)現(xiàn)分布式鎖詳細(xì)案例,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的朋友可以參考一下2022-08-08