How select first N items in Java TreeMap? - java

Given this map
SortedMap<Integer, String> myMap = new TreeMap<Integer, String>();
Instead of a for loop is there a utility function to copy first N items to a destination map?

Using the power of Java 8+:
TreeMap<Integer, String> myNewMap = myMap.entrySet().stream()
.limit(3)
.collect(TreeMap::new, (m, e) -> m.put(e.getKey(), e.getValue()), Map::putAll);

Maybe, but not as part of the standard Java API. And: the utility would use a loop inside.
So you'll need a loop, but you can create your own "utility" by doing it all in a static method in a utility class:
public static SortedMap<K,V> putFirstEntries(int max, SortedMap<K,V> source) {
int count = 0;
TreeMap<K,V> target = new TreeMap<K,V>();
for (Map.Entry<K,V> entry:source.entrySet()) {
if (count >= max) break;
target.put(entry.getKey(), entry.getValue());
count++;
}
return target;
}
The complexity is still O(n) (I doubt, that one can achieve O(1)) but you use it like a tool without "seeing" the loop:
SortedMap<Integer, String> firstFive = Util.putFirstEntries(5, sourceMap);

There's SortedMap.headMap() however you'd have to pass a key for the element to go up to. You could iterate N elements over Map.keySet() to find it, e.g.:
Integer toKey = null;
int i = 0;
for (Integer key : myMap.keySet()) {
if (i++ == N) {
toKey = key;
break;
}
}
// be careful that toKey isn't null because N is < 0 or >= myMap.size()
SortedMap<Integer, String> copyMap = myMap.headMap(toKey);

You can also use an ordored iterator to get the first x records, orderer by descending id for instance :
Iterator<Integer> iterator = myMap.descendingKeySet().iterator();

You can use the putAll(Map t) function to copy the items from the map to specified map.But it copies all the items. You cannot copy fixed number of items.
http://download.oracle.com/javase/1.4.2/docs/api/java/util/Map.html#putAll%28java.util.Map%29

Related

Finding number of values in a HashMap?

What is the best / most efficient way to find the total number of Values in a HashMap.
I do not mean the .size() method as it counts the number of keys. I want the total number of values in all the keys.
I want to do so as my key is a String, but my value is a List.
In Java 8, you can also utilize the Stream API:
int total = map.values()
.stream()
.mapToInt(List::size) // or (l -> l.size())
.sum()
This has the advantage that you don't have to repeat the List<Foo> type for a for variable, as in the pre-Java 8 solution:
int total = 0;
for (List<Foo> list : map.values())
{
total += list.size();
}
System.out.println(total);
In addition to that, although not advised, you could also use that value inline without needing a temp variable:
System.out.println(map.values().stream().mapToInt(List::size).sum());
Easiest would be, iterate and add over list sizes.
int total = 0;
for (List<Foo> l : map.values()) {
total += l.size();
}
// here is the total values size
Starting with Java 8, given a Map<K, List<V>> map you could use the Stream API and have:
int size = map.values().stream().mapToInt(List::size).sum();
This creates a Stream of the values with stream(), maps each of them to their size with mapToInt, where the mapper is the method reference List::size refering to List#size(), and sum the results with sum().
Say you have a map
Map<String, List<Object>> map = new HashMap<>();
You can do this by calling the values() method and calling size() method for all the lists:
int total = 0;
Collection<List<Object>> listOfValues = map.values();
for (List<Object> oneList : listOfValues) {
total += oneList.size();
}
If I understand the question correctly, you have a Map<String, List<Something>>, and you want to count the total number of items in all the Lists in the Map's values. Java 8 offers a pretty easy way of doing this by streaming the values, mapping them to their size() and then just summing them:
Map<String, List<Something>> map = ...;
int totalSize = map.values().stream().mapToInt(List::size).sum());
With Eclipse Collections, the following will work using MutableMap.
MutableMap<String, List<String>> map =
Maps.mutable.of("key1", Lists.mutable.of("a", "b", "c"),
"key2", Lists.mutable.of("d", "e", "f", "g"));
long total = map.sumOfInt(List::size);
Note: I am a committer for Eclipse Collections.
import java.util.HashMap;
public class Solution {
public static void main(String args[]) {
int total = 0;
HashMap<String,String> a = new HashMap<String,String>();
a.put("1.","1");
a.put("2.","11");
a.put("3.","21");
a.put("4.","1211");
a.put("5.","111221");
for (String l : a.values()) {
total ++;
}
System.out.println(total);
}
}

Sort Map by Top Scores

I have a map that stores a players name and there score on which i update and things fine.
public static Map<String, Integer> map = new HashMap<String, Integer>();
After the first round of my game finishes i want to remove the lowest scores from the map. For instance, There could be 8 players in the map 4 of whom have a score of 10 and the other 4 have a score of 0 how would i split the map in half based on the top scorers and remove the bottom 4? Keep in mind the 8 players is not defined, it could be any number
No one seems to have picked up on this not being a Mapping. If you look at high scores, names can be duplicates. What you want is an NavigableSet where
class HighScore implements Comparable<HighScore> {
private static final AtomicLong NEXT_ID = new AtomicLong(1);
protected final String name;
protected final long id = NEXT_ID.getAndIncrement();
protected final int score;
// ...
public int compareTo(HighScore o) {
int diff = score - o.score;
if (diff != 0) { return diff; }
long idDiff = id - o.id;
if (idDiff < 0) { return -1; }
else if (idDiff > 0) { return 1; }
else { return 0; }
}
}
And then you can just pollFirst() to remove.
Below is what might help you. I populated the Map with same values for key and values, to see the order when it is printed. The below example includes
a way of getting the top half view without removing the bottom half from original map
and also removing bottom half from original map.
Unless the requirement is to remove do not need to remove bottom half, still get the headMap that is backed by the original map.
import java.util.NavigableMap;
import java.util.TreeMap;
public class HalfTheMap {
static void addValues(TreeMap<String, Integer> map)
{
map.put("11", 11);
map.put("33", 33);
map.put("77", 77);
map.put("44", 44);
map.put("55", 55);
map.put("22", 22);
//map.put("66", 66);
}
public static void main(String[] args) {
TreeMap<String, Integer> map = new TreeMap<String, Integer>();
addValues(map);
System.out.printf("Original Map Initial Values : %s\n",map);
int size = map.size();
int midIndex = (size/2) - 1;
System.out.printf("size : %d \nmid : %d\n", size, midIndex);
// retrieve key of middle element
String midKey = (String)map.keySet().toArray()[midIndex];
// Top half view of the original map
NavigableMap<String, Integer> topMap = map.headMap(midKey, true);
System.out.printf("Top half map : %s\n", topMap);
// remove the bottom half from original map.
map.tailMap(midKey, false).clear();
System.out.printf("Original map after bottom half removed : %s\n", map);
}
}
Prints :
Original Map Initial Values : {11=11, 22=22, 33=33, 44=44, 55=55, 77=77}
size : 6
mid : 2
Top half map : {11=11, 22=22, 33=33}
Original map after bottom half removed : {11=11, 22=22, 33=33}
I am leaving the fine tuning of halving when size is odd value to you and any other fine tuning that are appropriate to your needs.
NOTE : I have noticed that my example using keys and values of same value in each entry seems to show that it could be a solution, infact not exactly. Though it demonstrates the usage of some important methods to solve the problem.
As answered by David Ehrmann above, changing the collections to Set rather than Map and using a class that modal name and score could be a better solution.
Hope this helps.
But there's a simplier way to do this.
You can use custom sorter algorithm on TreeMap.
For example: TreeMap map = new TreeMap(Your own comparator);
You can write your own comparator implementing the Comparator interface.
Example (taken from StackOverflow, don't know more precisely):
class ValueComparator implements Comparator {
Map<String, Integer> base;
public ValueComparator(Map<String, Integer> base) {
this.base = base;
}
public int compare(String a, String b) {
if (base.get(a) >= base.get(b)) {
return -1;
} else {
return 1;
} // returning 0 would merge keys
}
}
Then, you only have to edit this code, to ascending order, then you only have to remove the first X. elements from.
I found this post that might help. One you cannot sort a Hashmap because there is no definitive order to it. If you want to sort through a LinkedHashMap though, this is how you do it. (A LinkedHashMap just has a definitive iterative order)
public LinkedHashMap sortHashMapByValues(HashMap passedMap) {
List mapKeys = new ArrayList(passedMap.keySet());
List mapValues = new ArrayList(passedMap.values());
Collections.sort(mapValues);
Collections.sort(mapKeys);
LinkedHashMap sortedMap = new LinkedHashMap();
Iterator valueIt = mapValues.iterator();
while (valueIt.hasNext()) {
Object val = valueIt.next();
Iterator keyIt = mapKeys.iterator();
while (keyIt.hasNext()) {
Object key = keyIt.next();
String comp1 = passedMap.get(key).toString();
String comp2 = val.toString();
if (comp1.equals(comp2)){
passedMap.remove(key);
mapKeys.remove(key);
sortedMap.put((String)key, (Double)val);
break;
}
}
}
return sortedMap;
}
If you want to remove the lowest value on the other hand, I doubt this is the easiest thing you could do something like this to sort them.
public static Entry<String, Integer> removeLowest(LinkedHashMap<String, Integer> map){
Entry<String, Integer> lowest = null;
for(Entry<String,Integer> e: map){
if(lowest==null || e.getValue().compareTo(lowest.getValue()) < 0){
lowest = e;
}
}
return lowest;
}
PS: Don't forget to accept my answer if it works for you.
Update: If you want to remove say half of the map. You would sort it first then do this.
public static LinkedHashMap<String, Integer> getTopHalf(LinkedHashMap<String, Integer> map){
LinkedHashMap<String, Integer> sorted = sortHashMapByValues(map);
LinkedHashMap<String, Integer> out = new LinkedHashMap<String, Integer>();
Iterator<Entry<String,Integer>> it = sorted.entrySet().iterator();
for(int i = 0; i<map.size()/2; i++){
Entry<String, Integer> e = it.next();
out.put(e.getKey(), e.getValue());
}
return out;
}

Creating subset of HashMap based on some specifications?

So I have the following HashMap:
HashMap<String, List<someDataType>> map;
I want to create a new HashMap that is only composed of the k/v pairs in map that have a value (the list) whose length is less than a certain "x". The only way I know how to do this is to iterate through the HashMap and put k/v pairs into a new HashMap. Is there a more concise way to achieve what I'm looking for? Thanks.
Using guava:
Map<String, List<String>> newMap =
Maps.filterEntries(originalMap, new MyEntryPredicate(10));
where:
private static class MyEntryPredicate implements Predicate<Map.Entry<String, List<String>>> {
// max list length, exclusive
private int maxLength;
private MyEntryPredicate(int maxLength) {
this.maxLength = maxLength;
}
#Override
public boolean apply(Map.Entry<String, List<String>> input) {
return input != null && input.getValue().size() < maxLength;
}
}
If the Guava library is available to your project, you could use Maps.filterValues (somewhat echoing Keith's answer):
final int x = 42;
Map<String, List<String>> filteredMap =
Maps.filterValues(map, new Predicate<Collection<?>>() {
#Override
public boolean apply(final Collection<?> collection) {
return collection.size() < x;
}
});
Map<String, List<String>> filteredMapCopy = ImmutableMap.copyOf(filteredMap);
Note the need for a copy because filterValues returns a filtered view of the original map.
Update: with Java 8 you can simplify the predicate to a lambda expression:
Map<String, List<String>> filteredMap = Maps.filterValues(map, list -> list.size() < x);
Nowadays (Java 8+) this could be done with streams:
Predicate<Map.Entry<String, List<String>>> test = entry -> entry.getValue().size() <= x; // note this is java.util.function.Predicate
Map<String, List<String>> filteredMap = map.entrySet().stream().filter(test)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
This helps to avoid the dependency to guava which might be undesired.
You may want to look at the Guava library from Google. There's an enormous number of Collections and Map related utils in there, which let you do complex stuff quite concisely. An example of what you can do is:
Iterable<Long> list =
Iterables.limit(
Iterables.filter(
Ordering.natural()
.reverse()
.onResultOf(new Function<Long, Integer>() {
public Integer apply(Long id) {
return // result of this is for sorting purposes
}
})
.sortedCopy(
Multisets.intersection(set1, set2)),
new Predicate<Long>() {
public boolean apply(Long id) {
return // whether to filter this id
}
}), limit);
I'm sure you can find something in there which can do what you're looking for :-)
Going along with the other Guava examples, you can use Guava's MultiMaps:
final MultiMap<K, V> mmap = ArrayListMultiMap.create();
// do stuff.
final int limit = 10;
final MultiMap<K, V> mmapView =
MultiMaps.filterKeys(mmap, new Predicate<K>(){
public boolean apply(K k) {
return mmap.get(k).size() <= limit;
}
});
The MultiMaps.newListMultiMap method takes arguments you don't want to provide. You can't use MultiMaps.filterValues or .filterEntries here because those use the individual values, not the lists of values. On the other hand, mmap.get(k) never returns null. You cam, of course, use a static inner class that you pass mmap and limit to instead of using anonymous inner classes.
Alternatevely you can make a copy of the original map and iterate over the values removing those whose length is less than x.

Efficient way to get the most used keys in a HashMap - Java

I have a HashMap where the key is a word and the value is a number of occurrences of that string in a text. Now I'd like to reduce this HashMap to only 15 most used words (with greatest numbers of occurrences). Do you have any idea to do this efficiently?
Using an array instead of ArrayList as suggested by Pindatjuh could be better,
public class HashTest {
public static void main(String[] args) {
class hmComp implements Comparator<Map.Entry<String,Integer>> {
public int compare(Entry<String, Integer> o1,
Entry<String, Integer> o2) {
return o2.getValue() - o1.getValue();
}
}
HashMap<String, Integer> hm = new HashMap<String, Integer>();
Random rand = new Random();
for (int i = 0; i < 26; i++) {
hm.put("Word" +i, rand.nextInt(100));
}
ArrayList list = new ArrayList( hm.entrySet() );
Collections.sort(list, new hmComp() );
for ( int i = 0 ; i < 15 ; i++ ) {
System.out.println( list.get(i) );
}
}
}
EDIT reversed sorting order
One way I think of to tackle this, but it's probably not the most efficient, is:
Create an array of hashMap.entrySet().toArray(new Entry[]{}).
Sort this using Arrays.sort, create your own Comparator which will compare only on Entry.getValue() (which casts it to an Integer). Make it order descending, i.e. most/highest first, less/lowest latest.
Iterate over the sorted array and break when you've reached the 15th value.
Map<String, Integer> map = new HashMap<String, Integer>();
// --- Put entries into map here ---
// Get a list of the entries in the map
List<Map.Entry<String, Integer>> list = new Vector<Map.Entry<String, Integer>>(map.entrySet());
// Sort the list using an annonymous inner class implementing Comparator for the compare method
java.util.Collections.sort(list, new Comparator<Map.Entry<String, Integer>>(){
public int compare(Map.Entry<String, Integer> entry, Map.Entry<String, Integer> entry1)
{
// Return 0 for a match, -1 for less than and +1 for more then
return (entry.getValue().equals(entry1.getValue()) ? 0 : (entry.getValue() > entry1.getValue() ? 1 : -1));
}
});
// Clear the map
map.clear();
// Copy back the entries now in order
for (Map.Entry<String, Integer> entry: list)
{
map.put(entry.getKey(), entry.getValue());
}
Use first 15 entries of map. Or modify last 4 lines to put only 15 entries into map
You can use a LinkedHashMap and remove the least recently used items.

is the Java HashMap keySet() iteration order consistent?

I understand that the Set returned from a Map's keySet() method does not guarantee any particular order.
My question is, does it guarantee the same order over multiple iterations. For example
Map<K,V> map = getMap();
for( K k : map.keySet() )
{
}
...
for( K k : map.keySet() )
{
}
In the above code, assuming that the map is not modified, will the iteration over the keySets be in the same order. Using Sun's jdk15 it does iterate in the same order, but before I depend on this behavior, I'd like to know if all JDKs will do the same.
EDIT
I see from the answers that I cannot depend on it. Too bad. I was hoping to get away with not having to build some new Collection to guarantee my ordering. My code needed to iterate through, do some logic, and then iterate through again with the same ordering. I'll just create a new ArrayList from the keySet which will guarantee order.
You can use a LinkedHashMap if you want a HashMap whose iteration order does not change.
Moreover you should always use it if you iterate through the collection. Iterating over HashMap's entrySet or keySet is much slower than over LinkedHashMap's.
If it is not stated to be guaranteed in the API documentation, then you shouldn't depend on it. The behavior might even change from one release of the JDK to the next, even from the same vendor's JDK.
You could easily get the set and then just sort it yourself, right?
Map is only an interface (rather than a class), which means that the underlying class that implements it (and there are many) could behave differently, and the contract for keySet() in the API does not indicate that consistent iteration is required.
If you are looking at a specific class that implements Map (HashMap, LinkedHashMap, TreeMap, etc) then you could see how it implements the keySet() function to determine what the behaviour would be by checking out the source, you'd have to really take a close look at the algorithm to see if the property you are looking for is preserved (that is, consistent iteration order when the map has not had any insertions/removals between iterations). The source for HashMap, for example, is here (open JDK 6): http://www.docjar.com/html/api/java/util/HashMap.java.html
It could vary widely from one JDK to the next, so i definitely wouldn't rely on it.
That being said, if consistent iteration order is something you really need, you might want to try a LinkedHashMap.
The API for Map does not guarantee any ordering whatsoever, even between multiple invocations of the method on the same object.
In practice I would be very surprised if the iteration order changed for multiple subsequent invocations (assuming the map itself did not change in between) - but you should not (and according to the API cannot) rely on this.
EDIT - if you want to rely on the iteration order being consistent, then you want a SortedMap which provides exactly these guarantees.
Just for fun, I decided to write some code that you can use to guarantee a random order each time. This is useful so that you can catch cases where you are depending on the order but you should not be. If you want to depend on the order, than as others have said, you should use a SortedMap. If you just use a Map and happen to rely on the order then using the following RandomIterator will catch that. I'd only use it in testing code since it makes use of more memory then not doing it would.
You could also wrap the Map (or the Set) to have them return the RandomeIterator which would then let you use the for-each loop.
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
public class Main
{
private Main()
{
}
public static void main(final String[] args)
{
final Map<String, String> items;
items = new HashMap<String, String>();
items.put("A", "1");
items.put("B", "2");
items.put("C", "3");
items.put("D", "4");
items.put("E", "5");
items.put("F", "6");
items.put("G", "7");
display(items.keySet().iterator());
System.out.println("---");
display(items.keySet().iterator());
System.out.println("---");
display(new RandomIterator<String>(items.keySet().iterator()));
System.out.println("---");
display(new RandomIterator<String>(items.keySet().iterator()));
System.out.println("---");
}
private static <T> void display(final Iterator<T> iterator)
{
while(iterator.hasNext())
{
final T item;
item = iterator.next();
System.out.println(item);
}
}
}
class RandomIterator<T>
implements Iterator<T>
{
private final Iterator<T> iterator;
public RandomIterator(final Iterator<T> i)
{
final List<T> items;
items = new ArrayList<T>();
while(i.hasNext())
{
final T item;
item = i.next();
items.add(item);
}
Collections.shuffle(items);
iterator = items.iterator();
}
public boolean hasNext()
{
return (iterator.hasNext());
}
public T next()
{
return (iterator.next());
}
public void remove()
{
iterator.remove();
}
}
I agree with LinkedHashMap thing. Just putting my findings and experience while I was facing the problem when I was trying to sort HashMap by keys.
My code to create HashMap:
HashMap<Integer, String> map;
#Before
public void initData() {
map = new HashMap<>();
map.put(55, "John");
map.put(22, "Apple");
map.put(66, "Earl");
map.put(77, "Pearl");
map.put(12, "George");
map.put(6, "Rocky");
}
I have a function showMap which prints entries of map:
public void showMap (Map<Integer, String> map1) {
for (Map.Entry<Integer, String> entry: map1.entrySet()) {
System.out.println("[Key: "+entry.getKey()+ " , "+"Value: "+entry.getValue() +"] ");
}
}
Now when I print the map before sorting, it prints following sequence:
Map before sorting :
[Key: 66 , Value: Earl]
[Key: 22 , Value: Apple]
[Key: 6 , Value: Rocky]
[Key: 55 , Value: John]
[Key: 12 , Value: George]
[Key: 77 , Value: Pearl]
Which is basically different than the order in which map keys were put.
Now When I sort it with map keys:
List<Map.Entry<Integer, String>> entries = new ArrayList<>(map.entrySet());
Collections.sort(entries, new Comparator<Entry<Integer, String>>() {
#Override
public int compare(Entry<Integer, String> o1, Entry<Integer, String> o2) {
return o1.getKey().compareTo(o2.getKey());
}
});
HashMap<Integer, String> sortedMap = new LinkedHashMap<>();
for (Map.Entry<Integer, String> entry : entries) {
System.out.println("Putting key:"+entry.getKey());
sortedMap.put(entry.getKey(), entry.getValue());
}
System.out.println("Map after sorting:");
showMap(sortedMap);
the out put is:
Sorting by keys :
Putting key:6
Putting key:12
Putting key:22
Putting key:55
Putting key:66
Putting key:77
Map after sorting:
[Key: 66 , Value: Earl]
[Key: 6 , Value: Rocky]
[Key: 22 , Value: Apple]
[Key: 55 , Value: John]
[Key: 12 , Value: George]
[Key: 77 , Value: Pearl]
You can see the difference in order of keys. Sorted order of keys is fine but that of keys of copied map is again in the same order of the earlier map. I dont know if this is valid to say, but for two hashmap with same keys, order of keys is same. This implies to the statement that order of keys is not guaranteed but can be same for two maps with same keys because of inherent nature of key insertion algorithm if HashMap implementation of this JVM version.
Now when I use LinkedHashMap to copy sorted Entries to HashMap, I get desired result (which was natural, but that is not the point. Point is regarding order of keys of HashMap)
HashMap<Integer, String> sortedMap = new LinkedHashMap<>();
for (Map.Entry<Integer, String> entry : entries) {
System.out.println("Putting key:"+entry.getKey());
sortedMap.put(entry.getKey(), entry.getValue());
}
System.out.println("Map after sorting:");
showMap(sortedMap);
Output:
Sorting by keys :
Putting key:6
Putting key:12
Putting key:22
Putting key:55
Putting key:66
Putting key:77
Map after sorting:
[Key: 6 , Value: Rocky]
[Key: 12 , Value: George]
[Key: 22 , Value: Apple]
[Key: 55 , Value: John]
[Key: 66 , Value: Earl]
[Key: 77 , Value: Pearl]
Hashmap does not guarantee that the order of the map will remain constant over time.
It doesn't have to be. A map's keySet function returns a Set and the set's iterator method says this in its documentation:
"Returns an iterator over the elements in this set. The elements are returned in no particular order (unless this set is an instance of some class that provides a guarantee)."
So, unless you are using one of those classes with a guarantee, there is none.
Map is an interface and it does not define in the documentation that order should be the same. That means that you can't rely on the order. But if you control Map implementation returned by the getMap(), then you can use LinkedHashMap or TreeMap and get the same order of keys/values all the time you iterate through them.
tl;dr Yes.
I believe the iteration order for .keySet() and .values() is consistent (Java
8).
Proof 1: We load a HashMap with random keys and random values. We iterate on this HashMap using .keySet() and load the keys and it's corresponding values to a LinkedHashMap (it will preserve the order of the keys and values inserted). Then we compare the .keySet() of both the Maps and .values() of both the Maps. It always comes out to be the same, never fails.
public class Sample3 {
static final String AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
static SecureRandom rnd = new SecureRandom();
// from here: https://stackoverflow.com/a/157202/8430155
static String randomString(int len){
StringBuilder sb = new StringBuilder(len);
for (int i = 0; i < len; i++) {
sb.append(AB.charAt(rnd.nextInt(AB.length())));
}
return sb.toString();
}
public static void main(String[] args) throws Exception {
for (int j = 0; j < 10; j++) {
Map<String, String> map = new HashMap<>();
Map<String, String> linkedMap = new LinkedHashMap<>();
for (int i = 0; i < 1000; i++) {
String key = randomString(8);
String value = randomString(8);
map.put(key, value);
}
for (String k : map.keySet()) {
linkedMap.put(k, map.get(k));
}
if (!(map.keySet().toString().equals(linkedMap.keySet().toString()) &&
map.values().toString().equals(linkedMap.values().toString()))) {
// never fails
System.out.println("Failed");
break;
}
}
}
}
Proof 2: From here, the table is an array of Node<K,V> class. We know that iterating an array will give the same result every time.
/**
* The table, initialized on first use, and resized as
* necessary. When allocated, length is always a power of two.
* (We also tolerate length zero in some operations to allow
* bootstrapping mechanics that are currently not needed.)
*/
transient Node<K,V>[] table;
The class responsible for .values():
final class Values extends AbstractCollection<V> {
// more code here
public final void forEach(Consumer<? super V> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.value);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
The class responsible for .keySet():
final class KeySet extends AbstractSet<K> {
// more code here
public final void forEach(Consumer<? super K> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.key);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
Carefully look at both the inner classes. They are pretty much the same except:
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.key); <- from KeySet class
// action.accept(e.value); <- the only change from Values class
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
They iterate on the same array table to support .keySet() in KeySet class and .values() in Values class.
Proof 3: this answer also explicitly states - So, yes, keySet(), values(), and entrySet() return values in the order the internal linked list uses.
Therefore, the .keySet() and .values() are consistent.
Logically, if the contract says "no particular order is guaranteed", and since "the order it came out one time" is a particular order, then the answer is no, you can't depend on it coming out the same way twice.
You also can store the Set instance returned by the keySet() method and can use this instance whenever you need the same order.

Categories

Resources