# 二进制基础

# 一 、 原码 反码 补码

对于有符号的变量而言,首位代表符号,0 为正数,1 为负数。

原码:十进制转化为二进制

反码:负数的符号不变,其他位取反。

补码:负数的反码加一。

另外对于 8 位二进制而言,储存范围为 [-127,127],-128 无原码以及反码,但是有补码为 10000000。而 10000000 在反码以及原码中表示 0.

计算机内存储存的数据为补码,在进行减法运算的时候则是转化为加法计算,比如 1-1 转化为 1+(-1),将 1 与 - 1 的补码相加然后转为原码就是最终结果。如果加出了九位则将第一位舍弃。

相关资料:

【计算机基础】轻松学会原码、反码、补码的转换规则以及计算机内有符号数的计算方式!_哔哩哔哩_bilibili](https://www.bilibili.com/video/BV1aB4y177Ef/

深入理解计算机中的原码、补码、反码 - 知乎 (zhihu.com)

https://zhuanlan.zhihu.com/p/118432554

# 二、二进制逻辑运算

# 1. 或与非,异或

  1. 或(&):两个都为 1 时才为 1
  2. 与(|): 有一个为 1 就为 1
  3. 非(!):取反
  4. 异或(^): 不同为 1,相同为 0,自己与自己取异或为 0

# 2. 二进制移位

  1. 左移运算:二进制向左移动,最后一位补零。相当于乘 2 的 n 次方,n 为移动的位数 a<<n (a 为数,n 为移动位数)
  2. 有符号右移运算:对于正数,右移第一位补零,对于负数,右移第一位补一,相当于除于 2 的 n 次方,n 为移动的位数并向下取整。a>>n
  3. 无符号右移运算:右移第一位补零 a>>>n

相关资料:

【计算机基础】二进制的逻辑运算 - 与、或、非、异或、同或!_哔哩哔哩_bilibili](https://www.bilibili.com/video/BV1hY4y1T7au/

【计算机知识】移位运算 —— 左移运算、右移运算、无符号右移!_哔哩哔哩_bilibili](https://www.bilibili.com/video/BV1i14y1W7aF/

# 三。小数的储存以及储存及读取模式

小数的储存分为定点数以及浮点数储存。

  1. 定点数,小数点固定,一般不采用这种方法储存因为储存数据的范围小。将整数部分转为二进制,将小数部分转为二进制,然后组合。小数点在整数后面,小数前面。小数转为二进制的方法为乘 2 然后取整,第一位为 2 的 - 1 次方,第二位为 2 的 - 2 次方,依次类推。有些小数比如 0.2 无法完全转为二进制,会出现精度损失。这也是为什么 JavaScript 中 0.1+0.2!=0.3。如果用 8 位储存一个数据,第一位储存数据的正负,后四位储存整数,最后三位储存小数。那么整数部分就只能储存 1111 即负 - 16 到 15,小数部分只能储存 111,即 2 的 - 3 次方加 2 的 - 2 次方加 2 的 - 1 次方为 0.875 也即 0 到 0.875。使用定点数储存数据储存的范围不大,故一般不采用此方法储存小数。
  2. 浮点数,采用科学计数法储存小数,小数点不固定,一般使用 ieee754 标准。浮点数分为 float(单精度),double(双精度)。将小数转化为二进制然后用科学计数法表示,只不过与十进制的科学计数法不同,二进制下为基数为 2 而不是 10。对于 float,用四字节,32 位储存,第一位为储存数字的正负,后面 8 位储存阶码也即指数部分,指数加上偏移量 127 再转二进制即为阶码。剩下 23 位储存小数部分,因为整数一定为 1 故不额外花一位储存整数部分。double 用八字节,64 位储存。第一位储存数字正负,后面 11 位储存阶码,double 的偏移量为 1023. 剩下的储存小数部分。

储存模式分为大端法以及小端法。

  1. 小端法:数据低位储存低位地址
  2. 大端法:与小端法反过来

储存和读取一般使用小端发

相关资料:

https://www.bilibili.com/video/BV1Le4y137gU

什么是定点数? - 知乎 (zhihu.com)

什么是定点数? - 知乎 (zhihu.com)

# 四、File,Blob,ArrayBuffer,Buffer

Blob: 全称 binary large object。首先 blob 是一个对象,其次 blob 储存了不可以更改的二进制数据。

File: file 对象是一个特殊的 blob,或者说 blob 是 file 原型链上的原型对象。file 相对于 blob 多了两个属性一个是文件名字,一个是文件最近一次被修改的时间戳。另外 file 对象只在浏览器中有,在 nodejs 中是没有的。

1
File.prototype.__proto__==Blob.prototype //true

ArrayBuffer:是原始的二进制数据缓冲区,与 blob 一样储存了二进制数据,与 blob 不同的是 ArrayBuffer 不能设置 MIME 类型。虽然 ArrayBuffer 与 blob 都无法更改数据,但是 ArrayB 可以使用 TypedArray 对象和 DataView 对象对二进制数据进行更改。

Buffer:nodejs 中处理二进制数据的对象,与 ArrayBuffer 可以互相转化。

# 1. 数据的获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/*
file对象的获取
*/
let file = new File(['123'],'123.txt') //第一个参数可以是blob,第二个参数为文件名。

//使用input标签获取file

<input type="file" id="input"> //将type设置成file即可
let input = document.querySelector('#input')
input.onchange=(e)=>{ //input上onchange方法,当用户上传了文件就会调用这个事件
console.log(e.target.files) //这是一个数组储存用户上传的所有文件。
console.log(e.target.files[0].__proto__==File.prototype) //true,数据类型为file
}

//拖拽获取file

<div id="drag"></div>
let drag = document.querySelector('#drag')
drag.ondragover=(e)=>{
e.preventDefault() //阻止事件默认行为
}
drag.ondrop=(e)=>{
e.preventDefault()
console.log(e.dataTransfer.files)
}

//使用File System Access获取file

drag.addEventListener('click',async()=>{
let filelist = await showOpenFilePicker() //会返回一个数组,储存着用户上传的文件
let file = await filelist[0].getFile() //会返回一个file对象。
file.__proto__ == File.prototype // true,说明这个是file对象。

})

/*
Blob对象的获取
*/

let blob = new Blob(array,options)
array:由ArrayBuffer,ArrayBufferView,Blob,Dom等对象构成
options:type:MIME类型

/*
Buffer的获取
*/
在nodejs中使用fs模块读取文件。
fs.readFile(filename,(err,data)=>{
console.log(data) //此时的data是一个Buffer对象
})

buffer.tostring('utf-8') //buffer转成字符串

由ArrayBuffer转化而来。
let buffer = Buffer.from(ArrayBuffer)
/*
ArrayBuffer的获取
*/
ArrayBuffer一般是由file,blob或者Buffer转化的。

let read = new FileReader()
read.readAsArrayBuffer(blob) //这里的参数可以传blob以及file,同时这是异步,可以调用read的onload事件,读取完成后就可以获取。
read.onload = ((res)=>{
console.log(res) //res为ArrayBuffer
})

由Buffer转化
let ArrayBuffer = Buffer.from(Buffer).buffer

# 2. 转化为 url 或者 base64

对于图片或者视频如果要展示出来需要在 img 标签或者 video 标签上的 src 放上 url 链接或者 base64. 如果想要下载可以在 a 链接上添加 download 属性即可下载。

FileReader.readAsDataURL: 异步方法,通过 FileReader.onload 的事件获取值,值为一段 data:base64 字符串

URL.createObjectURL: 同步方法,获取到的是储存在内存中的 url。不用的话需手动释放防止内存泄露,获取速度要比 FileReader.readAsDataURL 要快.

1
2
3
4
5
6
let read = new FileReader() 
read.readAsDataURL(blob) //可以传入blob或者file对象
read.onload=((e)=>{
console.log(e.target.result) // 一段data:64字符串,可以在img以及video的src上挂载。
})
let url = URL.createObjectURL(blob)

# 3. Blob 和 ArrayBuffer 的切片处理

1
2
3
用法与字符串和数组的slice一致,第三个参数为MIME类型设置,如果没有则使用原blob的MIME类型
let newblob = oldblob.slice(start,end,type)
let newArrayBuffer = oldArrayBuffer,slice(start,end)

相关资料

[谈谈 JS 二进制:File、Blob、FileReader、ArrayBuffer、Base64 - 掘金 (juejin.cn)](https://juejin.cn/post/7148254347401363463

[JS 的二进制家族:base64、File、Blob、ArrayBuffer 的关系 - 掘金 (juejin.cn)](https://juejin.cn/post/6844904069165744135

https://juejin.cn/post/7203701875530039357

【Web 基础】Blob Url & Data Url_哔哩哔哩_bilibili](https://www.bilibili.com/video/BV1Kv4y1471j/

# 五、ArrayBuffer,TypedArray,DataView

ArrayBuffer 是二进制数据,如果想要读取或者修改还需要 TypedArray 和 DataView 的视图。视图可以理解为以何种方式读取。比如如果 ArrayBuffer 有四字节的数据,可以一字节一字节(8 位)的读取,也可以二字节二字节(16)的读取,这取决于数据是以何种方式储存。如果这个 ArrayBuffer 储存的是图片数据,众所周知图片每个像素由 rgba 构成,每一个数据的大小在 0-255 之间,所以这时候要用 uint8Array,无符号 8 位一字节读取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
let arraybuffer = new ArrayBuffer(8) //创建一个8字节的数据,数据默认是0
let uint8 = new Uint8Array(arraybuffer) //以uint8的方式读取
//ArrayBuffer有8字节,因为是以8int方式读取所以这里有8个数据,因为是无符号,所以取值范围为0-255
uint8[0] = 1 //修改第一个为1
console.log(uint8)//[1, 0, 0, 0, 0, 0, 0, 0]
//设值大于范围,转成二进制只取后8位。256的二进制是1 0000 0000,这里只取后八位。0000 0000即0
uint8[0] = 256
console.log(uint8)//[0, 0, 0, 0, 0, 0, 0, 0]
//如果是负数的话,取该负数的补码。因为是无符号所以负数的补码直接把所有位取反就行,补码是反码加一。
uint8[0] = -1 // -1反码1111 1110,补码1111 1111即255

set方法 可以复制一段数据
let arraybuffer = new ArrayBuffer(8)
let uint8 = new Uint8Array(arraybuffer)
let uint = new Uint8Array(4)
uint[0] = 1
uint8.set(uint) //不带第二个参数,默认从第一个开始复制
console.log(uint8) //[1, 0, 0, 0, 0, 0, 0, 0]
uint8.set(uint,4) //从第五个数据开始复制
console.log(uint8) //[0, 0, 0, 0, 1, 0, 0, 0]

如果后端发来一段一段ArrayBuffer将其合并
function (buffers){
let totalLength = 0
for(let buffer of buffers){
totalLength += buffer.length
} //获取总共ArrayBuffer的长度
//创建一个ArrayBuffer长度的ArrayBuffer,注意如果没有这么多连续的内存空间,创建会失败。
let uint = new uint8Array(totalLength)
let offset = 0 //记录每段内容的下标
for(let buffer of buffers){
uint,set(buffer,offset)
offset+= buffer.length
}
}

# 1. TypedArray

​ TypedArray 由以下九种类型组成。TypedArray 的特点在于每个数据的类型相同,适合处理一些简单的二进制数据,比如图片的 rgba 数据是由 8 位无符号整数组成。

  • Int8Array :8 位有符号整数,长度 1 个字节。
  • Uint8Array :8 位无符号整数,长度 1 个字节。
  • Uint8ClampedArray :8 位无符号整数,长度 1 个字节,溢出处理不同。
  • Int16Array :16 位有符号整数,长度 2 个字节。
  • Uint16Array :16 位无符号整数,长度 2 个字节。
  • Int32Array :32 位有符号整数,长度 4 个字节。
  • Uint32Array :32 位无符号整数,长度 4 个字节。
  • Float32Array :32 位浮点数,长度 4 个字节。
  • Float64Array :64 位浮点数,长度 8 个字节。