首页 > 编程语言 > 详细

深入分析JavaWeb的中文编码问题

时间:2020-03-29 00:02:02      阅读:56      评论:0      收藏:0      [点我收藏+]

1.几种常见的编码格式

 1.为什么需要编码?

(1)在计算机存储信息的最小单位是1个字节(byte),即8个bit,所以能表示的字符范围是0-255个。

(2)人类要表示的符号太多,无法用1个字节来完全表示。

  要解决这个矛盾必须要有一个新的数据结构char,而从char到byte必须编码。

2.如何编码

在计算机中提供了多种编码方式,常见的有ASCII、ISO-8859-1、GB2312、GBK、UTF-8、UTF-16等。其中GB2312、GBK、UTF-8、UTF-16都可以表示汉子,下面介绍几种编码方式。

1.ACSII码

  ACSII码总共128个,用1个字节的低7位表示,0-31控制字符如回车、换行等,32-126是打印字符串,可以通过键盘输入(键盘上能够找到的)并且能够显示处理。

2.ISO-8859-1

  128个字符显然是不够用的,于是ISO在ASCII的基础上扩展出ISO-8859-1到ISO8859-15,其中ISO-8859-1涵盖了大多数西欧语言字符,所以应用最广泛。8829-1仍然是单字节编码,总共能表示256个字符。

3.GB2312

  该编码集的全称是《信息技术中文编码字符集》,它是双字节编码,总的编码范围是A1-F7,其中A1-A9是符号区,总共包含682个符号,B0-B7是汉子区,包含6763个汉字。

4.GBK

  全称《汉字内码扩展规范》,是为了扩展GB2312,并加入更多的汉字。编码范围是8140-FEFE(去掉XX7F),总共23940个码位,它能表示21003个汉字,它的编码和GB2312兼容,也就是说用GB2312编码的汉字可以用GBK来解码,并且不会乱码。

5.UTF-16

  Unicode(Universal Code 统一码)是ISO试图创建的超语言字典,世界上所有的语言都可以用这个字典来翻译。Unicode 只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。其实现方式主流的有UTF-8和UTF-16。

  UTF-16具体定义了Unicode字符在计算机中的存取方法。UTF-16用两个字节来表示Unicode的转化格式,它采用定长的表示方法,即不论什么字符都可以使用两个字节来表示。两个字节是16bit,所以叫做UTF-16。UTF-16表示字符串非常方便,每两个字节表示一个字符,这就大大简化了字符串操作,这也是Java以UTF-16作为内存的字符存储格式的一个重要原因。

6.UTF-8

  UTF-16使用两个字节来表示一个字符,有时候一个字节就够用,所以会造成存储空间的浪费,而且会增大网络传输的流量。

  UTF-8采用了一种变长技术,每个编码区域有不同的字码长度。不同类型的字符可以由1-6个字节组成。UTF-8有如下规则:

(1)如果是1个字节,最高位(第8位)为0,则表示这是1个ASCII字符(00-7F)可见,所有ASCII编码已经是UTF-8编码了。

(2)如果是1个字节,以11开头,则连续的1的个数暗示这个字符的字节数。例如:110xxxxx代表它是双字节UTF-8的首字节。

(3)如果是1个字节,以10开始,代表它不是首字节,则需要向前查找才能得到当前字符的首字节。

例如:

1字节 0xxxxxxx 
2字节 110xxxxx 10xxxxxx 
3字节 1110xxxx 10xxxxxx 10xxxxxx 
4字节 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 
5字节 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 
6字节 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 

 

补充:参考网上的一个U8和unicode转换的例子  

UTF-8中可以用来表示字符编码的实际位数最多有31位,即上表中x所表示的位。除去那些控制位(每字节开头的10等),这些x表示的位与UNICODE编码是一一对应的,位高低顺序也相同。 实际将UNICODE转换为UTF-8编码时应先去除高位0,然后根据所剩编码的位数决定所需最小的UTF-8编码位数。

下表总结了编码规则,字母x表示可用编码的位。

Unicode符号范围 | UTF-8编码方式
(十六进制)       | (二进制)
--------------------+---------------------------------------------
0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

  以汉字严为例演示如何实现utf-8编码。"严"的unicode编码为4e25,二进制0100111000100101,根据上表,可以发现4E25处在第三行的范围内(0000 0800-0000 FFFF),,因此"严"的UTF-8编码需要三个字节,即格式是"1110xxxx 10xxxxxx 10xxxxxx"。然后,从"严"的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,"严"的UTF-8编码是"11100100 10111000 10100101",转换成十六进制就是E4B8A5。

验证如下:

import java.math.BigInteger;

public class Client {

    public static void main(String[] args) throws Exception {
        // Integer.toHexString(‘严‘) 获取unicode码
        // 十六进制unicode
        System.out.println(Integer.toHexString(‘严‘));
        // 二进制的unicode
        System.out.println(hexString2binaryString(Integer.toHexString(‘严‘)));

        // 二进制的UTF-8
        System.out.println(binary("严".getBytes("utf-8"), 2));
        // 十六进制的UTF-8
        System.out.println(binary("严".getBytes("utf-8"), 16));
    }

    /**
     * 将byte[]转为各种进制的字符串
     * 
     * @param bytes
     *            byte[]
     * @param radix
     *            基数可以转换进制的范围,从Character.MIN_RADIX到Character.MAX_RADIX,
     *            超出范围后变为10进制
     * @return 转换后的字符串
     */
    public static String binary(byte[] bytes, int radix) {
        return new BigInteger(1, bytes).toString(radix);// 这里的1代表正数
    }

    /**
     * 16进制转二进制
     * 
     * @param hexString
     * @return
     */
    public static String hexString2binaryString(String hexString) {
        if (hexString == null || hexString.length() % 2 != 0)
            return null;
        String bString = "", tmp;
        for (int i = 0; i < hexString.length(); i++) {
            tmp = "0000" + Integer.toBinaryString(Integer.parseInt(hexString.substring(i, i + 1), 16));
            bString += tmp.substring(tmp.length() - 4);
        }
        return bString;
    }

    // 2进制转16进制
    public static String binaryString2hexString(String bString) {
        if (bString == null || bString.equals("") || bString.length() % 8 != 0)
            return null;
        StringBuffer tmp = new StringBuffer();
        int iTmp = 0;
        for (int i = 0; i < bString.length(); i += 4) {
            iTmp = 0;
            for (int j = 0; j < 4; j++) {
                iTmp += Integer.parseInt(bString.substring(i + j, i + j + 1)) << (4 - j - 1);
            }
            tmp.append(Integer.toHexString(iTmp));
        }
        return tmp.toString();
    }

}

结果:

4e25
0100111000100101
111001001011100010100101
e4b8a5

  关于更多的参考:http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html

补充:Little endian 和 Big endian编码方式

以汉字严为例,Unicode 码是4E25,需要用两个字节存储,一个字节是4E,另一个字节是25。存储的时候,4E在前,25在后,这就是 Big endian 方式;25在前,4E在后,这是 Little endian 方式。

第一个字节在前,就是"大头方式"(Big endian),第二个字节在前就是"小头方式"(Little endian)。

那么很自然的,就会出现一个问题:计算机怎么知道某一个文件到底采用哪一种方式编码?

Unicode 规范定义,每一个文件的最前面分别加入一个表示编码顺序的字符,这个字符的名字叫做"零宽度非换行空格"(zero width no-break space),用FEFF表示。这正好是两个字节,而且FF比FE大1。

如果一个文本文件的头两个字节是FE FF,就表示该文件采用大头方式;如果头两个字节是FF FE,就表示该文件采用小头方式。

例如,另一种方式获取unicode码:

System.out.println(binary("严".getBytes("unicode"), 16));

 

结果:(表示大头存储方式)

feff4e25  

 

深入分析JavaWeb的中文编码问题

原文:https://www.cnblogs.com/qlqwjy/p/12581057.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!