在 JavaScript 中生成随机字符串 / 字符

我想要一个由从[a-zA-Z0-9]随机挑选的字符组成的 5 个字符串。

用 JavaScript 做到这一点的最佳方法是什么?

答案

我认为这将为您工作:

function makeid(length) {
   var result           = '';
   var characters       = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
   var charactersLength = characters.length;
   for ( var i = 0; i < length; i++ ) {
      result += characters.charAt(Math.floor(Math.random() * charactersLength));
   }
   return result;
}

console.log(makeid(5));

let r = Math.random().toString(36).substring(7);
console.log("random", r);

注意:以上算法具有以下缺点:

  • 由于在对浮点进行字符串化时会删除尾随零,因此它将生成 0 到 6 个字符之间的任意位置。
  • 这在很大程度上取决于用于对浮点数进行字符串化的算法,该算法极其复杂。 (请参阅论文“如何准确打印浮点数” 。)
  • Math.random()可能会产生可预测的(“看起来很随机”,但不是真正随机的)输出。当您需要保证唯一性或不可预测性时,结果字符串不适合。
  • 即使它产生了 6 个均匀随机且不可预测的字符,由于生日悖论 ,您也可以期望仅生成大约 50,000 个字符串后看到重复项。 (sqrt(36 ^ 6)= 46656)

Math.random对这种事情不利

选项 1

如果您能够在服务器端执行此操作,则只需使用crypto模块 -

var crypto = require("crypto");
var id = crypto.randomBytes(20).toString('hex');

// "bb5dc8842ca31d4603d6aa11448d1654"

结果字符串将是您生成的随机字节的两倍长;编码为十六进制的每个字节均为 2 个字符。 20 个字节将是十六进制的 40 个字符。


选项 2

如果您必须在客户端执行此操作,则可以尝试使用 uuid 模块 -

var uuid = require("uuid");
var id = uuid.v4();

// "110ec58a-a0f2-4ac4-8393-c866d813b8d1"

选项 3

如果您必须在客户端执行此操作,而不必支持旧版浏览器,则可以在没有依赖关系的情况下进行操作 -

// dec2hex :: Integer -> String
// i.e. 0-255 -> '00'-'ff'
function dec2hex (dec) {
  return ('0' + dec.toString(16)).substr(-2)
}

// generateId :: Integer -> String
function generateId (len) {
  var arr = new Uint8Array((len || 40) / 2)
  window.crypto.getRandomValues(arr)
  return Array.from(arr, dec2hex).join('')
}

console.log(generateId())
// "82defcf324571e70b0521d79cce2bf3fffccd69"

console.log(generateId(20))
// "c1a050a4cd1556948d41"


有关crypto.getRandomValues更多信息 -

crypto.getRandomValues()方法可让您获得加密强度高的随机值。作为参数给出的数组填充有随机数(其密码学含义是随机的)。

这是一个小的控制台示例 -

> var arr = new Uint8Array(4) # make array of 4 bytes (values 0-255)
> arr
Uint8Array(4) [ 0, 0, 0, 0 ]

> window.crypto
Crypto { subtle: SubtleCrypto }

> window.crypto.getRandomValues()
TypeError: Crypto.getRandomValues requires at least 1 argument, but only 0 were passed

> window.crypto.getRandomValues(arr)
Uint8Array(4) [ 235, 229, 94, 228 ]

对于 IE11 支持,您可以使用 -

(window.crypto || window.msCrypto).getRandomValues(arr)

有关浏览器的覆盖范围,请参见https://caniuse.com/#feat=getrandomvalues

简短,简单且可靠

返回恰好 5 个随机字符,与此处找到的一些评价最高的答案相反。

Math.random().toString(36).substr(2, 5);

这是doubletap 出色答案的改进。原始文件有两个缺点,在这里解决:

首先,正如其他人提到的那样,它产生短字符串甚至空字符串(如果随机数为 0)的可能性很小,这可能会破坏您的应用程序。这是一个解决方案:

(Math.random().toString(36)+'00000000000000000').slice(2, N+2)

其次,上述原始方法和解决方案都将字符串大小 N 限制为 16 个字符。下面的代码将为任何 N 返回一个大小为 N 的字符串(但请注意,使用 N> 16 不会增加随机性或减少碰撞的可能性):

Array(N+1).join((Math.random().toString(36)+'00000000000000000').slice(2, 18)).slice(0, N)

说明:

  1. 选择 [0,1)范围内的随机数,即介于 0(含)和 1(不含)之间。
  2. 将数字转换为以 36 为基数的字符串,即使用字符 0-9 和 az。
  3. 用零填充(解决第一个问题)。
  4. 删除开头的 “0”。前缀和额外的填充零。
  5. 重复该字符串足够的次数,以使其至少包含 N 个字符(通过将空字符串与较短的随机字符串用作分隔符进行连接)。
  6. 从字符串中精确切出 N 个字符。

进一步的想法:

  • 此解决方案不使用大写字母,但在几乎所有情况下(无双关语)都无关紧要。
  • 原始答案中 N = 16 时的最大字符串长度是在 Chrome 中测量的。在 Firefox 中,它的 N =11。但是,如前所述,第二种解决方案是支持任何请求的字符串长度,而不是增加随机性,因此没有太大区别。
  • 至少就 Math.random()返回的结果均匀分布而言,所有返回的字符串都有相等的返回概率(无论如何,这不是密码强度随机性)。
  • 并非所有可能的大小为 N 的字符串都将返回。在第二种解决方案中,这是显而易见的(因为只复制了较小的字符串),但在原始答案中也是如此,因为在转换为 base-36 时,最后几位可能不是原始随机位的一部分。具体来说,如果您查看 Math.random()。toString(36)的结果,您会注意到最后一个字符分布不均匀。同样,在几乎所有情况下都没有关系,但是我们从随机字符串的开头而不是结尾处切片最后一个字符串,这样短字符串(例如 N = 1)不会受到影响。

更新:

这是我想到的其他几个功能样式的单线纸。它们与上述解决方案的不同之处在于:

  • 他们使用一个明确的任意字母(更通用,并且适用于要求同时使用大写和小写字母的原始问题)。
  • 所有长度为 N 的字符串都有相等的返回概率(即字符串不包含重复)。
  • 它们基于映射函数,而不是 toString(36)技巧,这使它们更加直接和易于理解。

因此,假设您选择的字母是

var s = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

然后这两个彼此相等,因此您可以选择对您而言更直观的一个:

Array(N).join().split(',').map(function() { return s.charAt(Math.floor(Math.random() * s.length)); }).join('');

Array.apply(null, Array(N)).map(function() { return s.charAt(Math.floor(Math.random() * s.length)); }).join('');

编辑:

好像qubyteMartijn de Milliano提出了与后者类似的解决方案(赞!),我以某种方式错过了。由于它们看起来不那么短,所以我还是把它留在这里,以防有人真的想要单线:-)

同样,在所有解决方案中都将 “new Array” 替换为 “Array” 以节省更多字节。

最紧凑的解决方案,因为slicesubstring短。从字符串的末尾减去可以避免由random函数生成的浮点符号:

Math.random().toString(36).slice(-5);

甚至

(+new Date).toString(36).slice(-5);

更新:添加了另一种使用btoa方法的方法:

btoa(Math.random()).slice(0, 5);
btoa(+new Date).slice(-7, -2);
btoa(+new Date).substr(-7, 5);

// Using Math.random and Base 36:
console.log(Math.random().toString(36).slice(-5));

// Using new Date and Base 36:
console.log((+new Date).toString(36).slice(-5));

// Using Math.random and Base 64 (btoa):
console.log(btoa(Math.random()).slice(0, 5));

// Using new Date and Base 64 (btoa):
console.log(btoa(+new Date).slice(-7, -2));
console.log(btoa(+new Date).substr(-7, 5));

这样的事情应该工作

function randomString(len, charSet) {
    charSet = charSet || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    var randomString = '';
    for (var i = 0; i < len; i++) {
        var randomPoz = Math.floor(Math.random() * charSet.length);
        randomString += charSet.substring(randomPoz,randomPoz+1);
    }
    return randomString;
}

使用默认字符集 [a-zA-Z0-9] 调用或发送自己的字符集:

var randomValue = randomString(5);

var randomValue = randomString(5, 'PICKCHARSFROMTHISSET');

function randomstring(L) {
  var s = '';
  var randomchar = function() {
    var n = Math.floor(Math.random() * 62);
    if (n < 10) return n; //1-10
    if (n < 36) return String.fromCharCode(n + 55); //A-Z
    return String.fromCharCode(n + 61); //a-z
  }
  while (s.length < L) s += randomchar();
  return s;
}
console.log(randomstring(5));

随机字符串生成器(字母数字 | 字母 | 数字)

/**
 * RANDOM STRING GENERATOR
 *
 * Info:      http://stackoverflow.com/a/27872144/383904
 * Use:       randomString(length [,"A"] [,"N"] );
 * Default:   return a random alpha-numeric string
 * Arguments: If you use the optional "A", "N" flags:
 *            "A" (Alpha flag)   return random a-Z string
 *            "N" (Numeric flag) return random 0-9 string
 */
function randomString(len, an){
    an = an&&an.toLowerCase();
    var str="", i=0, min=an=="a"?10:0, max=an=="n"?10:62;
    for(;i++<len;){
      var r = Math.random()*(max-min)+min <<0;
      str += String.fromCharCode(r+=r>9?r<36?55:61:48);
    }
    return str;
}
randomString(10);        // "4Z8iNQag9v"
randomString(10, "A");   // "aUkZuHNcWw"
randomString(10, "N");   // "9055739230"

玩得开心。 jsBin 演示


尽管上面对所需的(A / N,A,N)输出使用了额外的检查,但是为了更好地理解我们将其分解为基本内容(仅字母数字)。

  • 创建一个接受参数的函数(随机 String 结果的期望长度)
  • 创建一个空字符串,如var str = "";连接随机字符
  • 在循环内创建0 到 61 rand索引号 (0..9 + A..Z + a..z = 62)
  • 创建一个条件逻辑调整 / 固定rand (因为它是 0..61),将其递增某个数字(请参见下面的示例),以获取正确的CharCode号和相关的 Character。
  • 在循环内连接一个str String.fromCharCode( incremented rand )

让我们描绘一下Character 表及其范围

_____0....9______A..........Z______a..........z___________  Character
     | 10 |      |    26    |      |    26    |             Tot = 62 characters
    48....57    65..........90    97..........122           CharCode ranges

Math.floor( Math.random * 62 )给出的范围是0..61 (我们需要)。 如何修正(增加)随机数以获得正确的charCode 范围

|   rand   | charCode |  (0..61)rand += fix            = charCode ranges |
------+----------+----------+--------------------------------+-----------------+
0..9  |   0..9   |  48..57  |  rand += 48                    =     48..57      |
A..Z  |  10..35  |  65..90  |  rand += 55 /*  90-35 = 55 */  =     65..90      |
a..z  |  36..61  |  97..122 |  rand += 61 /* 122-61 = 61 */  =     97..122     |

上表中的条件运算逻辑

rand += rand>9 ? ( rand<36 ? 55 : 61 ) : 48 ;
// rand +=  true  ? (  true   ? 55 else 61 ) else 48 ;

如果按照上述说明进行操作,则应该可以创建以下字母数字代码段

jsBin 演示

function randomString( len ) {
  var str = "";                                         // String result
  for(var i=0; i<len; i++){                             // Loop `len` times
    var rand = Math.floor( Math.random() * 62 );        // random: 0..61
    var charCode = rand+= rand>9? (rand<36?55:61) : 48; // Get correct charCode
    str += String.fromCharCode( charCode );             // add Character to str
  }
  return str;       // After all loops are done, return the concatenated string
}

console.log( randomString(10) ); // "7GL9F0ne6t"

或者,如果您会:

function randomString( n ) {
  var r="";
  while(n--)r+=String.fromCharCode((r=Math.random()*62|0,r+=r>9?(r<36?55:61):48));
  return r;
}

带有es6 传播算子的较新版本:

[...Array(30)].map(() => Math.random().toString(36)[2]).join('')

  • 30是任意数字,您可以选择所需的任何令牌长度
  • 36是您可以传递给numeric.toString()的最大基数,表示所有数字和小写字母 az
  • 2用于从如下所示的随机字符串中选择第二个数字: "0.mfbiohx64i" ,我们可以在0.之后获取任何索引0.