Wednesday, September 20, 2023

How to Sort HashMap in Java based on Keys and Values

HashMap is not meant to keep entries in sorted order, but if you have to sort HashMap based upon keys or values, you can do that in Java. Sorting HashMap on keys is quite easy, all you need to do is to create a TreeMap by copying entries from HashMap. TreeMap is an implementation of SortedMap and keeps keys in their natural order or a custom order specified by Comparator provided while creating TreeMap. This means you can process entries of HashMap in sorted order but you cannot pass a HashMap containing mappings in a specific order, this is just not possible because HashMap doesn't guarantee any order.

On other hand, sorting HashMap by values is rather complex because there is no direct method to support that operation. You need to write code for that. In order to sort HashMap by values you can first create a Comparator, which can compare two entries based on values. 

Then get the Set of entries from Map, convert Set to List, and use Collections.sort(List) method to sort your list of entries by values by passing your customized value comparator. This is similar to how you sort an ArrayList in Java


Half of the job is done by now. Now create a new LinkedHashMap and add sorted entries into that. Since LinkedHashMap guarantees insertion order of mappings, you will finally have a Map where contents are sorted by values.


5 Steps to sort HashMap by values

One difference between sorting HashMap by keys and values is that it can contain duplicate values by not duplicate keys. You cannot use TreeMap here because it only sorts entries by keys. In this case, you need to :
  1. Get all entries by calling entrySet() method of Map
  2. Create a custom Comparator to sort entries based upon values
  3. Convert entry set to list
  4. Sort entry list by using Collections.sort() method by passing your value comparator
  5. Create a LinkedHashMap by adding entries in sorted order.




Steps to sort HashMap by keys

There are two ways to sort HashMap by keys, first by using TreeMap and second by using LinkedHashMap. If you want to sort using TreeMap then it's simple, just create a TreeMap by copying the content of HashMap. 

On the other hand, if you want to create a LinkedHashMap then you first need to get a key set, convert that Set to a List, sort that List, and then add them into LHM in the same order. Remember HashMap can contain one null key but duplicate keys are not allowed.





HashMap Sorting by Keys and Values in Java Example

Here is our sample Java program to sort a HashMap first by keys and then by values. This program is divided into two part, the first part sorts HashMap by keys, and the second part sorts it by values. The second part is more tricky the first part as there is no native Map implementation that supports any order for values. 

In order to sort a HashMap by values, we had to create our own Comparator implementation which compares each entry by values to arrange them in a particular order. You can see that our valueComparator overrides compare() method and accepts two entries. Later it retrieves values from those entries and compare them and return result. 

Since there is no method in Java Collection API to sort Map, we need to use Collections.sort() method which accepts a List. This involves creating a temporary ArrayList with entries for sorting purpose and then again copying entries from sorted ArrayList to a new LinkedHashMap to keep them in sorted order. 

Finally, we create a HashMap from that LinkedHashMap, which is what we needed.

import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;

/**
 * How to sort HashMap in Java by keys and values. 
 * HashMap doesn't guarantee any order, so you cannot rely on it, even if
 * it appear that it storing entries in a particular order, because
 * it may not be available in future version e.g. earlier HashMap stores
 * integer keys on the order they are inserted but from Java 8 it has changed.
 * 
 * @author WINDOWS 8
 */

public class HashMapSorting{

    public static void main(String args[]) throws ParseException {
        
        // let's create a map with Java releases and their code names
        HashMap<String, String> codenames = new HashMap<String, String>();
        
        codenames.put("JDK 1.1.4", "Sparkler");
        codenames.put("J2SE 1.2", "Playground");
        codenames.put("J2SE 1.3", "Kestrel");
        codenames.put("J2SE 1.4", "Merlin");
        codenames.put("J2SE 5.0", "Tiger");
        codenames.put("Java SE 6", "Mustang");
        codenames.put("Java SE 7", "Dolphin");
        
        System.out.println("HashMap before sorting, random order ");
        Set<Entry<String, String>> entries = codenames.entrySet();
       
        for(Entry<String, String> entry : entries){
            System.out.println(entry.getKey() + " ==> " + entry.getValue());
        }
        
        // Now let's sort HashMap by keys first 
        // all you need to do is create a TreeMap with mappings of HashMap
        // TreeMap keeps all entries in sorted order
        TreeMap<String, String> sorted = new TreeMap<>(codenames);
        Set<Entry<String, String>> mappings = sorted.entrySet();
        
        System.out.println("HashMap after sorting by keys in ascending order ");
        for(Entry<String, String> mapping : mappings){
            System.out.println(mapping.getKey() + " ==> " + mapping.getValue());
        }
        
        
        // Now let's sort the HashMap by values
        // there is no direct way to sort HashMap by values but you
        // can do this by writing your own comparator, which takes
        // Map.Entry object and arrange them in order increasing 
        // or decreasing by values.
        
        Comparator<Entry<String, String>> valueComparator 
               = new Comparator<Entry<String,String>>() {
            
            @Override
            public int compare(Entry<String, String> e1, Entry<String, String> e2) {
                String v1 = e1.getValue();
                String v2 = e2.getValue();
                return v1.compareTo(v2);
            }
        };
        
        // Sort method needs a List, so let's first convert Set to List in Java
        List<Entry<String, String>> listOfEntries 
                  = new ArrayList<Entry<String, String>>(entries);
        
        // sorting HashMap by values using comparator
        Collections.sort(listOfEntries, valueComparator);
        
        LinkedHashMap<String, String> sortedByValue 
                    = new LinkedHashMap<String, String>(listOfEntries.size());
        
        // copying entries from List to Map
        for(Entry<String, String> entry : listOfEntries){
            sortedByValue.put(entry.getKey(), entry.getValue());
        }
        
        System.out.println("HashMap after sorting entries by values ");
        Set<Entry<String, String>> entrySetSortedByValue = sortedByValue.entrySet();
        
        for(Entry<String, String> mapping : entrySetSortedByValue){
            System.out.println(mapping.getKey() + " ==> " + mapping.getValue());
        }
    }

    
}

Output:
HashMap before sorting, random order 
Java SE 7 ==> Dolphin
J2SE 1.2 ==> Playground
Java SE 6 ==> Mustang
J2SE 5.0 ==> Tiger
J2SE 1.3 ==> Kestrel
J2SE 1.4 ==> Merlin
JDK 1.1.4 ==> Sparkler
HashMap after sorting by keys in ascending order 
J2SE 1.2 ==> Playground
J2SE 1.3 ==> Kestrel
J2SE 1.4 ==> Merlin
J2SE 5.0 ==> Tiger
JDK 1.1.4 ==> Sparkler
Java SE 6 ==> Mustang
Java SE 7 ==> Dolphin
HashMap after sorting entries by values 
Java SE 7 ==> Dolphin
J2SE 1.3 ==> Kestrel
J2SE 1.4 ==> Merlin
Java SE 6 ==> Mustang
J2SE 1.2 ==> Playground
JDK 1.1.4 ==> Sparkler
J2SE 5.0 ==> Tiger

That's all about how to sort HashMap by keys and values in Java. Remember, HashMap is not intended to keep entries in sorted order, so if you have a requirement to always keep entries in a particular order, don't use HashMap instead use TreeMap or LinkedHashMap

This method should only be used to cater to Adhoc needs where you receive a HashMap from some part of legacy code and you have to sort it first to process entries. If you have control of creating the Map initially prefer the right implementation of Map than just HashMap.

And, now is the quiz time? Does sorting a HashMap really make sense? Shouldn't you be using TreeMap or LinkedHashMap if you need to keep your data in sorted order of keys? 

11 comments:

  1. This is a great article. I once had a challenge along these lines that stumped me one time... and till date I still don't know how to do it though I know it can be done somehow. So I'm just going to put it out there incase someone has encountered\solved it before and feels like sharing:

    Basically, you know how hashmaps & sets don't show duplicates, well, I tried adding the values of duplicate map-keys, so that when displaying a map-key, rather than the hashmap showing one random set(of key to a mapped value for duplicate keys), I wanted to sort it in a way so that whichever duplicate key it displayed, the total value for the duplicate keys is shown in place of the value of the duplicate. I understood just by thinking through what I wanted to do that I had to create a comparator for it. However, I couldn't still create a working comparator for it.

    For a visual eg of the type of data I'm talking about:
    eg if the map key values were: map<String, Integer>
    jon 23
    Ike 65
    Linda 75
    jon 80
    Helen 32 etc
    Your output after sorting should have: jon 103

    ReplyDelete
    Replies
    1. Hi Ike,
      HashMap doesn't show any random key out of duplicates. Actually it can't keep duplicates within it. Whenever there is a duplicate key added to the map it overrides the existing one. So each time there will be only one key available with that name. For your desired output what you have to do is that you have to create another class extending HashMap. Whenever there will will be a entry put to the map you can query if it already exists in that map? if it exists then update the existing value of that key by summession of both the values.

      Delete
  2. Hi,

    I have lil doubt regarding below line


    LinkedHashMap sortedByValue = new LinkedHashMap(listOfEntries.size());

    Could you please tell, what is need of passing "listOfEntries.size()" while creating LinkedHashMap?

    ReplyDelete
    Replies
    1. Hello @Anonymous, that is to prevent resizing of LinkedHashMap. You might know that Collection start with default size e.g. 16 and then expand themselves by doubling the size or by a factor when size breaches load factor. If original Map is large than LinkedHashMap will go many round of re-sizing which is expensive as it require copying content from one array to another, to avoid that we have initialized with proper capacity. It's always good practice to initialize Collection with proper capacity, its must when you know the exact size in advance.

      Delete
  3. Thanks a lot. Explained very well.

    ReplyDelete
  4. You can sort the HashMap in Java 8 with less than 3 lines of code.
    map.entrySet().stream()
    .sorted(Map.EntrycomparingByValue().reversed())
    .collect(Collectors.toMap(e -> e.getKey(), e -> e.value(), (e1,e2) -> e1));

    ReplyDelete
    Replies
    1. Hello Anonymous, this will not work becuase you are collecting result in a Map which may not be ordered and all sorting will be lost. Instead you should use a LinkedHashMap to collect entries e.g.
      Collections.toMap(LinkedHashMap:new, ....)

      Delete
    2. map.entrySet().stream()
      .sorted(Map.Entry.comparingByValue())
      .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
      (oldValue, newValue) -> oldValue, LinkedHashMap::new));

      Delete
  5. If i have custom object that is stored as value in map and i want to sort it on the basis of id. What should i use Hashmap or treemap becuase in both the cases i need to implement Comparator in my custom object and override @compare method to provide my own implementation.

    if answer is treemap then Why because treemap stores the value in natural order and it will not benefit me in any case.

    ReplyDelete

Feel free to comment, ask questions if you have any doubt.