转载请注明出处:https://tlanyan.me/ascii-bina...最近好奇识别文件格式。不幸的是,我对字符编码感到困惑。我不知道原因,这让我很沮丧。为了消除对文件格式和字符编码的疑虑,上网搜索了一下,找到了之前收集的文章,总算定下心来。本文是个人对文件和字符编码的总结。文本文件和二进制文件最初的混淆是:文本文件和二进制文件有什么区别?为什么一个能显示内容,而另一个却经常无法正常显示(使用文本编辑器)?马里兰大学的这篇培训笔记清楚地解释了两者的区别:文本文件是二进制文件的一种,底层存储也是0和1;文本文件可读且可移植,但字符数有限;二进制文件数据存储紧凑,没有字符编码限制。基本上,文本文件只能存储数字、文本、标点符号等有限的字符;二进制没有字符限制,可以随意存储图像、音频和视频数据。以存储数字为例,可以直观的看出文本文件和二进制文件存储内容的区别。例如,要存储数字1234567890,文本文件需要存储十个数字0-9的ASCII码。对应的十六进制表示为:31323334353637383930,占用10个字节;1234567890对应Binary为“01001001100101100000001011010010”,占用4个字节(二进制表示为32位,一个字节为8位),文件中存储的十六进制表示为(bigendian):499602D2。文本文件按字符存储内容,二进制文件按字节存储内容。这是两个文件最本质的区别。根据这个特点,可以推导出一些共同的结论:二进制文件往往比文本文件更紧凑,占用空间更小;文本文件更加友好易用,所见即所得的编辑方式;二进制文件通常需要特殊程序才能打开等。回过头来看,当文本编辑器打开一个二进制文件时,经常出现的是乱码现象。例如,一个二进制文件存储一个整数1234(四个字节),十六进制表示为:000004D2。打开文本编辑器逐字逐句解释后,会发现这几个字节拼不出来能显示的字符,只好以乱码互相对待。出现乱码的原因是文本编辑器无法正确解析字节流,这也是二进制文件需要用特殊软件打开的原因。比如一个jpg文件,需要用图片查看软件打开。如果用音乐播放器打开就完了!视频文件要用播放器打开,用压缩软件打开,别煮了!文件格式了解了文本文件和二进制文件的区别之后,我们再来看看文件格式。我们知道Windows是根据文件扩展名来识别文件格式,并调用相应的程序来打开文件;(like)Unix系统,扩展名是可选的,那么你怎么知道文件是什么格式的呢?幸运的是,有一个文件命令,它可以告诉我们文件是什么格式。文件扩展名不是文件格式的本质区别,内容才是。把a.zip改成a.txt/a.jgp/a.mp3,不管文件名是什么,文件都会原形毕露:Zip存档数据,至少v1.0才能解压。可以在本文中找到文件命令的工作原理。Encoding说完了文件,再来说说文件内容中的编码。常见的127个ASCII字符,编码没啥好说的,反正几乎所有的编码方式都兼容。双字节和多字节字符、编码方式和字节顺序是困扰程序员的问题。对于一个汉字,GBK编码需要两个字节,还要考虑机器的字节序来决定最终的存储形式;网络通信时,必须转换成网络字节序(bigendianorder),接收方才能正常解析。如果开发者不熟悉字符编码,在通信中遇到乱码,调试起来会很困难。UCS(UniversalMultipleOctetCodedCharacterSet)标准的制定,让开发者远离混乱的多字节字符集。在UCS标准中,所有的字符都有一个唯一的码点(CodePoint),根据码点可以找到对应的字符。UCS用两个字节表示一个码位(UCS-4标准是4个字节),对应一个字符。由于使用了两个字节,所以可以容纳2^16-1(6w+)个字符,基本容纳了各国常用的字符(UCS-4理论上可以容纳20亿个字符,目前容纳超过16W个字符)。注意,UCS只是一个标准,规定了码点和字符的一一对应关系,但没有定义在计算机中如何存储。规定Unicode字符存储方式的工作是由UTF(UnicodeTransformationFormat)完成的,使用最广泛的方案是UTF-16和UTF-8。UTF-16使用两个字节来表示一个字符,Windows、MacOS和Java平台默认的字符编码方案是UTF-16。由于有两个字节,因此大端和小端方案之间存在区别。对于只有ASCII字符的文件,使用UTF-16编码时会严重浪费空间(浪费50%的存储空间)。由KenThompson(C语言的发明者)和RobePike(Go语言的发明者)提出的UTF-8编码方案迅速流行起来。UTF-8是单字节流,没有字节顺序问题,也不需要BOM。UTF-8是目前的网络标准。对应关系USC-2的取值范围为U+0000~U+FFFF,与UTF-8的对应关系如下:十六进制二进制00000000-0000007F0xxxxxxx00000080-000007FF110xxxxx10xxxxxx00000800-0000FFFF1110xxxx10xxx10xxx10xxx10xxx10xxx101010xxxxxx10xxxxxx10xxxxxx从编码可以看出,相比二进制,浪费了很多空间。但没办法,能显示的字符更易读易懂,人类很难抗拒这种诱惑。UTF-8转换规则是:1、如果一个字节的第一位为0,则判断为ASCII字节,除0外其余7位为ASCII码,因此UTF-8兼容ASCII码;2、如果第一个字节是1,那么连续几个“1”表示从这个字符开始,后面连续的字节实际上是一个字位,后面的字节必须以10开头。理解了上面的规则,我们的程序就可以轻松处理UTF-8编码的字节流。比如要找出“中”的UTF-8编码,可以这样处理(注意文件是UTF-8编码的):$char="中";$length=strlen($char);$bytes=pack("a".$length,$char);echo"UTF-8:".bin2hex($bytes)。"\n";//或echo"UTF-8:";for($index=0;$index<$length;++$index){echobin2hex($char{$index});}echoPHP_EOL;您还可以为UTF-8编码编写strlen函数:functionmyStrlen(string$string){$slen=strlen($string);$mlen=0;$最大字节长度=4;$maxOffset=7;对于($i=0;$i<$slen;++$i){$byte=ord($string{$i});//从01xxxxxx开始比较直到11110xxxx10xxxxxx10xxxxxx10xxxxxx。只需比较第一个字节($offset=0;$offset<$maxByteLength;++$offset){$result=$byte&(1<<($maxOffset-$offset));如果($result===0){$i+=$offset;++$米伦;休息;}}}return$mlen;}$string="Coderisnotanengineer!";echo"mb_strlen:".mb_strlen($string)."\n";echo"mStrlen:"。我的斯特伦($字符串)。"\n";明白了原理,乱码就不会再乱七八糟了。参考https://www.cs.umd.edu/class/...http://www.unicode.org/faq/ut...https://my.oschina.net/goal/b...http://mp.weixin.qq.com/s/2H6...https://www.lifewire.com/file...
