Javascript/PHP/Python/JSON编码总结

# Javascript/PHP/Python/JSON Encoding Summary#
项目中UI采用Javascript(前端) + PHP(中间层) + Python(后端) + Memcached(存储),中间使用JSON传递数据。
遇到一些编码的问题,索性在此总结一下。整套流程中间和最终存储数据均采用UTF-8。

## 1. Unicode, UTF-8, UTF-16, UTF-32, UCS-2, UCS-4区别 ##
### 1.1 UTF-8: 使用1到4个字节的变长编码 ###
    000000-00007F(128个编码)            0zzzzzzz                                    ASCII编码 
    000080-0007FF(1920个编码)           110yyyyy(C0-DF) 10zzzzzz(80-BF)             由110开始,接着1个10开始 
    000800-00D7FF
    00E000-00FFFF(61440个编码)          1110xxxx(E0-FF) 10yyyyyy 10zzzzzz           由1110开始,接着2个10开始  
    010000-10FFFF(1048576个编码)        11110www(F0-F7) 10xxxxxx 10yyyyyy 10zzzzzz  由11110开始,接着3个10开始
    对于UTF-8编码中的任意字节B,如果B的第一位为0,则B为ASCII码,并且B独立的表示一个字符;
        如果B的第一位为1,第二位为0,则B为一个非ASCII字符(该字符由多个字节表示)中的一个字节,并且不为字符的第一个字节编码;
        如果B的前两位为1,第三位为0,则B为一个非ASCII字符(该字符由多个字节表示)中的第一个字节,并且该字符由两个字节表示;
        如果B的前三位为1,第四位为0,则B为一个非ASCII字符(该字符由多个字节表示)中的第一个字节,并且该字符由三个字节表示;
        如果B的前四位为1,第五位为0,则B为一个非ASCII字符(该字符由多个字节表示)中的第一个字节,并且该字符由四个字节表示;
    因此,对UTF-8编码中的任意字节,根据第一位,可判断是否为ASCII字符;根据前二位,可判断该字节是否为一个字符编码的第一个字节;
    根据前四位(如果前两位均为1),可确定该字节为字符编码的第一个字节,并且可判断对应的字符由几个字节表示;根据前五位(如果前四位为1),可判断编码是否有错误或数据传输过程中是否有错误。

### 1.2 UTF-16 ###
    U+0000-U+FFFF       xxxxxxxx xxxxxxxx                   0-65535(1个基本平面)            2字节
    U+10000-U+10FFFF    110110yyyyyyyyyy 110111xxxxxxxxxx   65536-1114111(16个辅助平面)     4字节
    存储时存在字节序问题,分为UTF-16LE和UTF-16BE,可以通过BOM头来区分。

### 1.3 UCS-2 ###
    它是UTF-16的子集,在没有辅助平面时二者等价。

### 1.4 UTF-32/UCS-4 ###
    U+00000000-U+7FFFFFFF   占用4个字节,高字节首位为0,0~2^31

## 2. 网页编码 ##
    设定网页的charset为utf-8,这样用户提交的输入都是utf-8编码。
    Charset的指定:
        1. 在response header中设置content-type
            Content-Type: text/html;charset=utf-8
            在PHP中可以使用如下函数实现,
                header("Content-Type: text/html; charset=utf-8");
        2. 在page中设置meta标签
            <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
            因为标签包含在html页面中,因此用户通过保存网页也能正常工作
        3. 比较好的做法时同时设置header和meta标签,并保证两者一致。

## 3. 正向处理(Javascript->PHP->Python->Memcached)
### 3.1 Javascript Object处理 ###
    经过指定charset为UTF-8后,从表单中获取的输入的编码都是UTF-8编码。
    Javascript在构造string对象时会按照UCS-2编码,所以此时用户输入的UTF-8编码数据会转换为UCS-2。
    最终表单提交时,提交的字符串会根据网页设置的编码转化成UTF-8提交到服务器端。
    Javascript中JSON和字符串间的转换可以采用下面的函数实现。
    假设Javasciprt中的Object某个key的value是字符串,将这个object转化成string,此时的string是unicode(UCS-2)编码
        // Convert JSON to String
        function jsonToString(obj) {
            var THIS = this;
            switch(typeof(obj)){
                case 'string':
                    // 转义'"', '\'两个字符
                    return '"' + obj.replace(/(["\\])/g, '\\$1') + '"';
                case 'array':
                    return '[' + obj.map(THIS.jsonToString).join(',') + ']';
                case 'object':
                     if(obj instanceof Array){
                        var strArr = [];
                        var len = obj.length;
                        for(var i=0; i<len; i++){
                            strArr.push(THIS.jsonToString(obj[i]));
                        }
                        return '[' + strArr.join(',') + ']';
                    }else if(obj==null){
                        return 'null';
                    }else{
                        var string = [];
                        for (var property in obj) string.push(THIS.jsonToString(property) + ':' + THIS.jsonToString(obj[property]));
                        return '{' + string.join(',') + '}';
                    }
                case 'number':
                    return obj;
                case false:
                    return obj;
            }
        }
    可以使用如下代码验证string的编码:
        // Get the code point of javascript string
        var jsonString = jsonToString(obj);
        var codePoints = "";
        for(var i = 0; i < jsonString.length; i++) {
            codePoints += jsonString.charCodeAt(i) + " ";
        }

### 3.2 PHP处理 ###
    当PHP处理的request的charset是UTF-8时,PHP获取的字符串就是UTF-8编码。
    对于content-type:application/x-www-form-urlencoded时,PHP获取的post表单数据时经过url encode的,从$_POST获取的数据已经经过url decode。
    获取post数据(JSON),然后调用json_decode进行解码,json_decode接收的参数必须是UTF-8编码(不支持BOM),decode之后的对象中的字符串仍然是UTF-8编码。

### 3.3 Python处理 ###
    Python收到utf-8的输入,经过json.loads输出json对象,对象中所有的key和string value全部是unicode编码。
    具体Python使用哪种unicode编码由编译选项决定。
        >> sys.maxunicode
    如果是65535是UCS-2,1114111为UCS-4。
        >>> obj = json.loads('{"a": "aaa", "b": "排序"}')
        >>> obj
        {u'a': u'aaa', u'b': u'\u6392\u5e8f'}
    json对象dump成字符串时,默认会decode成ascii格式。对多字节unicode编码会decode成如下格式。
        >>> objstr = json.dumps(obj)
        >>> objstr
        '{"a": "aaa", "b": "\\u6392\\u5e8f"}'
        >>> type(objstr)
        <type 'str'>
        >>> print(objstr)
        {"a": "aaa", "b": "\u6392\u5e8f"}
    通过参数ensure_ascii=False,可以decode成unicode字符串。
        >>> objstr = json.dumps(obj, ensure_ascii=False)
        >>> print(objstr)
        {"a": "aaa", "b": "排序"}
        >>> type(objstr)
        <type 'unicode'>
        >>> objstr.encode('utf-8')
        '{"a": "aaa", "b": "\xe6\x8e\x92\xe5\xba\x8f"}'
### 3.4 Memcached ###
    通过Python的json.dumps调用后的JSON字符串,会被存储到Memcached。后台应用需要使用Memcached中的数据时,可以直接读出然后进行JSON解析。
    比如[Jansson](http://www.digip.org/jansson/),可以很好的进行JSON解析,但它只接受UTF-8编码的字符串输入。以下两种格式JSON都能很好支持,输出是UTF-8编码的字符串。
        {"a": "aaa", "b": "\u6392\u5e8f"}
        '{"a": "aaa", "b": "\xe6\x8e\x92\xe5\xba\x8f"}'

## 4. 反方向处理(Memcached->Python->PHP->Javascript) ##
### 4.1 Python ###
    从Memcached读取数据,经过处理后,使用json.dumps将json对象转换成ascii格式的字符串,多字节unicode以\u形式编码
        > {"a": "aaa", "b": "\u6392\u5e8f"}
    将ascii格式字符串传递给PHP。

### 4.2 PHP ###
    PHP将对应的ascii格式字符串返回给客户端Javascript。

### 4.3 Javascript ###
    Javascript使用如下函数将string转换成Javascript对象。
        // Convert String to JSON
        function stringToJSON(obj) {
            return eval('(' + obj + ')');
        }

## 5. Javascript UTF-16 support issue ##
    Javascript字符串默认使用unicode(UCS-2)编码,每个字符2个字节表示,比如
        > var data = "hello";
    字符串的length计算的是多少个2字节。所以对于UTF-16中高位的编码支持存在问题。
    以下是work around方法:
        String.prototype.getFullCharLength=function() {
            return this.length-this.split(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g).length+1;
        };
        String.fromFullCharCode=function() {
            var chars= [];
            for (var i= 0; i<arguments.length; i++) {
                var n= arguments[i];
                if (n<0x10000)
                    chars.push(String.fromCharCode(n));
                else
                    chars.push(String.fromCharCode(
                        0xD800+((n-0x10000)&0x3FF),
                        0xDC00+(((n-0x10000)>>10)&0x3FF)
                    ));
            }
            return chars.join('');
        };

## 6. PHP string encoding ##
    PHP字符串就是多个字节的组合,不包含任何编码信息。
    strlen计算的是字节数,mbstring模块支持多字节的编码,比如mb_strlen。

## 7. python string encoding ##
    和PHP类似,可以对字节串进行编码转换。

## Reference ##
* [How to find out if python is compiled with ucs2 or ucs4](http://stackoverflow.com/questions/1446347/how-to-find-out-if-python-is-compiled-with-ucs-2-or-ucs-4)
* [http://stackoverflow.com/questions/3744721/javascript-strings-outside-of-the-bmp](http://stackoverflow.com/questions/3744721/javascript-strings-outside-of-the-bmp)
* [http://terenceyim.wordpress.com/category/programming/javascript/](http://terenceyim.wordpress.com/category/programming/javascript/)
* [https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/charAt](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/charAt)
* [http://stackoverflow.com/questions/8715980/javascript-strings-utf-16-vs-ucs-2](http://stackoverflow.com/questions/8715980/javascript-strings-utf-16-vs-ucs-2)
* [How does PHP internally represent strings](http://programmers.stackexchange.com/questions/151293/how-does-php-internally-represent-strings)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值