zoukankan      html  css  js  c++  java
  • Java 编码与字符(1)

    一、字符集介绍

    ANSIAmerican National Standards Institute。中文:美国国家标准学会

    不同国家的和地区为此制定了不同标准,由此产生了 GB2312、GBK、Big5、Shift_JIS 等各自的编码标准。这些使用 1 至 4 个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码。在简体中文Windows操作系统中,ANSI 编码代表 GBK 编码;在日文Windows操作系统中,ANSI 编码代表 Shift_JIS 编码。 不同 ANSI 编码之间互不兼容,当信息在国际间交流时,无法将属于两种语言的文字,存储在同一段 ANSI 编码的文本中。

    UNICODEUnicode是国际组织制定的可以容纳世界上所有文字和符号的字符编码方案。Unicode用数字0-0x10FFFF来映射这些字符,最多可以容纳1114112个字符,或者说有1114112个码位。码位就是可以分配给字符的数字。UTF-8UTF-16UTF-32都是将数字转换到程序数据的编码方案。

    例:

    Unicode中文“艺”字: 827A
    二进制的“艺”字编码:1000 0010 0111 1010
    UTF-8的中文编码规则: 1110xxxx 10xxxxxx 10xxxxxx
    UTF-8的“艺”字编码: 1110【1000】 10【0010】【01】 10【11】【1010】
    UTF-8的转码过程解析: 8对应的1000被填入第一字节剩余的4位。2对应的0010被填入第2字节剩余的前4位。7对应的0111被拆开,前2位01被填入第2字节的后两位,后2位1被填入第3字节的前2位。A对应的1010被填入第3字节的后4位。
    UTF-8的最终编码结果:11101000---对应E8;10001001---对应89;10111010---对应BA。所以最终的UTF-8编码就是%E8%89%BA
    UnicodeUTF-8的转换:Unicode16进制编码<-->对应的2进制编码<-->UTF-8规范的2进制编码<-->UTF-8规范的16进制编码
    也就是说假如在Java的底层JVM,由于采用的是Unicode编码字符集,对“艺”字的编码是827A。那么在网络传输的过程中,我们当然不能直接传输827A这个字符过去代表艺”这个汉字,而必须要转换成0,1这样的字节流,才能在网络中传输。
    所以说UTF-8是一种为了方便网路传输,节省传输数量,而对Unicode的字符集的字符编号进行转换,从定长的2个字节(16进制)转换成1~3个的变长字节(2进制)表示的转换格式。
    由于Unicode采用的是2个字节的编码方式,而UTF-8转换后可能是1~3个字节,所以同一个汉字,在Unicode中的编码和经UTF-8转换后的编码值肯定是不同的。就好像艺字的Unicode编码是827A,经转换后的3个字节是E889BA。
    所以说对于英文字符来说,采用UTF-8对Unicode编码转换后节省了一倍的传输成本(由定长的2个字节变长1个字节),但对于原本双字节的东亚字符来说,反而增加了成本,是原来的1.5倍。

    小结:

    ASCIIGB2312GBKGB18030Big5Unicode都是字符集的名称。它们定义了采用1~2个字节的编码规范,为每个字符赋予了一个独一无二的编号。这个编号就是我们所说的“字符编码”。

    Unicode字符集定义的字符编码并不适合直接通过网络传输表达,因为它们必须转换成像0101这样的二进制字节流传输。所以就出现了不同的转换规范实现方式:UTF-8,TF-16等。这些不同的转换规范转换后的编码值和Unicode是不同的。

    对于UTF-8来说,它采用变长字节表示所有Unicode字符,对于英文来说和ASCII兼容,对于东亚字符来说,是原来传输成本的1.5倍。所以采用UTF-8编码转换方式虽然有利于统一,但增加了中文等双字节字符的传输成本。

    UTF-8采用首字节的高位"1"的个数表示字符的编码长度。例如在Unicode的编码规范中:汉字的表示区间为U-00000800至U-0000FFFF对应的UTF-8的转换规则为:1110xxxx 10xxxxxx 10xxxxxx 首字节3个1代表这个字符的编码长度为3个字节。如果是2个1则表示2个字节

    在底层的平台中如JVM,采用的是Unicode字符集,当要把这些字符通过网络传输时,可以选择通过UTF-8或其他(例如GB2312)编码转换方式对要传输的字符编码进行转换。如果目的端也是采用Unicode字符集,那么UTF-8转换后的编码可以被正常识别并解码成最终对应的Unicode字符集编号。如果是非Unicode字符集平台则可能出现乱码(UTF-8中汉字的3个连续字节被解析成GB2312的2个连续字节,出现丢失)。所以推荐在传输的两端采用Unicode字符集编码,在传输方式上采用UTF-8转换方式。

    javac命令是以系统默认编码读入源文件,然后按Unicode进行编码的。(备注:每个文件都有自己的编码,javac命令按照默认的文件编码读入,但是在将.java文件转换成.class的过程中,javac会将所有的字符转化成unicode的格式保存。)

    在运行时JVM也是采用unicode编码的,并且默认输入和输出使用的都是操作系统的默认编码。也就是说在new String(bytes[,encode])中,系统认为输入的bytes是编码为encode的字节流(如果不指定encode,那么就是默认使用系统的编码方式),换句话说,如果按encode来翻译bytes才能得到正确的原始字符,这个字符最后要在java中保存,它还是要从这个encode转换成Unicode的。

    也就是说,假如我们需要从磁盘文件、数据库记录、网络传输一些字符,保存到Java的变量中,要经历由bytes-->encode字符-->Unicode字符的转换(例如new String(bytes, encode));而要把Java变量保存到文件、数据库或者通过网络传输,系统要做一个Unicode字符-->encode字符-->bytes的转换(例如String.getBytes([encode]))

     

     

    二、JAVA编码与new String(byte[],charset)

    JAVA采用Unicode字符集。

    即不管采用什么样的编解码,最终char和String都是Unicode编码

    这里需要理解两个函数

    1、             getBytes();

    getBytes()、getBytes(encoding)函数的作用是使用系统默认或者指定的字符集编码方式,将字符串编码成字节数组。这里的系统不是windows那个系统的GBK,而是Eclipse文件右键属性的text file encoding指定的编码

    比如开发环境中设置的编码格式是utf-8,

    那么System.getProperty(“file encoding”)=utf-8。

        getBytes()默认采用的就是utf-8。

    具体的在开发环境中说,下面进入正题。

    str.getBytes("utf-8")

       以前在servlet中经常用这句话,但是从来都没有去理解过。这个函数的意思是对str这个字符串用iso-8859-1这个字符集进行重新编码并返回byte数组,这里要注意几点:

    首先str是Unicode,不管之前加了这个那种charset得到,它都是Unicode字符集编码的。我们知道String本身其实是

    char[]的包装类,这个char也是Unicode,双字节编码。这个函数就是对每个Unicode 的char字符采用iso-8859-1这个字符集进行编码,最终得到一个字节数组。

    例如:   

    private final static char[] HEX="0123456789abcdef".toCharArray();

    public static void main(String[] args) throws Exception{

           String string="艺艺";

           byte[] b=string.getBytes("utf-8");

           System.out.printf("utf-8     %s%n", bytes2HexString(b));

           byte[] c=string.getBytes("unicode");

           System.out.printf("unicode   %s%n", bytes2HexString(c));

        }

    public static String bytes2HexString(byte[] bys) {

            char[] chs = new char[bys.length * 2 + bys.length - 1];

            for(int i = 0, offset = 0; i < bys.length; i++) {

                if(i > 0) {

                    chs[offset++] = ' ';

                }

                chs[offset++] = HEX[bys[i] >> 4 & 0xf];

                chs[offset++] = HEX[bys[i] & 0xf];

            }

            return new String(chs);

    }

    输出:

    utf-8     e8 89 ba e8 89 ba

    unicode   fe ff 82 7a 82 7a

    默认采用的是utf-8格式,每个中文字符战三个字节,所以上面是6个字节

    但是Unicode每个字符占两个字节为什么这里也是6个呢?

    这是因为Unicode规范中推荐的标记字节顺序的方法是BOM。BOM不是“Bill Of Material”的BOM表,而是Byte Order Mark。

    (Unicode是一种字符编码方法,不过它是由国际组织设计,可以容纳全世界所有语言文字的编码方案。Unicode的学名是"Universal Multiple-Octet Coded Character Set",简称为UCS。UCS可以看作是"Unicode Character Set"的缩写。)

    在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。
    在 Java 中直接使用Unicode 转码时会按照UTF-16LE 的方式拆分,并加上 BOM。 如果采用 UTF-16 拆分,在 Java 中默认采用带有 BOM 的 UTF-16BE 拆分。 (其实Unicode与UTF-8是完全一样的)

    UTF-8 是采用 1~4 个字节来表示 Unicode 字符的,每个 Unicode 的 UTF-8 编码的
    第一个字节是有一定范围的,如果读取到某个字节的最高位为 0 那么采用一个字节表
    示,如果最高位是两个“1”就采用两个字节表示,最高位是三个“1”采用三个字节表
    示,以此类推。多字节表示时,第二个和后面的字节的最高位只能是“10”,也就是说
    UTF-8 编码时字符的第一个字节的最高位不可能是“10”。
    因此,UTF-8 只能采用 Big-Endian 的 BOM 方式。BOM 头 U+FEFF,UTF-8 编码为 EF BB BF就隐藏掉了。

    从上面可以看出这个函数最终实现的是按照charset来得到对应的byte数组

    2、new String(byte[],charset);

    这个函数是对byte[]按照charset进行编码,假如没有charset就直接采用file encoding进行编码。

    比如这个byte[]假如是”GBK”的,要是采用”UTF-8”进行编码肯定是错误的,因为JVM不会自动地对byte[]进行扩展,而是按照”UTF-8”的规则进行编码,这样肯定是会产生乱码的。

    所以对于new String(tmp.getBytes("GBK"), "UTF-8") 这个过程,JVM内部是不会帮你自动对字节进行扩展以适应UTF-8的编码的。正确的方法应该是根据UTF-8的编码规则进行字节的扩充,即手动从2个字节变成3个字节,然后再转换成十六进制的UTF-8编码。直接使用肯定得到的是乱码。

    一般来说编码最好统一。

    另外这句话的意思是什么呢?以前的理解是将这个byte[]数组编码成utf-8编码字符集的字符串,这个字符串就是

    utf-8的,这个理解是错的。这个函数是用charset字符集对byte[]进行编码,按照1110,,,等规则(见上面)判断得到字符并转换成Unicode编码的字符串,所以这个函数最终得到的还是Unicode编码的字符串,每个字符占两位。

    三、在IO中使用字符集

    1、data.json是ANSI字符集,中文windows是GBK,内容是 啊连发16565,Eclipse的环境是UTF_8

    读写txt文件时我们会经常使用FileReader

        BufferedReader reader=new BufferedReader(new FileReader("d:\data.json"));

        StringBuilder builder=new StringBuilder();

        String string=reader.readLine();

        while(string!=null)

        {

            builder.append(string);

            string=reader.readLine();

        }

        System.out.println(builder.toString());

        reader.close();

    直接采用这种方式输出������16565 出现乱码,因为FileReader不能设置编码方式只能使用UTF-8对GBK编码的文件进行读取,肯定出现乱码,出现这种乱码在后面无法还原。

    当把data.json换成UTF-8字符集的时候就能够正常的读出来      啊连发16565

    为此我们在不知道TXT编码读取文件的时候,最好采用下面的方法。

    先做一个简单的测试

        File file=new File("d:\data.json");

        InputStream stream=new FileInputStream(file);

        byte b1=(byte)stream.read();

        byte b2=(byte)stream.read();

        byte b3=(byte)stream.read();

        long len2=file.length();

        System.out.println(Integer.toHexString(b1 & 0xff));

        System.out.println(Integer.toHexString(b2 & 0xff));

         System.out.println(Integer.toHexString(b3 & 0xff));

        System.out.println(len2);

       stream.close();

    这段代码输出的是

    ef

    bb

    bf

    17

    前三个正是utf-8的BOM(BYTE ORDER MARK)

    那么可以采用这种方法来判断文件的编码,由于UNICODE采用的是2个字节,假如文件为空的话将会出现错误。

    所以采用下面的方法先判断文件编码格式

      private String getCharset(String fileName) throws IOException{

           

                BufferedInputStream bin = new BufferedInputStream(new FileInputStream(fileName)); 

                int p = (bin.read() << 8) + bin.read(); 

                String code = null; 

                switch (p) {  

                    case 0xefbb: 

                        code = "UTF-8"; 

                        break; 

                    case 0xfffe: 

                        code = "Unicode"; 

                        break; 

                    case 0xfeff: 

                        code = "UTF-16BE"; 

                        break; 

                    default: 

                        code = "GBK"; 

                } 

                return code;

        }

    获取字符串采用下面的

    public String getTextFromText(String filePath){

           

                try {

                    InputStreamReader isr = new InputStreamReader(new FileInputStream(filePath),getCharset(filePath));

                    BufferedReader br = new BufferedReader(isr);

                   

                    StringBuffer sb = new StringBuffer();  

                    String temp = null;  

                    while((temp = br.readLine()) != null){  

                        sb.append(temp);  

                    }  

                    br.close();       

                    return sb.toString();  

                } catch (FileNotFoundException e) {

                    // TODO Auto-generated catch block

                    e.printStackTrace();

                   

                }catch (IOException e) {

                    // TODO Auto-generated catch block

                    e.printStackTrace();

                }  

                return null;

        }

    这样就不会乱码啦

    四、在J2EE中使用(Request,Response,Tomcat)

    浏览器请求会先转到tomcat中 tomcat 配置文件中的默认编码格式 就是 iso-8859-1,所以对于utf-8格式的字符串,我们就采用

    new String(temp.getBytes(“iso-8859-1”),”utf-8”); 

    假如不想这样获取也可以采用下面的方式

    Post方式:request.setCharacterEncoding(”utf-8 “);

    对于get方式:需在server.xml中的:

    <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443"

    URIEncoding="utf-8" />设置

    输出:(必须在write之前调用)

    response.setContentType("text/html;charset=utf-8")是设置页面中为中文编码

    五、Eclipse设置

     

    Eclipse有四个地方可以设置字符集

    1、     设置整个workspace的字符集,所有的工程默认都会采用这个字符集。

    Windows->preference->Text File Ecncoding->UTF-8

    2、     在工程上右键

    Propertise->resource-> Text File Ecncoding->UTF-8

    3、     在源文件上

    Propertise->resource-> Text File Ecncoding->UTF-8

    注意这里的编码将直接影响到  System.getProperty(“file.encoding”)

        public static void main(String[] args) throws Exception{

        System.out.println(System.getProperty("file.encoding"));

        BufferedReader reader=new BufferedReader(new FileReader("d:\data.json"));

        StringBuilder builder=new StringBuilder();

        String string=reader.readLine();

        while(string!=null)

        {

            builder.append(string);

            string=reader.readLine();

        }

        System.out.println(builder.toString());

        reader.close();

    }

    这里将文件字符集改成Unicode   并且将text file encoding改成Unicode,那么输出OK,即默认采用的是该文件text file encoding,而不是系统的GBK

    UTF-16

    啊连发16565

    4、Run->run configuration->右边的Common控制的是控制台的输出

     

    六、如何防止乱码

      数据库、开发环境、页面编码、Java容器全部统一编码。

      如何涉及到socket通信,在传输的时候也要采用相同的字符集

  • 相关阅读:
    There is an overlap in the region chain修复
    There is an overlap in the region chain
    region xx not deployed on any region server
    python 中的re模块,正则表达式
    TCP粘包问题解析与解决
    yield from
    Git push提交时报错Permission denied(publickey)...Please make sure you have the correct access rights and the repository exists.
    mysql 中Varchar 与char的区别
    Mysql 字符集及排序规则
    请实现一个装饰器,限制该函数被调用的频率,如10秒一次
  • 原文地址:https://www.cnblogs.com/maydow/p/4593573.html
Copyright © 2011-2022 走看看