13

JS 简单实现UTF-8编码,Base64编码

 4 years ago
source link: https://juejin.im/post/5e328bdff265da3e4569ad9b
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
2020年01月30日 阅读 1754

JS 简单实现UTF-8编码,Base64编码

文章用JS简单的实现UTF-8编码和Base64编码,阅读本文可以了解Unicode 与 UTF-8 之间的转换,了解Base64编码为什么会使数据量变长。

  • Unicode简单了解
  • UTF-8编码
  • Base64编码

Unicode,ASCII,GB2312编码集合等,类似于字典。字符就类似于字,字符的编码,类似于字典中的字在哪一页哪一行。当不同系统用同一本字典查同一个编码得到的字符会一致。

1

1. Unicode简单了解

wikipedia:

Unicode is a computing industry standard for the consistent encoding, representation, and handling of text expressed in most of the world's writing systems.

在创造Unicode之前各种语言有不同的编码集合,ASCII,GB2312等也是发展过程中编码集合,而且这些编码集合相互冲突,给不同语言系统进行交流带来了麻烦。因为两种相同的字符在不同的编码系统中可能有完全不同的意思。于是Unicode出现了,Unicode编码集合给每个字符提供了一个唯一的数字,不论平台,程序,语言,Unicode 字符集因此被广泛应用。

Javascript程序是用Unicode字符集编写的, 字符串(string)中每个字符通常来自于Unicode 字符集。

Unicode 字符集类似于字典,字符就类似于字。字符的Unicode码值,就类似于字在字典的第页第几行。

2. UTF-8编码

2.1为何有了Unicode字符集还需要 一个编码来传输了?

因为Unicode 编码转换成二进制,是一串0,和1,传输个另一方的时候,需要一个规则来分割这一串0、1。

于是就出现了UTF-n 编码们。

16ff03618cded89d?imageslim

8bit = 1byte

资料

UTF(Universal Transformation Format,通用传输格式),其实就是不改变字符集中各个字符的代码,建立一套新的编码方式,把字符的代码通过这个编码方式映射成传输时的编码,最主要的任务就是在使用Unicode字符集保持通用性的同时节约流量和硬盘空间。

存储
Unicode是一个符号集,规定了符号的二进制代码,没有规定这个二进制代码应该如何存储(即占用多少个字节)所以出现了不同的存储实现方式。
UTF-32

字符用四个字节表示

UTF-16

字符用两个字节或四个字节表示

UTF-8

一种变长的编码方式,根据需要用1~4个字节来表示字符,(按需传递节约流量和硬盘空间,因此UTF-8用的比较广)

2.2UTF-8编码规则

  • 对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。
  • 对于n字节的符号(n > 1),第一个字节的前n位都设为1,第n+ 1位设为0,后面字节的前两位设为10。剩下,全部为这个符号的 Unicode 编码。
1

eg: 字符罗的UTF-8编码

用codePointAt得到了字符的Unicode 编码,确认用几个字节表示,然后按照规则填充。

1

编码流程:

1

资料补充:

ES6 提供了codePointAt()方法,能够正确处理字节储存的字符,返回一个字符的码点(Unicode 编码)。

ES6 提供了String.fromCodePoint()方法能正确处理一个码点(Unicode 编码),返回码点(Unicode 编码)对应的字符

2.3UTF-8编码解码简单实现

// 编码
function encodeUtf8(str) {
  var bytes = []
  for (ch of str) {
    // for...of循环,能正确识别 32 位的 UTF-16 字符, 可以查阅资料了解。
    let code = ch.codePointAt(0)
    if (code >= 65536 && code <= 1114111) {// 位运算, 补齐8位
      bytes.push((code >> 18) | 0xf0)
      bytes.push(((code >> 12) & 0x3f) | 0x80)
      bytes.push(((code >> 6) & 0x3f) | 0x80)
      bytes.push((code & 0x3f) | 0x80)
    } else if (code >= 2048 && code <= 65535) {
      bytes.push((code >> 12) | 0xe0)
      bytes.push(((code >> 6) & 0x3f) | 0x80)
      bytes.push((code & 0x3f) | 0x80)
    } else if (code >= 128 && code <= 2047) {
      bytes.push((code >> 6) | 0xc0)
      bytes.push((code & 0x3f) | 0x80)
    } else {
      bytes.push(code)
    }
  }
  return bytes
}
// 补位
function padStart(str, len, prefix) {
  return ((new Array(len + 1).join(prefix)) + str).slice(-len) //  也可用 new Array(len+1).fill(0)
}
// 解码
function decodeUtf8(str) {
  let strValue = ''
  let obStr = [...str].map((ch)=> {
    // 一位16进制数 转二进制 需要四位来表示  补全位数
    return padStart(parseInt(ch,16).toString(2), 4, 0)
  }).join('').match(/\d{8}/g).map((item)=> parseInt(item,2))
  for (var i = 0; i < obStr.length; ) {

    let code = obStr[i]
    let code1, code2, code3, code4, hex
    // 比较 前4 位 parseInt(11110000,2) = 240 4个字节
    if ((code & 240) == 240) {
      code1 = (code & 0x03).toString(2)
      code2 = padStart((obStr[i + 1] & 0x3f).toString(2),6, '0')
      code3 = padStart((obStr[i + 2] & 0x3f).toString(2),6, '0')
      code4 = padStart((obStr[i + 3] & 0x3f).toString(2),6, '0')
      hex = parseInt((code1 + code2 + code3 + code4),2)
      strValue = strValue + String.fromCodePoint(hex)
      i = i + 4
    } else if ((code & 224) == 224) { // 3个字节表
      code1 = (code & 0x07).toString(2)
      code2 = padStart((obStr[i + 1] & 0x3f).toString(2),6, '0')
      code3 = padStart((obStr[i + 2]& 0x3f).toString(2),6, '0')
      hex = parseInt((code1 + code2 + code3),2)
      strValue = strValue + String.fromCodePoint(hex)
      i = i + 3
    } else if ((code & 192) == 192) {// 2个字节表
      code1 = (code & 0x0f).toString(2)
      code2 = padStart((obStr[i + 1] & 0x3f).toString(2),6, '0')
      hex = parseInt((obStr + code2),2)
      strValue = strValue + String.fromCodePoint(hex)
      i = i + 2
    } else {
      hex = code
      strValue = strValue + String.fromCodePoint(code)
      i = i + 1
    }
  }
  return strValue
}
// byte to hex
function transferHex(bytes) {
  let s = ''
  bytes &&
    bytes.forEach(ch => {
      s = s + ch.toString(16)
    })
  return s
}
let text = "罗小步 啊哈哈 𠮷 ssdf 34534 ASD"
let strHax = transferHex(encodeUtf8(text))
console.log(strHax)
let str = decodeUtf8(strHax)
console.log(str)

console.log("test ok?", text === str)
复制代码

3. Base64编码

3.1 Base64编码规则

规则:Base64的编码方法要求把每三个8bit的字节转换成四个6bit的字节,然后把6Bit再添两位高位0,组成四个8Bit的字节。

如果要编码的二进制数据不是3的倍数,最后剩下一个或者两个字节Base64会在末尾补零,再在编码的末尾加上一个或者两个‘=’。

每个8bit 编码成:CHARST[paresInt(8bit ,2)]

CHARTS = ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
复制代码
1

编码流程:

1

3.2 Base64编码解码简单实现

const CHARTS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
const prefix = '='
const prefixTwo = 2
const prefixfour = 4
function padEnd(str, len, prefix) {
  return (str + (new Array(len + 1)).join(prefix)).slice(0, len)
}
function padStart(str, len, prefix) {
  return ((new Array(len + 1).join(prefix)) + str).slice(-len)
}
// 编码
function encodeBase64(str){
  let byteStr = ''
  for(let ch of encodeUtf8(str)){ 
    byteStr = byteStr + padStart(ch.toString(2),8,0)
  }
  let rest = byteStr.length % 6 // 余2 就是剩下了一个字节,余 4 就是剩下两个字节
  let restStr = rest === prefixTwo ? '==' :'='
  let prefixzero = rest === prefixTwo ? prefixfour: prefixTwo
  byteStr = padEnd(byteStr , byteStr.length + prefixzero,'0')
  return byteStr.match(/(\d{6})/g).map(val=>parseInt(val,2)).map(val=>CHARTS[val]).join('') + restStr;
}
// 解码
function decodeBase64(str) {
  let matchTime = str.match(/(ha)/g)

  let [...restStr] = str.replace(/=/g,'')
  restStr = restStr.map((item)=> {
    let value = CHARTS.indexOf(item)
    return padStart(value.toString(2),6,0)
  }).join('').match(/(\d{8})/g).map((item)=>parseInt(item,2).toString(16)).join()
  console.log(restStr)
  return decodeUtf8(restStr)
}

let text = "罗小步 啊哈哈 𠮷 ssdf 34534 ASD"
let strHax = encodeBase64(text)
console.log(strHax)
let str = decodeBase64(strHax)
console.log(str)

console.log("test ok?", text === str)
复制代码

Base64的编码方法要求把每三个8bit的字节转换成四个6bit的字节,编码会使数据量变长原来的1/3.

编码方式只是一种对字符集表现的形式。文章用js 简单的实现utf8编码和base64编码。代码实现比较粗糙,理解不准确之处,还请教正。欢迎一起讨论学习。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK