Elixir 提供了優雅的語法來處理二進位資料,就像我們用來處理位元字串的<<>>特殊形式一樣。
二進位型別是位元字串型別的一種特化。位元字串的長度可以是任意的(任意數量的位元),而二進位的位元數量則必須能被 8 整除。也就是說,在處理二進位時,我們通常會以位元組(8 個位元)為單位來思考。一個位元組可以表示從0到255的整數。我們經常以十六進位處理位元組的值,也就是0x00 - 0xFF。
二進位字面值是使用位元字串的特殊形式<<>>定義的。定義二進位字面值時,我們可以使用整數和字串字面值。大於 255 的整數值會溢位,只會使用該整數的最後 8 個位元。預設情況下,該值會套用::binary修飾詞。我們可以用<>/2運算子串接二進位。
<<255>> == <<0xFF>>
# Overflowing bits are truncated
<<256>> == <<0>>
<<2, 4, 6, 8, 10, 12, 14, 16>> == <<0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x10>>
null-byte 是<<0>>的另一個名稱。
模式匹配甚至可以套用到二進位上,我們可以針對二進位資料的某一部分進行模式匹配,就像我們對陣列所做的那樣。
# Ignore the first 8 bytes, match and bind the remaining to `body`
<<_::binary-size(8), body::binary>>
就像其他型別的模式匹配一樣,我們可以把這種寫法用在函式簽章裡,讓程式從多個函式子句中挑選符合的那一個。
你正在進行一個專案,這個專案讓使用者可以把檔案上傳到伺服器,與其他使用者分享。你被指派要寫一個函式,驗證上傳的檔案是否符合它的媒體類型。你查了一些資料後發現,檔案的前幾個位元組通常對該檔案類型來說是獨一無二的,就像是它的一種簽章。
請參考下表:
| 檔案類型 | 常見副檔名 | 媒體類型 | 二進位「簽章」 |
|---|---|---|---|
| ELF | "exe" |
"application/octet-stream" |
0x7F, 0x45, 0x4C, 0x46 |
| BMP | "bmp" |
"image/bmp" |
0x42, 0x4D |
| PNG | "png" |
"image/png" |
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A |
| JPG | "jpg" |
"image/jpg" |
0xFF, 0xD8, 0xFF |
| GIF | "gif" |
"image/gif" |
0x47, 0x49, 0x46 |
實作type_from_extension/1函式。它應該接受一個副檔名(字串),並回傳媒體類型(字串),如果副檔名不符合預期的那些,則回傳 nil。
FileSniffer.type_from_extension("exe")
# => "application/octet-stream"
FileSniffer.type_from_extension("txt")
# => nil
實作type_from_binary/1函式。它應該接受一個檔案(二進位),並回傳媒體類型(字串),如果檔案簽章不符合預期的那些,則回傳 nil。
file = File.read!("application.exe")
FileSniffer.type_from_binary(file)
# => "application/octet-stream"
file = File.read!("example.txt")
FileSniffer.type_from_binary(file)
# => nil
不用擔心要把檔案讀取成二進位,就假設這件事已經幫你做好了,測試會把它當作引數傳進來。
實作verify/2函式。它應該接受一個檔案(二進位)和副檔名(字串),並回傳:ok或:error元組。
file = File.read!("application.exe")
FileSniffer.verify(file, "exe")
# => {:ok, "application/octet-stream"}
FileSniffer.verify(file, "png")
# => {:error, "Warning, file format and file extension do not match."}