概述
在这个快速教程中,我们将学习如何使用Java核心类生成不重复的随机数。首先,我们将从头开始实现一些解决方案,然后利用Java 8及更高版本的功能,以获得更可扩展的方法。
2. 从小范围生成随机数
如果我们需要的数字范围较小,我们可以不断将递增的数字添加到列表中,直到达到大小n。然后,我们调用Collections.shuffle()
方法,它的时间复杂度是线性的。之后,我们就得到了一个随机排列的独特数字列表。让我们创建一个实用类来生成并使用这些数字:
public class UniqueRng implements Iterator<Integer> {
private List<Integer> numbers = new ArrayList<>();
public UniqueRng(int n) {
for (int i = 1; i <= n; i++) {
numbers.add(i);
}
Collections.shuffle(numbers);
}
}
创建对象后,我们就会得到从1到n的随机顺序数字。注意我们实现了Iterator
,因此每次调用next()
时都会得到一个随机数。此外,我们还可以通过hasNext()
检查是否还有数字剩余。因此,我们需要重写它们:
@Override
public Integer next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return numbers.remove(0);
}
@Override
public boolean hasNext() {
return !numbers.isEmpty();
}
remove()
方法会返回列表中移除的第一个项目。同样地,如果我们没有对集合进行随机排序,我们也可以传入一个随机索引。但提前在构造时打乱顺序的好处是可以预先知道整个序列。
2.1. 实际应用
使用时,只需选择需要的数字并消耗它们:
UniqueRng rng = new UniqueRng(5);
while (rng.hasNext()) {
System.out.print(rng.next() + " ");
}
这可能会产生如下输出:
4 1 2 5 3
3. 从大范围生成随机数
如果需要的数字范围更大,但只使用其中一部分,我们需要采取不同的策略。首先,我们不能依赖于向ArrayList
中添加随机数,因为这可能导致重复。因此,我们将使用Set
,因为它保证了唯一的项。然后,我们将使用LinkedHashSet
实现,因为它维护插入顺序。
这次,我们将在一个循环中向集合中添加元素,直到达到大小n。同时,我们将使用Random
生成0到max
之间的随机整数:
public class BigUniqueRng implements Iterator<Integer> {
private Random random = new Random();
private Set<Integer> generated = new LinkedHashSet<>();
public BigUniqueRng(int size, int max) {
while (generated.size() < size) {
Integer next = random.nextInt(max);
generated.add(next);
}
}
}
请注意,我们不需要检查数字是否已经在集合中,因为add()
方法会处理这个。由于无法通过索引删除项目,我们需要借助Iterator
来实现next()
:
public Integer next() {
Iterator<Integer> iterator = generated.iterator();
Integer next = iterator.next();
iterator.remove();
return next;
}
4. 利用Java 8+功能
虽然自定义实现更易于重用,但我们可以通过使用Stream
创建一个解决方案。从Java 8开始,Random
有一个ints()
方法,它返回一个IntStream
。我们可以流式处理它,并像之前一样设定要求,如范围和限制。让我们结合这些特性,将结果收集到一个Set
中:
Set<Integer> set = new Random().ints(-5, 15)
.distinct()
.limit(5)
.boxed()
.collect(Collectors.toSet());
遍历的集合可能会输出类似以下内容:
-5 13 9 -4 14
使用ints()
,从负整数开始的范围更加简单。但必须小心,避免生成无限流,例如如果不调用limit()
。
5. 总结
在这篇文章中,我们为两种场景编写了解决方案,生成不重复的随机数。首先,我们使这些类可迭代,以便轻松消费它们。然后,我们使用流创建了一个更自然的解决方案。
如往常一样,源代码可以在GitHub上找到。