מבט מעמיק על סוג התווים של ג'אווה

גרסת ה- 1.1 של Java מציגה מספר שיעורים להתמודדות עם תווים. מחלקות חדשות אלה יוצרות מופשט להמרה מתפיסה ספציפית של פלטפורמה של ערכי תווים לערכי Unicode . טור זה בוחן את מה שנוסף, ואת המניעים להוספת שיעורי אופי אלה.

הקלד char

אולי סוג הבסיס המנוצל ביותר בשפה C הוא סוג char . Char הסוג התעלל בין שאר משום שהוא מוגדר להיות 8 ביטים, ובמשך 25 השנים האחרונות, 8 ביטים גם הגדירו את הנתח לחלוקה הקטן של זיכרון במחשבים. כשמשלבים את העובדה האחרונה עם העובדה שמערכת התווים ASCII הוגדרה כך שתתאים ל- 7 ביטים, סוג char הופך לסוג "אוניברסלי" נוח מאוד. יתר על כן, ב- C, מצביע למשתנה מסוג char הפך לסוג המצביע האוניברסלי מכיוון שכל דבר שאפשר להתייחס אליו כ- char יכול להתייחס גם לכל סוג אחר באמצעות יציקה.

השימוש והשימוש לרעה בסוג char בשפת C הובילו לאי התאמות רבות בין יישומי מהדר, כך שבתקן ANSI ל- C בוצעו שני שינויים ספציפיים: המצביע האוניברסלי הוגדר מחדש כך שיהיה סוג של ריק, ובכך נדרש ביטוי מפורש הכרזה על ידי המתכנת; והערך המספרי של תווים נחשב לחתימה, וכך הוגדר כיצד יתייחסו אליהם בעת שימוש בחישובים מספריים. ואז, באמצע שנות השמונים, מהנדסים ומשתמשים הבינו כי 8 ביטים אינם מספיקים כדי לייצג את כל הדמויות בעולם. למרבה הצער, עד אז ג 'היה כל כך מושרש שאנשים לא היו מוכנים, אולי אפילו לא מסוגלים, לשנות את הגדרת החרדיתסוּג. עכשיו הבהב קדימה לשנות ה -90, להתחלה המוקדמת של ג'אווה. אחד העקרונות הרבים שנקבעו בעיצוב שפת ג'אווה היה שתווים יהיו 16 סיביות. בחירה זו תומכת בשימוש ב- Unicode , דרך רגילה לייצג סוגים רבים ושונים של תווים בשפות רבות ושונות. למרבה הצער, זה גם היווה במה למגוון בעיות שרק כעת מתוקנות.

מהי בכלל דמות?

ידעתי שאני בצרות כאשר מצאתי את עצמי שואל את השאלה, "אז מה היא דמות?" ובכן, דמות היא אות, נכון? חבורה של אותיות מרכיבות מילה, מילים יוצרות משפטים וכו '. אולם המציאות היא שהקשר בין ייצוג של דמות על גבי מסך מחשב, המכונה " גליף" שלה , לערך המספרי המציין שגליף, הנקרא a code point, אינו ממש פשוט בכלל.

אני רואה את עצמי בר מזל להיות דובר שפת האם בשפה האנגלית. ראשית, משום שזו הייתה השפה הנפוצה של מספר לא מבוטל של אלה שתרמו לעיצוב ופיתוח המחשב הדיגיטלי של ימינו; שנית, מכיוון שיש בה מספר קטן יחסית של גליפים. בהגדרת ASCII יש 96 תווים הניתנים להדפסה שניתן להשתמש בהם כדי לכתוב אנגלית. השווה זאת לסינית, שם מוגדרים מעל 20,000 גליפים והגדרה זו אינה שלמה. כבר מההתחלה המוקדמת בקוד מורס ובודוט, הפשטות הכללית (מעט גליפים, תדירות הופעה סטטיסטית) של השפה האנגלית הפכה אותה ללינגה-פרנקה של העידן הדיגיטלי. אך ככל שמספר האנשים הנכנסים לעידן הדיגיטלי גדל, כך גדל מספר הדוברים שאינם אנגלית. ככל שהמספרים גדלו,יותר ויותר אנשים לא הסכימו יותר ויותר לקבל מחשבים שמשתמשים ב- ASCII ומדברים רק אנגלית. זה הגדיל מאוד את מספר המחשבים "תווים" הדרושים להבנה. כתוצאה מכך, מספר הגליפים המקודדים על ידי מחשבים היה צריך להכפיל.

מספר התווים הזמינים הוכפל כאשר קוד ASCII הנכבד של 7 סיביות שולב בקידוד תווים של 8 סיביות בשם ISO Latin-1 (או ISO 8859_1, "ISO" הוא ארגון התקנים הבינלאומי). כפי שייתכן שאספת בשם הקידוד, תקן זה אפשר לייצג רבות מהשפות שמקורן בלטינית המשמשות ביבשת אירופה. רק בגלל שהתקן נוצר, לעומת זאת, לא היה אומר שהוא שמיש. באותה תקופה, הרבה מחשבים כבר החלו להשתמש בשאר 128 ה"תווים "שעשויים להיות מיוצגים על ידי תו של 8 סיביות ליתרון כלשהו. שתי הדוגמאות ששרדו לשימוש בתווים הנוספים הללו הם המחשב האישי של IBM (PC), ומסוף המחשבים הפופולרי ביותר אי פעם, תאגיד הציוד הדיגיטלי VT-100.האחרון ממשיך בצורת תוכנת אמולטור מסוף.

זמן המוות האמיתי של הדמות של 8 סיביות ללא ספק יתווכח במשך עשרות שנים, אבל אני מצמיד אותה עם הצגתו של מחשב מקינטוש בשנת 1984. המקינטוש הכניס למחשבים המיינסטרים שני מושגים מאוד מהפכניים: גופני תווים שאוחסנו ב RAM; ו- WorldScript, שניתן להשתמש בהם לייצוג תווים בכל שפה שהיא. כמובן, זה היה פשוט עותק של מה ש- Xerox העבירה למכונות של שן הארי שלה בצורה של מערכת עיבוד תמלילים Star, אך ה- Macintosh הביא את ערכות התווים והגופנים החדשים האלה לקהל שעדיין השתמש במסופי "מטומטמים". . ברגע שהתחלנו, לא ניתן היה להפסיק את השימוש בגופנים שונים - זה היה פשוט מושך יותר מדי עבור אנשים רבים מדי. בסוף שנות ה -80,הלחץ לתקנן את השימוש בכל הדמויות הללו עלה בראשו עם הקמת קונסורציום יוניקוד, שפרסם את המפרט הראשון שלו בשנת 1990. לרוע המזל, בשנות ה -80 ואפילו בשנות ה -90, מספר ערכות התווים הוכפל. מעט מאוד מהמהנדסים שיצרו אז קודי תווים חדשים ראו את תקן Unicode המתהווה בר קיימא, ולכן הם יצרו מיפושי קודים משלהם לגליפים. אז בעוד שאוניקוד לא התקבלה היטב, התפיסה שיש רק 128 או לכל היותר 256 תווים זמינים בהחלט נעלמה. לאחר המקינטוש, התמיכה בגופנים שונים הפכה לתכונה חובה לעיבוד תמלילים. שמונה סיביות דמויות נמוגו להכחדה.בשנות ה -80 ואפילו בשנות ה -90, מספר ערכות התווים הוכפל. מעט מאוד מהמהנדסים שיצרו אז קודי תווים חדשים ראו את תקן Unicode המתהווה בר קיימא, ולכן הם יצרו מיפושי קודים משלהם לגליפים. אז בעוד שאוניקוד לא התקבלה היטב, התפיסה שיש רק 128 או לכל היותר 256 תווים זמינים בהחלט נעלמה. לאחר המקינטוש, התמיכה בגופנים שונים הפכה לתכונה חובה לעיבוד תמלילים. שמונה סיביות דמויות נמוגו להכחדה.בשנות ה -80 ואפילו בשנות ה -90, מספר ערכות התווים הוכפל. מעט מאוד מהמהנדסים שיצרו אז קודי תווים חדשים ראו את תקן Unicode המתהווה בר קיימא, ולכן הם יצרו מיפושי קודים משלהם לגליפים. אז למרות שלא קיבלו את Unicode, התפיסה שיש רק 128 או לכל היותר 256 תווים זמינה בהחלט נעלמה. לאחר המקינטוש, התמיכה בגופנים שונים הפכה לתכונה חובה לעיבוד תמלילים. שמונה סיביות דמויות נמוגו להכחדה.הרעיון שיש רק 128 או לכל היותר 256 תווים זמין בהחלט נעלם. לאחר המקינטוש, התמיכה בגופנים שונים הפכה לתכונה חובה לעיבוד תמלילים. שמונה סיביות דמויות נמוגו להכחדה.הרעיון שיש רק 128 או לכל היותר 256 תווים זמין בהחלט נעלם. לאחר המקינטוש, התמיכה בגופנים שונים הפכה לתכונה חובה לעיבוד תמלילים. שמונה סיביות דמויות נמוגו להכחדה.

Java ו- Unicode

נכנסתי לסיפור בשנת 1992 כשהצטרפתי לקבוצת האלון (שפת ג'אווה נקראה אלון כאשר פותחה לראשונה) ב- Sun. סוג הבסיס charהוגדר כ- 16 ביטים לא חתומים, הסוג היחיד שלא חתום ב- Java. הרציונל לדמות 16 סיביות היה שהיא תתמוך בכל ייצוג תווים של Unicode, ובכך ג'אווה מתאימה לייצוג מחרוזות בכל שפה הנתמכת על ידי Unicode. אך היכולת לייצג את המחרוזת והיכולת להדפיס אותה היו תמיד בעיות נפרדות. בהתחשב בעובדה שרוב הניסיון בקבוצת אלון הגיע ממערכות יוניקס וממערכות הנגזרות מאוניקס, מערכת התווים הנוחה ביותר הייתה, שוב, ISO Latin-1. כמו כן, עם מורשת יוניקס של הקבוצה, מערכת הקלטות / פלט של Java תוכננה במידה רבה בהפשטת זרם יוניקס לפיה כל מכשיר קלט / פלט יכול להיות מיוצג על ידי זרם של בתים של 8 סיביות. שילוב זה הותיר שגיאה מסוימת בשפה בין התקן קלט של 8 סיביות לבין תווי ה- 16 סיביות של Java. לכן,בכל מקום שהיה צריך לקרוא ממנו מחרוזות ג'אווה או לכתוב אותן לזרם של 8 סיביות, היה מעט קוד קטן, פריצה, כדי למפות באופן קסום תווים של 8 סיביות לתוך Unicode של 16 סיביות.

בגרסאות 1.0 של ערכת Java Developer (JDK), פריצת הקלט הייתה DataInputStreamבכיתה, ופריצת הפלט הייתה כל PrintStreamהמחלקה. (למעשה היה מחלקת קלט בשם TextInputStreamהמהדורה האלפא 2 של ג'אווה, אך היא הוחלפה על ידי DataInputStreamהפריצה במהדורה האמיתית.) זה ממשיך לגרום לבעיות עבור מתכנתי Java מתחילים, מכיוון שהם מחפשים נואשות אחר המקבילה Java של ה- C פונקציה getc(). שקול את תוכנית Java 1.0 הבאה:

ייבא java.io. *; זיוף מחלקה ציבורי {public static void main (String args []) {FileInputStream fis; DataInputStream דיס; char c; נסה את {fis = FileInputStream חדש ("data.txt"); dis = DataInputStream חדש (fis); בעוד (נכון) {c = dis.readChar (); System.out.print (c); System.out.flush (); אם (c == '\ n') הפסקה; } fis.close (); } לתפוס (חריג e) {} System.exit (0); }}

במבט ראשון, נראה שתוכנית זו פותחת קובץ, קוראת אותו תו אחד בכל פעם ויוצאת כאשר קוראים את השורה החדשה הראשונה. עם זאת, בפועל, מה שאתה מקבל הוא פלט זבל. והסיבה שאתה מקבל זבל היא שקריאת readChar קוראת תווי Unicode של 16 סיביות System.out.printומדפיסה את מה שהיא מניחה שהם תווי ISO 8 של 8 סיביות. עם זאת, אם תשנה את התוכנית לעיל כדי להשתמש בפונקציה readLine של DataInputStream, נראה שהיא עובדת מכיוון שהקוד ב- readLineקורא פורמט שמוגדר עם הנהון חולף למפרט Unicode כ- "UTF-8 שונה". (UTF-8 הוא הפורמט ש- Unicode מציין לייצוג תווי Unicode בזרם קלט של 8 סיביות.) אז המצב ב- Java 1.0 הוא שמחרוזות Java מורכבות מתווי Unicode של 16 סיביות, אך יש רק מיפוי אחד הממפה תווי ISO לטיניים -1 לתוך Unicode. למרבה המזל, Unicode מגדיר את עמוד הקוד "0" - כלומר את 256 התווים ש -8 הסיביות העליונות שלהם אפסות - כדי להתאים בדיוק לקבוצת ISO Latin-1. לפיכך, המיפוי הוא די טריוויאלי, וכל עוד אתה משתמש רק בקובצי תו ISO Latin-1, לא תהיה לך שום בעיה כאשר הנתונים עוזבים קובץ, עוברים מניפולציה על ידי מחלקת Java ואז משוחזרים לקובץ. .

היו שתי בעיות בהטמנת קוד המרת הקלט למחלקות אלה: לא כל הפלטפורמות אחסנו את הקבצים הרב-לשוניים שלהם בפורמט UTF-8 שונה; ובוודאי, היישומים בפלטפורמות אלה לא בהכרח ציפו לתווים שאינם לטיניים בצורה זו. לכן, תמיכת ההטמעה לא הושלמה, ולא הייתה דרך קלה להוסיף את התמיכה הדרושה במהדורה מאוחרת יותר.

Java 1.1 ו- Unicode

מהדורת Java 1.1 הציגה מערך ממשקים חדש לחלוטין לטיפול בתווים, שנקרא Readersו- Writers. שיניתי את הכיתה ששמה bogusמלמעלה לכיתה בשם cool. coolבכיתה משתמשת InputStreamReaderבכיתה לעבד את הקובץ ולא DataInputStreamבכיתה. שים לב InputStreamReaderשהוא תת-מחלקה של Readerהמחלקה החדשה System.outוכעת הוא PrintWriterאובייקט, שהוא תת-מחלקה של Writerהמחלקה. הקוד לדוגמא זו מוצג להלן:

ייבא java.io. *; class class מגניב {public static void main (String args []) {FileInputStream fis; InputStreamReader irs; char c; נסה את {fis = FileInputStream חדש ("data.txt"); irs = InputStreamReader חדש (fis); System.out.println ("באמצעות קידוד:" + irs.getEncoding ()); בעוד (נכון) {c = (char) irs.read (); System.out.print (c); System.out.flush (); אם (c == '\ n') הפסקה; } fis.close (); } לתפוס (חריג e) {} System.exit (0); }}

ההבדל העיקרי בין דוגמה זו לבין רישום הקוד הקודם הוא השימוש InputStreamReaderבמחלקה במקום DataInputStreamבמחלקה. דרך נוספת בה דוגמה זו שונה מקודמתה היא שיש שורה נוספת המדפיסה את הקידוד בו השתמש InputStreamReaderהכיתה.

הנקודה החשובה היא שהקוד הקיים, לאחר שלא היה מתועד (ולכאורה לא ניתן לדעת) והוטמע בתוך יישום getCharהשיטה של DataInputStreamהמחלקה, הוסר (למעשה השימוש בו הוצא משימוש; הוא יוסר במהדורה עתידית). בגירסת 1.1 של Java, המנגנון שמבצע את ההמרה נעטף כעת Readerבכיתה. אנקפסולציה זו מספקת דרך עבור ספריות מחלקות Java לתמוך בייצוגים חיצוניים רבים ושונים של תווים שאינם לטיניים תוך שימוש תמיד ב- Unicode באופן פנימי.

כמובן שבדומה לתכנון תת מערכות ה- I / O המקורי, יש עמיתים סימטריים לשיעורי הקריאה שמבצעים כתיבה. OutputStreamWriterניתן להשתמש במחלקה לכתיבת מחרוזות לזרם פלט, המחלקה BufferedWriterמוסיפה שכבת חציצה וכו '.

Trading warts or real progress?

The somewhat lofty goal of the design of the Reader and Writerclasses was to tame what is currently a hodge-podge of representation standards for the same information by providing a standard way of converting back and forth between the legacy representation -- be it Macintosh Greek or Windows Cyrillic -- and Unicode. So, a Java class that deals with strings need not change when it moves from platform to platform. This might be the end of the story, except that now that the conversion code is encapsulated, the question arises as to what that code assumes.

תוך כדי חקר הטור הזה נזכרתי בציטוט מפורסם של מנהל זירוקס (לפני שהיה זירוקס, כשהייתה חברת הלואיד) על כך שמכונת הצילום הייתה מיותרת מכיוון שהיה קל למדי למזכירה להכניס פיסת נייר פחמן לתוך את מכונת הכתיבה שלה והכין עותק של מסמך בזמן שהיא יצרה את המקור. כמובן שברור בדיעבד הוא שמכונת הצילום מועילה לאדם שמקבל מסמך הרבה יותר מאשר לאדם שמייצר מסמך. JavaSoft הראתה חוסר תובנה דומה לשימוש בשיעורי קידוד תווים ופענוח בעיצובם של חלק זה של המערכת.