Table of Contents
- Introduction
- LinkedHashSet vs HashSet vs TreeSet
- Internal Implementation
- Insertion Order Preservation
- Access Order (LRU Cache)
- Common Operations and Examples
- Performance Characteristics
- Use Cases
- Best Practices
- Complete Examples
Introduction
LinkedHashSet is a hash table and linked list implementation of the Set interface with predictable iteration order. It maintains a doubly-linked list running through all its entries, which defines the iteration ordering.
Key Characteristics:
- Insertion Order: Maintains the order in which elements were inserted
- No Duplicates: Like all Set implementations, doesn't allow duplicates
- Null Elements: Allows one null element
- Performance: Similar to HashSet but with slightly lower performance due to maintaining linked list
LinkedHashSet vs HashSet vs TreeSet
Comparison Table:
| Feature | LinkedHashSet | HashSet | TreeSet |
|---|---|---|---|
| Ordering | Insertion order | No order | Natural sorting / Custom comparator |
| Implementation | Hash table + Linked list | Hash table | Red-Black tree |
| Performance | O(1) for basic operations | O(1) for basic operations | O(log n) for basic operations |
| Null elements | Allows one null | Allows one null | Doesn't allow null (if natural ordering) |
| Use Case | Maintain insertion order | General purpose set | Sorted set requirements |
Code Comparison:
import java.util.*; public class SetComparison { public static void main(String[] args) { // HashSet - No order guarantee Set<String> hashSet = new HashSet<>(); hashSet.add("Apple"); hashSet.add("Banana"); hashSet.add("Cherry"); hashSet.add("Date"); System.out.println("HashSet: " + hashSet); // Order not guaranteed // LinkedHashSet - Insertion order maintained Set<String> linkedHashSet = new LinkedHashSet<>(); linkedHashSet.add("Apple"); linkedHashSet.add("Banana"); linkedHashSet.add("Cherry"); linkedHashSet.add("Date"); System.out.println("LinkedHashSet: " + linkedHashSet); // Insertion order // TreeSet - Natural sorting order Set<String> treeSet = new TreeSet<>(); treeSet.add("Apple"); treeSet.add("Banana"); treeSet.add("Cherry"); treeSet.add("Date"); System.out.println("TreeSet: " + treeSet); // Sorted order } } Output:
HashSet: [Apple, Cherry, Date, Banana] # Random order LinkedHashSet: [Apple, Banana, Cherry, Date] # Insertion order TreeSet: [Apple, Banana, Cherry, Date] # Sorted order
Internal Implementation
Data Structure:
LinkedHashSet uses:
- HashMap for storage (like HashSet)
- Doubly-linked list for maintaining order
// Simplified internal structure public class LinkedHashSet<E> extends HashSet<E> implements Set<E> { // Internal LinkedHashMap that maintains order private transient LinkedHashMap<E, Object> map; // Dummy value to associate with Object in backing Map private static final Object PRESENT = new Object(); public LinkedHashSet() { map = new LinkedHashMap<>(); } public boolean add(E e) { return map.put(e, PRESENT) == null; } // Other methods delegate to the LinkedHashMap } Insertion Order Preservation
Basic Insertion Order Example:
import java.util.*; public class InsertionOrderExample { public static void main(String[] args) { LinkedHashSet<String> fruits = new LinkedHashSet<>(); // Add elements in specific order fruits.add("Apple"); fruits.add("Banana"); fruits.add("Cherry"); fruits.add("Date"); fruits.add("Elderberry"); System.out.println("Initial order: " + fruits); // Adding duplicate - order remains same fruits.add("Apple"); System.out.println("After adding duplicate: " + fruits); // Adding new element - goes to end fruits.add("Fig"); System.out.println("After adding new element: " + fruits); // Remove and add - goes to end fruits.remove("Cherry"); fruits.add("Cherry"); System.out.println("After remove and add: " + fruits); } } Output:
Initial order: [Apple, Banana, Cherry, Date, Elderberry] After adding duplicate: [Apple, Banana, Cherry, Date, Elderberry] After adding new element: [Apple, Banana, Cherry, Date, Elderberry, Fig] After remove and add: [Apple, Banana, Date, Elderberry, Fig, Cherry]
Maintaining Order with Custom Objects:
import java.util.*; class Student { private int id; private String name; public Student(int id, String name) { this.id = id; this.name = name; } // Important: Override equals and hashCode for proper Set behavior @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Student student = (Student) o; return id == student.id; } @Override public int hashCode() { return Objects.hash(id); } @Override public String toString() { return "Student{id=" + id + ", name='" + name + "'}"; } } public class CustomObjectOrder { public static void main(String[] args) { LinkedHashSet<Student> students = new LinkedHashSet<>(); students.add(new Student(101, "Alice")); students.add(new Student(102, "Bob")); students.add(new Student(103, "Charlie")); students.add(new Student(104, "Diana")); System.out.println("Students in insertion order:"); for (Student student : students) { System.out.println(student); } // Adding duplicate student (same ID) students.add(new Student(102, "Robert")); // Won't be added System.out.println("\nAfter trying to add duplicate:"); for (Student student : students) { System.out.println(student); } } } Output:
Students in insertion order: Student{id=101, name='Alice'} Student{id=102, name='Bob'} Student{id=103, name='Charlie'} Student{id=104, name='Diana'} After trying to add duplicate: Student{id=101, name='Alice'} Student{id=102, name='Bob'} Student{id=103, name='Charlie'} Student{id=104, name='Diana'} Access Order (LRU Cache)
While LinkedHashSet itself doesn't support access order, its backing implementation LinkedHashMap does. We can create an LRU Cache using access order.
LRU Cache Implementation:
import java.util.*; public class LRUCache<K, V> extends LinkedHashMap<K, V> { private final int capacity; public LRUCache(int capacity) { // accessOrder = true means access order, false means insertion order super(capacity, 0.75f, true); this.capacity = capacity; } @Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { return size() > capacity; } public static void main(String[] args) { LRUCache<Integer, String> cache = new LRUCache<>(3); cache.put(1, "Apple"); cache.put(2, "Banana"); cache.put(3, "Cherry"); System.out.println("Initial cache: " + cache.keySet()); // Access element 2 - moves to end (most recently used) cache.get(2); System.out.println("After accessing key 2: " + cache.keySet()); // Access element 1 - moves to end cache.get(1); System.out.println("After accessing key 1: " + cache.keySet()); // Add new element - removes least recently used (key 3) cache.put(4, "Date"); System.out.println("After adding key 4: " + cache.keySet()); } } Output:
Initial cache: [1, 2, 3] After accessing key 2: [1, 3, 2] After accessing key 1: [3, 2, 1] After adding key 4: [2, 1, 4]
Common Operations and Examples
1. Basic Operations:
import java.util.*; public class BasicOperations { public static void main(String[] args) { LinkedHashSet<Integer> numbers = new LinkedHashSet<>(); // Add elements numbers.add(10); numbers.add(20); numbers.add(30); numbers.add(40); numbers.add(50); System.out.println("After adding: " + numbers); // Check contains System.out.println("Contains 30: " + numbers.contains(30)); System.out.println("Contains 35: " + numbers.contains(35)); // Remove element numbers.remove(20); System.out.println("After removing 20: " + numbers); // Size and empty check System.out.println("Size: " + numbers.size()); System.out.println("Is empty: " + numbers.isEmpty()); // Iteration maintains order System.out.print("Iteration: "); for (Integer num : numbers) { System.out.print(num + " "); } System.out.println(); // Convert to array (maintains order) Integer[] array = numbers.toArray(new Integer[0]); System.out.println("Array: " + Arrays.toString(array)); } } Output:
After adding: [10, 20, 30, 40, 50] Contains 30: true Contains 35: false After removing 20: [10, 30, 40, 50] Size: 4 Is empty: false Iteration: 10 30 40 50 Array: [10, 30, 40, 50]
2. Set Operations with Order Preservation:
import java.util.*; public class SetOperations { public static void main(String[] args) { LinkedHashSet<String> set1 = new LinkedHashSet<>( Arrays.asList("A", "B", "C", "D", "E")); LinkedHashSet<String> set2 = new LinkedHashSet<>( Arrays.asList("C", "D", "E", "F", "G")); System.out.println("Set 1: " + set1); System.out.println("Set 2: " + set2); // Union - maintains order from first set, then second LinkedHashSet<String> union = new LinkedHashSet<>(set1); union.addAll(set2); System.out.println("Union: " + union); // Intersection - maintains order from first set LinkedHashSet<String> intersection = new LinkedHashSet<>(set1); intersection.retainAll(set2); System.out.println("Intersection: " + intersection); // Difference - maintains order from first set LinkedHashSet<String> difference = new LinkedHashSet<>(set1); difference.removeAll(set2); System.out.println("Difference (set1 - set2): " + difference); } } Output:
Set 1: [A, B, C, D, E] Set 2: [C, D, E, F, G] Union: [A, B, C, D, E, F, G] Intersection: [C, D, E] Difference (set1 - set2): [A, B]
3. Removing Duplicates While Preserving Order:
import java.util.*; public class RemoveDuplicates { public static <T> List<T> removeDuplicatesPreserveOrder(List<T> list) { LinkedHashSet<T> set = new LinkedHashSet<>(list); return new ArrayList<>(set); } public static void main(String[] args) { List<String> namesWithDuplicates = Arrays.asList( "Alice", "Bob", "Charlie", "Alice", "David", "Bob", "Eve" ); System.out.println("Original list: " + namesWithDuplicates); List<String> uniqueNames = removeDuplicatesPreserveOrder(namesWithDuplicates); System.out.println("After removing duplicates: " + uniqueNames); // Example with integers List<Integer> numbersWithDuplicates = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5); System.out.println("Original numbers: " + numbersWithDuplicates); List<Integer> uniqueNumbers = removeDuplicatesPreserveOrder(numbersWithDuplicates); System.out.println("After removing duplicates: " + uniqueNumbers); } } Output:
Original list: [Alice, Bob, Charlie, Alice, David, Bob, Eve] After removing duplicates: [Alice, Bob, Charlie, David, Eve] Original numbers: [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5] After removing duplicates: [3, 1, 4, 5, 9, 2, 6]
Performance Characteristics
Time Complexity:
| Operation | Time Complexity |
|---|---|
| add() | O(1) |
| remove() | O(1) |
| contains() | O(1) |
| next() (iteration) | O(1) |
| Size | O(1) |
Memory Overhead:
- Slightly more memory than HashSet due to maintaining linked list
- Each element has additional previous/next pointers
Performance Comparison Example:
import java.util.*; public class PerformanceComparison { public static void main(String[] args) { final int SIZE = 100000; // HashSet Set<Integer> hashSet = new HashSet<>(); long startTime = System.nanoTime(); for (int i = 0; i < SIZE; i++) { hashSet.add(i); } long hashSetTime = System.nanoTime() - startTime; // LinkedHashSet Set<Integer> linkedHashSet = new LinkedHashSet<>(); startTime = System.nanoTime(); for (int i = 0; i < SIZE; i++) { linkedHashSet.add(i); } long linkedHashSetTime = System.nanoTime() - startTime; // TreeSet Set<Integer> treeSet = new TreeSet<>(); startTime = System.nanoTime(); for (int i = 0; i < SIZE; i++) { treeSet.add(i); } long treeSetTime = System.nanoTime() - startTime; System.out.println("Performance Comparison (nanoseconds):"); System.out.println("HashSet: " + hashSetTime); System.out.println("LinkedHashSet: " + linkedHashSetTime); System.out.println("TreeSet: " + treeSetTime); System.out.println("LinkedHashSet is " + (linkedHashSetTime - hashSetTime) + " ns slower than HashSet"); } } Use Cases
1. Maintaining Insertion Order:
import java.util.*; public class TaskManager { private LinkedHashSet<String> completedTasks; public TaskManager() { completedTasks = new LinkedHashSet<>(); } public void completeTask(String task) { completedTasks.add(task); System.out.println("Task completed: " + task); } public void displayCompletedTasks() { System.out.println("Tasks completed in order:"); int count = 1; for (String task : completedTasks) { System.out.println(count++ + ". " + task); } } public static void main(String[] args) { TaskManager manager = new TaskManager(); manager.completeTask("Design database schema"); manager.completeTask("Implement user authentication"); manager.completeTask("Write unit tests"); manager.completeTask("Deploy to production"); // Try to complete duplicate task manager.completeTask("Implement user authentication"); manager.displayCompletedTasks(); } } Output:
Task completed: Design database schema Task completed: Implement user authentication Task completed: Write unit tests Task completed: Deploy to production Task completed: Implement user authentication Tasks completed in order: 1. Design database schema 2. Implement user authentication 3. Write unit tests 4. Deploy to production
2. URL History in Web Browser:
import java.util.*; public class BrowserHistory { private LinkedHashSet<String> history; private final int maxHistorySize; public BrowserHistory(int maxHistorySize) { this.maxHistorySize = maxHistorySize; this.history = new LinkedHashSet<>(maxHistorySize, 0.75f); } public void visitUrl(String url) { if (history.size() >= maxHistorySize) { // Remove oldest entry String oldest = history.iterator().next(); history.remove(oldest); } history.add(url); } public void displayHistory() { System.out.println("Browser History (newest last):"); int count = 1; for (String url : history) { System.out.println(count++ + ". " + url); } } public static void main(String[] args) { BrowserHistory browser = new BrowserHistory(5); browser.visitUrl("https://www.google.com"); browser.visitUrl("https://www.github.com"); browser.visitUrl("https://www.stackoverflow.com"); browser.visitUrl("https://www.reddit.com"); browser.visitUrl("https://www.twitter.com"); browser.displayHistory(); // Visit existing URL - moves to end browser.visitUrl("https://www.github.com"); browser.displayHistory(); // Add new URL when full browser.visitUrl("https://www.linkedin.com"); browser.displayHistory(); } } Output:
Browser History (newest last): 1. https://www.google.com 2. https://www.github.com 3. https://www.stackoverflow.com 4. https://www.reddit.com 5. https://www.twitter.com Browser History (newest last): 1. https://www.google.com 2. https://www.stackoverflow.com 3. https://www.reddit.com 4. https://www.twitter.com 5. https://www.github.com Browser History (newest last): 1. https://www.stackoverflow.com 2. https://www.reddit.com 3. https://www.twitter.com 4. https://www.github.com 5. https://www.linkedin.com
3. Recent Items List:
import java.util.*; public class RecentItems<T> { private LinkedHashSet<T> items; private final int capacity; public RecentItems(int capacity) { this.capacity = capacity; this.items = new LinkedHashSet<>(capacity, 0.75f); } public void addItem(T item) { if (items.contains(item)) { items.remove(item); } else if (items.size() >= capacity) { // Remove least recent (first element) T first = items.iterator().next(); items.remove(first); } items.add(item); } public List<T> getRecentItems() { return new ArrayList<>(items); } public List<T> getRecentItems(int count) { List<T> result = new ArrayList<>(); Iterator<T> iterator = items.iterator(); for (int i = 0; i < count && iterator.hasNext(); i++) { result.add(iterator.next()); } return result; } public static void main(String[] args) { RecentItems<String> recentFiles = new RecentItems<>(5); recentFiles.addItem("document1.txt"); recentFiles.addItem("image.png"); recentFiles.addItem("spreadsheet.xlsx"); recentFiles.addItem("presentation.ppt"); recentFiles.addItem("notes.txt"); System.out.println("Recent files: " + recentFiles.getRecentItems()); // Add existing file - moves to end recentFiles.addItem("document1.txt"); System.out.println("After reopening document1: " + recentFiles.getRecentItems()); // Add new file when full recentFiles.addItem("config.properties"); System.out.println("After adding config: " + recentFiles.getRecentItems()); // Get only 3 most recent System.out.println("3 most recent: " + recentFiles.getRecentItems(3)); } } Output:
Recent files: [document1.txt, image.png, spreadsheet.xlsx, presentation.ppt, notes.txt] After reopening document1: [image.png, spreadsheet.xlsx, presentation.ppt, notes.txt, document1.txt] After adding config: [spreadsheet.xlsx, presentation.ppt, notes.txt, document1.txt, config.properties] 3 most recent: [spreadsheet.xlsx, presentation.ppt, notes.txt]
Best Practices
1. Choose the Right Set Implementation:
// Use LinkedHashSet when: // - You need to maintain insertion order // - You need predictable iteration order // - You need both Set behavior and order // Use HashSet when: // - Order doesn't matter // - Maximum performance needed // Use TreeSet when: // - You need sorted order // - You need range operations
2. Proper equals() and hashCode():
class Product { private String id; private String name; public Product(String id, String name) { this.id = id; this.name = name; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Product)) return false; Product product = (Product) o; return Objects.equals(id, product.id); } @Override public int hashCode() { return Objects.hash(id); } // Always implement toString() for debugging @Override public String toString() { return "Product{id='" + id + "', name='" + name + "'}"; } } 3. Initial Capacity and Load Factor:
public class LinkedHashSetConfiguration { public static void main(String[] args) { // Default constructor - initial capacity 16, load factor 0.75 LinkedHashSet<String> defaultSet = new LinkedHashSet<>(); // Specify initial capacity LinkedHashSet<String> capacitySet = new LinkedHashSet<>(100); // Specify initial capacity and load factor LinkedHashSet<String> configuredSet = new LinkedHashSet<>(100, 0.6f); // Create from existing collection (maintains order) List<String> list = Arrays.asList("A", "B", "C", "A", "D"); LinkedHashSet<String> fromCollection = new LinkedHashSet<>(list); System.out.println("From collection: " + fromCollection); } } Complete Examples
Complete Working Example:
import java.util.*; public class LinkedHashSetCompleteExample { public static void main(String[] args) { System.out.println("=== LinkedHashSet Complete Example ===\n"); // Example 1: Basic Order Preservation basicOrderExample(); // Example 2: Custom Objects with Order customObjectsExample(); // Example 3: Practical Use Case - Shopping Cart shoppingCartExample(); // Example 4: Advanced - LRU Cache Simulation lruCacheSimulation(); } public static void basicOrderExample() { System.out.println("1. Basic Order Preservation:"); LinkedHashSet<String> colors = new LinkedHashSet<>(); // Add colors in specific order String[] colorArray = {"Red", "Blue", "Green", "Yellow", "Purple", "Blue", "Red"}; for (String color : colorArray) { colors.add(color); System.out.println("Added: " + color + " -> Set: " + colors); } System.out.println("Final order: " + colors); System.out.println(); } public static void customObjectsExample() { System.out.println("2. Custom Objects with Order:"); LinkedHashSet<Employee> employees = new LinkedHashSet<>(); employees.add(new Employee(101, "John Doe", "Engineering")); employees.add(new Employee(102, "Jane Smith", "Marketing")); employees.add(new Employee(103, "Bob Johnson", "Sales")); employees.add(new Employee(101, "John Doe", "HR")); // Duplicate ID System.out.println("Employees in insertion order:"); for (Employee emp : employees) { System.out.println(emp); } System.out.println(); } public static void shoppingCartExample() { System.out.println("3. Shopping Cart Example:"); ShoppingCart cart = new ShoppingCart(); cart.addItem("Laptop", 999.99); cart.addItem("Mouse", 25.99); cart.addItem("Keyboard", 75.50); cart.addItem("Mouse", 25.99); // Duplicate cart.addItem("Monitor", 299.99); cart.displayCart(); cart.removeItem("Mouse"); cart.displayCart(); System.out.println("Total: $" + cart.getTotal()); System.out.println(); } public static void lruCacheSimulation() { System.out.println("4. LRU Cache Simulation:"); LRUSimulation cache = new LRUSimulation(3); cache.access("A"); cache.access("B"); cache.access("C"); cache.displayCache(); cache.access("A"); // Move A to most recent cache.displayCache(); cache.access("D"); // Should remove B (least recently used) cache.displayCache(); cache.access("C"); // Move C to most recent cache.displayCache(); } } // Supporting classes class Employee { private int id; private String name; private String department; public Employee(int id, String name, String department) { this.id = id; this.name = name; this.department = department; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Employee)) return false; Employee employee = (Employee) o; return id == employee.id; } @Override public int hashCode() { return Objects.hash(id); } @Override public String toString() { return String.format("Employee[ID=%d, Name=%-12s, Dept=%-12s]", id, name, department); } } class ShoppingCart { private LinkedHashSet<CartItem> items; public ShoppingCart() { items = new LinkedHashSet<>(); } public void addItem(String name, double price) { CartItem item = new CartItem(name, price); if (!items.add(item)) { System.out.println("Item '" + name + "' already in cart"); } } public void removeItem(String name) { items.remove(new CartItem(name, 0)); } public void displayCart() { System.out.println("Shopping Cart:"); if (items.isEmpty()) { System.out.println(" (empty)"); } else { int count = 1; for (CartItem item : items) { System.out.printf(" %d. %-15s $%.2f\n", count++, item.getName(), item.getPrice()); } } } public double getTotal() { return items.stream().mapToDouble(CartItem::getPrice).sum(); } } class CartItem { private String name; private double price; public CartItem(String name, double price) { this.name = name; this.price = price; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof CartItem)) return false; CartItem cartItem = (CartItem) o; return Objects.equals(name, cartItem.name); } @Override public int hashCode() { return Objects.hash(name); } public String getName() { return name; } public double getPrice() { return price; } } class LRUSimulation { private LinkedHashSet<String> cache; private final int capacity; public LRUSimulation(int capacity) { this.capacity = capacity; this.cache = new LinkedHashSet<>(capacity, 0.75f); } public void access(String item) { if (cache.contains(item)) { cache.remove(item); } else if (cache.size() >= capacity) { // Remove least recently used (first element) String lru = cache.iterator().next(); cache.remove(lru); System.out.println("Evicted: " + lru); } cache.add(item); System.out.println("Accessed: " + item); } public void displayCache() { System.out.println("Cache (LRU -> MRU): " + cache); } } Expected Output:
=== LinkedHashSet Complete Example === 1. Basic Order Preservation: Added: Red -> Set: [Red] Added: Blue -> Set: [Red, Blue] Added: Green -> Set: [Red, Blue, Green] Added: Yellow -> Set: [Red, Blue, Green, Yellow] Added: Purple -> Set: [Red, Blue, Green, Yellow, Purple] Added: Blue -> Set: [Red, Blue, Green, Yellow, Purple] Added: Red -> Set: [Red, Blue, Green, Yellow, Purple] Final order: [Red, Blue, Green, Yellow, Purple] 2. Custom Objects with Order: Employees in insertion order: Employee[ID=101, Name=John Doe , Dept=Engineering ] Employee[ID=102, Name=Jane Smith , Dept=Marketing ] Employee[ID=103, Name=Bob Johnson , Dept=Sales ] 3. Shopping Cart Example: Item 'Mouse' already in cart Shopping Cart: 1. Laptop $999.99 2. Mouse $25.99 3. Keyboard $75.50 4. Monitor $299.99 Shopping Cart: 1. Laptop $999.99 2. Keyboard $75.50 3. Monitor $299.99 Total: $1375.48 4. LRU Cache Simulation: Accessed: A Accessed: B Accessed: C Cache (LRU -> MRU): [A, B, C] Accessed: A Cache (LRU -> MRU): [B, C, A] Evicted: B Accessed: D Cache (LRU -> MRU): [C, A, D] Accessed: C Cache (LRU -> MRU): [A, D, C]
Key Takeaways
- LinkedHashSet maintains insertion order - elements are iterated in the order they were added
- No duplicates - like all Set implementations, it doesn't allow duplicate elements
- Good performance - O(1) for basic operations, slightly slower than HashSet due to linked list maintenance
- Perfect for order-sensitive scenarios - task management, history tracking, recent items
- Combines benefits of HashSet (performance) and LinkedList (order)
- Use when you need both Set semantics and predictable iteration order
LinkedHashSet is an excellent choice when you need the uniqueness guarantee of a Set combined with predictable iteration order, making it ideal for many real-world applications where order matters.