JS实现UTF8编解码及Base64编解码

来源:互联网 发布:空难 尼尔森 知乎 编辑:程序博客网 时间:2024/04/29 18:30

最近抽了点时间去了解了下Unicode,UTF-8,Base64之间的关系,以及它们之间 的一些转换规则,并且自己动手按照相应的编码规则实现了相应的编解码,虽然写的很生硬,没有网上一些大神写的那么简洁,编解码效率可能也不那么高,但是我还是决定把我自己的实现思路分享一下,希望可以为那些想了解具体编码规则及过程的网友有一定的帮助,另外也希望各位大神指点指点,看看如何实现编解码可以让代码更高效更简洁。

对于Unicode,UTF-8,Base64的具体定义本文不会过多解释,因为网上一搜一大把,本文主要讲编解码的规则和实现。以下内容便是按照相应规则对UTF-8,Base64的编解码实现。对于以下代码我已经进行了初步的验证,可正常编解码,各位网友可以直接拷贝使用,如有疑问或是代码有问题,请留言,我会尽快回复并跟进,谢谢!

/*

百度百科对UTF-8的定义:UTF-8(8-bit Unicode Transformation Format)是一种针对Unicode的可变长度字符编码,又称万国码。由Ken Thompson于1992年创建。现在已经标准化为RFC 3629。UTF-8用1到6个字节编码Unicode字符。用在网页上可以统一页面显示中文简体繁体及其它语言(如英文,日文,韩文),简单来说就是UTF-8是Unicode的一种实现方式。

   Unicode符号范围 | UTF-8编码方式

   (十六进制) | (十进制) | (二进制)
   --------------------+---------------------------------------------
   0000 0000-0000 007F | 0-127 | 0xxxxxxx
   0000 0080-0000 07FF | 128-2047 | 110xxxxx 10xxxxxx
   0000 0800-0000 FFFF | 2048-65535 | 1110xxxx 10xxxxxx 10xxxxxx
   0001 0000-0010 FFFF | 65536-1114111 | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
   UTF-8的编码规则只有二条:
   1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。
   2)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。x表示可用编码的位。
   Unicode符号范围 | UTF-8编码方式
   */
function UTF8()
{
    this.encode = function (string) {
        var utftext = "";
        var byte = [];
        for (var n = 0; n < string.length; n++) {
            var c = string.charCodeAt(n);//获取对应的unicode
            if (c < 128) {
                utftext += '\\x' + c.toString(16).toUpperCase();//单字节字符
                byte.push(c);
            }
            else {
                var byte_count = 2;
                if (c > 127 && c < 2048)
                    byte_count = 2;
                else if (c > 2047 && c < 65536)
                    byte_count = 3;
                else if (c > 65535 && c < 1114112)
                    byte_count = 4;
                else
                    return "编码失败!仅支持4位字节及以下的字符串编码!";


                var height_code = '';
                for (var j = 0; j < 8; j++) {
                    if (j < byte_count)
                        height_code += '1';
                    else
                        height_code += '0';
                }


                var d_code = parseInt(height_code, 2);
                for (var i = byte_count - 1; i > -1; i--)
                {
                    var bit = i * 6;
                    if (i == byte_count - 1) {
                        var code = ((c >> bit) | d_code);
                        utftext += '\\x' + code.toString(16).toUpperCase();//按位右移6位获取到需要与高8位11000000也就是192进行补位的位数,然后通过按位或运算进行补位
                        byte.push(code)
                    }
                    else {
                        //63的二进制数是00000000 00111111,通过按位与运算获取后6位需要与低8位10000000进行补位的位数,然后或运算补位
                        var code = (((c >> bit) & 63) | 128);
                        utftext += '\\x' + code.toString(16).toUpperCase();
                        byte.push(code);
                    }
                        
                }
            }
        }
        return utftext;
    }

    this.decode = function (utftext) {
        var string = "";
        //将utf8字符串分割成单个的十六进制
        var split = utftext.split('\\x');
        //删除第一个空字符串
        split.remove('');
        //对分割好的十六进制按照编码规则分组
        var group = [];
        var complete_binary_code = '';
        var count = -1;
        for (var i = 0; i < split.length; i++)
        {
            var str = split[i];
            var number = parseInt(str, 16);//转为10进制
            var binary = number.toString(2);//转为二进制字符串
            //不足8位的要进行补零
            for (var j = 0; j < 8 - binary.length; j++)
            {
                binary = binary.insert(0, '0');
            }
            var index = binary.indexOf('0');//查找第一个0出现的位置,也就是该组utf8的字节数
            if (index > 0) {
                if ((index > count && count != -1 && complete_binary_code != ''))
                {
                    //index > count 表明是第二个分组了,但是要排除count==-1的情况,因为这是循环的开始
                    group.push(complete_binary_code);
                    complete_binary_code = '';
                }
                else if (i == split.length - 1) {
                    //最后一个元素
                    complete_binary_code += binary.substr(index + 1, 7 - index);
                    group.push(complete_binary_code);
                }
                complete_binary_code += binary.substr(index + 1, 7 - index);
                count = index;
            }
            else {
                if (count != -1 && complete_binary_code != '')
                    group.push(complete_binary_code);//上一组的数据需要先保存
                complete_binary_code = binary.substr(1, 7);
                group.push(complete_binary_code);
                complete_binary_code = '';
            }
        }


        for (var i = 0; i < group.length; i++)
        {
            var binary_str = group[i];
            var char_code = parseInt(binary_str, 2);
            string += String.fromCharCode(char_code);
        }
        return string;
    }
}


/*

一、维基百科对Base64的定义是:Base64是一种基于64个可打印字符来表示二进制数据的表示方法。由于2的6次方等于64,所以每6个比特为一个单元,对应某个可打印字符。
三个字节有24个比特,对应于4个Base64单元,即3个字节可表示4个可打印字符(个人觉得更准确的说应该是3个字节对应4个可打印字符,4个可打印字符可表示3个字节,
因为是based64对字节进行编码,说3个字节表示4个可打印字符不太恰当)。
二、Base64编码字符包括大小写字母各26个,加上10个数字,和加号“+”,斜杠“/”,一共64个字符,等号“=”用来作为后缀用途。
三、编码规则:文本转为unicode码->unicode码转为二进制数->二进制数每每6位转换为十进制的索引->使用索引在base64字符表中查找对应的字符。
四、思考:
1.为什么要每6个比特对应一个字符,为什么不是4个或8个?因为二进制转base64是通过索引表的方式进行转换的,索引为0-63,那多少位二进制含括这些索引呢?
根据二进制转十进制规则可以知道,4个二进制数只能表示0-16的数字,所以不符合要求,而6个二进制数可以表示0-64的数字,8个二进制数可以表示0-256的数字,
这两个都可以含括全部索引,但是8位或更多的二进制数来表示的话显然是在浪费资源,所以相比之下每6个比特为一个单元,对应一个字符是最合理的。
2.为什么用4个可打印字符表示3个字节?因为编码后的base64对应的比特位数肯定和编码前的原字符对应的比特位数是一样的,而一个base64字符对应6个比特,一个字节对应8个比特,也就是说
8*字节=6*base64字符,得出base64:字节=4:3,也就是只有每4个可打印字符和3个字节一一对应才能进行正常的解密。不然解码出来的数据就和原来的数据不一致了。
*/


function Base64() {
    var _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
    var utf8 = new UTF8();
    // public method for encoding  
    this.encode = function (input) {
        //转为utf8获取对应的字节数
        var utf8_str = utf8.encode(input);
        var bytes = utf8_str.split('\\x');
        //删除第一个空字符串
        bytes.remove('');
        var binary_code = "";
        for (var i = 0; i < bytes.length; i++)
        {
            //转成10进制
            var number = parseInt(bytes[i], 16);
            //转成2进制
            var baniry = number.toString(2);
            //不足8位的进行补位
            var length = baniry.length;
            for (var j = 0; j < 8 - length; j++) {
                baniry = baniry.insert(0, "0");
            }
            binary_code += baniry;
        }

        var output = "";
        //获取based64字符数
        var base64_count = Math.ceil(binary_code.length / 6);
        var byte_remainder = binary_code.length % 6;
        byte_remainder = byte_remainder == 0 ? 6 : byte_remainder;
        for (var j = 0; j < (6 - byte_remainder) ; j++)
        {
            //不足6位在后面追加0
            binary_code += "0";
        }
        var i = 0;
        var dec_number = parseInt(binary_code, 2);
        var base64_remainder = base64_count % 4;
        base64_remainder = base64_remainder == 0 ? 4 : base64_remainder;
        while (i < (base64_count + (4 - base64_remainder)))
        {
            //if (i < (4 - base64_remainder))
            //    output += "=";
            //else
            //{
            //    //思路一:采用逆序的处理逻辑进行编码,每次向右按位移动4 - base64_remainder位,再与63即(111111)进行按位与运算得到对应的数
            //    //var bit = 6 * (i - (4 - base64_remainder));
            //    //var index = (dec_number >> bit) & 63;
            //    //var base64_code = _keyStr.substr(index, 1);
            //    //output = output.insert(0, base64_code);
            //    /*
            //    注意:js的按位移动操作存在一个问题,对在int的范围(-2147483648~2147483647)内的十进制数进行按位移动操作是正常的
            //    如:
            //    var a = 2147483647;
            //    var s = a.toString(2);
            //    console.log(s);输出:1111111111111111111111111111111


            //    var a = 2147483647 >> 0;
            //    var s = a.toString(2);
            //    console.log(s);输出:1111111111111111111111111111111


            //    但是当十进制数超出了int的范围就会出问题,比如向右移动0位,按道理来说二进制数是不应该发生变化,但是结果却出乎我的意料
            //    如:
            //    var a = 26141406784;
            //    var s = a.toString(2);
            //    console.log(s); 输出:11000010110001001100011011001000000
            //    但是如果对其进行按位移动操作之后就会出问题了
            //    var a = 26141406784 >> 0;
            //    var s = a.toString(2);
            //    console.log(s); 输出:10110001001100011011001000000 这里的前面6位莫名奇妙的消失了,最大的可能就是js的按位运算
            //    再如:
            //    var a = -2147483649;
            //    var s = a.toString(2);
            //    console.log(s);输出:-10000000000000000000000000000001
                
            //    var a = -2147483649 >> 0;
            //    var s = a.toString(2);
            //    console.log(s);输出:1111111111111111111111111111111 what?这是什么鬼!!
            //    由于以上这个问题,思路一的方式是行不通的
            //    */
            //}
            //i++;


            //思路二:为要避免以上问题,我选择了截取对应的字符串的方式进行编码
            if (i >= base64_count)
                output += "=";
            else {
                var idx = 6 * i;
                var code = binary_code.substr(idx, 6);
                var index = parseInt(code, 2);
                var base64_code = _keyStr.substr(index, 1);
                output += base64_code;
            }
            i++;
        }
        return output;
    }


    // public method for decoding  
    this.decode = function (input) {
        var output = "";
        i = 0;
        var binary_code = "";
        while (i < input.length)
        {
            var char = input.substr(i, 1);
            var index = _keyStr.indexOf(char);
            if (index == 64)
                break;


            var binary = index.toString(2);
            var length = binary.length;
            for (var j = 0; j < 6 - length; j++)
            {
                binary = binary.insert(0, "0");
            }
            
            binary_code += binary;
            i++;
        }
        var utf8_array = [];
        var byte_count = Math.floor(binary_code.length / 8);
        for (var j = 0; j < byte_count; j++) {
            var code = binary_code.substr(j * 8, 8);
            var dec_code = parseInt(code, 2);
            utf8_array.push(dec_code.toString(16));
        }
        var utf8_str = utf8_array.join('\\x');
        output = utf8.decode(utf8_str);
        return output;
    }
}

//============================js Extention=================================
Array.prototype.indexOf = function (val) {
    for (var i = 0; i < this.length; i++) {
        if (this[i] == val) return i;
    }
    return -1;
};
Array.prototype.remove = function (val) {
    var index = this.indexOf(val);
    if (index > -1) {
        this.splice(index, 1);
    }
};


String.prototype.insert = function (index, string) {
    if (index> 0)
        return this.substring(0, index) + string + this.substring(index, this.length);
    else
        return string + this;

};




原创粉丝点击