Java中常见的编码集问题总结

作者:哪吒编程 时间:2023-11-29 01:40:04 

一、遇到一个问题

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、控制台输出

Java中常见的编码集问题总结

这是什么鬼?

Java中常见的编码集问题总结

原来我的csv文件的编码集是带BOM的UTF-8,没听过啊,众里寻他千百度。

二、带有BOM的UTF-8

1、BOM

在UCS 编码中有一个叫做 &ldquo;Zero Width No-Break Space&rdquo; ,中文译名作&ldquo;零宽无间断间隔&rdquo;的字符,它的编码是 FEFF。而 FEFF 在 UCS 中是不存在的字符,所以不应该出现在实际传输中。UCS 规范建议我们在传输字节流前,先传输字符 &ldquo;Zero Width No-Break Space&rdquo;。这样如果接收者收到 FEFF,就表明这个字节流是 Big-Endian 的;如果收到FFFE,就表明这个字节流是 Little- Endian 的。因此字符 &ldquo;Zero Width No-Break Space&rdquo; (&ldquo;零宽无间断间隔&rdquo;)又被称作 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 来表明编码方式。字符 &ldquo;Zero Width No-Break Space&rdquo; 的 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类中,提供了一些编码及常用方法。

四、解决读取&ldquo;带有BOM的UTF-8文件乱码&rdquo;问题

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();
   }
}

来源:https://juejin.cn/post/7200364375148167228

标签:Java,编码集,问题
0
投稿

猜你喜欢

  • Android RecyclerView使用GridLayoutManager间距设置的方法

    2023-03-20 07:32:44
  • Java如何实现Word文档分栏效果

    2023-09-11 00:28:53
  • java数据结构与算法之快速排序详解

    2023-02-23 10:23:43
  • SpringBoot如何用java生成静态html

    2023-08-08 08:46:59
  • Java 把json对象转成map键值对的方法

    2023-08-25 01:58:14
  • Android仿外卖购物车功能

    2023-06-01 02:45:26
  • jpa实现多对多的属性时查询的两种方法

    2022-01-14 00:47:45
  • 一篇文章带你使用C语言编写内核

    2022-01-12 12:57:48
  • Java编程Socket实现多个客户端连接同一个服务端代码

    2023-12-27 05:32:11
  • Java8中CompletableFuture的用法全解

    2023-09-08 15:08:55
  • Java数据结构学习之栈和队列

    2022-02-21 11:32:45
  • Android实现本地Service方法控制音乐播放

    2021-12-16 07:22:31
  • 教你怎么用idea创建web项目

    2021-12-23 14:45:16
  • c#数字图像处理的3种方法示例分享

    2021-10-09 09:56:51
  • SpringBoot之Helloword 快速搭建一个web项目(图文)

    2023-08-23 17:36:21
  • Activiti开发环境的搭建过程详解

    2021-08-16 07:13:05
  • java String的intern方法

    2021-07-05 03:23:52
  • java利用Future实现多线程执行与结果聚合实例代码

    2023-09-24 11:07:18
  • android桌面悬浮窗显示录屏时间控制效果

    2022-04-21 14:54:12
  • java对象转型实例分析

    2021-12-19 14:14:15
  • asp之家 软件编程 m.aspxhome.com