4
\$\begingroup\$

I made an attempt to implement a Properties File MRU Cache with a limited cache size which are of the most recently used properties can only be held in. A read miss from this cache leads to a read from the file for that particular property and keep it in the cache until it goes out of the limit where other recently properties gets read into the cache from the file.

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.LinkedHashMap;

public class PropertiesFileMRUCache {

    private LinkedHashMap<String, String> propertiesMap = new LinkedHashMap<>(
            10);

    private int limit = 8;

    private String propertiesFile = "default.properties";

    public PropertiesFileMRUCache() {

    }

    public PropertiesFileMRUCache(String propertiesFile) {
        this.propertiesFile = propertiesFile;
    }

    public PropertiesFileMRUCache(int limit, String propertiesFile) {
        this(propertiesFile);
        this.limit = limit;
    }

    public String getCapital(String country) {
        String capital = propertiesMap.get(country);
        if (capital == null) {
            capital = readFromFile(country);
            if (propertiesMap.size() == limit) {
                propertiesMap.remove(propertiesMap.keySet().iterator().next());
            }
        } else {
            propertiesMap.remove(country);
        }
        propertiesMap.put(country, capital);
        return capital;
    }

    private String readFromFile(String country) {
        try (BufferedReader br = new BufferedReader(new FileReader(
                propertiesFile))) {
            for (String line; (line = br.readLine()) != null;) {
                if (line.startsWith(country)) {
                    return line.substring(line.indexOf('=') + 1);
                }
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) {
        PropertiesFileMRUCache pfc = new PropertiesFileMRUCache("res\\CountryCapital.properties");
        pfc.getCapital("India");
        pfc.getCapital("USA");
        pfc.getCapital("UK");
        pfc.getCapital("Kuwait");
        pfc.getCapital("Iraq");
        pfc.getCapital("Canada");
        pfc.getCapital("Australia");
        pfc.getCapital("India");
        pfc.getCapital("India");
        pfc.getCapital("India");
        pfc.getCapital("Germany");
        pfc.getCapital("India");
        pfc.getCapital("Germany");
        pfc.getCapital("China");
        pfc.getCapital("Pakistan");
        System.out.println(pfc.propertiesMap);
    }
}

For testing purpose I provided a user specified property as res\CountryCapital.properties with the default cache limit as 8. The content of the CountryCapital.properties is provided below for reference:

India=New Delhi
USA=Washington
Egypt=Cairo
Srilanka=Columbo
South Korea=Seoul
Tailand=Bangkok
Qutar=Doha
Lebanon=Beirut
Canada=Toronto
Norway=Oslo
Sweden=Stockholm
Finland=Helsinki
China=Beiging
Japan=Tokyo
Bangladesh=Dhaka
Pakistan=Karachi
Afganistan=Kabul
Australia=Sydney
Newzeland=Wellington
Ireland=Dublin
Germany=Berlin
Itlay=Rome
Nepal=Kadmantu
North Korea=Pyongyang
Brazil=Brazilia
South Africa=Cape Town
France=Paris
Czech Repulic=Prague
Austria=Vienna
Israel=Jerusalem
Kuwait=Kuwait City
Greece=Athens
Iraq=Bagdad
Syria=Demascaus
Denmark=Copengahen
UK=London
\$\endgroup\$
3
\$\begingroup\$

There's a little-known, though powerful feature of Java's LinkedHashMap code, and it is purpose-built for managing caches like yours. In your case, you should create the Map with the access-order constructor:

private static final class MyCache extends LinkedHashMap<String, String> {

    private final int limit;

    public MyCache(int limit) {
        super(limit, 0.75f, true);
        this.limit = limit;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {
        return size() > limit;
    }
}

Note how you can override the removeEldestEntry method?

If you put this all together in an application, it looks like:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.LinkedHashMap;
import java.util.Map;


public class PropertiesFileMRUCache {
    private static final class MyCache extends LinkedHashMap<String, String> {

        private final int limit;

        public MyCache(int limit) {
            super(limit, 0.75f, true);
            this.limit = limit;
        }

        @Override
        protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {
            return size() > limit;
        }
    }

    private final MyCache cache;

    private final Path propertiesFile;

    public PropertiesFileMRUCache(int limit, String file) {
        cache = new MyCache(limit);
        propertiesFile = Paths.get(file);
    }

    public String getCapital(String country) {
        return cache.computeIfAbsent(country, c -> readFromFile(c));
    }

    private String readFromFile(String country) {
        final String prefix = country + "=";
        try (Stream<String> lines = Files.lines(propertiesFile)){
            return lines.filter(line -> line.startsWith(prefix))
                    .map(line -> line.substring(prefix.length()))
                    .findFirst()
                    .orElse(null);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }


    public static void main(String[] args) {
        PropertiesFileMRUCache pfc = new PropertiesFileMRUCache(8, "ccap.properties");
        pfc.getCapital("India");
        pfc.getCapital("USA");
        pfc.getCapital("UK");
        pfc.getCapital("Kuwait");
        pfc.getCapital("Iraq");
        pfc.getCapital("Canada");
        pfc.getCapital("Australia");
        pfc.getCapital("India");
        pfc.getCapital("India");
        pfc.getCapital("India");
        pfc.getCapital("Germany");
        pfc.getCapital("India");
        pfc.getCapital("Germany");
        pfc.getCapital("China");
        pfc.getCapital("Pakistan");
        System.out.println(pfc.cache);
    }
}

Note how I have also hacked around in your readFromFile method. I have made it NIO2-based using Paths, and Files. This is the best practice way to do it in Java 8. I have also streamed the lines, which I believe will help. A try-with-resources cleanly closes the stream too. Additionally, a computeIfAbsent is the right way to reuse an existing entry, and create a missing one.

There are a number of things you should identify as being different. Consider the extension of the LinkedHashMap to be the most significant, and the improved use of the computeIfAbsent to be second most important.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.