How do I generate a random string?
Here is a static method that accepts a desired string length as an argument. It takes a full string of ASCII numbers and letters and loops through the index of your desired string length, randomly choosing an index in the full character string.
public static String generateRandomString(Integer len) {
final String chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz';
String randStr = '';
while (randStr.length() < len) {
Integer idx = Math.mod(Math.abs(Crypto.getRandomInteger()), chars.length());
randStr += chars.substring(idx, idx+1);
}
return randStr;
}
You can create random strings by stringing together Integer values. Here's how I would perform such a task:
public static String randomStringAll(Integer count) {
Integer[] chars = new Integer[0],
offsets = new Integer[] { 48, 65, 97 },
mods = new Integer[] { 10, 26, 26 };
while(count > chars.size()) {
Integer cat = Math.abs(Math.mod(Crypto.getRandomInteger(),3));
chars.add(Math.mod(Math.abs(Crypto.getRandomInteger()), mods[cat])+offsets[cat]);
}
return String.fromCharArray(chars);
}
You can extend this to include any Unicode characters, but, of course, this is plain ASCII ("Latin") characters, a-z, A-Z, and 0-9.
Edit: Forgot to use absolute value. Fixed.
Edit: In response to the question/comment, I tried a simple benchmark (I'll come back and perform heavier analysis later).
I created two tests. The first test calls the function to make a string 100,000 large. All times are in MS.
generateRandomString randomStringAll
1 5369 5165
2 4856 4151
3 5754 4447
4 4821 4359
5 4985 4171
Avg 5157 4458.6
Then, I created a test where we requested 10,000 strings 10 characters long. All times are in MS.
1 3437 4813
2 3102 5030
3 3478 5268
4 3180 5270
5 3437 4953
Avg 3326.8 5066.8
Observations:
Repeated string concatenation seems to slow the system down at larger sizes. Crypto.getRandomInteger/getRandomLong is really expensive, so at smaller sizes, generateRandom string is superior, while at larger sizes, the lack of memory thrashing makes randomStringAll faster.
Based on these observations, I made further adjustments, and finally came up with:
public static String randomStringAllEnhanced2(Integer count) {
Integer[] chars = new Integer[count],
offsets = new Integer[] { 48, 65, 97 },
mods = new Integer[] { 10, 26, 26 };
while(count > 0) {
Integer rnd = Math.abs(Crypto.getRandomInteger()), cat = Math.mod(rnd, 3), seed = Math.mod(rnd, mods[cat]);
chars[(count--)-1] = seed+offsets[cat];
}
return String.fromCharArray(chars);
}
I ran the same tests, and came up with the following, first for 100k strings:
1 5369 2940
2 4856 3258
3 5754 2935
4 4821 2942
5 4985 3093
Avg 5157 3033.6
This method, taking advantage of static memory, and reducing the number of Crypto calls, made this function blazing fast (comparatively) for very large strings.
Then, I ran the second test, and came up with the following numbers:
1 3437 3218
2 3102 3425
3 3478 3465
4 3180 3696
5 3437 3931
Avg 3326.8 3547
Here, the winner appears to still be generateRandomString, but due to the few number of samples, a deviation of simply 200 ms is probably statistically insignificant (I'd say it takes at least 500 ms to be statistically interesting).
I'll probably run an extended batch of 1000 or more each through Visualforce or something to see the long-term gains/loss, but I think the point here is that generateRandomString is probably better if you need alphanumeric characters. If you need just a-z or A-Z, you can use these:
public static String randomStringUCAZ(Integer count) {
Integer[] chars = new Integer[0];
while(count > chars.size()) {
chars.add(Math.mod(Crypto.getRandomInteger(),26)+65);
}
return String.fromCharArray(chars);
}
public static String randomStringLCAZ(Integer count) {
Integer[] chars = new Integer[0];
while(count > chars.size()) {
chars.add(Math.mod(Crypto.getRandomInteger(),26)+97);
}
return String.fromCharArray(chars);
}
100k Strings were impressive:
1 1881
2 1867
3 1942
4 1774
5 1790
Avg 1850.8
10k 10 character strings were blazing:
1 2092
2 1991
3 2245
4 1971
5 1976
Avg 2055
After this experiment, I realized that we could try to combine what appeared to be faster features, and came up with:
public static String generateRandomString2(Integer len) {
String chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz';
String[] result = new String[len];
Integer idx = 0;
while(idx < len) {
Integer chr = Math.mod(Math.abs(Crypto.getRandomInteger()), 62);
result[idx++] = chars.substring(chr, chr+1);
}
return String.join(result,'');
}
This method excels at longer strings than shorter, but shows little statistical difference between generateRandomString (but on average, faster, for a small sample).
100k Strings:
1 2842
2 2772
3 2707
4 2511
5 2641
Avg 2694.6
10k 10 character strings:
1 3034
2 3415
3 3367
4 3084
5 2798
Avg 3139.6
Finally, I tried creating a function that had a better "average" performance across big and small strings (all prior functions seemed to have some specific use), and the result was:
static Integer[] charset;
static {
charset = new Integer[0];
for(Integer i = 48; i < 58; i++) charset.add(i);
for(Integer i = 65; i < 91; i++) charset.add(i);
for(Integer i = 97; i < 123; i++) charset.add(i);
}
public static String genRndStrFast(Integer len) {
Integer[] chars = new Integer[len];
Integer key, size = charset.size();
for(Integer idx = 0; idx < len; idx++) {
chars[idx] = charset[Math.mod(Math.abs(Crypto.getRandomInteger()), size)];
}
return String.fromCharArray(chars);
}
Despite the extra initialization time (static variable), "caching" this data resulted in smoother performance:
100k strings:
1 2104
2 2077
3 2038
4 2276
5 2266
Avg 2152.2
10k 10 character strings:
1 2489
2 2056
3 2594
4 2402
5 2333
Avg 2374.8
Overall, there's little benefit to choosing one function over the other if you're calling it just once, but there is a benefit in deciding which algorithm to use depending on how long of a string you need or when you need to generate many strings. This answer originally was simply meant to be an alternative function, but actually yielded tons of useful information.
From your original, proposal, you can expand the AES key and use base64encode to get a wider range of characters. Doing:
Blob blobKey = crypto.generateAesKey(192);
String key = EncodingUtil.base64encode(blobKey);
return key.substring(0,len);
Gets you a string of the same length but with 64 different characters instead of 16. It also runs much faster than manually using loops and indexes to create your string (for 1000 x 32 char strings, I timed 158 ms
, vs 5535 ms
for manually).
(Btw, here's the template I use for comparing algorithm performances on Salesforce. http://pastebin.com/XDAtu4ip)