Unicode, UTF-8 और कैरेक्टर एन्कोडिंग: हर डेवलपर को क्या जानना चाहिए
आप एक वेब फ़ॉर्म से कोई नाम कॉपी करते हैं और वह आपके डेटाबेस में é के रूप में सेव हो जाता है। आप Excel में एक CSV खोलते हैं और accent वाले कैरेक्टर सब गलत दिखते हैं। एक यूज़र टेक्स्ट फ़ील्ड में इमोजी पेस्ट करता है और आपका पूरा MySQL INSERT एक अजीब एरर के साथ फेल हो जाता है। आप Excel के लिए एक फ़ाइल में BOM जोड़ते हैं और अब आपका shell script पहली लाइन पर ही टूट जाता है।
ये कैरेक्टर एन्कोडिंग बग हैं। ये डेवलपर के सामने आने वाली सबसे भ्रमित करने वाली समस्याओं में से हैं क्योंकि लक्षण यादृच्छिक लगते हैं — एक ही स्ट्रिंग एक संदर्भ में सही दिख सकती है और दूसरे में बकवास में बदल सकती है। कारण लगभग हमेशा एक ही होता है: दो सॉफ़्टवेयर इस बात पर असहमत हैं कि बाइट्स कैरेक्टर्स से कैसे मैप होते हैं।
यह गाइड उस मैपिंग को शुरू से समझाती है: कैरेक्टर एन्कोडिंग क्या हैं, Unicode और UTF-8 कैसे काम करते हैं, और सबसे सामान्य वास्तविक दुनिया के बगों को कैसे डायग्नोज़ और ठीक करें।
Unicode से पहले का युग: ASCII और Extended ASCII की अराजकता
कंप्यूटर में स्टोर हर कैरेक्टर अंततः एक संख्या है। एक कैरेक्टर एन्कोडिंग वह लुकअप टेबल है जो संख्याओं को कैरेक्टर्स से और वापस मैप करती है।
पहला व्यापक रूप से अपनाया गया एन्कोडिंग ASCII (American Standard Code for Information Interchange) था, जो 1963 में प्रकाशित हुआ। ASCII 7 बिट्स का उपयोग करता है, जो इसे 128 स्लॉट (0–127) देता है। यह अंग्रेजी वर्णमाला को बड़े और छोटे अक्षरों में, दस अंकों, सामान्य विराम चिह्नों और 33 non-printing control कैरेक्टर्स जैसे newline और tab को कवर करता है।
128 कैरेक्टर अंग्रेजी के लिए पर्याप्त हैं। वे फ्रेंच, जर्मन, स्पेनिश, अरबी, चीनी, जापानी, या दुनिया की हजारों अन्य लिखित भाषाओं के लिए बिल्कुल पर्याप्त नहीं हैं।
Extended ASCII की समस्या
एक बाइट में 8वाँ बिट उपलब्ध था, जिसका मतलब था 128 और स्लॉट (128–255)। विभिन्न संगठनों और ऑपरेटिंग सिस्टम ने उन स्लॉट्स को अलग-अलग तरीकों से भरने की जल्दी की:
- Windows-1252 (जिसे CP1252 भी कहते हैं): Microsoft Windows द्वारा पश्चिमी यूरोप में उपयोग किया जाता है। ऊपरी आधे भाग में
é,ñऔर€जैसे कैरेक्टर रखता है। - ISO-8859-1 (Latin-1): पश्चिमी यूरोपीय भाषाओं के लिए एक अंतर्राष्ट्रीय मानक। Windows-1252 के साथ काफी हद तक ओवरलैप करता है लेकिन समान नहीं है — रेंज 128–159 Latin-1 में control कैरेक्टर हैं लेकिन Windows-1252 में printable कैरेक्टर हैं।
- ISO-8859-5: Cyrillic को कवर करता है।
- ISO-8859-6: अरबी को कवर करता है।
- Shift JIS, EUC-JP, Big5: पूर्वी एशियाई भाषाओं के लिए पूरी तरह अलग multi-byte स्कीम।
परिणाम यह था कि एक सिस्टम पर बना फ़ाइल दूसरे पर बकवास दिखाई देती थी जब तक दोनों एक ही एन्कोडिंग पर सहमत न हों — और यह संकेत करने का कोई विश्वसनीय तरीका नहीं था कि फ़ाइल किस एन्कोडिंग का उपयोग करती है। सॉफ़्टवेयर अनुमान लगाते थे, और लगातार गलत अनुमान लगाते थे।
यही वह समस्या है जिसे हल करने के लिए Unicode बनाया गया था।
Unicode: हर भाषा के लिए एक कैरेक्टर सेट
Unicode एक एन्कोडिंग नहीं है। यह एक कैरेक्टर सेट है — एक विशाल लुकअप टेबल जो उपयोग में हर लेखन प्रणाली में हर कैरेक्टर को एक अद्वितीय संख्या असाइन करती है, साथ ही हजारों प्रतीक, इमोजी और ऐतिहासिक लिपियाँ।
उस टेबल में प्रत्येक प्रविष्टि को code point कहा जाता है, जो U+ के बाद एक हेक्साडेसिमल संख्या के रूप में लिखी जाती है। कुछ उदाहरण:
Unicode मानक वर्तमान में 140,000 से अधिक कैरेक्टर को परिभाषित करता है और नियमित रूप से अपडेट होता रहता है।
Unicode Planes
Unicode code points U+0000 से U+10FFFF तक होते हैं, जो थोड़े से 1.1 मिलियन से अधिक संभावित स्लॉट देते हैं। वह स्थान प्रत्येक 65,536 code points के 17 planes में विभाजित है।
- Plane 0 — Basic Multilingual Plane (BMP): U+0000 से U+FFFF। आज सामान्य उपयोग में लगभग हर कैरेक्टर शामिल है: Latin, Greek, Cyrillic, Arabic, Hebrew, CJK और अधिक।
- Plane 1 — Supplementary Multilingual Plane (SMP): U+10000 से U+1FFFF। इमोजी, ऐतिहासिक लिपियाँ, संगीत संकेतन।
- Planes 2–16: अतिरिक्त CJK unified ideographs, दुर्लभ ऐतिहासिक लिपियाँ, और अनअसाइन्ड स्थान।
MySQL टेबल्स को तोड़ने वाले इमोजी Plane 1 में रहते हैं। यह विवरण मायने रखता है, जैसा हम जल्द देखेंगे।
UTF-8: Unicode वास्तव में कैसे स्टोर होता है
Unicode परिभाषित करता है कि हर कैरेक्टर को कौन सी संख्या मिलती है। यह यह नहीं बताता कि उन संख्याओं को डिस्क पर या मेमोरी में बाइट्स के रूप में कैसे स्टोर किया जाए। यह Unicode एन्कोडिंग का काम है।
UTF-8 वेब पर और आधुनिक सॉफ़्टवेयर में अब तक का सबसे व्यापक रूप से उपयोग किया जाने वाला Unicode एन्कोडिंग है। यह variable-width है, जिसका मतलब है कि अलग-अलग कैरेक्टर अलग-अलग संख्या में बाइट्स लेते हैं:
UTF-8 बाइट सीक्वेंस कैसे काम करते हैं
UTF-8 self-synchronizing है। किसी भी कैरेक्टर का पहला बाइट आपको बताता है कि कितने बाइट्स इसके बाद आते हैं:
- 0xxxxxxx — 1-बाइट कैरेक्टर (ASCII)। कोई भी valid ASCII फ़ाइल valid UTF-8 है।
- 110xxxxx 10xxxxxx — 2-बाइट कैरेक्टर।
- 1110xxxx 10xxxxxx 10xxxxxx — 3-बाइट कैरेक्टर।
- 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx — 4-बाइट कैरेक्टर।
Continuation बाइट्स हमेशा 10 से शुरू होते हैं, इसलिए आप तुरंत बता सकते हैं कि कोई बाइट नए कैरेक्टर की शुरुआत है या किसी के बीच का हिस्सा। इससे partial stream पढ़ने के बाद resynchronize करना संभव होता है।
ठोस उदाहरण — é (U+00E9) को encode करना:
U+00E9 binary में 11101001 है। यह 2-बाइट रेंज में आता है। Template 110xxxxx 10xxxxxx 11000011 10101001 उत्पन्न करती है — bytes 0xC3 0xA9 — जो कि é के लिए UTF-8 बिल्कुल यही स्टोर करता है।
UTF-8 क्यों हावी है
UTF-8 में तीन गुण हैं जिनके कारण यह जीता:
- ASCII compatibility। कोई भी ASCII-only फ़ाइल valid UTF-8 है। मौजूदा सिस्टम को migrate करने के लिए उन फ़ाइलों को छूने की ज़रूरत नहीं पड़ी जिनमें कोई non-ASCII सामग्री नहीं थी।
- Latin लिपियों के लिए स्थान दक्षता। अंग्रेजी टेक्स्ट प्रति कैरेक्टर एक बाइट का उपयोग करती है, ASCII जैसा ही। केवल ASCII के बाहर के कैरेक्टर्स को अधिक बाइट्स की लागत आती है।
- कोई byte-order अस्पष्टता नहीं। बाइट संरचना बिना किसी हेडर के स्पष्ट है। (इसकी तुलना UTF-16 से करें, जिसे byte-order mark की आवश्यकता होती है।)
UTF-16 और UTF-32: आप उन्हें कब देखेंगे
UTF-8 केवल Unicode एन्कोडिंग नहीं है, और कुछ वातावरण दूसरों का उपयोग करते हैं।
UTF-16 BMP कैरेक्टर को दो बाइट्स (एक 16-बिट code unit) के रूप में encode करता है और supplementary plane कैरेक्टर को चार बाइट्स (दो 16-बिट code units, जिन्हें surrogate pair कहते हैं) के रूप में। Windows internals, Java strings, JavaScript strings (engine level पर), और .NET strings सभी आंतरिक रूप से UTF-16 का उपयोग करते हैं।
UTF-32 हमेशा प्रति कैरेक्टर ठीक चार बाइट्स का उपयोग करता है। यह स्थान बर्बाद करता है लेकिन character index द्वारा random access को तेजी से आसान बनाता है। कुछ database internals इसका उपयोग करते हैं।
Byte Order Mark (BOM)
UTF-16 में एक endianness समस्या है। एक दो-बाइट सीक्वेंस को big-endian (0x00 0x41 for A) या little-endian (0x41 0x00) के रूप में स्टोर किया जा सकता है। रिसीवर को जानना होगा कि कौन सा क्रम उपयोग किया गया था।
समाधान Byte Order Mark है: code point U+FEFF को फ़ाइल की शुरुआत में रखा जाता है। UTF-16 big-endian में, यह bytes FE FF के रूप में दिखाई देता है। UTF-16 little-endian में, यह FF FE के रूप में दिखाई देता है। जो reader इनमें से किसी एक को देखता है वह byte order जान लेता है।
UTF-8 में तकनीकी रूप से एक BOM भी है (EF BB BF) लेकिन यह UTF-8 के लिए अर्थहीन है क्योंकि UTF-8 में कोई byte-order अस्पष्टता नहीं है। यही वह जगह है जहाँ यह समस्याएं पैदा करता है:
- Excel और CSV फ़ाइलें। Excel UTF-8 BOM को CSV को UTF-8 के रूप में interpret करने के संकेत के रूप में उपयोग करता है। इसके बिना, Excel system locale encoding (अक्सर Windows-1252) पर default करता है, जो accent वाले कैरेक्टर्स को खराब कर देता है। कई developers Excel को खुश करने के लिए विशेष रूप से BOM जोड़ते हैं।
- Shell scripts। एक shell script जो
#!/bin/bashसे शुरू होती है उसमें वह सीक्वेंस बिल्कुल पहले bytes के रूप में होना चाहिए। यदि UTF-8 BOM shebang से पहले आती है, तो kernelEF BB BF 23 21देखता है और shebang को नहीं पहचानता, जिससे script fail हो जाती है या गलत interpreter के साथ चलती है।
अंगूठे का नियम: Excel-targeted CSV फ़ाइलों के लिए BOM जोड़ें, shell scripts या JSON फ़ाइलों में कभी न जोड़ें।
सामान्य वास्तविक दुनिया के एन्कोडिंग बग
Mojibake: UTF-8 को Latin-1 की तरह पढ़ना
Mojibake (文字化け) encoding mismatch के कारण garbled text के लिए जापानी शब्द है। आधुनिक वेब पर सबसे सामान्य variant: एक UTF-8 string को ऐसे पढ़ा जाता है जैसे वह Latin-1 (ISO-8859-1 या Windows-1252) हो।
कैरेक्टर é U+00E9 है। UTF-8 में यह दो bytes 0xC3 0xA9 के रूप में स्टोर होता है। यदि कोई program उन दो bytes को Latin-1 के रूप में पढ़ता है, तो यह दो अलग कैरेक्टर देखता है: 0xC3 है à और 0xA9 है ©। String café बन जाती है café।
यह विशिष्ट corruption pattern — é के लिए é, è के लिए è, à के लिए à — UTF-8 के Latin-1 के रूप में interpret होने का स्पष्ट संकेत है। आप इसे सबसे अधिक देखेंगे:
- डेटाबेस में जहाँ insert पर connection charset UTF-8 पर सेट नहीं था।
- पुराने mail servers के माध्यम से भेजे गए emails में।
- mismatched locale settings वाले systems के बीच कॉपी की गई फ़ाइलों में।
ठीक करने का तरीका यह है कि mismatch कहाँ होता है यह पहचानें और उस layer पर encoding declaration को सही करें — corrupted string को बाद में patch करने की कोशिश न करें।
Replacement Character (U+FFFD)
जब एक decoder उस byte sequence का सामना करता है जो expected encoding में valid नहीं है, तो उसे कुछ करना होगा। Unicode मानक replacement character U+FFFD को substitute करने की सिफारिश करता है, जो <0xFFFD> के रूप में प्रदर्शित होता है। यदि आप output में ? boxes या <0xFFFD> कैरेक्टर देखते हैं, तो decoder ऐसे bytes का सामना कर चुका है जो वह interpret नहीं कर सका।
सामान्य कारण:
- एक multi-byte UTF-8 sequence को truncate करना। यदि आप एक string को उस byte boundary पर काटते हैं जो एक 3-byte CJK कैरेक्टर के अंदर पड़ता है, तो partial sequence invalid है।
- एक Latin-1 encoded फ़ाइल को strict UTF-8 decoder को पास करना।
- एक ऐसे source से डेटा receive करना जो encodings को mix करता है।
MySQL में utf8 बनाम utf8mb4: इमोजी की समस्या
यह web development में सबसे प्रसिद्ध encoding bugs में से एक है। MySQL के पास utf8 और utf8mb4 नाम के दो character sets हैं।
MySQL का utf8 वास्तविक UTF-8 नहीं है। यह केवल U+FFFF तक के code points को support करता है — ठीक Basic Multilingual Plane — maximum तीन bytes per character का उपयोग करके। इमोजी Plane 1 (U+10000 और ऊपर) में रहते हैं और चार bytes की आवश्यकता होती है। जब आप utf8 के रूप में defined column में four-byte UTF-8 character insert करने की कोशिश करते हैं, MySQL चुपचाप value को truncate कर देता है या error throw करता है।
utf8mb4 full four-byte support के साथ actual UTF-8 है। यह इमोजी और अन्य Supplementary Plane characters को handle करने के लिए specifically introduced किया गया था।
सही तरीका: नए tables और databases के लिए हमेशा utf8mb4 का उपयोग करें। Connection character set और collation को भी explicitly सेट करें:
ALTER DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE posts CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;और अपने application config में connection parameter सेट करें:
charset=utf8mb4
MySQL में utf8 का उपयोग करना एक legacy footgun है। इसे deprecated मानें।
Python में String Length बनाम Character Count
Python 2 ने strings को default रूप से byte sequences के रूप में स्टोर किया। UTF-8 string के लिए len("café") 5 return करता था — क्योंकि é दो bytes लेता है — 4 नहीं। सही multi-byte handling के लिए explicit u"" unicode literals और सावधानीपूर्वक encode/decode discipline की आवश्यकता थी।
Python 3 ने इसे ठीक किया: str Unicode code points का एक sequence है, इसलिए len("café") 4 return करता है। Byte data को explicit .encode() और .decode() calls के साथ bytes type में स्थानांतरित किया गया।
एक subtlety बनी रहती है। Python 3 का len() code points गिनता है, grapheme clusters नहीं। कैरेक्टर é को एक single code point (U+00E9) के रूप में या e (U+0065) plus combining acute accent (U+0301) के रूप में represent किया जा सकता है। दोनों एक जैसे render होते हैं लेकिन अलग len() values produce करते हैं। Multilingual applications जो users को character count expose करते हैं उन्हें raw len() की बजाय Unicode segmentation library का उपयोग करना चाहिए।
जब आप JSON data inspect करते हैं जिसमें multilingual strings हों तो आप यह notice कर सकते हैं — जो एक single character जैसा दिखता है वह internally दो code points हो सकता है।
एन्कोडिंग तुलना तालिका
एन्कोडिंग समस्याओं को कैसे detect और ठीक करें
Detection
Python में:
import chardet
with open("mystery_file.txt", "rb") as f:
raw = f.read()
result = chardet.detect(raw)
print(result) # {'encoding': 'Windows-1252', 'confidence': 0.73, 'language': ''}chardet library byte patterns से encoding guess करने के लिए statistical analysis का उपयोग करती है। Confidence score ईमानदार है — encoding detection heuristic है, deterministic नहीं।
Browser में: DevTools खोलें, Network tab पर जाएं, एक HTML page के लिए response खोजें, और Content-Type header check करें। इसे text/html; charset=utf-8 पढ़ना चाहिए। यदि यह absent है या गलत है, तो browser guess करेगा, अक्सर गलत तरीके से।
Database में: Database, table, और column level पर character set check करें:
SHOW VARIABLES LIKE 'character_set%';
SHOW CREATE TABLE posts;Mojibake Database को ठीक करना
यदि data UTF-8 के रूप में insert किया गया था लेकिन Latin-1 के रूप में store हुआ (एक सामान्य migration गलती), तो आप कभी-कभी इसे recover कर सकते हैं:
-- Corrupted data को binary के रूप में पढ़ें, फिर UTF-8 के रूप में interpret करें
UPDATE posts
SET body = CONVERT(CAST(CONVERT(body USING latin1) AS BINARY) USING utf8mb4)
WHERE body REGEXP '[Ã]';यह तभी काम करता है जब मूल bytes बरकरार हों और केवल गलत तरीके से labeled हों। यदि data सच में corrupt हो गया था — bytes drop या substitute हो गए — तो backup के बिना recovery संभव नहीं है।
बचाव
- Encoding को हमेशा explicitly declare करें। Defaults पर कभी भरोसा न करें।
- हर HTML document में
<meta charset="UTF-8">सेट करें। - API responses पर
Content-Type: application/json; charset=utf-8सेट करें। - Database connections खोलते समय connection charset specify करें।
- अपने editor में UTF-8 का उपयोग करें और text files के लिए इसे enforce करने के लिए अपना version control system configure करें।
Character sets से परे encoding tasks के लिए — जैसे binary data को text-safe representations में convert करना — FileCrank पर encode and decode text tool बिना किसी setup के Base64 conversion handle करता है। मौजूदा encoding article Base64, URL encoding, और HTML entities को cover करता है यदि आपको उनकी आवश्यकता है। यदि आप configuration files के साथ काम कर रहे हैं और verify करना चाहते हैं कि multilingual strings सही दिखती हैं, तो आप browser में directly JSON data inspect कर सकते हैं।
क्या याद रखें
Character encoding bugs रहस्यमयी लगते हैं क्योंकि failure mode — garbled या missing characters — वास्तविक कारण से बहुत दूर दिखाई देता है। लेकिन underlying mechanism सरल है: दो programs bytes को characters में convert करने के लिए अलग-अलग lookup tables का उपयोग कर रहे हैं।
इन तीन नियमों को अपने सामने रखें:
- हर जगह हमेशा UTF-8 का उपयोग करें जब तक कोई specific system कुछ और force न करे। इसे explicitly सेट करें; defaults पर कभी भरोसा न करें।
- MySQL में,
utf8mb4का उपयोग करें,utf8का नहीं। जिस क्षण कोई user emoji भेजता है उस पर अंतर पड़ता है। - जब आप mojibake देखें, boundary खोजें। देखें कि bytes कहाँ एक ऐसे system को पार करते हैं जो उनकी encoding नहीं जानता। उस point पर declaration ठीक करें, downstream symptoms को नहीं।
Mechanics को समझना — code points, byte sequences, encoding declarations — का मतलब है कि आप अनुमान लगाने की बजाय एक नए encoding bug के बारे में systematically सोच सकते हैं। अगली बार जब आप अपने output में é देखें, आप तुरंत जान लेंगे कि क्या हुआ और कहाँ देखना है।
सभी FileCrank tools पूरी तरह से आपके browser में चलते हैं। कोई फ़ाइल server पर upload नहीं की जाती। यदि आप ऐसे टेक्स्ट के साथ काम कर रहे हैं जिसमें multilingual content है, तो JSON Viewer या Base64 Encode/Decode tool आज़माएं — दोनों बिना किसी configuration के Unicode को सही ढंग से handle करते हैं।