Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

聊聊编码那些事,顺带实现base64 #4

Open
careteenL opened this issue Sep 6, 2019 · 0 comments
Open

聊聊编码那些事,顺带实现base64 #4

careteenL opened this issue Sep 6, 2019 · 0 comments

Comments

@careteenL
Copy link
Owner

目录

前言

日常工作中,频繁的使用base64取代小图标,以便减少HTTP请求进而达到性能优化的目的。基于此来聊聊编码的发展、为什么需要base64以及如何实现base64。此文章首发于聊聊编码那些事,顺带实现base64转载请注明来源。

进制间的转换

对任意进制的数进行任意进制转换

Number.prototype.toString(radix)

将任意进制数转换为十进制数

parseInt(string, radix)

几道关于parseInt的面试题

说到parseInt,不得不提到一个很有意思的面试题

// 会输出什么?
[1, 2, 3].map(parseInt)
// => 1, NaN, NaN

map方法第一个参数为函数,函数有三个参数,array.map((item, index, array) => { ... })

实际上相当于

function fn (item, index) {
  return parseInt(item, index)
}
[1, 2, 3].map(fn)
// parseInt迭代过程相当于如下
// parseInt(1, 0) => 1
// parseInt(2, 1) => NaN
// parseInt(3, 2) => NaN

再来看一个类似的面试题

// 会输出什么?
'1 2 3'.replace(/\d/g, parseInt)
// => 1, NaN, 3

replace方法第二个参数若是一个函数,函数会有若干个参数。第一个为匹配模式的字符串;第二个为与模式中子表达式匹配的字符串,可以有零个或多个这样的参数。

实际上相当于如下

function fn (...args) {
  // 只会取前两个参数
  return parseInt(args[0], args[1])
}
'1 2 3'.replace(/\d/g, fn)
// parseInt迭代过程相当于如下
// parseInt('1', 0) => 1
// parseInt('2', 2) => NaN
// parseInt('3', 4) => 3

其实在mdn中对parseInt/map/replace已经讲解的很详细,期望大家在工作之余不要太过浮躁,别做伸手党,静下心来啃一下文档并多做实践,很多面试题自然会迎刃而解。

编码发展历史

ASCII

GBK2312

GBK

GB18030/DBCS

Unicode

UTF-8

现在的标准,有如下特点

  • UTF-8 就是在互联网上使用最广的一种 Unicode 的实现方式
  • UTF-8就是每次以8个位为单位传输数据
  • 而UTF-16就是每次 16 个位
  • UTF-8 最大的一个特点,就是它是一种变长的编码方式
  • Unicode 一个中文字符占 2 个字节,而 UTF-8 一个中文字符占 3 个字节
  • UTF-8 是 Unicode 的实现方式之一

base64编码

为什么需要base64

在开发时,经常会有一些小图标图片,每一个图片都会有一次HTTP请求,由于浏览器对同一个域名的并发数量有限制,所以我们应该尽可能减少HTTP请求个数。

本文主要讲解编码相关,那就只讲解从编码入手如何去减少HTTP请求。

在计算机内部,任何信息最终都是使用一系列二进制存储,图片也不例外。

而且在img标签的src属性后跟上一个base64字符,如果该字符有效,那么会正常显示图片。

如何实现base64

以下涉及的所有代码均在仓库中,感兴趣的可以自取。

读取buffer转为json对象

首先准备一个2.txt文件。

冯兰兰啊我说今晚月色这么美,你说是的。

case.js代码

const fs = require('mz/fs')
const path = require('path')

// 读取成buffer对象
async function read2JSON () {
   let ret = await fs.readFile(path.resolve(__dirname, '2.txt'))
   console.log(ret.toJSON())
   return ret.toJSON()
}
read2JSON()
// => { type: 'Buffer', data: [ 229, 134, 175, 229... ] }

上面的依赖mz/fs已经将fs都包装成promise,所以我们能写的更像同步。

readFile函数如果第二个参数没有指定会读取成一个buffer流,是由一个个16进制数组合在一起的。

buffer.toJSON可以将一个buffer流转为一个json对象,十六进制也会被转十进制。如上输出所示。

将10进制转为2进制

十进制转为二进制可以通过Number.toString(2)方法

// 将10进制转为2进制
async function data2b () {
  let data = await read2JSON()
  let ret = []
  data.data.forEach(item => {
    ret.push(item.toString(2))
  })
  console.log(ret)
  return ret
}
data2b()
// => [ '11100101', '10000110', '10101111', '11100101'...]

将2进制拼一起3*8然后分隔成4*6

一个汉字在UTF-8规范中由三个字节组成,一个字节由8个二进制物理位构成。所以一个汉字实际占用内存3*8base64中我们实际需要6个物理位表示一个字节即2**6,所以做重新分割4*6

async function split () {
  let data = await data2b()
  let dataStr = data.join('')
  let ret = []

  let splitUnit = 6
  let flag = 0
  while (flag < dataStr.length) {
    ret.push(dataStr.substr(flag, splitUnit))
    flag = flag + splitUnit
  }
  console.log(ret)
  return ret
}
split()
// => [ '111001', '011000', '011010', '101111'...]

然后将2进制转成10进制

二进制转为十进制可以通过parseInt(string, 2)方法

async function data20 () {
  let data = await split()
  let ret = data.map(item => {
    return parseInt(item, 2)
  })
  console.log(ret)
  return ret
}
data20()
// => [ 57, 24, 26, 47, 57, 24, 22, 48, 57, 24, 22, 48 ]

base64码

base64中的64实际上是根据2**6所来,表示则由大写字母、小写字母、数字、+/构成。

const lowerCases = 'abcdefghijklmnopqrstuvwxyz'
const numbers = '0123456789'
const base64lib = `${lowerCases.toUpperCase()}${lowerCases}${numbers}+/`
console.log(base64lib)
// => ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/

取到每一个base64码

然后我们则可以取到每一个base64

async function main () {
  let data = await data20()
  let ret = []
  data.forEach(item => {
    ret.push(base64lib[item])
  })
  console.log(ret.join(''))
  return ret.join()
}

main()
// => 5Yav5YWw5YWw5ZWK5oiR6K+05LuK5pma5pyI6Imy6L+Z5LmI576O77yM5L2g6K+05piv55qE44CC

我们可以前往base64在线转码解码进行验证。

encoding-base64-decode

简化代码

对以上思路进行代码简化

const CHARTS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
function transfer (str) {
  let buf = Buffer.from(str)
  let result = ''
  for(let b of buf){
    result += b.toString(2)
  }
  return result.match(/(\d{6})/g).map(val => parseInt(val, 2)).map(val => CHARTS[val]).join('')
}
let fl = transfer('冯')
console.log(fl) // => 5Yav

小结

如上我们可以实现将中文转为base64,同理我们也可以转换图片。

async function read2JSON () {
  // let ret = await fs.readFile(path.resolve(__dirname, '2.txt'))
  // 读取图片
  let ret = await fs.readFile(path.resolve(__dirname, '../assets/encoding-base64-example.png'))
  console.log(ret.toJSON())
  return ret.toJSON()
}

特别的: 由于将2进制拼一起3*8然后分隔成4*6,原来存储一个汉字需要三个字节,现在需要四个字节存储,所以转换为base64后会比之前大3/1

下面笑脸图片则是由img的src属性展示的(github似乎将base64过滤了,并没有展示),不过本文并没有实现图片转base64,因为其逻辑较为复杂,但是本文讲解了大致思路,感兴趣的可再做深究。

encoding-base64-example

encoding-base64-code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant