הבלוג של ארתיום
בלוג על לינוקס, תוכנה חופשית, מוזיקה, סלסה, ומה לא!
UTF-8, UTF-16, Unicode וכל מה שביניהם.
היו היה בית, בתקופה של תחילת עידן המחשבים הוא היה מסוגל לשמור תו בודד. אבל אנושות הלכה והתפתח ויום אחד התברר שהוא לא מספיק. יש צרפתים, יש רוסים, יש סינים ולא ניתן לדחוף את הכל התווים הידועים לבית אחד מסכן. אז התאספו אנשים חכמים ובסוף שנות ה־80 --- תחילת 90 החליטו לבנות תקן אחיד שיכסה את כל השפות האפשריות --- Unicode.
האנשים האלה חשבו וחישבו ששני בתים יספיקו לכסות את כל השפות אפשריות. כך נולד קידוד של Unicode בשני בתים. בתחילת ה־90, יצרתי UNIX הגדולים גם חשבו ובנו UTF-8 שכדי להבטיח תאימות לאחור עם כל התכנה הקיימת --- כך נולד קידוד בעל אורך משתנה.
עבר הזמן, יצרני תכנה שונים הולכו בשני כיוונים: אחד להציג כל תו בשני בתים והשני להמשיך להשתמש ב־ANSI C String ולעבוד עם תווים בעלי ייצוג באורך שונה. אבל, באמצע שנות ה־90 כבר היה ברור ששני בתים לא יספיקו כדי לכסות את כל השפות האפשריות. ולכן התקן הורחב כך שהוא כלל ערכים מ־0 ועד ל־0x10FFFF.
אבל מה עם קידוד של שני בתים? אבל מה לעשות עם כל התכנה שנכתבה עבור 16 ביט? להמשיך ולתמוך! כך נולד UTF-16. גם הוא בעל אורך משתנה (כמעט) וגם הוא תומך בכל האוסף הרחב של תווי Unicode. למעשה: Win32 API, .Net, Java, ICU, Cooca, Qt כולם עובדים עם קידוד UTF-16 בו כל תו של Unicode יכול להיות מוצג ע"י איבר אחד או שניים של המחרוזת.
עכשיו בואו נראה מה קורה במציאות. קחו את קוד ה־Java הבא:
class test {
public static void main(String argv[])
{
String s1="שלום";
String s2="𠂊";
System.out.println(s1.length());
System.out.println(s2.length());
}
}
ותסתכלו: המחרוזת הראשונה מכילה 4 תווים והשנייה תו אחד בלבד. תקמפלו ותריצו. הפתעה! אורך של המחרוזת הראשונה הוא 4 תווים ושל השנייה 2(!). למעשה, דברים כאלה קיימים כמעט בכל toolkit שעובד עם utf-16.
הבעיה הוא שב־99% מהמקרים אפשר להתעלם מתווים כפולים וזה יעבוד מצוין, כיוון שרוב השפות (אפילו במזרח הרחוק) עדיין מיוצגות בתחום הקידוד הדורש רק שני בתים. זה מקור מעולה של באגים שקשה מאוד לאתר, לשחזר, לדבג ולתקן, גם אם אתה מודע לבעיה הזו 100% מהזמן.
אני יכול להבין את הגישה של Microsoft שבחרה בקידוד של 2 בתים כשהיה נראה שהם מספיקים לחלוטין, אבל עדיין, המון הכלים המודרניים (גם לא קשורים ל־MS) כמו ICU, Java, QT4, Python מתעקשים לעבוד עם ייצוג פניני של תווים בעלי 16 ביטים המהווה מתכון לצרות.
השאלה הנוספת היא, מתי אנחנו באמת צריכים לעבוד עם קידוד שונה מ־UTF-8? מתי באמת מעניין אותנו תו ספציפי? כמעט ואף פעם! צריך לעשות מיון? הוא יעבוד גם ב־utf8. צריך לעשות חיפוש? עובד מצוין ב־utf8. צריך לחתוך משפט? פשוט חפשו רווח! כפי שזה ב־ASCII. במקרים נדירים אנחנו באמת מתייחסים לתווים כפי שהם.
כאשר אנחנו באמת צריכים לעבוד עם תווים כפי שהם (למשל כדי להשתמש ב־toupper עבור שפה ספציפית). אז המרה ל־Unicode מ־utf8 ובחזרה היא פשוטה לחלוטין (גם בלי ספריות מסובכות), וכאשר אנחנו כבר עובדים עם Unicode --- יש לנו ייצוג אמתי --- תו אחד זה איבר אחד בלי חשש שמשהו ייחתך באמצע.
רק חבל מאוד, שרוב הכלים החזקים בתחום לוקליזציה (כמו ICU) עדיין עובדים עם utf-16 במקום לעבוד עם תצוגה טבעית של UTF-32 או תצוגה שתואמת לכל תכנה אחרת utf-8.
תגובות
סליחה, אבל UTF-8 הוא בין בית אחד ל4 בתים (הבית הבודד שומר את ערך ה ASCII וכל דבר מעל 255 זה כבר קוד של "יוניקוד"). אני לא זוכר לגבי UTF-16.
ד"א האירגון Unicode מציג את התווים בUTF-16 ולא ב UTF-8.
הזיהוי של קידוד של UTF-16, UTF32 הוא באמצעות BOM - Byte Order Mark המסביר מה הקידוד ואם הוא מתעסק עם אינדיאנים גדולים או קטנים. ב UTF-8 אין את העניין של אינדיאנים עד כמ שאני יודע, אבל גם לו יש BOM שלא כולם משתמשים בו.
קידוד utf-16 שומר כל תו כשני בתים או כארבעה.
הוא מציג אותם ב־UCS-32, למשל 0x0020 או 0x10034 (כן, 16 ביט לא מספיק!)
כן, חייבים BOM כדי להבין מה האינדיאניות שלו, אבל, כשמדובר בייצוג פנימי זה לא ממש משנה.
יוניקוד זה חרא.
טוב, לא ביוניקוד הבעיה, אלא בכל שאר העולם.
הבעיה היא שקשה מידי לעבוד עם יוניקוד, צריך המרות מ ASCII ליוניקוד ואתה אף פעם לא באמת בטוח באיזה פורמט ה ASCII והאם הוא לא בעצם איזה קידוד יוניקוד כלשהו (מה לעשות שהרבה פעמים ה BOM לא קיים ואתה צריך להתחיל להסתמך על כל מיני ניחושים מושכלים/וודו בשביל שזה יעבוד כמו שצריך). אה, ואז כמובן מגיע הקטע שבו אתה משתמש בחבילות קוד חיצוניות שלא נבנו לעבוד כמו שצריך עם יוניקוד ולך תתחיל לדבג אותם...
ראבאק, אנחנו כבר בסוף העשור הראשון של שנות האלפיים ועדיין לעבוד עם טקסט רב לשוני גורם לי כל פעם מחדש לחפש את העץ הגבוה ביותר בסביבה כדי לתלות את עצמי...
ושאני לא אתחיל לדבר על לוקאליזציה, שמתגנבת לך מאחור ברגע שאתה לא מסתכל.
אגב, הקטע של לחפש רווח עובד רק בשפות מערביות, לך תתחיל עכשיו לעשות טוקאניזציה לוייטנאמית ששם אין רווח בין המילים...
נקודה טובה, בכל אופן, עדיין אפשר להמיר בקלות מ־utf-8 ל־utf32 ואז לחתוך... בקיצור. כן, צריך אפשרות לעבוד עם תווים כפישהם, רק utf-16 לצורך ייצוג פנימי זאת לא בדיוק בחירה מוצלחת (גם בלי BOMים למיניהם).
אאל"ט UTF8 יכול להגיע גם לשישה תווים במקרים קיצוניים, אבל זה משתלם בסוף כי תוחלת אורך המחרוזות קצרה יותר מUTF32 ולרב גם מUTF16.
עדו - לגבי ASCII, הוא מכיל גם קונטרולים ותווים שאינם אותיות, והוא מכיל להזכירך רק 128 סימנים כולל הכל. ה128 העליונים אינם חלק מASCII והם יכולים להיות סט תווים בשפה אחרת אם הוגדר "קוד פייג'" או במקרה של UTF למיניהם הם משמשים לבחור עמוד קידוד לתו הבא או ענף נוסף של בחירת תת-קידודים אם התו מכיל יותר משני בייטים וכולי. תחשוב עץ בינארי רק בבסיס 256 במקום 2...
לא מדויק. קידוד Unicode תקנה שנמצא בטווח 0--0x10FFFF תמיד לא יעבור אורך של 4 בתים. אם לוקחים נתונים בנאריים כלליים אז האורך יכול להגיע ברמת העיקרון עד 7. אבל זה כבר לא יהיה קידוד Unicode תקני.
מתיאוריה למציאות, או עד כמה UTF-16 באמת בעייתי
בהמשך לפוסט הקודם, החלטתי לעשות סקירה קצרה: "האם באמת תכנות שכתובות עם utf-16 לא עובדות כמו שצריך עם קידודים כפולים ומה קורה עם תכנות שמשתמשות
אופס, בדיקה חוזרת גילתה שהמידע שנמסר לי היה בלתי מבוסס. טעות, סליחה...
עירא, לא הבנתי באיזה הקשר נתת את ההסבר?
הוסף תגובה:
חובה לאפשר JavaScript כדי להגיב.