在使用计算机的过程中,我们无时无刻在接触着各种各样的文件,一些初学编程的同学可能也常常会听说文本文件 和二进制文件这两个概念,但是这两类文件之间又有着什么区别呢?既然文件会被分为这两种类型,那为什么又有人说文件本质都是二进制数据呢?
以及一些Java的初学者在学习如何读写文件时,可能会被字节流 和字符流 弄得晕头转向:为什么文件可以是以字符串形式读取,也可以是byte
类型读取?
事实上,这些问题都归结到了计算机文件的储存形式上了,只要我们弄清楚了文件在计算机中本质储存形式,以及文本文件和二进制文件是怎么划分的,上述的问题就迎刃而解了!
1,文件在计算机中的实际储存形式
事实上,任何文件在计算机中都是以二进制的形式存在的 。也就是说,任何文件在硬盘中,只不过是一堆0
和1
组成的数据罢了。
那么计算机是如何读取它们的呢?事实上在大学计算机基础中我们就学过:计算机存储容量的基本单位是字节(byte
) ,而每个0
或1
就是一个位(bit
) ,位是内存的最小单位 。1
字节由8
个位组成 ,也就是说每个字节其取值范围以二进制表示就是00000000 ~ 11111111
,以十进制表示就是0 ~ 255
。
不过当我们使用8
位二进制数据表示一个字节时,会显得数据非常地长,如果说里面每一个字节都显示成8
位的二进制数据例如00000000
或者01100100
这样的,不仅看得难受,还难以分析。
而如果把这些字节数据都转换成十六进制 ,就简洁多了。0x00
刚好可以表示字节00000000
,而0xff
刚好可以表示字节11111111
,可见使用十六进制表示字节数据不仅更加简洁,而且仅使用2
位长度即可表示字节数据的全部范围。
大家如果安装了VSCode编辑器的话,可以在里面安装Hex Editor
这个插件,这个插件可以读取文件的二进制内容。
首先用VSCode打开一个文件,然后按下Ctrl + Shift + P
呼出命令面板,输入ReOpen Editor With
,点击重新打开编辑器的方式:
选择Hex Editor
:
然后就可以查看文件的二进制内容了!
如下就是一个png
图片的二进制数据:
可见,里面的每一个字节,都是以十六进制显示的,这样便于我们解析文件的原始二进制内容。
ImHex也是一个强大、专业、开源的二进制数据查看和编辑器,大家可以在其Github页面下载:传送门
所以说,计算机储存的文件实质上都是0
和1
,也就是一些二进制的数据。
事实上,几乎所有编程语言都提供了一个数据类型表示字节 这种类型,例如Java、C#以及Go都有
byte
数据类型,而Python在其3
版本也加入了byte
数据类型,因此在任何编程语言中读取文件为二进制数据时,都会使用byte
数组表示。
2,文本文件vs二进制文件
事实上,在计算机中所有的文件应当称作是二进制文件,因为文件的储存形式都是二进制的。
但是根据文件的用途和表现形式的差异,人们通常还是会把文件分为文本文件 和二进制文件这两种。
我们来进一步地看看它们二者的区别。
(1) 文本文件和它的字符集
文本文件事实上在计算机中非常的常见,例如txt
格式的文本文档、各种源代码文件、脚本文件等等。
从计算机发明至今,储存文本就是一个很大的需求,但是计算机设计而来就是二进制的,只能存放二进制数据,那么如何使计算机能够容纳我们这么多类型的字符成为了一个问题。
所以字符集 这个东西就被发明了,我们可以把字符集理解为二进制值和不同字符之间的映射表 。既然字节数据能够表示0 ~ 255
的整数,那么我们让每一个英文字母以及常用的标点符号代表一个数字不就可以了吗?
所以,ASCII字符集就被发明了,最初的ASCII字符集使用7
位二进制表示所有的英文字符和标点符号,也就是说只用一个字节数据的后7
个位表示。例如大写字母A
就用二进制数据01000001
表示,转换成十进制就是65
。这样,当计算机读取到值为01000001
的二进制字节数据时,就会显示成A
了!这样,使用ASCII表就可以表示128
个字符了!
完整的ASCII表大家可以自行查看菜鸟教程:传送门
然而后面又出现了新的问题:不同的国家有不同的字母,因此后续又对ASCII表进行了扩展,增加了更多的字符和二进制字节数值的对应关系。
可是一个字节只能表示0 - 255
的十进制数据,也就是说一个字节最多只能表示256
个字符,对于像中文这样有着成千上万的汉字的情况,又应当如何表示呢?
既然一个字节表示不了太多,我们组合两个字节来表示总可以了吧?的确如此,我们使用两个字节表示一个字符的话,是否就可以表示最多 <math xmlns="http://www.w3.org/1998/Math/MathML"> 25 6 2 256 ^ 2 </math>2562也就是65536
种字符了呢?
因此,我国推出了GB 2312字符集,该字符集共收录6763
个汉字,它使用两个字节表示一个汉字,例如汉字哈
就使用二进制字节表示为10111001 11111110
。
但是后来人们发现GB 2312字符集也不够用了,毕竟汉字还是太多了,因此我国基于该字符集进行扩充,又推出了GB 18030和GBK标准,它们同样是使用两个字节表示一个汉字,但是有时会使用三个字节表示一些生僻字。
随着计算机的发展,全球各个国家都会使用计算机来储存、交换信息,但是各国有着各国的字符集,因此现在需要一个全球性的标准来"一统天下",于是便出现了Unicode字符编码,用于表示世界上几乎所有文字的字符集。其中,UTF-8字符集就是目前使用地最为广泛的字符集,它使用1 ~ 4
个字节来表示字符,并且它是向后兼容ASCII编码的,即ASCII编码中的字符在UTF-8编码中使用一个字节表示,这使得在使用纯英文的文本时,UTF-8 编码的文件和ASCII编码的文件几乎没有区别。
在UTF-8字符集中:
- 常用的英文字符和数字使用
1
个字节编码 - 拉丁文等大多数常见字符使用
2
个字节编码 - 大部分常见的汉字使用
3
个字节编码 - 一些较为罕见的字符使用
4
个字节编码
UTF-8的编码规则保证了它的兼容性和高效性,使得它成为现代计算机系统中广泛使用的字符编码方式。
讲了这么多字符集相关的内容,相信大家也知道了我们的计算机是怎么读取文本文件的了:我们的文本编辑器或者阅读器(例如系统自带的文本文档、Vim、VSCode等等),它们就像是一个翻译官 一样,将二进制字节数据翻译成一个个字符显示出来,使得我们可以阅读,在我们输入并保存文件时,它又会根据我们输入的内容翻译成二进制字节数据保存到计算机硬盘中。而字符集就是这些翻译官的参考手册,它们依照字符集中二进制字节值和字符的对应关系进行上述所说的"翻译"过程,不同的字符集定义了不同的字符编码方式,每种字符集规定了字符到二进制编码的映射关系,以便计算机软件能够正确地解释文本文件中的二进制数据并将其显示为可读的文本内容。
到这里,大家如果遇到文件乱码的情况,相信大家知道是为什么了:使用了和原本内容不匹配的字符集来查看文件
例如一个文本文件以GBK字符集储存了下列内容:
当你以UTF-8字符集打开并阅读时,由于打开的字符集何其内容本身字符集不匹配,就会导致我们的"翻译官"翻译出现错误:
可见,文本文件本质仍然是二进制形式存储的,只不过储存时会按照特定的字符集来编码字符并进行储存,这样才能使得我们正确地阅读到其中的内容。
(2) 二进制文件
那么我们平时所说的二进制文件又是什么呢?事实上我们通常把无法直接被人类阅读的文件 称之为二进制文件(注意我这里说的是阅读),这些文件的数据不按照任何一种字符集进行编码储存,而是按照特定的格式或协议组织和存储的数据,这种文件的内容不以人类可读的字符形式存储,因此普通的文本编辑器或阅读器无法直接解释和显示其内容。
那这是不是说明二进制文件的储存形式都是杂乱无章的呢?那也不是,否则这个文件就没有任何意义了,只不过这些文件不存放人类可读的字符而已,例如:
- 可执行文件是按照特定的机器指令编码格式存储的,计算机能够读取并执行其中的指令,但是人类无法直接理解这些二进制数据
- 图片文件按照像素、色彩空间以及特定的图像编码格式进行存储,需要特定的图像处理软件才能正确解释并显示图像
- 音频文件则是根据声音的采样频率、声道等信息以及特定的音频编码格式进行存储,专门的音频播放器或编辑软件才能解码并播放声音
- ...
虽然二进制文件的存储形式看起来可能混乱无序,但实际上它们遵循特定的规范和格式,有着严格的数据组织方式。这些文件的二进制数据是按照特定的约定和协议存储的,只是它们不是按照人类可读的字符编码,而是按照对应的数据类型和格式来存储信息。不同类型的二进制文件具有各自的编码规则和结构,确保了它们能够被相应的软件正确地解释和处理。
3,总结
可见理解了文件的本质,我们就知道了文本文件和二进制文件是如何被定义的,在学习编程时遇到读取文件的各种操作,相信大家也能够理解了!
无论如何,计算机中任何文件都是以二进制形式储存的,只不过我们会人为地将它们按照用途和表现形式分为二进制文件和文本文件,我们可以总结它们的区别如下:
- 文本文件 :能够被人类直接阅读的文件即为文本文件,例如我们的
txt
格式的文本文档,以及源代码文件等等。文本文件无论其扩展名是什么,都可以修改成txt
打开或者是直接用文本编辑器打开,也就是说,文本文件中所有的字节都可以通过已有的字符集映射为人类可读的字符 - 二进制文件 :不能够直接被人类所阅读的文件则是二进制文件,如果说你用记事本或者文本编辑器强行打开,你只会看到很多无法阅读的乱码(即使你切换很多字符集去读取它),也就是说,二进制文件中存在一些甚至是大部分字节都不能够通过已有的字符集映射为人类可读的字符
我们常用的版本管理工具Git,就是通过读取并判断文件中是否存在字节值为
00000000
(控制字符\0
)的字节来判断这个文件是文本文件还是二进制文件,若存在这样的字节就是二进制文件。
事实上,不仅仅是文件,任何在计算机中存在的数据,例如网络传输的信息、读取到内存的数据都是二进制形式的,只不过一些文本数据是可以通过指定的编码转换成字符的。
那么在编程语言中进行文件读写操作时,我们也可以将文件内容读取成字符串(例如Java中的字符流),或者是直接读取文件二进制数据(例如Java中字节流),相信现在大家也知道这两种读取方式是怎么回事了:
- 以文本形式读取文件时:事实上是程序先读取了文件的二进制数据,再按照字符集转换成字符,并组织为字符串
- 以二进制形式读取文件时:就是直接读取了文件的二进制字节数据,未做任何转换
这也可见在我们使用编程语言读写文件时,所有的文件都可以以二进制形式读取,因为文件本身就是二进制形式储存的,而只有文本文件可以以文本形式读取,因为二进制文件中的内容无法通过已有的字符集转换成字符,这样得到的字符串会是乱码。