Về cơ bản máy tính chỉ làm việc với các con số. Nên các ký tự muốn máy tính hiểu được thì chúng phải ở dạng số. Vì vậy các Encoding Standard (chuẩn Encoding) sinh ra để quy định số thứ tự cho mỗi ký tự. Mỗi encoding quy định một danh sách ký tự mà nó hỗ trợ. Có khá nhiều encoding standard: ASCII, ISO-8859-1, JIS X 0208, Unicode...
Character Encoding là việc convert số thứ tự của ký tự (trong chuẩn Encoding mà nó đang tuân theo) sang binary. Mỗi encoding sẽ có một tập quy tắc encode/decode tùy theo chuẩn của nó.
Ví dụ với encoding phổ biến mà chúng ta (mình) hay dùng: UTF-8 - là một encoding tuân theo chuẩn Unicode.
Với ký tự trong khoảng 0 - U+007F
trong Unicode table, UTF-8 sử dụng 8bit để encode, với bit đầu = 0
để đánh dấu, các bit còn lại để xác định vị trí của ký tự trong bảng Unicode. Nghĩa là khi decode, gặp byte nào có bit đầu tiên = 0
thì có nghĩa là byte đó lưu một trong các ký tự trong khoảng 0 - U+007F
.
Các ký tự còn lại được sử dụng nhiều hơn 8 bit:
U+0080 - U+07FF
, sử dụng 2 byte để encode. Byte đầu có dạng 110xxxxx
và ngay sau đó là 1 byte dạng 10xxxxxx
U+0800 - U+FFFF
có dạng: 1110xxxx 10xxxxxx 10xxxxxx
U+10000 - U+10FFFF
có dạng: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
Loại trừ các bit có tác dụng đánh dấu, các bit còn lại sẽ là vị trí của ký tự trong bảng Unicode.
Ví dụ:
1. 01100001
có dạng 0xxxxxxx
, loại bỏ bit đánh dấu (bit 0 đầu tiên) ta được số bit còn lại là 1100001
Chuyển đổi sang decimal hoặc hex: 11000012 = 6116 =9710. Sau đó tìm trong Unicode table thì ta được ký tự a
ở vị trí số 97 (hay 61 trong hệ hexadecimal)
2. 11100011 10000010 10110101
có dạng 1110xxxx 10xxxxxx 10xxxxxx
, lược bỏ các bit có tác dụng đánh dấu:
Kết quả: 00110000101101012 = 30B516 = 1246910. Tra trong bảng Unicode ta được
Với ví dụ trên thì đó là bước decode từ binary ➜ index. Còn để encode, thì chỉ cần xác định index của ký tự thuộc khoảng nào, chọn sequence tương ứng, convert index ➜ binary rồi fill các bit vào.
Với quy luật này thì nếu có thêm một lượng lớn ký tự nữa, UTF-8 vẫn tiếp tục đáp ứng được bằng cách thêm sequence mới (ví dụ 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
).
Mặc dù Unicode về sau đã có gần như có đầy đủ các ký tự cho tất cả các ngôn ngữ, emoji, symbols... Tuy nhiên không phải ở đâu cũng dùng UTF-8. Vì với một số ngôn ngữ, ký tự của họ trong Unicode nằm ở index lớn, dẫn đến việc lưu trữ bằng UTF-8 sẽ gây lãng phí bộ nhớ (hoặc băng thông khi truyền dữ liệu). Ví dụ cùng ký tự モ
trong tiếng Nhật, nếu dùng encoding Shift_Jis chỉ mất 1 byte (11010011
) để lưu trữ còn UTF-8 mất đến tận 3 byte (11101111 10111110 10010011
). Vì thế nếu dữ liệu đó chỉ lưu ở một tập ngôn ngữ cố định và bài toán về độ lớn của dữ liệu là quan trọng thì ta nên chọn encoding một cách hợp lý.
Việc có nhiều encoding tuân theo các chuẩn encoding khác nhau dẫn đến chúng ta hay gặp một vài lỗi như:
Việc implement một encoding cũng không phải đơn giản. Encoding không chỉ đáp ứng quy luật để encode/decode các ký tự cho đúng với Encoding standard, mà nó còn phải đáp ứng các giải thuật tìm kiếm (như Boyer–Moore, Bitap...).
Bên trên chỉ là những thứ cơ bản mà mình hiểu về Character encoding. Còn khá nhiều điều thú vị như convert giữa 2 encoding, mối quan hệ với font chữ, chi tiết về các giải thuật tìm kiếm, cũng như Regex hoạt động thế nào... Hy vọng sẽ tìm hiểu được trong tương lai không xa để chia sẻ thêm .