UTF-8 與 BOM

Unicode 簡介

Unicode(中文翻成「統一碼」)是一種文字編碼方式。主要的目的,是希望能提供一套統一的標準,以數字的方式表示全世界人類使用的文字,方便利用電腦、網路儲存及傳輸文字資訊。詳細的歷史沿革,網路上可以找到很多文章,在此不再贅述。

關於碼點 Code Point

每一個被 Unicode 規範定義的文字,都有一個獨一無二的數值,我們稱之為「碼點 (Code Point,亦翻作「代碼點」)」。通常我們會用 U+xxxxxxxx 代表 16 進位的數字)這樣的格式來表示碼點。

舉例來說,中文的「我」這個字的碼點是 U+6211,以 16 進位來表示的話是 0x6211,換算成 10 進位就是 25105。下面這段簡單的 HTML 文件:

<HTML>
  <BODY>
    <H1>&#25105</H1>
    <H2>&#x6211</H2>
  </BODY>
</HTML>

會顯示大小不同的兩個「我」字。

編碼方式

雖然每個文字都有一個碼點,但實際應用上,為了電腦儲存、處理、傳輸方便,人們發展出了各種不同的編碼方式。通常我們稱之為 Unicode Transformation Format,縮寫為 UTF,中文譯為「Unicode 轉換格式」。常見的編碼方式有 UTF-7、UTF-8、UTF-16 以及 UTF-32 等。本文主要討論的為 UTF-8 與 UTF-16。

UTF-16

UTF-16 是 Windows 和 Mac OS X 預設的 Unicode 編碼方式。名稱中 “16″ 的意義是「以 16 位元 (bit) 的數字為單位,去表示 Unicode 的文字」。碼點在 U+FFFF 以下的字,其 UTF-16 編碼基本上就跟碼點一樣。例如「我」的 UTF-16 編碼就是 0x6211

UTF-8

UTF-16 有一個缺點:在 Unicode 發明以前,大部份的文字檔案都是以 ASCII 格式儲存的;但 UTF-16 和 ASCII 並不相容。舉例來說,ABC 這個字串以 ASCII 表示的話,就是 0x41 0x42 0x43,但 UTF-16 則為 0x00 0x41 0x00 0x42 0x00 0x43。這個問題會導致現有處理文字的程式、函式庫全部得要重寫,現有的文字檔案也全部得要轉換成 UTF-16 才能使用。

為了解決這個問題,有人發明了 UTF-8 這種格式。原本 ASCII 定義的範圍(碼點 U+0000 ~ U+007F)的文字,UTF-8 和 ASCII 完全相容。但有一好沒兩好:碼點稍微大一點,所需要的儲存空間就會成長得很快。例如「我」用 UTF-16 編碼的話只需要 2 個位元組 (byte) 的空間;但用 UTF-8 則需要 3 個位元組 (0xE6 0x88 0x91)。至於碼點如何轉換成 UTF-8 的編碼,請自行 google。

Linux 預設的 Unicode 編碼方式就是 UTF-8。此外,目前世界上絕大部份的網頁也是以 UTF-8 方式儲存/傳輸的。

在此整理一下幾個文字的碼點與編碼比較表:

文字 碼點 UTF-16 UTF-8
A U+0041 0x0041 0x41
£ U+00A3 0x00A3 0xC2 0xA3
U+6211 0x6211 0xE6 0x88 0x91
𐌻 U+1033B 0xD800 0xDF3B 0xF0 0x90 0x8C 0xBB

位元順序與 BOM

UTF-16 還有一個問題。它的編碼基本單位為 16 位元的數字;但文字檔案儲存時大多是以位元組(長度為 8 位元)為單位。當我們想把 0x6211 這個字存進檔案裡時,究竟該存成 0x62 0x11 還是 0x11 0x62 呢?

唸資訊的人應該都知道,這是典型的 “Big-Endian vs Little-Endian" 的問題。而這兩種做法其實並無優劣之別,因此兩種方法都符合 UTF-16 的規範。

但問題還是得解決。讀檔案的人(或程式)要怎麼知道這個檔案是 Big-Endian 還是 Little-Endian 呢?Unicode 的做法是:所有 UTF-16 的檔案開頭,都會插入 U+FEFF 這個「文字」。讀取檔案的時候,藉由觀察開頭 2 個位元的順序(0xFE 0xFF 或是 0xFF 0xFE),就能知道這個檔案使用的順序為何。

U+FEFF 這個「文字」被稱做 Byte Order Mark,縮寫為 BOM,中譯為「位元順序記號」。

UTF-8 需要 BOM 嗎?

相較於 UTF-16,UTF-8 的編碼基本單位為 8 位元,也就是 1 個位元組,因此沒有 endian 的問題。雖然 BOM 能以 UTF-8 表示(畢竟它是有碼點的合法文字),但存在的意義不大,Unicode 的規範中甚至明文寫著:「UTF-8 不需要且不建議使用 BOM。」因為它會破壞 UTF-8 與 ASCII 的相容性。

BUT!!

微軟說:「(UTF-8) 要有 BOM!」就有了 BOM….(笑)

微軟認為,UTF-8 的 BOM 有助於程式判斷該檔案是否以 UTF-8 編碼;而一般微軟提供的文字編輯器,例如記事本 (Notepad),在「另存新檔」時若編碼選擇 UTF-8,檔案的前面就會自動被加上 BOM。

然而,許多程式在處理 UTF-8 編碼的檔案時,都沒有特別處理 BOM,結果就是把 BOM 當成是多餘的字元,導致程式發生錯誤。

如何移除 UTF-8 的 BOM?

如果只有少數的幾個檔案要處理,可以利用文字編輯器手動處理。例如免費的 Notepad++,就可以利用選單中的「編碼 -> 轉換至 UTF-8 碼格式(檔首無 BOM)」功能,將檔案前面的 BOM 拿掉。

如果想要大量批次轉換檔案,建議可以利用批次檔(或 shell script)搭配簡單的 awk 程式處理。詳細的 awk 程式可以參考文章後的連結。


參考資料

  1. Wikipedia: Unicode
  2. Wikipedia: UTF-8
  3. Usage of character encodings for websites
  4. UTF-8 Everywhere
  5. StackOverflow: How to make Notepad to save text in UTF-8 without BOM?
  6. StackOverflow: Using awk to remove the Byte-order mark
  7. 約耳談軟體:每個軟體開發者都絕對一定要會的Unicode及字元集必備知識 (沒有藉口!)

「感謝日本」布條背後的故事

DSC_7498e

這次日本馬拉松式看球之旅,最重要的計畫,就是這張「感謝日本」的布條。

開始籌劃旅行是過年前的事。過年期間台灣發生了不幸的事情,接下來看到日本人們以「報恩」的態度支援救災、募款,內心真的相當感動。台灣在 2011 年對日本伸出援手,日本人一直到今天都還在感謝台灣。這次的感謝計畫,我是懷著「如果不表達感謝之意,就太失禮了」這樣的心情進行的。

Continue reading  

[C++11] Ranged For Loop

在以前,若是我們要針對 STL container 中的所有物件做事情,最「方便」的方法是使用 iterator 搭配傳統的 for-loop:

那行 for 的敘述式真是又臭又長。即使是 C++11 導入了 auto 關鍵字,可以用來取代 itor 前面的型別,整體的可讀性還是很差。

幸好,C++11 同時導入了其他高階語言常見的 ranged for-loop,可以增加可讀性,也不用再 key 那麼多囉嗦的程式碼了:

使用上有幾個需要注意的地方:

  1. 元素 e 的作用範圍 (scope) 僅在 for-loop 中。出了 for-loop 就不能用了(這是優點)。
  2. 若是型別的後面沒有 reference 符號 &,那麼 e 就是「複製出來」的物件,類似 C/C++ 中 “call by value" 的概念。此時 e 是不能修改的(或者說:就算修改了,也不會影響到 my_list 中對應物件的內容)。若是需要修改,就要加上 &
  3. 加上 & 還有一個優點,那就是不需要複製整個物件。這對於占用空間較大的物件(通常是使用者定義的 class 生出來的物件)來說比較有效率。
  4. 若是需要效率,又希望保有「唯讀」的特性,可以在型別前面加上 const。以此例來說,就是變成:for (const auto& e : my_list)

Swapping Nibbles in Keil C51

今天寫程式的時候,有個地方需要 swap 某個 byte 的兩個 nibbles。其實 8051 原本就有一個 instruction 專門做這件事。若是直接寫組語的話,只要 3 行就搞定了:

問題我是用 C 在寫 code 的,所以我就開始研究是不是有辦法讓 Keil C 幫我產生這樣的 code。

首先,我們用標準的 C 的作法來做:

Keil C 產生出來的組語程式如下:

這樣翻出來的組語程式雖然看起來很長,不過我們還是可以看出 Keil C 厲害的地方:雖然我們寫的程式碼是「向左/向右 shift 4 個 bits」,但翻出來的並沒有「連續 shift 4 次」這樣的過程(8051 的 shift 一次只能 shift 1 個 bit,所以要 shift 多個 bits 就得用迴圈來跑),而是很聰明地利用 SWAP,分別取得 high/low nibble,再重新組合成 完成的 byte。

這樣的結果讓我蠻興奮的,因為這表示 Keil C 會去看 bit operation 的位數,適時地使用處理 nibble 的指令。也就是說,我有機會找到一種寫法,讓 Keil C 翻出直接 SWAP 的組語程式。

但接下來我就卡住了,因為我找不到更好的寫法去表達 “swap nibble" 這樣的想法。

後來,我在 Keil 的文件中找到一系列 intrinsic routines。很不幸地,裡面還是沒有 swap;但卻_crol_()_cror_() 這樣的 rotate 函式。於是我試著這樣寫:

結果翻出來的組語如下:

結果令人失望:變得更糟了!沒有直接用 SWAP 就算了,還利用迴圈的方式來處理。看樣子這個方法也行不通。

剩下一個方法:寫 inline assembly。但 Keil C 的 inline assembly 實在很難用:只要用了這個功能,該 .C 檔就不能直接編譯成 .OBJ 檔,而必須先產生出組語程式,再另外去組譯這個檔案。很麻煩。站在專案維護的角度來看,代價太高了,所以我放棄這樣的做法。

到頭來,我還是採用一開始的那種做法,畢竟在可維護性、執行速度、code size 等各方面,都算是平衡的做法。只是不能用到 8051 提供的功能,心中還是有些遺憾….

PS: 在 這個討論串中,也有人提到用 intrisic routine 的做法,而且說 Keil C 會幫忙做最佳化;不過我實驗的結果並不是這樣。

偶爾的素食

過去的這五天,在公司吃了五天不那麼嚴格(鍋邊素、就算不小心遇到肉也會把它吃完)的素食。

同事問我為什麼,我也很難講出個所以然來。
所以最後我稱這個行動為「個人式的反美肉」。(笑)

其實我的出發點不只是美牛,而是所有不正常產生的肉品。
仔細去思考的話,會覺得這些肉的確不應該去吃——
不管從道德、健康、環保的角度想,都是這樣。

但身為一個長期的雜食者,我得承認我戒不掉肉。
偶爾吃吃素大概就是我(目前)的極限了。
所以這星期過去後,我還是會繼續吃肉;
但也許之後還是會不定時地找個一週來吃素吧!

也歡迎大家一起來參與「沒有壓力的偶爾吃素行動」。
行動準則:

  1. 時間沒有壓力:隨便挑一週來執行,或是一週裡挑一天來執行,或是只有遇到素食來執行…. 都可以。但建議有個規則,這樣自己會比較有成就感、比較能達到執行的目的。
  2. 行動沒有壓力:鍋邊素即可。就算是有放肉絲的青菜也可以挾(當然儘可能少挾肉絲),和豬肉一起滷的豆干也可以吃。牛奶和雞蛋可以視自己的目標決定要不要吃。
  3. 不要浪費食物:盤子上的東西請全部吃完。(你會發現:因為沒有骨頭,可以吃得很乾淨)
  4. 還是要注意營養均衡:其實我指的是蛋白質。吃肉的時候蛋白質不太可能缺乏,但吃素時就要提醒自己要去攝取植物性蛋白質。(所以其實我這種喜歡黃豆製品的人應該比較適合吃素 XDDD)

其實我不是什麼注重養生的人,也不算什麼環保主義者。
甚至以前我也是為了追求美食、享樂,不顧一切的人。
但有了孩子以後,我覺得看某些事情的角度,慢慢在改變。
如果孩子們只能吃垃圾食物(或說是「飼料」)長大,那不是很可憐嗎?
如果我們一些小小的舉動,能讓世界(稍微地)變好一點點,那不是很好嗎?

「勿以惡小而為之、勿以善小而不為。」
這不是國中生的作文,而是我們的生活。

[悼] 菅野ひろゆき(洋之)過世了….

今年真是不平靜。
許多內外的(其中包含好幾位我相當尊敬或喜愛的)名人相繼傳出過世或罹癌的消息。

今天一早在噗浪上看到菅野ひろゆき(筆名:剣乃ゆきひろ)過世的消息,更是令我一下子說不出話來。

我很少在 blog 中談遊戲的事情,真的有在看我 blog 的人大概沒幾個人認識他。但他創作出來的《この世の果てで恋を唄う少女YU-NO(譯:在世界的盡頭唱著愛的少女~YU-NO)》,不管是在當時、還是用現在的角度來看,都是件前無古人、後無來者的偉大作品。即使是菅野本人後來的作品,也很難再創造如 YU-NO 般的成績。

強者我朋友 lordmi 寫了一篇部落格文記念他。暫且讓小弟仿造此格式,貼上當年在聖誕夜玩了十數個小時 YU-NO 後的感想(玩完後得了重感冒… orz)。願菅野桑能安息。

警告:下文充滿御宅之氣!具有強烈高貴氣息、無法忍受宅氣者,請勿閱讀

被九把刀勾起的青春…

最近,著了魔似地,瘋狂地在吸收《那些年,我們一起追的女孩》的相關訊息。

網路上的小說看不過癮,直接在網路書店買一本;到九把刀的部落格,把所有電影相關的文章看完;到 PTT 找別人看完電影後的心得;就連聽著主題曲,都能讓我感覺眼眶濕潤。

我應該不算是九把刀的書迷,但他的小說有一股莫名的魔力。當初看《樓下的房客》,越看越覺得噁心,卻停不下來,一口氣看到凌晨兩、三點,把它看完後,才帶著一顆亂七八糟的腦袋爬上床睡覺。

不過《那些年》的情況並不是只有「寫得很好」這樣而已;而是讓我整個人都陷入青春的回憶之中。

不是柯景騰的青春,而是我自己的青春。

「怎麼會這樣啊?」我問著我自己。

也許是九把刀的年紀和我差不多,書中描述的場景、事件都和我的人生經歷重疊;也許是我年輕時的個性也和九把刀一樣自命不凡、充滿自信(不過我的勇氣是遠不如他的);也許是我也像九把刀一樣,有一群可以不帶心防、廝混打屁的朋友……

也許,是我們心中都有一位女神,在她面前總是會不知所措,做些不合時宜的可笑舉動,只為了見到她的一顰一笑……

我遠比九把刀膽小,不像九把刀一樣鬼點子那麼多。他在他的女主角心中永遠占有一個特別的角落,而我,也許,永遠只是個不討人喜歡的小丑。

反正,故事中的許多地方,都讓人情不自禁地把自己的青春投射其中,進而產生共鳴。我想不只是我,很多人應該也有一樣的感受(小說可以印到兩百多刷、累計賣出超過 30 萬本,不是沒有理由的)。同時這也能說明,為什麼男生對這個故事的感受比較深(Well, 至少在我們家是這樣 🙂 )。

電影是一定會去看的啦!不奢望它能幫我「找回青春」什麼的,能讓我有一點點的感動,那就夠了。

 

 

喔當然,有很漂亮的女主角也是很重要的啦! XDDDDD


(取自九把刀的部落格)

[歌詞] 俄羅斯輪盤

[Update] 2011/08/07: MV 換成 official 版的。

我被這首歌洗腦了。 XDDDD

Tizzy Bac rocks!!


俄羅斯輪盤

主唱:Tizzy Bac
作曲:Tizzy Bac
填詞:陳惠婷

I don’t wanna be ok 所以離我遠一點
反正我也受夠這世界
早就知道不應該 偏要痛了才學乖
我們為何沒人能例外

以為真心擁有過 常常都只是搞錯
賭上真實的自我 自己造孽自己受

如果忘了你是誰 也許比較好一點
美夢清醒的瞬間 只剩下 我的心碎 和你的狼狽

會痛 沒什麼 這點苦我還能夠
只是我現在不明所以
落單 沒什麼 好過找錯人寄託
只是我現在有點兒Lonely
有一點Lonely

I don’t wanna be ok 不能就只是ok
我曾相信這世上有絕對
美好往事多可愛 壞掉的就隨風散
可我要怎麼給自己交代

以為真心擁有過 常常都只是搞錯
賭上真實的自我 自己造孽自己受

如果可以都不算 也不曾有你的陪伴
是否就能夠避開 這尷尬的殘酷毀滅 也不用妥協
畢竟錯加錯 不會變成對

賤事 我很多 這點苦不算什麼
只是我現在不明所以
落單 沒什麼 好過找錯人寄託
只是我現在覺得Lonely
會不會來不及?
真的好Lonely

愛過 沒有錯 我為人人沒什麼
只是我現在覺得不公平( hey hey hey !)
滯銷 沒什麼 好過送錯人寄託
只是我現在覺得Lonely

不屬於的時候 該放手的就放手
只是我現在 只是我現在 只是我現在
只想耍賴

I don’t wanna be ok 所以離我遠一點
反正我也受夠這世界!

以「中央儲存庫 (Centralized Repository)」的方式建立 Mercurial 儲存庫

  1. 建立存放 central repositories 的目錄。(例:D:hg_repos)
  2. 在其中建立存放專案 central repository 的目錄。(例:D:hg_reposprj1)
  3. 使用 ‘hg init’,在步驟 2 的目錄中建立儲存庫。
  4. 切換到工作目錄下。(例:D:working)
  5. 使用 ‘hg clone’,clone 一份 repository 到工作目錄中。
  6. 在工作目錄中,使用 ‘hg add’ 建立第一版的 source tree。
  7. hg commit
  8. 使用 ‘hg push’,將 local 端的變更 push 回 central repository。