文件的二进制本质
计算机是二进制的世界,无论是文本文件,图片文件还是视频文件音频文件,底层都是以二进制的形式存储的。如果我们想探究文件底层的二进制到底是什么样的,可以借助VS code的Hex Editor插件来查看。这里演示两种文件的底层二进制,一种是txt文本文件,一种是PNG图片文件。
首先看一下文本文件里面的字符串,几个简单字母和数字:
选中文件,右键->Open With->Hex Editor,以十六进制打开文件的内容,这里之所以用十六进制显示,是因为一个十六进制数字等价于四个二进制数字,一个十六进制数字比四个二进制数字更易阅读。
这里我将鼠标点到字母c上,可以看到它对应的十六进制数字是63,这个其实就是小写字母c对应的十六进制ASCII码。下面截取了一部分ASCII码作为参考:
| 二进制 | 八进制 | 十进制 | 十六进制 | 字符 | 解释 |
|---|---|---|---|---|---|
| 0110 0001 | 141 | 97 | 0x61 | a | 小写字母a |
| 0110 0010 | 142 | 98 | 0x62 | b | 小写字母b |
| 0110 0011 | 143 | 99 | 0x63 | c | 小写字母c |
| 0110 0100 | 144 | 100 | 0x64 | d | 小写字母d |
| 0110 0101 | 145 | 101 | 0x65 | e | 小写字母e |
| 0110 0110 | 146 | 102 | 0x66 | f | 小写字母f |
| 0110 0111 | 147 | 103 | 0x67 | g | 小写字母g |
看完了文本文件,再来用Hex Editor查看一个PNG文件的内容。图片文件原始内容如下:
使用Hex Editor查看文件的十六进制:
这个PNG文件的二进制内容主要分为四部分:
-
前8个字节是文件魔数,表示这个文件是一个
PNG文件, -
第9至33个字节是
IHDR(Image Header),文件头数据块,包含文件的基本信息和结构信息, -
第34至2773字节是
IDAT(Image Data),图像数据块,存储实际的图像数据, -
第2774字节至文件结束是
IEND(Image END),图像结束数据块,用来标记图像结束。
如果想要知道PNG文件都有哪些文件块,可以借助www.nayuki.io/page/png-fi… 网站查看。
Blob和File
Blob(Binary Large Object)在JavaScript中表示一个不可变的、原始数据的类文件对象,原始数据指的就是二进制数据。
File是JavaScript中表示文件数据的对象,它继承自Blob并且扩展了文件名,最后修改时间等信息。
这里先分别创建一个Blob对象和File对象,点击按钮之后在浏览器控制台打印这两个对象的信息。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!-- 引入样式 -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- 引入组件库 -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
</head>
<body>
<div id="app">
<el-button :plain="true" @click="createFileAndBlob">创建</el-button>
</div>
<script>
new Vue({
el: '#app',
methods: {
async createFileAndBlob() {
let blob = new Blob(['123']);
console.log(blob)
let file = new File(['123'], '123.txt');
console.log(file)
console.log(`blob原型和file原型的原型是否相等: ${Object.getPrototypeOf(blob)===Object.getPrototypeOf(Object.getPrototypeOf(file))}`); }
}
})
</script>
</body>
</html>
Blob对象
File对象
从原型链也可以看出,File的原型的原型是Blob。打印结果也说明了这个关系:
blob原型和file原型的原型是否相等: true
arrayBuffer方法
arrayBuffer方法返回一个二进制数据的ArrayBuffer,blob和file都可以调用,修改一下前面的createFileAndBlob方法,把blob和file的arrayBuffer结果打印出来。
async createFileAndBlob() {
let blob = new Blob(['123']);
let file = new File(['123'], '123.txt');
console.log(await blob.arrayBuffer())
console.log(await file.arrayBuffer())
}
从浏览器控制台查看结果,两个arrayBuffer基本一致。其实file对象身上并没有arrayBuffer方法,它调用的是原型链上Blob的arrayBuffer方法,所以两个arrayBuffer的返回结果才会如此的相似。
arrayBuffer的[[Int8Array]]存储了字节数据的具体信息,49、50、51表示的是字符1、2、3的十进制ASCII码,具体可以参考下面:
| 二进制 | 八进制 | 十进制 | 十六进制 | 字符 | 解释 |
|---|---|---|---|---|---|
| 0010 1111 | 057 | 47 | 0x2F | / | 斜杠 |
| 0011 0000 | 060 | 48 | 0x30 | 0 | 字符0 |
| 0011 0001 | 061 | 49 | 0x31 | 1 | 字符1 |
| 0011 0010 | 062 | 50 | 0x32 | 2 | 字符2 |
| 0011 0011 | 063 | 51 | 0x33 | 3 | 字符3 |
| 0011 0100 | 064 | 52 | 0x34 | 4 | 字符4 |
通过FileReader获取ArrayBuffer
FileReader对象可以读取文件内容,创建一个FileReader对象,调用readAsArrayBuffer方法来读取文件对象,当文件内容读取完毕之后就会进入onloadend回调,在回调函数中可以通过事件的taeget.result来获取ArrayBuffer。
async createFileAndBlob() {
let file = new File(['123'], '123.txt');
const reader = new FileReader();
reader.onloadend = (event) => {
console.log(event.target.result)
}
reader.readAsArrayBuffer(file);
}
DataView
ArrayBuffer中的数据只能查看,不能修改,如果想要修改数据,可以使用DataView。它是一个可以从二进制 ArrayBuffer 对象中读写多种数值类型的底层接口。
修改之前先看一下原始的数据:
let file = new File(['123'], '123.txt');
let arrayBuffer = await file.arrayBuffer();
console.log(arrayBuffer);
修改第二个字节的内容:
let file = new File(['123'], '123.txt');
let arrayBuffer = await file.arrayBuffer();
let dv = new DataView(arrayBuffer);
// 修改第二个字节,改成57,
dv.setInt8(1, 57);
console.log(arrayBuffer);
可以看到ArrayBuffer中第二个字节已经变成57了。