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通信,在传输的时候也要采用相同的字符集

  • 相关阅读:
    Silverlight 4中把DataGrid数据导出Excel
    C#正则的委托和lambda表达式用法
    C#简单的写日志方法
    GAE上传失败
    asp.net后台进程做定时任务
    ASP.NET页面生命周期描述
    巴士电台新版发布
    jQuery 1.51.7一些值得注意的更新
    wxPython应用心得
    Ajax保留浏览器历史的两种解决方案(Hash&Pjax)[转]
  • 原文地址:https://www.cnblogs.com/maydow/p/4593573.html
Copyright © 2011-2022 走看看