Collections Deep Dive — Set (HashSet/TreeSet) & Map (HashMap/TreeMap) (సెట్ & మ్యాప్ ఆర్కిటెక్చర్)
ఎంటర్ప్రైజ్ సాఫ్ట్వేర్ అభివృద్ధిలో మిలియన్ల కొద్దీ యూజర్ రికార్డులను నిల్వ చేసేటప్పుడు రెండు ప్రధాన అవసరాలు ఉంటాయి: ఒకటి — డూప్లికేట్ డేటాను పూర్తిగా నిరోధించడం (Unique Elements), రెండవది — కీ ఆధారంగా సెకనులో లక్ష వంతు సమయంలో డేటాను వెతికి తీయడం (Ultra-Fast Key-Value Lookup). జావా కలెక్షన్స్ ఫ్రేమ్వర్క్ ఈ రెండు అతిపెద్ద సమస్యలను సమర్థవంతంగా పరిష్కరించడానికి Set మరియు Map ఇంటర్ఫేస్లను రూపొందించింది. ప్రపంచవ్యాప్తంగా ప్రతి హై-స్కేల్ జావా సిస్టమ్ వెనుక గుండెకాయలా పనిచేసే HashMap యొక్క అంతర్గత ఆర్కిటెక్చర్, హాష్ బకెట్లు (Hash Buckets), హాష్ కొలీజన్లు (Collisions), జావా 8 లో ప్రవేశపెట్టిన ట్రీయిఫికేషన్ (Treeification to Red-Black Trees), మరియు equals() & hashCode() కాంట్రాక్ట్ వెనుక ఉన్న రహస్యాలను ఈ అధ్యాయంలో లోతుగా నేర్చుకుందాం.
యూనిక్ వేలిముద్రల డేటాబేస్ & లైబ్రరీ క్యాటలాగ్ కార్డ్ ఇండెక్స్ అనాలజీ (The Unique Fingerprint Database & Library Catalog Card Index Analogy):
-
1. Set (యూనిక్ వేలిముద్రల రిజిస్టర్ - Unique Fingerprint Registry): ప్రభుత్వ ఆధార్ లేదా పాస్పోర్ట్ డేటాబేస్లో ఒక పౌరుడి వేలిముద్ర ఇప్పటికే ఉంటే, అతడు మళ్లీ వచ్చి నమోదు చేయాలని చూసినా సిస్టమ్ అంగీకరించదు. జావాలోని
Setసరిగ్గా అలాగే పనిచేస్తుంది — ఇది డూప్లికేట్ ఎలిమెంట్లను ఏమాత్రం అనుమతించదు. ఒకే ఆధార్ నంబర్ను పదిసార్లు చేర్చినా, సెట్లో కేవలం ఒక్క ఎంట్రీ మాత్రమే మిగులుతుంది. -
2. Map (లైబ్రరీ క్యాటలాగ్ కార్డ్ & నిఘంటువు - Library Catalog & Word Dictionary): లైబ్రరీలో లక్షల పుస్తకాలుంటాయి. ప్రతి పుస్తకానికి ఒక ప్రత్యేకమైన ISBN కోడ్ (Key) మరియు ఆ పుస్తకం వివరాలు (Value) ఉంటాయి. మనం పుస్తకం పేరు మొత్తం వెతకకుండా ISBN కోడ్ ఇవ్వగానే లైబ్రేరియన్ క్షణంలో పుస్తకాన్ని చేతిలో పెడతారు. జావాలోని
MapఈKey-Valueసూత్రంపై ఆధారపడి పనిచేస్తుంది. ఇక్కడ 'కీ' ఎప్పుడూ యునిక్గా ఉంటుంది, ఒకవేళ ఇప్పటికే ఉన్న కీతో కొత్త విలువను జోడిస్తే పాత విలువ స్థానంలో కొత్తది రీప్లేస్ అవుతుంది. -
3. HashMap vs TreeSet/TreeMap (హ్యాష్ బకెట్లు vs ఆల్ఫాబెటికల్ ఇండెక్స్ బుక్):
HashMapమరియుHashSetఒక గణిత హాషింగ్ ఫార్ములా ఉపయోగించి డేటాను క్షణంలో కనుగొంటాయి (సగటునO(1)సమయం). అయితే వాటిలో డేటా ఆర్డర్ ఉండదు. దీనికి భిన్నంగాTreeSetమరియుTreeMapఒక డిక్షనరీలోని పదాల లాగా డేటాను ఎల్లప్పుడూ ఆరోహణ క్రమంలో (Sorted Natural Order) భద్రపరుస్తాయి. దీనికోసం అవి వెనుక 'రెడ్-బ్లాక్ బ్యాలెన్స్డ్ ట్రీ'ని ఉపయోగించిO(log N)సమయంలో ఎలిమెంట్లను అందిస్తాయి.
💡 ఈ పాఠంలో మీరు సెట్ మరియు మ్యాప్ ఇంటర్ఫేస్ల సూక్ష్మ వ్యత్యాసాలు, సమాంతర డేటా స్ట్రక్చర్ల సామర్థ్యాలు, హాష్ కొలీజన్ మేనేజ్మెంట్ మరియు ఎంటర్ప్రైజ్ లెవల్ యూజర్ సెషన్ క్యాష్ ఇంజిన్ను ప్రాక్టికల్గా నిర్మిస్తారు!
📑 పాఠ్యాంశ విషయ సూచిక (Table of Contents)
1. Set ఇంటర్ఫేస్ & యునిక్నెస్ కాంట్రాక్ట్ (Set Interface & Uniqueness Contract)
జావా కలెక్షన్ ఫ్రేమ్వర్క్లో java.util.Set అనేది Collection ఇంటర్ఫేస్ నుండి ఉద్భవించిన ఒక ప్రధాన విభాగం. సెట్ యొక్క ప్రాథమిక నిబంధన ఒక్కటే: "ఇది ఏ విధమైన డూప్లికేట్ ఎలిమెంట్లను కలిగి ఉండదు" (No duplicate elements allowed). గణిత శాస్త్రంలోని సెట్ థియరీ (Mathematical Set Theory) లాగానే, జావా సెట్ కూడా ఒక నిర్దిష్ట సమూహంలో ప్రతి మూలకం ప్రత్యేకమైనదిగా ఉండేలా నిర్ధారిస్తుంది.
రెండు ఆబ్జెక్టులు సమానమా కాదా అని నిర్ణయించడానికి జావా సెట్ e1.equals(e2) పద్ధతిని ఉపయోగిస్తుంది. అంతేకాకుండా, గరిష్టంగా కేవలం ఒకే ఒక null ఎలిమెంట్ను మాత్రమే అనుమతిస్తుంది (HashSet/LinkedHashSet లలో; అయితే TreeSet లో null పూర్తిగా నిషిద్ధం).
2. HashSet ఆర్కిటెక్చర్ & HashMap డిపెండెన్సీ (HashSet Internals)
జావా లైబ్రరీలో HashSet క్లాస్ సోర్స్ కోడ్ను పరిశీలిస్తే ఒక ఆశ్చర్యకరమైన నిజం బయటపడుతుంది: HashSet స్వయంగా డేటాను నిల్వ చేయదు; అది తెరవెనుక ఒక HashMap ఆబ్జెక్టును ఉపయోగిస్తుంది!
public class HashSet<E> extends AbstractSet<E> implements Set<E> {
private transient HashMap<E, Object> map;
private static final Object PRESENT = new Object(); // Dummy Value
public boolean add(E e) {
return map.put(e, PRESENT) == null;
}
}
మీరు సెట్లోకి set.add("Apple") అన్నప్పుడు, HashSet తన అంతర్గత HashMap లో map.put("Apple", PRESENT) అని ఇన్సర్ట్ చేస్తుంది. ఇక్కడ "Apple" అనేది HashMap యొక్క Key గా మారుతుంది, మరియు PRESENT అనే ఒక డమ్మీ స్టాటిక్ ఆబ్జెక్ట్ Value గా కూర్చుంటుంది. HashMap కీలను ఎల్లప్పుడూ యునిక్గా ఉంచుతుంది కాబట్టి, డూప్లికేట్ ఎలిమెంట్ను యాడ్ చేస్తే map.put() పాత డమ్మీ ఆబ్జెక్ట్ను రిటర్న్ చేస్తుంది, ఫలితంగా add() మెథడ్ false రిటర్న్ చేసి డూప్లికేషన్ను నిరోధిస్తుంది!
3. TreeSet & NavigableSet (Red-Black Tree ఆర్కిటెక్చర్)
ఒకవేళ మనకు డూప్లికేట్లు లేకుండా ఉండటంతో పాటు ఎలిమెంట్లు ఎల్లప్పుడూ చిన్న నుండి పెద్ద క్రమంలో (Sorted Natural Order) లేదా మన స్వంత Comparator క్రమంలో ఆటోమేటిక్గా అమర్చబడి ఉండాలంటే java.util.TreeSet సరైన పరిష్కారం.
TreeSet అనేది NavigableSet మరియు SortedSet ఇంటర్ఫేస్లను ఇంప్లిమెంట్ చేస్తుంది. ఇది తెరవెనుక Red-Black Self-Balancing Binary Search Tree ని ఉపయోగిస్తుంది. ఈ చెట్టులో ప్రతి నోడ్ గరిష్టంగా రెండు పిల్లలను మాత్రమే కలిగి ఉంటుంది మరియు చెట్టు ఎల్లప్పుడూ సమతుల్యంగా ఉండేలా రంగు నియమాలను (Red & Black rules) అనుసరిస్తుంది.
⚡ TreeSet యొక్క శక్తివంతమైన నావిగేషన్ మెథడ్స్:
first()&last(): సెట్లోని అత్యల్ప మరియు అత్యధిక విలువలను అందిస్తాయి.lower(E e)&higher(E e): నిర్దిష్ట ఎలిమెంట్ కంటే కచ్చితంగా చిన్నదైన లేదా పెద్దదైన విలువను శోధిస్తాయి.floor(E e)&ceiling(E e): ఎలిమెంట్ లేదా దానికి సమీపంలోని చిన్న/పెద్ద విలువను తీస్తాయి.subSet(from, to): ఒక నిర్దిష్ట పరిధిలోని సబ్-సెట్ను క్షణంలో రిటర్న్ చేస్తాయి.
4. ప్రాక్టికల్ కోడ్: SetDeduplication & TreeSet నావిగేషన్
క్రింది జావా ప్రోగ్రామ్లో డూప్లికేట్ యూజర్ ఐడీల నుండి యునిక్ ఐడీలను వేరు చేయడం (Deduplication), HashSet యొక్క ఆర్డర్లెస్ ప్రవర్తన, మరియు TreeSet ద్వారా ర్యాంకింగ్ స్కోర్ల నావిగేషన్ ఎలా పనిచేస్తుందో స్వయంగా పరిశీలించండి.
import java.util.*;
public class SetDeduplicationAndTreeSetBenchmark {
public static void main(String[] args) {
System.out.println("=== 1. HASHSET DEDUPLICATION DEMO ===");
// సిస్టమ్లోకి వచ్చిన లాగిన్ ఐడీలు (ఇందులో తీవ్రమైన డూప్లికేట్లు ఉన్నాయి)
List<String> incomingUserIds = List.of(
"USR-101", "USR-205", "USR-101", "USR-304",
"USR-205", "USR-500", "USR-101"
);
System.out.println("మొత్తం ఇన్కమింగ్ రిక్వెస్ట్లు: " + incomingUserIds.size());
// HashSet లోకి చేర్చడం ద్వారా క్షణంలో డూప్లికేట్లను తొలగించడం
Set<String> uniqueUsers = new HashSet<>(incomingUserIds);
System.out.println("డూప్లికేట్లు తొలగించబడిన యునిక్ యూజర్లు: " + uniqueUsers);
System.out.println("యునిక్ యూజర్ల సంఖ్య: " + uniqueUsers.size());
System.out.println("
=== 2. TREESET NAVIGABLE SET DEMO ===");
// గేమింగ్ లీడర్బోర్డ్ స్కోర్లు (ఆటోమేటిక్ సార్టింగ్ మరియు రేంజ్ క్వెరీలు)
TreeSet<Integer> highScores = new TreeSet<>();
highScores.addAll(List.of(850, 420, 990, 710, 310, 950, 600));
System.out.println("సహజంగా క్రమబద్ధీకరించబడిన స్కోర్లు: " + highScores);
System.out.println("అత్యల్ప స్కోరు (first): " + highScores.first());
System.out.println("అత్యధిక స్కోరు (last): " + highScores.last());
int targetCutoff = 710;
System.out.println("
--- టార్గెట్ స్కోర్ చుట్టూ నావిగేషన్: " + targetCutoff + " ---");
System.out.println("Target కంటే తక్కువైన స్కోరు (lower): " + highScores.lower(targetCutoff));
System.out.println("Target కు సమానమైన లేదా తక్కువైన స్కోరు (floor): " + highScores.floor(targetCutoff));
System.out.println("Target కు సమానమైన లేదా ఎక్కువైన స్కోరు (ceiling): " + highScores.ceiling(targetCutoff));
System.out.println("Target కంటే కచ్చితంగా ఎక్కువైన స్కోరు (higher): " + highScores.higher(targetCutoff));
// సబ్సెట్ రేంజ్ క్వెరీ: 400 నుండి 900 మధ్య స్కోర్లు
SortedSet<Integer> midTier = highScores.subSet(400, 900);
System.out.println("400 నుండి 900 మధ్య స్కోర్లు (subSet): " + midTier);
}
}
=== 1. HASHSET DEDUPLICATION DEMO === మొత్తం ఇన్కమింగ్ రిక్వెస్ట్లు: 7 డూప్లికేట్లు తొలగించబడిన యునిక్ యూజర్లు: [USR-101, USR-304, USR-500, USR-205] యునిక్ యూజర్ల సంఖ్య: 4 === 2. TREESET NAVIGABLE SET DEMO === సహజంగా క్రమబద్ధీకరించబడిన స్కోర్లు: [310, 420, 600, 710, 850, 950, 990] అత్యల్ప స్కోరు (first): 310 అత్యధిక స్కోరు (last): 990 --- టార్గెట్ స్కోర్ చుట్టూ నావిగేషన్: 710 --- Target కంటే తక్కువైన స్కోరు (lower): 600 Target కు సమానమైన లేదా తక్కువైన స్కోరు (floor): 710 Target కు సమానమైన లేదా ఎక్కువైన స్కోరు (ceiling): 710 Target కంటే కచ్చితంగా ఎక్కువైన స్కోరు (higher): 850 400 నుండి 900 మధ్య స్కోర్లు (subSet): [420, 600, 710, 850]
5. Map ఇంటర్ఫేస్ & Key-Value ఆర్కిటెక్చర్ (Map Interface Overview)
జావాలో java.util.Map అనేది Collection ఇంటర్ఫేస్ కుటుంబానికి చెందని ఒక ప్రత్యేకమైన ఇంటర్ఫేస్. ఎందుకంటే సాధారణ కలెక్షన్లు సింగిల్ ఎలిమెంట్లను (Single Elements) స్టోర్ చేస్తే, Map ఎల్లప్పుడూ కీ మరియు వ్యాల్యూ జంటలను (Key-Value Pairs - Map.Entry<K, V>) నిల్వ చేస్తుంది.
🔑 Map ఇంటర్ఫేస్ యొక్క 4 ప్రధాన సూత్రాలు:
- Keys must be unique: ఒక మ్యాప్లో ఒకే కీ రెండుసార్లు ఉండకూడదు. ఇప్పటికే ఉన్న కీతో మరో విలువ పెడితే పాత విలువ ఓవర్రైట్ అవుతుంది.
- Values can be duplicates: వేర్వేరు కీలు ఒకే సమానమైన విలువను కలిగి ఉండవచ్చు (ఉదాహరణకు, వేర్వేరు విద్యార్థి రోల్ నంబర్లకు ఒకే మార్కులు ఉండవచ్చు).
- View Collections: మ్యాప్ నుండి కీలను మాత్రమే చూడటానికి
keySet(), విలువలను చూడటానికిvalues(), మరియు జంటలను చూడటానికిentrySet()పద్ధతులను ఉపయోగిస్తాము. - Modern Java 8+ Methods:
getOrDefault(key, defaultVal),putIfAbsent(key, val),computeIfAbsent(key, mappingFn)లతో కోడ్ అత్యంత సంక్షిప్తంగా మారుతుంది.
6. HashMap ఇంటర్నల్స్: Buckets, Collisions, Treeification & Load Factor
ప్రపంచవ్యాప్తంగా ప్రతి జావా ఇంటర్వ్యూలో తప్పకుండా అడిగే ప్రశ్న: "How does HashMap work internally in Java?". జావా HashMap యొక్క అంతర్గత ఇంజినీరింగ్ నిజంగా ఒక అద్భుతం. దీనిని విడమరచి వివరంగా అర్థం చేసుకుందాం:
1. నోడ్ ఎరే & బకెట్ ఇండెక్స్ కాలిక్యులేషన్ (Bucket Array & Index Formula):
HashMap అంతర్గతంగా Node<K,V>[] table అనే ఎరేను కలిగి ఉంటుంది. డిఫాల్ట్గా ప్రారంభంలో ఈ ఎరే సైజు 16 బకెట్లు (Initial Capacity = 16) ఉంటుంది.
మనం map.put(key, value) అన్నప్పుడు క్రింది క్రమం జరుగుతుంది:
- కీ యొక్క
hashCode()ను కాల్ చేసి 32-బిట్ ఇంటిజర్ను లెక్కిస్తుంది. - బిట్వైస్ హాష్ స్ప్రెడింగ్ ఫంక్షన్ ద్వారా హైయర్ బిట్లను లోయర్ బిట్లలోకి మిక్స్ చేస్తుంది:
h = key.hashCode() ^ (h >>> 16). - బకెట్ ఇండెక్స్ను కనుగొనడానికి మోడ్యులో (%) బదులుగా అత్యంత వేగవంతమైన బిట్వైస్ AND ఆపరేషన్ను ఉపయోగిస్తుంది:
index = (n - 1) & hash(ఇక్కడ n అనేది బకెట్ల సైజు 16).
2. హాష్ కొలీజన్ అంటే ఏమిటి? (Hash Collision & Linked List Chaining):
రెండు వేర్వేరు కీలు అనుకోకుండా ఒకే బకెట్ ఇండెక్స్ను పొందినప్పుడు దానిని హాష్ కొలీజన్ (Hash Collision) అంటారు. ఉదాహరణకు "Aa" మరియు "BB" రెండింటి hashCode ఒక్కటే (2112). అటువంటి సందర్భంలో Java ఆ బకెట్లో ఒక Singly Linked List ను నిర్మించి కొత్త నోడ్ను చివరికి లింక్ చేస్తుంది.
3. Java 8 ట్రీయిఫికేషన్ (Treeification Threshold = 8):
Java 7 వరకు ఒకే బకెట్లో కొలీజన్లు పెరిగిపోతే లింక్డ్ లిస్ట్ పొడవైపోయి వెతకడానికి O(N) సమయం పట్టేది. దీనివల్ల హ్యాకర్లు ఉద్దేశపూర్వకంగా ఒకే హాష్కోడ్ వచ్చే కీలను పంపి సిస్టమ్ను స్లో చేసేవారు (Hash DOS Attack).
ఈ లోపాన్ని అధిగమించడానికి Java 8 లో ఆర్కిటెక్చర్ మార్చబడింది:
ఒక బకెట్లోని ఎలిమెంట్ల సంఖ్య TREEIFY_THRESHOLD = 8 కి చేరుకుని, మరియు మొత్తం HashMap కెపాసిటీ కనీసం MIN_TREEIFY_CAPACITY = 64 ఉంటే, జావా ఆ పొడవైన లింక్డ్ లిస్ట్ను ఒక Red-Black Self-Balancing Tree (TreeNode) గా మారుస్తుంది! దీనివలన చెత్త పరిస్థితిలో కూడా శోధన సమయం
O(N) నుండి అత్యంత వేగవంతమైన O(log N) కి పడిపోతుంది! ఆ తర్వాత ఎలిమెంట్లు తగ్గి 6 కి పడిపోతే (UNTREEIFY_THRESHOLD = 6), అది తిరిగి సాధారణ లింక్డ్ లిస్ట్గా మారిపోతుంది.
4. లోడ్ ఫ్యాక్టర్ & రీహాషింగ్ (Load Factor 0.75 & Re-hashing):
డిఫాల్ట్ లోడ్ ఫ్యాక్టర్ 0.75 గా ఉంటుంది. అంటే 16 బకెట్లలో 75% నిండినప్పుడు (అంటే 16 * 0.75 = 12 ఎలిమెంట్లు చేరగానే), బకెట్ ఎరే సైజును రెట్టింపు (16 నుండి 32 కి) చేసి, పాత ఎలిమెంట్లన్నింటినీ కొత్త బకెట్లలోకి రీ-డిస్ట్రిబ్యూట్ చేస్తుంది. దీనినే Rehashing అంటారు.
7. LinkedHashMap & LRU Cache మెకానిక్స్ (Least Recently Used Cache)
సాధారణ HashMap మనం ఎలిమెంట్లను ఏ క్రమంలో ఇన్సర్ట్ చేశామో ఆ క్రమాన్ని గుర్తుపెట్టుకోదు. మనకు O(1) హాష్ లుకప్తో పాటు ఇన్సర్షన్ క్రమం (Insertion Order) లేదా యాక్సెస్ క్రమం (Access Order) రెండూ కావాలంటే LinkedHashMap ను ఉపయోగిస్తాము.
LinkedHashMap తన ప్రతి నోడ్కు ముందు మరియు వెనుక పాయింటర్లను (before & after pointers) జోడించి ఒక డబుల్-లింక్డ్ లిస్ట్ను నిర్వహిస్తుంది. దీనిని ఉపయోగించి కేవలం removeEldestEntry() మెథడ్ను ఓవర్రైడ్ చేయడం ద్వారా మనం ప్రపంచ ప్రసిద్ధ LRU Cache (Least Recently Used Cache) ని కేవలం 5 లైన్లలో నిర్మించవచ్చు!
8. TreeMap & Custom Comparators (Sorted Navigation)
java.util.TreeMap అనేది NavigableMap ఇంటర్ఫేస్ను అమలు చేస్తుంది. ఇది కీలను ఎల్లప్పుడూ సార్ట్ చేసి ఉంచుతుంది. దీని కోసం కీ తప్పనిసరిగా Comparable ఇంటర్ఫేస్ను ఇంప్లిమెంట్ చేసి ఉండాలి, లేదా మనం కన్స్ట్రక్టర్లో స్వయంగా ఒక Comparator లాంబ్డాను పాస్ చేయాలి.
శోధన సమయం O(log N) అయినప్పటికీ, తేదీల ఆధారంగా క్రమబద్ధీకరించిన రిపోర్టులు, ధరల శ్రేణులు (Price Brackets), మరియు క్రమబద్ధీకరించిన ఆర్థిక ఖాతాల లెడ్జర్లలో TreeMap ఒక అత్యుత్తమ ఎంపిక.
9. ప్రాక్టికల్ కోడ్: HashMap Collisions & LRU Cache డెమో
ఈ సమగ్ర డెమోలో ఉద్దేశపూర్వకంగా ఒకే హాష్కోడ్ ఇచ్చే కస్టమ్ కీలను ఉపయోగించి HashMap కొలీజన్లను ఎలా తట్టుకుంటుందో, అలాగే LinkedHashMap తో 3 ఐటెమ్ల కెపాసిటీ గల LRU క్యాష్ ఎలా పాత వస్తువులను ఆటోమేటిక్గా తొలగిస్తుందో లైవ్గా చూడండి.
import java.util.*;
public class HashMapCollisionAndTreeificationDemo {
// ఉద్దేశపూర్వకంగా ఒకే స్థిరమైన హాష్కోడ్ (Zero) ఇచ్చే బ్యాడ్ కీ క్లాస్
static class BadHashKey implements Comparable<BadHashKey> {
final String keyName;
final int id;
public BadHashKey(String keyName, int id) {
this.keyName = keyName;
this.id = id;
}
@Override
public int hashCode() {
return 42; // ప్రతి ఆబ్జెక్టుకు ఒకే స్థిరమైన హాష్కోడ్! తీవ్రమైన కొలీజన్!
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof BadHashKey other)) return false;
return this.id == other.id && Objects.equals(this.keyName, other.keyName);
}
@Override
public int compareTo(BadHashKey o) {
return Integer.compare(this.id, o.id);
}
@Override
public String toString() {
return keyName + "(id=" + id + ")";
}
}
public static void main(String[] args) {
System.out.println("=== 1. HASH COLLISION & BUCKET CHAINING TEST ===");
Map<BadHashKey, String> collisionMap = new HashMap<>();
// 10 ఎలిమెంట్లను ఒకే బకెట్ ఇండెక్స్లోకి బలవంతంగా ఇన్సర్ట్ చేస్తున్నాం
for (int i = 1; i <= 10; i++) {
collisionMap.put(new BadHashKey("KeyItem", i), "Payload-Data-" + i);
}
System.out.println("మొత్తం ఇన్సర్ట్ చేయబడిన కొలీజన్ ఎలిమెంట్లు: " + collisionMap.size());
// హాష్కోడ్ ఒకటే అయినా equals() సరిగ్గా ఉండటం వల్ల సరైన డేటా లభిస్తుంది
BadHashKey searchKey = new BadHashKey("KeyItem", 7);
String foundValue = collisionMap.get(searchKey);
System.out.println("Search Key [KeyItem, id=7] లభించిన డేటా: " + foundValue);
System.out.println("
=== 2. LINKEDHASHMAP LRU CACHE (MAX 3 ITEMS) ===");
// 3 ఎంట్రీలు మించగానే అతి తక్కువగా వాడిన ఎంట్రీని ఆటోమేటిక్గా తొలగించే LRU క్యాష్
Map<String, String> lruCache = new LinkedHashMap<>(16, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {
return size() > 3; // పరిమితి 3 ఐటెమ్స్
}
};
lruCache.put("token1", "User-Balaji");
lruCache.put("token2", "User-Suresh");
lruCache.put("token3", "User-Kavitha");
System.out.println("ప్రారంభ క్యాష్ (3 ఐటెమ్స్): " + lruCache.keySet());
// token1 ను యాక్సెస్ చేస్తున్నాం (దీనివల్ల ఇది ఇటీవల వాడినదిగా మారి చివరికి వెళుతుంది)
lruCache.get("token1");
System.out.println("token1 ను యాక్సెస్ చేసిన తర్వాత ఆర్డర్: " + lruCache.keySet());
// ఇప్పుడు 4వ ఐటెమ్ను ఇన్సర్ట్ చేస్తున్నాం (అప్పుడు అతి పాతదైన token2 ఆటోమేటిక్గా తొలగిపోతుంది)
lruCache.put("token4", "User-Ramesh");
System.out.println("కొత్త token4 చేర్చిన తర్వాత క్యాష్: " + lruCache.keySet());
System.out.println("token2 తొలగించబడిందా? " + (!lruCache.containsKey("token2")));
}
}
=== 1. HASH COLLISION & BUCKET CHAINING TEST === మొత్తం ఇన్సర్ట్ చేయబడిన కొలీజన్ ఎలిమెంట్లు: 10 Search Key [KeyItem, id=7] లభించిన డేటా: Payload-Data-7 === 2. LINKEDHASHMAP LRU CACHE (MAX 3 ITEMS) === ప్రారంభ క్యాష్ (3 ఐటెమ్స్): [token1, token2, token3] token1 ను యాక్సెస్ చేసిన తర్వాత ఆర్డర్: [token2, token3, token1] కొత్త token4 చేర్చిన తర్వాత క్యాష్: [token3, token1, token4] token2 తొలగించబడిందా? true
10. క్యాప్స్టోన్ ప్రాజెక్ట్: Enterprise User Session & Permission Cache Engine
ఒక వాస్తవ ఎంటర్ప్రైజ్ క్లౌడ్ అప్లికేషన్లో (ఉదాహరణకు AWS Cognito లేదా Spring Security గేట్వేలో), యూజర్ అనుమతులు (Role Permissions) డూప్లికేట్ కాకుండా Set లో భద్రపరచాలి, అలాగే లాగిన్ సెషన్లను వేగంగా ధృవీకరించడానికి Map క్యాష్ అవసరం. ఈ క్రింది ఆర్కిటెక్చర్లో జావా రికార్డ్స్, Set మరియు Map లను కలిపి పూర్తి స్థాయి సెక్యూరిటీ ఇంజిన్ను నిర్మించాం:
import java.time.Instant;
import java.util.*;
public class EnterpriseUserSessionCacheEngine {
// ఆధునిక Java 21 ఇమ్యూటబుల్ రికార్డ్: యూజర్ సెషన్ వివరాలు
public record UserSession(
String sessionId,
String username,
String role,
Set<String> permissions,
Instant createdAt
) {}
// ఎంటర్ప్రైజ్ లెవల్ సెషన్ అడ్మినిస్ట్రేటర్
public static class SessionRegistry {
// Fast O(1) Session Lookup Cache
private final Map<String, UserSession> activeSessions = new HashMap<>();
// యూజర్ పేరు ఆధారంగా వారి అన్ని యాక్టివ్ సెషన్లను ట్రాక్ చేసే మ్యాప్
private final Map<String, Set<String>> userToSessionIndex = new HashMap<>();
// 1. కొత్త సెషన్ను నమోదు చేయడం
public void registerSession(String username, String role, Set<String> incomingRoles) {
String sessionId = "SESS-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
// HashSet ద్వారా రోల్ పర్మిషన్లలో ఏ విధమైన డూప్లికేట్లు లేకుండా చేయడం
Set<String> sanitizedPermissions = new HashSet<>(incomingRoles);
UserSession session = new UserSession(sessionId, username, role, sanitizedPermissions, Instant.now());
activeSessions.put(sessionId, session);
// Java 8 computeIfAbsent తో యూజర్ ఇండెక్స్ను అప్డేట్ చేయడం
userToSessionIndex.computeIfAbsent(username, k -> new HashSet<>()).add(sessionId);
System.out.printf("✅ [REGISTERED] User: %-10s | Session: %s | Permissions: %s%n",
username, sessionId, sanitizedPermissions);
}
// 2. సెషన్ టోకెన్ను ప్రామాణీకరించడం & అనుమతిని పరిశీలించడం
public boolean authorize(String sessionId, String requiredPermission) {
UserSession session = activeSessions.get(sessionId);
if (session == null) {
System.out.println("❌ [DENIED] చెల్లని సెషన్ ఐడీ: " + sessionId);
return false;
}
boolean hasAccess = session.permissions().contains(requiredPermission);
System.out.printf("🔍 [AUTH CHECK] User: %-10s | Req: %-15s | Granted: %b%n",
session.username(), requiredPermission, hasAccess);
return hasAccess;
}
// 3. యూజర్ లాగౌట్ లేదా ఫోర్స్ ఎక్స్పైరీ
public void revokeUserSessions(String username) {
Set<String> sessions = userToSessionIndex.remove(username);
if (sessions != null) {
for (String sid : sessions) {
activeSessions.remove(sid);
}
System.out.println("🚫 [REVOKED] యూజర్ '" + username + "' యొక్క " + sessions.size() + " సెషన్లు క్లోజ్ చేయబడ్డాయి.");
}
}
public void printTelemetry() {
System.out.println("
📊 --- REAL-TIME ENGINE TELEMETRY ---");
System.out.println("యాక్టివ్ సెషన్ల మొత్తం సంఖ్య (Total Active Sessions): " + activeSessions.size());
System.out.println("యాక్టివ్ యూజర్ల సంఖ్య (Active Distinct Users): " + userToSessionIndex.size());
}
}
public static void main(String[] args) {
System.out.println("=== ENTERPRISE CLOUD AUTH & SESSION ENGINE ===");
SessionRegistry authEngine = new SessionRegistry();
// డూప్లికేట్ పర్మిషన్లతో కూడిన ఇన్కమింగ్ డేటా
Set<String> adminPerms = new HashSet<>(List.of("READ", "WRITE", "DELETE", "READ", "AUDIT"));
Set<String> userPerms = new HashSet<>(List.of("READ", "READ", "PROFILE_EDIT"));
// సెషన్లను రిజిస్టర్ చేయడం
authEngine.registerSession("balaji_lead", "ADMIN", adminPerms);
authEngine.registerSession("balaji_lead", "ADMIN", adminPerms); // రెండవ పరికరం నుండి లాగిన్
authEngine.registerSession("kavitha_dev", "USER", userPerms);
authEngine.printTelemetry();
// మొదటి యాక్టివ్ సెషన్ను వెతకడం
String sampleAdminSession = authEngine.activeSessions.keySet().iterator().next();
System.out.println("
--- AUTHORIZATION AUDIT ---");
authEngine.authorize(sampleAdminSession, "DELETE");
authEngine.authorize(sampleAdminSession, "SUPER_ROOT_KILL");
// యూజర్ సెషన్లను రద్దు చేయడం
System.out.println("
--- LOGOUT OPERATION ---");
authEngine.revokeUserSessions("balaji_lead");
authEngine.printTelemetry();
}
}
=== ENTERPRISE CLOUD AUTH & SESSION ENGINE === ✅ [REGISTERED] User: balaji_lead | Session: SESS-4D9A1F82 | Permissions: [AUDIT, READ, WRITE, DELETE] ✅ [REGISTERED] User: balaji_lead | Session: SESS-C71E0B34 | Permissions: [AUDIT, READ, WRITE, DELETE] ✅ [REGISTERED] User: kavitha_dev | Session: SESS-98B22A10 | Permissions: [READ, PROFILE_EDIT] 📊 --- REAL-TIME ENGINE TELEMETRY --- యాక్టివ్ సెషన్ల మొత్తం సంఖ్య (Total Active Sessions): 3 యాక్టివ్ యూజర్ల సంఖ్య (Active Distinct Users): 2 --- AUTHORIZATION AUDIT --- 🔍 [AUTH CHECK] User: balaji_lead | Req: DELETE | Granted: true 🔍 [AUTH CHECK] User: balaji_lead | Req: SUPER_ROOT_KILL | Granted: false --- LOGOUT OPERATION --- 🚫 [REVOKED] యూజర్ 'balaji_lead' యొక్క 2 సెషన్లు క్లోజ్ చేయబడ్డాయి. 📊 --- REAL-TIME ENGINE TELEMETRY --- యాక్టివ్ సెషన్ల మొత్తం సంఖ్య (Total Active Sessions): 1 యాక్టివ్ యూజర్ల సంఖ్య (Active Distinct Users): 1
11. ప్రొడక్షన్ పిట్ఫాల్స్ & ఆంటీ-పాటర్న్స్ (5 Critical Production Traps)
1. The Broken equals & hashCode Contract (తీవ్రమైన మెమరీ లీక్)
ఎట్టి పరిస్థితుల్లోనూ కేవలం equals() ను మాత్రమే ఓవర్రైడ్ చేసి hashCode() ను విస్మరించకూడదు! అలా చేస్తే రెండు సమానమైన ఆబ్జెక్టులు వేర్వేరు హాష్కోడ్లను పొంది వేర్వేరు బకెట్లలో పడతాయి. ఫలితంగా map.get(key) ఎప్పటికీ null రిటర్న్ చేస్తుంది. ఆధునిక జావాలో ఎల్లప్పుడూ Java 14+ Record ను ఉపయోగించండి లేదా IDE సహాయంతో రెండింటినీ కలిపి జనరేట్ చేయండి.
2. Mutable Objects as Keys in HashMap (మ్యుటబుల్ కీ ట్రాప్)
ఒక కస్టమ్ ఆబ్జెక్ట్ను HashMap లో కీగా పెట్టిన తర్వాత, దానిలోని ఫీల్డ్లను మార్చకూడదు (Do not mutate key fields). ఫీల్డ్ మారితే దాని hashCode() మారిపోతుంది. కానీ అది పాత బకెట్లోనే ఉండిపోతుంది! ఫలితంగా ఆబ్జెక్ట్ మ్యాప్లోనే ఉన్నప్పటికీ map.get() ద్వారా దాన్ని ఎప్పటికీ వెతకలేము! కీలుగా ఎల్లప్పుడూ String, Integer, లేదా ఇమ్యూటబుల్ రికార్డులనే వాడాలి.
3. Modifying Collections during Iteration (ConcurrentModificationException)
ఫర్-ఈచ్ లూప్ for (String key : map.keySet()) లో తిరుగుతున్నప్పుడు map.remove(key) చేయకూడదు. ఇది తక్షణమే క్రాష్ అవుతుంది. ఎలిమెంట్లను తొలగించాలంటే ఎల్లప్పుడూ iterator.remove() లేదా Java 8 removeIf() ని మాత్రమే వాడాలి.
4. Multithreaded HashMap Corruption (Deadlock Trap)
బహుళ థ్రెడ్లు (Multi-threading) ఉన్నప్పుడు సాధారణ HashMap ను ఎప్పుడూ వాడకూడదు. రీహాషింగ్ సమయంలో సర్క్యులర్ లింక్ ఏర్పడి CPU 100% కి చేరి అప్లికేషన్ హ్యాంగ్ అయ్యే ప్రమాదం ఉంది. మల్టీ-థ్రెడెడ్ ఎన్విరాన్మెంట్లో తప్పనిసరిగా ConcurrentHashMap నే వాడాలి.
5. Inserting Null into TreeSet / TreeMap
HashSet మరియు HashMap లలో గరిష్టంగా ఒక null కీని అనుమతిస్తారు. కానీ TreeSet లేదా TreeMap లలో null ను ఇన్సర్ట్ చేయడానికి ప్రయత్నిస్తే, అది ఇతరులతో పోల్చడానికి null.compareTo() ని పిలిచి తక్షణమే NullPointerException ను విసురుతుంది!
12. ఇంటర్వ్యూ క్విజ్ & సెల్ఫ్-టెస్ట్ (5 Deep Interview Practice Quizzes)
సెట్ మరియు మ్యాప్ ఆర్కిటెక్చర్పై మీకున్న అవగాహనను ఈ 5 లోతైన ప్రాక్టికల్ ప్రశ్నలతో పరీక్షించుకోండి. సమాధానం కోసం ప్రశ్నపై క్లిక్ చేయండి:
ప్రశ్న 1: HashMap అంతర్గతంగా డేటాను ఎలా స్టోర్ చేస్తుంది? Java 8 లో వచ్చిన కీలకమైన మార్పు ఏమిటి?
సమాధానం: HashMap అంతర్గతంగా Node<K,V>[] అనే బకెట్ ఎరేను ఉపయోగిస్తుంది. కీ యొక్క hashCode() ఆధారంగా బకెట్ ఇండెక్స్ నిర్ణయించబడుతుంది. ఒకే బకెట్లో కొలీజన్లు వస్తే Java 7 వరకు సింగ్లీ లింక్డ్ లిస్ట్గా భద్రపరిచేవారు. అయితే Java 8 లో ఒక బకెట్లోని ఎలిమెంట్లు 8 దాటితే (TREEIFY_THRESHOLD = 8), ఆ లింక్డ్ లిస్ట్ ఒక Red-Black Balanced Tree గా మారుతుంది. దీనివల్ల లుకప్ సమయం O(N) నుండి O(log N) కి వేగవంతం అవుతుంది.
ప్రశ్న 2: equals() మరియు hashCode() కాంట్రాక్ట్ నియమాలు ఏమిటి? ఉల్లంఘిస్తే ఏమవుతుంది?
సమాధానం: నియమం 1: రెండు ఆబ్జెక్టులు equals() ప్రకారం సమానమైతే, వాటి hashCode() లు కూడా తప్పనిసరిగా సమానమై ఉండాలి. నియమం 2: రెండు ఆబ్జెక్టుల హాష్కోడ్లు సమానమైనంత మాత్రాన అవి equals కానవసరం లేదు (దానినే కొలీజన్ అంటారు). ఈ కాంట్రాక్ట్ను ఉల్లంఘిస్తే, HashMap లో ఒక ఆబ్జెక్ట్ను స్టోర్ చేసిన తర్వాత తిరిగి get() చేసినప్పుడు అది వేరొక బకెట్లో వెతికి null అని ఇస్తుంది.
ప్రశ్న 3: HashSet అంతర్గతంగా ఎలా ఇంప్లిమెంట్ చేయబడింది?
సమాధానం: HashSet స్వతంత్ర డేటా స్ట్రక్చర్ కాదు; అది ఒక HashMap ఆబ్జెక్ట్ను రాపర్ (Wrapper) గా కలిగి ఉంటుంది. మనం సెట్లో యాడ్ చేసే ప్రతి ఎలిమెంట్ HashMap యొక్క Key గా కూర్చుంటుంది, మరియు దానికి అసోసియేట్ అయ్యే విలువగా ఒక స్థిరమైన డమ్మీ ఆబ్జెక్ట్ (private static final Object PRESENT = new Object();) స్టోర్ అవుతుంది.
ప్రశ్న 4: HashMap, LinkedHashMap, మరియు TreeMap ల మధ్య తేడా ఏమిటి?
సమాధానం: 1) HashMap: అత్యంత వేగవంతమైనది (O(1)), కానీ ఎలిమెంట్ల క్రమం (Ordering) ఏదీ ఉండదు. 2) LinkedHashMap: O(1) వేగంతో పాటు ఇన్సర్షన్ ఆర్డర్ లేదా యాక్సెస్ ఆర్డర్ (LRU) ను డబుల్ లింక్డ్ లిస్ట్ ద్వారా కాపాడుతుంది. 3) TreeMap: రెడ్-బ్లాక్ ట్రీ ద్వారా కీలను ఎల్లప్పుడూ ఆరోహణ క్రమంలో (Natural/Comparator Order) క్రమబద్ధీకరిస్తుంది (O(log N)).
ప్రశ్న 5: HashMap లో లోడ్ ఫ్యాక్టర్ (0.75) మరియు రీహాషింగ్ (Rehashing) అంటే ఏమిటి?
సమాధానం: Load Factor (డిఫాల్ట్ 0.75) అనేది బకెట్లు ఎంత శాతం నిండినప్పుడు ఎరే సైజును పెంచాలో నిర్ణయించే నిష్పత్తి. 16 బకెట్లలో 12 నిండగానే (16 * 0.75 = 12), HashMap తన బకెట్ కెపాసిటీని రెట్టింపు చేస్తుంది (16 నుండి 32 కి). ఆ తర్వాత ఇప్పటికే ఉన్న నోడ్లను కొత్త ఇండెక్స్లకు తిరిగి లెక్కించి అమర్చడాన్ని Rehashing అంటారు.