Intro

Đây là bài dịch đầu tiên trong series về Elixir và Unicode của tác giả Nathan Long, Các bạn có thể đọc phần 2 tại đâyBài gốc: Part 1 | Part 2Đây là một series rất chi tiết và dễ hiểu, mình có chỉnh sửa và bổ sung 1 chút, hi vọng đem đến cho mọi người 1 cái nhìn rõ ràng nhất về Unicode và UTF-8, thứ chúng ta gặp rất nhiều nhưng chưa chắc đã biết rõ

*

(Shame on other languages

*

Source: Zazzle

Trước khi nói về Unicode, hãy nói về ASCII, thứ mà những người nói tiếng Anh như tôi khi nhắc đến sẽ nghĩ ngay đến những đoạn văn bản thông thường. Nếu chạy lệnh man ascii trên máy của bạn, bạn sẽ nhận được kết quả tương tự như thế này.

Bạn đang xem: Utf-8 là gì

*

Về cơ bản, ASCII đơn giản là một cách ánh xạ (mapping) từ kí tự sang số. Đây là sự đồng ý giữa các lập trình viên rằng chữ a hoa, A, có thể được biểu diễn bằng số 65 và tương tự cho các kí tự khác (Tại sao lại là 65 ??? có lí do riêng cho việc này đấy

*

Tạm thời chúng ta vẫn ổn với cách encode này, nhưng chúng ta muốn viết nhiều hơn những kí tự thông thường. Chúng ta muốn viết:

Những kí tự đi kèm với trọng âm

á é í ó ú ü ñ ź đẹp trai

Chữ Hi Lạp

λ φ θ Ω

Kí hiệu toán học

∫f(x)dx ∞ △ABC ~ △DEF

Chữ tượng hình Trung Quốc

夜露死苦

Vẽ tranh chắc cũng không chết ai đâu nhỉ

???? = ???? + ???? (Chữ Hàn này có nghĩa là “thiến gà”)

Ta còn muốn viết emoji nữa: cười, khóc, hôn, lật mặt, hám tiền,…

???? ???? ???? ???? ????

Với Unicode, ta có thể viết tất cả các kí tự của tất cả các ngôn ngữ của con người (trên lý thuyết là vậy)Thực tế thì, Unicode được tạo ra như một chuẩn mực nhưng nó cũng là một quá trình liên quan đến chính trị, và có ai đó nói rằng, ngôn ngữ của họ không được đối xử công bằng như các ngôn ngữ khác. Ví dụ, trong bài viết I Can Text You A Pile of Poo, But I Can’t Write My Name (Tạm dịch: tôi có thể nhắn cho bạn cả 1 đống sh!t nhưng lại không thể viết tên mình 1 cách hẳn hoi) tác giả Aditya Mukerjee có giải thích rằng, ngôn ngữ Bengal (một ngôn ngữ ở vùng Nam Á) có hơn 200 triệu người bản xứ sử dụng, nhiều hơn cả tiếng Nga nhưng không phải lúc nào cũng có thể gõ 1 cách hẳn hoi trên máy tính.Tương tự, những người viết tiếng Trung, Hàn, Nhật cũng bị bảo như sau: Này, các bạn sẽ sử dụng chung “Han unification”, như vậy chúng ta sẽ tiết kiệm thêm được vài chỗ trống

*

Source: The Sorry State of Japanese on the Internet

Hoặc ít nhất là đấy là cách mà họ giải thích các kí tự trên. Có một bài viết trên trang chủ của Unicode giải thích về ngôn ngữ học, tính lịch sử cũng như các lí do kĩ thuật. Bài này cũng có nhắc đến:

Quá trình này được nhận thức rất rõ bởi tiêu chuẩn quốc gia của các nước tham gia, Nhật Bản, Trung Quốc, Hàn Quốc và các nước khác, tất cả đều cùng nhau tham gia vào phần lớn công việc bao gồm tối thiểu hoá những mã hoá mà tất cả các thành viên khác của uỷ ban đều đống ý rằng chúng là của cùng một kí tự.

Đối với tôi, một người không viết bất cứ ngôn ngữ nào được nói ở trên, tôi chẳng thể đánh giá được. Nhưng nếu mà lấy việc tiết kiệm chỗ trống làm lí do kĩ thuật thì có vẻ lạ khi mà Unicode có cả bảng mã cho bài tú lơ khơ ???

*

Hay là cả kí tự giả kim thuật…

Xem thêm: Vermiculite Là Gì – Đá Và Có Ứng Dụng Như Thế Nào

*

và kí hiệu âm nhạc của Hi Lạp cổ đại…

*

à và cả Linear B, thứ mà chả ai dùng đã cả nghìn năm rồi (wtf).Nhưng đối với mục đích của bài viết này, cái quan trọng mà tôi muốn nói đến là Unicode trên lý thuyết có thể hỗ trợ bất cứ thứ gì chúng ta muốn gõ ở bất kì ngôn ngữ nàoỞ lõi của mình, Unicode giống như ASCII: 1 danh sách các kí tự mà chúng ta muốn gõ vào máy tính. Mỗi kí tự sẽ tương ứng với một số (numberic codepoint), cho dù nó là chữ a hoa, A, chữ lambda thường, hoặc là “người đàn ông lơ lửng trong bộ vest”.

A = 65λ = 923????= 128,372 # Unicode nói: “Ok, kí tự này tồn tại, chúng tôi đã gắn cho nó một tên chính thức và một codepoint, đây là nó ở dạng chữ thường và dạng chữ hoa (nếu có), và đây (có thể) là ảnh của nó. Này những nhà thiết kế font chữ, nó hiển thị thế nào chỉ phụ thuộc vào cách bạn vẽ nó thôi đấy.”Giống như ASCII, các Unicode string (hãy tưởng tượng nó là một chuỗi “codepoint 121, codepoint 111…”) cần phải được encode thành 0 và 1 trước khi ta có thể lưu trữ và truyền chúng đi. Nhưng không giống như ASCII, Unicode có nhiền hơn 1 triệu codepoint, nên ta không thể nhét vừa tất cả vào 1 byte được. Và không giống như ASCII, chúng ta KHÔNG CÓ 1 CÁCH DUY NHẤT để encode chúng.Vậy ta có thể làm gì ? 1 ý tưởng đó là cứ 3 byte tương ứng với 1 kí tự. Nếu thế thì sẽ rất dễ cho việc duyệt string vì codepoint thứ 3 sẽ luôn bắt đầu ở byte số 7. Tuy nhiên, về mặt không gian lưu trữ và băng thông thì cách làm này không hiệu quả.Thay vì vậy, giải pháp phổ biến nhất đó là cách mã hoá UTF-8

UTF-8

UTF-8 đưa ra cho chúng ta 4 mẫu (template) để lựa chọn: mẫu 1 byte, mẫu 2 byte, mẫu 3 byte, mẫu 4 byte

*

Đối với mỗi template sẽ có những phần header giống nhau (đánh dấu bằng màu đỏ trong hình trên) và những vị trí mà ta có thể điền dữ liệu codepoint vào (đánh dấu bằng “x” ở phần trên).Template 4 byte cho phép ta lưu trữ 21 bit, tương đương với 2,097,151 giá trị khác nhau, quá dư đủ so với 128,000 codepoint hiện tại. Nên là, tương lai nếu có thêm nhiều Unicode codepoint nữa thì UTF-8 cũng dễ dàng mã hoá được.Để sử dụng các template này, đầu tiên bạn cần biểu diễn codepoint đó dưới dạng các bit.Ví dụ, ⏰ có codepoint là 9200 (trong iex bạn có thể dùng ? để thấy giá trị này)

?⏰ # => 9200Chuyển số đó sang hệ cơ số 2:

base_2.(?⏰) == “10001111110000”Tổng cộng là 14 bit, không đủ nếu chúng ta dùng template 2 byte, nhưng với 3 byte thì ok. Chúng ta sẽ chèn chúng vào các vị trí lưu codepoint, từ phải sang trái và pad phần trống bằng số 0 như hình dưới:

*

Cơ mà đây có phải là điều mà Elixir thực sự làm không ? Trong iex, ta có thể dùng hàm IEx.Helpers.i/1 để kiểm tra 1 string có chứa ⏰ như sau:

i “⏰”….Raw representation 226, 143, 176>>Ta có thể thấy string này được biểu diễn như 1 chuỗi nhị phân gồm 3 byte. Trong Elixir, “bitstring” là bất cứ cái gì nằm giữa và >> là một chuỗi liên tiếp các bit trong bộ nhớ. Nếu chuỗi đó có số bit chia hết cho 8, ta gọi chúng là “binary” – chuỗi các byte. Và nếu các byte đó là UTF-8 hợp lệ thì nó sẽ được gọi là “string”.Ba số ở trên là biểu diễn thập phân của 3 byte của “binary”, ta có thể chuyển chúng sang cơ số 2 như sau:

|> Enum.map(base_2)# => Yeah, đúng y chang những gì ta làm ở phía trên. hehe

Ba kiểu byte của UTF-8

UTF-8 đẹp trai (cool) vì chỉ cần nhìn vào những byte đó, bạn có thể nhận ra ngay nó thuộc loại nào, dựa vào nó bắt đầu bằng gì. Có những “solo” byte (byte chứa toàn bộ một code point, bắt đầu bằng 0), “leading” byte (byte đầu tiên trong 1 vài codepoint) bắt đầu bằng 11 (có thể là có cả những số 1 khác ở sau đó) và “continuation” byte (những byte thêm của codepoint), bắt đầu bằng 10. Leading bytes cho bạn biết sắp tới có bao nhiêu continuation byte: nếu bắt đầu là 110 thì codepoint sẽ có 2 byte, nếu bắt đầu là 1110, sẽ có 3 byte trong codepoint, vân vân và mây mây…

Xem thêm: Anise Là Gì – Sử Dụng Và Sử Dụng ẩm Thực 2021

*

Dưới đây là ví dụ cho các template của UTF-8:

*

Chữ cái a được mã hoá bằng 1 solo byte – 1 byte duy nhất bắt đầu bằng 0. Kí tự “khoai lang nướng” có leading byte bắt đầu bằng 4 số 1, nghĩa là nó dài 4 byte và sau đây sẽ còn 3 continuation byte nữa bắt đầu bằng 10.Thêm nữa, mã hoá của “a” ở UTF-8 giống hệt như ASCII. Thực tế thì bất cứ đoạn text ASCII nào cũng có thể coi là UTF-8, nghĩa là nếu bạn có một đoạn text ASCII rồi, bạn có thể đơn giản tuyên bố: “OK, giờ nó là UTF-8 rồi nhé” và bắt đầu thêm các kí tự Unicode vào.Việc các loại byte bắt đầu một cách khác nhau cho phép ta có thể đọc UTF-8 từ giữa file hoặc stream dữ liệu, và nếu bạn có rơi vào giữa 1 kí tự thì bạn cũng biết làm thế nào để bỏ qua cho đến leading byte hoặc solo byte tiếp theo.Đây cũng là cơ sở để bạn có thể thực hiện các thao táo như đảo ngược chuỗi mà không lo làm vỡ chữ, đo chiều dài string, lấy substring bằng index. Trong bài tiếp theo, chúng ta sẽ xem Elixir làm những việc này như thế nào. Chúng ta cũng sẽ học được lí do tại sao mà “noël” lại không giống “noël” , làm sao để viết comment làm vỡ layout của 1 website, hay tại sao mà đến cả Elixir cũng không thể chuyển những kí tự “ΦΒΣ” từ chữ hoa thành chữ thường.

Bonus: giải thích bằng Python

110xxxxx + 11 = 11000011 = 0xc310xxxxxx + 100001 = 10100001 = 0xa1Đúng như những gì ta mong đợi

*

Chuyên mục: Hỏi Đáp