Java 当产生随机数字时,为什么它们比人们想象的更早开始重复,还有更好的方法吗?

Java 当产生随机数字时,为什么它们比人们想象的更早开始重复,还有更好的方法吗?,java,random,Java,Random,我试图产生许多由4位数字组成的随机字符串,它们不应该相互重复。我不知道确切的数字,但大约几百。我试过nextInt: public static String generateLogID() { Random rdm = new Random(); String s = ""; for (int i=0;i<4;i++) { String digit = String.valueOf(rdm.nextInt(9)); s = s.c

我试图产生许多由4位数字组成的随机字符串,它们不应该相互重复。我不知道确切的数字,但大约几百。我试过nextInt:

public static String generateLogID() {

    Random rdm = new Random();
    String s = "";
    for (int i=0;i<4;i++) {
        String digit = String.valueOf(rdm.nextInt(9));
        s = s.concat(digit);    
    }
    return s;
}
然而,当它在70号或80号附近时,它得到了重复字符串。理论上会有10*10*10*10的可能性,为什么它会这么快重复,我应该做些什么来避免重复?谢谢你的建议

我使用HashMap来保存所有记录,以避免重复,它工作得非常好

HashMap<Integer, String> map = new HashMap<Integer, String>();
int count = 0;
for(loop conditions){
 String id = IDGenerator.generateLogID();
                while(map.containsValue(id)){
                    id = IDGenerator.generateLogID();
                    }
                map.put(count, id);
                count++;
}
但我真正想知道的是,为什么这个生成器生成repeat这么快,还有其他生成方法可以降低重复率吗

到目前为止,80个随机的4位十进制数字中出现重复的几率为27.1%,100个这样的随机值增加到39.1%,118个这样的随机值增加到50%。因此,观察到的情况并不令人惊讶

这些几率可以计算为: p0=0 pi+1=1-1-pi*k-i/k 式中,k是等概率可能值的数量,此处k=10000

要生成不同的类随机数,我们可以

利用一种技术,使用适当的密码用一个恒定的密钥对计数器进行加密。这允许使用Ologk内存处理非常大的k,并且每个生成的ID的工作量随着Ologk的增长而增加。 使用ID生成[0..k-1]范围内整数的随机排列,然后ID是无序数组的第一个元素;这对于中等k的代码来说比较简单,但需要Ok logk内存和一个优雅的实现所需的初始工作,即在数组中搜索id。 到目前为止,80个随机的4位十进制数字中出现重复的几率为27.1%,100个这样的随机值增加到39.1%,118个这样的随机值增加到50%。因此,观察到的情况并不令人惊讶

这些几率可以计算为: p0=0 pi+1=1-1-pi*k-i/k 式中,k是等概率可能值的数量,此处k=10000

要生成不同的类随机数,我们可以

利用一种技术,使用适当的密码用一个恒定的密钥对计数器进行加密。这允许使用Ologk内存处理非常大的k,并且每个生成的ID的工作量随着Ologk的增长而增加。 使用ID生成[0..k-1]范围内整数的随机排列,然后ID是无序数组的第一个元素;这对于中等k的代码来说比较简单,但需要Ok logk内存和一个优雅的实现所需的初始工作,即在数组中搜索id。
您可以尝试使用java中的内置UUID生成器。文件 像这样

UUID.randomUUID().toString()

每次调用它时,它都会给您一个唯一的标识符。

您可以尝试java中内置的UUID生成器。文件 像这样

UUID.randomUUID().toString()
每次你调用它时,它都会给你一个唯一的标识符

当它在70号或80号左右时,它得到了重复字符串。理论上会有10*10*10*10的可能性,为什么它会这么快重复,我应该做些什么来避免重复

这是标准的一种变体。重复的次数比人们想象的要多。要意识到,要想保持唯一性,每个新号码都需要与以前的号码不同。随着以前的数字列表的增长,很快就会出现至少一个新数字与一个旧数字匹配的情况。如果有10^4个可能的数字,那么在随机生成118个数字之后,有50%的几率重复出现

代码中的一个小错误使问题更加复杂。的边界是独占的,因此您使用rdm.nextInt9将只生成0到8的数字。这意味着你只生成了9^4个可能的数字,并且在96个随机生成的数字与你观察到的非常接近之后,有50%的几率重复

好的,还有一些小事情:

与其单独生成4个随机数字,不如生成一个0到9999之间的数字,并将其填充到4个数字宽。我可以为您做填充:

return String.format("%04d", rdm.nextInt(10000));
在每次方法调用时创建一个新的随机数生成器效率低下。我建议您创建并使用静态实例:

private static final Random rdm = new Random();
public static String generateLogID() {
    return String.format("%04d", rdm.nextInt(10000));
}
或者有点草率但可能很好,调用Math.random并将结果敲入整数:

public static String generateLogID() {
    return String.format("%04d", (int)(Math.random() * 10000));
}
如何防止重复:

当前使用地图存储以前的数字的方式效率极低。生成的每个数字都调用containsValue,它必须缓慢地搜索前面的每个数字。HashMap可以按键快速搜索条目,但不能按值搜索。从你目前使用它的方式来看,它实际上是一个重量级的ArrayList

使用基于散列的结构来检测重复项是可以的,但要使其成为一个映射,而不是一个映射。像这样:

private static final Random random = new Random();
private static final HashSet<Integer> previousIDs = new HashSet<>();
public static synchronized String generateUniqueLogID() {
    if (previousIDs.size() == 10000) throw new RuntimeException("Out of IDs!");
    int id;
    do {
        id = random.nextInt(10000);
    } while (!previousIDs.add(id));
    return String.format("%04d", id);
}
当生成的ID数量接近最大值时,哈希集和位集的性能都会降低。例如,在生成9999个ID后,它希望在发现最后一个免费ID之前尝试10000次

一个更好的解决方案,再一次利用了小范围的数字,是将所有可能的ID填充到一个A中 rray,然后要生成ID,请从已知剩余ID中随机选择一个:

private static final int[] ids = new int[10000];
private static int remainingIDs = ids.length;
static {
    for (int i = 0; i < ids.length; i++) ids[i] = i;
}
private static final Random random = new Random();
public static synchronized String generateUniqueLogID() {
    if (remainingIDs == 0) throw new RuntimeException("Out of IDs!");
    int index = random.nextInt(remainingIDs);
    int id = ids[index];
    ids[index] = ids[--remainingIDs];
    return String.format("%04d", id);
}   
最后一种可能性:你真的需要随机排列的ID吗?也许这是一个愚蠢的问题,但我们不要忽略一个提供唯一ID的非常简单的方法:

private static int nextID = 0;
public static synchronized String generateUniqueLogID() {
    if (nextID == 10000) throw new RuntimeException("Out of IDs!");
    return String.format("%04d", nextID++);
}
当它在70号或80号左右时,它得到了重复字符串。理论上会有10*10*10*10的可能性,为什么它会这么快重复,我应该做些什么来避免重复

这是标准的一种变体。重复的次数比人们想象的要多。要意识到,要想保持唯一性,每个新号码都需要与以前的号码不同。随着以前的数字列表的增长,很快就会出现至少一个新数字与一个旧数字匹配的情况。如果有10^4个可能的数字,那么在随机生成118个数字之后,有50%的几率重复出现

代码中的一个小错误使问题更加复杂。的边界是独占的,因此您使用rdm.nextInt9将只生成0到8的数字。这意味着你只生成了9^4个可能的数字,并且在96个随机生成的数字与你观察到的非常接近之后,有50%的几率重复

好的,还有一些小事情:

与其单独生成4个随机数字,不如生成一个0到9999之间的数字,并将其填充到4个数字宽。我可以为您做填充:

return String.format("%04d", rdm.nextInt(10000));
在每次方法调用时创建一个新的随机数生成器效率低下。我建议您创建并使用静态实例:

private static final Random rdm = new Random();
public static String generateLogID() {
    return String.format("%04d", rdm.nextInt(10000));
}
或者有点草率但可能很好,调用Math.random并将结果敲入整数:

public static String generateLogID() {
    return String.format("%04d", (int)(Math.random() * 10000));
}
如何防止重复:

当前使用地图存储以前的数字的方式效率极低。生成的每个数字都调用containsValue,它必须缓慢地搜索前面的每个数字。HashMap可以按键快速搜索条目,但不能按值搜索。从你目前使用它的方式来看,它实际上是一个重量级的ArrayList

使用基于散列的结构来检测重复项是可以的,但要使其成为一个映射,而不是一个映射。像这样:

private static final Random random = new Random();
private static final HashSet<Integer> previousIDs = new HashSet<>();
public static synchronized String generateUniqueLogID() {
    if (previousIDs.size() == 10000) throw new RuntimeException("Out of IDs!");
    int id;
    do {
        id = random.nextInt(10000);
    } while (!previousIDs.add(id));
    return String.format("%04d", id);
}
当生成的ID数量接近最大值时,哈希集和位集的性能都会降低。例如,在生成9999个ID后,它希望在发现最后一个免费ID之前尝试10000次

一个更好的解决方案(同样利用了较小的数字范围)是将所有可能的ID填充到一个数组中,然后从已知的剩余ID中随机选择一个ID来生成一个ID:

private static final int[] ids = new int[10000];
private static int remainingIDs = ids.length;
static {
    for (int i = 0; i < ids.length; i++) ids[i] = i;
}
private static final Random random = new Random();
public static synchronized String generateUniqueLogID() {
    if (remainingIDs == 0) throw new RuntimeException("Out of IDs!");
    int index = random.nextInt(remainingIDs);
    int id = ids[index];
    ids[index] = ids[--remainingIDs];
    return String.format("%04d", id);
}   
最后一种可能性:你真的需要随机排列的ID吗?也许这是一个愚蠢的问题,但我们不要忽略一个提供唯一ID的非常简单的方法:

private static int nextID = 0;
public static synchronized String generateUniqueLogID() {
    if (nextID == 10000) throw new RuntimeException("Out of IDs!");
    return String.format("%04d", nextID++);
}

保留所有值的映射,并检查映射是否已包含新的随机值?为此使用HashMap类型。这里有一个字符串示例,感谢您的评论,我已经在问题中添加了HashMap方法,但是是否有任何函数可以降低重复,并且我不必仅对一个或两百个随机数使用HashMap?重复开始得很快,因为它是真正的随机数。区分手工制作的随机序列和真正随机序列的一个标准是重复次数太少。但我真正想知道的是,为什么这个生成器会这么快产生重复,这就是随机的意思。前三个数字也可能相等…保留一个所有值的映射,并检查映射是否已经包含新的随机值?为此使用HashMap类型。这里有一个字符串示例,感谢您的评论,我已经在问题中添加了HashMap方法,但是是否有任何函数可以降低重复,并且我不必仅对一个或两百个随机数使用HashMap?重复开始得很快,因为它是真正的随机数。区分手工制作的随机序列和真正随机序列的一个标准是重复次数太少。但我真正想知道的是,为什么这个生成器会这么快产生重复,这就是随机的意思。前三个数字也可能相等……如图所示,LFSR技术的一个问题是,知道生成的数字和掩码0x2015,就可以很容易地计算出下一个数字。即使不知道这个常数,下一个数字也可以从之前的一个小序列中计算出来。这与CS中公认的随机定义背道而驰:在计算上无法与真正的随机区分。@fgrieu CS不仅仅是密码学。在很多情况下,伪随机数是可以接受的。但是你是对的,如果OP要求数字不可用,那么LFSR是不合适的。在这种情况下,任何使用Random的行为都是不安全的,需要用SecureRandom来代替。同意,我应该写一篇..密码学中Random的最小定义。。。尽管如此,使用LFSR技术,任何奇数ID N至少为1,后面总是跟着N-1/2,这是一个非常容易识别的特性
蒂蒂!非常感谢你!这正是我想问的,谢谢你的建议!{ids[index]=ids[-remainingIDs];}非常棘手:只需用一个尚未使用的ID覆盖当前ID即可。如果当前ID的索引意外地成为列表中的最后一个索引,那么下次它将被过滤掉{random.nextIntremainingIDs;}如图所示的LFSR技术的一个问题是,知道生成的数字和掩码0x2015,下一个数字可以很容易地计算出来。即使不知道这个常数,下一个数字也可以从之前的一个小序列中计算出来。这与CS中公认的随机定义背道而驰:在计算上无法与真正的随机区分。@fgrieu CS不仅仅是密码学。在很多情况下,伪随机数是可以接受的。但是你是对的,如果OP要求数字不可用,那么LFSR是不合适的。在这种情况下,任何使用Random的行为都是不安全的,需要用SecureRandom来代替。同意,我应该写一篇..密码学中Random的最小定义。。。尽管如此,使用LFSR技术,任何奇数ID N(至少1)后面总是跟有N-1/2,这是一个非常容易识别的属性!非常感谢你!这正是我想问的,谢谢你的建议!{ids[index]=ids[-remainingIDs];}非常棘手:只需用一个尚未使用的ID覆盖当前ID即可。如果当前ID的索引意外地成为列表中的最后一个索引,则下次仍会将其过滤掉{random.nextIntremainingIDs;}到目前为止,我发现混洗卡片组模型是思考这个问题的最佳方式。到目前为止,我发现混洗卡片组模型是思考这个问题的最佳方式。然后,您仍然需要将UUID映射到一个介于0和9999之间的整数。在这种情况下,唯一性可能会丢失。然后您仍然需要将UUID映射到一个介于0和9999之间的整数。这样一来,独特性就会消失。