漫話:給女友解釋爲何隨機播放歌曲並不隨機


週末,開車帶女友出去玩,車裏面,隨機播放着周杰倫的歌曲。我正沉浸在『得兒飄,得兒飄,得兒意的飄』中,幻想着本身是秋名山車神,忽然,旁邊的豆腐,哦不,女友說話了。java






僞隨機性(英語:Pseudorandomness)是一個過程彷佛是隨機的,但實際上並非。僞隨機數是看似隨機實質是固定的週期性序列,也就是有規則的隨機。
linux




什麼是隨機數

隨機數在計算機應用中使用的比較普遍,最爲熟知的即是在密碼學中的應用。隨機數有3個特性,具體以下:算法

隨機性:不存在統計學誤差,是徹底雜亂的數列api

不可預測性:不能從過去的數列推測出下一個出現的數安全

不可重現性:除非將數列自己保存下來,不然不能重現相同的數列bash

音樂播放器的隨機播放如何實現的

如今的音樂播放器都比較智能了,都帶有一些相似於歌曲推薦的功能,會給聽衆隨機推薦歌曲,這種是基於用戶聽歌習慣的隨機推薦,不在咱們討論的範圍內。咱們只討論那種簡單的,好比使用簡單的播放器隨機播放一個CD卡中的列表的狀況。多線程

常見的音樂隨機播放算法有兩種,分別是Random算法和Shuffle算法。併發

Random算法dom

Random算法相對比較簡單,播放當前歌曲時才隨機生成下一曲。函數

Random算法是在選取即將播放的歌曲時,進行一個隨機數的運算,獲得即將播放的歌曲在播放列表中的索引,播放列表自己並無被打亂,只是利用隨機函數從播放列表中選取一首歌曲進行播放而已。

可使用Java語言實現這種Random隨機數算法:

Calendar ca = Calendar.getInstance();//獲取系統當前時間
int i;
Random rand =new Random(ca.get(Calendar.MINUTE)*ca.get(Calendar.SECOND));//將隨機數的種子設置爲當前系統時間的分*秒
i=rand.nextInt(maxnum);//maxnum是隨機數最大不超過得值
複製代碼

Random算法另外一個缺陷是當點擊「上一曲」時,跟「下一曲」功能徹底同樣,都是從新生成隨機數,並利用它從播放列表中選取歌曲進行播放,而不會回到剛剛播放的那一首歌。






Shuffle算法

Shuffle算法和排序算法正好相反,是從有序到亂序的一個過程,俗稱洗牌算法。

它將播放列表中的歌曲順序打亂,變成一個和原來歌曲順序沒有任何關係的亂序的播放列表,以後進行歌曲的播放,並支持當用戶點擊「上一首」時,可以回到剛剛播放的那一首歌曲。

這種算法相對於Random算法來講,並非徹底意義上的隨機,只不過是對歌曲列表的亂序而已,歌曲的播放順序仍是相對固定的。

在Java中,有現成的shuffle算法實現,即Collections類中的兩個重載的shuffle方法:

public static void shuffle(List<?> list) {
    Random rnd = r;
    if (rnd == null)
        r = rnd = new Random();
    shuffle(list, rnd);
}
private static Random r;

public static void shuffle(List<?> list, Random rnd) {
    int size = list.size();
    if (size < SHUFFLE_THRESHOLD || list instanceof RandomAccess) {
        for (int i=size; i>1; i--)
            swap(list, i-1, rnd.nextInt(i));
    } else {
        Object arr[] = list.toArray();

        // Shuffle array
        for (int i=size; i>1; i--)
            swap(arr, i-1, rnd.nextInt(i));

        // Dump array back into list
        ListIterator it = list.listIterator();
        for (int i=0; i<arr.length; i++) {
            it.next();
            it.set(arr[i]);
        }
    }
}
複製代碼







真隨機與僞隨機

隨機數分爲真隨機數和僞隨機數,咱們程序使用的基本都是僞隨機數,其中僞隨機又分爲強僞隨機數和弱僞隨機數。

  • 真隨機數,經過物理實驗得出,好比擲錢幣、骰子、轉輪、使用電子元件的噪音、核裂變等。須要知足隨機性、不可預測性、不可重現性。

  • 僞隨機數,經過必定算法和種子得出。軟件實現的是僞隨機數。

    • 強僞隨機數,難以預測的隨機數。須要知足隨機性和不可預測性。

    • 弱僞隨機數,易於預測的隨機數。須要知足隨機性。

上面介紹Random算法和Shuffle算法的時候,代碼實現都是僞隨機算法。能夠這樣說:

只要這個隨機數是由肯定算法生成的,那就是僞隨機。只能經過不斷算法優化,使你的隨機數更接近隨機。

有限狀態機不能產生真正的隨機數的,因此,現代計算機中,沒法經過一個純算法來生成真正的隨機數,不管是哪一種語言,單純的算法生成的數字都是僞隨機數,都是由可肯定的函數經過一個種子,產生的僞隨機數。

這也就意味着,若是知道了種子,就能夠推斷接下來的隨機數序列的信息。這就有了可預測性。

那麼真隨機數怎麼產生的呢?

經過真實隨機事件取得的隨機數纔是真隨機數。

真正的隨機數是使用物理現象產生的:好比擲錢幣、骰子、轉輪、使用電子元件的噪音、核裂變等等。這樣的隨機數發生器叫作物理性隨機數發生器,它們的缺點是技術要求比較高,效率低。

現有的真隨機數生成器,好比PuTTYgen的隨機數是讓用戶移動鼠標達到必定的長度,以後把鼠標的運動軌跡轉化爲種子;Intel經過電阻和振盪器來生成熱噪聲做爲信息熵資源;Unix/Linux的dev/random和/dev/urandom採用硬件噪音生成隨機數;

因此,要想生成真的隨機數,是沒法用任何一個純算法實現的。都須要藉助外部物理現象。






Java中的隨機數生成器

Java語言提供了幾種隨機數生成器,如前面提到的Random類,還有SecureRandom類。

僞隨機數生成器

僞隨機數發生器採用特定的算法,將隨機數種子seed轉換成一系列的僞隨機數。僞隨機數依賴於seed的值,給定相同的seed值老是生成相同的隨機數。僞隨機數的生成過程只依賴CPU,不依賴任何外部設備,生成速度快,不會阻塞。

Java提供的僞隨機數發生器有java.util.Random類和java.util.concurrent.ThreadLocalRandom類。

Random類採用AtomicLong實現,保證多線程的線程安全性,但正如該類註釋上說明的,多線程併發獲取隨機數時性能較差。

多線程環境中可使用ThreadLocalRandom做爲隨機數發生器,ThreadLocalRandom採用了線程局部變量來改善性能,這樣就可使用long而不是AtomicLong,此外,ThreadLocalRandom還進行了字節填充,以免僞共享。

強隨機數發生器

強隨機數發生器依賴於操做系統底層提供的隨機事件。強隨機數生成器的初始化速度和生成速度都較慢,並且因爲須要必定的熵累積才能生成足夠強度的隨機數,因此可能會形成阻塞。熵累積一般來源於多個隨機事件源,如敲擊鍵盤的時間間隔,移動鼠標的距離與間隔,特定中斷的時間間隔等。因此,只有在須要生成加密性強的隨機數據的時候才用它。

Java提供的強隨機數發生器是java.security.SecureRandom類,該類也是一個線程安全類,使用synchronize方法保證線程安全,但jdk並無作出承諾在未來改變SecureRandom的線程安全性。所以,同Random同樣,在高併發的多線程環境中可能會有性能問題。

在linux的實現中,可使用/dev/random/dev/urandom做爲隨機事件源。因爲/dev/random是堵塞的,在讀取隨機數的時候,當熵池值爲空的時候會堵塞影響性能,尤爲是系統大併發的生成隨機數的時候。

真隨機數發生器

在Linux系統中,SecureRandom的實現藉助了/dev/random/dev/urandom,可使用硬件噪音生成隨機數;

http://random.org/,從1998年開始提供在線真隨機數服務了,它用大氣噪音生成真隨機數。他也提供了Java工具類,能夠拿來使用。地址:https://sourceforge.net/projects/randomjapi/






爲了躲避這個看(wu)似(li)合(qu)理(nao)的問題,我拉着她回到車子,找了一首她最喜歡的《演員》單曲循環了。


相關文章
相關標籤/搜索