Java中常见的编码集问题总结
作者:哪吒编程
一、遇到一个问题
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、控制台输出
这是什么鬼?
原来我的csv文件的编码集是带BOM的UTF-8,没听过啊,众里寻他千百度。
二、带有BOM的UTF-8
1、BOM
在UCS 编码中有一个叫做 “Zero Width No-Break Space” ,中文译名作“零宽无间断间隔”的字符,它的编码是 FEFF。而 FEFF 在 UCS 中是不存在的字符,所以不应该出现在实际传输中。UCS 规范建议我们在传输字节流前,先传输字符 “Zero Width No-Break Space”。这样如果接收者收到 FEFF,就表明这个字节流是 Big-Endian 的;如果收到FFFE,就表明这个字节流是 Little- Endian 的。因此字符 “Zero Width No-Break Space” (“零宽无间断间隔”)又被称作 BOM。
2、UTF-8
UTF-8(8位元,Universal Character Set/Unicode Transformation Format)是针对Unicode的一种可变长度字符编码。它可以用来表示Unicode标准中的任何字符,而且其编码中的第一个字节仍与ASCII相容,使得原来处理ASCII字符的软件无须或只进行少部分修改后,便可继续使用。因此,它逐渐成为电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。
3、UTF-8 BOM
UTF-8 不需要 BOM 来表明字节顺序,但可以用 BOM 来表明编码方式。字符 “Zero Width No-Break Space” 的 UTF-8 编码是 EF BB BF。所以如果接收者收到以 EF BB BF 开头的字节流,就知道这是 UTF-8编码了。Windows 就是使用 BOM 来标记文本文件的编码方式的。
4、CSV文件乱码问题
类似WINDOWS自带的记事本等软件,在保存一个以UTF-8编码的文件时,会在文件开始的地方插入UTF-8 BOM头。记事本等编辑器通过它来识别这个文件是否以UTF-8编码(当然即便没有UTF-8 BOM头记事本也能通过其它方式正确识别UTF-8编码)。
三、编码解码
在计算机发展初期,美国等少数国家最先给自己的语言设置了一套编码,即ASCII。因为英语只有26个英文字母及一些常见的符号即可,因此只需要一个字节的 7 位(即 128 个整数)就能完全表示英文字符。
但随着计算机的发展,欧洲一些国家也需要为自己的语言设置一套编码,ASCII的128个字符不够用了,因此就产生了第二套编码类型 ISO-8859-1 ~ ISO-8859-15,其中使用最广泛的是ISO-8859-1,ISO-8859-1使用了一个字节的 8 位,可以表示256个字符。为了避免乱码问题,ISO-8859-1完全兼容ASCII,ISO-8859-1的前128位和ASCII完全一致,后128个字符才是ISO-8859-1自身新扩展的字符编码。
后来,中国也给汉语设置了一套编码,提出了适合汉语的编码集GB2312。GB2312包含了682个英文、字母等符号及常见的6763个简体中文,我勒个去,中华上下五千年,博大精深啊。
再后来,为了将简体中文和繁体中文兼容到字符集里,又发布了新的编码集GBK。GBK实际是GB2312的扩展,使用1个字节存储ASCII中的字符,使用2个字节存储一个中文汉字。
最后,为了给世界上的所有字符设置一套统一的编码集,出台了统一的字符集规范Unicode(国际标准字符集)。其中就包含最著名的UTF-8。
UTF-8是ASCII的超集,ASCII中每个字符的编码与UTF-8是完全一致的,因此当用UTF-8存储汉字或其他字符时,可能会使用2个、3个或4个字节。
- 1个字节:英文,数字,回车符,ASCII中的常用符号+、-、*、/等;
- 2个字节:个别特殊符号;
- 3个字节:常见汉字,在GBK中存在的汉字;
- 4个字节:中日韩等超大字符集里面的汉字;
在java.nio.charset.Charset类中,提供了一些编码及常用方法。
四、解决读取“带有BOM的UTF-8文件乱码”问题
1、读取文件编码集
按照给定的字符集存储文件时,在文件的最开头的三个字节中就有可能存储着编码信息,所以,基本的原理就是只要读出文件前三个字节,判定这些字节的值,就可以得知其编码的格式。其实,如果项目运行的平台就是中文操作系统,如果这些文本文件在项目内产生,即开发人员可以控制文本的编码格式。
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+"查看耗时开始"); 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+"查看耗时="+(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+",关闭流异常:", 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("文件的前四个字符==="+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中常见的编码集问题总结的详细内容,更多关于Java编码集问题的资料请关注脚本之家其它相关文章!