/อ่าน 9 นาที

UTF-8 คืออะไร? Encoding ที่ขับเคลื่อนเว็บยุคใหม่

UTF-8 คือ encoding ที่ทำให้หน้าเดียวแสดงภาษาอังกฤษ ภาษาไทย ภาษาจีน ภาษาอาหรับ สัญลักษณ์ทางคณิตศาสตร์ และอีโมจิได้โดยไม่ต้องเปลี่ยนรูปแบบ บทความนี้อธิบายวิธีทำงานและเหตุผลที่มันกลายเป็นค่าเริ่มต้นของเว็บยุคใหม่

UTF-8 คืออะไร?

UTF-8 ย่อมาจาก Unicode Transformation Format - 8-bit เป็น การเข้ารหัสอักขระแบบความยาวแปรผัน ที่สามารถแทนอักขระทุกตัวในมาตรฐาน Unicode ได้

UTF-8 ใช้พื้นที่ 1 ถึง 4 ไบต์ ต่ออักขระ อักขระ ASCII พื้นฐาน เช่น A, z, 5 และ ! ใช้เพียง 1 ไบต์ ส่วนตัวอักษรมีวรรณยุกต์ อักษรไทย อักษรจีน และอีโมจิจะใช้หลายไบต์ตามความจำเป็น

UTF-8 ถูกออกแบบในปี 1992 โดย Ken Thompson และ Rob Pike ปัจจุบันเป็น encoding หลักของเว็บไซต์ APIs source code ฐานข้อมูล และไฟล์ตั้งค่าจำนวนมาก

Unicode กับ UTF-8 ต่างกันอย่างไร?

หลายคนมักสับสนระหว่าง Unicode กับ UTF-8 แต่ทั้งสองอย่างทำหน้าที่คนละส่วนของปัญหาเดียวกัน:

  • Unicode คือชุดอักขระขนาดใหญ่ที่กำหนดหมายเลขเฉพาะหรือ code point ให้กับอักขระแต่ละตัว เช่น A คือ U+0041 และเครื่องหมายยูโรคือ U+20AC
  • UTF-8 คือ encoding หรือวิธีแปลง code point ของ Unicode ให้กลายเป็นไบต์จริงที่คอมพิวเตอร์เก็บ ส่ง และอ่านได้

เปรียบเทียบง่าย ๆ คือ Unicode เป็นพจนานุกรม ที่บอกว่าอักขระแต่ละตัวมีหมายเลขอะไร ส่วน UTF-8 เป็นวิธีจัดส่ง ที่บรรจุหมายเลขเหล่านั้นลงเป็นไบต์ ยังมี encoding อื่นอย่าง UTF-16 และ UTF-32 แต่ UTF-8 กลายเป็นมาตรฐานเว็บเพราะกะทัดรัดและเข้ากันย้อนหลังกับ ASCII

UTF-8 Encoding ทำงานอย่างไร

UTF-8 ใช้รูปแบบไบต์แบบความยาวแปรผัน จำนวนไบต์ขึ้นอยู่กับค่าของ Unicode code point:

ช่วง Code Pointไบต์รูปแบบไบต์ตัวอย่าง
U+0000 - U+007F10xxxxxxxA, z, 5, !
U+0080 - U+07FF2110xxxxx 10xxxxxxé, ñ, ü
U+0800 - U+FFFF3ก, €, 中, ✓
U+10000 - U+10FFFF411110xxx 10xxxxxx 10xxxxxx 10xxxxxxอีโมจิและอักษรโบราณบางชุด

บิตนำหน้าของแต่ละไบต์บอก decoder ได้ทันทีว่าไบต์นั้นเป็นส่วนไหน:

  • ขึ้นต้นด้วย 0: อักขระ ASCII แบบ 1 ไบต์
  • ขึ้นต้นด้วย 110: ไบต์แรกของอักขระ 2 ไบต์
  • ขึ้นต้นด้วย 1110: ไบต์แรกของอักขระ 3 ไบต์
  • ขึ้นต้นด้วย 11110: ไบต์แรกของอักขระ 4 ไบต์
  • ขึ้นต้นด้วย 10: continuation byte ไม่ใช่จุดเริ่มต้นของอักขระ

โครงสร้างนี้ทำให้ UTF-8 self-synchronizing หากโปรแกรมเริ่มอ่านกลางกระแสไบต์ ก็สามารถเลื่อนไปจนเจอไบต์ที่ไม่ได้ขึ้นต้นด้วย 10 เพื่อหาขอบเขตอักขระถัดไปได้

ตัวอย่างการเข้ารหัสทีละขั้นตอน

1 ไบต์: "A" (U+0041)

Code point: U+0041 = 65 = 1000001 in binary
Range: U+0000-U+007F -> 1 byte
Pattern: 0xxxxxxx

Fill in: 0 1000001
Byte:    01000001 = 0x41

"A" in UTF-8 = 0x41 (identical to ASCII)

2 ไบต์: e มี accent (U+00E9)

Code point: U+00E9 = 233 = 11101001 in binary
Range: U+0080-U+07FF -> 2 bytes
Pattern: 110xxxxx 10xxxxxx

Split bits: 00011  101001
Fill in:    11000011 10101001
Bytes:      0xC3     0xA9

"e with acute" in UTF-8 = 0xC3 0xA9

3 ไบต์: เครื่องหมายยูโร (U+20AC)

Code point: U+20AC = 8364 = 10000010101100 in binary
Range: U+0800-U+FFFF -> 3 bytes
Pattern: 1110xxxx 10xxxxxx 10xxxxxx

Split bits: 0010  000010  101100
Fill in:    11100010 10000010 10101100
Bytes:      0xE2     0x82     0xAC

"Euro sign" in UTF-8 = 0xE2 0x82 0xAC

4 ไบต์: อีโมจิหน้ายิ้ม (U+1F600)

Code point: U+1F600 = 128512
Range: U+10000-U+10FFFF -> 4 bytes
Pattern: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

Split bits: 000  011111  011000  000000
Fill in:    11110000 10011111 10011000 10000000
Bytes:      0xF0     0x9F     0x98     0x80

"grinning face emoji" in UTF-8 = 0xF0 0x9F 0x98 0x80

ทำไม UTF-8 ถึงได้รับความนิยมสูงสุด

UTF-8 กลายเป็น encoding หลักด้วยเหตุผลที่ใช้งานได้จริงหลายข้อ:

  • เข้ากันย้อนหลังกับ ASCII - เอกสาร ASCII เดิมถือเป็น UTF-8 ที่ถูกต้องอยู่แล้ว
  • ประหยัดพื้นที่ - ข้อความภาษาอังกฤษใช้ 1 ไบต์ต่ออักขระ ส่วนภาษาอื่นใช้เท่าที่จำเป็น
  • ไม่มีปัญหา byte order - ต่างจาก UTF-16 และ UTF-32 ที่อาจต้องสนใจลำดับไบต์
  • Self-synchronizing - โปรแกรมกู้ขอบเขตอักขระหลังเกิด error หรือการ seek กลางข้อมูลได้ง่ายกว่า
  • เข้ากับ C-style strings ได้ดี - ข้อความทั่วไปไม่มี null byte แทรกโดยไม่คาดคิด
  • ครอบคลุมทั่วโลก - แทนอักขระ Unicode ได้ทั้งหมด ตั้งแต่ Latin พื้นฐาน ภาษาไทย ไปจนถึงอีโมจิ

UTF-8 เทียบกับ UTF-16 และ UTF-32

Unicode มี encoding หลัก 3 แบบ ข้อแตกต่างสำคัญมีดังนี้:

คุณสมบัติUTF-8UTF-16UTF-32
ไบต์ต่ออักขระ1-42 หรือ 44 เสมอ
เข้ากันกับ ASCIIใช่ไม่ไม่
ปัญหา byte orderไม่มีมี อาจต้องใช้ BOMมี อาจต้องใช้ BOM
ขนาดคำว่า "Hello"5 ไบต์10 ไบต์20 ไบต์
การใช้งานทั่วไปเว็บ, Linux, macOS, JSONWindows APIs, Java, โครงสร้าง string ภายใน JavaScriptการประมวลผลภายในบางระบบ
การใช้งานบนเว็บครองส่วนใหญ่พบน้อยมากแทบไม่พบ

UTF-16 ยังสำคัญเพราะ JavaScript และ Java ใช้หน่วย 16 บิตภายใน string นี่คือเหตุผลที่ความยาว string กับจำนวนอักขระที่มองเห็นอาจไม่ตรงกันเมื่อมีอีโมจิหรืออักขระนอก Basic Multilingual Plane

การใช้ UTF-8 ในโค้ด

ภาษาโปรแกรมและเบราว์เซอร์สมัยใหม่มักมีเครื่องมือ UTF-8 ในตัว:

JavaScript

// Encode string to UTF-8 bytes
const encoder = new TextEncoder();
const bytes = encoder.encode("Hello €");
console.log(bytes);
// Uint8Array [72, 101, 108, 108, 111, 32, 226, 130, 172]

// Decode UTF-8 bytes back to string
const decoder = new TextDecoder("utf-8");
const text = decoder.decode(bytes);
console.log(text);  // "Hello €"

// Character length is not always byte length
"Hello".length;  // 5
new TextEncoder().encode("Hello").length;  // 5
new TextEncoder().encode("cafe").length;   // 4
new TextEncoder().encode("café").length;   // 5

Python

# Encode string to UTF-8 bytes
text = "Hello €"
utf8_bytes = text.encode("utf-8")
print(utf8_bytes)       # b'Hello \xe2\x82\xac'
print(len(utf8_bytes))  # 9 bytes

# Decode UTF-8 bytes to string
decoded = utf8_bytes.decode("utf-8")
print(decoded)  # "Hello €"

# Character vs byte length
len("café")                    # 4 characters
len("café".encode("utf-8"))    # 5 bytes

HTML

<!-- Always declare UTF-8 in HTML -->
<meta charset="UTF-8">

<!-- Put it early inside <head>, before text content that needs decoding -->

ปัญหา UTF-8 ที่พบบ่อย

บั๊กเกี่ยวกับ encoding ส่วนใหญ่มักเกิดจากการอ่านไบต์ด้วย encoding ผิด หรือสับสนระหว่างจำนวนไบต์กับจำนวนอักขระ:

Mojibake หรือข้อความเพี้ยน

เมื่อไบต์ UTF-8 ถูก decode เป็น Latin-1 หรือ Windows-1252 ข้อความที่อ่านได้อาจกลายเป็นตัวอักษรแปลก ๆ วิธีแก้คือให้ฝั่งเขียนและฝั่งอ่านใช้ UTF-8 ตรงกัน

Replacement characters

อักขระแทนค่า U+FFFD จะปรากฏเมื่อ decoder เจอไบต์ที่ไม่ใช่ UTF-8 ที่ถูกต้อง ควรตรวจ encoding ของไฟล์ต้นฉบับและหลีกเลี่ยงการ decode ซ้ำ

BOM ทำให้ระบบบางตัวมีปัญหา

เครื่องมือบางตัวใส่ UTF-8 Byte Order Mark ไว้ต้นไฟล์ ซึ่งอาจทำให้ JSON shell script หรือ parser ที่เข้มงวดทำงานพลาด ควรบันทึกเป็น UTF-8 without BOM เมื่อเป็นไปได้

จำนวนอักขระไม่เท่ากับจำนวนไบต์

string 10 อักขระอาจมี 10 ไบต์หรือมากกว่านั้นมาก ควรใช้ limit แบบอักขระสำหรับ UI และ limit แบบไบต์เมื่อเกี่ยวกับ storage หรือ network

แนวทางปฏิบัติที่ดี

แนวทางเหล่านี้ช่วยลดปัญหา encoding ได้มาก:

  • ใช้ UTF-8 เป็นค่าเริ่มต้น ยกเว้นมีข้อจำกัด legacy ที่จำเป็นจริง ๆ
  • ประกาศ encoding ให้ชัดเจน ด้วย <meta charset="UTF-8"> ใน HTML และ charset=utf-8 ใน HTTP headers
  • บันทึก source files เป็น UTF-8 โดยเฉพาะไฟล์ code และ data ควรใช้แบบไม่มี BOM
  • ใช้ utf8mb4 ใน MySQL เพื่อรองรับ Unicode เต็มรูปแบบ รวมถึงอีโมจิ
  • อย่าตัด raw UTF-8 bytes แบบสุ่ม หากเป็นข้อความที่ผู้ใช้เห็น ควรตัดตาม characters, code points หรือ grapheme clusters
  • ทดสอบด้วยข้อความหลายภาษา เพื่อให้บั๊กปรากฏก่อนผู้ใช้ป้อนชื่อ ที่อยู่ หรือข้อความจริง

เข้ารหัสและถอดรหัสข้อความ UTF-8

ใช้เครื่องมือ UTF-8 Converter ฟรีของเราเพื่อตรวจ byte sequences เข้ารหัสข้อความ และถอดรหัสไบต์ได้ทันทีในเบราว์เซอร์

ลองใช้ UTF-8 Converter

แหล่งอ้างอิง

  1. Yergeau, F. (2003). UTF-8, a transformation format of ISO 10646. RFC 3629, IETF. https://datatracker.ietf.org/doc/html/rfc3629
  2. Pike, R. & Thompson, K. (2003). UTF-8 history. https://www.cl.cam.ac.uk/~mgk25/ucs/utf-8-history.txt
  3. The Unicode Consortium. The Unicode Standard. https://www.unicode.org/standard/standard.html
  4. Mozilla Developer Network. TextEncoder - Web APIs. https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder
  5. W3Techs. Usage statistics of character encodings for websites. https://w3techs.com/technologies/overview/character_encoding
USTHJP