Elixir надає елегантний синтаксис для роботи з бінарними даними, як ми вже бачили на прикладі спеціальної форми <<>>, призначеної для роботи з бітовими рядками.
Тип «бінарні дані» є спеціалізацією типу «бітовий рядок». Бітові рядки можуть мати будь-яку довжину (будь-яку кількість бітів), а в бінарних даних кількість бітів ділиться на 8 без остачі. Тобто, працюючи з бінарними даними, ми часто думаємо про них у термінах байтів (8 бітів). Байт може представляти цілі числа від 0 до 255. Зазвичай значення байтів записують у шістнадцятковій системі, 0x00 - 0xFF.
Бінарні літерали визначають за допомогою спеціальної форми <<>>. Коли ми визначаємо бінарний літерал, можна використовувати цілі числа та рядки тексту (англ. string). Цілі числа, більші за 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>>
Нульовий байт - це інша назва для <<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. Вона приймає розширення файлу, тобто рядок тексту (англ. string), і повертає тип медіа, теж рядок тексту, або 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."}
Зареєструйтеся на Exercism, щоб вивчати й опановувати Elixir, а також 58 концепцій168 вправ та справжнє наставництво від людей, і все це безкоштовно.