You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
JImmutables.array()|O(log32(n))|A sparse array allowing any integer (positive or negative) as index. Indexes do not need to be consecutive and the array allocates memory only for indexes actually inserted into the array. Implemented internally as an integer trie with 32-way branching. Cursors visit elements in order by index with negative indexes visited before positive indexes. No direct java.util analog but similar to a TreeMap but with better performance.
263
263
JImmutables.map()|O(log32(n))|A hash map using hashCode() and equals() methods of keys. Keys and values are stored in hash mapped array tries using linked lists or 2-3 trees for collision handling (two keys having same hash code). Performance scales roughly linearly with size of the collection since depth of the trie never exceeds 7 levels. Intended as a replacement for java.util.HashMap. Cursors visit elements in an unspecified order.
264
-
JImmutables.sortedMap()|O(log2(n))|A tree map using a Comparator object to sort and compare keys. Keys and values are stored in 2-3 trees. Intended as a replacement for java.util.TreeMap. Cursors visit elements in sort order of keys as specified by the Comparator.
264
+
JImmutables.sortedMap()|O(log2(n))|A tree map using a Comparator object to sort and compare keys. Keys and values are stored in B-trees. Intended as a replacement for java.util.TreeMap. Cursors visit elements in sort order of keys as specified by the Comparator.
265
265
JImmutables.insertOrderMap()|O(2 * log32(n))|A hash map using hashCode() and equals() methods of keys. Keys and values are stored in hash mapped array tries using linked lists or 2-3 trees for collision handling (two keys having same hash code). A second integer trie is also used to govern cursor order. Performance scales roughly linearly with size of the collection since depth of the trie never exceeds 7 levels. Perhaps as much as twice the overhead of a map() since it maintains two data structures internally. Intended as a replacement for java.util.LinkedHashMap. Cursors visit elements in the order they were originally inserted into the map.
266
266
JImmutables.stack()|O(1) inserts/deletes at head, O(n) searches|A singly linked linear list of elements maintained in reverse insertion order. Extremely fast inserts and deletes from the head but searches require looping through all elements to find a match. Does not support insertion or deletion inside the list. Useful as a stack or a fast temporary list of items where LIFO order is acceptable. Cursors visit elements in LIFO (last in first out) order.
267
267
JImmutables.list()|O(log32(n))|A "list" implemented internally as a 32-way tree. Allows elements to be inserted or removed only at either end of the list. Allows elements to be replaced anywhere within the list. Intended as a replacement for java.util.List. Cursors visit elements in order by index.
268
268
JImmutables.ralist()|O(log2(n))|A "list" implemented internally as a B-tree. Allows elements to be inserted, removed, and updated anywhere within the list. Intended as a replacement for java.util.List in algorithms that require insertion or removal from the middle of the list. Otherwise use list(). Cursors visit elements in order by index.
269
269
JImmutables.set()|O(log32(n))|A set implemented internally using a hash map. Keys are stored in hash mapped array tries using hashCode() and equals() methods to compare keys. Intended as a replacement for HashSet. Cursors visit elements in an unspecified order.
270
-
JImmutables.sortedSet()|O(log2(n))|A set implemented internally using a 2-3 tree. Keys are compared using a Comparator. Intended as a replacement for TreeSet. Cursors visit elements in sort order of keys as specified by the Comparator.
270
+
JImmutables.sortedSet()|O(log2(n))|A set implemented internally using a B-tree. Keys are compared using a Comparator. Intended as a replacement for TreeSet. Cursors visit elements in sort order of keys as specified by the Comparator.
271
271
JImmutables.insertOrderSet()|O(2 * log32(n))|A set implemented internally using an integer trie for sort order and a hash mapped trie for searching. Performance scales roughly linearly with size of the collection since depth of the trie never exceeds 7 levels. Perhaps as much as twice the overhead of a set() since it maintains two data structures internally. Intended as a replacement for java.util.LinkedHashSet. Cursors visit elements in the order they were originally inserted into the map.
272
272
JImmutables.listMap()|O(log32(n))|A hash map mapping keys to JImmutableLists. Performance similar to JImmutables.map(). Cursors visit elements in an unspecified order.
273
273
JImmutables.sortedListMap()|O(log2(n))|A sorted map mapping keys to JImmutableLists. Performance similar to JImmutables.sortedMap(). Cursors visit elements in sort order of keys as specified by the Comparator.
JImmutables.sortedSetMap()|O(log2(n))|A sorted map mapping keys to JImmutableSets. Performance similar to JImmutables.sortedMap(). Cursors visit elements in sort order of keys as specified by the Comparator. Sets are all equivalent to one created by JImmutables.set().
277
277
JImmutables.insertOrderSetMap()|O(2 * log32(n))|A sorted map mapping keys to JImmutableSets. Performance similar to JImmutables.insertOrderMap(). Cursors visit elements in the order they were originally inserted into the map. Sets are all equivalent to one created by JImmutables.set().
278
278
JImmutables.multiset()|O(log32(n))|A multiset implemented internally using a hash map. Keys are stored in hash mapped array tries using hashCode() and equals() methods to compare keys. Cursors visit elements in an unspecified order.
279
-
JImmutables.sortedMultiset()|O(log2(n))|A multiset implemented internally using a 2-3 tree. Keys are compared using a Comparator. Cursors visit elements in sort order of keys as specified by the Comparator.
279
+
JImmutables.sortedMultiset()|O(log2(n))|A multiset implemented internally using a B-tree. Keys are compared using a Comparator. Cursors visit elements in sort order of keys as specified by the Comparator.
280
280
JImmutables.insertOrderMultiset()|O(2 * log32(n))|A multiset implemented internally using an integer trie for sort order and a hash mapped trie for searching. Performance scales roughly linearly with size of the collection since depth of the trie never exceeds 7 levels. Perhaps as much as twice the overhead of a multiset() since it maintains two data structures internally. Cursors visit elements in the order they were originally inserted into the map.
281
281
282
282
@@ -295,27 +295,27 @@ Since hash collisions are always possible hash map implementations have to adopt
295
295
296
296
**Collision Handling Strategies**
297
297
298
-
Versions of JImmutable Collections prior to 1.6 relied on the hash functions producing very few collisions. Based on that simplifying assumption they adopted the simple strategy of using linked lists of key/value pairs to resolve collisions at any given location in the HAMT. For example if 9 different keys stored in the hash map have exactly the same hash code the map would store all nine elements in a linked list at the HAMT node corresponding to that hash code. Whenever the get() method is called for one of those keys the map would find the node with the appropriate hash code in the HAMT and then walk the linked list looking for a matching key. Obviously this implementations performance would degrade in the presence of poor hashCode() methods that generate frequent collisions.
298
+
Versions of JImmutable Collections prior to 1.6 relied on the hash functions producing very few collisions. Based on that simplifying assumption they adopted the simple strategy of using linked lists of key/value pairs to resolve collisions at any given location in the HAMT. For example if 9 different keys stored in the hash map have exactly the same hash code the map would store all nine elements in a linked list at the HAMT node corresponding to that hash code. Whenever the `get()` method is called for one of those keys the map would find the node with the appropriate hash code in the HAMT and then walk the linked list looking for a matching key. Obviously this implementations performance would degrade in the presence of poor `hashCode()` methods that generate frequent collisions.
299
299
300
-
A second hash collision strategy has been available beginning with version 1.6. This second strategy uses balanced 2-3 trees instead of linked lists at each node. In the example above with 9 keys in the same HAMT node a linked list could require up to 9 list nodes to be visited searching for a key but with a 2-3 tree at most 3 tree nodes would have to be visited. With 128 collisions in a single node the list would require visiting up to 128 list nodes but the 2-3 tree would require at most 7 tree nodes.
300
+
A second hash collision strategy has been available beginning with version 1.6. This second strategy uses balanced B-trees instead of linked lists at each node. In the example above with 9 keys in the same HAMT node a linked list could require up to 9 list nodes to be visited searching for a key but with a B-tree at most 2 tree nodes would have to be visited. With 128 collisions in a single node the list would require visiting up to 128 list nodes but the B-tree would require at most 7 tree nodes.
301
301
302
-
Obviously the 2-3 tree strategy offers superior performance. However since it relies on the ability to keep keys sorted in the tree it can only be used for certain types of keys. Specifically keys must implement the Comparable interface. Since the various factory methods, JImmutables.map(), do not require that keys implement Comparable each map waits to decide which strategy to use until the first key is added to the map. On the first call to insert() the map determines if the key is Comparable. If it is the map uses the 2-3 tree strategy. Otherwise it falls back on the linked list strategy.
302
+
Obviously the b-tree strategy offers superior performance. However since it relies on the ability to keep keys sorted in the tree it can only be used for certain types of keys. Specifically keys must implement the Comparable interface. Since the various factory methods, JImmutables.map(), do not require that keys implement Comparable each map waits to decide which strategy to use until the first key is added to the map. On the first call to insert() the map determines if the key is Comparable. If it is the map uses the B-tree strategy. Otherwise it falls back on the linked list strategy.
303
303
304
304
**Best Practices for Unsorted Map Keys**
305
305
306
306
All of this background boils down to a few simple best practices to follow when deciding which objects to use as keys in your unsorted maps.
307
307
308
308
1. Ensure that all keys in the map are implementations of a single class.
309
309
2. Ensure the key class is immutable.
310
-
3. Ensure the key class has an excellent hashCode() method.
310
+
3. Ensure the key class has an excellent `hashCode()` method.
311
311
4. Ensure the key class `K` implements `Comparable<K>`.
312
312
313
-
Restricting your keys to a single class ensures that all of the keys will always be comparable to one another. It also ensures that all keys either implement Comparable or do not. For example if you have a class A which is not Comparable and a subclass B of A which is Comparable then an unsorted map will encounter exceptions if the first key inserted is of class B but a later key is of class A. In that case the map will choose to use the 2-3 strategy when the B key is inserted but when the map tries to cast the A key to Comparable an exception will be thrown. All keys in a given map **must** either implement Comparable or not implement Comparable. You cannot have a mix of the two in a single map. The easiest way to prevent this problem from happening is to use homogeneous hash keys.
313
+
Restricting your keys to a single class ensures that all of the keys will always be comparable to one another. It also ensures that all keys either implement Comparable or do not. For example if you have a class A which is not Comparable and a subclass B of A which is Comparable then an unsorted map will encounter exceptions if the first key inserted is of class B but a later key is of class A. In that case the map will choose to use the B-strategy when the B key is inserted but when the map tries to cast the A key to Comparable an exception will be thrown. All keys in a given map **must** either implement Comparable or not implement Comparable. You cannot have a mix of the two in a single map. The easiest way to prevent this problem from happening is to use homogeneous hash keys.
314
314
315
-
Restricting your keys to be immutable is required not just in the JImmutable maps but also in the java.util maps. If a key is mutable it would be possible for its hashCode() to change after the key has already been added to the map. If that were to happen later attempts to delete or move the key could corrupt the map.
315
+
Restricting your keys to be immutable is required not just in the JImmutable maps but also in the java.util maps. If a key is mutable it would be possible for its `hashCode()` to change after the key has already been added to the map. If that were to happen later attempts to delete or move the key could corrupt the map.
316
316
317
-
Having an excellent hashCode() will make it highly unlikely that any collisions will happen in a HAMT. Integer is an obvious example of a class whose hash codes never collide. (note: if your keys are Integers though you could use a JImmutableArray for better efficiency) There is always a trade off of performance vs collision avoidance. For example computing an MD5 digest and using the first 32 bits would probably yield a fantastic hash code but would be extremely slow. Do some reading on how to implement efficient hashCode() methods. The time expended will pay off in better performance for your program.
317
+
Having an excellent `hashCode()` will make it highly unlikely that any collisions will happen in a HAMT. Integer is an obvious example of a class whose hash codes never collide. (note: if your keys are Integers though you could use a JImmutableArray for better efficiency) There is always a trade off of performance vs collision avoidance. For example computing an MD5 digest and using the first 32 bits would probably yield a fantastic hash code but would be extremely slow. Do some reading on how to implement efficient `hashCode()` methods. The time expended will pay off in better performance for your program.
318
318
319
-
Restricting your keys to those classes which implement Comparable will allow the map to use a 2-3 tree for maximum performance if collisions do happen. Imagine the worst case scenario of a hashCode() that always generates the same number. In that case a map using the 2-3 tree strategy will have O(log(N)) performance while a map using linked list strategy will have O(N) performance. So the small amount of time needed to add a well written compareTo() method can pay off in better performance if your hashCode() method proves to be less than stellar.
319
+
Restricting your keys to those classes which implement Comparable will allow the map to use a B-tree for maximum performance if collisions do happen. Imagine the worst case scenario of a `hashCode()` that always generates the same number. In that case a map using the B-tree strategy will have O(log(N)) performance while a map using linked list strategy will have O(N) performance. So the small amount of time needed to add a well written `compareTo()` method can pay off in better performance if your `hashCode()` method proves to be less than stellar.
320
320
321
-
Of course the easiest strategy is to simply use one of Java's built in value types as keys. String, Integer, Double, etc all implement Comparable and have good hashCode() implementations.
321
+
Of course the easiest strategy is to simply use one of Java's built in value types as keys. String, Integer, Double, etc all implement Comparable and have good `hashCode()` implementations.
0 commit comments