記得大學(xué)時看過一本書,上邊寫到一個撩妹的小技巧:提出和對方玩一個意識游戲,讓她心中想一個1到10、對她而言比較有意義的一個數(shù)字,然后自己在另一個地方寫下一個數(shù)字,往往能猜中。這個小技巧利用了人的心理,同時,「程序」其本身本質(zhì)上就是數(shù)字,本文將收集一些有趣的數(shù)字,主要以Java語言中的數(shù)字為主。給枯燥的生活一些調(diào)劑。

1. 神奇的數(shù)字 7 和 142857
上邊的那個問題的答案是7。我們平時生活中總是會在各種各樣的地方遇到7這個數(shù)字,當(dāng)你讓別人去想一個1到10的數(shù)字時,它就歡呼雀躍地跳了出來。最廣為人知的就是「一周有7天」這個事情了,同時,7還可以延伸出來另外一個很有趣的數(shù)字142857,看下邊的算式:
1 / 7 = 0.142857, 142857, 142857...
2 / 7 = 0.285714, 285714...
3 / 7 = 0.428571...
4 / 7 = 0.571428...
5 / 7 = 0.714285...
6 / 7 = 0.857142...
當(dāng)我看到這個結(jié)果時已經(jīng)很震驚了,但是還有更多的:
著了魔的數(shù)字
142857 x 1 = 142857
142857 x 2 = 258714
142857 x 3 = 428571
142857 x 4 = 571428
142857 x 5 = 714285
148257 x 6 = 857142
2. 美劇「迷失」中的 4 8 15 16 23 42
這個太玄,就不多說了。

3. 藏在Java隨機數(shù)中的hello world
這算是一個廣為人知的現(xiàn)象,在Java的Random類中實現(xiàn)的隨機數(shù)生成算法并不是真正的隨機數(shù),只是概率上大致的隨機,而其隨機性取決于種子的選取,如果選取了特定的種子,那么產(chǎn)生的隨機數(shù)也就確定了。

根據(jù)以上的事實,把-229985452當(dāng)做種子傳入Random類,就可以得到hello,相應(yīng)的-147909649則能得到world。在Java中執(zhí)行如下程序,它最終會打印輸出hello world。
public static String randomString(int i) {
Random ran = new Random(i);
StringBuilder sb = new StringBuilder();
while (true) {
int k = ran.nextInt(27);
System.out.println("char:" + k + ",number:" + k);
if (k == 0)
break;
k += 96;
sb.append((char) k);
}
return sb.toString();
}
System.out.println(randomString(-229985452) + " " + randomString(-147909649));
根據(jù)這個原理,你可以找到很多字符串的種子。原來除了簡單的編碼,字符串還可以使用隨機數(shù)的形式隱藏起來。
4. JDK的版本號
我們知道的JDK版本號往往是1.6,1.8,或者Java8這種,但是在編譯后的class文件中并不是這么記錄的,偶爾在運行程序時會碰到這個錯誤
java.lang.UnsupportedClassVersionError: Bad version number in .class file [at java.lang.ClassLoader.defineClass1(Native Method)]
在class文件中版本號有[主版本號(major version number)].[次版本號(minor version number)],其中「主版本號」和「JDK版本號」的對應(yīng)關(guān)系如下所示:
- Java SE 9 = 53 (0x35 hex),
- Java SE 8 = 52 (0x34 hex),
- Java SE 7 = 51 (0x33 hex),
- Java SE 6.0 = 50 (0x32 hex),
- Java SE 5.0 = 49 (0x31 hex),
- JDK 1.4 = 48 (0x30 hex),
- JDK 1.3 = 47 (0x2F hex),
- JDK 1.2 = 46 (0x2E hex),
- JDK 1.1 = 45 (0x2D hex).
5. class文件中的 0xcafebabe,或者說是3405691582
如果你用任何一個文本文檔編輯器打開一個Java的class文件或者Mac平臺上的可執(zhí)行文件,它的第一行一般如下所示:
//Java編譯后的class文件
cafe babe 0000 0034 0117 0700 0201 0012
//iOS平臺Objective-C編譯后的可執(zhí)行文件
cafe babe 0000 0005 0000 000c 0000 0009
其中第5、6個字節(jié)代表這個class文件的minor version number,這里全為0,第7、8個字節(jié)代表major version number的值,52,代表我的版本是1.8。而前4個字節(jié)從最開始一直就是「CAFE BABE」,這個詞本來是 James Gosling對他經(jīng)常去的一個咖啡館,里的一個咖啡師的昵稱,后來陰差陽錯的一致被沿用至今。
6. Mach-o文件中也有0xcafebabe
Mach-o是一種在某些平臺上(現(xiàn)在主要是Mac OS X和iOS)的可執(zhí)行文件類型,在上一節(jié)中的代碼中看到Objective-C編譯后的文件中也出現(xiàn)了0xcafebabe這個詞,這個詞的歷史其實可以追回到NeXT時期。

在Unix/Linux中使用/etc/magic中定義的值來判定讀取到的文件類型(現(xiàn)在的Mac OS X中可以在/usr/share/file/magic目錄下找到),就是說在程序讀取文件時,會根據(jù)讀到的前幾個字節(jié)進行文件類型的判斷(而不是擴展名),在Unix/Linux的系統(tǒng)中有個命令file可以判斷一個二進制文件的類型。
NeXTSTEP是Mac OS X的前身,是一個基于BSD Unix的系統(tǒng),自然也遵循了這個方法,當(dāng)年NeXT獲取了Objective-C的使用權(quán),同時開發(fā)了一整套的開發(fā)套件,那么就需要找一個值來指代其文件類型,于是就使用了0xcafebabe,通常認為是一位叫Mike DeMoney的哥們做的這個決定。
7. 怎么Mach-o和Java還有這層關(guān)系
差不多與此同時,Sun公司也在半秘密地開發(fā)著名的Oak項目,這個項目也選擇了0xcafebabe來作為魔術(shù)數(shù)。有一個有趣的故事是——這個有趣的故事好像是來源于Java項目的第一位工程師Patrick Naughton——cafe babe代表的是一個Sun公司的漂亮的市場經(jīng)理(就是開篇的那張照片,Kim Polese),她當(dāng)時在一個C++編譯器的項目中工作,那個項目叫做cafe(C++, A Front End),于是她就被叫做cafe baby——在都是程序員的環(huán)境里大約就是會這樣——后來她調(diào)到了Oak項目。話說后來Kim在硅谷也是風(fēng)生水起,現(xiàn)在是某公司CEO。下面來感受一下當(dāng)年的Kim。


當(dāng)然,一個好八卦必須要有很多版本。另一個說法是,NeXT的很多人后來不想跟喬幫主混了,去了FirstPerson公司,這些人在那里一手創(chuàng)立了Java——呃,當(dāng)然Java創(chuàng)始人公認是James Gosling——但這些人確實對Java產(chǎn)生了很大的影響。不得不承認,Java的設(shè)計理念和Objective-C有太多的相似之處(Interface屬于完全的抄……呃……借鑒),而且很多地方Java和Objective-C的設(shè)計理念相同,只是由于拋掉了c語言的枷鎖,得以走得更遠??傊?,他們來了,然后這群人里有一個人(Mike DeMoney)使用了和Mach-o相同的魔術(shù)數(shù)。
然而,八卦止于無聊的人(逃……),公認的Java創(chuàng)始人James Gosling出來說:很抱歉打擾大家談?wù)摪素缘难排d,不過小可我才是那個選擇了在class文件中使用0xcafebabe的人,這跟上邊那幾位一點關(guān)系都沒有。
As far as I know, I'm the guilty party on this one. I was totally unaware of the NeXT connection. The small number of interesting HEX words is probably the source of the match. As for the derivation of the use of CAFEBABE in Java, it's somewhat circuitous:

當(dāng)然,由這個magic number背后也可以看出Objective-C和Java一脈相承的關(guān)系。
8. 更多的魔術(shù)數(shù)
話說NeXT的這群工程師都好調(diào)皮。
0xbaaaaaad iOS錯誤日志代碼,表示一個日志是全局日志快照,very bad
0xbad22222 iOS錯誤日志代碼
0x8badf00d (Ate Bad Food) iOS錯誤日志代碼,表示代碼吃壞了肚子,被殺掉了
0xdeadfa11 (Dead Fall) iOS錯誤日志代碼,用戶強制退出
0xDEAD10CC (Dead Lock) iOS錯誤日志代碼,這個很清晰,哎呀,死鎖了
0xBAADF00D (Bad Food) Windows中的錯誤代碼
0xCAFED00D (Cafe dude) Java中使用
0xCAFEBABE (Cafe babe) Java和Mach-o文件類型
0x0D15EA5E (Disease)
0x1BADB002 (1 bad boot) 啟動失敗
0xDEADDEAD Windows的藍屏???
9. Integer類型中的-128和127
很多書、文章或者各種編程指南中都建議在Java中不要使用基本數(shù)據(jù)類型(int,long,char,byte等),Java也提供了自動裝箱來盡可能的保證在JVM中奔跑的數(shù)據(jù)盡可能都是「對象」。然而,有時候這種習(xí)慣也會帶來一些副作用。
for (int i = 0; i < 10; i++) {
System.out.println((Integer) i);
}
//正常情況下,此代碼會循環(huán)輸出0到9等十個數(shù)字
但是,我們可以通過一些小手段,使這個代碼輸出其他的數(shù)字。利用到Java的兩個特性,反射和Integer類的緩存。
大家都知道,當(dāng)你在Java中獲取-128到127的Integer類型時,其實是不會新建對象的,很多面試題都喜歡考這個特性,其實沒什么高深的,只是想給人挖陷阱而已。Integer類有一個內(nèi)部類叫IntegerCache,在這個類中創(chuàng)建了256個數(shù)字的緩存,在JDK的源碼中是這樣寫的:
public static Integer valueOf(int i) {
//這里的low和high就是-128和127,也就是一個字節(jié)的長度
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
針對上邊這塊代碼,我只要把這個靜態(tài)內(nèi)部類IntegerCache中的cache數(shù)組的內(nèi)容改掉,別人再調(diào)用valueOf()就拿不到正確的數(shù)據(jù)了,代碼如下:
Class<?> clazz = Class.forName("java.lang.Integer$IntegerCache");
Field field = clazz.getDeclaredField("cache");
field.setAccessible(true);
Integer[] cache = (Integer[]) field.get(clazz);
// 寫入錯誤的值
for (int i = 0; i < cache.length; i++) {
cache[i] = new Integer(128 - i);
}
10. ArrayList的默認大小
使用Java集合時,有時候因為不希望碰到null的情況,所以一些List會像這樣賦值
List<String> list = new ArrayList<>();
這條語句的意思是建立一個默認為空的ArrayList,但是Java的ArrayList的空間是自增的,且ArrayList底層是用數(shù)組實現(xiàn)的,所以就會有一個默認大小,作為第一次需要自增時初始化的大小,這個數(shù)字在ArrayList而言是10。所以如果你使用上邊的語句建立List,那么在第一次插入元素時,它就會生成一個空間為10的數(shù)組。如果你這個List最多只會插入4個元素,那么就浪費了空間。所以如果要建立的這個List大小大約已知的話,在性能需求較緊張時,最好把你預(yù)測的空間填進去。
當(dāng)然,不要過早去優(yōu)化,但一個好習(xí)慣總是好的。