Длина бесконечного IntStream?
Я создал randomIntStream следующим образом:
final static PrimitiveIterator.OfInt startValue = new Random().ints(0, 60).iterator();
В документации говорится, что этот поток на самом деле бесконечен.
Я хочу понять, что там происходит на заднем плане.
ints(0,60)
порождает бесконечный поток целых чисел. Если это бесконечно, почему моя машина не пропускает какую-либо память?
Интересно, сколько чисел действительно создано и если эта реализация может вызвать ошибку в точке, где поток все еще заканчивается? Или этот поток будет постоянно наполняться новыми целыми точками "на лету", и это действительно никогда не заканчивается?
И если я уже задаю этот вопрос, какова сейчас лучшая практика генерации случайных чисел в наши дни?
Ответы
Ответ 1
Если быть точным,
IntStream java.util.Random.ints(int randomNumberOrigin, int randomNumberBound)
возвращает:
эффективно неограниченный поток псевдослучайных значений int, каждый из которых соответствует данному происхождению (включительно) и связанному (исключительному).
Это не означает бесконечность. Глядя на Javadoc, вы увидите примечание к реализации, в котором говорится, что он фактически ограничивает возвращаемые IntStream
Long.MAX_VALUE
:
Примечание по реализации:
Этот метод реализуется так, чтобы он был эквивалентен ints (Long.MAX_VALUE, randomNumberOrigin, randomNumberBound).
Конечно, Long.MAX_VALUE
- очень большое число, и поэтому возвращаемый IntStream
можно рассматривать как "эффективно" без ограничений. Например, если вы потребляете 1000000 int
этого потока каждую секунду, вам потребуется около 292471 года для исчерпания элементов.
Тем не менее, как упоминалось в других ответах, IntStream
генерирует столько чисел, сколько требуется его потребителю (т. IntStream
Операция терминала, которая потребляет int
s).
Ответ 2
Поток бесконечен¹, поэтому вы можете генерировать столько ints, сколько хотите, но не исчерпываете. Это не означает, что он продолжает генерировать их, когда вы не просите о них.
Сколько чисел действительно генерируется, зависит от кода, который вы пишете. Каждый раз, когда вы извлекаете значение из итератора, генерируется значение. В фоновом режиме ничего не генерируется, поэтому нет "лишней" памяти.
¹ в отношении вашей жизни, см. Ответ Эрана
Ответ 3
Как сказал @Kayaman в его ответе. Поток бесконечен в том смысле, что числа могут генерироваться вечно. Дело в том, что слово может. Он только генерирует числа, если вы действительно запрашиваете их. Он не просто генерирует X количество чисел, а затем сохраняет их где-нибудь (если вы не скажете об этом).
Поэтому, если вы хотите сгенерировать n (где n - целое число) случайных чисел. Вы можете просто вызвать перегрузку ints(0, 60)
, ints(n, 0, 60)
в потоке, возвращаемом Random#ints()
:
new Random().ints(n, 0, 60)
Выше все равно не будет генерировать n случайных чисел, потому что это IntStream
который лениво выполняется. Поэтому, когда вы не используете терминальную операцию (например, collect()
или forEach()
), ничего действительно не происходит.
Ответ 4
В потоках нет (в общем случае 1) хранить все свои элементы в какой-либо структуре данных:
Нет хранения. Поток не является структурой данных, в которой хранятся элементы; вместо этого он передает элементы из источника, такого как структура данных, массив, функция генератора или канал ввода-вывода, через конвейер вычислительных операций.
Вместо этого каждый элемент потока вычисляется один за другим каждый раз, когда поток продвигается. В вашем примере каждый случайный int
будет фактически вычисляться при вызове startValue.nextInt()
.
Поэтому, когда мы делаем, например, new Random().ints(0,60)
, тот факт, что поток эффективно бесконечен, не является проблемой, потому что никакой случайный int
фактически не вычисляется до тех пор, пока мы не выполним какое-либо действие, которое пересекает поток. Как только мы проходим поток, int
вычисляются только тогда, когда мы запрашиваем их.
Вот небольшой пример использования Stream.generate
(также бесконечный поток), который показывает этот порядок операций:
Stream.generate(() -> {
System.out.println("generating...");
return "hello!";
})
.limit(3)
.forEach(elem -> {
System.out.println(elem);
});
Результат этого кода:
generating...
hello!
generating...
hello!
generating...
hello!
Обратите внимание, что наш поставщик генератора вызывается один раз перед каждым вызовом нашего потребителя forEach
, и не более того. Если мы не использовали limit(3)
, программа могла работать вечно, но у нее не хватило бы памяти.
Если бы мы выполнили new Random().ints(0,60).forEach(...)
, он будет работать одинаково. Поток будет делать random.nextInt(60)
один раз перед каждым вызовом для потребителя forEach
. Элементы не будут накапливаться нигде, если мы не использовали какое-то действие, которое требовало этого, например, distinct()
или коллекционер, а не forEach
.
- Некоторые потоки, вероятно, используют структуру данных за кулисами для временного хранения. Например, обычно используется стек во время алгоритмов обхода дерева. Кроме того, некоторые потоки, такие как созданные с помощью
Stream.Builder
, требуют, чтобы структура данных Stream.Builder
их элементы.
Ответ 5
Создание генератора не генерирует никаких чисел. Понятно, что этот генератор будет постоянно генерировать новые числа; нет смысла, чтобы он не возвращал следующее значение, когда его спрашивали.