מדריך ל- LinkedHashMap בג'אווה

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

במאמר זה, אנו הולכים לחקור את היישום הפנימי של LinkedHashMap מעמד. LinkedHashMap הוא יישום נפוץ של מַפָּה מִמְשָׁק.

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

2. LinkedHashMap לעומת מפת גיבוב

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

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

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

מחלקה סטטית כניסה מאריכה את HashMap.Node {כניסה לפני, אחרי; ערך (int hash, K key, V value, Node next) {super (hash, key, value, next); }}

שימו לב שה- כְּנִיסָה הכיתה פשוט מוסיפה שתי עצות; לפני ו לאחר המאפשרים לו להתחבר לרשימה המקושרת. מלבד זאת, הוא משתמש ב- כְּנִיסָה יישום בכיתה של HashMap.

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

3. הזמנת הכנסה LinkedHashMap

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

@Test public void givenLinkedHashMap_whenGetsOrderedKeyset_thenCorrect () {LinkedHashMap map = חדש LinkedHashMap (); map.put (1, null); map.put (2, null); map.put (3, null); map.put (4, null); map.put (5, null); הגדר מקשים = map.keySet (); מספר שלם [] arr = keys.toArray (מספר שלם חדש [0]); עבור (int i = 0; i <arr.length; i ++) {assertEquals (מספר שלם חדש (i + 1), arr [i]); }}

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

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

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

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

4. צו גישה LinkedHashMap

LinkedHashMap מספק קונסטרוקטור מיוחד המאפשר לנו לציין, בין גורם עומס מותאם אישית (LF) לבין קיבולת ראשונית, מנגנון / אסטרטגיית הזמנה אחרת הנקראת הזמנת גישה:

LinkedHashMap map = חדש LinkedHashMap (16, .75f, נכון);

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

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

וכך, בניית מטמון הכי פחות בשימוש (LRU) היא די קלה ומעשית עם סוג מסוג זה. מוצלח לָשִׂים אוֹ לקבל פעולה מביאה לגישה לערך:

@Test public void givenLinkedHashMap_whenAccessOrderWorks_thenCorrect () {LinkedHashMap map = חדש LinkedHashMap (16, .75f, נכון); map.put (1, null); map.put (2, null); map.put (3, null); map.put (4, null); map.put (5, null); הגדר מקשים = map.keySet (); assertEquals ("[1, 2, 3, 4, 5]", keys.toString ()); map.get (4); assertEquals ("[1, 2, 3, 5, 4]", keys.toString ()); map.get (1); assertEquals ("[2, 3, 5, 4, 1]", keys.toString ()); map.get (3); assertEquals ("[2, 5, 4, 1, 3]", keys.toString ()); }

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

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

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

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

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

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

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

מחלקה ציבורית MyLinkedHashMap מרחיב את LinkedHashMap {גמר סטטי פרטי פרטי MAX_ENTRIES = 5; ציבורי MyLinkedHashMap (int initialCapacity, float loadFactor, Boolean accessOrder) {super (initialCapacity, loadFactor, accessOrder); } @ Override מוגן בוליאני removeEldestEntry (Map.Entry Eldest) {גודל גודל ()> MAX_ENTRIES; }}

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

@Test public void givenLinkedHashMap_whenRemovesEldestEntry_thenCorrect () {LinkedHashMap map = MyLinkedHashMap חדש (16, .75f, נכון); map.put (1, null); map.put (2, null); map.put (3, null); map.put (4, null); map.put (5, null); הגדר מקשים = map.keySet (); assertEquals ("[1, 2, 3, 4, 5]", keys.toString ()); map.put (6, null); assertEquals ("[2, 3, 4, 5, 6]", keys.toString ()); map.put (7, null); assertEquals ("[3, 4, 5, 6, 7]", keys.toString ()); map.put (8, null); assertEquals ("[4, 5, 6, 7, 8]", keys.toString ()); }

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

5. שיקולי ביצוע

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

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

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

הסיבה לכך היא, עבור LinkedHashMap, נ ב עַל) הוא רק מספר הערכים במפה ללא קשר לקיבולת. ואילו, עבור מפת גיבוב, נ הוא קיבולת והגודל מסוכם, O (גודל + קיבולת).

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

6. מקביליות

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

עדיף לעשות זאת בעת הבריאה:

מפה m = Collections.synchronizedMap (חדש LinkedHashMap ());

ההבדל עם מפת גיבוב טמון במה שכרוך בשינוי מבני. במפות חשיש מקושרות שהוזמנו באמצעות גישה, פשוט קוראות לקבל API גורם לשינוי מבני. לצד זה, פעולות כמו לָשִׂים ו לְהַסִיר.

7. מסקנה

במאמר זה חקרנו את ג'אווה LinkedHashMap הכיתה כאחד היישומים הראשונים של מַפָּה ממשק מבחינת השימוש. בדקנו גם את פעולתו הפנימית מבחינת ההבדל בין מפת גיבוב שהוא מעמד העל שלו.

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

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