В нашем приложении мы используем Random и SecureRandom для генерации очень больших наборов информации для некоторых клиентов. После некоторых измерений мы поняли, что было быстрее и менее интенсивно использовать память для повторного создания информации с использованием начальных чисел, хранящихся на диске, чем для хранения и чтения этой информации. Я просмотрел javadoc и не нашел ничего, что гарантировало бы, что, учитывая константу seed n, результат, скажем, new Random(n).nextInt() будет одинаковым для разных версий Java.

У меня вопрос : можно ли сделать безопасное предположение для Java 8 и предыдущих версий, что new Random(n).nextInt() в Java 8 должен возвращать то же значение, что и в предыдущих версиях Java?

(Честно говоря, я приму ответы на вышеуказанный вопрос, даже если они не ответят на приведенный ниже вопрос.)

Хотя я знаю, что нет никаких гарантий, что это будет верно и для более поздних версий Java, и что этот второй вопрос вызовет некоторые мнения, как вы думаете, каковы шансы того, что будущая версия Java изменит алгоритм, используемый для генерации псевдослучайных числа с использованием Random и SecureRandom?

Благодарность!

8
Jonathan Pitre 16 Окт 2015 в 16:06

2 ответа

Лучший ответ

Да, это гарантировано для Random во всех версиях до Java 8. Однако, похоже, нет никаких аналогичных гарантий для SecureRandom.

Если вы посмотрите Javadocs for Random, , вы можете увидеть это:

Если два экземпляра Random создаются с одним и тем же начальным значением и для каждого выполняется одна и та же последовательность вызовов методов, они будут генерировать и возвращать идентичные последовательности чисел. Чтобы гарантировать это свойство, для класса Random указаны определенные алгоритмы. Реализации Java должны использовать все алгоритмы, показанные здесь для класса Random, ради абсолютной переносимости кода Java. Однако подклассам класса Random разрешается использовать другие алгоритмы, если они придерживаются общих контрактов. для всех методов.

Акцент мой. Таким образом, некоторые размышления над этой проблемой были явно заданы, и было принято решение указать базовые алгоритмы в документации для класса. Теоретически люди могут предоставить другую реализацию, но тогда она не будет соответствовать спецификации Java.

Для получения дополнительных доказательств посмотрите, что это за реализация между версиями. Например, метод next определен точно так же во всех версиях, начиная с Java 1.4, по крайней мере (насколько я смотрел).

seed = (seed * 0x5DEECE66DL + 0xBL) & ((1L << 48) - 1);
return (int)(seed >>> (48 - bits));

Поскольку это одно и то же, у вас есть четкая гарантия, что не только возвращаемые значения будут одинаковыми для разных версий, но и вы сможете надежно вычислить одни и те же значения самостоятельно, используя этот алгоритм.

Однако я не могу найти никаких аналогичных гарантий для SecureRandom, и поскольку Random говорит, что подклассы могут нарушать это правило, то нет гарантии, что оно будет согласовано между версиями. Единственное доказательство, которое я могу найти для этого, - это сообщение на форуме < / a> кого-то, кто наблюдает за генерируемыми несовместимыми значениями. Согласно ответу Яна МакЛайрда, SecureRandom на практике, похоже, генерирует согласованные значения, поэтому вы можете решить, что это стоит рискнуть предположить, что это сработает. Однако такое поведение не гарантируется.

Чтобы ответить на вторую часть вашего вопроса, теоретически возможно, что они могли бы это изменить. Однако, поскольку в настоящее время это четко определенная операция, и с учетом того, насколько усердно Oracle работает, чтобы избежать нарушения обратной совместимости, я считаю ее невероятно маловероятной.

9
Community 23 Май 2017 в 12:18

Чтобы прямо ответить на ваш точный вопрос: «Является ли это безопасным предположением для Java 8 и более ранних версий?» Я сделал это (удобно, что я держу несколько старых JDK как раз для таких случаев).

import java.util.Random;

public class RandomTest {
    public static void main(String[] args) {
        System.out.println("Random = " + new Random(0).nextInt());
        System.out.println("SecureRandom = " + new SecureRandom(new byte[] {0, 0, 0, 0}).nextInt());
    }
}

И результаты:

> & 'C:\Program Files\Java\jdk1.6.0_45\bin\javac.exe' .\RandomTest.java
> & 'C:\Program Files\Java\jdk1.6.0_45\bin\java.exe' RandomTest
Random = -1155484576
SecureRandom = -1439655693

> & 'C:\Program Files\Java\jdk1.7.0_17\bin\javac.exe' .\RandomTest.java
> & 'C:\Program Files\Java\jdk1.7.0_17\bin\java.exe' RandomTest
Random = -1155484576
SecureRandom = -1439655693

> & 'C:\Program Files\Java\jdk1.8.0_25\bin\javac.exe' .\RandomTest.java
> & 'C:\Program Files\Java\jdk1.8.0_25\bin\java.exe' RandomTest
Random = -1155484576
SecureRandom = -1439655693

Так да. Эмпирически кажется, что это безопасное предположение как минимум для как минимум Java 6, 7 и 8.

ИЗМЕНИТЬ Обновлено выше для SecureRandom. SecureRandom не имеет конструктора, который принимает int, поэтому я добавил четыре байта нулей (для тех же 32 бита). Поведение одинаково для всех протестированных версий Java, но не дает такого же результата, как java.util.Random .

3
Ian McLaird 16 Окт 2015 в 14:04