初看一臉問號,看懂直接跪下!

来源:https://www.cnblogs.com/thisiswhy/archive/2022/04/13/16139704.html
-Advertisement-
Play Games

你好呀,我是歪歪。 我最近在 stackoverflow 上看到一段代碼,怎麼說呢。 就是初看一臉懵逼,看懂直接跪下! 我先帶你看看 stackoverflow 上的這個問題是啥,然後引出這段代碼: https://stackoverflow.com/questions/15182496/why-d ...


你好呀,我是歪歪。

我最近在 stackoverflow 上看到一段代碼,怎麼說呢。

就是初看一臉懵逼,看懂直接跪下!

我先帶你看看 stackoverflow 上的這個問題是啥,然後引出這段代碼:

https://stackoverflow.com/questions/15182496/why-does-this-code-using-random-strings-print-hello-world

問題特別簡單,就一句話:

誰能給我解釋一下:為什麼這段代碼使用隨機字元串列印出了 hello world?

代碼也很簡單,我把它拿出來給你跑一下:

public class MainTest {

    public static void main(String[] args) {
        System.out.println(randomString(-229985452) + " " + randomString(-147909649));
    }

    public static String randomString(int i) {
        Random ran = new Random(i);
        StringBuilder sb = new StringBuilder();
        while (true) {
            int k = ran.nextInt(27);
            if (k == 0)
                break;

            sb.append((char) ('`' + k));
        }
        return sb.toString();
    }
}

上面的代碼你也可以直接粘貼到你的運行環境中跑一下,看看是不是也輸出的 hello world:

我就問你:即使代碼都給你了,第一眼看到 hello world 的時候你懵不懵逼?

高贊回答

高贊回答也特別簡單,就這麼兩句話。

我給你翻譯一下,這個哥們說:

當我們調用 Random 的構造方法時,給定了一個“種子”(seed)參數。比如本例子中的:-229985452 或 -147909649。

那麼 Random 將從指定的種子值開始生成隨機數。

而每個用相同的種子構造的 Random 對象,都會按照產生相同的模式產生數字。

沒看的太明白,對不對?

沒關係,我給你上一段代碼,你就能恍然大悟上面這一段說的是啥事:

public static void main(String[] args) {

    randomString(-229985452);
    System.out.println("------------");
    randomString(-229985452);

}

private static void randomString(int i) {
    Random ran = new Random(i);
    System.out.println(ran.nextInt());
    System.out.println(ran.nextInt());
    System.out.println(ran.nextInt());
    System.out.println(ran.nextInt());
    System.out.println(ran.nextInt());

}

這段代碼,在我的機器上運行結果是這樣的:

你拿過去跑,你的運行結果也一定是這樣的。

這是為什麼呢?

答案就在 Javadoc 上寫著的:

如果用相同的種子創建了兩個 Random 的實例,並且對每個實例進行了相同的方法調用序列,那麼它們將生成並返回相同的數字序列。

在上面的代碼中兩個 -229985452 就是相同的種子,而三次 nextInt() 調用,就是相同的調用序列。

所以,他們生成並返回相同的、看起來是隨機的數字。

而我們正常在程式裡面的用法應該是這樣的:

在 new Random() 的時候,不會去指定一個值。

我們都知道 Random 是一個偽隨機演算法,而構建的時候指定了 seed 參數的就是一個更加偽的偽隨機演算法了。

因為如果我可以推測出你的 seed 的話,或者你的 seed 泄露了,那麼理論上我就可以推測出你隨機數生成序列。

這個我已經在前面的代碼中演示了。

再看看問題

在前面稍微解釋了 “seed” 的關鍵之處之後,我們再回過頭去品一品這個問題,大概就能看出點端倪了。

主要看這個迴圈裡面的代碼。

首先 nextInt(27) 就限定了,當前返回的數 k 一定是在 [0,27) 之間的一個數字。

如果返回 0,那麼迴圈結束,如果不為零。則做一個類型轉換。

接下來就是一個 char 類型的強制轉換。

看到數字轉 char 類型,就應該條件反射的想到 ascii 碼:

從 ascii 碼 表中,我們可以到 “96” 就是這裡的這個符號:

所以,下麵這個代碼的範圍就是 [96+1,96+26]:

'`' + k

也就是 [97,122],即對應 ascii 碼的 a-z。

所以,我帶你再把上面的演示代碼拆解一下。

首先 new Random(-229985452).nextInt(27) 的前五個返回是這樣的:

而 new Random(-147909649).nextInt(27) 的前五個返回是這樣的:

所以,對照著 ascii 碼表看,就能看出其對應的字母了:

8 + 96 = 104 --> h
5 + 96 = 101 --> e
12 + 96 = 108 --> l
12 + 96 = 108 --> l
15 + 96 = 111 --> o

23 + 96 = 119 --> w
15 + 96 = 111 --> o
18 + 96 = 114 --> r
12 + 96 = 108 --> l
4 + 96 = 100 --> d

現在,對於這一段謎一樣的代碼為什麼輸出了 “hello world” 的原因,心裡是不是撥開雲霧見青天,心裡跟明鏡兒似的。

看穿了,也就是一個小把戲而已。

然後這個問題下麵還有個評論,讓我看到了另外一種打開方式:

你能指定列印出 hello world,那麼理論上我也能指定打出其他的單詞。

比如這個老哥就打了一個短語:the quick browny fox jumps over a lazy dog.

如果從字面上直譯過來,那麼就是“敏捷的棕色狐狸跨過懶狗”,好像也是狗屁不通的樣子。

但是,你知道的,我的 English 水平是比較 high 的,一眼就看出這個短語在這裡肯定不簡單。

於是查了一下:

果然是有點故事的,屬於 tricks in tricks。

你看學沙雕技術的時候還順便豐富了自己的英語技能,一舉多得,這一會還不得在文末給我點個贊、點個“在看”啥的?

看完這個老哥的 quick brown fox 示例之後,我又有一點新想法了。

既然它能把所有的字母都打出來,那我是不是也能把我想要的特定的短語也打出來呢?

比如 i am fine thank you and you 這樣的東西。

而查找指定單詞對應的 seed 這樣的功能的代碼,在這個問題的回答中,已經有“好事之人”幫我們寫出來了。

我就直接粘過來,你也可以直接拿去就用:

public static long generateSeed(String goal, long start, long finish) {
    char[] input = goal.toCharArray();
    char[] pool = new char[input.length];
    label:
    for (long seed = start; seed < finish; seed++) {
        Random random = new Random(seed);

        for (int i = 0; i < input.length; i++)
            pool[i] = (char) (random.nextInt(27) + '`');

        if (random.nextInt(27) == 0) {
            for (int i = 0; i < input.length; i++) {
                if (input[i] != pool[i])
                    continue label;
            }
            return seed;
        }
    }
    throw new NoSuchElementException("Sorry :/");
}

那麼我要找前面提到的短語,就很簡單了:

而且運行的時候我明顯感覺到,在搜索“thank”這個單詞的時候,花了很多時間。

為什麼?

我給你講一個故事啊,只有一句話,你肯定聽過:

只要時間足夠漫長,猴子都能敲出一部《莎士比亞》。

我們這裡 generateSeed 方法,就相當於這個猴子。而 thank 這個單詞,就是《莎士比亞》。

在 generateSeed 方法裡面,通過 26 個字母不斷的排列組合,總是能排列出 “thank” 的,只是時間長短而已。

單詞越長,需要的時間就越長。

比如我來個 congratulations,這麼長的單詞,我從 00:05 分,跑了 23 個小時都還沒跑出來:

但是理論上來講只要有足夠長的時間,這個 seed 一定會被找到。

至此,你應該完全明白了為什麼前面提到的那段代碼,使用隨機字元串的方式列印出了 hello world。

源碼

你以為我要帶你讀源碼?

不是的,我主要帶你吃瓜。

首先,看一下的 Random 無參構造函數:

好家伙,原來也是套個了個“無參”的殼而已,實際上還是自己搞了一個 seed,然後調用了有參構造方法。

只是它構建的時候加入了“System.nanoTime()”這個變數,讓 seed 看起來隨機了一點而已。

等等,前面不是還有一個“seedUniquifier”方法嗎?

這個方法是這樣的:

好家伙,看到第一眼的時候我頭都大了,這裡面有兩個“魔法數”啊:

181783497276652981L
8682522807148012L

這玩意也看不懂啊?

遇事不決,stackoverflow。

一搜就能找到這個地方:

https://stackoverflow.com/questions/18092160/whats-with-181783497276652981-and-8682522807148012-in-random-java-7

在這個問題裡面,他說他對這兩個數字也感到很懵逼,網上找了一圈,相關的資料非常的少。但是找到一個論文,裡面提到了其中一個很接近的“魔數”:

論文中提到的數是這樣的:

看到沒有?

這 Java 源碼中的數字前面少了一個“1”呀,咋回事呢,該不會是拷貝的時候弄錯了吧?

下麵的一個高贊回答是這樣的:

“看起來確實像是拷錯了。”

有點意思,你要說這是寫 Java 源碼的老哥 copy 代碼的時候手抖了,我就來勁了。

馬上去 Java Bug 的頁面上拿著那串數字搜一下,還真有意外收穫:

https://bugs.openjdk.java.net/browse/JDK-8201634

在這個 bug 的描述裡面,他讓我註意到了源碼的這個地方:

原來這個地方的註釋代表著一個論文呀,那麼這個論文裡面肯定就藏著這個數的來源。

等等,我怎麼感覺這個論文的名字有點像眼熟啊?

前面 stackoverflow 中提到的這個鏈接,點進去就是一個論文地址:

你看看這個論文的名稱和 Java 這裡的註釋是不是說的一回事呀:

那必須是一回事啊,只是一個小寫一個大寫而已。

所以,到這裡實錘了,確實是最開始寫 Java 這塊源碼的老哥 copy 數字的時候手抖了,少 copy 了一個 “1”。

而且我甚至都能想象到當時寫這部分源碼的時候,那個老哥把“1181783497276652981”這個數字粘過來,發現:哎,這前面怎麼有兩個 1 啊,整重覆了,刪除了吧。

至於把這個“1”刪除了之後,會帶來什麼問題呢?

反正這裡關聯了一個問題,說的是:併發調用 new Random() 的隨機性不夠大。

這我就沒去研究了,有興趣可以去看看,我只負責帶你吃瓜。

所以,基於這個“瓜”,官方修改了一次這個代碼:

剛好我這裡有 JDK 15 和 JDK 8 版本的代碼,我去看了一下,還真是差了一個 “1” :

而且關於隨機數,現在一般很少用 Random 了吧。

直接就是上 ThreadLocalRandom 了,它不香嗎?

什麼,你說不會?


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、線程的兩種調度模型: 1.分時調度模型:所有線程輪流使用CPU的使用權,平均分配每個線程占用CPU的時間片 2.搶占式調度模型:優先讓優先順序高的線程使用CPU,如果線程的優先順序相同,那麼會隨機選擇一個,優先順序高的線程獲取的CPU時間片相對多一點(Java使用的就是搶占式調度模型) 為什麼說搶占式 ...
  • 嵌套:將一系列字典存儲在列表中,或將列表作為值存儲在字典中,這稱為嵌套。既可以在列表中嵌套字典,也可以在字典中嵌套列表,甚至在字典中嵌套字典。 一、列表中嵌套字典 1)一般創建方式: student_A ={'name':'Allen','age':'14','grade':'8'} student ...
  • 第一章正則表達式 1.3 1.3.12 使用sub()和subn()搜索與替換 在最後一段,文中提到: “前面講到,使用匹配對象的group()方法除了能夠取出匹配分組編號外,還可以使用\N,其中 N 是在替換字元串中使用的分組編號。下麵的代碼僅僅只是將美式的日期表示法MM/DD/YY{,YY}格式 ...
  • 時間真是好快啊,又到每日跟大家分享Python小技巧的時候了,今天跟大家分享的是爬取豆瓣top250電影。這篇文章我會把源碼 以及視頻教程給大家,想學的小伙伴可以動手操辦起來。話不多說,這就來。 ## 1.python程式 Python學習交流Q群:906715085### #導入庫 import ...
  • 目錄 一.簡介 二.效果演示 三.源碼下載 四.猜你喜歡 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 基礎 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 轉場 零基礎 O ...
  • 前言 水果忍者到家都玩過吧,但是Python寫的水果忍者你肯定沒有玩過。今天就給你表演一個新的,用Python寫一個水果忍者。 水果忍者的玩法很簡單,儘可能的切開拋出的水果就行。 今天就用python簡單的模擬一下這個游戲。在這個簡單的項目中,我們用滑鼠選擇水果來切割,同時炸彈也會隱藏在水果 中,如 ...
  • 一、RedisInsight 簡介 RedisInsight 是一個直觀高效的 Redis GUI 管理工具,它可以對 Redis 的記憶體、連接數、命中率以及正常運行時間進行監控,並且可以在界面上使用 CLI 和連接的 Redis 進行交互(RedisInsight 內置對 Redis 模塊支持): ...
  • 前言 風玫瑰是由氣象學家用於給出如何風速和風向在特定位置通常分佈的簡明視圖的圖形工具。它也可以用來描述空氣質量污染源。 風玫瑰工具使用Matplotlib作為後端。 安裝方式直接使用pip install windrose 導入模塊 Python學習交流Q群:906715085#### import ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...