1

I want to cache a large number of Java objects (String, byte[]) with a composite key (String, int) in a cache like JCS or Infinispan.

The keys can be grouped by the string part (let's call it ID) of it:

KEY = VALUE
-------------
A 1 = valueA1
A 4 = valueA4
A 5 = valueA5
B 9 = valueB9
C 3 = valueC3
C 7 = valueC7

I need to remove elements grouped by the ID part of the key, so for example A should remove A 1, A 4 and A 5.

First I tried something like this:

   final List<String> keys = cache.keySet()
            .stream().filter(k -> k.getId().equals(id)).collect(Collectors.toList());
    keys.forEach(cache::remove);

While this works, it is - not surprising - very expensive and thus slow.

So I tried another approach by using only the ID as key and group the values in a map:

KEY = VALUE
---------------------------------------------
  A = {1 = valueA1, 4 = valueA4, 5 = valueA5}
  B = {9 = valueB9}
  C = {3 = valueC3, 7 = valueC7}

Removing a group is then very efficient:

cache.remove(id);

But putting requires a get:

    Map<Integer, Value> map = cache.get(key.getId());
    if (map == null) {
        map = new HashMap<>();
    }
    map.put(key.getInt(), value);

    cache.put(key.getId(), map);

Now there are less elements in the cache with a simpler key, but the values are larger and more complex. Testing with hundreds of thousands of elements in the cache, deletes are fast and puts and gets don't seem to be noticeably slower.

Is this a valid solution or are there better approaches?

Torsten Römer
  • 3,834
  • 4
  • 40
  • 53

1 Answers1

1

I suggest you use computeIfAbsent and save a put and get invocation as follows:

cache.computeIfAbsent(key.getId(), k -> new HashMap<Integer,Value>()).put(key.getInt(),value);

This method ensures the creation of the secondary map only if it is not already mapped in the primary map, and saves the need for an additional get invocation since it returns the secondary map mapped to the primary key.

References:

Rann Lifshitz
  • 4,040
  • 4
  • 22
  • 42