שליטה ב-Java Collections
רוב המפתחים משתמשים כברירת מחדל ב-ArrayList או ב-HashSet. זה עובד למשימות פשוטות, אך זה נכשל כשזקוקים למהירות או ליכולת גדילה (scale).
פעם בניתי לוח תוצאות (leaderboard) למשחק באמצעות ArrayList רגיל. מיין אותו בכל פעם שציון השתנה. ממשק המשתמש (UI) קפא כל הזמן. הייתי נלחם בשפה במקום להשתמש בה.
הפסיקו להשתמש בכלים הלא נכונים. השתמשו בשלוש ה-collections הייעודיות הללו כדי לכתוב קוד מהיר ונקי יותר.
- EnumSet עבור קבועים מסוג Enum
אם אתם משתמשים ב-HashSet עבור enums, אתם משלמים "מס ביצועים". כל הכנסה (insertion) מבצעת boxing ל-enum לאובייקט. זה מוסיף overhead מיותר.
EnumSet משתמש ב-bit vector. הוא מבצע בדיקות באמצעות הוראת CPU בודדת.
- השתמשו בו כאשר יש לכם סט קבוע של ערכי enum.
- הוא מפחית את ה-garbage collection.
- הוא מספק עלייה של פי 10 במהירות בלולאות צפופות (tight loops).
Before:
Set<Ability> abilities = new HashSet<>();
abilities.add(Ability.FIRE);
After:
EnumSet<Ability> abilities = EnumSet.of(Ability.FIRE);
- NavigableSet עבור שאילתות טווח (Range Queries)
מעבר ידני בלולאה על רשימה ממוינת כדי למצוא טווח הוא איטי וחשוף לשגיאות. לעיתים קרובות תמצאו את עצמכם עם באגים מסוג off-by-one.
NavigableSet שומר על הנתונים שלכם ממוינים באופן אוטומטי. הוא מספק חיפושים (lookups) בסיבוכיות של O(log n) עבור תת-קבוצות (subsets).
- השתמשו בו עבור טבלאות ניקוד גבוה או טווחי מחירים.
- השתמשו ב-headSet() או ב-subSet() כדי לקבל טווחים ספציפיים.
- זה מסיר את הצורך בקוד מיון ידני.
Before:
Collections.sort(scores);
List<Integer> topTen = scores.subList(size - 10, size);
After:
NavigableSet<Integer> scores = new TreeSet<>(Comparator.reverseOrder());
scores.add(1542);
NavigableSet<Integer> topTen = scores.headSet(scores.first(), true).stream().limit(10).collect(Collectors.toCollection(TreeSet::new));
- CopyOnWriteArrayList עבור רשימות עם קריאות מרובות (Read-Heavy)
שימוש בבלוקים מסוג synchronized על ArrayList מאט כל קריאה. זה גם גורם ל-ConcurrentModificationException אם תהליכון (thread) אחד כותב בזמן שאחר קורא.
CopyOnWriteArrayList יוצר עותק חדש של המערך בכל פעולת כתיבה. הקוראים צופים ב-snapshot (תמונת מצב) של המערך.
- השתמשו בו עבור מאזיני אירועים (event listeners) או הגדרות קונפיגורציה.
- השתמשו בו כאשר קריאות מתבצעות בתדירות גבוהה בהרבה מכתיבות.
- הוא מאפשר קריאות ללא נעילה (lock-free).
Before:
List<String> log = Collections.synchronizedList(new ArrayList<>());
// Iterating here can crash if a writer joins in.
After:
CopyOnWriteArrayList<String> log = new CopyOnWriteArrayList<>();
// Iteration is safe and never crashes.
הפסק להסתמך כברירת מחדל על אותם שני אוספים. בחר בכלי שמתאים לתבנית הנתונים שלך.
מקור: https://dev.to/timevolt/the-java-collections-force-mastering-the-hidden-gems-like-a-jedi-4438