There are a couple of optimizations you can try on your current implementation. These are low-cost, quick wins:
Use explicit initial capacity and load factor
By using the appropriate constructor you can specify both initial capacity and load factor for your LinkedHashMap.
Load factor
Higher values decrease the space overhead but increase the lookup cost, according to the docs. You would have to experiment with values between 0.75
(the default) and 0.99
in order to find a sweet spot.
Initial capacity
By using a high value, you can minimize re-hashing due to buckets getting full. Since you are using LinkedHashMap, the impact of a large initial capacity is less critical since iteration time is unaffected. If your use case allows it, you can even eliminate re-hashing by choosing a large enough value to cover all distinct entries (i.e. if you have historical data of how many distinct keys you count or your dataset has finite elements anyway). If you can minimize/eliminate re-hashing, you also minimize any drawbacks cause by larger load factor values.
Only keep interesting entries
It seems that you only need to find one key per frequency. If this is true, you can reduce the data retained in memory once you are done and keep only one key per frequency (count).
Sample code
Map<String, Long> counts2 = new LinkedHashMap<String, Long>(10_000, 0.95f); //Using the appropriate constructor
for (String val : str) {
long count = counts2.getOrDefault(val, 0L);
counts2.put(val, ++count);
}
// Clean up unneeded (?) entries
final HashMap<Long, Integer> data = new HashMap<>();
for (Iterator<Map.Entry<String, Long>> it = counts2.entrySet().iterator(); it.hasNext();) {
Map.Entry<String, Long> entry = it.next();
if (data.containsKey(entry.getValue())) {
it.remove();//Already exists; this will save space
} else {
data.put(entry.getValue(), str.indexOf(entry.getKey()));
}
}
//You can now remove original counts2 now
Integer indexOf3 = data.get(Long.valueOf(3));
System.out.println(str.get(indexOf3) + " @ " + data.get(Long.valueOf(3)));
//Original code
for (String key : counts2.keySet()) {
if (counts2.get(key) == 3) {
System.out.println(key + " @ " + str.indexOf(key));
break;
}
}
Bonus note:
Your use case reminded me of how Redis optimizes memory usage for hashes. This is an interesting approach, should you consider adding Redis to your stack.