JS 中的二进制,不仅仅于JS

eddie, jsbinaryencode
Back

进制数的表示

进制数字面量

二进制数的表示法

以八个二进制位的举例。

原码

第 1 位表示符号位,0 表示正数,1 表示负数。后 7 位表示真值

0000 0010 // 2 原码
1000 0010 // -2 原码

反码

对于正数:是其原码本身不变。

对于负数:符号位不变,后 7 位的真值按位取反。

0000 0010 // 2 原码
0000 0010 // 2 反码

1000 0010 // -2 原码
1111 1101 // -2 反码

补码

对于正数:是其原码本身不变。

对于负数:在原码的基础上,取其反码,再加 1。

0000 0010 // 2 原码
0000 0010 // 2 反码
0000 0010 // 2 补码

1000 0010 // -2 原码
1111 1101 // -2 反码
1111 1110 // -2 补码

以上结论就是,正数的表示法都一样,负数则不同。

为什么会有反码、补码的存在?

为了减少计算机运算逻辑的复杂度,所以将符号位也加入运算,补码的表示可以做到只用加法就进行加减法的运算。

// 1 + 2 补码运算
0000 0001 + 0000 0010 = 0000 0011
0000 0011(补码) = 0000 0011(反码) = 0000 0011(原码) = 3

// 1 + (-2) 补码运算
0000 0001 + 1111 1110 = 1111 1111
1111 1111(补码) = 1111 1110(反码) = 1000 0001(原码) = -1

进制转换

十进制转其他进制

toString

6..toString(2) // '110'
9..toString(8) // '11'

其他进制十进制转

parseInt

parseInt('110', 2) // 6
parseInt('11', 8) // 9

Number

Number('0b110') // 6
Number('0o11') // 9

+(一元运算符)

+'0b110' // 6
+'0o11' // 9

二进制的位运算

位运算符将它的操作数视为 32 位元的二进制串数。 位运算符在此表示上执行运算。JavaScript 数值。

JS 位运算符

按位与(&)

a & b:在 a,b 的位表示中,每一个对应的位都为 1 则返回 1,否则返回 0。

// 2 & 6 = 2
010
110
---
010

妙用:

按位或(|)

a | b:在 a,b 的位表示中,每一个对应的位,只要有一个为 1 则返回 1,否则返回 0。

// 2 | 6 = 6
010
110
---
110

按位异或(^)

a ^ b:在 a,b 的位表示中,每一个对应的位,不相同则返回 1,相同则返回 0。

// 2 ^ 6 = 4
010
110
---
100

妙用:

按位非(~)

~a:反转被操作数的位

// ~6 = -7
0000 0110
---
1111 1001 // 反转后 = 补码
1111 1000 // 反码
1000 0111 // 原码

妙用:

左移(<<)

a << b:将 a 的二进制串向左移动 b 位,右边移入 0

// 6 << 2 = 24
0000 0110
---
0001 1000

// -6 << 2 = -24
1000 0110
---
1001 1000

妙用:

算术右移(>>)

a >> b:把 a 的二进制表示向右移动 b 位,丢弃被移出的所有位

// 6 >> 2 = 1
0000 0110
---
0000 0001

// -6 >> 2 = -2
1000 0110 // 原码
1111 1001 // 反码
1111 1010 // 补码
1111 1110 // 右移2位
1111 1101 // 右移后反码
---
1000 0010 // 移位后原码

妙用:

无符号右移(>>>)

a >>> b:把 a 的二进制表示向右移动 b 位,丢弃被移出的所有位,并把左边空出的位都填充为 0

// 6 >>> 2 = 1
0000 0110
---
0000 0001

// -6 >>> 2 = 1073741822
10000000 00000000 00000000 00000110 // 32位原码
11111111 11111111 11111111 11111001 // 反码
11111111 11111111 11111111 11111010 // 补码
00111111 11111111 11111111 11111110 // 移位
---
00111111 11111111 11111111 11111110 // 移位后原码

位运算有什么意义?

位运算能够高效率的完成数值的计算。

因为机器最终是转化为二进制去运算,位运算过程则更为直接。底层使用补码运算加法和移位,实现不仅仅加法的其他运算(减乘除模法等)。

💡虽然位运算妙用更简洁更高效,但是不建议在业务代码中组合使用,会影响可读性。位运算更适合用在一些性能要求高的底层逻辑代码。

位掩码

位掩码(BitMask),是”位(Bit)“和”掩码(Mask)“的组合词。”位“指代着二进制数据当中的二进制位,而”掩码“指的是一串用于与目标数据进行按位操作的二进制数字。

位掩码就是位运算的一个应用场景。
举一个例子:一个4位二进制的权限值,依次对应 新增删除修改查询

let createFlag = 0b1000
let deleteFlag = 0b0100
let modifyFlag = 0b0010
let queryFlag = 0b00001
let permission = 0b0000 // 权限初始值

// 更改 '删除' 的权限
permission = permission ^ deleteFlag  // 0b0100

// 更改 '查询' 的权限
permission = permission ^ queryFlag  // 0b0101

// 读取 
const allowDelete = Boolean(permission & deleteFlag) // true
const allowQuery = Boolean(permission & queryFlag) // true
const allowModify = Boolean(permission & modifyFlag) // false

二进制缓冲区

ArrayBuffer

固定长度的原始二进制数据缓冲区(字节数组)。不能直接操作,只能通过类型视图(DataView、TypedArray)操作。

构造函数传参一个长度,创建以字节(8 位)为单位的数组。

// 8位 * 16字节 = 128位
const buffer = new ArrayBuffer(16);

// 128位 / 8位 = 长度16
const i8 = new Int8Array(buffer)
console.log(i8.length) // 16

// 128位 / 32位 = 长度4
const i32 = new Int32Array(buffer)
console.log(i32.length) // 4

TypedArray

作为 ArrayBuffer 类型化的视图,提供方便地读写二进制数据的操作。TypedArray - JavaScript | MDN

初始化接收 固定长度或原始 buffer 数组,以对应的类型数组的设置范围内写入和读取。

Uint8Array

数组每个元素大小为一个字节,值范围 0 到 255,无符号的整型(正整数)

const u8 = new Uint8Array(6)
u8[0] = 100
u8[1] = 300
console.log(u8[0]) // 100
console.log(u8[1]) // ?(越界赋值会怎么样)

// 解析:

DataView

是一个可以从 ArrayBuffer 对象中读写多种数值类型的底层接口视图。 DataView - JavaScript | MDN

const buffer = new ArrayBuffer(32)
const dv = new DataView(buffer)

dv.setInt8(0, 100)
dv.setInt16(1, 200)

console.log(
    dv.getInt8(0),  // 100
    dv.getInt16(0), // ?(Int16 取 Int8)
    dv.getInt8(1),  // 0
    dv.getInt16(1), // 200
)

// 解析:

二进制编码

编码类型

是一种基于拉丁字母的字符编码标准,用于电子通信。它最初是美国信息交换标准代码,使用 7 位二进制数来表示 128 个字符。 ASCII-wiki

特征:仅表示英文及其符号;一个字符占用了一个字节,但是实际用了 7 位,第一位始终在为 0。

是计算机科学领域里的一项业界标准,包括字符集、编码方案等。Unicode 使用两个字节来表示一个字符,可以表示几乎所有的语言文字。

特征:是一种编码方式;UCS-2(unicode 的标准实现)占用两个字节,但有一些字符实际只使用了一个字节。

SYMBL - Unicode 查询

是一种针对 Unicode 的可变长度字符编码。它使用一至四个字节来表示每个字符,节省了存储空间,也方便了传输。

是一种用于传输二进制数据的编码方式,可以将任意二进制数据编码成可打印字符,便于传输和处理。

base64 要求将每三个 8bits 字节转换为四个 6bit 的字节,再根据这 4 个字节的十进制在索引表中查找对应的值。

The Base64 algorithm description

JS 的字符编码方法

charCodeAt 与 fromCharCode

charCodeAt 返回一个字符的 unicode 编码(与 ASCII 重合的字符则一致)。

fromCharCode 返回 unicode 编码对应的字符。

const char = '编码'
const charcode = [char.charCodeAt(0), char.charCodeAt(1)]

console.log(charcode)
// [32534, 30721]
console.log(String.fromCharCode(...charcode))
// '编码'

TextEncoder 与 TextDecoder

TextEncoder 把字符转为 UTF-8 的字节流。

TextDecoder 则解码对应的字节流为字符。

const encoder = new TextEncoder()
const view = encoder.encode('编码')
console.log(view)
// [231, 188, 150, 231, 160, 129]

const decoder = new TextDecoder()
const chars = decoder.decode(view)
console.log(chars)
// '编码'

const u8 = new Uint8Array(3)
encoder.encodeInto('编码', u8)
console.log(u8) // [231, 188, 150]
console.log(
    decoder.decode(u8), // '编'
)

atob 与 atob

btoa 将字符串编码为 base64(仅支持 ascii 字符,不支持其他如中文)。

atob 解码 base64 编码的字符串。

const charsEn = 'base64'
const base64En = btoa(charsEn)
console.log(base64En) // 'YmFzZTY0'
console.log(atob(base64En)) // 'base64'

const charsZh = '编码'
const base64Zh = btoa(encodeURIComponent(charsZh))
console.log(base64Zh) // 'JUU3JUJDJTk2JUU3JUEwJTgx'
console.log(
    decodeURIComponent(atob(base64Zh)), // '编码'
)

二进制的应用

上传下载

Ajax、Fetch 和 WebSocket 接收和上传 arraybuffer 二进制数组

图像、视频音频处理

canval、webGL、blob 等

协议

如 protobuf 等

Links

参考文章

© eddie.RSS