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 בסן-דיאגו שנערך ב-29-25 בינואר 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 מבינת ערך המספר).
הקידוד ביוניקוד פועל לפי מספר עקרונות פשוטים:
- התווים U+0000 עד U+007F (שהם תווי הASCII שכזכור ערכיהם נעים בין 0 ל-127) מומרים פשוט למספרים 0x00 עד 0x7F בהתאמה ומקודדים לבית אחד. מספרים אלו קטנים יחסית ומספיק 7 סיביות כדי לייצגם ולכן בכל בית בו מקודד אחד מהתווים הנ"ל, יהיה הסיבית המשמעותי ביותר כבוי (ערכו 0). משום שכל תווי ה-ASCII מקודדים ביוניקוד באמצעות בית אחד, אנו מקבלים שטקסט הכתוב המקודד ב-ASCII ואותו הטקסט המקודד בUTF-8 מקודדים בעצם בדיוק באותה הצורה. דבר זה נותן לנו תאימות לאחור ל-ASCII.
- תווים מעבר ל-U+007F יקודדו באמצעות מספר בתים (בפועל, שניים עד ארבעה בתים). הסיבית המשמעותי ביותר בכל אחד מהבתים הללו יהיה דלוק (ערכו יהיה 1). הבחירה בכמה בתים להשתמש בתלויה בערך המספר הייצוגי, מספרים בתווך 0x0007FF–0x000080 יקודדו באמצעות שני בתים, מספרים בטווח 0x00FFFF–0x000800 יקודד באמצעות שלושה בתים ומספרים גדולים יותר (מספרים בטווח 0x1FFFFF–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 (שהוא הקידוד המכיל את האותיות העבריות במשפחה).
תוספת הסיביות בתחילת כל רצף בתים המייצגים מותירה פחות סיביות לשם ייצוג המספר ולכן דורשת לעיתים בית נוסף. כבר ראינו קודם שהיתרונות בשימוש בסיביות אלה רבים ובהחלט מצדיקים אותו. למרות שנראה ש-UTF-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 מדובר בעלייה של 33⅓% בתפוסת הזכרון. עבור 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, אך רק שני בתים ב-UTF16. לכן, קידודו של טקסט הכתוב בסינית, יפנית או קוריאנית, ב-UTF-16 יצרוך פחות זכרון מאשר אם נקודד את אותו הטקסט ב-UTF16. קיימות עוד מספר קבוצות תווים פחות מוכרות שהדבר נכון גם לגביהן.
[עריכה] שימושים במערכות קיימות
UTF-8 הינו קידוד ברירת המחדל במערכות יוניקס ולינוקס.
שפת התכנות Java תומכת בקריאה וכתיבה של מחרוזות המקודדות ב-UTF-8, דרך המחלקות InputStreamReader ו-InputStreamWriter. מאידך, למטרות של סיריאליזציה של אובייקטים, שימוש ב-Jave 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.

