מדריך לקידוד תווים

1. סקירה כללית

במדריך זה נדון ביסודות קידוד התווים ובאופן הטיפול בו בג'אווה.

2. חשיבות קידוד הדמויות

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

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

כדי להבין זאת טוב יותר, נגדיר שיטה לפענוח טקסט ב- Java:

DecodeText מחרוזת (קלט מחרוזת, קידוד מחרוזת) זורק IOException {להחזיר BufferedReader חדש (InputStreamReader חדש (ByteArrayInputStream חדש (input.getBytes ()), Charset.forName (קידוד))) .readLine (); }

שים לב כי טקסט הקלט שאנו מזינים כאן משתמש בקידוד ברירת המחדל לפלטפורמה.

אם נפעיל את השיטה הזו עם קֶלֶט כמו "דפוס החזית הוא דפוס עיצוב תוכנה." ו הַצפָּנָה כ- "US-ASCII", זה יפיק:

דפוס החזית הוא דפוס עיצוב תוכנה.

ובכן, לא בדיוק מה שציפינו.

מה יכול היה להשתבש? ננסה להבין ולתקן זאת בהמשך מדריך זה.

3. יסודות

לפני שנעמיק יותר, בואו נסקור במהירות שלוש מונחים: הַצפָּנָה, ערכות, ו נקודת קוד.

3.1. הַצפָּנָה

מחשבים יכולים להבין רק ייצוגים בינאריים כמו 1 ו 0. עיבוד כל דבר אחר מחייב מיפוי כלשהו מהטקסט האמיתי לייצוגו הבינארי. מיפוי זה הוא מה שאנחנו מכירים קידוד תווים או פשוט כמו הַצפָּנָה.

לדוגמא, האות הראשונה בהודעה שלנו, "T", ב- US-ASCII מקודד אל “01010100”.

3.2. ערכות

מיפוי הדמויות לייצוגים הבינאריים שלהם יכול להשתנות מאוד מבחינת הדמויות שהם כוללים. מספר התווים הכלולים במיפוי יכול להשתנות בין מעטים בלבד לכל הדמויות בשימוש מעשי. קבוצת התווים הכלולים בהגדרת מיפוי נקראת באופן רשמי a ערכת תווים.

לדוגמה, ל- ASCII יש ערכת תווים של 128 תווים.

3.3. נקודת קוד

נקודת קוד היא הפשטה המפרידה בין תו לקידוד בפועל. א נקודת קוד הוא התייחסות שלמה לדמות מסוימת.

אנו יכולים לייצג את המספר השלם עצמו בבסיסים עשרוניים פשוטים או חלופיים כמו הקסדצימלי או אוקטלי. אנו משתמשים בבסיסים חלופיים כדי להקל על הפניית מספרים גדולים.

לדוגמא, לאות הראשונה בהודעה שלנו, T, ב- Unicode יש נקודת קוד "U + 0054" (או 84 בעשרוני).

4. הבנת תוכניות קידוד

קידוד תווים יכול ללבוש צורות שונות בהתאם למספר התווים שהוא מקודד.

למספר התווים המקודדים יש קשר ישיר לאורך כל ייצוג שנמדד בדרך כלל כמספר הבתים. למעשה יש יותר תווים לקידוד פירושם צורך בייצוגים בינאריים ארוכים יותר.

בואו נעבור כמה מתוכניות הקידוד הפופולריות כיום.

4.1. קידוד בתים בודדים

אחת מתכניות הקידוד המוקדמות ביותר, הנקראת ASCII (קוד תקן אמריקאי להחלפת מידע) משתמשת בתכנית קידוד בתים אחת. זה בעצם אומר את זה כל תו ב- ASCII מיוצג עם מספרים בינאריים של שבעה סיביות. זה עדיין משאיר קצת פנוי בכל בית!

ערכת ה- 128 תווים של ASCII מכסה אלפביתים באנגלית באותיות קטנות וקטנות, ספרות וכמה תווים מיוחדים ובקרה.

בואו נגדיר שיטה פשוטה ב- Java להצגת הייצוג הבינארי של תו תחת ערכת קידוד מסוימת:

String convertToBinary (קלט מחרוזת, קידוד מחרוזת) זורק UnsupportedEncodingException {byte [] encoded_input = Charset.forName (קידוד). Encode (קלט) .array (); החזר IntStream.range (0, encoded_input.length) .map (i -> encoded_input [i]) .mapToObj (e -> Integer.toBinaryString (e ^ 255)) .map (e -> String.format ("% 1 $ "+ Byte.SIZE +" s ", e) .replace (" "," 0 ")) .collect (Collectors.joining (" ")); }

כעת, לתו 'T' נקודת קוד של 84 ב- US-ASCII (ASCII מכונה ב- US US-ASCII ב- Java).

ואם אנו משתמשים בשיטת השירות שלנו, אנו יכולים לראות את הייצוג הבינארי שלה:

assertEquals (convertToBinary ("T", "US-ASCII"), "01010100");

זהו, כפי שציפינו, ייצוג בינארי בן שבע סיביות עבור הדמות 'T'.

ה- ASCII המקורי הותיר את החלק המשמעותי ביותר מכל בתים ללא שימוש. יחד עם זאת, ASCII הותיר די הרבה דמויות בלתי מיוצגות, במיוחד עבור שפות שאינן אנגלית.

זה הוביל למאמץ להשתמש בסיבית הלא מנוצלת ולכלול 128 תווים נוספים.

היו כמה וריאציות של תוכנית הקידוד ASCII שהוצעו ואומצו לאורך זמן. אלה נקראו באופן רופף "הרחבות ASCII".

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

אחת התוספות הפופולריות יותר של ASCII הייתה ISO-8859-1, המכונה גם "ISO Latin 1".

4.2. קידוד רב בתים

ככל שהצורך להכיל יותר ויותר תווים גדל, תוכניות קידוד של בתים בודדים כמו ASCII לא היו בר קיימא.

זה הוליד תוכניות קידוד מרובות בתים בעלות יכולת טובה בהרבה אם כי במחיר של דרישות שטח מוגברות.

דוגמאות ל- BIG5 ו- SHIFT-JIS תוכניות קידוד תווים מרובות בתים שהחלו להשתמש באחת וגם בשני בתים לייצוג ערכות תווים רחבות יותר. רוב אלה נוצרו לצורך הצורך לייצג סקריפטים סיניים ודומים עם מספר גדול יותר של תווים.

בואו עכשיו נקרא לשיטה convertToBinary עם קֶלֶט כמו '語', אופי סיני, ו הַצפָּנָה בתור "Big5":

assertEquals (convertToBinary ("語", "Big5"), "10111011 01111001");

הפלט שלמעלה מראה שקידוד Big5 משתמש בשני בתים כדי לייצג את התו '語'.

רשימה מקיפה של קידודי תווים, יחד עם כינוייהם, מנוהלת על ידי רשות המספרים הבינלאומית.

5. יוניקוד

לא קשה להבין שבעוד קידוד חשוב, פענוח חיוני באותה מידה כדי להבין את הייצוגים. זה אפשרי רק בפועל אם נעשה שימוש נרחב בתכנית קידוד עקבית או תואמת.

תוכניות קידוד שונות שפותחו בבידוד ונהגו בגיאוגרפיות מקומיות החלו להיות מאתגרות.

אתגר זה הוליד תקן קידוד יחיד בשם Unicode אשר יכולת לכל דמות אפשרית בעולם. זה כולל את הדמויות שנמצאות בשימוש ואפילו את הדמויות שאינן פעולות!

ובכן, זה חייב לדרוש כמה בתים כדי לאחסן כל תו? בכנות כן, אבל ל- Unicode יש פיתרון גאוני.

יוניקוד כסטנדרט מגדיר נקודות קוד לכל תו אפשרי בעולם. נקודת הקוד עבור התו 'T' ב- Unicode היא 84 בעשרוני. בדרך כלל אנו מתייחסים לזה כ- "U + 0054" ב- Unicode שאינו אלא U + ואחריו המספר ההקסדצימלי.

אנו משתמשים בהקסדצימלי כבסיס לנקודות קוד ב- Unicode מכיוון שיש 1,114,112 נקודות, שזה מספר די גדול לתקשר בצורה נוחה בעשרונית!

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

5.1. UTF-32

UTF-32 הוא ערכת קידוד עבור Unicode המעסיקה ארבעה בתים לייצוג כל נקודת קוד מוגדר על ידי יוניקוד. ברור שזה לא יעיל במקום להשתמש בארבעה בתים לכל תו.

בואו נראה איך דמות פשוטה כמו 'T' מיוצגת ב- UTF-32. נשתמש בשיטה convertToBinary הוצג קודם:

assertEquals (convertToBinary ("T", "UTF-32"), "00000000 00000000 00000000 01010100");

הפלט שלמעלה מראה את השימוש בארבעה בתים כדי לייצג את התו 'T' כאשר שלושת הבתים הראשונים הם פשוט מקום מבוזבז.

5.2. UTF-8

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

בואו נקרא שוב לשיטה convertToBinary עם קלט כמו 'T' וקידוד כמו "UTF-8":

assertEquals (convertToBinary ("T", "UTF-8"), "01010100");

הפלט דומה בדיוק ל- ASCII באמצעות בת אחד בלבד. למעשה, UTF-8 תואם לאחור לחלוטין ל- ASCII.

בואו נקרא שוב לשיטה convertToBinary עם קלט '語' וקידוד כ- "UTF-8":

assertEquals (convertToBinary ("語", "UTF-8"), "11101000 10101010 10011110");

כפי שניתן לראות כאן UTF-8 משתמש בשלושה בתים כדי לייצג את התו '語'. זה ידוע בשם קידוד ברוחב משתנה.

UTF-8, בשל יעילות החלל שלו, הוא הקידוד הנפוץ ביותר המשמש באינטרנט.

6. קידוד תמיכה בג'אווה

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

זה כולל US-ASCII, ISO-8859-1, UTF-8 ו- UTF-16 עד כמה שם. יישום מסוים של Java עשוי לתמוך אופציונלי בקידודים נוספים.

יש כמה דקויות באופן שבו ג'אווה מרימה ערכת תווים לעבודה. בואו נעבור אותם בפרטים נוספים.

6.1. ערכת ברירת מחדל

פלטפורמת Java תלויה במידה רבה בנכס שנקרא ערכת ברירת המחדל. מכונת ה- Java Virtual Java (JVM) קובעת את ערכת ברירת המחדל של התווים במהלך ההפעלה.

זה תלוי במיקום ובמערכת התווים של מערכת ההפעלה הבסיסית שעליה פועל JVM. לדוגמא ב- MacOS, ערכת ברירת המחדל היא UTF-8.

בואו נראה כיצד נוכל לקבוע את ערכת ברירת המחדל:

Charset.defaultCharset (). DisplayName ();

אם אנו מריצים את קטע הקוד הזה במחשב Windows, הפלט שנקבל:

windows-1252

כעת, "windows-1252" הוא ערכת ברירת המחדל של פלטפורמת Windows באנגלית, אשר במקרה זה קבעה את ערכת ברירת המחדל של JVM הפועלת ב- Windows.

6.2. מי משתמש בערכת ברירת המחדל?

רבים מממשקי ה- API של Java משתמשים בערכת ברירת המחדל כפי שנקבעה על ידי ה- JVM. להזכיר כמה:

  • InputStreamReader ו FileReader
  • OutputStreamWriter ו FileWriter
  • מעצב ו סוֹרֵק
  • URLEncoder ו URLDecoder

אז זה אומר שאם היינו מציגים את הדוגמה שלנו בלי לציין את ערכת התווים:

BufferedReader חדש (InputStreamReader חדש (ByteArrayInputStream חדש (input.getBytes ()))). readLine ();

ואז הוא ישתמש בערכת ברירת המחדל כדי לפענח אותה.

ויש כמה ממשקי API שמבצעים את אותה בחירה כברירת מחדל.

לפיכך, ערכת ברירת המחדל מניחה חשיבות שלא נוכל להתעלם ממנה בבטחה.

6.3. בעיות עם ערכת ברירת המחדל

כפי שראינו כי ערכת ברירת המחדל ב- Java נקבעת באופן דינמי כאשר ה- JVM מתחיל. זה עושה את הפלטפורמה פחות אמינה או נוטה לטעויות כאשר משתמשים בה במערכות הפעלה שונות.

למשל, אם אנחנו רצים

BufferedReader חדש (InputStreamReader חדש (ByteArrayInputStream חדש (input.getBytes ()))). readLine ();

ב- macOS, הוא ישתמש ב- UTF-8.

אם ננסה את אותו קטע ב- Windows, הוא ישתמש ב- Windows-1252 כדי לפענח את אותו הטקסט.

לחלופין, דמיין לכתוב קובץ ב- macOS ואז לקרוא את אותו קובץ ב- Windows.

לא קשה להבין שבגלל תוכניות קידוד שונות זה עלול להוביל לאובדן נתונים או לשחיתות.

6.4. האם נוכל לבטל את ערכת ברירת המחדל?

הקביעה של ערכת ברירת המחדל ב- Java מובילה לשני מאפייני מערכת:

  • קובץ קידוד: הערך של מאפיין מערכת זה הוא שם ערכת ברירת המחדל
  • sun.jnu. קידוד: הערך של מאפיין מערכת זה הוא שם ערכת התווים המשמשת לקידוד / פענוח של נתיבי קבצים

עכשיו, זה אינטואיטיבי לעקוף את מאפייני המערכת הללו באמצעות ארגומנטים של שורת פקודה:

-Dfile.encoding = "UTF-8" -Dsun.jnu.encoding = "UTF-8"

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

לָכֵן, עלינו להימנע מעקיפת ערכת ברירת המחדל ב- Java.

6.5. מדוע ג'אווה אינה פותרת זאת?

יש הצעה לשיפור Java (JEP) שקובעת את השימוש ב- "UTF-8" כמערכת ברירת המחדל ב- Java במקום לבסס אותה על ערכת התווים המקומית ומערכת ההפעלה.

JEP זה נמצא במצב טיוטה נכון לעכשיו וכאשר הוא (בתקווה!) יעבור הוא יפתור את רוב הנושאים עליהם דנו קודם.

שים לב שממשקי ה- API החדשים יותר כמו אלה שב- java.nio.file.Files אל תשתמש בערכת ברירת המחדל. השיטות בממשקי ה- API הללו קוראות או כותבות זרמי תווים עם ערכת תווים כ- UTF-8 ולא כמערכת ברירת המחדל.

6.6. פתרון בעיה זו בתוכניות שלנו

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

למרבה המזל, הדוגמה שלנו כבר מגדירה את ערכת התווים. אנחנו רק צריכים לבחור את הנכון ולתת לג'אווה לעשות את השאר.

עלינו להבין עד עכשיו שתווים מודגשים כמו 'ç' אינם קיימים בסכימת הקידוד ASCII ולכן אנו זקוקים לקידוד הכולל אותם. אולי, UTF-8?

בואו ננסה את זה, כעת נפעיל את השיטה decodeText עם אותה קלט אך קידוד כמו "UTF-8":

דפוס החזית הוא דפוס עיצוב תוכנה.

בינגו! אנו יכולים לראות את התפוקה שקיווינו לראות כעת.

כאן קבענו את הקידוד שלדעתנו מתאים ביותר לצורך שלנו בבנאי InputStreamReader. זו בדרך כלל השיטה הבטוחה ביותר להתמודד עם תווים והמרות בתים בג'אווה.

בדומה לכך, OutputStreamWriter וממשקי API רבים אחרים תומכים בקביעת ערכת קידוד באמצעות הבנאי שלהם.

6.7. MalformedInputException

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

ישנן שלוש אסטרטגיות מוגדרות מראש (או CodingErrorAction) כאשר לרצף הקלט יש קלט שגוי:

  • להתעלם יתעלם מתווים פגומים ויחדש את פעולת הקידוד
  • החלף יחליף את התווים הלא תקינים במאגר הפלט ויחדש את פעולת הקידוד
  • להגיש תלונה יזרוק א MalformedInputException

ברירת המחדל malformedInputAction בשביל ה CharsetDecoder הוא דוח, ואת ברירת המחדל malformedInputAction של מפענח ברירת המחדל ב InputStreamReader הוא החלף.

בואו נגדיר פונקציית פענוח המקבלת ספציפי ערכת, א CodingErrorAction סוג, ומחרוזת לפענוח:

DecodeText מחרוזת (קלט מחרוזת, Charset Charset, CodingErrorAction codingErrorAction) זורק IOException {CharsetDecoder charsetDecoder = charset.newDecoder (); charsetDecoder.onMalformedInput (codingErrorAction); להחזיר BufferedReader חדש (InputStreamReader חדש (ByteArrayInputStream חדש (input.getBytes ()), charsetDecoder)). readLine (); }

לכן, אם אנו מפענחים "דפוס החזית הוא דפוס עיצוב תוכנה." עם US_ASCIIהתפוקה עבור כל אסטרטגיה תהיה שונה. ראשית, אנו משתמשים CodingErrorAction.IGNORE שמדלג על דמויות לא חוקיות:

Assertions.assertEquals ("דפוס החזית הוא דפוס עיצוב תוכנה.", CharacterEncodingExamples.decodeText ("דפוס החזית הוא דפוס עיצוב תוכנה.", StandardCharsets.US_ASCII, CodingErrorAction.IGNORE));

למבחן השני אנו משתמשים CodingErrorAction.REPLACE במקום את הדמויות הבלתי חוקיות:

Assertions.assertEquals ("דפוס החזית הוא דפוס עיצוב תוכנה.", CharacterEncodingExamples.decodeText ("דפוס החזית הוא דפוס עיצוב תוכנה.", StandardCharsets.US_ASCII, CodingErrorAction.REPLACE));

למבחן השלישי, אנו משתמשים CodingErrorAction.REPORT מה שמוביל לזריקה MalformedInputException:

Assertions.assertThrows (MalformedInputException.class, () -> CharacterEncodingExamples.decodeText ("דפוס החזית הוא תבנית עיצוב תוכנה.", StandardCharsets.US_ASCII, CodingErrorAction.REPORT));

7. מקומות אחרים בהם חשוב קידוד

אנחנו לא צריכים רק לשקול קידוד תווים בזמן התכנות. טקסטים יכולים להשתבש באופן סופי במקומות רבים אחרים.

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

בואו נעבור במהירות על כמה מקומות שבהם אנו עשויים להיתקל בבעיות בעת קידוד או פענוח טקסט.

7.1. עורכי טקסט

ברוב המקרים, עורך טקסט הוא המקום שמקורם בטקסטים. ישנם עורכי טקסטים רבים בבחירה הפופולרית, כולל vi, Notepad ו- MS Word. רוב עורכי הטקסט הללו מאפשרים לנו לבחור את ערכת הקידוד. לפיכך, עלינו תמיד לוודא שהם מתאימים לטקסט שאנו מטפלים בו.

7.2. מערכת קבצים

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

7.3. רֶשֶׁת

טקסטים המועברים ברשת באמצעות פרוטוקול כמו FTP (File Transfer Protocol) כוללים גם המרה בין קידוד תווים. עבור כל דבר שמקודד ב- Unicode, הכי בטוח להעביר אותו כבינארי כדי למזער את הסיכון לאובדן בהמרה. עם זאת, העברת טקסט ברשת היא אחד הגורמים השכיחים פחות לשחיתות נתונים.

7.4. מאגרי מידע

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

7.5. דפדפנים

לבסוף, ברוב יישומי האינטרנט, אנו יוצרים טקסטים ומעבירים אותם דרך שכבות שונות מתוך כוונה להציג אותם בממשק משתמש, כמו בדפדפן. גם כאן חובה עלינו לבחור את קידוד התווים הנכון שיוכל להציג את הדמויות כראוי. הדפדפנים הפופולריים ביותר כמו Chrome, Edge מאפשרים לבחור את קידוד התווים באמצעות ההגדרות שלהם.

8. מסקנה

במאמר זה דנו כיצד קידוד יכול להיות נושא במהלך התכנות.

עוד דנו ביסודות כולל קידוד וערכות. יתר על כן, עברנו תוכניות קידוד שונות ושימושן.

לקחנו גם דוגמה לשימוש שגוי בקידוד תווים בג'אווה וראינו איך להשיג את זה נכון. לבסוף, דנו בתרחישים אחרים של שגיאות נפוצות הקשורות לקידוד תווים.

כמו תמיד, הקוד לדוגמאות זמין ב- GitHub.