Java collection : Map getting updated while using fail-safe iterator - java

I was working on analyzing fail-safe iterators with Maps and verifying, if we are updating the keyset, will the operation happen on the clone or the actual map
private static void failSafeIterator() {
ConcurrentHashMap<String, String> map=new ConcurrentHashMap<>();
map.put("a", "one");
map.put("b", "two");
Iterator<String> keyIterator=map.keySet().iterator();
while(keyIterator.hasNext()){
String key=keyIterator.next();
System.out.println(key+":"+map.get(key));
map.put("c", "three");
map.put("q", "four");
map.put("W", "five");
}
System.out.println(map.get("q"));
}
As per the above code snippet,
the addition of c,q and w should have happened on the clone and not on
the actual collection
But i can see the update happening over the collection.
Also the output is a bit confusing as not all the key value pairs are printed, even though the key is present in the map.
Output:
a:one
b:two
c:three
W:five
four

The problem here is that you don't understand what weakly consistent iterator stands for, when using keySet().iterator(), specifically this part of the documentation:
they are guaranteed to traverse elements as they existed upon construction exactly once, and may (but are not guaranteed to) reflect any modifications subsequent to construction.
Imagine a case like this: you are iterating a ConcurrentHashMap and printing whatever it has. Once you have seen a certain bucket and showed all of it's elements, you move to the next one, also updating the (suppose you add a key-value pair to it) previous one. Updates to that previous one are not going to be shown, although they do exist.
After your loop, you can do :
System.out.println(map);
And see that everything is now present.

Related

Get first and last element from a HashMap

I have the following code and I'd like to get able to get the first and last element from the Map and assign each to a String.
String result1stElement = null;
String resultLastElement = null;
Map<String, String> result = new HashMap<String, String>();
result = myModel.getSampleResults();
Any ideas.
Thanks in advance.
First of all, Maps are not ordered so you wont really have a first and last element.
However, if you wish to get the first and last element of this anyways you could just get the values and convert this into an array. This isn't really pretty, but it'll work.
Map<String, String> result = new HashMap<String, String>();
result = myModel.getSampleResults();
map.values().toArray()[0]; //First result
map.values().toArray()[result.size()-1]; //Last result
Note: This is not tested with a compiler.
First and last element concepts not applicable to Hash-based structures like HashMap and HashSet.
Insertion or deletion of key may cause element reordering on-the-fly.
I guess your model results is an key-value pairs list, not hash map. In this case element ordering is in place. LinkedHashMap keeps insertion order of elements.
Replace HashMap to LinkedHashMap (and modify .getSampleResults()) to return LinkedHashMap and check this question for futher details Java LinkedHashMap get first or last entry .
HashMap has no such thing as order. From HashMap javadoc:
This class makes no guarantees as to the order of the map; in
particular, it does not guarantee that the order will remain constant
over time.
You'll have to use LinkedHashMap. Take a look at entrySet() method and this question+answer
"toArray" method of Set interface can be used.
But iterating over the entries in the entry set and getting the first and last entry is a better approach.
This example might be helpful:
public static void main(final String[] args) {
final Map<Integer,String> orderMap = new LinkedHashMap<Integer,String>();
orderMap.put(6, "Six");
orderMap.put(7, "Seven");
orderMap.put(3, "Three");
orderMap.put(100, "Hundered");
orderMap.put(10, "Ten");
final Set<Entry<Integer, String>> mapValues = orderMap.entrySet();
final int maplength = mapValues.size();
final Entry<Integer,String>[] test = new Entry[maplength];
mapValues.toArray(test);
System.out.print("First Key:"+test[0].getKey());
System.out.println(" First Value:"+test[0].getValue());
System.out.print("Last Key:"+test[maplength-1].getKey());
System.out.println(" Last Value:"+test[maplength-1].getValue());
}
// the output geneated is :
First Key:6 First Value:Six
Last Key:10 Last Value:Ten
There is no such a thing as a first and last element in a HashMap. This is the price you have to pay for O(1) lookup: internally the implementation will chuck your entries into a list of buckets in no easily identifiable (but deterministic) order. This process puts the Hash in HashMap, and in fact the more chaotic it is, the better the performance.
You can use a TreeMap if you want a map sorted by the natural order of its keys (or a custom comparator) or you can have a LinkedHashMap if you want the elements to be arranged in the order of insertion.
P.s.: even if you choose a Map implementation that maintains some kind of order, calling toArray() just to get the first and last elements is a massive overkill, I wouldn't do it. TreeMap has firstEntry() and lastEntry() methods, and even with LinkedHashMap, it's a lot cheaper to just manually iterate across the elements and keep the first and last one instead of allocating a potentially huge array.
Your comment to #Nikolay's answer shows an important detail of your question that was hidden until now.
So, you want to test a method which uses a HashMap structure for refering to some added objects and you want to test, if this method delivers some ordering in this structure? First added object shall remain at a "first position", last added object at a "last position"?
As the other answers already show, there is no way without refactoring that method. HashMap doesn't deliver any meaningful ordering at all and if that method should deliver some ordering, it is simply broken - implementation is faulty.
Of course, you can write a unit test using the algorithm provided by #Sander. This test will fail most of the time. And this again shows the fact, that the tested method has to be refactured like #Nikolay showed in his answer, for instance.

Is put() on an access-ordered LinkedHashMap a structural modification even if an item with the specified key already exists?

Title say it all:
There is a slight ambiguity in the documentation as I understand it.
First the documentation speaks about insertion-ordered LinkedHashMap's and notes that the iteration order is not affected upon inserting an item already mapped.
Then, it introduces access-ordered LinkedHashMap's and insists on the fact that "merely a get is a structural modification", i.e. it affects iteration order.
But, it is not clear whether the ''put()'' of an existing item is a structural modification.
I expect the answer to be yes.
This simple piece of code should answer your question:
final Map<String, String> x = new LinkedHashMap<>(10, 0.75f, true);
x.put("a", "a");
x.put("b", "b");
System.out.println(x);
x.put("a", "a");
System.out.println(x);
prints
{a=a, b=b}
{b=b, a=a}
Interpretation: put under an existing key, even with the same value, is a structural modification.

Same iteration order on Map.keySet and Map.values?

For a map like:
Map<Integer, Integer> map = ...;
map.put(1, 1);
map.put(2, 2);
map.put(3, 3);
map.put(4, 4);
Is this code...
for (Integer i : map.keySet()) System.out.println(i);
for (Integer i : map.values()) System.out.println(i);
...guaranteed print the same same sequence twice?
If not, are there any guarantees in for example java.util.HashMap?
No, there is no guarantee, although in practice it will happen (there's no good reason for the map to use a different iterator for the keys and values).
If you want to guarantee iteration order, iterate the entrySet():
for (Map.Entry<Integer,Integer> entry : map.entrySet())
// ...
Since you ask about HashMap, note also that any changes to the map will potentially change iteration order, as a result of the mapbeing rehashed.
No, not guaranteed. One is Set and one is Collection, neither guarantee the order.
If you would like to keep order. May be LinkedHashMap() with entrySet() help you.
Yes. Sort of. You can use a subclass of SortedMap, i.e. TreeMap. That will keep keys in a natural order. (or you can give it a specific comparator). But when you use tree map, you HAVE to make sure the compareTo method "must be consistent with equals". Read the javadocs for more details. But in short, yes, you CAN sort a map.

LinkedHashMap Structure for LRU Cache

I am a little confused about how to build a LRU cache by using LinkedHashMap (How would you implement an LRU cache in Java 6?), and I want to make sure I understand how it work internally behind the scene.
Lets say I define a class LRUMap that extends the LinkedHashMap and override the removeEldestEntry(final Map.Entry<A, B> eldest) just like the way it is.
Then I construct the data structure and insert 4 items into the map
LRUMap<String,String> map = new LRUMap<String,String>(3); //capacity 3
map.put("a", "a");
map.put("b", "b");
map.put("c", "c");
map.put("d", "d");
and interally LinkedHashMap uses an Entry object called header as a starting node to link with all the items that you add to the map. So in this case it will be
[header] -> ["a"] -> ["b"] -> ["c"] -> ["d"] -> [header]
The header Entry object is both the start and the end of the doubly linked list since header.before = header.after = header when it initially constructs.
And lets say the map reaches the maximum Entries (3 items) that I want it to be, and from
Entry<K,V> eldest = header.after;
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
}
.....
So does that mean it will remove ["a"] first ?
And when we call get(Object key) does it rearrange the list order where it puts that key (lets say "b") before the header node, so it becomes
[header] -> ["c"] -> ["d"] -> ["b"] -> [header]
Just want to clarify that.
Yes; the entry <"a", "a"> will be removed first :-)
Maybe; LinkedHashMap by default uses insertion-order, not access-order...
straight from the specification:
Hash table and linked list implementation of the Map interface, with predictable iteration order. This implementation differs from HashMap in that it maintains a doubly-linked list running through all of its entries. This linked list defines the iteration ordering, which is normally the order in which keys were inserted into the map (insertion-order).
That being said, LinkedHashMap also supports access-order;
A special constructor is provided to create a linked hash map whose order of iteration is the order in which its entries were last accessed, from least-recently accessed to most-recently (access-order). This kind of map is well-suited to building LRU caches. Invoking the put or get method results in an access to the corresponding entry (assuming it exists after the invocation completes).
So, if you're using insertion-order, then the order will not change from get("b"); if you're using access-order (which generally an LRU cache would ;-) then the order will change.
Any other questions? :-)

java.util.TreeMap behavior question

The class docs state that Entrys cannot be modified via .setValue(...) but also caveat that put(...) works fine.
Does that mean put(...) will work fine when iterating over the collection views like navigableKeySet() (i.e., not result in a ConcurrentModificationException), as long as no structural modifications (i.e., adding a new key) are made?
I'm in the middle of testing it out, but if I'm unable to break the iteration, I'd still like some verification that it's working fine (instead of me being unable to break it).
The javadocs for TreeMap state:
A structural modification is any operation that adds or deletes one or more mappings; merely changing the value associated with an existing key is not a structural modification.
Therefore one can assume that changing the values associated with a given key while iterating over the set of keys is allowed.
I believe you are right that as long as you're not making structural modification by adding a new key, you're in no danger of ConcurrentModificationException.
That is, code like this is legal by design:
NavigableMap<Integer,String> nmap =
new TreeMap<Integer,String>();
nmap.put(1, "One");
nmap.put(2, "Two");
nmap.put(3, "Three");
nmap.put(4, "Four");
nmap.put(5, "Five");
NavigableSet<Integer> nkeys =
nmap.navigableKeySet().subSet(2, true, 4, true);
for (Integer key : nkeys) {
nmap.put(key, "Blah");
}
System.out.println(nmap);
// prints "{1=One, 2=Blah, 3=Blah, 4=Blah, 5=Five}"
I'm backing this up also by looking at the source code of the OpenJDK version, where modCount++; is only executed if a new Entry is added.
(In TreeMap, modCount is declared as private transiet, but its analog in AbstractList is declared protected transient, and there it's intended usage is documented to count the number of structural modification for the purpose of detecting ConcurrentModificationException).
Additionally, the documentation for TreeMap explicitly clarifies what counts as structural modification:
A structural modification is any operation that adds or deletes one or more mappings; merely changing the value associated with an existing key is not a structural modification
Based on all of the above, I will say that yes, a put that does not add a new key/value pair is not a structural modification, and thus would not cause a ConcurrentModificationException.

Categories