Gecko是一套網絡排版引擎,由來已久,爲當年大名鼎鼎的netscape網絡瀏覽器流傳而來,後面也成爲了firefox瀏覽器,thunderbird等等軟件的基礎。詳細的發展歷程在這裏就不展開作具體介紹了,讀者能夠自行查閱百度百科,維基百科等資料。php
在這一章咱們重點介紹一下gecko中是如何對全球各類不一樣的網頁文檔的編碼方式來作出識別和轉換的。html
咱們知道,netscape或者firefox是面向全球用戶的,而且,在互聯網的世界,並無什麼界限妨礙一個美國的用戶訪問中文或者日文的網頁。因此,在這種場景下,瀏覽器是否能正確識別每一個地區的網頁的編碼格式,並正確地顯示出來,就尤其重要了。算法
有一部分網頁,可能會在html的標籤中寫上charset , 可是還有很是大的一部分網頁,是缺乏這個信息的。因此,瀏覽器須要經過頁面的數據內容,去猜想這個頁面內容最有可能的編碼是什麼。數組
固然,有可能會猜錯,因此用戶若是看到亂碼了,並且又知道頁面是哪種編碼,能夠手動強制改變。瀏覽器
1:編碼空間(coding scheme)網絡
咱們知道,在多字節編碼中,總會有一些碼點(code point)是用不到的。若是咱們遇到若干個字節是不屬於當前的編碼,那麼咱們能夠立刻否認當前的編碼方式。另外,某些編碼擁有屬於本身特定的編碼特徵,經過這種特性咱們也能夠立刻肯定具體的編碼。app
在gecko中,使用了一種狀態機(Parallel State Machine)的算法來作這種檢測。ide
在這個狀態機中,存在三種狀態:大數據
這個算法的思路主要是結合上一個字符的檢測狀態來判斷當前字符的狀態,也就是說他的狀態改變是受上一個字符影響的。這對東亞文字的處理頗有用,由於東亞文字通常都是多字節的。編碼
在任何一種語言的編碼中,一些字符的出現方式每每會比其餘的編碼要更多一點,這種狀況每每適合使用了特別多的碼點來走編碼的東亞文字,例如:中文,日文和韓文等。
有人分別就簡體中文,繁體中文,日文,韓文作過專門的調查。最常用的字符每每分佈在比較小的字符範圍內,大部分字符使用頻率較低。以下表所示:
以上數據代表使用頻率高的字符每每分佈在較小的字符範圍內,並且高頻字符足以說明該語言的語言特性。而且每種字符的碼點分佈是很稀疏的,從而大大減小了不一樣編碼之間重複交叉的範圍。這就爲區分不一樣編碼提供了一個比較有效的解決方案。
gecko基於這種分析使用了Confidence based的檢測方式,每一趟數據分發分析過程當中,都會作兩次重要的檢查,一次是檢查符合當前編碼器編碼範圍的字符個數(mTotalChars)。另外一個是檢查當前數據文件中落在使用頻率較高的字符集中的個數(mFreqChars)。爲減小不一樣編碼類型交叉帶來的干擾,使用頻率高的字符集也不能選的太大,上表顯示使用頻率最高的前512個字符幾乎涵蓋了每種編碼的大部分字符。而基於上述思想,每種字符集都被分紅兩部分,頻繁使用的(frequence used)和非頻繁使用的(not frequence uesed).若一個字符分佈在前512個字符範圍內,它就是頻繁使用的。因而gecko中又使用了另外一個概念Distribution Ratio,它指的是當前字符集中前512個字符的使用頻率與剩餘字符的使用頻率的比值。
例如如在一篇標準的用GB2312編碼的中文文檔中,前512個字符的使用頻率爲0.79135,後面的使用頻率爲(1- 0,79135),因此Distribution Ratio爲0.79135/(1- 0,79135)=3.79。這個值只是一個理想狀態,還不能用來求Confidence Level。欲求Confidence Level咱們須要乘以一個常數,這個值咱們叫作Typical Distribution Ratio,這個值因各類語言不一樣而各異,是一個通過分析各類語言的多份文檔後得出的一個經驗值。
基於以上分析及代碼中的展現,Confidence Level的定義以下:
float confidence = mFreqChars/ ((mTotalChars – mFreqChars) * mTypicalDistributionRatio
這樣,每一次編碼檢查,編碼檢測器都會將每一個字符交付狀態機和分析器逐一掃描,直到遇到一個特有的字符,或者是將全部字符所有掃描完畢。最後,系統會從這些衆多掃描器中選擇一個Confidence Level最高的檢測器,並將其對應的編碼類型做爲最終結果。
這個方法專門用以檢測單字節編碼。
相對於多字節編碼檢測,單字節編碼檢測就變得容易的多了,它不須要狀態機和分發分析器。但因爲衆多單字節編碼共享256個編碼空間,並且還要去除ASCII碼127個編碼空間,單純的字節範圍比對,很難精確的區分西文字符。
關於這個問題gecko的開發人員進行了大量的分析,提出了2-Char Sequence的概念,即它不是用單個字節做爲考量字符編碼的單位,而是以兩個字節爲單位考量編碼類型。研究人員發現,在西文字符集中有不少字符常常以成對的形式出現,並且這種比例也比較高。而不一樣語言之間這種成對的字符交叉率顯然很低。這顯然爲這些編碼的檢測提供了一種解決方案。
有人曾經下載20M的俄語純文本文件,而後寫代碼研究這段文件,總共發現了21,199,528個 2-Char sequence,除去space-space組合的垃圾數據外,剩下的20,134, 122 個2-Char sequence佔據了全部序列的95%,這些能夠用來構建語言模型的序列能夠被分紅4096個序列,在21,199,528個 2-Char sequence中有1961個序列出現的機率明顯偏低。咱們把這1961個序列叫作咱們語言中的Negative Sequence。
gecko的單字節編碼檢測方案就是基於這個實驗結論,併爲每個編碼檢查定義了一個語言模型(SequenceModel)用來描述這種2-Char Sequence。 每個SequenceModel中都定義了一個256*256的二位矩陣,用來映射每兩個字符組合對應的等級,共有4個等級,0表明Nagative Sequence,3表明Positive Sequence,其他爲中間階層;每個SequenceModeld都有一個mTypicalPositiveRatio用來描述Positive Sequece在全部Sequece中的比例;每個編碼檢測器又爲這種等級劃分定義了一個數組PRUint32 mSeqCounters[NUMBER_OF_SEQ_CAT]用來記錄每個等級的2-Char sequence在被檢查文本中出現的次數。
有了上述理論的鋪墊,單字節編碼檢查就變得容易的多了,每掃描一個字符總會結合上一次掃描過的字符作2-Char Sequence序列檢查,並把其出現次數記錄在對應的mSeqCounters中。
同多字節編碼檢測同樣,單字節編碼檢測也是confidence based。不一樣於多字節檢測的是,計算方式有所改變。若定義了NEGATIVE_APPROACH,它的計算式爲:
((float)(mTotalSeqs – mSeqCounters[NEGATIVE_CAT]*10))/mTotalSeqs * mFreqChar / mTotalChar;
若未定義,它的計算式爲:
r = ((float)1.0) * mSeqCounters[POSITIVE_CAT] / mTotalSeqs / mModel->mTypicalPositiveRatio;
r = r*mFreqChar/mTotalChar;
最終依然是選取confidence level最高的單字節編碼檢測器對應的編碼做爲最終的結果。
對於一段文本輸入,咱們不知道它的編碼類型,那麼咱們就要把文件數據交付全部的多字節檢測器和單字節檢測器,最終找到confidence level最高的做爲結果。固然這只是gecko編碼檢測的中心思想,詳細過程還要更復雜一些,好比對Unicode系列的檢測,它會考慮BOM的因素等。
圖四.編碼檢測結構圖
編碼檢測方案中,編碼檢測方式整體上能夠分爲四類:多字節檢測,單字節檢測,EscCharSet檢測及Latin1檢測。
多字節檢測室基於狀態機和分發器的。每獲取一個字符輸入都會交付狀態機進行判斷,若返回狀態是eItsMe,檢測就結束,不然交付分發器統計高頻字符和符合當前編碼集的字符數。
單字節檢測主要是基於語言模型的。每獲取一個字符輸入,它都會結合上一次的輸入到語言模型中查詢肯定其是不是高頻的2-Char Sequence,並統計高頻的個數。
EscCharProber主要是針對HZ,ISO-2022系列的,他們有一個明顯的特徵,就是數據中存在ESC字符或者」~{」字符(待進一步研究)
因爲Latin1字符的匹配率較高,這裏單獨處理它。最終肯定confidence的時候,它會主動下降50%,以給其餘編碼提供機會。
1. nsUniversalDetector首先根據判斷是否有當前數據是否有BOM,如有,直接根據BOM斷定當前編碼的類型。若無,就遍歷一遍當前數據判斷當前數據應該使用的編碼檢測組。
2. 編碼檢測組將輸入的數據交付給每個編碼檢測器。若檢測過程當中遇到eItsMe狀態,結束全部檢測,返回結果。
3. 調用nsUniversalDetector的DataEnd方法獲取編碼類型。若獲取失敗,nsUniversalDetector會統計每個檢測器的confidence值,選取confidence最大的檢測器對應的編碼類型返回。
在gecko的編碼檢測中,對於以上三種方法是結合起來使用的,在實際的使用過程當中,仍是收到了很好的效果。
咱們能夠看到,不管單字節編碼仍是多字節編碼的檢查,都加入了語言特性,經過語言特性來彌補編碼檢測能力的不足。二者相互配合,仍是比較完美,對全部大數據文件基本上都能給出正確的結論。
在實際的應用中,
1:Positive Sequence或大機率字符集出現的頻率越高檢測越準確。
2:字符越多且重複率不高時檢測越準確
在gecko中,全部的編碼都是基於Unicode的,全部的其餘編碼最終都會轉換成Unicode。因此在gecko中,一般把轉換成Unicode的過程叫作Decode,逆向轉換爲自身的過程叫作Encode。
咱們知道,當前主流的編碼從字節長度上主要分爲以下幾種:
而同一個字符在不一樣編碼方案中的編碼多是不同的;在同一編碼方案中編碼連續的兩個字符在另外一種編碼方案中未必是連續的。基於此,想經過一種直接的數學轉換來解決全部編碼之間的轉換是至關困難的。
因此gecko使用了基於表查詢的編碼轉換解決方案,每次編碼轉化都會基於一張或多張表完成。它的基本規則以下:
每種編碼的最大查詢表的個數由第一個字節的區段決定的,如EUC-KR每一個字符的首字節的區段範圍是,{ 0×00, 0×7E },{ 0xA4, 0xA4 },{ 0xA1, 0xFE },{ 0xA1, 0xC6 },{ 0×80, 0xA0 },那麼EUC-KR向Unicode轉換就會基於5個表來完成。
圖4,查詢表結構示意圖
每個查詢表的結構如上圖所示,其中每一個字段的意義是:
itemOfList: 查詢區間的個數,這裏我還理解爲處理策略的個數。
offsetToFormatArray: FormatArray數組相對於查詢表的偏移位置,在表查詢方案中,每一個查詢區間都對應了一組處理策略,經過這個formatArray能夠肯定一個特定字符的處理策略。
offsetToMapCellArray: uMapCell數組相對於查詢表的偏移位置,uMapCell能夠認爲是查詢區間的規則說明,它定義了經過各類format查詢表時的查詢結構(詳細見uMapCell的定義)。
offsetToMappingTable: 他定義了被查詢數據相對查詢表的偏移量。被查詢數據多是一個區間,也可能映射到一個具體的編碼。具體的使用方式是由format所對應的映射方法肯定的。
gecko編碼轉換的主體過程,基本上是結合這個表來完成的。每獲取一個新的字符輸入,它都會查找該字符對應區段的uScanClassID (uScanClassID的定義見intl/uconv/util/uconvutil.h),並結合該id找到相應的處理方法,該處理方法會以當前字節開頭的字符按字節順序組裝成一個16位的數值med,這個值主要用來映射原始字符的查詢範圍,咱們能夠經過med值方便的肯定它的偏移量,format等,而後找到對應的uMapCell及format,交付uMapFormate*映射出具體的編碼。一次典型的編碼轉換過程以下圖所示:
經過med值肯定具體的format.這一過程由uGetFormat,uGetMapCell,uHit三個方法或宏協調完成的,每個format都對應了一組處理方法,包括對med值區間的斷定方式及後面的Mapping方式等。
調用uMap方法去映射具體的編碼值。該調用依然會使用format值肯定Mapping方法。不一樣format值映射方式是不同的。
這是gecko中一次典型的編碼轉換過程,固然具體到每一個轉換器又有所不一樣,好比GB18030轉換成Unicode的時候,它會直接根據當前字節序直接查找一個映射表,只有遇到四字節編碼或者當前表中查詢不到的字符時交付新的轉換器處理。
by panyunhong