這些車15萬預算就給你30萬般高顏值上檔次的內飾!

99-15。99萬英朗外觀造型非常的有神韻。英朗的懸浮式的中控台造型上讓車廂內更具簡潔、時尚的氣質,懸浮式的中控台設計層次分明,按鍵數量精簡,簡約設計更讓視覺效果更顯檔次,英朗的內飾做工用料出色,銀色飾板的點綴到位,給人厚重紮實的感覺,全彩的3。

一台車,我們待的時間最多就是在車廂里,而外觀只有在上車時或者下車時,多看兩眼,正所謂,一步三回頭,欣賞它的美。所以內飾對我我們來說也是非常重要,好看的內飾,決定了你的品位,好看的內飾,還能調動你內心的情緒。

我們都知道

概念車的內飾

可實際上靠譜的內飾應該是這樣

東風本田-傑德

指導價:14.98-18.38萬

傑德的外觀可是顏值爆表,尤其是側面,非常符合年輕人的審美。傑德的內飾也是它的亮點之一,傑德的內飾整體給人一種很居家的感覺,沒有任何誇張的元素,簡約實用的設計風格。傑德的內飾雖然沒有讓人過目不忘的視覺衝擊,但是整體感覺非常舒適溫馨,中控台的設計也比較簡單,區域的劃分也很明顯,按鍵的布局排列也十分整齊。內種大面積的採用了木紋裝飾板,這種裝飾板摸上去手感還是不錯的,整體效果看上去更更居家,傑德採用了液晶屏式的儀錶盤,相對比傳統樣式的儀錶盤,信息的显示會更加豐富。時尚運動的外表之下,也可以有一顆居家的心,這就是傑德內飾給我的感覺。

上汽通用別克-英朗

指導價:10.99-15.99萬

英朗外觀造型非常的有神韻。英朗的懸浮式的中控台造型上讓車廂內更具簡潔、時尚的氣質,懸浮式的中控台設計層次分明,按鍵數量精簡,簡約設計更讓視覺效果更顯檔次,英朗的內飾做工用料出色,銀色飾板的點綴到位,給人厚重紮實的感覺,全彩的3.5英寸液晶儀錶清晰明了,錶盤刻度設計非常精緻,行車電腦显示信息豐富。英朗的內飾做工用料非常出色,坐在車內,你能感受到沉穩、從容的感覺。

長安標緻雪鐵龍-DS 5LS

指導價:14.98-24.68萬

法系車的外觀設計一直都是天馬行空,現在這種誇張的元素也慢慢收斂了許多,慢慢趨向大眾化,但它的外觀設計依然是特別有味道,DS 5LS外形依然顯豪華的質感,非常搶眼!5LS的內飾設計非常精緻和豪華感,中控的布局也是比較分明,功能區按鍵手感細膩,空間區按鍵和旋鈕搭配的布局合理,直觀,使用便捷,非常精緻,細節之處凸顯文化底蘊。DS 5LS的儀錶盤造型也很獨特、很有科技感,行車電腦显示的信息也比較豐富。DS 5LS的內飾設計更加豪華、上檔次。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

網頁設計最專業,超強功能平台可客製化

※別再煩惱如何寫文案,掌握八大原則!

聽說奧迪要找“新歡”?一汽只打算用硬實力說話

前有一汽-大眾奧迪佔盡先機,後有競爭對手環飼左右,前景如何,殊未可知。相比之下,顯然合作已近30年,彼此知根知底的一汽-大眾奧迪更為老練。從率先實現國產、開創軸距加長,到成為第一個突破“百萬”銷量的豪華車品牌,再到如今打出新能源、数字出行服務和未來營銷這三張牌,都掐住了奧迪未來的全球布局,在中國市場佔盡先機。

11月11日,上汽集團與奧迪汽車股份有限公司在德國沃爾夫斯堡簽署合作框架協議,雙方將成立股比對等的合資公司,共同生產和銷售奧迪相關車型。

上汽和奧迪要聯手的消息早已傳得沸沸揚揚,隨着這次緋聞被坐實,關於奧迪“移情別戀”、“紅杏出牆”、“二嫁上汽大眾”等一系列犀利點評便紛至沓來。

然而,就在大家為一汽-大眾奧迪未來處境擔憂的時候,劇情發生了驚天翻轉——首先在11月14日上汽發布與奧迪合作的公告中,上汽最後明確指出該合作事項存在不確定性,請投資者注意風險。可見,未來仍然存在較大變數,上汽和奧迪的聯姻走向如何尚未可知。

其次就在同一天,奧迪鄭重發出與一汽的未來10年合作計劃。雙方將在電動車、移動出行服務和数字化服務及市場銷售等諸多領域展開深入合作。相比於上汽公告的語焉不詳,這份聲明拳拳到肉,明晰直接得多。

如今中國豪華車市場競爭激烈,奧迪為了滿足戰略需求,在華謀求新的合作夥伴,這無可厚非。不過上汽和奧迪將以怎樣的方式合作、又得經歷怎樣的磨合模式都在摸索中,如何落子仍然舉棋不定,然而變化多端絞殺激烈的市場形勢卻未必給他們足夠的摸索時間。前有一汽-大眾奧迪佔盡先機,後有競爭對手環飼左右,前景如何,殊未可知。

相比之下,顯然合作已近30年,彼此知根知底的一汽-大眾奧迪更為老練。從率先實現國產、開創軸距加長,到成為第一個突破“百萬”銷量的豪華車品牌,再到如今打出新能源、数字出行服務和未來營銷這三張牌,都掐住了奧迪未來的全球布局,在中國市場佔盡先機。未來一汽-大眾奧迪還將國產5款奧迪e-tron車型,其中就包括在今年品牌峰會中被提及的續航里程超過500公里的純電動車型。

此外,奧迪也是最早將4S體系引入到中國市場的豪華品牌。一汽-大眾奧迪在華有近400萬用戶,堅實的群眾基礎和體系能力優勢,也是上汽在短期內無法複製的。

所以,對於一汽來說,奧迪即使找了個“新歡”,也根本構不成威脅,最多也就是一種激勵吧。比較,硬實力不是一天能練成的,領先者的地位也不是能被輕易撼動的。總是周遭再彩旗飄飄,家裡這桿指引方向的紅旗依舊屹立不倒。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

重學 Java 設計模式:實戰橋接模式(多支付渠道「微信、支付寶」與多支付模式「刷臉、指紋」場景)

作者:小傅哥
博客:https://bugstack.cn – 編寫系列原創專題文章

沉澱、分享、成長,讓自己和他人都能有所收穫!

一、前言

為什麼你的代碼那麼多ifelse

同類的業務、同樣的功能,怎麼就你能寫出來那麼多ifelse。很多時候一些剛剛從校園進入企業的萌新,或者一部分從小公司跳槽到大企業的程序員,初次承接業務需求的時候,往往編碼還不成熟,經常一桿到底的寫需求。初次實現確實很快,但是後期維護和擴展就十分痛苦。因為一段代碼的可讀性閱讀他後期的維護成本也就越高。

設計模式是可以幫助你改善代碼

很多時候你寫出來的ifelse都是沒有考慮使用設計模式優化,就像;同類服務的不同接口適配包裝、同類物料不同組合的建造、多種獎品組合的營銷工廠等等。它們都可以讓你代碼中原本使用if判斷的地方,變成一組組類和面向對象的實現過程。

怎麼把設計模式和實際開發結合起來

多從實際場景思考,只找到代碼優化的最佳點,不要可以想着設計模式的使用。就像你最開始看設計模式適合,因為沒有真實的場景模擬案例,都是一些畫圓形、方形,對新人或者理解能力還不到的夥伴來說很不友好。所以即使學了半天 ,但實際使用還是摸不着頭腦。

二、開發環境

  1. JDK 1.8
  2. Idea + Maven
  3. 涉及工程三個,可以通過關注公眾號bugstack蟲洞棧,回復源碼下載獲取(打開獲取的鏈接,找到序號18)
工程 描述
itstack-demo-design-7-01 使用一坨代碼實現業務需求
itstack-demo-design-7-02 通過設計模式優化改造代碼,產生對比性從而學習

三、橋接模式介紹

橋接模式的主要作用就是通過將抽象部分與實現部分分離,把多種可匹配的使用進行組合。說白了核心實現也就是在A類中含有B類接口,通過構造函數傳遞B類的實現,這個B類就是設計的

那麼這樣的橋接模式,在我們平常的開發中有哪些場景

JDBC多種驅動程序的實現、同品牌類型的台式機和筆記本平板、業務實現中的多類接口同組過濾服務等。這些場景都比較適合使用橋接模式進行實現,因為在一些組合中如果有如果每一個類都實現不同的服務可能會出現笛卡爾積,而使用橋接模式就可以非常簡單。

四、案例場景模擬

隨着市場的競爭在支付服務行業出現了微信和支付寶還包括一些其他支付服務,但是對於商家來說並不希望改變用戶習慣。就像如果我的地攤只能使用微信或者只能使用支付寶付款,那麼就會讓我顧客傷心,雞蛋灌餅也賣不動了。

在這個時候就出現了第三方平台,把市面上綜合佔據市場90%以上的支付服務都集中到自己平台中,再把這樣的平台提供給店鋪、超市、地攤使用,同時支持人臉、掃描、密碼多種方式。

我們這個案例就模擬一個這樣的第三方平台來承接各個支付能力,同時使用自家的人臉讓用戶支付起來更加容易。那麼這裏就出現了多支付多模式的融合使用,如果給每一個支付都實現一次不同的模式,即使是繼承類也需要開發好多。而且隨着後面接入了更多的支付服務或者支付方式,就會呈爆炸似的擴展。

所以你現在可以思考一下這樣的場景該如何實現?

五、用一坨坨代碼實現

產品經理說老闆要的需求,要儘快上,kpi你看着弄!

既然你逼我那就別怪我無情,還沒有我一個類寫不完的需求!反正寫完就完事了,拿完績效也要走了天天逼着寫需求,代碼越來越亂心疼後面的兄弟3秒。

1. 工程結構

itstack-demo-design-7-01
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                └── PayController.java
  • 只有一個類裏面都是ifelse,這個類實現了支付和模式的全部功能。

2. 代碼實現

public class PayController {

    private Logger logger = LoggerFactory.getLogger(PayController.class);

    public boolean doPay(String uId, String tradeId, BigDecimal amount, int channelType, int modeType) {
        // 微信支付
        if (1 == channelType) {
            logger.info("模擬微信渠道支付划賬開始。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
            if (1 == modeType) {
                logger.info("密碼支付,風控校驗環境安全");
            } else if (2 == modeType) {
                logger.info("人臉支付,風控校驗臉部識別");
            } else if (3 == modeType) {
                logger.info("指紋支付,風控校驗指紋信息");
            }
        }
        // 支付寶支付
        else if (2 == channelType) {
            logger.info("模擬支付寶渠道支付划賬開始。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
            if (1 == modeType) {
                logger.info("密碼支付,風控校驗環境安全");
            } else if (2 == modeType) {
                logger.info("人臉支付,風控校驗臉部識別");
            } else if (3 == modeType) {
                logger.info("指紋支付,風控校驗指紋信息");
            }
        }
        return true;
    }

}
  • 上面的類提供了一個支付服務功能,通過提供的必要字段;用戶ID交易ID金額渠道模式,來控制支付方式。
  • 以上的ifelse應該是最差的一種寫法,即使寫ifelse也是可以優化的方式去寫的。

3. 測試驗證

3.1 編寫測試類

@Test
public void test_pay() {
    PayController pay = new PayController();
    System.out.println("\r\n模擬測試場景;微信支付、人臉方式。");
    pay.doPay("weixin_1092033111", "100000109893", new BigDecimal(100), 1, 2);
    
    System.out.println("\r\n模擬測試場景;支付寶支付、指紋方式。");
    pay.doPay("jlu19dlxo111","100000109894",new BigDecimal(100), 2, 3);
}
  • 以上分別測試了兩種不同的支付類型和支付模式;微信人臉支付、支付寶指紋支付

3.2 測試結果

模擬測試場景;微信支付、人臉方式。
23:05:59.152 [main] INFO  o.i.demo.design.pay.channel.Pay - 模擬微信渠道支付划賬開始。uId:weixin_1092033111 tradeId:100000109893 amount:100
23:05:59.155 [main] INFO  o.i.demo.design.pay.mode.PayCypher - 人臉支付,風控校驗臉部識別
23:05:59.155 [main] INFO  o.i.demo.design.pay.channel.Pay - 模擬微信渠道支付風控校驗。uId:weixin_1092033111 tradeId:100000109893 security:true
23:05:59.155 [main] INFO  o.i.demo.design.pay.channel.Pay - 模擬微信渠道支付划賬成功。uId:weixin_1092033111 tradeId:100000109893 amount:100

模擬測試場景;支付寶支付、指紋方式。
23:05:59.156 [main] INFO  o.i.demo.design.pay.channel.Pay - 模擬支付寶渠道支付划賬開始。uId:jlu19dlxo111 tradeId:100000109894 amount:100
23:05:59.156 [main] INFO  o.i.demo.design.pay.mode.PayCypher - 指紋支付,風控校驗指紋信息
23:05:59.156 [main] INFO  o.i.demo.design.pay.channel.Pay - 模擬支付寶渠道支付風控校驗。uId:jlu19dlxo111 tradeId:100000109894 security:true
23:05:59.156 [main] INFO  o.i.demo.design.pay.channel.Pay - 模擬支付寶渠道支付划賬成功。uId:jlu19dlxo111 tradeId:100000109894 amount:100

Process finished with exit code 0
  • 從測試結果看已經滿足了我們的不同支付類型和支付模式的組合,但是這樣的代碼在後面的維護以及擴展都會變得非常複雜。

六、橋接模式重構代碼

接下來使用橋接模式來進行代碼優化,也算是一次很小的重構。

從上面的ifelse方式實現來看,這是兩種不同類型的相互組合。那麼就可以把支付方式支付模式進行分離通過抽象類依賴實現類的方式進行橋接,通過這樣的拆分后支付與模式其實是可以單獨使用的,當需要組合時候只需要把模式傳遞給支付即可。

橋接模式的關鍵是選擇的橋接點拆分,是否可以找到這樣類似的相互組合,如果沒有就不必要非得使用橋接模式。

1. 工程結構

itstack-demo-design-7-02
└── src
    ├── main
    │   └── java
    │       └── org.itstack.demo.design.pay
    │           ├── channel
    │           │   ├── Pay.java
    │           │   ├── WxPay.java
    │           │   └── ZfbPay.java
    │           └── mode
    │               ├── IPayMode.java
    │               ├── PayCypher.java
    │               ├── PayFaceMode.java
    │               └── PayFingerprintMode.java
    └── test
         └── java
             └── org.itstack.demo.design.test
                 └── ApiTest.java

橋接模式模型結構

  • 左側Pay是一個抽象類,往下是它的兩個支付類型實現;微信支付、支付寶支付。
  • 右側IPayMode是一個接口,往下是它的兩個支付模型;刷臉支付、指紋支付。
  • 那麼,支付類型 × 支付模型 = 就可以得到相應的組合。

2. 代碼實現

2.1 支付類型橋接抽象類

public abstract class Pay {

    protected Logger logger = LoggerFactory.getLogger(Pay.class);

    protected IPayMode payMode;

    public Pay(IPayMode payMode) {
        this.payMode = payMode;
    }

    public abstract String transfer(String uId, String tradeId, BigDecimal amount);

}
  • 在這個類中定義了支付方式的需要實現的划賬接口:transfer,以及橋接接口;IPayMode,並在構造函數中用戶方自行選擇支付方式。
  • 如果沒有接觸過此類實現,可以重點關注 IPayMode payMode,這部分是橋接的核心。

2.2 兩個支付類型的實現

微信支付

public class WxPay extends Pay {

    public WxPay(IPayMode payMode) {
        super(payMode);
    }

    public String transfer(String uId, String tradeId, BigDecimal amount) {
        logger.info("模擬微信渠道支付划賬開始。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
        boolean security = payMode.security(uId);
        logger.info("模擬微信渠道支付風控校驗。uId:{} tradeId:{} security:{}", uId, tradeId, security);
        if (!security) {
            logger.info("模擬微信渠道支付划賬攔截。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
            return "0001";
        }
        logger.info("模擬微信渠道支付划賬成功。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
        return "0000";
    }

}

支付寶支付

public class ZfbPay extends Pay {

    public ZfbPay(IPayMode payMode) {
        super(payMode);
    }

    public String transfer(String uId, String tradeId, BigDecimal amount) {
        logger.info("模擬支付寶渠道支付划賬開始。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
        boolean security = payMode.security(uId);
        logger.info("模擬支付寶渠道支付風控校驗。uId:{} tradeId:{} security:{}", uId, tradeId, security);
        if (!security) {
            logger.info("模擬支付寶渠道支付划賬攔截。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
            return "0001";
        }
        logger.info("模擬支付寶渠道支付划賬成功。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
        return "0000";
    }

}
  • 這裏分別模擬了調用第三方的兩個支付渠道;微信、支付寶,當然作為支付綜合平台可能不只是接了這兩個渠道,還會有其很跟多渠道。
  • 另外可以看到在支付的時候分別都調用了風控的接口進行驗證,也就是不同模式的支付(刷臉指紋),都需要過指定的風控,才能保證支付安全。

2.3 定義支付模式接口

public interface IPayMode {

    boolean security(String uId);

}
  • 任何一個支付模式;刷臉、指紋、密碼,都會過不同程度的安全風控,這裏定義一個安全校驗接口。

2.4 三種支付模式風控(刷臉、指紋、密碼)

刷臉

public class PayFaceMode implements IPayMode{

    protected Logger logger = LoggerFactory.getLogger(PayCypher.class);

    public boolean security(String uId) {
        logger.info("人臉支付,風控校驗臉部識別");
        return true;
    }

}

指紋

public class PayFingerprintMode implements IPayMode{

    protected Logger logger = LoggerFactory.getLogger(PayCypher.class);

    public boolean security(String uId) {
        logger.info("指紋支付,風控校驗指紋信息");
        return true;
    }

}

密碼

public class PayCypher implements IPayMode{

    protected Logger logger = LoggerFactory.getLogger(PayCypher.class);

    public boolean security(String uId) {
        logger.info("密碼支付,風控校驗環境安全");
        return true;
    }

}
  • 在這裏實現了三種支付模式(刷臉、指紋、密碼)的風控校驗,在用戶選擇不同支付類型的時候,則會進行相應的風控攔截以此保障支付安全。

3. 測試驗證

3.1 編寫測試類

@Test
public void test_pay() {
    System.out.println("\r\n模擬測試場景;微信支付、人臉方式。");
    Pay wxPay = new WxPay(new PayFaceMode());
    wxPay.transfer("weixin_1092033111", "100000109893", new BigDecimal(100));

    System.out.println("\r\n模擬測試場景;支付寶支付、指紋方式。");
    Pay zfbPay = new ZfbPay(new PayFingerprintMode());
    zfbPay.transfer("jlu19dlxo111","100000109894",new BigDecimal(100));
}
  • 與上面的ifelse實現方式相比,這裏的調用方式變得整潔、乾淨、易使用;new WxPay(new PayFaceMode())new ZfbPay(new PayFingerprintMode())
  • 外部的使用接口的用戶不需要關心具體的實現,只按需選擇使用即可。

3.2 測試結果

模擬測試場景;微信支付、人臉方式。
23:14:40.911 [main] INFO  o.i.demo.design.pay.channel.Pay - 模擬微信渠道支付划賬開始。uId:weixin_1092033111 tradeId:100000109893 amount:100
23:14:40.914 [main] INFO  o.i.demo.design.pay.mode.PayCypher - 人臉支付,風控校驗臉部識別
23:14:40.914 [main] INFO  o.i.demo.design.pay.channel.Pay - 模擬微信渠道支付風控校驗。uId:weixin_1092033111 tradeId:100000109893 security:true
23:14:40.915 [main] INFO  o.i.demo.design.pay.channel.Pay - 模擬微信渠道支付划賬成功。uId:weixin_1092033111 tradeId:100000109893 amount:100

模擬測試場景;支付寶支付、指紋方式。
23:14:40.915 [main] INFO  o.i.demo.design.pay.channel.Pay - 模擬支付寶渠道支付划賬開始。uId:jlu19dlxo111 tradeId:100000109894 amount:100
23:14:40.915 [main] INFO  o.i.demo.design.pay.mode.PayCypher - 指紋支付,風控校驗指紋信息
23:14:40.915 [main] INFO  o.i.demo.design.pay.channel.Pay - 模擬支付寶渠道支付風控校驗。uId:jlu19dlxo111 tradeId:100000109894 security:true
23:14:40.915 [main] INFO  o.i.demo.design.pay.channel.Pay - 模擬支付寶渠道支付划賬成功。uId:jlu19dlxo111 tradeId:100000109894 amount:100

Process finished with exit code 0
  • 從測試結果看內容是一樣的,但是整體的實現方式有了很大的變化。所以有時候不能只看結果,也要看看過程

七、總結

  • 通過模擬微信與支付寶兩個支付渠道在不同的支付模式下,刷臉指紋密碼,的組合從而體現了橋接模式的在這類場景中的合理運用。簡化了代碼的開發,給後續的需求迭代增加了很好的擴展性。
  • 從橋接模式的實現形式來看滿足了單一職責和開閉原則,讓每一部分內容都很清晰易於維護和拓展,但如果我們是實現的高內聚的代碼,那麼就會很複雜。所以在選擇重構代碼的時候,需要考慮好整體的設計,否則選不到合理的設計模式,將會讓代碼變得難以開發。
  • 任何一種設計模式的選擇和使用都應該遵頊符合場景為主,不要刻意使用。而且統一場景因為業務的複雜從而可能需要使用到多種設計模式的組合,才能將代碼設計的更加合理。但這種經驗需要從實際的項目中學習經驗,並提不斷的運用。

八、推薦閱讀

  • 1. 重學 Java 設計模式:實戰工廠方法模式(多種類型商品發獎場景)
  • 2. 重學 Java 設計模式:實戰抽象工廠模式(替換Redis雙集群升級場景)
  • 3. 重學 Java 設計模式:實戰建造者模式(裝修物料組合套餐選配場景)
  • 4. 重學 Java 設計模式:實戰原型模式(多套試每人題目和答案亂序場景)
  • 5. 重學 Java 設計模式:實戰單例模式(Effective Java 作者推薦枚舉單例模式)

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

網頁設計最專業,超強功能平台可客製化

※別再煩惱如何寫文案,掌握八大原則!

MongoDB 4.X CRUD基本操作

本文總結了MongoDB 4.X在mongo shell客戶端涉及的對文檔一些基本的增刪改查操作,即CRUD操作。主要結合了自己平時使用MongoDB的操作命令,更詳細的命令可以參考官方文檔: https://docs.mongodb.com/manual/crud/

創建(Create Operations)

創建(Create Operations)也叫插入操作,當集合不存在時,插入操作同時也會創建集合。MongoDB提供以下幾種插入文檔方法:

  • db.collection.insert():在指定集合中插入單個或多個文檔。
  • db.collection.insertOne():在指定集合中插入單個文檔(版本3.2新增)。
  • db.collection.insertMany():在指定集合中插入多個文檔(版本3.2新增)。

db.collection.insert()

在平時的使用當中,db.collection.insert()是我用得最多的文檔插入方式,具體的語法格式如下:

db.collection.insert(
   <document or array of documents>,
   {
     writeConcern: <document>,
     ordered: <boolean>
   }
)

參數說明:

  • document:指定一個或多個文檔;
  • writeConcern:文檔寫入確認級別(可選),關於讀寫策略確認級別,以後再進行討論;
  • ordered:指定文檔是否按順序插入(可選),默認為true;
    • 當指定為true時,插入多個文檔時將文檔排序保存在一個數組中進行插入,如果其中有一個文檔插入失敗,則會導致數組中餘下的文檔不進行插入操作;
    • 當指定為false時,插入多個文檔時將文檔不進行排序保存在一個數組中進行插入,如果其中有一個文檔插入失敗,則不影響數組中餘下的文檔進行插入操作。

如果插入的文檔當中沒有指定_id字段,則MongoDB會自動為文檔生成具有唯一ObjectId值的字段_id

使用示例:

// 沒有指定_id字段的插入單個文檔
db.products.insert( { item: "card", qty: 15 } );

// 指定_id字段的插入單個文檔
db.products.insert( { _id: 10, item: "box", qty: 20 } );

// 插入多個文檔,不進行排序,多個文檔包含在數組[]中
db.products.insert(
   [
     { _id: 11, item: "pencil", qty: 50, type: "no.2" },
     { item: "pen", qty: 20 },
     { item: "eraser", qty: 25 }
   ]
);

// 插入多個文檔,並進行排序
db.products.insert(
   [
     { _id: 20, item: "lamp", qty: 50, type: "desk" },
     { _id: 21, item: "lamp", qty: 20, type: "floor" },
     { _id: 22, item: "bulk", qty: 100 }
   ],
   { ordered: false }
);

db.collection.insertOne()

語法格式如下:

db.collection.insertOne(
   <document>,
   {
      writeConcern: <document>
   }
)

參數說明:

參考db.collection.insert()的參數說明。

使用示例:

// 單行插入文檔,關於_id字段指定與否也與db.collection.insert()一致
db.products.insertOne( { item: "card", qty: 15 } );

db.collection.insertMany()

語法格式如下:

db.collection.insertMany(
   [ <document 1> , <document 2>, ... ],
   {
      writeConcern: <document>,
      ordered: <boolean>
   }
)

參數說明:

參考db.collection.insert()的參數說明。

使用示例:

參考db.collection.insert()的參數說明。

關於返回確認信息

db.collection.insert()在插入文檔成功之後返回的信息相對較為簡潔:

db.products.insert( { item: "card", qty: 15 } );
WriteResult({ "nInserted" : 1, "writeConcernError" : [ ] })

db.collection.insertOne()db.collection.insertMany()返回的信息較為詳細:

db.products.insertOne( { item: "card", qty: 15 } );
{
    "acknowledged": true,
    "insertedId": ObjectId("5eccbd214139000074003be8")
}

db.products.insertMany( [
      { _id: 10, item: "large box", qty: 20 },
      { _id: 11, item: "small box", qty: 55 },
      { _id: 12, item: "medium box", qty: 30 }
   ] );
{
    "acknowledged": true,
    "insertedIds": [
        10,
        11,
        12
    ]
}

查詢(Read Operations)

查詢(Read Operations)讀操作,是對集合中已存在的文檔進行查詢,即對應關係型數據庫當中的select操作,比如MySQL,MongoDB提供以下幾種主要查詢文檔方法:

  • db.collection.find():查詢指定集合中滿足條件的一個或多個文檔和視圖;
  • db.collection.findOne():查詢指定集合中滿足條件的第一個文檔,並以格式化方式展現,通過pretty()方法。

來自官方文檔的測試數據:

db.inventory.insertMany([
   { item: "journal", qty: 25, size: { h: 14, w: 21, uom: "cm" }, status: "A" },
   { item: "notebook", qty: 50, size: { h: 8.5, w: 11, uom: "in" }, status: "A" },
   { item: "paper", qty: 100, size: { h: 8.5, w: 11, uom: "in" }, status: "D" },
   { item: "planner", qty: 75, size: { h: 22.85, w: 30, uom: "cm" }, status: "D" },
   { item: "postcard", qty: 45, size: { h: 10, w: 15.25, uom: "cm" }, status: "A" }
]);

db.collection.find()

db.collection.find()可以說是使用頻率最高的方法了,可以用來查詢數據庫集合當中的文檔。

語法格式如下:

db.collection.find(<query>, <projection>)
  • query:查詢表達式;
  • projection:指定查詢結果集中需要显示的字段。
    • Col_name:1|true 代表显示該字段;
    • Col_name:0 | false 代表不显示該字段。

_id字段是默認显示的,如果不想显示,則顯式指定{"_id" : 0}

查詢所有文檔:

db.inventory.find()

db.inventory.find({})

db.collection.findOne()

db.collection.findOne()方法显示符合條件查詢的第一條文檔,接受的參數與db.collection.find()方法一致。

條件查詢操作符

通常對文檔的查詢,是需要帶條件的,而很少使用到不帶條件的全文檔檢索,以下總結了幾種常使用的查詢操作符:

比較操作符

比較操作符涉及的操作如下錶所示:

名稱 說明
$eq 與指定值相等
$gt 大於指定的值
$gte 大於或等於指定的值
$in 指定的值在數組中
$lt 小於指定的值
$lte 小於或等於指定的值
$ne 所有不等於指定的值
$nin 指定的值不在數組中

使用示例:

// $eq:等值查詢 SQL: SELECT * FROM inventory WHERE status = "D";
db.inventory.find( { status: "D" } )

// $ne 同$eq

// $gt:範圍查詢(以大於為例) SQL: SELECT * FROM inventory WHERE qty > 30;
db.inventory.find( { qty: { $gt: 30 } } )

// $gte、$lt、$lte 同$gt

// $in:或查詢,可使用or代替 SQL: SELECT * FROM inventory WHERE status in ("A", "D")
db.inventory.find( { status: { $in: [ "A", "D" ] } } )

// $nin 同$in

邏輯操作符

邏輯操作符涉及的操作如下錶所示:

名稱 說明
$and 指定查詢同時滿足多個條件查詢子句
$not 指定查詢不滿足條件查詢子句
$nor 指定查詢無法滿足多個條件查詢子句
$or 指定查詢滿足其中某個條件查詢子句

使用示例:

// $and: 邏輯與查詢 SQL: SELECT * FROM inventory WHERE status = "A" AND qty < 30;
db.inventory.find( { $and: [ { status: { $eq: "A" },  qty: { $lt: 30 } } ] } )

// $not: 不符合查詢 SQL: SELECT * FROM inventory WHERE status <> "A";
db.inventory.find( { status: { $not: { $eq: "A" } } } )

/*
$nor: 無法同時滿足多個條件查詢,字段不存在時也符合 SQL: SELECT * FROM inventory WHERE status <> "A" AND qty > 30; 
符合以下條件之一都會出現在結果集中:
1.文檔包含status和qty字段並且符合條件;
2.文檔包含status字段並且符合條件,不包含qty字段;
3.文檔不包含status字段,包含qty字段並且符合條件;
4.文檔不包含status字段和qty字段。
*/
db.inventory.find( { $nor: [ { status: { $eq: "A" } },  { qty: { $lt: 30 } } ] } )

// $or: 邏輯或查詢 SQL: SELECT * FROM inventory WHERE status = "A" OR qty < 30;
db.inventory.find( { $or: [ { status: "A" }, { qty: { $lt: 30 } } ] } )

元素操作符

元素操作符主要涉及的操作如下錶所示:

名稱 說明
$exists 指定查詢文檔是否有對應的字段
$type 指定查詢文檔的某個字段是否是對應類型

使用示例:

// $exists: 是否存在指定字段查詢
db.inventory.find( { price: { $exists: true } } )

// $type: 字段是否是指定類型查詢
db.inventory.find( { "qty": { $type: "double" } } )

評估操作符

評估操作符主要涉及的操作如下錶所示,更多操作符可以參考官方文檔:https://docs.mongodb.com/manual/reference/operator/query-evaluation/

名稱 說明
$expr 為同一個文檔中的字段指定表達式並且符合條件的查詢,比如比較同一文檔當中兩個字段的值
$mod 為字段值取模並且符合條件的查詢

為了更好的使用這兩個主要的操作符,額外創建個文檔:

db.monthlyBudget.insertMany([
    { "_id" : 1, "category" : "food", "budget": 400, "spent": 450 },
    { "_id" : 2, "category" : "drinks", "budget": 100, "spent": 150 },
    { "_id" : 3, "category" : "clothes", "budget": 100, "spent": 50 },
    { "_id" : 4, "category" : "misc", "budget": 500, "spent": 300 },
    { "_id" : 5, "category" : "travel", "budget": 200, "spent": 650 }
]);

使用示例:

// $expr: 允許使用聚合表達式,這裏以$gt為例,更多表達式參考 https://docs.mongodb.com/manual/meta/aggregation-quick-reference/#aggregation-expressions
db.monthlyBudget.find( { $expr: { $gt: [ "$spent" , "$budget" ] } } )

// $mod: 對字段所在值進行取模運算,显示符合條件的查詢,如qty字段值對4取模,並且餘數為0
db.inventory.find( { qty: { $mod: [ 4, 0 ] } } )

更新(Update Operations)

更新(Update Operations)是對已存在的文檔進行修改操作,MongoDB提供以下幾種主要更新文檔方法:

  • db.collection.update():更新或替換集合中符合條件的一個或多個文檔;
  • db.collection.updateOne():只更新集合中符合條件的第一個文檔,即使有多個文檔(版本3.2新增);
  • db.collection.updateMany():更新集合中所有符合條件的文檔(版本3.2新增)。

db.collection.update()

根據update指定的表達式可以修改文檔中符合條件的字段或代替整個文檔。具體的語法格式如下:

db.collection.update(
   <query>,   //查詢表達式
   <update>,  //更新表達式
   {
     upsert: <boolean>,
     multi: <boolean>,
     writeConcern: <document>,
     collation: <document>,
     arrayFilters: [ <filterdocument1>, ... ],
     hint:  <document|string>                   // 版本4.2新增
   }
)

參數說明:

  • query:更新文檔的查詢表達式;如果指定了參數upsert: true並且集合中沒有符合查詢條件的文檔,查詢條件中有關於字段_id指定了.分隔符的,並不會插入新的文檔;

  • update:主要包含三種格式

    • 1.更新文檔:只包含更新操作符表達式;
    • 2.替換文檔:只包含<field1>: <value1>對;
    • 3.聚合管道:版本4.2新增,詳細參考官方文檔。
  • upsert:當query查詢條件沒符合更新的文檔,就新創建文檔(可選),默認值為false

  • multi:是否更新多個符合條件的文檔(可選),默認值為false,只更新符合條件的第一個文檔;

  • writeConcern:參考db.collection.insert()相同參數說明;

  • collation:指定校對規則(可選,版本3.4新增);

  • arrayFilters:文檔數組更新過濾操作符(可選,版本3.6新增);

    詳細參考:https://docs.mongodb.com/manual/reference/method/db.collection.update/#specify-arrayfilters-for-array-update-operations

  • hint:採用文檔或字符串的形式指定適用於查詢表達式的索引,如果索引不存在則報錯(可選,版本4.2新增)。

使用示例:

使用示例將通過使用兩種場景進行,一是沒有使用參數選項upsert,二是使用參數選項upsert

  • 不使用選項upsert
// 測試數據
db.books.remove({});

db.books.insertMany([
  {
    "_id" : 1,
    "item" : "TBD",
    "stock" : 0,
    "info" : { "publisher" : "1111", "pages" : 430 },
    "tags" : [ "technology", "computer" ],
    "ratings" : [ { "by" : "ijk", "rating" : 4 }, { "by" : "lmn", "rating" : 5 } ],
    "reorder" : false
   },
   {
    "_id" : 2,
    "item" : "XYZ123",
    "stock" : 15,
    "info" : { "publisher" : "5555", "pages" : 150 },
    "tags" : [ ],
    "ratings" : [ { "by" : "xyz", "rating" : 5 } ],
    "reorder" : false
   }
]);


/* 使用選項參數 upsert: true
1、如果查詢表達式找到匹配的文檔,則執行更新操作;
2、如果查詢表達式沒有找到匹配的文檔,則執行插入操作;
*/
db.books.update(
   { item: "ZZZ135" },   // 查詢表達式
   {                     // 更新或替換文檔
     item: "ZZZ135",
     stock: 5,
     tags: [ "database" ]
   },
   { upsert: true }
);

// 1.使用更新操作表達式
/* $set操作符
1、查詢表達式指定需要更新的文檔 _id;
2、$inc操作符: stock的字段值+5;
3、$set操作符: 替換item字段值,替換嵌入文檔info的publisher字段值,替換tags字段值,替換數組ratings的第二個元素值
*/
db.books.update(
   { _id: 1 },
   {
     $inc: { stock: 5 },
     $set: {
       item: "ABC123",
       "info.publisher": "2222",
       tags: [ "software" ],
       "ratings.1": { by: "xyz", rating: 3 }
     }
   }
);
更新之後的文檔:
{

  "_id" : 1,
  "item" : "ABC123",
  "stock" : 5,
  "info" : { "publisher" : "2222", "pages" : 430 },
  "tags" : [ "software" ],
  "ratings" : [ { "by" : "ijk", "rating" : 4 }, { "by" : "xyz", "rating" : 3 } ],
  "reorder" : false
}

// 2.為已存在的數組添加元素
// $push操作符: 為指定文檔數組ratings添加一個元素
db.books.update(
   { _id: 2 },
   {
     $push: { ratings: { "by" : "jkl", "rating" : 2 } }
   }
);
更新之後的文檔:
{
  "_id" : 2,
  "item" : "XYZ123",
  "stock" : 15,
  "info" : {
   "publisher" : "5555",
   "pages" : 150
  },
  "tags" : [ ],
  "ratings" : [
   { "by" : "xyz", "rating" : 5 },

   { "by" : "jkl", "rating" : 2 }

  ],
  "reorder" : false
 }

// 3.文檔移除字段
// $unset操作符: 移除文檔的指定字段,為_id:1文檔移除tags字段
db.books.update( { _id: 1 }, { $unset: { tags: 1 } } );
更新后的文檔:
{
  "_id" : 1,
  "item" : "TBD",
  "stock" : 0,
  "info" : {
   "publisher" : "1111",
   "pages" : 430
  },
  "ratings" : [ { "by" : "ijk", "rating" : 4 }, { "by" : "lmn", "rating" : 5 } ],
  "reorder" : false
 }

// 4.替換整個文檔
// 替換_id:2的文檔
db.books.update(
   { _id: 2 },
   {
     item: "XYZ123",
     stock: 10,
     info: { publisher: "2255", pages: 150 },
     tags: [ "baking", "cooking" ]
   }
);
更新后的文檔:
{
   "_id" : 2,
   "item" : "XYZ123",
   "stock" : 10,
   "info" : { "publisher" : "2255", "pages" : 150 },
   "tags" : [ "baking", "cooking" ]
}

// 5.更新多個文檔
db.books.update(
   { stock: { $lte: 10 } },
   { $set: { reorder: true } },
   { multi: true }
);
更新后的全部文檔:
[
  {
    "_id" : 1,
    "item" : "ABC123",
    "stock" : 5,
    "info" : {
     "publisher" : "2222",
     "pages" : 430
    },
    "ratings" : [ { "by" : "ijk", "rating" : 4 }, { "by" : "xyz", "rating" : 3 } ],

    "reorder" : true

   }
   {
     "_id" : 2,
     "item" : "XYZ123",
     "stock" : 10,
     "info" : { "publisher" : "2255", "pages" : 150 },
     "tags" : [ "baking", "cooking" ],

     "reorder" : true

   }
]
  • 使用upserts選項
/* 使用選項參數 upsert: true
1、如果查詢表達式找到匹配的文檔,則執行更新操作;
2、如果查詢表達式沒有找到匹配的文檔,則執行插入操作;
*/

// 1.插入未符合更新條件的文檔
db.books.update(
   { item: "ZZZ135" },   
   {                     
     item: "ZZZ135",
     stock: 5,
     tags: [ "database" ]
   },

   { upsert: true }      

);
因為集合併未滿足條件的文檔,則插入的文檔為:
{
  "_id" : ObjectId("5da78973835b2f1c75347a83"),
  "item" : "ZZZ135",
  "stock" : 5,
  "tags" : [ "database" ]
}

// 2.插入未符合更新條件並且基於更新操作符的文檔
// 如果沒有符合更新查詢條件,並且使用的是更新操作符,則會基於當前的查詢條件和更新操作符字段插入新的文檔
db.books.update(
   { item: "BLP921" },   
   {                     
      $set: { reorder: false },
      $setOnInsert: { stock: 10 }
   },
   { upsert: true }      
);
新插入的文檔為:
{
  "_id" : ObjectId("5da79019835b2f1c75348a0a"),
  "item" : "BLP921",
  "reorder" : false,
  "stock" : 10
}

// 3.插入未符合更新條件並且基於聚合管道的文檔
// 關於聚合管道請參考官方文檔:https://docs.mongodb.com/manual/reference/method/db.collection.update/#update-with-aggregation-pipeline

// 4.插入未符合更新條件並且同時聯合多文檔操作符的文檔
如果不符合查詢條件,則只會插入單個文檔
db.books.update(
  { "info.publisher": "Self-Published" },   
  {                                         
    $set: { reorder: false, tags: [ "literature", "hardcover" ], stock: 25 }
  },
  { upsert: true, multi: true }             
);
新插入的文檔:
{
  "_id" : ObjectId("5db337934f670d584b6ca8e0"),
  "info" : { "publisher" : "Self-Published" },
  "reorder" : false,
  "stock" : 25,
  "tags" : [ "literature", "hardcover" ]
}

db.collection.updateOne()

根據update指定的參數可以修改文檔中符合條件的字段或代替整個文檔,與db.collection.update()不同的是每次只更新單個文檔。

語法格式如下:

db.collection.updateOne(
   <filter>,
   <update>,
   {
     upsert: <boolean>,
     writeConcern: <document>,
     collation: <document>,
     arrayFilters: [ <filterdocument1>, ... ],
     hint:  <document|string>        
   }
)

參數說明:

參考db.collection.update()的參數說明。

使用示例:

// 參考db.collection.update()

db.collection.updateMany()

根據update指定的參數可以修改文檔中符合條件的字段或代替整個文檔,與db.collection.updateOne()不同的是更新所有符合條件的文檔。

語法格式如下:

db.collection.updateMany(
   <filter>,
   <update>,
   {
     upsert: <boolean>,
     writeConcern: <document>,
     collation: <document>,
     arrayFilters: [ <filterdocument1>, ... ],
     hint:  <document|string>        
   }
)

參數說明:

參考db.collection.update()的參數說明。

使用示例:

// 參考db.collection.update()

刪除(Delete Operations)

刪除是指對集合當中已存在的文檔進行清除操作,MongoDB提供以下幾種主要刪除文檔方法:

  • db.collection.deleteOne():只刪除集合中符合條件的一個文檔;
  • db.collection.deleteMany():刪除集合中所有符合條件的文檔;
  • db.collection.remove():刪除集合中符合條件的一個或多個文檔。

db.collection.deleteOne()

根據filter選項條件刪除集合中的單個文檔,具體語法格式如下:

db.collection.deleteOne(
   <filter>,
   {
      writeConcern: <document>,
      collation: <document>
   }
)

參數說明:

  • filter:指定基於查詢表達式的過濾條件,關於查詢表達式可以查看db.collecion.find()中的<query>
  • writeConcern:參考db.collection.insert()相同參數說明;
  • collation:指定校對規則(可選,版本3.4新增);

使用示例:

// 刪除指定條件的單個文檔
db.orders.deleteOne( { "_id" : 1 } );
{ "acknowledged" : true, "deletedCount" : 1 }

db.collection.deleteMany()

根據filter選項條件刪除集合中的單個文檔,具體語法格式如下:

db.collection.deleteMany(
   <filter>,
   {
      writeConcern: <document>,
      collation: <document>
   }
)

參數說明:

參考db.collection.deleteOne()的參數說明。

使用示例:

// 刪除指定條件的多個文檔
db.orders.deleteMany( {"cust_id" : "Cam Elot"} );
{ "acknowledged" : true, "deletedCount" : 2 }

注意: 如果是對固定集合進行刪除文檔操作則會報錯,固定集合的清除操作使用方法db.collection.drop()

總結

  1. 本文簡單梳理了在Mongo Shell下基本的CRUD操作,主要適用於DBA的運維管理,如果是研發同學,根據不同的編程語言使用不同客戶端驅動進行操作,詳細同樣可以參考官方文檔;
  2. 針對CRUD各個方面還有其他一些額外的方法,比如查詢修改文檔方法db.collection.findAndModify(),這裏只是總結每個文檔操作中一些最基礎的方法,對於額外高級的方法這裏不再贅述;
  3. 掌握了這些基本的CRUD操作,就可以對MongoDB文檔進行操作了,但還是需要控制好權限,畢竟數據安全不是小事,做變更之前做好數據的備份,以防萬一。

參考

https://docs.mongodb.com/manual/crud/

https://docs.mongodb.com/manual/reference/operator/query-evaluation/

https://docs.mongodb.com/manual/reference/method/db.collection.update/#specify-arrayfilters-for-array-update-operations

https://docs.mongodb.com/manual/reference/method/db.collection.update/#update-with-aggregation-pipeline

〖本人水平有限,文中如有錯誤還請留言批評指正!〗

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

國外 YouTuber 實測 M1 MacBook Air 貼上導熱矽膠片後,效能有感提升(更接近 MacBook Pro)

MacBook Air 與 MacBook Pro 雖然都是搭載 M1 晶片,但 MacBook Air 的 GPU 部分少一顆,也沒有散熱風扇,因此正常來說重度工作的處理效能,MacBook Air 表現會差一些,不過最近一位國外 YouTuber 找到一個解決方案,只需要花個 30 美金購入導熱矽膠片並貼在 MacBook Air 上,就能立刻提升效能表現,甚至有些測試還超過 MacBook Pro,讓人眼睛為之一亮。

M1 MacBook Air 貼上導熱矽膠片效能有感提升

近日 Max Tech 頻道上傳一部 M1 MacBook Air 加裝導熱矽膠貼片的實測影片,這貼片是從 Amazon 網站上購買的,僅 30 美金(買兩片)。不過黏貼時需要把 MacBook Air 背蓋拆掉,因此有可能會喪失保固,有興趣的人這點要注意。

導熱矽膠片黏貼在左上角的位置,他也有拍攝一部黏貼教學影片,有興趣了解的人文末會附上:

下圖為這次的測試比較內容,從 Geekbench、Cinebench、影片輸出、圖片剪輯、一直到電池續航力比較都有:

Geekbench 5 成績跟 MacBook Pro 幾乎一樣,多核心還多出 21 分:

GPU 因為少一顆,因此即便 M1 MacBook Air 加上導熱矽膠貼片也無法追上 MacBook Pro:

Cinebench R23 跑分就跟 M1 MacBook Pro 差不多,差距僅 40 分不到:

而跟未黏貼的 M1 MacBook Air 相比,黏貼後整整高出 1,000 多分,這相當驚人:

Xcode 測試也相當接近,黏貼後的 M1 MacBook Air 僅慢個 1 秒:

BruceX Benchmark 測試也非常亮眼,比 M1 MacBook Pro 還快,23.91 秒就完成:

5 分鐘 4K H.264 格式影片輸出速度,黏貼後的 M1 MacBook Air 也比 M1 MacBook Pro 快,約 3 分 2 秒就完成:

8 位元 HEVC 穩定性測試就一樣,皆為 10 秒鐘完成:

10 位元 HEVC 輸出也相同時間完成,都是 1 分鐘 12 秒,Max Tech 也提到,這項測試過去 MacBook Air 都比 MacBook Pro 還慢,因此非常讓人驚艷:

4.5K Red Raw 影片輸出則是 M1 MacBook Pro 快一些,2 分 36 秒完成,不過黏貼後的 M1 MacBook Air 也僅慢 11 秒:

8K Red Raw 影片輸出差距就稍微大一點,M1 MacBook Pro 約 20 分 20 秒就完成,黏貼後的 M1 MacBook Air 為 23 分 39 秒:

Lightroom Classic 50x 42MP 輸出測試部分,黏貼後的 M1 MacBook Air 就比較快,2 分 37 秒完成,M1 MacBook Pro 慢了 17 秒:

而最後的電池續航力測試,一開始這兩台都是 100% 充飽電,過程都進行一樣的操作測試,跑到最後一項 M1 MacBook Pro 還有 67% 電量,黏貼後的 M1 MacBook Air 剩下 52% 電量,因此對於注重續航力的人,M1 MacBook Pro 還是比較好。

不得不說,花點小錢貼上幾片導熱矽膠片,就能讓 M1 MacBook Air 效能有感提升,感覺真的挺不錯的,但必須承擔可能喪失保固的風險,已過保的人就蠻值得試試。

完整測試影片:

 

這部則是黏貼教學影片,後半部有跟未黏貼的 M1 MacBook Air 測試比較:

這有點猛!疑似 M1X 晶片的規格、效能跑分現身國外網站,12 核心 CPU + 16 核心 GPU

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

【其他文章推薦】

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

網頁設計最專業,超強功能平台可客製化

※別再煩惱如何寫文案,掌握八大原則!

iOS 14.5 Beta 2 暗示將推出 MagSafe 充電配件,磁吸無線充電行動電源即將來臨?

Apple 針對 iPhone 推出的聰穎電池護殼,一直是有著「原廠控」的果粉們選擇為 iPhone 補給電力會列入考慮的配件選項。但隨著 iPhone 12 系列支持 MagSafe 磁吸無線充電後,至今仍 Apple 就未推出聰穎電池護殼這樣內建電池的保護殼配件。然而,日前在針對開發者釋出的 iOS 14.5 Beta 2 預覽版本,除了新增多達 217 組的全新表情符號,從中似乎也找到些暗示未來即將推出對應 MagSafe 介面的行動電源配件。

▲圖片來源:Steve Moser (Twitter/ @SteveMoser)

iOS 14.5 Beta 2 暗示將推出「MagSafe 充電配件」,磁吸無線充電行動電源即將來臨?

▲圖片來源:MacRumors

據相關消息表示, Apple 將在未來 iOS 更新加入「行動充電模式(Mobile Charge Mode)」的全新電力補給方式,這預期能讓像是 iPhone 12 系列支持 MagSafe 的手機能讓電力維持在 90% 以上,以應付一整天的使用需求。
除了連接行動電源之外,過去若在外想讓 iPhone 隨時保持在充足電量,就得考慮「聰穎電池護殼」這類將行動電源設計與手機背蓋結合的充電保護殼配件。
然而這樣的配件補充電力相當方便,但也伴隨著需要隨時讓手機多了一個笨重、厚重的電池背在手機上,這也是除了價格考量外讓許多消費者卻步的原因。

▲圖片來源:Steve Moser (Twitter/ @SteveMoser)

不過,隨著 iPhone 12 系列在機身背面加入 MagSafe 介面,讓未來能透過支持 MagSafe 的行動電源或相關 MagSafe 無線充電配件,預期能直接透過無線充電方式補充電力。這項改變有別於過去聰穎電池護殼需要長時間都安裝在手機上,而是只在用戶需要為 iPhone 12 系列充電時再吸上即可。
而 MagSafe 無線充電帶來的另一項好處,則是手機只需要裸機或使用支持 MagSafe 的保護殼即可讓充電配件固定在最佳的充電位置,確保無線充電過程順暢。此外,除了 Apple 原廠的 MagSafe 配件,近期也陸續有更多第三方配件廠商陸續推出支持 Apple MFM (Made for MagSafe)認證的配件。
近期隨著傳聞 Apple 將在未來加入行動充電模式後,網路上也有許多人陸續合成 MagSafe 充電配件的概念圖:

▲圖片來源:Antonio Adrian Chavez (Twitter/ @antonio07341176)

此外,在 iOS 14.5 Beta 2 預覽版本中加入 217 組全新表情符號:

▲圖片來源:MacRumors

有趣的是耳機的表情符號也改為 Apple 自家的主動式降噪頭戴式耳機 AirPods Max :
▲圖片來源:MacRumors

消息來源:MacRumors|Steve Moser (Twitter/ @SteveMoser)

延伸閱讀:
傳聞 Apple 將於 3/16 舉行春季新品發表會,或將推出全新 iPad mini 和 AirTags 等新品

iPhone 12 mini DXOMARK 相機評測成績揭曉:總分 122 與 iPhone 12 同分

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

STL常用序列容器

這裏簡要的記述一下STL常用容器的實現原理,要點等內容。

vector

vector是比較常用的stl容器,用法與數組是非類似,其內部實現是連續空間分配,與數組的不同之處在於可彈性增加空間,而array是靜態空間,分配后不能動態擴展。vecotr的實現較為簡單,主要的關鍵點在於當空間不足時,會新分配當前空間2倍的空間,將舊空間數據拷貝到新空間,然後刪除舊空間。

struct _Vector_impl: public _Tp_alloc_type {
    pointer _M_start;   // 元素頭
    pointer _M_finish;  // 元素尾
    pointer _M_end_of_storage;  // 可用空間尾,
          // 省略部分代碼...
};

這個是向尾部添加元素的代碼實現,可以看到如果當前還有剩餘空間的話,直接在尾部添加,如果沒有剩餘空間,則會動態擴展。

void push_back(const value_type& __x) {
    if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage) {
	    _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish, __x);
	    ++this->_M_impl._M_finish;
    } else
	  _M_realloc_insert(end(), __x);
}

template<typename _Tp, typename _Alloc>
void vector<_Tp, _Alloc>::_M_realloc_insert(iterator __position, const _Tp& __x) {
    const size_type __len = _M_check_len(size_type(1), "vector::_M_realloc_insert");      // 2倍當前大小
    const size_type __elems_before = __position - begin();
    pointer __new_start(this->_M_allocate(__len));
    pointer __new_finish(__new_start);
    __try {
	  // The order of the three operations is dictated by the C++11
	  // case, where the moves could alter a new element belonging
	  // to the existing vector.  This is an issue only for callers
	  // taking the element by lvalue ref (see last bullet of C++11
	  // [res.on.arguments]).
	  _Alloc_traits::construct(this->_M_impl, __new_start + __elems_before, __x);
	  __new_finish = pointer();

	  __new_finish = std::__uninitialized_move_if_noexcept_a(this->_M_impl._M_start, __position.base(), __new_start, _M_get_Tp_allocator();

	  ++__new_finish;

	  __new_finish = std::__uninitialized_move_if_noexcept_a(__position.base(), this->_M_impl._M_finish, __new_finish, _M_get_Tp_allocator());
	}__catch(...) {
	  if (!__new_finish)
	    _Alloc_traits::destroy(this->_M_impl,
				   __new_start + __elems_before);
	  else
	    std::_Destroy(__new_start, __new_finish, _M_get_Tp_allocator());
	  _M_deallocate(__new_start, __len);
	  __throw_exception_again;
	}
      std::_Destroy(this->_M_impl._M_start, this->_M_impl._M_finish, _M_get_Tp_allocator());
      _M_deallocate(this->_M_impl._M_start, this->_M_impl._M_end_of_storage - this->_M_impl._M_start);
      this->_M_impl._M_start = __new_start;
      this->_M_impl._M_finish = __new_finish;
      this->_M_impl._M_end_of_storage = __new_start + __len;
    }

// Called by _M_fill_insert, _M_insert_aux etc.
size_type _M_check_len(size_type __n, const char* __s) const {
    if (max_size() - size() < __n)
	  __throw_length_error(__N(__s));

    const size_type __len = size() + std::max(size(), __n);       // 二倍長
    return (__len < size() || __len > max_size()) ? max_size() : __len;
}

pointer _M_allocate(size_t __n) {
    typedef __gnu_cxx::__alloc_traits<_Tp_alloc_type> _Tr;
    return __n != 0 ? _Tr::allocate(_M_impl, __n) : pointer();
}

使用時可使用[],因為其已實現重載[]

      reference
      operator[](size_type __n) _GLIBCXX_NOEXCEPT {
	      __glibcxx_requires_subscript(__n);
	      return *(this->_M_impl._M_start + __n);
      }

使用時要注意迭代器失效問題,這個在很多STL容器中都有這個問題。

list

鏈表list,與vector不同,元素在內存中不連續分配,不支持隨機存取,好處就是插入與刪除時間複雜度為O(1)。在STL中,其實現的是雙向鏈表,其節點的定義可以看到有前驅和後繼指針,實現也較為簡單。

  /// An actual node in the %list.
template<typename _Tp>
struct _List_node : public __detail::_List_node_base {
    _Tp _M_data;
    _Tp* _M_valptr() { return std::__addressof(_M_data); }
    _Tp const* _M_valptr() const { return std::__addressof(_M_data); }
};

struct _List_node_base {
    _List_node_base* _M_next;
    _List_node_base* _M_prev;

    static void swap(_List_node_base& __x, _List_node_base& __y) _GLIBCXX_USE_NOEXCEPT;
    void _M_transfer(_List_node_base* const __first, _List_node_base* const __last) _GLIBCXX_USE_NOEXCEPT;
    void _M_reverse() _GLIBCXX_USE_NOEXCEPT;
    void _M_hook(_List_node_base* const __position) _GLIBCXX_USE_NOEXCEPT;
    void _M_unhook() _GLIBCXX_USE_NOEXCEPT;
};

deque

雙端隊列,具體實現不同於vectorlist,它是一小段一小段連續空間,每段連續空間之間通過指針數組(這個數組中存放的是每個連續空間數組的頭指針)串聯起來,這樣就能訪問到所有元素。之所以採用這種存儲布局,是有原因的,是有其應用場景的,等分析完源碼后,我們就明白其為何要這麼做了。

deque源碼分析

我們摘取部分源碼看一下其實現細節。雙端隊列的迭代器實現代碼如下(相較於vectorlist,對元素的訪問因為其存儲布局不同,在每一段連續分配空間的邊緣要做特殊處理):

#define _GLIBCXX_DEQUE_BUF_SIZE 512       // 默認連續空間大小

  _GLIBCXX_CONSTEXPR inline size_t __deque_buf_size(size_t __size) { 
        return (__size < _GLIBCXX_DEQUE_BUF_SIZE ? size_t(_GLIBCXX_DEQUE_BUF_SIZE / __size) :size_t(1)); 
    }
template<typename _Tp, typename _Ref, typename _Ptr>
    struct _Deque_iterator {
      typedef _Deque_iterator<_Tp, _Tp&, _Tp*>	     iterator;
      typedef _Deque_iterator<_Tp, const _Tp&, const _Tp*> const_iterator;
      typedef _Tp*					 _Elt_pointer;
      typedef _Tp**					_Map_pointer;

      static size_t _S_buffer_size() _GLIBCXX_NOEXCEPT { 
            return __deque_buf_size(sizeof(_Tp)); 
      }

      typedef std::random_access_iterator_tag	iterator_category;
      typedef _Tp				value_type;
      typedef _Ptr				pointer;
      typedef _Ref				reference;
      typedef size_t				size_type;
      typedef ptrdiff_t				difference_type;
      typedef _Deque_iterator			_Self;

      _Elt_pointer _M_cur;          // 當前位置
      _Elt_pointer _M_first;        // 每一小段空間的開始
      _Elt_pointer _M_last;         // 每一小段空間的結束
      _Map_pointer _M_node;         // 指針數組,可通過這裏訪問到所有連續存儲空間片段

      // 構造函數
      _Deque_iterator(_Elt_pointer __x, _Map_pointer __y) _GLIBCXX_NOEXCEPT : _M_cur(__x), _M_first(*__y),
_M_last(*__y + _S_buffer_size()), _M_node(__y) { }

      _Deque_iterator() _GLIBCXX_NOEXCEPT: _M_cur(), _M_first(), _M_last(), _M_node() { }

      _Deque_iterator(const iterator& __x) _GLIBCXX_NOEXCEPT: _M_cur(__x._M_cur), _M_first(__x._M_first),
	_M_last(__x._M_last), _M_node(__x._M_node) { }

      iterator _M_const_cast() const _GLIBCXX_NOEXCEPT {
          return iterator(_M_cur, _M_node);     // 返回當前元素迭代器
      }

      reference operator*() const _GLIBCXX_NOEXCEPT { 
          return *_M_cur; 
      }

      pointer operator->() const _GLIBCXX_NOEXCEPT { 
          return _M_cur; 
      }

      // 重載++運算符,可以看到,當_M_cur指向本段連續空間尾部時,訪問下一個元素的話是下一段連續空間的首地址
      _Self& operator++() _GLIBCXX_NOEXCEPT {   
	    ++_M_cur;     
	    if (_M_cur == _M_last) {  
	        _M_set_node(_M_node + 1);   // 移向下一段連續存儲空間
	        _M_cur = _M_first;          // 下一段連續空間的首元素
	    }
	    return *this;
      }

      _Self operator++(int) _GLIBCXX_NOEXCEPT {
	    _Self __tmp = *this;
	    ++*this;
	    return __tmp;
      }

      _Self& operator--() _GLIBCXX_NOEXCEPT {
	    if (_M_cur == _M_first) {             // 與++類似,如果當前是第一個元素,--時,就應該調到上一個連續存儲空間
	        _M_set_node(_M_node - 1);
	        _M_cur = _M_last;           // 移到上一段空間的最後,
	    }
	    --_M_cur;                       // 因為是[start, last)區間,這裏要--_M_cur;
	    return *this;
      }

      _Self operator--(int) _GLIBCXX_NOEXCEPT {
	    _Self __tmp = *this;
	    --*this;
	    return __tmp;
      }

      _Self& operator+=(difference_type __n) _GLIBCXX_NOEXCEPT {
	    const difference_type __offset = __n + (_M_cur - _M_first);
	    if (__offset >= 0 && __offset < difference_type(_S_buffer_size()))  // 如果當前連續空間滿足
	       _M_cur += __n;
	    else {  // 如果當前段連續空間不夠用了,需要計算跳到連續空間
	        const difference_type __node_offset = __offset > 0 ? __offset / difference_type(_S_buffer_size()) : -difference_type((-__offset - 1) / _S_buffer_size()) - 1;
	        _M_set_node(_M_node + __node_offset);
	        _M_cur = _M_first + (__offset - __node_offset * difference_type(_S_buffer_size()));
	    }
	    return *this;
      }

      _Self operator+(difference_type __n) const _GLIBCXX_NOEXCEPT {
	    _Self __tmp = *this;
	    return __tmp += __n;
      }

      _Self& operator-=(difference_type __n) _GLIBCXX_NOEXCEPT {
          return *this += -__n; }

      _Self operator-(difference_type __n) const _GLIBCXX_NOEXCEPT {
	    _Self __tmp = *this;
	    return __tmp -= __n;
      }

      reference operator[](difference_type __n) const _GLIBCXX_NOEXCEPT { return *(*this + __n); }

      // Prepares to traverse new_node.  Sets everything except _M_cur, which should therefore be set by the caller immediately afterwards, based on _M_first and _M_last.
      void _M_set_node(_Map_pointer __new_node) _GLIBCXX_NOEXCEPT {     // 跳到新的一段連續存儲空間
	    _M_node = __new_node;
	    _M_first = *__new_node;
	    _M_last = _M_first + difference_type(_S_buffer_size());
      }
    };

從上面deque迭代器的實現來看,主要需要注意的地方就是每段連續空間的邊緣。看完迭代器后,我們看一下deque類的實現代碼,這裏刪減掉大部分代碼,保留部分代碼。其中重點看一下deque中最常用的push_frontpop_frontpush_backpop_back的實現。push_back時間複雜度O(1)比較好理解,過程類似於vector,但push_front為什麼也是O(1)呢?如果在頭部插入一個元素,第一個連續空間距離起始start還有剩餘空間的的話,直接插入就好了,如果沒有剩餘空間的話,就創建一段新的連續空間,將首地址放到map中,如果map沒有空間放置這個首地址,就調整map,再插入首地址,詳細過程請看源碼的具體實現:

 template<typename _Tp, typename _Alloc = std::allocator<_Tp> >
    class deque : protected _Deque_base<_Tp, _Alloc> {
      typedef _Deque_base<_Tp, _Alloc>			_Base;
      typedef typename _Base::_Tp_alloc_type		_Tp_alloc_type;
      typedef typename _Base::_Alloc_traits		_Alloc_traits;
      typedef typename _Base::_Map_pointer		_Map_pointer;

    public:
      typedef _Tp					value_type;
      typedef typename _Alloc_traits::pointer		pointer;
      typedef typename _Alloc_traits::const_pointer	const_pointer;
      typedef typename _Alloc_traits::reference		reference;
      typedef typename _Alloc_traits::const_reference	const_reference;
      typedef typename _Base::iterator			iterator;
      typedef typename _Base::const_iterator		const_iterator;
      typedef std::reverse_iterator<const_iterator>	const_reverse_iterator;
      typedef std::reverse_iterator<iterator>		reverse_iterator;
      typedef size_t					size_type;
      typedef ptrdiff_t					difference_type;
      typedef _Alloc					allocator_type;

    protected:
      static size_t _S_buffer_size() _GLIBCXX_NOEXCEPT { return __deque_buf_size(sizeof(_Tp)); }

      // Functions controlling memory layout, and nothing else.
      using _Base::_M_initialize_map;
      using _Base::_M_create_nodes;
      using _Base::_M_destroy_nodes;
      using _Base::_M_allocate_node;
      using _Base::_M_deallocate_node;
      using _Base::_M_allocate_map;
      using _Base::_M_deallocate_map;
      using _Base::_M_get_Tp_allocator;

      /**
       *  A total of four data members accumulated down the hierarchy.
       *  May be accessed via _M_impl.*
       */
      using _Base::_M_impl;

    public:
	  // 省略構造函數與析構函數......

      /*
       *  @brief  Assigns a given value to a %deque.
       *  @param  __n  Number of elements to be assigned.
       *  @param  __val  Value to be assigned.
       *
       *  This function fills a %deque with @a n copies of the given
       *  value.  Note that the assignment completely changes the
       *  %deque and that the resulting %deque's size is the same as
       *  the number of elements assigned.
       */
      void assign(size_type __n, const value_type& __val) { _M_fill_assign(__n, __val); }

	  // 省略其他assign重載函數......	


      /// Get a copy of the memory allocation object.
      allocator_type get_allocator() const _GLIBCXX_NOEXCEPT{ return _Base::get_allocator(); }

      // iterators
      /**
       *  Returns a read/write iterator that points to the first element in the
       *  %deque.  Iteration is done in ordinary element order.
       */
      iterator begin() _GLIBCXX_NOEXCEPT { return this->_M_impl._M_start; }
      const_iterator begin() const _GLIBCXX_NOEXCEPT { return this->_M_impl._M_start; }

      /**
       *  Returns a read/write iterator that points one past the last
       *  element in the %deque.  Iteration is done in ordinary
       *  element order.
       */
      iterator end() _GLIBCXX_NOEXCEPT{ return this->_M_impl._M_finish; }
      const_iterator end() const _GLIBCXX_NOEXCEPT { return this->_M_impl._M_finish; }


	// 省略其他迭代器相關代碼......

      // [23.2.1.2] capacity
      /**  Returns the number of elements in the %deque.  */
      size_type size() const _GLIBCXX_NOEXCEPT { return this->_M_impl._M_finish - this->_M_impl._M_start; }

      /**  Returns the size() of the largest possible %deque.  */
      size_type max_size() const _GLIBCXX_NOEXCEPT { return _Alloc_traits::max_size(_M_get_Tp_allocator()); }

      /**
       *  @brief  Resizes the %deque to the specified number of elements.
       *  @param  __new_size  Number of elements the %deque should contain.
       *
       *  This function will %resize the %deque to the specified
       *  number of elements.  If the number is smaller than the
       *  %deque's current size the %deque is truncated, otherwise
       *  default constructed elements are appended.
       */
      void resize(size_type __new_size) {
		  const size_type __len = size();
		  if (__new_size > __len)
	  		  _M_default_append(__new_size - __len);
		  else if (__new_size < __len)
	  		  _M_erase_at_end(this->_M_impl._M_start + difference_type(__new_size));
      }

#if __cplusplus >= 201103L
      /**  A non-binding request to reduce memory use.  */
      void shrink_to_fit() noexcept { _M_shrink_to_fit(); }
#endif

      /**
       *  Returns true if the %deque is empty.  (Thus begin() would
       *  equal end().)
       */
      bool empty() const _GLIBCXX_NOEXCEPT { return this->_M_impl._M_finish == this->_M_impl._M_start; }

      // element access
      /**
       *  @brief Subscript access to the data contained in the %deque.
       *  @param __n The index of the element for which data should be
       *  accessed.
       *  @return  Read/write reference to data.
       *
       *  This operator allows for easy, array-style, data access.
       *  Note that data access with this operator is unchecked and
       *  out_of_range lookups are not defined. (For checked lookups
       *  see at().)
       */
      reference operator[](size_type __n) _GLIBCXX_NOEXCEPT {
		  __glibcxx_requires_subscript(__n);
		  return this->_M_impl._M_start[difference_type(__n)];
      }

    protected:
      /// Safety check used only from at().
      void _M_range_check(size_type __n) const {
		  if (__n >= this->size())
	  		  __throw_out_of_range_fmt(__N("deque::_M_range_check: __n "
				       "(which is %zu)>= this->size() "
				       "(which is %zu)"), __n, this->size());
      }

    public:
      /**
       *  @brief  Provides access to the data contained in the %deque.
       *  @param __n The index of the element for which data should be
       *  accessed.
       *  @return  Read/write reference to data.
       *  @throw  std::out_of_range  If @a __n is an invalid index.
       *
       *  This function provides for safer data access.  The parameter
       *  is first checked that it is in the range of the deque.  The
       *  function throws out_of_range if the check fails.
       */
      reference at(size_type __n) {
		  _M_range_check(__n);
		  return (*this)[__n];
      }

      /**
       *  @brief  Provides access to the data contained in the %deque.
       *  @param __n The index of the element for which data should be
       *  accessed.
       *  @return  Read-only (constant) reference to data.
       *  @throw  std::out_of_range  If @a __n is an invalid index.
       *
       *  This function provides for safer data access.  The parameter is first
       *  checked that it is in the range of the deque.  The function throws
       *  out_of_range if the check fails.
       */
      const_reference at(size_type __n) const {
		  _M_range_check(__n);
		  return (*this)[__n];
      }

      /**
       *  Returns a read/write reference to the data at the first
       *  element of the %deque.
       */
      reference front() _GLIBCXX_NOEXCEPT {
		  __glibcxx_requires_nonempty();
		  return *begin();
      }

      /**
       *  Returns a read/write reference to the data at the last element of the
       *  %deque.
       */
      reference back() _GLIBCXX_NOEXCEPT {
		  __glibcxx_requires_nonempty();
		  iterator __tmp = end();
		  --__tmp;
		  return *__tmp;
      }


      /**
       *  @brief  Add data to the front of the %deque.
       *  @param  __x  Data to be added.
       *
       *  This is a typical stack operation.  The function creates an
       *  element at the front of the %deque and assigns the given
       *  data to it.  Due to the nature of a %deque this operation
       *  can be done in constant time.
       */
      void push_front(const value_type& __x) {		// 如果第一段連續空間頭部還有剩餘空間的話,直接插入元素
		  if (this->_M_impl._M_start._M_cur != this->_M_impl._M_start._M_first) {
	      	_Alloc_traits::construct(this->_M_impl, this->_M_impl._M_start._M_cur - 1, __x);
	      	--this->_M_impl._M_start._M_cur;
	  	  } else		// 如果沒有,在前部重新分配空間
	    	  _M_push_front_aux(__x);
      }

      /**
       *  @brief  Add data to the end of the %deque.
       *  @param  __x  Data to be added.
       *
       *  This is a typical stack operation.  The function creates an
       *  element at the end of the %deque and assigns the given data
       *  to it.  Due to the nature of a %deque this operation can be
       *  done in constant time.
       */
      void push_back(const value_type& __x) {
		  if (this->_M_impl._M_finish._M_cur != this->_M_impl._M_finish._M_last - 1) {
	    	  _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish._M_cur, __x);
	          ++this->_M_impl._M_finish._M_cur;
	  	  } else 
			_M_push_back_aux(__x);
      }

      /**
       *  @brief  Removes first element.
       *
       *  This is a typical stack operation.  It shrinks the %deque by one.
       *
       *  Note that no data is returned, and if the first element's data is
       *  needed, it should be retrieved before pop_front() is called.
       */
      void pop_front() _GLIBCXX_NOEXCEPT {
		  __glibcxx_requires_nonempty();
		  if (this->_M_impl._M_start._M_cur != this->_M_impl._M_start._M_last - 1) {
	    	  _Alloc_traits::destroy(this->_M_impl,	this->_M_impl._M_start._M_cur);
	    	  ++this->_M_impl._M_start._M_cur;
	  	  } else
	  	  _M_pop_front_aux();
      }

      /**
       *  @brief  Removes last element.
       *
       *  This is a typical stack operation.  It shrinks the %deque by one.
       *
       *  Note that no data is returned, and if the last element's data is
       *  needed, it should be retrieved before pop_back() is called.
       */
      void pop_back() _GLIBCXX_NOEXCEPT {
		  __glibcxx_requires_nonempty();
		  if (this->_M_impl._M_finish._M_cur != this->_M_impl._M_finish._M_first) {
	          --this->_M_impl._M_finish._M_cur;
	    	  _Alloc_traits::destroy(this->_M_impl, this->_M_impl._M_finish._M_cur);
	  	  } else
	  	  _M_pop_back_aux();
      }

      /**
       *  @brief  Inserts given value into %deque before specified iterator.
       *  @param  __position  An iterator into the %deque.
       *  @param  __x  Data to be inserted.
       *  @return  An iterator that points to the inserted data.
       *
       *  This function will insert a copy of the given value before the
       *  specified location.
       */
      iterator insert(iterator __position, const value_type& __x);

      /**
       *  Erases all the elements.  Note that this function only erases the
       *  elements, and that if the elements themselves are pointers, the
       *  pointed-to memory is not touched in any way.  Managing the pointer is
       *  the user's responsibility.
       */
      void clear() _GLIBCXX_NOEXCEPT { _M_erase_at_end(begin()); }

    protected:
      // Internal constructor functions follow.
	  // 省略部分代碼......
	 	
      void _M_push_back_aux(const value_type&);
      void _M_push_front_aux(const value_type&);
      void _M_pop_back_aux();
      void _M_pop_front_aux();

	  // 省略部分代碼......
    };

deque的實現比vectorlist要複雜的多,主要是因為其空間布局不太一樣。下面的代碼主要是對雙端隊列隊首與隊尾的操作(push_frontpush_backpop_frontpop_back)中涉及到空間變動的部分代碼實現:

// Called only if _M_impl._M_finish._M_cur == _M_impl._M_finish._M_last - 1.
template<typename _Tp, typename _Alloc>
void deque<_Tp, _Alloc>::_M_push_back_aux(const value_type& __t) {
	_M_reserve_map_at_back();
	*(this->_M_impl._M_finish._M_node + 1) = this->_M_allocate_node();      // map新指針指向新分配的連續空間
	__try {
	    this->_M_impl.construct(this->_M_impl._M_finish._M_cur, __t);
	    this->_M_impl._M_finish._M_set_node(this->_M_impl._M_finish._M_node + 1);
	    this->_M_impl._M_finish._M_cur = this->_M_impl._M_finish._M_first;
	} __catch(...) {
	    _M_deallocate_node(*(this->_M_impl._M_finish._M_node + 1));
	    __throw_exception_again;
	}
}

// Called only if _M_impl._M_start._M_cur == _M_impl._M_start._M_first.
template<typename _Tp, typename _Alloc>
void deque<_Tp, _Alloc>::_M_push_front_aux(const value_type& __t) {
	_M_reserve_map_at_front();
	*(this->_M_impl._M_start._M_node - 1) = this->_M_allocate_node();       // map指定位置指向新分配的連續空間
	__try {
	    this->_M_impl._M_start._M_set_node(this->_M_impl._M_start._M_node - 1);
	    this->_M_impl._M_start._M_cur = this->_M_impl._M_start._M_last - 1;
	    this->_M_impl.construct(this->_M_impl._M_start._M_cur, __t);
	} __catch(...) {
	    ++this->_M_impl._M_start;
	    _M_deallocate_node(*(this->_M_impl._M_start._M_node - 1));
	    __throw_exception_again;
	}
}

// Called only if _M_impl._M_finish._M_cur == _M_impl._M_finish._M_first.
template <typename _Tp, typename _Alloc>
void deque<_Tp, _Alloc>::_M_pop_back_aux() {
    _M_deallocate_node(this->_M_impl._M_finish._M_first);
    this->_M_impl._M_finish._M_set_node(this->_M_impl._M_finish._M_node - 1);
    this->_M_impl._M_finish._M_cur = this->_M_impl._M_finish._M_last - 1;
    _Alloc_traits::destroy(_M_get_Tp_allocator(), this->_M_impl._M_finish._M_cur);
}

  // Called only if _M_impl._M_start._M_cur == _M_impl._M_start._M_last - 1.
  // Note that if the deque has at least one element (a precondition for this
  // member function), and if
  //   _M_impl._M_start._M_cur == _M_impl._M_start._M_last,
  // then the deque must have at least two nodes.
  template <typename _Tp, typename _Alloc>
void deque<_Tp, _Alloc>::_M_pop_front_aux() {
    _Alloc_traits::destroy(_M_get_Tp_allocator(), this->_M_impl._M_start._M_cur);
    _M_deallocate_node(this->_M_impl._M_start._M_first);
    this->_M_impl._M_start._M_set_node(this->_M_impl._M_start._M_node + 1);
    this->_M_impl._M_start._M_cur = this->_M_impl._M_start._M_first;
}

下面的原代碼是調整map的,如果map沒有適當空間插入新的連續空間首地址,就重新分配map(這種情況比如,map的後面全部插滿了,但前面還大量空着,就需要將目前的map中的元素進行移動,使map的元素分佈在中間位置,首尾兩端是空閑的,以便於後面插入新元素; 如果是map的空間不足了,則需要新分配map空間,新空間大小要大於新指針元素數量+2)。

void _M_reserve_map_at_back(size_type __nodes_to_add = 1) {
    if (__nodes_to_add + 1 > this->_M_impl._M_map_size - (this->_M_impl._M_finish._M_node - this->_M_impl._M_map))
	    _M_reallocate_map(__nodes_to_add, false);
}

void _M_reserve_map_at_front(size_type __nodes_to_add = 1) {
    if (__nodes_to_add > size_type(this->_M_impl._M_start._M_node - this->_M_impl._M_map))
	    _M_reallocate_map(__nodes_to_add, true);
}

template <typename _Tp, typename _Alloc>
void deque<_Tp, _Alloc>::_M_reallocate_map(size_type __nodes_to_add, bool __add_at_front) {
    const size_type __old_num_nodes = this->_M_impl._M_finish._M_node - this->_M_impl._M_start._M_node + 1;
    const size_type __new_num_nodes = __old_num_nodes + __nodes_to_add;

    _Map_pointer __new_nstart;
    if (this->_M_impl._M_map_size > 2 * __new_num_nodes) {
	    __new_nstart = this->_M_impl._M_map + (this->_M_impl._M_map_size - __new_num_nodes) / 2 + (__add_at_front ? __nodes_to_add : 0);	// 這裏新map的開始往後移動了一段位置,是為了將來在前部插入的時候有剩餘空間,後部空餘一段位置也是。
	    if (__new_nstart < this->_M_impl._M_start._M_node)
	    	std::copy(this->_M_impl._M_start._M_node, this->_M_impl._M_finish._M_node + 1, __new_nstart);
	    else
	    	std::copy_backward(this->_M_impl._M_start._M_node, this->_M_impl._M_finish._M_node + 1, __new_nstart + __old_num_nodes);
	} else {
	    size_type __new_map_size = this->_M_impl._M_map_size + std::max(this->_M_impl._M_map_size, __nodes_to_add) + 2;		// 要至少空餘2個空閑位置
	    _Map_pointer __new_map = this->_M_allocate_map(__new_map_size);
	    __new_nstart = __new_map + (__new_map_size - __new_num_nodes) / 2 + (__add_at_front ? __nodes_to_add : 0);
	    std::copy(this->_M_impl._M_start._M_node, this->_M_impl._M_finish._M_node + 1, __new_nstart);
	    _M_deallocate_map(this->_M_impl._M_map, this->_M_impl._M_map_size);

	    this->_M_impl._M_map = __new_map;
	    this->_M_impl._M_map_size = __new_map_size;
	}

    this->_M_impl._M_start._M_set_node(__new_nstart);
    this->_M_impl._M_finish._M_set_node(__new_nstart + __old_num_nodes - 1);
}

更詳細的還是自己看STL的源碼吧,順便吐槽一下STL的源碼,代碼太臃腫了,看起來太累了,如果按照其實現原理,自己實現一個mini版STL,應該會簡潔許多許多。

到這裏,deque中比較核心的源碼已經基本分析完了,也基本展現了deque中幾個關鍵成員函數是如何實現的,其迭代器的實現,其map的實現與調整。

deque與vector、list的對比

vector能夠實現隨機訪問,動態擴展,但在頭部插入O(n),開銷較大,且動態擴展時需要複製所有的元素,同樣效率較低。list插入、刪除頭尾部元素效率很高O(n),但是不能隨機訪問,查找效率O(n),每個節點需要存儲前後節點指針,有較大的額外存儲開銷。而deque等於是在兩種容器的優缺點進行了一定的平衡,在收尾插入、刪除元素,效率很高O(1),在中間插入O(n)都差不多,但其能實現隨機訪問,且動態擴展時不需要複製全體元素,只需要新分配足夠的連續存儲空間,最多重新複製一下map到新map,而map是各個連續存儲空間首地址指針數組,容量相比全體元素小非常多,動態擴展時代價很小。所以,deque相比vector在更一般的情況下有更高的性能,相比list有更小的額外存儲空間(但deque擁有較大的最小內存開銷,原因是它需要map和一段連續存儲空間開銷,即元素數目非常小時開銷比list大,但當元素數目較多時,空間開銷比list少)。

stack

棧也是經常用的數據結構,其實現較為簡單,內部實現依賴deque,當然也可以用vectorlist實現。

// Stack implementation -*- C++ -*-
template<typename _Tp, typename _Sequence = deque<_Tp> >
class stack {
// concept requirements
typedef typename _Sequence::value_type _Sequence_value_type;

public:
    typedef typename _Sequence::value_type		value_type;
    typedef typename _Sequence::reference		reference;
    typedef typename _Sequence::const_reference	const_reference;
    typedef typename _Sequence::size_type		size_type;
    typedef	       _Sequence			container_type;

protected:
    _Sequence c;

public:
	stack(): c() { }
	// 省略構造函數與析構函數......
      /**
       *  Returns true if the %stack is empty.
       */
    bool empty() const { return c.empty(); }

    /**  Returns the number of elements in the %stack.  */
    size_type size() const { return c.size(); }

      /**
       *  Returns a read/write reference to the data at the first
       *  element of the %stack.
       */
    reference top() {
		__glibcxx_requires_nonempty();
		return c.back();
    }
      /**
       *  @brief  Add data to the top of the %stack.
       *  @param  __x  Data to be added.
       *
       *  This is a typical %stack operation.  The function creates an
       *  element at the top of the %stack and assigns the given data
       *  to it.  The time complexity of the operation depends on the
       *  underlying sequence.
       */
    void push(const value_type& __x) { c.push_back(__x); }

      /**
       *  @brief  Removes first element.
       *
       *  This is a typical %stack operation.  It shrinks the %stack
       *  by one.  The time complexity of the operation depends on the
       *  underlying sequence.
       *
       *  Note that no data is returned, and if the first element's
       *  data is needed, it should be retrieved before pop() is
       *  called.
       */
    void pop() {
		__glibcxx_requires_nonempty();
		c.pop_back();
    }

	// 省略其他非關鍵代碼......
}; 

queue

隊列有普通的先進先出的隊列,還有優先隊列,優先級隊列不僅僅要按先後順序,更強調優先級高的先出隊列。

普通隊列的實現

普通隊列的實現與棧實現差不多,也是基於deque實現的。

template<typename _Tp, typename _Sequence = deque<_Tp> >
class queue {
// concept requirements
typedef typename _Sequence::value_type _Sequence_value_type;

public:
    typedef typename	_Sequence::value_type		value_type;
    typedef typename	_Sequence::reference		reference;
    typedef typename	_Sequence::const_reference	const_reference;
    typedef typename	_Sequence::size_type		size_type;
    typedef		_Sequence			container_type;

protected:
      /*  Maintainers wondering why this isn't uglified as per style
       *  guidelines should note that this name is specified in the standard,
       *  C++98 [23.2.3.1].
       *  (Why? Presumably for the same reason that it's protected instead
       *  of private: to allow derivation.  But none of the other
       *  containers allow for derivation.  Odd.)
       */
       ///  @c c is the underlying container.
    _Sequence c;

public:
	queue(): c() { }
	// 省略構造函數與析構函數......

    bool empty() const { return c.empty(); }
    size_type size() const { return c.size(); }

    reference front() {
		__glibcxx_requires_nonempty();
		return c.front();
    }

    reference back() {
		__glibcxx_requires_nonempty();
		return c.back();
    }

    // Add data to the end of the %queue.
    void push(const value_type& __x) { c.push_back(__x); }
      
    // Removes first element.
    void pop() {
		__glibcxx_requires_nonempty();
		c.pop_front();
    }
};

優先隊列priority_queue實現

優先隊列的實現原理是基於堆實現的,堆底層是數組,所以,這裏priority_queue底層的序列容器是vector,選則vector而不是其他容器,是因為優先隊列基於堆,而堆的各種操作中,插入、刪除、都是從尾部插入、刪除操作最後實際上物理刪除的是尾部元素,而且其擴容是2倍擴容,符合二叉樹下一層節點數目是上一次所有數目+1,二倍擴容恰好合適,當然也可以用其他容器(例如deque,但不是最優的)。至於堆實現優先隊列的原理,這裏不再敘述。源碼實現如下:

template<typename _Tp, typename _Sequence = vector<_Tp>, typename _Compare  = less<typename _Sequence::value_type> >
class priority_queue {
#ifdef _GLIBCXX_CONCEPT_CHECKS
// concept requirements
typedef typename _Sequence::value_type _Sequence_value_type;
# if __cplusplus < 201103L
    __glibcxx_class_requires(_Tp, _SGIAssignableConcept)
# endif
    __glibcxx_class_requires(_Sequence, _SequenceConcept)
    __glibcxx_class_requires(_Sequence, _RandomAccessContainerConcept)
    __glibcxx_class_requires2(_Tp, _Sequence_value_type, _SameTypeConcept)
    __glibcxx_class_requires4(_Compare, bool, _Tp, _Tp, _BinaryFunctionConcept)
#endif

#if __cplusplus >= 201103L
template<typename _Alloc>
using _Uses = typename
enable_if<uses_allocator<_Sequence, _Alloc>::value>::type;
#endif

public:
    typedef typename	_Sequence::value_type		value_type;
    typedef typename	_Sequence::reference		 reference;
    typedef typename	_Sequence::const_reference	   const_reference;
    typedef typename	_Sequence::size_type		 size_type;
    typedef		_Sequence			    container_type;
    typedef	       _Compare				    value_compare;

protected:
     _Sequence  c;
    _Compare   comp;	// 優先隊列基於堆,而堆經常需要比較操作

public:
    //    *  @brief  Default constructor creates no elements.
    explicit priority_queue(const _Compare& __x = _Compare(), const _Sequence& __s = _Sequence()): c(__s), comp(__x) { 		
        std::make_heap(c.begin(), c.end(), comp); 	// 構造堆
	}

	// 省略其他構造函數......

      /**
       *  Returns true if the %queue is empty.
       */
    bool empty() const { 
		return c.empty(); 
	}

      /**  Returns the number of elements in the %queue.  */
    size_type size() const { return c.size(); }

      /**
       *  Returns a read-only (constant) reference to the data at the first
       *  element of the %queue.
       */
    const_reference top() const {
		__glibcxx_requires_nonempty();
		return c.front();
    }

      /**
       *  @brief  Add data to the %queue.
       *  @param  __x  Data to be added.
       *
       *  This is a typical %queue operation.
       *  The time complexity of the operation depends on the underlying
       *  sequence.
       */
    void push(const value_type& __x) {		// 優先隊列中插入元素,先放到容器尾部,再進行“上移”操作使之滿足堆性質。
		c.push_back(__x);
		std::push_heap(c.begin(), c.end(), comp);
    }

      /**
       *  @brief  Removes first element.
       *
       *  This is a typical %queue operation.  It shrinks the %queue
       *  by one.  The time complexity of the operation depends on the
       *  underlying sequence.
       *
       *  Note that no data is returned, and if the first element's
       *  data is needed, it should be retrieved before pop() is
       *  called.
       */
    void pop() {		//從優先隊列中彈出首元素
		__glibcxx_requires_nonempty();
		std::pop_heap(c.begin(), c.end(), comp);
		c.pop_back();
    }
};

可以看到只要理解了堆的實現原理,優先隊列的實現原理就非常容易理解,堆的相關STL源碼分析不在這裏繼續分析。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

網頁設計最專業,超強功能平台可客製化

※別再煩惱如何寫文案,掌握八大原則!

不知道怎麼提高代碼質量?來看看這幾種設計模式吧!

提高代碼質量的目的

程序猿的本職工作就是寫代碼,寫出高質量的代碼應該是我們的追求和對自己的要求,因為:

  1. 高質量的代碼往往意味着更少的BUG,更好的模塊化,是我們擴展性,復用性的基礎
  2. 高質量的代碼也意味着更好的書寫,更好的命名,有利於我們的維護

什麼代碼算好的質量

怎樣來定義代碼質量的”好”,業界有很多標準,本文認為好的代碼應該有以下特點:

  1. 代碼整潔,比如縮進之類的,現在有很多工具可以自動解決這個問題,比如eslint。
  2. 結構規整,沒有漫長的結構,函數拆分合理,不會來一個幾千行的函數,也不會有幾十個if...else。這要求寫代碼的人有一些優化的經驗,本文會介紹幾種模式來優化這些情況。
  3. 閱讀起來好理解,不會出現一堆a,b,c這種命名,而是應該盡量語義化,變量名和函數名都盡量有意義,最好是代碼即註釋,讓別人看你的代碼就知道你在幹嘛。

本文介紹的設計模式主要有策略/狀態模式外觀模式迭代器模式備忘錄模式

策略/狀態模式

策略模式基本結構

假如我們需要做一個計算器,需要支持加減乘除,為了判斷用戶具體需要進行哪個操作,我們需要4個if...else來進行判斷,如果支持更多操作,那if...else會更長,不利於閱讀,看着也不優雅。所以我們可以用策略模式優化如下:

function calculator(type, a, b) {
  const strategy = {
    add: function(a, b) {
      return a + b;
    },
    minus: function(a, b) {
      return a - b;
    },
    division: function(a, b) {
      return a / b;
    },
    times: function(a, b) {
      return a * b;
    }
  }
  
  return strategy[type](a, b);
}

// 使用時
calculator('add', 1, 1);

上述代碼我們用一個對象取代了多個if...else,我們需要的操作都對應這個對象裏面的一個屬性,這個屬性名字對應我們傳入的type,我們直接用這個屬性名字就可以獲取對應的操作。

狀態模式基本結構

狀態模式和策略模式很像,也是有一個對象存儲一些策略,但是還有一個變量來存儲當前的狀態,我們根據當前狀態來獲取具體的操作:

function stateFactor(state) {
  const stateObj = {
    status: '',
    state: {
      state1: function(){},
      state2: function(){},
    },
    run: function() {
      return this.state[this.status];
    }
  }
  
  stateObj.status = state;
  return stateObj;
}

// 使用時
stateFactor('state1').run();

if...else其實是根據不同的條件來改變代碼的行為,而策略模式和狀態模式都可以根據傳入的策略或者狀態的不同來改變行為,所有我們可以用這兩種模式來替代if...else

實例:訪問權限

這個例子的需求是我們的頁面需要根據不同的角色來渲染不同的內容,如果我們用if...else寫就是這樣:

// 有三個模塊需要显示,不同角色看到的模塊應該不同
function showPart1() {}
function showPart2() {}
function showPart3() {}

// 獲取當前用戶的角色,然後決定显示哪些部分
axios.get('xxx').then((role) => {
  if(role === 'boss'){
    showPart1();
    showPart2();
    showPart3();
  } else if(role === 'manager') {
    showPart1();
    showPart2();
  } else if(role === 'staff') {
    showPart3();
  }
});

上述代碼中我們通過API請求獲得了當前用戶的角色,然後一堆if...else去判斷應該显示哪些模塊,如果角色很多,這裏的if...else就可能很長,我們可以嘗試用狀態模式優化下:

// 先把各種角色都包裝到一個ShowController類裏面
function ShowController() {
  this.role = '';
  this.roleMap = {
    boss: function() {
      showPart1();
      showPart2();
      showPart3();
    },
    manager: function() {
      showPart1();
    	showPart2();
    },
    staff: function() {
      showPart3();
    }
  }
}

// ShowController上添加一個實例方法show,用來根據角色展示不同的內容
ShowController.prototype.show = function() {
  axios.get('xxx').then((role) => {
    this.role = role;
    this.roleMap[this.role]();
  });
}

// 使用時
new ShowController().show();

上述代碼我們通過一個狀態模式改寫了訪問權限模塊,去掉了if...else,而且不同角色的展示都封裝到了roleMap裏面,後面要增加或者減少都會方便很多。

實例:複合運動

這個例子的需求是我們現在有一個小球,我們需要控制他移動,他移動的方向可以是上下左右,還可以是左上,右下之類的複合運動。如果我們也用if...else來寫,這頭都會寫大:

// 先來四個方向的基本運動
function moveUp() {}
function moveDown() {}
function moveLeft() {}
function moveRight() {}

// 具體移動的方法,可以接收一個或兩個參數,一個就是基本操作,兩個參數就是左上,右下這類操作
function move(...args) {
  if(args.length === 1) {
    if(args[0] === 'up') {
      moveUp();
    } else if(args[0] === 'down') {
      moveDown();        
    } else if(args[0] === 'left') {
      moveLeft();        
    } else if(args[0] === 'right') {
      moveRight();        
    }
  } else {
    if(args[0] === 'left' && args[1] === 'up') {
      moveLeft();
      moveUp();
    } else if(args[0] === 'right' && args[1] === 'down') {
      moveRight();
      moveDown();
    }
    // 後面還有很多if...
  }
}

可以看到這裏if...else看得我們頭都大了,還是用策略模式來優化下吧:

// 建一個移動控制類
function MoveController() {
  this.status = [];
  this.moveHanders = {
    // 寫上每個指令對應的方法
    up: moveUp,
    dowm: moveDown,
    left: moveLeft,
    right: moveRight
  }
}

// MoveController添加一個實例方法來觸發運動
MoveController.prototype.run = function(...args) {
  this.status = args;
  this.status.forEach((move) => {
    this.moveHanders[move]();
  });
}

// 使用時
new MoveController().run('left', 'up')

上述代碼我們也是將所有的策略都封裝到了moveHanders裏面,然後通過實例方法run傳入的方法來執行具體的策略。

外觀模式

基本結構

當我們設計一個模塊時,裏面的方法可以會設計得比較細,但是暴露給外面使用的時候,不一定非得直接暴露這些細小的接口,外部使用者需要的可能是組合部分接口來實現某個功能,我們暴露的時候其實就可以將這個組織好。這就像餐廳裏面的菜單,有很多菜,用戶可以一個一個菜去點,也可以直接點一個套餐,外觀模式提供的就類似於這樣一個組織好的套餐:

function model1() {}

function model2() {}

// 可以提供一個更高階的接口,組合好了model1和model2給外部使用
function use() {
  model2(model1());
}

實例:常見的接口封裝

外觀模式說起來其實非常常見,很多模塊內部都很複雜,但是對外的接口可能都是一兩個,我們無需知道複雜的內部細節,只需要調用統一的高級接口就行,比如下面的選項卡模塊:

// 一個選項卡類,他內部可能有多個子模塊
function Tab() {}

Tab.prototype.renderHTML = function() {}    // 渲染頁面的子模塊
Tab.prototype.bindEvent = function() {}    // 綁定事件的子模塊
Tab.prototype.loadCss = function() {}    // 加載樣式的子模塊

// 對外不需要暴露上面那些具體的子模塊,只需要一個高級接口就行
Tab.prototype.init = function(config) {
  this.loadCss();
  this.renderHTML();
  this.bindEvent();
}

上述代碼這種封裝模式非常常見,其實也是用到了外觀模式,他當然也可以暴露具體的renderHTMLbindEventloadCss這些子模塊,但是外部使用者可能並不關心這些細節,只需要給一個統一的高級接口就行,就相當於改變了外觀暴露出來,所以叫外觀模式

實例:方法的封裝

這個例子也很常見,就是把一些類似的功能封裝成一個方法,而不是每個地方去寫一遍。在以前還是IE主導天下的時候,我們需要做很多兼容的工作,僅僅是一個綁定事件就有addEventListenerattachEvent,onclick等,為了避免每次都進行這些檢測,我們可以將他們封裝成一個方法:

function addEvent(dom, type, fn) {
  if(dom.addEventListener) {
    return dom.addEventListener(type, fn, false);
  } else if(dom.attachEvent) {
    return dom.attachEvent("on" + type, fn);
  } else {
    dom["on" + type] = fn;
  }
}

然後將addEvent暴露出去給外面使用,其實我們在實際編碼時經常這樣封裝方法,只是我們自己可能沒意識到這個是外觀模式。

迭代器模式

基本結構

迭代器模式模式在JS裏面很常見了,數組自帶的forEach就是迭代器模式的一個應用,我們也可以實現一個類似的功能:

function Iterator(items) {
  this.items = items;
}

Iterator.prototype.dealEach = function(fn) {
  for(let i = 0; i < this.items.length; i++) {
    fn(this.items[i], i);
  }
}

上述代碼我們新建了一個迭代器類,構造函數接收一個數組,實例方法dealEach可以接收一個回調,對實例上的items每一項都執行這個回調。

實例:數據迭代器

其實JS數組很多原生方法都用了迭代器模式,比如findfind接收一個測試函數,返回符合這個測試函數的第一個數據。這個例子要做的是擴展這個功能,返回所有符合這個測試函數的數據項,而且也可以接收兩個參數,第一個參數是屬性名,第二個參數是值,同樣返回所有該屬性與值匹配的項:

// 外層用一個工廠模式封裝下,調用時不用寫new
function iteratorFactory(data) {
  function Iterator(data) {
    this.data = data;
  }
  
  Iterator.prototype.findAll = function(handler, value) {
    const result = [];
    let handlerFn;
    // 處理參數,如果第一個參數是函數,直接拿來用
    // 如果不是函數,就是屬性名,給一個對比的默認函數
    if(typeof handler === 'function') {
      handlerFn = handler;
    } else {
      handlerFn = function(item) {
        if(item[handler] === value) {
          return true;
        }
        
        return false;
      }
    }
    
    // 循環數據裏面的每一項,將符合結果的塞入結果數組
    for(let i = 0; i < this.data.length; i++) {
      const item = this.data[i];
      const res = handlerFn(item);
      if(res) {
        result.push(item)
      }
    }
    
    return result;
  }
  
  return new Iterator(data);
}

// 寫個數據測試下
const data = [{num: 1}, {num: 2}, {num: 3}];
iteratorFactory(data).findAll('num', 2);    // [{num: 2}]
iteratorFactory(data).findAll(item => item.num >= 2); // [{num: 2}, {num: 3}]

上述代碼封裝了一個類似數組find的迭代器,擴展了他的功能,這種迭代器非常適合用來處理API返回的大量結構相似的數據。

備忘錄模式

基本結構

備忘錄模式類似於JS經常使用的緩存函數,內部記錄一個狀態,也就是緩存,當我們再次訪問的時候可以直接拿緩存數據:

function memo() {
  const cache = {};
  
  return function(arg) {
    if(cache[arg]) {
      return cache[arg];
    } else {
      // 沒緩存的時候先執行方法,得到結果res
      // 然後將res寫入緩存
      cache[arg] = res;
      return res;
    }
}

實例:文章緩存

這個例子在實際項目中也比較常見,用戶每次點進一個新文章都需要從API請求數據,如果他下次再點進同一篇文章,我們可能希望直接用上次請求的數據,而不再次請求,這時候就可以用到我們的備忘錄模式了,直接拿上面的結構來用就行了:

function pageCache(pageId) {
  const cache = {};
  
  return function(pageId) {
    // 為了保持返回類型一致,我們都返回一個Promise
    if(cache[pageId]) {
      return Promise.resolve(cache[pageId]);
    } else {
      return axios.get(pageId).then((data) => {
        cache[pageId] = data;
        return data;
      })
    }
  }
}

上述代碼用了備忘錄模式來解決這個問題,但是代碼比較簡單,實際項目中可能需求會更加複雜一些,但是這個思路還是可以參考的。

實例:前進後退功能

這個例子的需求是,我們需要做一個可以移動的DIV,用戶把這個DIV隨意移動,但是他有時候可能誤操作或者反悔了,想把這個DIV移動回去,也就是將狀態回退到上一次,有了回退狀態的需求,當然還有配對的前進狀態的需求。這種類似的需求我們就可以用備忘錄模式實現:

function moveDiv() {
  this.states = [];       // 一個數組記錄所有狀態
  this.currentState = 0;  // 一個變量記錄當前狀態位置
}

// 移動方法,每次移動記錄狀態
moveDiv.prototype.move = function(type, num) {
  changeDiv(type, num);       // 偽代碼,移動DIV的具體操作,這裏並未實現
  
  // 記錄本次操作到states裏面去
  this.states.push({type,num});
  this.currentState = this.states.length - 1;   // 改變當前狀態指針
}

// 前進方法,取出狀態執行
moveDiv.prototype.forward = function() {
  // 如果當前不是最後一個狀態
  if(this.currentState < this.states.length - 1) {
    // 取出前進的狀態
    this.currentState++;
    const state = this.states[this.currentState];
    
    // 執行該狀態位置
    changeDiv(state.type, state.num);
  }
}

// 後退方法是類似的
moveDiv.prototype.back = function() {
  // 如果當前不是第一個狀態
  if(this.currentState > 0) {
    // 取出後退的狀態
    this.currentState--;
    const state = this.states[this.currentState];
    
    // 執行該狀態位置
    changeDiv(state.type, state.num);
  }
}

上述代碼通過一個數組將用戶所有操作過的狀態都記錄下來了,用戶可以隨時在狀態間進行前進和後退。

總結

本文講的這幾種設計模式策略/狀態模式外觀模式迭代器模式備忘錄模式都很好理解,而且在實際工作中也非常常見,熟練使用他們可以有效減少冗餘代碼,提高我們的代碼質量。

  1. 策略模式通過將我們的if條件改寫為一條條的策略減少了if...else的數量,看起來更清爽,擴展起來也更方便。狀態模式策略模式很像,只是還多了一個狀態,可以根據這個狀態來選取具體的策略。
  2. 外觀模式可能我們已經在無意間使用了,就是將模塊一些內部邏輯封裝在一個更高級的接口內部,或者將一些類似操作封裝在一個方法內部,從而讓外部調用更加方便。
  3. 迭代器模式在JS數組上有很多實現,我們也可以模仿他們做一下數據處理的工作,特別適合處理從API拿來的大量結構相似的數據。
  4. 備忘錄模式就是加一個緩存對象,用來記錄之前獲取過的數據或者操作的狀態,後面可以用來加快訪問速度或者進行狀態回滾。
  5. 還是那句話,設計模式的重點在於理解思想,實現方式可以多種多樣。

本文是講設計模式的最後一篇文章,前面三篇是:

(500+贊!)不知道怎麼封裝代碼?看看這幾種設計模式吧!

(100+贊!)框架源碼中用來提高擴展性的設計模式

不知道怎麼提高代碼復用性?看看這幾種設計模式吧

文章的最後,感謝你花費寶貴的時間閱讀本文,如果本文給了你一點點幫助或者啟發,請不要吝嗇你的贊和GitHub小星星,你的支持是作者持續創作的動力。

本文素材來自於網易高級前端開發工程師微專業唐磊老師的設計模式課程。

作者博文GitHub項目地址: https://github.com/dennis-jiang/Front-End-Knowledges

作者掘金文章匯總:https://juejin.im/post/5e3ffc85518825494e2772fd

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

國外測試 macOS Big Sur 使用 Chrome 時,RAM 記憶體使用量是 Safari 的 10 倍以上

Google Chrome 非常吃記憶體這是大家都知道的事情,即便 Chrome 87 版本已經有所改善,但相較於其他瀏覽器,佔用率還是高出不少,最近國外就有人實測 macOS 最新 Big Sur 版本的 Safari vs Chrome 比較,不比還好,一比沒想到竟然相差 10 倍以上,而且是分頁數量越開越多,差距也越來越大,這也代表說 Mac 用戶瀏覽器最好還是使用 Safari。

macOS Big Sur 使用 Chrome 時,RAM 記憶體使用量是 Safari 的 10 倍以上

最近一位 Flotato 的開發者 Morten Just,在官網上分享一篇 “瀏覽器吃掉 RAM” 的文章,他在 macOS 最新系統版本中,實驗兩種情況,來測試 Safari 與 Chrome 佔用多少記憶體。

首先是打開 Twitter.com 網站後,瀏覽一下,然後再開啟 Gmail 電子郵件服務。從下圖可以看到,光是僅開啟兩個網頁分頁,Chrome 最高峰就已經佔用 1GB RAM:

Safari 則保持在 80MB 以下。另外這測試是在 VM 虛擬機上運行

平均來看,Chrome 使用約 730MB,Safari 只有 73MB,差距達到 10 倍:

第二項則是壓力測試,他在 2019 年 MacBook Pro  16 上運行,共打開 54 個網頁分頁,Chrome 斜線快速上升,幾分鐘內 RAM 佔用率就達到了 1500MB:

Safari 依舊非常穩定,從這表格來看低到根本看不出使用多少 RAM:

而換算每個分頁使用的 RAM,Chrome 約 290MB,Safari 只有 12MB,意味著差距來到了 24 倍:

雖然這次實驗主要是為了推廣 Flotato 軟體,但他也提到 Chrome 的 RAM 佔用率比他想像還多,不過他確信 Chrome 還是會有效管理每個分頁佔用 RAM 的情況,進而確保當前網頁瀏覽順暢。

另外當開啟許多應用程式時,如:Sketch、Final Cut、Photoshop,Mac 也會嚴格分配 Chrome 的 RAM 使用率,因此實際使用可能也不會那麼多。

不過可以確定的是,如果你不希望瀏覽器消耗掉你太多 RAM,Safari 絕對是 Mac 系統的最佳選擇。

資料來源:flotato

Chrome 變萌了,教你如何製作小恐龍 QR Code 分享網址

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

【其他文章推薦】

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

網頁設計最專業,超強功能平台可客製化

※別再煩惱如何寫文案,掌握八大原則!

AirPods 3 新洩漏照流出,可選配耳塞加強「主動式降噪」體驗?

蘋果的 AirPods 真無線耳機將吹「混合風」?根據最新的媒體爆料,不僅有了疑似官圖比較照,就連實機照片居然也整個被貼出。是說,更有趣的其實反而是在規格方面的預測… 繼續閱讀 AirPods 3 新洩漏照流出,可選配耳塞加強「主動式降噪」體驗?報導內文。

▲圖片來源:我愛音頻網

AirPods 3 新洩漏照流出,可選配耳塞加強「主動式降噪」體驗?

相對於耳罩式的 AirPods Max,價格與規格更為接近的半入耳式 AirPods 與入耳式的 AirPods Pro,似乎將有可能更為「融合」?根據我愛音頻網的報導,他們以新世代 AirPods 3 的許多洩漏照搭配一系列專業分析與規格預測,讓我們有機會提前知道,這預計可能會在 3 月推出的蘋果新款真無線耳機的可能規格。

據報 AirPods 3 將巧妙融合半入耳與入耳式耳塞耳機的規格,並且也能使用主動式降噪(!)。也就是能在一般 AirPods 的半入耳式聆聽體驗下啟動降噪,但又能透過裝上耳塞提供更好的密合度,提升主動式降噪的效果。

▲圖片來源:我愛音頻網

既然橫看豎看應該沒有人不直接使用效果更好的解決方案。小編猜測這耳塞應該會可能會是「更 Pro」的加價升級選項?不得不說這還蠻有創意的,也可以觀察到洩漏的官圖細節,其實相對於 AirPods Pro,AirPods 3 在耳塞的部分是以拆卸下來的狀態,也許意味著這並非內附的配件(本段都是小編猜的,畢竟在正式發表前一切應該都要存疑)。

▲圖片來源:我愛音頻網

我愛音頻網提到,AirPods 3 基本上就是更為壓縮緊湊版的隨身型蘋果真無線耳機(總覺得這名號有點長)。充電盒的體積相對於 Pro 要更小(窄),但耳機柄倒是介於之間,比起 Pro 版要長一些些。功能方面可以預期現階段 AirPods Max 與 AirPods Pro 包括空間音訊等,都可以在 AirPods 3 上使用。價位部分,既然有搭載主動式降噪,據稱也會比起一般版本稍貴些,來到 US$150。

是說,重點搞不好會在 AirPods 3 的耳塞套是否會隨附?需另購的話會賣多少錢吧?目前 AirPods Pro 的定價是 NT$240,其實即便需要另購似乎也不會太痛?

延伸閱讀:

不是你想的那種機器貓,呆萌的 NICOBO 是會放屁(?)的可愛機器寵物

暗黑破壞神 2》HD 重製版今年登 PC / PS / XBOX / Switch 平台,畫質比較看這裡

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!