UTF-8
UTF-8 (ראשי תיבות של 8-bit Unicode Transformation Format או 8-bit UCS Transformation Format) הוא קידוד תווים באורך משתנה ליוניקוד, שנוצר על ידי רוב פייק וקן תומפסון. ניתן לקודד בו כל תו המצוי בתקן יוניקוד על ידי שימוש באחד עד ארבעה בתים, תלוי בתו. הקידוד ב-UTF-8 מעניק את כל יתרונות השימוש בקידוד ליוניקוד ומוסיף עליהם, בין היתר, גם חיסכון בזיכרון, עמידות בפני איבוד או השחתת בתים ותאימות לאחור ל-ASCII. ה-IETF מעדיף בבירור את UTF-8 ומחייב כל פרוטוקול אינטרנט לתמוך בו, וכן קונסורציום הדואר האלקטרוני, ה-IMC, ממליץ שכל תוכנת דואר אלקטרוני תוכל להציג וליצור דואר באמצעות UTF-8.
רקע
[עריכת קוד מקור | עריכה]שיטת הקידוד המוכרת כ-UTF-8 הומצאה על ידי קן תומפסון בערב 2 בספטמבר 1992. תומפסון כתב את רעיונו על מצעית בעת ששהה במזנון מהיר בניו ג'רזי עם רוב פייק. ביום שלאחר מכן, פייק ותומפסון מימשו את רעיונם והפכו את מערכת ההפעלה Plan9 של מעבדות בל למערכת ההפעלה הראשונה בעולם המשתמשת בקידוד. UTF-8 הוצג רשמית לראשונה בכנס USENIX בסן דייגו שנערך ב-25 - 29 בינואר 1993.
מאז פורסמו מספר הגדרות ל-UTF-8, בהן:
- ISO/IEC 10646-1:1993 Amendment 2 / Annex R (1996)
- The Unicode Standard, Version 2.0, Appendix A (1996)
- RFC 2044 (1996)
- RFC 2279 (1998)
- The Unicode Standard, Version 3.0, §2.3 (2000) plus Corrigendum #1: UTF-8 Shortest Form (2000)
- Unicode Standard Annex #27: Unicode 3.1 (2001)
הגדרות אלו מיושנות ואינן בשימוש יותר. במקומן קיימות ההגדרות הבאות:
- RFC 3629 / STD 63 (2003) הקובע את UTF-8 כאלמנט סטנדרטי בפרוטוקולי אינטרנט
- The Unicode Standard, Version 4.0, §3.9–§3.10 (2003)
- ISO/IEC 10646-1:2000 Annex D (2000)
שלוש ההגדרות מסכימות על מנגנון הקידוד, וההבדלים ביניהן נסובים בעיקר סביב נושאים כגון טווח המספרים המייצגים המותרים לשימוש ביוניקוד וכיצד יש לטפל בקלט שגוי.
ההבדל בין יוניקוד ל-UTF-8
[עריכת קוד מקור | עריכה]פרויקט יוניקוד החל כחלק ממאמץ ליצור מערכת תווים אחידה בינלאומית (באנגלית: UCS - Unified Character Set). בפשטנות גדולה ניתן לומר שיוניקוד הוא טבלה גדולה של תווים הממפה כל תו למספר המייצג אותו. יוניקוד אינו מפרט כיצד יש לייצג מספר זה במחשב. דרושה שיטה שבאמצעותה ניקח את המספר המייצג את התו ונשמור אותו במחשב בתור בית (או רצף של בתים). לשיטה זו אנו קוראים "קידוד תווים".
UTF-8 הוא קידוד תווים, הוא מורה לנו כיצד יש לקחת את המספר הייצוגי ולשמור אותו במחשב (וכיצד יש לקרוא מספרים השמורים במחשב ולהמירם למספרים ייצוגיים שלאחר מכן נוכל להמיר לתווים). אם ברשותנו טקסט ואנו מעוניינים להכניסו למחשב באמצעות קידוד יוניקוד, תחילה נחליף כל תו בטקסט למספרו הייצוגי לפי טבלת יוניקוד. כעת ברשותנו רצף של מספרים, באמצעות UTF-8 נשמור את המספרים הללו במחשב. UTF-8 מבוסס על יוניקוד בכך ששיטת השמירה שלו מבוססת על המספרים הייצוגיים שנקבעו ביוניקוד.
בעוד שקיים מיפוי אחד של תווים למספרים ייצוגיים בשם יוניקוד, קיימות דרכים רבות לשמור מספרים אלה במחשב, קרי, קיימים קידודי תווים רבים ליוניקוד. בהם UCS-2 ו-UCS-4 הפשוטים המורים לשמור את המספר באמצעות 2 או 4 בתים בהתאמה (קידודים אלה אינם בשימוש, שני בתים אינם מספיקים יותר לכל תווי יוניקוד ו-UCS-4 הפך ל-UTF-32) וכן גם קידודים מתוחכמים יותר כמו UTF-8 ו-UTF-16.
תיאור הקידוד
[עריכת קוד מקור | עריכה]הקידוד ב-UTF-8 הוא תלוי תו, או נכון יותר תלוי במספר המייצג את התו בתקן היוניקוד. לכל תו ברפרטואר התווים של יוניקוד יש מספר המייצג אותו. קיימים תווים שמסיבות של תאימות או סיבות אחרות חוזרים מספר פעמים ברפטואר אך לכל אחד מהם מספר שונה. כל מספר מייצג תו אחד בלבד ולכן ניתן, ואולי אף עדיף, לדבר על התווים דרך מספריהם הייצוגיים ולא דרך שמותיהם, כדי למנוע בלבול. נהוג לרשום את המספרים המייצגים את התווים בצורה הקסדצימלית בשל קלות ההמרה מצורה זו לצורה הבינארית שהיא הצורה בה נכתבים המספרים למחשב. נהוג גם, כאשר מדברים על תווים או המספרים המייצגים אותם, לנקוט בסימונים מיוחדים המבהירים שמדובר ביוניקוד:
- אם התו (קרי, המספר המייצג אותו) נמצא בטווח שבין 0x00000000 ל-0x0000FFFD, נאמר שהתו נמצא במשטח הרב-לשוני הבסיסי ונכתוב רק את ארבע הספרות האחרונות של המספר עם הקידומת U+. לדוגמה, המספר המייצג את האות אל"ף ביוניקוד הוא 0x000005D0, ולכן נתייחס אליה בתור U+05D0. המשטח הרב-לשוני הבסיסי טומן בחובו את מרבית התווים בהם השתמשו ומשתמשים גם היום.
- אם המספר גדול מ-0x0000FFFD, נאמר שהוא מחוץ למשטח הרב-לשוני הבסיסי ונכתוב את כל ספרות המספר המייצג עם הקידומת U-. משטחים אלה מכילים בתוכם תווים רבים שהשימוש בהם מועט לרוב, לדוגמה: תווים מוזיקליים, סימנים מתמטיים ואותיותיהן של שפות היסטוריות.
כאשר נאמר U+05D0 נתכוון בעצם לאות אל"ף (א') כפי שהיא מוגדרת בתקן היוניקוד. כאשר נאמר 0x05D0 נתכוון בעצם למספר מסוים (שבמקרה מייצג ביוניקוד את האות אל"ף). משום שמדובר אך ורק במספר, נוכל לכותבו בכל צורה שנחפוץ כל עוד ערכו זהה. נוכל לרשום למשל גם 0x000005D0, או 1488, שהוא ערכו של המספר בבסיס העשרוני (ראו בסיס ספירה) – ממש כפי שאין הבדל אם נרשום 2 או 2.0 או 002 מבחינת ערך המספר.
הקידוד ב-UTF-8 פועל לפי מספר עקרונות פשוטים:
- התווים U+0000 עד U+007F (שהם תווי ASCII שכזכור ערכיהם נעים בין 0 ל-127) מומרים פשוט למספרים 0x00 עד 0x7F בהתאמה ומקודדים לבית אחד. מספרים אלו קטנים, ודי ב-7 סיביות כדי לייצגם ולכן בכל בית בו מקודד אחד מהתווים הנ"ל, תהיה הסיבית המשמעותית ביותר כבויה (ערכה 0). משום שכל תווי ה-ASCII מקודדים ב-UTF-8 באמצעות בית אחד, אנו מקבלים שטקסט המקודד ב-ASCII, מקודד בדיוק באותה הצורה ב-UTF-8. דבר זה נותן לנו תאימות לאחור ל-ASCII.
- תווים מעבר ל-U+007F יקודדו באמצעות מספר בתים (בפועל, שניים עד ארבעה בתים). הסיבית המשמעותית ביותר בכל אחד מהבתים הללו תהיה דלוקה (ערכה יהיה 1). הבחירה בכמה בתים להשתמש תלויה בערך המספר הייצוגי, מספרים בטווח 0x0007FF–0x000080 יקודדו באמצעות שני בתים, מספרים בטווח 0x00FFFF–0x000800 יקודדו באמצעות שלושה בתים ומספרים גדולים יותר (מספרים בטווח 0x10FFFF–0x010000) יקודדו באמצעות ארבעה בתים. הקידוד תוך שימוש ברצף של בתים כדי לייצג תו אחד ייעשה כך:
- הבית הראשון יתחיל ברצף של 1 כמספר הבתים הדרושים ואחריהם יבוא 0. לדוגמה, אם ברצוננו לקודד את המספר באמצעות שלושה בתים, הבית הראשון יחל ברצף 1110 ובכלליות יהיה מהצורה 1110xxxx (בהמשך יוסבר מהם הערכים הנוספים).
- כל בית נוסף (קרי, הבית השני, השלישי והרביעי ברצף, אם קיימים) יחל ברצף 10 ובכלליות ייראה כך: 10xxxxxx.
- כעת ברשותנו מספר בתים, בהם חלק מהסיביות כבר תפוסות בשל הכללים הקודמים וכן סיביות חופשיות (אותן סימנו ב-x בסעיפים הקודמים). נמיר את המספר לייצוג בינארי עם מספר סיביות כמספר הסיביות הפנויות ונמלא אותן במספר שקיבלנו.
הטבלה הבאה מסכמת את עקרונות הקידוד:
טווח מספרים הקסדצימלי |
ערך המספר הייצוגי בינארי |
UTF-8 בינארי |
הערות |
---|---|---|---|
000000–00007F 128 ערכים |
0zzzzzzz | 0zzzzzzz | ערכים המקבילים ל-ASCII, הסיבית המשמעותית ביותר כבויה. |
שבעה z | שבעה z | ||
000080–0007FF 1920 ערכים |
00000yyy yyzzzzzz | 110yyyyy 10zzzzzz | הבית הראשון מתחיל ברצף 110, שאר הבתים מתחילים ברצף 10. |
שלושה y; שני y, שישה z | חמישה y; שישה z | ||
000800–00FFFF 63488 ערכים |
xxxxyyyy yyzzzzzz | 1110xxxx 10yyyyyy 10zzzzzz | הבית הראשון מתחיל ברצף 1110, שאר הבתים מתחילים ברצף 10. |
ארבעה x,ארבעה y; שני y,שישה z | ארבעה x; שישה y; שישה z | ||
010000–10FFFF 1048576 ערכים |
000wwwxx xxxxyyyy yyzzzzzz | 11110www 10xxxxxx 10yyyyyy 10zzzzzz | הבית הראשון מתחיל ברצף 11110, שאר הבתים מתחילים ברצף 10. |
שלושה w, שני x; ארבעה x, ארבעה y; שני y, שישה z | שלושה w; שישה x; שישה y; שישה z |
לשם המחשה נקודד את האות העברית אל"ף (א'), סימנה ביוניקוד U+05D0, באמצעות UTF-8:
- האות נמצאת בטווח האותיות שבין U+0080 ל-U+07FF ולכן תקודד על ידי שני בתים.
- הבית הראשון יהיה מהצורה 110xxxxx (שני 1 משום שיש שני בתים, ואחריהם 0 כנדרש) והבית השני יהיה מהצורה 10xxxxxx (משום שהוא בית נוסף הוא מתחיל ברצף 10).
- המספר המייצג את אל"ף הוא 0x05D0. אנו רואים שברשותנו 11 סיביות פנויות (נספור את סך ה-x בשני הבתים) ולכן נרשום את המספר בייצוג בינארי תוך שימוש ב-11 סיביות, נקבל: 101-1101-0000 (המקפים הם לשם בהירות הקריאה בלבד ואינם נדרשים).
- נחדיר את המספר הבינארי לתוך הסיביות הפנויות. נצטרך לחלק את המספר לחמש סיביות ושש סיביות בצורה 10111-010000 (זהו אותו מספר, פשוט שינינו את החלוקה) ונקבל: 10010000 11010111.
- התוצאה הסופית היא שני הבתים לעיל, אותם ניתן לבטא בנוחיות רבה בצורה ההקסדצימלית 0xD7 0x90, וכך מקודדת ב-UTF-8 האות אל"ף.
במקור, UTF-8 תמך גם בקידודו של תו באמצעות חמישה ואף שישה בתים. קידוד באמצעות שישה בתים מעניק מרחב של 31 סיביות חופשיות וכך ניתן לכסות את כל האזור 0x7FFFFFFF-0x00000000. בפועל, הוחלט שכל המספרים הייצוגיים של יוניקוד יישארו בטווח 0x10FFFF-0x000000, הדורש רק 21 סיביות, ולכן UTF-8 הוגבל על ידי RFC 3629 לייצוג באמצעות עד ארבעה בתים (המעניקים 21 סיביות פנויות).
רציונל הקידוד ונגזרותיו
[עריכת קוד מקור | עריכה]הקידוד באמצעות מספר משתנה של בתים ורצפי הסיביות בתחילת כל בית, אם הוא חלק ממספר בתים המייצגים תו בודד, הופך את UTF-8 לאמין, עמיד בפני תקלות שונות וחסכוני.
חוקי הקידוד דורשים שיתקיימו התנאים הבאים:
- הסיבית המשמעותית ביותר בבית המייצג תו אחד, היא תמיד 0.
- הסיבית המשמעותית ביותר בבית שהוא חלק ממספר בתים המייצגים תו אחד, היא תמיד 1.
- כל בית הפותח רצף של מספר בתים המגדירים תו בודד, מתחיל ברצף של 1 כמספר הבתים שמייצגים את התו ואחריהם 0. אם התו ייוצג על ידי שני בתים, יתחיל הבית ב-110, אם ייוצג התו על ידי שלושה בתים, יתחיל הבית ב-1110 ואם ייוצג התו על ידי ארבעה בתים, יתחיל הבית ב-11110.
- כל בית שהוא בית נוסף (לא הראשון) ברצף של בתים המייצגים תו בודד מתחיל ב-10.
כתוצאה מכך, לא ניתן להתבלבל בין בית המייצג תו ASCII (בית בודד המייצג תו בודד) לבין בית שהוא חלק ממספר בתים המייצגים יחדיו תו אחד (זאת משום שבמקרה הראשון הסיבית המשמעותית ביותר היא 0 ובמקרה השני תהיה הסיבית המשמעותית ביותר 1). באופן כללי יותר, החוקים הנ"ל מבטיחים שלא יכול להתקיים מצב בו רצף בתים המייצג תו מסוים יוכל בתוך רצף בתים גדול יותר המייצג תו אחר. המשמעות של העניין היא שגם אם נסתכל על חלקים מהקידוד, לעולם לא נוכל לפרש אותם כתווים אחרים פרט לתווים שהם יועדו להיות מלכתחילה. לא ייתכן שמתוך ארבעה בתים המייצגים תו, אם נסתכל רק על שניים נוכל לחשוב בטעות שמדובר בתו אחר. כעת ניתן להשתמש באלגוריתמי חיפוש המסתמכים על הבתים מהם מורכב הטקסט, כדי לחפש מילים או ביטויים בטקסט בצורה יעילה. נוסף על כך, אם בית אחד הושחת או אבד בנסיבות כלשהן (למשל, במהלך העברה ברשת), רק תו אחד ייפגם, שכן ברגע שנגיע לתו הבא נבחין שמתחיל רצף חדש של בתים ולכן תו חדש. בשיטות אחרות, איבוד בית אחד יכול להוביל להשחתה של הטקסט כולו.
מבחינה סטטיסטית, אם רצף של בתים עבר את בדיקות ה-UTF-8 והתברר כרצף שעלול לייצג תו או מספר תווים ב-UTF-8, נראה שאכן מדובר בקידוד UTF-8. תכונה זו עוזרת בכך שהיא מונעת מצב בו תוכנה תטעה לחשוב שטקסט מסוים קודד ב-UTF-8, תנסה להציגו כך ויתקבל טקסט לא קריא. תופעה זו שכיחה מאוד בקרב קידודי ISO 8859, שכן לא ניתן להבדיל בין קידוד אחד במשפחה לשני. גולשי אינטרנט ישראלים רבים נתקלו בתופעה של דף המציג אותיות מערב-אירופאיות במקום האותיות העבריות משום שהדפדפן טעה לחשוב שהדף מקודד ב-ISO-8859-1 במקום ISO-8859-8 (שהוא הקידוד המכיל את האותיות העבריות במשפחה).
תוספת הסיביות בתחילת כל רצף בתים המייצגים מותירה פחות סיביות לשם ייצוג המספר ולכן דורשת לעיתים בית נוסף. 128 התווים הראשונים צורכים רק בית אחד, תווים אלו הם תווי ASCII ואלו הם התווים בהם נעשה השימוש הנרחב ביותר בכל הטקסטים בעולם (כל שיטות הקידוד האחרות ליוניקוד יקודדו תווים אלה ביותר מבית אחד). 1920 התווים הבאים צורכים שני בתים לקידוד, אלה כוללים את תווי האלפבית לטיני עם הסימנים הדיאקריטיים, האלפבית היווני, האלפבית הקירילי, הקופטית, הארמנית, האלפבית העברי והאלפבית הערבי. שאר התווים במשטח הרב-לשוני הבסיסי (65,536 התווים הראשונים) משתמשים בשלושה בתים, ושאר התווים הנתמכים ביוניקוד משתמשים בארבעה בתים לקידוד. ככל ש-UTF-8 צורך יותר בתים לקידוד התו, אנו שמים לב שמדובר בתו ששכיחותו בטקסטים באופן כללי נמוכה יותר. התווים שאינם במשטח הרב-לשוני הבסיסי הם תווים שכמעט ולא, או כלל לא, היו קידודים שתמכו בהם קודם לכן והשימוש בהם הוא מזערי (מדובר בתווים מוזיקליים, סימנים מתמטיים ואותיות בשפות היסטוריות). טקסט שנכתב כולו באנגלית יתפוס את אותה כמות המקום בזיכרון כפי שהיה תופס לו קודד בשיטות הקידוד הישנות, וטקסט שנכתב בשפה שכיחה, שאינה אנגלית, יתפוס עד פי שניים מהמקום שהיה תופס לו קודד בשיטות הישנות. טקסט המשלב בתוכו מספר שפות שכיחות יתפוס כמות מקום זהה לטקסט הכתוב רק בשפה אחת שכיחה, אך לא ניתן להשוות לכמות הזיכרון שהיה תופס לו היה נכתב בשיטות הקידוד הישנות, משום שאותן שיטות כלל לא אפשרו הצגה של מספר שפות (שאחת מהן אינה אנגלית) באותו הטקסט.
בעיית הצורות הארוכות
[עריכת קוד מקור | עריכה]בשעת קידוד, מקודד ה-UTF-8 ממיר את התו המבוקש למספר לפי טבלת יוניקוד ואז, תלוי בערכו של אותו מספר, ממיר אותו למספר הבתים הנדרש. מפענח UTF-8 הקורא קטע שקודד קודם לכן קורא את הבתים, על-פי הסימונים מחליט כמה בתים עליו לקרוא לשם קבלת תו בודד, מוציא מבתים אלה את המספר ואז הולך לטבלת יוניקוד ורושם את התו המתאים.
אם ברצוננו לקודד את התו U+000A שהוא תו הזנת השורה, עלינו רק ללכת לפי אלגוריתם הקידוד ומיד נגלה שכל שעלינו לעשות הוא לכתוב בבית אחד את המספר 0x000A. אם מפענח UTF-8 יתקל בבית 0x000A, מיד ידע שמדובר בבית בודד המייצג תו בודד (הסיבית המשמעותית ביותר היא 0) ואז יבדוק מהו התו U+000A. נשאלת השאלה מה יקרה אם מפענח ה-UTF-8 יתקל באחד מרצפי הבתים הבאים:
- 0xC0 0x8A
- 0xE0 0x80 0x8A
- 0xF0 0x80 0x80 0x8A
נבחן את האפשרות הראשונה, 0xC0 0x8A, ההמרה הבינארית היא 10001010 11000000. הסיביות המודגשות הן הסיביות המכילות את המספר בהצגתו הבינארית, המספר הוא 0x000A ומייצג את התו U+000A ביוניקוד. הליך פיענוח דומה של שתי האפשרויות האחרות יביא לתוצאה זהה. נראה אם כן שניתן להציג תווים מסוימים ביותר מדרך אחת. לכאורה ניתן להציג תווים בדרך המומלצת (והחסכונית) או להציג אותם על ידי שימוש ביותר בתים.
הצגתו של תו על ידי יותר בתים ממה שצריך מכונה הצגתו ב"צורה הארוכה" (Overlong Form). בעבר נעשה שימוש זדוני באפשרות של הצגת תווים בצורתם הארוכה לשם עקיפת מנגנוני אבטחה שונים. תוכנות האבטחה לא פענחו את הקוד לפני הבדיקה אלא ביצעו את הבדיקה ישירות על הבתים המקודדים תוך כדי הסתמכות על אלגוריתם הקידוד המוכר ועל העובדה שכל תו מקודד בצורה הקצרה ביותר. בשיטה זו נעקפו מנגנוני הביטחון של שרתי IIS של מיקרוסופט. השימוש בצורה הארוכה של תווים מנע מתוכנת האבטחה למצוא אותם. עקב כך השימוש בצורות ארוכות נאסר על ידי סטנדרט RFC 3629.
טיפול בקלט לא תקין
[עריכת קוד מקור | עריכה]הסטנדרטים המגדירים את UTF-8 אינם מגדירים תגובה אחידה לאופן בו צריך מפענח UTF-8 לנהוג אם הוא נתקל בקלט לא תקין (למשל בית המצהיר על התחלה של רצף של ארבעה בתים ולאחריו בית אחד בלבד, או למשל צורה ארוכה עליה נכתב קודם לכן). קיימות מספר אפשרויות לפיהן יכול המפענח לנהוג:
- להחליף את התו המושחת בתו אחר כגון '�', '?', או תו אחר שמייצג תו לא תקין.
- להתעלם מהבתים הלא תקינים.
- לפענח את הבתים לפי קידוד תווים אחר (לעיתים קרובות ISO-8859-1).
- לא לשים לב ולהמשיך לפענח כאילו שמדובר בקוד תקני של UTF-8 (אפשרי בייחוד כשמדובר בצורות ארוכות).
- להפסיק לפענח ולדווח על שגיאה (אולי עם אפשרות לתת למשתמש להמשיך בקידוד בכל זאת).
קיימת אפשרות גם שהמפענח ינהג בצורה שונה כאשר הוא נתקל בסוגי קלטים לא תקינים שונים.
סטנדרט RFC 3629 דורש רק שמפענחי UTF-8 לא יפענחו "צורות ארוכות". סטנדרט היוניקוד דורש ש"כל מפענח יוניקוד יתייחס לכל קטע קוד שגוי כשגיאה. זה יבטיח שהוא לא יפרש או יפלוט רצף קוד שגוי".
הטיפול בקלטים לא תקינים חשוב מטעמי אבטחת מידע, כפי שראינו לגבי צורות ארוכות. ישנן שתי אפשרויות לטפל בקלט לא תקין ולשמור על ביטחון המידע: האפשרות הראשונה היא לפענח את קידוד UTF-8 לפני שעושים בדיקות כלשהן (ואז לבצע את הבדיקות על הטקסט המפוענח). האפשרות השנייה היא להשתמש במפענח שבמקרה של קלט לא תקין מוציא הודעת שגיאה או טקסט שהאפליקציה מחשיבה כבלתי מזיק. אפשרות נוספת היא כלל לא לפענח את קידוד UTF-8, אך זה מחייב את התוכנה אליה מעבירים את הטקסט המקודד לטפל בעצמה בקלט לא תקין.
חשיבות נוספת לזיהוי קלטים לא תקינים ודרך הטיפול בהם היא לשם טיפול במקרים של איבוד או השחתה של בתים. אם בית הושחת או אבד והמערכת מזהה מספר לא נכון של בתים המייצגים תו, ביכולתה למזער את הנזק לפגיעה בתו זה בלבד. התעלמות מהבעיה והנחה שכל הבתים מופיעים ללא בדיקה שאכן כל אחד מהבתים העוקבים תואם את הדגם, עלולה במקרה של בית אחד חסר להביא לקריסת הפענוח כולו והשחתה של הטקסט (בניגוד להשחתה של תו אחד).
יתרונות וחסרונות
[עריכת קוד מקור | עריכה]כללי
[עריכת קוד מקור | עריכה]יתרונות
[עריכת קוד מקור | עריכה]- UTF-8 מכיל בתוכו את ASCII (קידוד האותיות הזהות נעשה בצורה דומה בשני הקידודים). משום שכל טקסט שקודד ב-ASCII הוא גם טקסט תקני לפי UTF-8, אין צורך בשום פעולת המרה לטקסטים קיימים שכבר קודדו ב-ASCII. ניתן להשתמש ב-UTF-8 גם בתוכנות שתוכננו עבור קידודי ASCII מורחבים מסורתיים לאחר ביצוע מספר שינויים קלים ולעיתים אף ללא שינוי כלל.
- מיון מחרוזות שקודדו ב-UTF-8 על ידי אלגוריתמים המסתמכים על מיון לפי בתים, יביא אותן התוצאות כאילו מוינו המחרוזות לפי המסרים המייצגים של התווים ביוניקוד (לעובדה זו השלכות מעטות בלבד שכן לא סביר שמיון זה ייצג צורך או מיון מקובל כלשהו).
- UTF-8 ו-UTF-16 הם המיונים הסטנדרטים עבור מסמכי XML. שימוש בכל קידוד אחר מחייב ציון מיוחד על ידי אמצעי חיצוני או דרך הגדרת טקסט.
- ניתן להפעיל כל אלגוריתם חיפוש המבוסס על חיפוש לפי בתים על טקסטים שקודד ב-UTF-8 (כל עוד מוודאים שהקלט תקין לפני כן). יש לשים לב לבנייה תקינה של ביטויים רגולריים ומבנים אחרים הסופרים תווים.
- ניתן לזהות בצורה יחסית פשוטה ואמינה טקסטים שקודדו ב-UTF-8. הסיכוי שתו או מחרוזת שקודדו בקידוד אחר יראו כקידוד UTF-8 תקין נמוכים מאוד וככל שהטקסט ארוך יותר הם הולכים ופוחתים אף יותר. הבתים 0xC0, 0xC1, 0xFF-0xF5, למשל, לעולם לא יופיעו בטקסט שקודד ב-UTF-8. כדי להשיג תוצאות אמינות יותר, ניתן להשתמש בביטויים רגולריים בשביל לבדוק הימצאותם של צורות ארוכות או ערכים ממלאי מקום (surrogate values). למידע נוסף על העניין ניתן לפנות למקבץ השאלות הנפוצות: W3 FAQ: Multilingual Forms for a Perl regular expression to validate a UTF-8 string.
חסרונות
[עריכת קוד מקור | עריכה]- מפענח UTF-8 שנכתב בצורה לקויה ואינו תואם את הגרסאות העדכניות של הסטנדרטים, עלול לקבל מספר גרסאות שונות של ייצוגי תווים הנראים כמו ייצוגים חוקיים ב-UTF-8 (צורות ארוכות למשל) ולפענח אותם לתווי יוניקוד שהם כביכול מייצגים. בצורה זו מידע יכול לדלוף מעבר להליכי בדיקה ואימות שונים שתוכננו לבדוק מידע בייצוגו בשמונה סיביות.
בהשוואה לקידודים אחרים
[עריכת קוד מקור | עריכה]בהשוואה לקידוד לגסי
[עריכת קוד מקור | עריכה]יתרונות
[עריכת קוד מקור | עריכה]- UTF-8 יכול לקודד כל תו יוניקוד. ברוב המקרים, ניתן להמיר (אם כי לא לגמרי) כל טקסט שקודד בקידוד מיושן לקידוד יוניקוד ובחזרה מבלי לאבד מידע. משום ש-UTF-8 הוא קידוד יוניקוד, הדבר תקף גם לגביו.
- ניתן לזהות בקלות היכן מתחיל ומסתיים כל תו מכל מקום ברצף הבתים (בשל הסימונים המורים שבית מסוים מתחיל רצף של בתים המייצגים תו בודד, כמה בתים מעורבים ברצף זה והסימון על כל אחד מהבתים הנוספים שהוא מהווה חלק מהרצף). כתוצאה מכך, אם נתחיל לסרוק את הטקסט המקודד מאמצע רצף בתים המייצג תו אחד, רק המידע על התו הבודד שהתחלנו את הסריקה מאמצע ייצוגו יאבד, וניתן להמשיך בפענוח תקין החל מהתו הבא. יתרון זה בא לידי שימוש אם בית אחד או מספר בתים מושחתים או אובדים ואז רק המידע על התווים שאותם בתים הרכיבו אובד וניתן לפענח בצורה נכונה את כל שאר הטקסט. סינכרון מחדש של קידוד מיושן שחלק מבתיו הושחתו או אבדו הוא מטלה קשה בהרבה.
- לא ייתכן מצב בו רצף בתים המייצג תו בודד יוכל ברצף בתים ארוך יותר המייצג תו בודד אחר (או אותו תו בודד לצורך העניין), תופעה שכן קיימת בקידודי תווים אחרים בעלי אורך משתנה כמו Shift-JIS. באופן פרטי, תווי ASCII המקודדים, כזכור, כבית בודד, אינם מופיעים בקידוד UTF-8 בשום צורה אחרת (כחלק מרצף תווים ארוך יותר למשל), אלא רק במקומות בהם נדרש קידוד של התו המדובר. עובדה זו מאפשרת תאימות עם מערכות קבצים או תוכנות אחרות (למשל פונקציית ה-printf() של שפת C) המבצעות ניתוחים לפי ערכי ASCII השקופים לערכים אחרים.
- הבית הראשון ברצף בתים המייצג תו בודד מספיק כדי לקבוע מהו אורך הרצף. הדבר מאפשר להוציא תת-מחרוזת מן המחרוזת מבלי לבצע ניתוח מתוחכם של הטקסט. אין הדבר נכון לרוב בקידודים מיושנים התומכים באורך משתנה.
- UTF-8 אינו צורך שימוש בפעולות מתמטיות מסובכות כגון כפל או חילוק ומסתפק בפעולות על סיביות בלבד שהן פעולות מהירות בהרבה.
חסרונות
[עריכת קוד מקור | עריכה]- לרוב, הקידוד ב-UTF-8 יביא לצריכת זיכרון גדולה יותר מאשר קידוד אותו הטקסט באחד מהקידודים המיושנים, אלא אם מדובר בטקסט עם אלפבית לטיני ללא ניקוד (טקסט בו אותיות אנגליות בלבד). בקידודים מיושנים, מרבית הכתבים מבוססי האלפבית קודדו על ידי שימוש בבית אחד לאות אחת בעוד שב-UTF-8 כל אות לוקחת לרוב שני בתים. לוגוגרמות לרוב דרשו שני בתים לתו בקידוד מיושן אך דורשים שלושה בתים לתו בקידוד UTF-8.
- קידודים מיושנים מקודדים תווים שאינם לוגוגרמות על ידי שימוש בבית אחד לתו ובכך הופכים חיתוך וחיבור של מחרוזות לפעולה פשוטה.
בהשוואה ל-UTF-7
[עריכת קוד מקור | עריכה]יתרונות
[עריכת קוד מקור | עריכה]- UTF-8 משתמש במספר קטן משמעותית של בתים לקידוד תווים שאינם תווי ASCII.
- UTF-8 מקודד את התו "+" כעצמו בעוד ש-UTF-7 מקודד אותו כ"-+".
חסרונות
[עריכת קוד מקור | עריכה]- UTF-8 דורש מהמערכת להיות מערכת המבוססת על שמונה סיביות. במקרה של דואר אלקטרוני, משמעות הדבר היא שיש צורך בקידוד נוסף בשיטת Quoted Printable או base64. שלב זה של קידוד נוסף מוביל לגידול משמעותי בתפוסת הזיכרון שדורש הקידוד. עבור base64 מדובר בעלייה של שליש בצריכת הזיכרון. עבור Quoted Printable הגידול תלוי במה חלקם היחסי של תווי ASCII בטקסט. עבור טקסט שנכתב בצרפתית, תתרחש עלייה של 14% בתפוסת הזיכרון, אך טקסטים באותיות שאינן אותיות רומיות ואינם מכילים תווי ASCII יובילו לעלייה של 200% בצריכת הזיכרון. קידודו של כל טקסט המכיל יותר תווים שאינם תווי ASCII מאשר סימני "+" ב-UTF-7 יתפוס פחות זיכרון מאשר קידוד ב-UTF-8 משולב עם Quoted Printable, כאשר ההפרש בין השניים גדל ביחס ישר לאחוז היחסי של תווים שאינם תווי ASCII בטקסט (אפילו בשפות בהן מרבית האותיות הן אותיות ASCII, כמו צרפתית, קידוד ב-UTF-7 יהיה קטן ב-4% מאשר קידוד ב-UTF-8 עם Quoted Printable). בטקסטים רבים, הקידוד ב-UTF-7 יביס גם את הקידוד ב-UTF-8 משולב עם base64.
בהשוואה ל-UTF-16
[עריכת קוד מקור | עריכה]יתרונות
[עריכת קוד מקור | עריכה]- לא קיימים בתים שערכם 0 (תו ה-NUL בASCII) אלא אם מופיע התו U+0000 שהוא תו ה-NUL ביוניקוד. בזכות זה, פונקציות ספריית legacy של שפת C המטפלות במחרוזות (כמו strcpy() למשל) המשתמשות בתו ה-NUL כדי לציין סוף מחרוזת, לא יחתכו מחרוזות בשוגג לפני סיומן.
- משום שתווי ASCII מקודדים באמצעות בית אחד, קידודם של טקסטים המורכבים בעיקר מאותיות לטיניות ללא ניקוד ב-UTF-8, יתפוס בערך חצי מהזיכרון שיתפוס קידוד אותם הטקסטים ב-UTF-16. קידודם של טקסטים רבים אחרים, שתוויהם אינם תווי ASCII, ב-UTF-8 יהיה קטן במעט מקידוד אותו הטקסט ב-UTF-16 בשל נוכחות רווחים.
- רוב התוכנות הקיימות היום, ובהן גם מערכות ההפעלה, לא נכתבו תוך כדי מחשבה על יוניקוד. שימוש ב-UTF-16 על תוכנות אלו ייצור בעיות תאימות קשות ביותר שכן UTF-16 אינו מכיל בתוכו את ASCII, כמו UTF-8. UTF-8 מאפשר לתוכנות להמשיך להתייחס לתווי ASCII בדיוק כפי שהתייחסו אליהם קודם ולשנות התנהגותם רק לגבי תווים שאינם ב-ASCII שהם ממילא תלויי מקום.
- ב-UTF-8, תווים שהם מחוץ למרחב הרב-לשוני הבסיסי אינם מקרה יוצא דופן.
- UTF-8 משתמש בבית כיחידה הקטנה ביותר שלו, ואילו UTF-16 משתמש במילה בגודל 16 סיביות, אשר לרוב מיוצגת כזוג בתים. סדרם של בתים אלה בעייתי. קיימים מספר מנגנונים היכולים לטפל בנושא זה (למשל, "סימן סדר הבתים", ה-Byte Order Mask), אך הדבר עדיין מסבך את התוכנה ועיצוב הפרוטוקול.
- אם מספר אי-זוגי של בתים יורד מהתחלתו של טקסט המקודד ב-UTF-16, התוצאה תהיה טקסט שאינו תואם את תקן UTF-16 או השחתה מוחלטת של הטקסט. לעומת זאת, ב-UTF-8 הורדה של בית יכולה במקרה הגרוע ביותר להשחית תו אחד ולכן הורדה של n בתים, במקרה הגרוע ביותר תשחית רק n תווים ושאר הטקסט יישאר תקין.
- לעיתים מתייחסים בשגגה ל-UTF-16 כאל קידוד בעל אורך קבוע של שני בתים. יחס זה מוביל לתכנות מקודדים ומפענחים הפועלים על מרבית הטקסטים אך נכשלים עבור תווים שאינם במרחב הרב-לשוני הבסיסי. תיקון התוכנות הוא משימה קשה לרוב ולכן עדיף מראש ליישם תמיכה לכל טווח תווי יוניקוד.
חסרונות
[עריכת קוד מקור | עריכה]- קידודן של לוגוגרמות סיניות, יפניות וקוריאניות (בקיצור באנגלית: CJK - Chinese, Japanese, and Korean) דורש שלושה בתים ב-UTF-8, אך רק שני בתים ב-UTF-16. לכן, קידודו ב-UTF-16 של טקסט הכתוב בסינית, יפנית או קוריאנית, יצרוך פחות זיכרון מאשר אם נקודד את אותו הטקסט ב-UTF-8. קיימות עוד מספר קבוצות תווים פחות מוכרות שהדבר נכון גם לגביהן.
שימושים במערכות קיימות
[עריכת קוד מקור | עריכה]UTF-8 הוא קידוד ברירת המחדל במערכות יוניקס ולינוקס.
שפת התכנות Java תומכת בקריאה וכתיבה של מחרוזות המקודדות ב-UTF-8, דרך המחלקות InputStreamReader ו-OutputStreamWriter. מאידך, למטרות של סיריאליזציה של אובייקטים, שימוש ב-Java Native Interface ולשם הטמעת קבועים בקובצי class, משתמשת Java בגרסה קצת שונה של UTF-8 המכונה UTF-8 משופר (modified UTF-8). ישנם שני הבדלים בין UTF-8 התקני לזה המשופר. הראשון שבהם הוא שתו ה-NUL, שסימונו ביוניקוד U+0000, מקודד באמצעות שני בתים במקום בית אחד בצורה: 10000000 11000000. שינוי זה מבטיח שבעת קידוד המחרוזת לא יוטמעו בה תווי NUL וכך נפתרת בעיית עיבוד המחרוזות בשפות כמו שפת C המשתמשות בתו ה-NUL כסימון לסיום מחרוזת וכל תו NUL מוטמע יביא לחיתוך המחרוזת בטרם עת. ההבדל השני הוא בצורת קידודם של תווים מחוץ למשטח הרב-לשוני הבסיסי. ב-UTF-8 התקני, מקודדים תווים אלה באמצעות ארבעה בתים. ב-UTF-8 המשופר, תווים אלה מומרים קודם כל לצורה של זוגות ממלאי מקום (surrogate pairs), כמו ב-UTF-16 ואז כל אחד מבני הזוג מקודד לבדו והקידודים נכתבים ברצף, כמו ב-CESU-8. הסיבה להבדל זה מעודנת יותר. ב-Java, אורכו של משתנה מסוג תו הוא 16 סיביות. מסיבה זו, חלק מתווי יוניקוד דורשים שימוש בשני משתנים מסוג תו לשם ייצוג הולם. תכונה זו של השפה אינה תואמת את ההתקדמות ביוניקוד וההחלטה לפרוץ את גבולות המרחב הרב-לשוני הבסיסי, אך היא חיונית לשם ביצועים ולשם תאימות לאחור ולכן לא סביר שתשתנה. השימוש ב-UTF-8 משופר מבטיח שניתן יהיה לפענח מחרוזות מקודדות על ידי פיענוח של יחידת קוד של UTF-16 אחת בכל פעם ולא על ידי פיענוח מספר ייצוגי אחד של יוניקוד בכל פעם. השימוש ב-UTF-8 משופר גורם לכך שקידודם של תווים הדורשים ארבעה בתים כאשר משתמשים בצורה התקנית של UTF-8, דורש ב-UTF-8 המשופר שימוש בשישה בתים.
מערכת ההפעלה, Mac OS X, המיועדת למחשבי מקינטוש, מקודדת את שמות הקבצים במערכת הקבצים שלה על ידי שימוש ב-UTF-8 המותאם ליוניקוד מופרד קנונית. לעיתים מתייחסים לגרסה זו של UTF-8 כאל UTF-8-MAC. ביוניקוד, אם ברצוננו לייצג למשל את האות Ä (האות האנגלית הגדולה A עם שתי נקודות מעליה), נוכל להשתמש בתו היוניקוד U+00C4, שהוא בדיוק האות Ä. לחלופין, נוכל גם להשתמש בתו U+0041 שהוא התו A (האות האנגלית הגדולה A) ומיד לאחריו לשים את התו U+0308 שהוא הסימן הדיאקריטי שתי נקודות מעל לאות. בעיקרון תקן היוניקוד קובע חוקים לשילוב סימנים דיאקריטיים. קיימים בתקן תווים מיוחדים הנקראים תווים משלבים (combining characters). כתיבת אות ולאחריה תווים משלבים אמורים להוביל להצגת האות עם הניקוד. מטעמי תאימות לאחור ומסיבות אחרות, חלק מהאותיות עם הניקוד קיימות כתו נפרד ביוניקוד. תווים אלה נקראים תווים מורכבים מראש (precomposed characters) והתקן קובע גם לאילו תווים הם שקולים. יוניקוד מופרד, או מפורק, קנונית הוא יוניקוד שבו נאסר השימוש בתווים מורכבים מראש ולכן חייבים להשתמש בשילוב של אותיות וסימנים דיאקריטיים בכל מקום שבו נחוץ תו עם סימן דיאקריטי. השימוש ביוניקוד מופרד מקל רבות על הליכי מיון אך יוצר בלבול בקרב תוכנות המניחות שתווים מורכבים הם הצורה השכיחה ושילוב של תווים משלבים נעשה רק עבור צירופים מיוחדים. (זוהי דוגמה להגוון ה-NFD לנירמול טקסט). קיים דיון נרחב בנושא במסמך Apple Computer's Q&A 1173.
קישורים חיצוניים
[עריכת קוד מקור | עריכה]- רוב פייק מספר את סיפור יצירתו של UTF-8
- מסמך הגדרתו המקורי של UTF-8 (פורמט pdf) שנכתב עבור plan9 של מעבדות בל
- RFC 3629, סטנדרט ה-UTF-8
- RFC 2277, מדיניות ה-IETF לגבי קידודי תווים ושפות
- שאלות נפוצות לגבי Unicode ו-UTF-8 עבור יוניקס/לינוקס
- אתר המרכז קישורים ומעט חומר לגבי UTF-8
- דפי ניסוי עבור UTF-8: [1], [2], דף הניסוי של ארגון W3
- How-To לגבי UTF-8 בלינוקס
- UTF-8 בדביאן GNU/לינוקס
- שימוש ב-UTF-8 בג'נטו לינוקס
- טבלת קידוד UTF-8 ותווי יוניקוד - קידוד UTF-8 מוצג בפורמטים שונים עם מידע לגבי הקידוד ביוניקוד ו-HTML
- המרה של קידודים ו-HTML