1. 程式人生 > >劍指Offer面試題:1.實現Singleton模式

劍指Offer面試題:1.實現Singleton模式

說來慚愧,自己在畢業之前就該好好看看《劍指Offer》這本書的,但是各種原因就是沒看,也因此錯過了很多機會,後悔莫及。但是後悔是沒用的,現在趁還有餘力,把這本書好好看一遍,並通過C#通通實現一遍,並記錄在我的部落格中,作為學習筆記。

book

一、題目:實現Singleton模式

題目:設計一個類,我們只能生成該類的一個例項。 

Singleton

  只能生成一個例項的類是實現了Singleton(單例)模式的型別。由於設計模式在面向物件程式設計中起著舉足輕重的作用,在面試過程中很多公司都喜歡問一些與設計模式相關的問題。在常用的模式中,Singleton是唯一一個能夠用短短几十行程式碼完整實現的模式。因此,寫一個Singleton的型別是一個很常見的面試題。

  例如,在一個Flappy Bird遊戲中,小鳥這個遊戲物件在整個遊戲中應該只存在一個例項,所有對於這個小鳥的操作(向上飛、向下掉等)都應該只會針對唯一的一個例項進行。

二、幾種不好的解法

2.1 不好的解法一:只適用於單執行緒環境

    public sealed class Singleton1
    {
        private Singleton1() { }

        private static Singleton1 instance = null;

        public static Singleton1 Instance
        {
            
get { if(instance == null) { instance = new Singleton1(); } return instance; } } }

  解法一的程式碼在單執行緒的時候工作正常,但在多執行緒的情況下多個執行緒都會建立一個自己的例項,無法保證單例模式的要求。

2.2 不好的解法二:雖然在多執行緒環境中能工作但效率不高 

    public sealed class Singleton2
    {
        private Singleton2() { }

        private static readonly object syncObject = new object();

        private static Singleton2 instance = null;

        public static Singleton2 Instance
        {
            get
            {
                // 每個執行緒來之前先等待鎖
                lock(syncObject)
                {
                    if (instance == null)
                    {
                        instance = new Singleton2();
                    }
                }

                return instance;
            }
        }
    }

  解法二就保證了我們在多執行緒環境中也只能得到一個例項,但是加鎖是一個非常耗時的操作,在沒有必要的時候我們應該儘量避免

2.3 可行的解法三:加同步鎖前後兩次判斷例項是否已存在

  前面講到的執行緒安全的實現方式的問題是要進行同步操作,那麼我們是否可以降低通過操作的次數呢?其實我們只需在同步操作之前,新增判斷該例項是否為null就可以降低通過操作的次數了,這樣是經典的Double-Checked Locking方法,修改上面的屬性程式碼如下:

    public static Singleton3 Instance
    {
        get
        {
            // Double-Check 雙重判斷避免不必要的加鎖
            if (instance == null)
            {
                // 確定例項為空時再等待加鎖
                lock (syncObject)
                {
                    // 確定加鎖後例項仍然未建立
                    if (instance == null)
                    {
                        instance = new Singleton3();
                    }
                }
            }

            return instance;
        }
    }

  解法三用加鎖機制來確保在多執行緒環境下只建立一個例項,並且用兩個if判斷來提高效率。但是,這樣的程式碼實現起來比較複雜,容易出錯。

三、兩種較好的解法

3.1 較好的解法一:利用靜態建構函式

C#的語法中有一個函式能夠確保只調用一次,那就是靜態建構函式。由於C#是在呼叫靜態建構函式時初始化靜態變數,.NET執行時(CLR)能夠確保只調用一次靜態建構函式,這樣我們就能夠保證只初始化一次instance。

    public sealed class Singleton4
    {
        private Singleton4() { }
        // 在大多數情況下,靜態初始化是在.NET中實現Singleton的首選方法。
        static Singleton4() { }

        private static readonly Singleton4 instance = new Singleton4();

        public static Singleton4 Instance
        {
            get
            {
                return instance;
            }
        }
    }

  該解法是在 .NET 中實現 Singleton 的首選方法,但是,由於在C#中呼叫靜態建構函式的時機不是由程式設計師掌控的,而是當.NET執行時發現第一次使用該型別的時候自動呼叫該型別的靜態建構函式(也就是說在用到Singleton4時就會被建立,而不是用到Singleton4.Instance時),這樣會過早地建立例項,從而降低記憶體的使用效率。此外,靜態建構函式由 .NET Framework 負責執行初始化,我們對對例項化機制的控制權也相對較少。

3.2 較好的解法二:實現按需建立例項

    public sealed class Singleton5
    {
        private Singleton5() { }

        public static Singleton5 Instance
        {
            get
            {
                return Nested.instance;
            }
        }

        // 使用內部類+靜態建構函式實現延遲初始化
        class Nested
        {
            static Nested() { }

            internal static readonly Singleton5 instance = new Singleton5();
        }
    }

  該解法在內部定義了一個私有型別Nested。當第一次用到這個巢狀型別的時候,會呼叫靜態建構函式建立Singleton5的例項instance。如果我們不呼叫屬性Singleton5.Instance,那麼就不會觸發.NET執行時(CLR)呼叫Nested,也就不會建立例項,因此也就保證了按需建立例項(或延遲初始化)。

四、總結

  在前面的5種實現單例模式的方法中:

  第一種方法在多執行緒環境中不能正常工作,第二種模式雖然能在多執行緒環境中正常工作但時間效率很低,都不是面試官期待的解法。在第三種方法中我們通過兩次判斷一次加鎖確保在多執行緒環境能高效率地工作。

  第四種方法利用C#的靜態建構函式的特性,確保只建立一個例項。第五種方法利用私有巢狀型別的特性,做到只在真正需要的時候才會建立例項,提高空間使用效率。

作者:周旭龍

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連結。

相關推薦

Offer試題1.實現Singleton模式

說來慚愧,自己在畢業之前就該好好看看《劍指Offer》這本書的,但是各種原因就是沒看,也因此錯過了很多機會,後悔莫及。但是後悔是沒用的,現在趁還有餘力,把這本書好好看一遍,並通過C#通通實現一遍,並記錄在我的部落格中,作為學習筆記。 一、題目:實現Singleton模式 題目:設計一個類,我們只能

Offer試題6.用兩個棧實現佇列

一、題目:用兩個棧實現佇列 題目:用兩個棧實現一個佇列。佇列的宣告如下,請實現它的兩個函式appendTail和deleteHead,分別完成在佇列尾部插入結點和在佇列頭部刪除結點的功能。   原文是使用C++結合模板實現的定義,這裡我們採用C#結合泛型來實現這個佇列的定義,我們要實現的就是兩

Offer試題11.列印1到最大的n位數

一、題目:列印1到最大的n位數 題目:輸入數字n,按順序打印出從1最大的n位十進位制數。比如輸入3,則打印出1、2、3一直到最大的3位數即999。 二、不同的解法 2.1 不假思索的解法   最容易想到的辦法是先求出最大的n位數,然後用一個迴圈從1開始逐個列印: static v

Offer試題9.二進位制中1的個數

一、題目:二進位制中1的個數 題目:請實現一個函式,輸入一個整數,輸出該數二進位制表示中1的個數。例如把9表示成二進位制是1001,有2位是1。因此如果輸入9,該函式輸出2。 二、可能引起死迴圈的解法   一個基本的思路:先判斷整數二進位制表示中最右邊一位是不是1。接著把輸入的整數右移一位,此時

Offer試題12.在O(1)時間刪除連結串列結點

一、題目:在O(1)時間刪除連結串列結點 題目:給定單向連結串列的頭指標和一個結點指標,定義一個函式在O(1)時間刪除該結點。   原文采用的是C/C++,這裡採用C#,節點定義如下: public class Node<T> { // 資料域

offer——試題15.1判斷一個數是否為2的整數次方

lose while ios play 技術 using pow ret offer 1 #include"iostream" 2 using namespace std; 3 4 bool IsTwoPower(int n) 5 { 6 retu

offer 試題重建二叉樹

題目:輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建出該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重複的數字。例如輸入前序遍歷序列{1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6},則重建二叉樹並返回。 思路:二叉樹先序是根左右,中序 是左根右。所以先找到

offer 試題從尾到頭列印連結串列

題目:輸入一個連結串列,按連結串列值從尾到頭的順序返回一個ArrayList。 思路:有多種放法。(1)先反轉連結串列,再列印連結串列。(2)使用棧。 /** * struct ListNode { * int val; * struct ListNode *n

Offer試題17.樹的子結構

一、題目:樹的子結構 題目:輸入兩棵二叉樹A和B,判斷B是不是A的子結構。例如下圖中的兩棵二叉樹,由於A中有一部分子樹的結構和B是一樣的,因此B是A的子結構。   該二叉樹的節點定義如下,這裡使用C#語言描述: public class BinaryTreeNode {

Offer試題31.兩個連結串列的第一個公共節點

一、題目:兩個連結串列的第一個公共節點 題目:輸入兩個連結串列,找出它們的第一個公共結點。   連結串列結點定義如下,這裡使用C#語言描述: public class Node { public int key; public Node

Offer試題2.二維陣列中的查詢

一、題目:二維陣列中的查詢 題目:在一個二維陣列中,每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請完成一個函式,輸入這樣的一個二維陣列和一個整數,判斷陣列中是否含有該整數。     例如下面的二維陣列就是每行、每列都遞增排序。如果在這個陣列中查詢數字7,則返回true;

Offer試題22.二叉搜尋樹的後序遍歷序列

一、題目:二叉搜尋樹的後序遍歷序列 題目:輸入一個整數陣列,判斷該陣列是不是某二叉搜尋樹的後序遍歷的結果。如果是則返回true,否則返回false。假設輸入的陣列的任意兩個數字都互不相同。   例如在下面的一顆二叉搜尋樹中,輸入陣列{5,7,6,9,11,10,8},則返回true,因為這個整數序列是

Offer試題14.連結串列的倒數第k個節點

PS:這是一道出境率極高的題目,記得去年參加校園招聘時我看到了3次,但是每次寫的都不完善。 一、題目:連結串列的倒數第k個節點 題目:輸入一個連結串列,輸出該連結串列中倒數第k個結點。為了符合大多數人的習慣,本題從1開始計數,即連結串列的尾結點是倒數第1個結點。例如一個連結串列有6個結點,從頭結點開始

Offer試題28.連續子陣列的最大和

一、題目:連續子陣列的最大和 題目:輸入一個整型陣列,數組裡有正數也有負數。陣列中一個或連續的多個整陣列成一個子陣列。求所有子陣列的和的最大值。要求時間複雜度為O(n)。例如輸入的陣列為{1,-2,3,10,-4,7,2,-5},和最大的子陣列為{3,10,-4,7,2},因此輸出為該子陣列的和18。

Offer試題4.從尾到頭列印連結串列

一、題目:從尾到頭列印連結串列 題目:輸入一個連結串列的頭結點,從尾到頭反過來打印出每個結點的值。   到解決這個問題肯定要遍歷連結串列。遍歷的順序是從頭到尾的順序,可輸出的順序卻是從尾到頭。也就是說第一個遍歷到的結點最後一個輸出,而最後一個遍歷到的結點第一個輸出。這就是典型的“後進先出”,我

Offer試題10.數值的整數次方

一、題目:數值的整數次方 題目:實現函式double Power(doublebase, int exponent),求base的exponent次方。不得使用庫函式,同時不需要考慮大數問題。   在.NET Framework提供的BCL中,Math類實現了一個Pow方法,例如要求2的三次方,可

Offer試題20.棧的壓入、彈出序列

一、題目:棧的壓入、彈出序列 題目:輸入兩個整數序列,第一個序列表示棧的壓入順序,請判斷第二個序列是否為該棧的彈出順序。假設壓入棧的所有數字均不相等。例如序列1、2、3、4、5是某棧的壓棧序列,序列4、5、3、2、1是該壓棧序列對應的一個彈出序列,但4、3、5、1、2就不可能是該壓棧序列的彈出序列。

Offer試題30.第一個只出現一次的字元

一、題目:第一個只出現一次的字元 題目:在字串中找出第一個只出現一次的字元。如輸入"abaccdeff",則輸出'b'。要求時間複雜度為O(n)。   最直觀的想法是從頭開始掃描這個字串中的每個字元。當訪問到某字元時拿這個字元和後面的每個字元相比較,如果在後面沒有發現重複的字元,則該字元就是隻出現

Offer試題34.翻轉單詞順序VS左旋轉字串

一、題目一:翻轉單詞順序 1.1 題目說明 題目一:輸入一個英文句子,翻轉句子中單詞的順序,但單詞內字元的順序不變。為簡單起見,標點符號和普通字母一樣處理。例如輸入字串"I am a student.",則輸出"student. a am I"。 1.2 解題思路   第一步翻轉句子中所有的字

Offer試題23.二叉樹中和為某一值的路徑

一、題目:二叉樹中和為某一值的路徑 題目:輸入一棵二叉樹和一個整數,打印出二叉樹中結點值的和為輸入整數的所有路徑。從樹的根結點開始往下一直到葉結點所經過的結點形成一條路徑。例如輸入下圖中二叉樹和整數22,則打印出兩條路徑,第一條路徑包含結點10、12,第二條路徑包含結點10、5和7。   二叉