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+007F | 1 | 0xxxxxxx | A, z, 5, ! |
| U+0080 - U+07FF | 2 | 110xxxxx 10xxxxxx | é, ñ, ü |
| U+0800 - U+FFFF | 3 | ก, €, 中, ✓ | |
| U+10000 - U+10FFFF | 4 | 11110xxx 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 0xA93 ไบต์: เครื่องหมายยูโร (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 0xAC4 ไบต์: อีโมจิหน้ายิ้ม (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-8 | UTF-16 | UTF-32 |
|---|---|---|---|
| ไบต์ต่ออักขระ | 1-4 | 2 หรือ 4 | 4 เสมอ |
| เข้ากันกับ ASCII | ใช่ | ไม่ | ไม่ |
| ปัญหา byte order | ไม่มี | มี อาจต้องใช้ BOM | มี อาจต้องใช้ BOM |
| ขนาดคำว่า "Hello" | 5 ไบต์ | 10 ไบต์ | 20 ไบต์ |
| การใช้งานทั่วไป | เว็บ, Linux, macOS, JSON | Windows 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; // 5Python
# 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 bytesHTML
<!-- 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แหล่งอ้างอิง
- Yergeau, F. (2003). UTF-8, a transformation format of ISO 10646. RFC 3629, IETF. https://datatracker.ietf.org/doc/html/rfc3629
- Pike, R. & Thompson, K. (2003). UTF-8 history. https://www.cl.cam.ac.uk/~mgk25/ucs/utf-8-history.txt
- The Unicode Consortium. The Unicode Standard. https://www.unicode.org/standard/standard.html
- Mozilla Developer Network. TextEncoder - Web APIs. https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder
- W3Techs. Usage statistics of character encodings for websites. https://w3techs.com/technologies/overview/character_encoding