Chapter 14: Hash Tables, Maps and Sets
14.05Beginner / Intermediate5 topics13 min1 interactive animation

Java sets

Java's Set implementations: HashSet, LinkedHashSet and TreeSet, how HashSet is built on HashMap, and the difference between ordered, sorted and unordered.

Learning goals

  • Choose the right Set for membership, insertion order or sorted order
  • Explain that HashSet delegates to HashMap
  • Use add's return value and the sorted-set navigation methods

Prerequisites

Java maps
14.05.1

HashSet

HashSet<E> stores each element once and answers contains in expected O(1). add returns true if the element was new and false if it was already present, which makes duplicate detection a one-liner. Iteration order is unspecified.

Elements need consistent hashCode and equals, exactly like map keys.

Java
1static boolean hasDuplicate(int[] a) {
2 Set<Integer> seen = new HashSet<>();
3 for (int x : a) if (!seen.add(x)) return true; // add returns false when x was already there
4 return false;
5}
14.05.2

LinkedHashSet

LinkedHashSet is a HashSet that also threads a linked list through its elements, so iteration follows insertion order. Re-adding an element that is already present does not move it. Use it to remove duplicates while keeping the first-seen order, or when output must be reproducible.

Java
1static List<Integer> dedupe(int[] a) {
2 Set<Integer> seen = new LinkedHashSet<>();
3 for (int x : a) seen.add(x);
4 return new ArrayList<>(seen); // first-seen order: [42, 7, 19, 3, 25, 16, 33] for the animation's input
5}
14.05.3

TreeSet

TreeSet keeps its elements sorted in a red-black tree: add, contains and remove are O(log n). It adds order-based queries: first, last, floor(x) (largest element ≤ x), ceiling(x) (smallest ≥ x), headSet, tailSet and subSet.

A TreeSet decides two elements are the same when its comparator returns 0, not when equals says so. With String.CASE_INSENSITIVE_ORDER, adding "a" and then "A" leaves a one-element set [a].

Java
1static void sortedSet() {
2 TreeSet<Integer> s = new TreeSet<>(List.of(10, 20, 30));
3 System.out.println(s.floor(25) + " " + s.ceiling(25) + " " + s.first() + " " + s.headSet(20)); // 20 30 10 [10]
4 TreeSet<String> ci = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
5 ci.add("a"); ci.add("A");
6 System.out.println(ci); // [a]
7}
14.05.4

HashSet backed by HashMap

HashSet is a thin wrapper around a HashMap: every element is a key and every value is the same dummy object. add(e) is map.put(e, PRESENT) == null, contains(e) is map.containsKey(e) and remove(e) is map.remove(e) == PRESENT. LinkedHashSet wraps a LinkedHashMap and TreeSet wraps a TreeMap in the same way.

This explains why a set has the same costs, the same iteration order and the same hashCode/equals requirements as the corresponding map, and why a set uses more memory than the stored elements alone suggest.

Java
1final class MiniSet<E> {
2 private static final Object PRESENT = new Object();
3 private final HashMap<E, Object> map = new HashMap<>();
4 boolean add(E e) { return map.put(e, PRESENT) == null; } // null means the key was new
5 boolean contains(Object e) { return map.containsKey(e); }
6 boolean remove(Object e) { return map.remove(e) == PRESENT; }
7 int size() { return map.size(); }
8}
14.05.5

Ordered vs sorted vs unordered

Three words are often confused. **Unordered**: no promise about iteration order (HashSet). **Ordered** (insertion-ordered): iteration follows the order in which elements were added (LinkedHashSet, ArrayList). **Sorted**: iteration follows a comparison order of the values themselves (TreeSet).

Run the animation and compare the three outputs for the same input values. Sorting costs O(log n) per operation; insertion order costs only memory.

KindIteration orderExampleadd / contains
UnorderedUnspecifiedHashSetO(1) expected
OrderedOrder of insertionLinkedHashSetO(1) expected
SortedBy comparisonTreeSetO(log n)

Same values, different collections

Interactive · edit the input

Your input

Try

ArrayList

empty

LinkedHashSet

empty

HashSet (capacity 16)

empty

TreeSet

empty

PriorityQueue array

empty
Setup

Five collections receive exactly the same values in the same order. Watch how each one stores them.

Step 1 / 10
Java
1List<Integer> list = new ArrayList<>();
2Set<Integer> linked = new LinkedHashSet<>();
3Set<Integer> hash = new HashSet<>();
4Set<Integer> tree = new TreeSet<>();
5PriorityQueue<Integer> heap = new PriorityQueue<>();
6for (int x : arr) {
7 list.add(x); // keeps insertion order, keeps duplicates
8 linked.add(x); // keeps insertion order, drops duplicates
9 hash.add(x); // order decided by hash buckets
10 tree.add(x); // always sorted
11 heap.add(x); // heap array: only the smallest is guaranteed first
12}
13System.out.println(list + " " + linked + " " + hash + " " + tree + " " + heap);

Live state

values

8

No condition evaluated on this step
Execution trace (dry run table)Show 10 steps
StepLineStateWhat happens
1L1values=8Five collections receive exactly the same values in the same order. Watch how each one stores them.
2L7x=42 adds so far=1 distinct=1 HashSet capacity=1642 goes to bucket 10 of 16 in the HashSet (index = (h ^ (h >>> 16)) & 15).
3L7x=7 adds so far=2 distinct=2 HashSet capacity=167 goes to bucket 7 of 16 in the HashSet (index = (h ^ (h >>> 16)) & 15).
4L7x=19 adds so far=3 distinct=3 HashSet capacity=1619 goes to bucket 3 of 16 in the HashSet (index = (h ^ (h >>> 16)) & 15).
5L7x=7 adds so far=4 distinct=3 HashSet capacity=167 is already present: the ArrayList and the PriorityQueue keep a second copy, the three sets ignore it.
6L7x=3 adds so far=5 distinct=4 HashSet capacity=163 goes to bucket 3 of 16 in the HashSet (index = (h ^ (h >>> 16)) & 15).
7L7x=25 adds so far=6 distinct=5 HashSet capacity=1625 goes to bucket 9 of 16 in the HashSet (index = (h ^ (h >>> 16)) & 15).
8L7x=16 adds so far=7 distinct=6 HashSet capacity=1616 goes to bucket 0 of 16 in the HashSet (index = (h ^ (h >>> 16)) & 15).
9L7x=33 adds so far=8 distinct=7 HashSet capacity=1633 goes to bucket 1 of 16 in the HashSet (index = (h ^ (h >>> 16)) & 15).
10L13distinct=7 capacity=16Same values, five different orders. Only the TreeSet is sorted; the HashSet order is an implementation detail you must never depend on, and the PriorityQueue promises only that index 0 is the minimum.

Practice ladder

Now try it yourself

Easy

  • Contains Duplicate
  • Intersection of Two Arrays

Medium

  • Longest Consecutive Sequence
  • Happy Number

Advanced

  • Contains Duplicate III (use TreeSet.ceiling)
  • Insert Delete GetRandom O(1)

Key takeaways

  • HashSet for membership, LinkedHashSet for insertion order, TreeSet for sorted order and floor/ceiling.
  • HashSet is a HashMap whose values are all the same dummy object.
  • Unordered, ordered and sorted are different promises; choose the weakest one that works.