一、背景
公司hr给了我一段代码,她给我说可以输出 “hello world”,我看到代码的那一刻,说实话,不太信!所以我跑了一下,发现:我天,原来还真是啊!到底是为什么呢?代码如下:
public static void main(String[] args) {
System.out.println(randomString(-229985452) + " " + randomString(-147909649));
}
public static String randomString(int seed){
Random rand = new Random(seed);
StringBuilder sb = new StringBuilder();
while(true){
int n = rand.nextInt(27);
if(n==0) break;
sb.append((char)('`'+n));
}
return sb.toString();
}
简单看了下源码,好像是seed一定的时候,序列是固定的:
protected int next(int bits) {
long oldseed, nextseed;
AtomicLong seed = this.seed;
do {
oldseed = seed.get();
nextseed = (oldseed * multiplier + addend) & mask;
} while (!seed.compareAndSet(oldseed, nextseed));
return (int)(nextseed >>> (48 - bits));
}
也就是说,传入一个seed,seed一定时,每次random出来的字符串是一定的,也就是一种假随机!
二、如何找到我想要的随机字符串,而得到对应的seed呢?
有了这样的代码,个人觉得是很新奇的,但由于又是hr给我的,自己感觉自己好笨,居然不知道。找了下朋友,问他能不能一下子就看出来结果是什么。后来在交流过程中说到 如果想知道像love you这样的字符串对应的seed是多少该如何做?我以为有什么简便的方法,比如倒推算法之类的,朋友说暴力破解。想了想,好像暴力破解更容易一些,那就暴力破解吧!
第二天,朋友给了我 love you 对应的seed,我受到了刺激,开始跑,寻找我的名字。
int start = Integer.MIN_VALUE;
for ( ; start <= Integer.MAX_VALUE ; start ++){
if (randomString(start).equals("yourstring")){
System.out.println(start + "yourstring");
break;
}
}
三、坑多多
3.1 一不小心写了死循环
跑了一晚上,发现还在跑,怀疑自己写了死循环。还别说,真有死循环,哈哈哈!!!那到底是不是因为死循环没跑完呢?
图上是朋友的代码,跑了接近12个小时,而我已经超过了12个小时,但是离遍历两遍还是不可能。所以,我大概计算了一下,代码如下:
int start = Integer.MIN_VALUE;
for ( ; start <= Integer.MAX_VALUE ; start ++){
if (randomString(start).equals("yourstring")){
System.out.println(start + "yourstring");
break;
}
System.out.println(start + " == is not");
}
两小时大概跑了5亿条数据,Integer大概40亿,所以大概需要16个小时!!!还好,不是因为死循环才跑了这边久,原来是真的需要跑这么久啊!!!不过,上面的代码是真的有死循环哦~
不过,我和朋友为什么相差4个小时呢???我们电脑配置一模一样,就因为一句 System.out.println(start + " == is not"); ??
3.2一句打印的语句竟然这么耗时
long startTime = System.currentTimeMillis();
for (long i=0;i<1000;i++){
System.out.println("hi");
}
long endTime = System.currentTimeMillis();
long elapsedTime = endTime - startTime;
System.out.println("elapsedTime=" + elapsedTime);
打印了一千条语句,我和朋友都花费了 20 ms,而不打印的话,花费0ms!!!
那为什么这么耗时呢,println究竟做了些啥?忍不住好奇,接触了4年java,我第一次打开了 println 的源码
//上来就一个锁,不过应该是偏向锁
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}
//print方法调用了 write 方法,又一个锁,虽然和上面的锁一样,不过可重入锁也是需要比较消耗性能的吧
private void write(String s) {
try {
synchronized (this) {
ensureOpen();
textOut.write(s);
textOut.flushBuffer();
charOut.flushBuffer();
if (autoFlush && (s.indexOf('\n') >= 0))
out.flush();
}
}
catch (InterruptedIOException x) {
Thread.currentThread().interrupt();
}
catch (IOException x) {
trouble = true;
}
}
// 再往下跟,又有一个lock锁,哈哈哈
public void write(String str, int off, int len) throws IOException {
synchronized (lock) {
char cbuf[];
if (len <= WRITE_BUFFER_SIZE) {
if (writeBuffer == null) {
writeBuffer = new char[WRITE_BUFFER_SIZE];
}
cbuf = writeBuffer;
} else { // Don't permanently allocate very large buffers.
cbuf = new char[len];
}
str.getChars(off, (off + len), cbuf, 0);
write(cbuf, 0, len);
}
}
跟了一下源码,发现多处加锁的地方,分别有两个锁,一个是this,一个是lock对象。虽然锁是可重入、此时应该是偏向锁,但是加了多处还是会比较消耗性能的吧!所以这大概就是为什么我只打印了一句话比朋友花的时间花多了几个小时!!
四、如何优化
程序员一般都是非常懒得,是的,为了找出这样一个随机字符串对应的seed,我竟然要花费13个小时。电脑是4核,那最多可以用4个线程来分段跑,就是还是需要3个小时的时间。况且这只是Integer的范围
着了魔的朋友提到写一个集群来跑,还建了个仓库,哈哈哈。不过,好像并没有写~
五、用集群跑真的就可以成功吗?
理想总是美好,但现实总是残忍的。还好我们还没开始写代码!!!
为什么这么说呢?因为long的范围实在是太大太大了!!!有多大,可以量化吗???当然可以
我大概计算了一下:以单线程计算
- int是2的32次方,大概40亿条数据,以12个小时跑完40亿条数据来计算
- long是2的64次方,那就是40亿个 40亿条数据,所以需要 40亿/2 = 20亿天
用一个线程,需要跑 20 亿天,我的天,什么概念,大概就是用电脑跑一辈子我都跑不完吧!那一辈子到底跑不跑的完?
为了好计算,以一年400天来计算:20亿天/400天 = 500 0000年
五百万年,哈哈哈,那真的用一辈子(50岁 + 20岁)的话,需要多少个机器来跑呢?
- 500 0000年/50 约等于 10 0000 辈子,一个机器 4核,所以真用一辈子来跑,那大概需要是25000台机器一起跑!!!
- 如果只有两个人,两台机器共8核,那大概需要1万辈子吧
看来用集群跑也是不可以的!!!
六、 long的范围大到超乎想象!!!
那long究竟有大多呢?上面的计算,是因为我调用其他的方法,那如果只是单纯的遍历一遍,需要用多久呢?
遍历40亿什么都不做,大概需要1.5秒,那遍历一下long的话就需要 40亿 * 1.5 秒 = 60 0000 0000 秒
一天:86400秒,那总共需要 60亿 / 86400秒 / 365天 = 190年
我从来都没有想象过long竟然有这么大!!!单纯的遍历都需要190年,还是用电脑。如果让我数的话,我想会疯吧