首頁  >  文章  >  後端開發  >  php檔案和字符編碼詳解

php檔案和字符編碼詳解

小云云
小云云原創
2018-03-14 15:15:051967瀏覽


剛開始的疑惑是:文字檔案和二進位檔案有什麼差別?為什麼一個能顯示內容,另一個的內容經常無法(用文字編輯器)正常顯示?

馬裡蘭大學的這篇培訓筆記,把兩者的區別講得清楚:文本文件是二進位文件的一種,底層存儲也是0和1;文本文件可讀性和移植性好,但表現字元有限;二進位檔案資料儲存緊湊,無字元編碼限制。文字檔案基本上只能存放數字、文字、標點等有限字元組成的內容;二進位沒有字元約束,可隨意儲存影像、音訊視訊等資料。

用儲存數字的例子可以形象的看出文字檔案和二進位檔案儲存內容上的差異。例如要儲存數字1234567890,文字檔案要儲存0-9這十個數字的ASCII碼,對應的十六進位表示為:31 32 33 34 35 36 37 38 39 30,佔用10個位元組;1234567890對應的二進位為“0100 1001 1001 0110 0000 0010 1101 0010”,佔用4個位元組(二進位表示32位,一個位元組816位元),儲存到檔案的816位元),儲存到檔案的進位表示為(大端):49 96 02 D2

文字檔案按字元存放內容,二進位按位元組存放,這是兩個檔案最本質的區別。根據這個特性,可以推斷出一些常見結論:二進位檔案常常比文字檔案緊湊,佔用空間少;文字檔案更友善易用,能用所見即所得的方式編輯;二進位檔案常常需要專用程式打開,等等。

回頭看文字編輯器開啟二進位檔案常常是亂碼的現象。例如一個二進位檔案存放了一個整數1234(四個位元組),用16進位表示為:00 00 04 D2。文字編輯器開啟後逐個字符解釋,會發現這幾個字節拼不出可顯示的字符,只好亂碼相待。亂碼的原因是文字編輯器不能正確解析位元組流,這也是二進位檔案需要用專用軟體開啟的原因。例如jpg檔案要用看圖軟體打開,如果用音樂播放器打開,就完蛋!影片檔要用播放器打開,用壓縮軟體打開,歇菜!

檔案格式

了解文字檔案和二進位檔案的差異後,再來看檔案格式。我們知道,Windows依檔案拓展名識別檔案格式,並呼叫對應的程式開啟檔案;(類別)Unix系統,拓展名稱可有可無,那麼怎麼知道這個檔案是什麼格式呢?

幸好有file指令,這個指令可以告訴我們檔案到底是什麼格式。文件拓展名不是文件格式的本質區別,內容才是。把a.zip改成a.txt/a.jgp/a.mp3,無論什麼檔名,file都讓其原形畢露:Zip archive data, at least v1.0 to extract

編碼

說完了文件,再來來說文件內容中的編碼。常見的127個ASCII字符,沒啥編碼好說的,反正幾乎所有的編碼方式都相容於它。雙位元組、多位元組字符,編碼方式和位元組序,才是困擾程式設計師的問題。一個漢字,GBK編碼需要兩個字節,還要考慮本機的大小端,才能確定存放的最終形式;網路通訊時,要轉換成網路字節序(大端序),接收者才能正常解析。開發人員如果對字元編碼不熟悉,通訊時遇到亂碼問題,除錯就很困難。

UCS(Universal Multiple Octet Coded Character Set)標準的製定,讓開發人員遠離混亂的多位元組字元集。 UCS標準裡,所有的字元都有唯一的碼點(Code Point),根據碼點就可查到對應字元。 UCS用兩個位元組表示一個碼點(UCS-4標準是4個位元組),對應一個字元。由於使用了兩個字節,可容納2^16-1(6w+)字符,基本上容下各國常用的字符(UCS-4理論上可容納上2十億個字符,目前收納超過16W個字符) 。注意UCS只是一個標準,規定了碼點與字元一一的對應關係,但沒有定義如何儲存在電腦中。

規定Unicode字元儲存方式的工作由UTF(Unicode Transformation Format)完成,應用最多的方案是UTF-16和UTF-8。 UTF-16使用兩個位元組表示一個字符,Windows, MacOS, Java平台預設的字符編碼方案都是UTF-16。由於有兩個字節,便存在大端和小端兩種方案的區分。只有ASCII字符的文件,使用UTF-16編碼存在空間浪費嚴重的現象(浪費50%的儲存),由Ken Thompson(C語言發明人)和Robe Pike(Go語言發明人)提出的UTF-8編碼方案很快流行起來。 UTF-8是單字節流,不存在字節序問題,也不需要BOM。目前UTF-8是web通行標準。

對應關係

USC-2的值範圍是U+0000~U+FFFF,與UTF-8的對應關係如下:

1110xxxx 100xxx 10xxx
十六進位 二進位
#0000 0000-0000 007F #0xxxxxxx
#0000 0080-0000 07FF 110xxxxx 10xxxxxx
#0000 0800-0000 FFFF 1110xxxx 10800-0000 FFFF
0001 0000-0010 FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
#

从编码可以看出,与二进制相比,浪费了很多空间。不过这也没办法,可显示的字符更容易阅读和理解,人类很难抗拒这个诱惑。

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) 
{    echo bin2hex($char{$index});
}echo PHP_EOL;

也可以写出针对UTF-8编码的strlen函数:

function myStrlen(string $string){
    $slen = strlen($string);
    $mlen = 0;
    $maxByteLength = 4;
    $maxOffset = 7;    for ($i = 0; $i < $slen; ++ $i) {
        $byte = ord($string{$i});        // 从01xxxxxx开始对比,直到11110xxxx 10xxxxxx 10xxxxxx 10xxxxxx。只需要对比第一个字节即可
        for ($offset = 0; $offset < $maxByteLength; ++ $offset) {
            $result = $byte & (1 << ($maxOffset - $offset));            if ($result === 0) {
                $i += $offset;
                ++ $mlen;                break;
            }
        }
    }    return $mlen;
}

$string = "Coder不是工程师!";echo "mb_strlen:" . mb_strlen($string) . "\n";echo "mStrlen:" . myStrlen($string) . "\n";

相关推荐:

关于字符编码问题的详细介绍

以上是php檔案和字符編碼詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn