Сегодня я узнал из некоторого курса JS, что такое запоминание, и попытался реализовать его на Java.У меня была простая рекурсивная функция для вычисления n-го числа Фибоначчи:
long fib(long n) {
if (n < 2) {
return n;
}
return fib(n - 1) + fib(n - 2);
}
Затем я решил использовать HashMap для кэширования результатов рекурсивного метода:
private static <I, O> Function<I, O> memoize(Function<I, O> fn) {
final Map<I, O> cache = new HashMap<>();
return in -> {
if (cache.get(in) != null) {
return cache.get(in);
} else {
O result = fn.apply(in);
cache.put(in, result);
return result;
}
};
}
Это работало какЯ ожидал, и это позволило мне запоминать fib()
вот так memoize(this::fib)
Затем я гуглил тему запоминания в Java и нашел вопрос: Метод запоминания Java , где computeIfAbsent
былопредложил намного короче, чем мое условное выражение.
Итак, мой последний код, который я ожидал сработать, был:
public class FibMemo {
private final Function<Long, Long> memoizedFib = memoize(this::slowFib);
public long fib(long n) {
return memoizedFib.apply(n);
}
long slowFib(long n) {
if (n < 2) {
return n;
}
return fib(n - 1) + fib(n - 2);
}
private static <I, O> Function<I, O> memoize(Function<I, O> fn) {
final Map<I, O> cache = new HashMap<>();
return in -> cache.computeIfAbsent(in, fn);
}
public static void main(String[] args) {
new FibMemo().fib(50);
}
}
Так как я использовал Java 11, я получил:
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.HashMap.computeIfAbsent(HashMap.java:1134)
at algocasts.matrix.fibonacci.FibMemo.lambda$memoize$0(FibMemo.java:24)
at algocasts.matrix.fibonacci.FibMemo.fib(FibMemo.java:11)
at algocasts.matrix.fibonacci.FibMemo.slowFib(FibMemo.java:19)
at java.base/java.util.HashMap.computeIfAbsent(HashMap.java:1133)
at algocasts.matrix.fibonacci.FibMemo.lambda$memoize$0(FibMemo.java:24)
Проблема быстро привела меня к следующему вопросу, который очень похож: Рекурсивный вызов ConcurrentHashMap.computeIfAbsent () никогда не прекращается.Ошибка или «фича»?
, за исключением того, что Лукас использовал ConcurrentHashMap и получил бесконечный цикл.В статье, связанной с вопросом, Лукас посоветовал:
Самым простым решением для конкретной проблемы использования сайта было бы не использовать ConcurrentHashMap, а вместо этого просто HashMap:
static Map<Integer, Integer> cache = new HashMap<>();
Это именно то, что я пытался сделать, но безуспешно с Java 11. Я эмпирически обнаружил, что HashMap генерирует исключение ConcurrentModificationException начиная с Java 9 (спасибо SDKMAN):
sdk use java 9.0.7-zulu && javac Test.java && java Test # throws ConcurrentModificationException
sdk use java 8.0.202-zulufx && javac Test.java && java Test # works as expected
Вот краткое изложение моих попыток:
- Java 8 && ConcurrentHashMap -> работает, если ввод <13, в противном случае бесконечный цикл </li>
- Java 9 && ConcurrentHashMap -> работает, если ввод<13, зависает, если input = 14, выдает <code>IllegalStateException: Recursive update, если input 50
- Java 8 && HashMap -> Работает!
- Java 9 && HashMap -> Throws
ConcurrentModificationException
после ввода> = 3
Я хотел бы знать, почему возникает исключение ConcurrentModificationException при попытке использовать HashMap в качестве кэша для рекурсивной функции?Была ли это ошибка в Java 8, позволяющая мне сделать это, или это другая ошибка в Java> 9, которая вызывает исключение ConcurrentModificationException?