2015年宇通共獲新能源汽車補貼逾68億

4月4日,宇通客車發佈2015年年報。報告期內,累計完成客車銷售 67,018 輛,實現營業收入312.1 億元,實現歸屬于母公司所有者淨利潤 35.35 億元。公司新能源客車合計生產20568輛,同比增長173.91%;銷售20446台,同比增長 176.1%。其中純電動客車銷量為13885輛,同比增長706.8%;插電式混合動力客車銷量6560輛,同比增長17.8%。

在收入方面,純電動客車營收約92.62億元,插電式混合動力客車營收47.13億元。公告指出,報告期內,公司抓住了新能源市場的爆發式增長的機會,營業收入同比增長 21.31%,歸屬于母公司所有者的淨利潤同比增長 35.31%,經營活動現金淨流量 60.1 億元。

2015年宇通客車新能源汽車產銷及獲得補貼統計(來源:宇通客車2015年年報)

據瞭解,2015年宇通客車共獲得新能源汽車推廣應用補貼68.565億元。其中純電動客車補貼52.345億元,插電式混合動力客車補貼16.22億元。從獲得的補貼比例來看,純電動客車佔據56.52%的份額,插電式混合動力客車占比34.41%。

根據公告,新能源客車的關鍵零部件中,整車控制系統為自主研發自主生產,集成式電機控制器、電機、超級電容和動力電池系統均與行業綜合實力較強的供應商聯合開發,通過整合行業資源,研製出技術領先有競爭力的零部件,支撐公司新能源客車的技術領先。

報告指出,新能源市場受國家補貼政策的推動快速發展,大量企業切入客車市場,競爭情況更加複雜,隨著補貼政策的進一步完善,產品經過時間的檢驗,技術實力強、產品優質、綜合性價比高的企業將在未來新能源客車市場中居於主導地位。

根據公告,2015年,宇通客車實現了全產品端新能源產品的覆蓋,在節能技術、NVH 技術、無人駕駛技術等方面開展了深入研究,整體研發實力進一步增強。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

小三通物流營運型態?

※快速運回,大陸空運推薦?

Model 3 預售量逾 27.6 萬輛,分析師預言 Tesla 無法準時交貨

電動車製造商 Tesla 宣布,新款電動車 Model 3 開放預售後,僅 36 小時內 Model 3 的預訂總量就超過了 27.6 萬輛,這是 Tesla 迄今為止推出的最廉價的電動車,Tesla CEO Elon Musk 表示,Model 3 將在 2017 年年底投產,首批新車至少需要等待 18 個月後才能發貨。

2016 年 4 月 1 日 Tesla 發表最新入門級電動車 Model 3,這款電動車起售價為 3.5 萬美元,續航里程大約 350 公里,計劃在 2017 年年底投產,Tesla 首先將交付北美的訂單,其次是歐洲、亞太等地區。   儘管這款新車需要等待非常長的時間,預售還是吸引了大量消費者,Model 3 訂單總量超過了 27.6 萬輛,訂單總額為 106 億美元。Tesla CEO Elon Musk 表示,Model 3 的基礎定價是 3.5 萬美元,加上運輸費和稅費,首批在加州生產的 Model 3 平均售價將達到了 5 萬至 6 萬美元。   Tesla 加州工廠正在為 Model 3 的生產線擴建,目標是在 2020 年將產能提升到每年 50 萬輛,以滿足市場需求,由於產能提升緩慢,Tesla 將需要很長的時間才能交付首批訂單,部分分析師認為 Tesla 無法完成首批訂單的生產,無法向消費者交貨 Model 3 不得不在 2020 年向預訂的消費者退還 1,000 美元的訂金。   分析師預期 2016 年 6 月 Model 3 的訂單將達到 25 萬輛到 30 萬輛,由於該車的預訂量超過預期,將推高 Tesla 的股價,便於該公司增發新股,募集資金投建新的工廠。自 2016 年 2 月 Tesla 股價創下新低後已累計反彈 60%。

(本文授權轉載自《》─〈〉)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

台灣寄大陸海運貨物規則及重量限制?

大陸寄台灣海運費用試算一覽表

台中搬家,彰化搬家,南投搬家前需注意的眉眉角角,別等搬了再說!

裕隆、工研院攜手布局電動車充電站 合推新解決方案

裕隆自有品牌納智捷今(2016)年年中將開賣電動車,同時,裕隆也積極拓展充電站市場,旗下裕隆電能繼與BMW合作建置充電站之外,也將爭取與即將來台開賣電動車的特斯拉(Tesla)合作,在台建置電動車充電站;此外,裕隆電能也積極布局大陸市場,初期將與集團相關車廠東裕及東南汽車合作,預估將設立充電站達千站以上。   2016台灣國際電動車展於6日登場,工研院表示,根據調查,全台有近千支電動車的充電柱,提供納智捷及BMW、Tesla和Leaf等多款電動車用戶充電,其中逾8成來自工研院充電模組技術,顯示工研院在電動車充電技術具有舉足輕重的地位。在2016台灣國際電動車展的台灣車輛研發聯盟主題館中,展示裕隆電能採用工研院新一代充電模組的「U-charger充電柱」。    工研院指出,占國內電動車充電柱市場80%以上之國內充電柱生產二大龍頭華創及裕隆電能,皆採用工研院研發的最新一代充電模組。目前U-Charger充電柱中採用的這項新一代專利充電模組,具備多項安全保護,以符合各國法規要求,並獲得裕隆電能充電柱U-charger採用,準備進軍全世界,而裕隆電能已在全台配置約700座充電柱。   (本文內容由授權使用;首圖來源: CC BY 2.0)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

大陸寄台灣空運注意事項

大陸海運台灣交貨時間多久?

※避免吃悶虧無故遭抬價!台中搬家公司免費估價,有契約讓您安心有保障!

中國限定?Tesla Model S 加入防霧霾功能

發表不久的 Model 3 是 Tesla 旗下最受關注的車款,但距離這款車出貨還有一年多的時間,Tesla 衝擊 2016 年銷量目標的核心還是 Model S 電動車,這款車確立了 Tesla 在電動車領域的地位,Tesla 也不斷為這款車加入新功能,以應對同價位豪華汽車的競爭,近日 Tesla 為 Model S 加入了一項空氣過濾系統,能夠有效地吸收有害氣體和塵埃,這一功能對於空氣質量較差的中國市場擁有非常大的競爭力。

Model S 以超強動力和出眾的外觀設計廣受歡迎,但定價過高也一直阻礙著這款車衝擊高銷售,Tesla 也對 Model S 進行了升級,包括加入自動駕駛、更多的內飾材料、提高電池容量等。現在 Tesla 為 Model S 加入了一項非常實用的功能──空氣過濾系統,車主可以按下一個按鈕,啟動車內空氣過濾系統,這一系統能夠吸收車內的有害氣體和粉塵,可在幾秒鐘之內把車中的異味去除掉。   媒體對 Model S 的空氣過濾系統進行測試,這一系統清潔空氣的效率是其他同類產品的 10 倍,Tesla CEO Elon Musk 在 Twitter 上透露,Model S 的空氣過濾系統能夠給用戶帶來像醫院室內環境級別的空氣。   Tesla 對 Model S 的升級多集中在軟體和系統方面,更新的車款也沒有在命令上進行迭代,新款 Model S 的定價還需要等待正式發售後才會公開,Tesla 公司未透露新款的定價。

(本文授權轉載自《》─〈〉)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※專營大陸空運台灣貨物推薦

台灣空運大陸一條龍服務

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

彰化搬家費用,南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

【從今天開始好好學數據結構04】程序員你心中就沒點“樹”嗎?

目錄

前面我們講的都是線性表結構,棧、隊列等等。今天我們講一種非線性表結構,樹。樹這種數據結構比線性表的數據結構要複雜得多,內容也比較多,首先我們先從樹(Tree)開始講起。
@

樹(Tree)

樹型結構是一種非線性結構,它的數據元素之間呈現分支、分層的特點。

1.樹的定義

樹(Tree)是由n(n≥0)個結點構成的有限集合T,當n=0時T稱為空樹;否則,在任一非空樹T中:
(1)有且僅有一個特定的結點,它沒有前驅結點,稱其為根(Root)結點;
(2)剩下的結點可分為m(m≥0)個互不相交的子集T1,T2,…,Tm,其中每個子集本身又是一棵樹,並稱其為根的子樹(Subtree)。

注意:樹的定義具有遞歸性,即“樹中還有樹”。樹的遞歸定義揭示出了樹的固有特性

2.什麼是樹結構

什麼是“樹”?再好的定義,都沒有圖解來的直觀。所以我在圖中畫了幾棵“樹”。你來看看,這些“樹”都有什麼特徵?

你有沒有發現,“樹”這種數據結構真的很像我們現實生活中的“樹”

3.為什麼使用樹結構

在有序數組中,可以快速找到特定的值,但是想在有序數組中插入一個新的數據項,就必須首先找出新數據項插入的位置,然後將比新數據項大的數據項向後移動一位,來給新的數據項騰出空間,刪除同理,這樣移動很費時。顯而易見,如果要做很多的插入和刪除操作和刪除操作,就不該選用有序數組。另一方面,鏈表中可以快速添加和刪除某個數據項,但是在鏈表中查找數據項可不容易,必須從頭開始訪問鏈表的每一個數據項,直到找到該數據項為止,這個過程很慢。 樹這種數據結構,既能像鏈表那樣快速的插入和刪除,又能想有序數組那樣快速查找

4.樹的常用術語

結點——包含一個數據元素和若干指向其子樹的分支
度——結點擁有的子樹個數
樹的度——該樹中結點的最大度數
恭弘=叶 恭弘子——度為零的結點
分支結點(非終端結點)——度不為零的結點
孩子和雙親——結點的子樹的根稱為該結點的孩子,相應地,該結點稱為孩子的雙親
兄弟——同一個雙親的孩子
祖先和子孫——從根到該結點所經分支上的所有結點。相應地,以某一結點為根的子樹中的任一結點稱為該結點的子孫。
結點的層次——結點的層次從根開始定義,根結點的層次為1,其孩子結點的層次為2,……
堂兄弟——雙親在同一層的結點
樹的深度——樹中結點的最大層次
有序樹和無序樹——如果將樹中每個結點的各子樹看成是從左到右有次序的(即位置不能互換),則稱該樹為有序樹;否則稱為無序樹。
森林——m(m≥0)棵互不相交的樹的有限集合

到這裏,樹就講的差不多了,接下來講講二叉樹(Binary Tree)

二叉樹(Binary Tree)

樹結構多種多樣,不過我們最常用還是二叉樹,我們平時最常用的樹就是二叉樹。二叉樹的每個節點最多有兩個子節點,分別是左子節點和右子節點。二叉樹中,有兩種比較特殊的樹,分別是滿二叉樹和完全二叉樹。滿二叉樹又是完全二叉樹的一種特殊情況。

1.二叉樹的定義和特點

二叉樹的定義:
二叉樹(Binary Tree)是n(n≥0)個結點的有限集合BT,它或者是空集,或者由一個根結點和兩棵分別稱為左子樹和右子樹的互不相交的二叉樹組成 。
————————————
二叉樹的特點:
每個結點至多有二棵子樹(即不存在度大於2的結點);二叉樹的子樹有左、右之分,且其次序不能任意顛倒。

2.幾種特殊形式的二叉樹

1、滿二叉樹
定義:深度為k且有2k-1個結點的二叉樹,稱為滿二叉樹。
特點:每一層上的結點數都是最大結點數
2、完全二叉樹
定義:
深度為k,有n個結點的二叉樹當且僅當其每一個結點都與深度為k的滿二叉樹中編號從1至n的結點一一對應時,稱為完全二叉樹
特點:
特點一 : 恭弘=叶 恭弘子結點只可能在層次最大的兩層上出現;
特點二 : 對任一結點,若其右分支下子孫的最大層次為l,則其左分支下子孫的最大層次必為l 或l+1

建議看圖對應文字綜合理解

代碼創建二叉樹

首先,創建一個節點Node類

package demo5;
/*
 * 節(結)點類 
 */
public class Node {
    //節點的權
    int value;
    //左兒子(左節點)
    Node leftNode;
    //右兒子(右節點)
    Node rightNode;
    //構造函數,初始化的時候就給二叉樹賦上權值
    public Node(int value) {
        this.value=value;
    }
    
    //設置左兒子(左節點)
    public void setLeftNode(Node leftNode) {
        this.leftNode = leftNode;
    }
    //設置右兒子(右節點)
    public void setRightNode(Node rightNode) {
        this.rightNode = rightNode;
    }

接着創建一個二叉樹BinaryTree 類

package demo5;
/*
 * 二叉樹Class
 */
public class BinaryTree {
    //根節點root
    Node root;
    
    //設置根節點
    public void setRoot(Node root) {
        this.root = root;
    }
    
    //獲取根節點
    public Node getRoot() {
        return root;
    }
}

最後創建TestBinaryTree 類(該類主要是main方法用來測試)來創建一個二叉樹

package demo5;
public class TestBinaryTree {

    public static void main(String[] args) {
        //創建一顆樹
        BinaryTree binTree = new BinaryTree();
        //創建一個根節點
        Node root = new Node(1);
        //把根節點賦給樹
        binTree.setRoot(root);
        //創建一個左節點
        Node rootL = new Node(2);
        //把新創建的節點設置為根節點的子節點
        root.setLeftNode(rootL);
        //創建一個右節點
        Node rootR = new Node(3);
        //把新創建的節點設置為根節點的子節點
        root.setRightNode(rootR);
        //為第二層的左節點創建兩個子節點
        rootL.setLeftNode(new Node(4));
        rootL.setRightNode(new Node(5));
        //為第二層的右節點創建兩個子節點
        rootR.setLeftNode(new Node(6));
        rootR.setRightNode(new Node(7));
    }

}

下面將會講的遍歷、查找節點、刪除節點都將圍繞這三個類開展

不難看出創建好的二叉樹如下(畫的不好,還望各位見諒):

3.二叉樹的兩種存儲方式

二叉樹既可以用鏈式存儲,也可以用數組順序存儲。數組順序存儲的方式比較適合完全二叉樹,其他類型的二叉樹用數組存儲會比較浪費存儲空間,所以鏈式存儲更合適。

我們先來看比較簡單、直觀的鏈式存儲法

接着是基於數組的順序存儲法(該例子是一棵完全二叉樹)

上面例子是一棵完全二叉樹,所以僅僅“浪費”了一個下標為0的存儲位置。如果是非完全二叉樹,則會浪費比較多的數組存儲空間,如下。

還記得堆和堆排序嗎,堆其實就是一種完全二叉樹,最常用的存儲方式就是數組。

4.二叉樹的遍歷

前面我講了二叉樹的基本定義和存儲方法,現在我們來看二叉樹中非常重要的操作,二叉樹的遍歷。這也是非常常見的面試題。

經典遍歷的方法有三種,前序遍歷中序遍歷後序遍歷

前序遍歷是指,對於樹中的任意節點來說,先打印這個節點,然後再打印它的左子樹,最後打印它的右子樹。

中序遍歷是指,對於樹中的任意節點來說,先打印它的左子樹,然後再打印它本身,最後打印它的右子樹。

後序遍歷是指,對於樹中的任意節點來說,先打印它的左子樹,然後再打印它的右子樹,最後打印這個節點本身。

我想,睿智的你已經想到了二叉樹的前、中、後序遍歷就是一個遞歸的過程。比如,前序遍歷,其實就是先打印根節點,然後再遞歸地打印左子樹,最後遞歸地打印右子樹。

在之前創建好的二叉樹代碼之上,我們來使用這三種方法遍歷一下~

依舊是在Node節點類上添加方法:可以看出遍歷方法都是用的遞歸思想

package demo5;
/*
 * 節(結)點類 
 */
public class Node {
//===================================開始 遍歷========================================
    //前序遍歷
    public void frontShow() {
        //先遍歷當前節點的內容
        System.out.println(value);
        //左節點
        if(leftNode!=null) {
            leftNode.frontShow();
        }
        //右節點
        if(rightNode!=null) {
            rightNode.frontShow();
        }
    }

    //中序遍歷
    public void midShow() {
        //左子節點
        if(leftNode!=null) {
            leftNode.midShow();
        }
        //當前節點
        System.out.println(value);
        //右子節點
        if(rightNode!=null) {
            rightNode.midShow();
        }
    }

    //後序遍歷
    public void afterShow() {
        //左子節點
        if(leftNode!=null) {
            leftNode.afterShow();
        }
        //右子節點
        if(rightNode!=null) {
            rightNode.afterShow();
        }
        //當前節點
        System.out.println(value);
    }

}

然後依舊是在二叉樹BinaryTree 類上添加方法,並且添加的方法調用Node類中的遍歷方法

package demo5;
/*
 * 二叉樹Class
 */
public class BinaryTree {

    public void frontShow() {
        if(root!=null) {
            //調用節點類Node中的前序遍歷frontShow()方法
            root.frontShow();
        }
    }

    public void midShow() {
        if(root!=null) {
            //調用節點類Node中的中序遍歷midShow()方法
            root.midShow();
        }
    }

    public void afterShow() {
        if(root!=null) {
            //調用節點類Node中的後序遍歷afterShow()方法
            root.afterShow();
        }
    }

}

依舊是在TestBinaryTree類中測試

package demo5;

public class TestBinaryTree {

    public static void main(String[] args) {
        //前序遍歷樹
        binTree.frontShow();
        System.out.println("===============");
        //中序遍歷
        binTree.midShow();
        System.out.println("===============");
        //後序遍歷
        binTree.afterShow();
        System.out.println("===============");
        //前序查找
        Node result = binTree.frontSearch(5);
        System.out.println(result);
        
}

如果遞歸理解的不是很透,我可以分享一個學習的小方法:我建議各位可以這樣斷點調試,一步一步調,思維跟上,仔細推敲每一步的運行相信我,你會重新認識到遞歸!(像下面這樣貼個圖再一步一步斷點思維更加清晰)

貼一下我斷點對遞歸的分析,希望對你有一定的幫助~

二叉樹遍歷的遞歸實現思路自然、簡單,易於理解,但執行效率較低。為了提高程序的執行效率,可以顯式的設置棧,寫出相應的非遞歸遍歷算法。非遞歸的遍歷算法可以根據遞歸算法的執行過程寫出。至於代碼可以嘗試去寫一寫,這也是一種提升!具體的非遞歸算法主要流程圖貼在下面了:

二叉樹遍歷算法分析:

二叉樹遍歷算法中的基本操作是訪問根結點,不論按哪種次序遍歷,都要訪問所有的結點,對含n個結點的二叉樹,其時間複雜度均為O(n)。所需輔助空間為遍歷過程中所需的棧空間,最多等於二叉樹的深度k乘以每個結點所需空間數,最壞情況下樹的深度為結點的個數n,因此,其空間複雜度也為O(n)。

5.二叉樹中節點的查找與刪除

剛才講到二叉樹的三種金典遍歷放法,那麼節點的查找同樣是可以效仿的,分別叫做前序查找、中序查找以及後序查找,下面代碼只以前序查找為例,三者查找方法思路類似~

至於刪除節點,有三種情況:

1、如果刪除的是根節點,那麼二叉樹就完全被刪了
2、如果刪除的是雙親節點,那麼該雙親節點以及他下面的所有子節點所構成的子樹將被刪除
3、如果刪除的是恭弘=叶 恭弘子節點,那麼就直接刪除該恭弘=叶 恭弘子節點

那麼,我把完整的三個類給貼出來(包含創建、遍歷、查找、刪除)

依舊是Node節點類

package demo5;
/*
 * 節(結)點類 
 */
public class Node {
    //節點的權
    int value;
    //左兒子
    Node leftNode;
    //右兒子
    Node rightNode;
    //構造函數,初始化的時候就給二叉樹賦上權值
    public Node(int value) {
        this.value=value;
    }
    
    //設置左兒子
    public void setLeftNode(Node leftNode) {
        this.leftNode = leftNode;
    }
    //設置右兒子
    public void setRightNode(Node rightNode) {
        this.rightNode = rightNode;
    }
    
    //前序遍歷
    public void frontShow() {
        //先遍歷當前節點的內容
        System.out.println(value);
        //左節點
        if(leftNode!=null) {
            leftNode.frontShow();
        }
        //右節點
        if(rightNode!=null) {
            rightNode.frontShow();
        }
    }

    //中序遍歷
    public void midShow() {
        //左子節點
        if(leftNode!=null) {
            leftNode.midShow();
        }
        //當前節點
        System.out.println(value);
        //右子節點
        if(rightNode!=null) {
            rightNode.midShow();
        }
    }

    //後序遍歷
    public void afterShow() {
        //左子節點
        if(leftNode!=null) {
            leftNode.afterShow();
        }
        //右子節點
        if(rightNode!=null) {
            rightNode.afterShow();
        }
        //當前節點
        System.out.println(value);
    }

    //前序查找
    public Node frontSearch(int i) {
        Node target=null;
        //對比當前節點的值
        if(this.value==i) {
            return this;
        //當前節點的值不是要查找的節點
        }else {
            //查找左兒子
            if(leftNode!=null) {
                //有可能可以查到,也可以查不到,查不到的話,target還是一個null
                target = leftNode.frontSearch(i);
            }
            //如果不為空,說明在左兒子中已經找到
            if(target!=null) {
                return target;
            }
            //查找右兒子
            if(rightNode!=null) {
                target=rightNode.frontSearch(i);
            }
        }
        return target;
    }
    
    //刪除一個子樹
    public void delete(int i) {
        Node parent = this;
        //判斷左兒子
        if(parent.leftNode!=null&&parent.leftNode.value==i) {
            parent.leftNode=null;
            return;
        }
        //判斷右兒子
        if(parent.rightNode!=null&&parent.rightNode.value==i) {
            parent.rightNode=null;
            return;
        }
        
        //遞歸檢查並刪除左兒子
        parent=leftNode;
        if(parent!=null) {
            parent.delete(i);
        }
        
        //遞歸檢查並刪除右兒子
        parent=rightNode;
        if(parent!=null) {
            parent.delete(i);
        }
    }

}

依舊是BinaryTree 二叉樹類

package demo5;
/*
 * 二叉樹Class
 */
public class BinaryTree {
    //根節點root
    Node root;
    
    //設置根節點
    public void setRoot(Node root) {
        this.root = root;
    }
    
    //獲取根節點
    public Node getRoot() {
        return root;
    }

    public void frontShow() {
        if(root!=null) {
            //調用節點類Node中的前序遍歷frontShow()方法
            root.frontShow();
        }
    }

    public void midShow() {
        if(root!=null) {
            //調用節點類Node中的中序遍歷midShow()方法
            root.midShow();
        }
    }

    public void afterShow() {
        if(root!=null) {
            //調用節點類Node中的後序遍歷afterShow()方法
            root.afterShow();
        }
    }
    //查找節點i
    public Node frontSearch(int i) {
        return root.frontSearch(i);
    }
    //刪除節點i
    public void delete(int i) {
        if(root.value==i) {
            root=null;
        }else {
            root.delete(i);
        }
    }
    
}

依舊是TestBinaryTree測試類

package demo5;

public class TestBinaryTree {

    public static void main(String[] args) {
        //創建一顆樹
        BinaryTree binTree = new BinaryTree();
        //創建一個根節點
        Node root = new Node(1);
        //把根節點賦給樹
        binTree.setRoot(root);
        //創建一個左節點
        Node rootL = new Node(2);
        //把新創建的節點設置為根節點的子節點
        root.setLeftNode(rootL);
        //創建一個右節點
        Node rootR = new Node(3);
        //把新創建的節點設置為根節點的子節點
        root.setRightNode(rootR);
        //為第二層的左節點創建兩個子節點
        rootL.setLeftNode(new Node(4));
        rootL.setRightNode(new Node(5));
        //為第二層的右節點創建兩個子節點
        rootR.setLeftNode(new Node(6));
        rootR.setRightNode(new Node(7));
        //前序遍歷樹
        binTree.frontShow();
        System.out.println("===============");
        //中序遍歷
        binTree.midShow();
        System.out.println("===============");
        //後序遍歷
        binTree.afterShow();
        System.out.println("===============");
        //前序查找
        Node result = binTree.frontSearch(5);
        System.out.println(result);
        
        System.out.println("===============");
        //刪除一個子樹
        binTree.delete(4);
        binTree.frontShow();
        
    }

}

到這裏,總結一下,我們學了一種非線性表數據結構,樹。關於樹,有幾個比較常用的概念你需要掌握,那就是:根節點、恭弘=叶 恭弘子節點、父節點、子節點、兄弟節點,還有節點的高度、深度、層數,以及樹的高度等。我們平時最常用的樹就是二叉樹。二叉樹的每個節點最多有兩個子節點,分別是左子節點和右子節點。二叉樹中,有兩種比較特殊的樹,分別是滿二叉樹和完全二叉樹。滿二叉樹又是完全二叉樹的一種特殊情況。二叉樹既可以用鏈式存儲,也可以用數組順序存儲。數組順序存儲的方式比較適合完全二叉樹,其他類型的二叉樹用數組存儲會比較浪費存儲空間。除此之外,二叉樹里非常重要的操作就是前、中、後序遍歷操作,遍歷的時間複雜度是O(n),你需要理解並能用遞歸代碼來實現。

如果本文章對你有幫助,哪怕是一點點,請點個讚唄,謝謝~

歡迎各位關注我的公眾號,一起探討技術,嚮往技術,追求技術…說好了來了就是盆友喔…

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

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

※試算大陸海運運費!

Head First設計模式——命令模式

前言:命令模式我們平常可能會經常使用,如果我們不了解命令模式的結構和定義那麼在使用的時候也不會將它對號入座。

舉個例子:在winform開發的時候我們常常要用同一個界面來進行文件的下載,但是並不是所有地方都用同一個下載邏輯處理文件,然後下載界面卻可以是同一個界面。

為了以後復用下載界面(下載显示,進度條等)我們常常將下載執行操作定義成一個接口,在具體使用的時候實現接口,將具體執行對象設置到下載界面。當下載按鈕被按下的時候,就調用設置的具體執行對象(接收者)來執行下載的處理。

那接下來我們就看下命令模式的具體細節和實現,再回頭想想我們平時什麼時候不經意就使用到了命令模式,這樣以後交流使用專業的術語不僅能裝還能用。

1、遙控器應用場景

HeadFirst設計模式一書中以遙控器為例實現命令模式,以餐館點餐講解命令模式的對象和結構。為了邏輯清晰我們不混合兩種講解方式,只以遙控器為例講解。

現在需求是有一個遙控器,遙控器上面有控制各種電器的開關,而開關的執行控制電器是由各個廠家開發的設備(對象)插入到對應開關位置的卡槽裏面,基於這些條件我們來實現遙控器系統。

簡單粗暴的解決方案可以對開關做一個標識,當某個開關被按下時根據開關類型進行if判斷。形如 if slot1==Light ,then light.on(), else if slot1==Tv then tv.on() 這種代碼將出現一堆,對於以後增加減少開關或者更換開關都是比較糟糕的。而對於設計遙控器類來說我們應該讓遙控器代碼盡量保持簡單,而不用去關心具體廠商類怎麼執行。所以我們應該將執行封裝在一個命令對象里中,那麼我們就試着一步步實現遙控器。

  首先我們為命令對象定義一個統一的接。

  接口只有一個簡單的execute執行命令方法。

    public interface Command
    {
        //執行命令的方法
        public void execute();
    }

  接下來我們實現一個打開電燈的命令

    public class Light
    {
        public void on() {
            Console.WriteLine("打開電燈");
        }

        public void off()
        {
            Console.WriteLine("關閉電燈");
        }
    }

    public class LightOnCommand : Command
    {
        Light light;

        public LightOnCommand(Light light)
        {
            this.light = light;
        }
        public void execute()
        {
            light.on();
        }
    }

  為了簡單我們假設遙控器只有一個開關,實現遙控器。

    public class SimpleRemoteControl
    {
        //卡槽
        Command slot;

        public void setCommand(Command command)
        {
            slot = command;
        }

        //按下開關
        public void ButtonWasPressed() {
            slot.execute();
        }

    }

  測試

     static void Main(string[] args)
        {
            SimpleRemoteControl remoteControl = new SimpleRemoteControl();
            //廠商提供的電燈類,命令的接收者
            Light light = new Light();

            //我們封裝的命令對象,設置接收者
            LightOnCommand lightOnCommand = new LightOnCommand(light);

            //設置遙控器開關對應的命令對象
            remoteControl.setCommand(lightOnCommand);
            remoteControl.ButtonWasPressed();
            Console.ReadKey();
        }

  

2、命令模式、類圖

通過上面的例子我們已經使用了命令模式來實現一個簡單的遙控器,再回顧【前言】我們說的界面下載文件按鈕操作是不是就是一個典型的可以使用命令模式的應用場景。

只是有一點我們可能不會有什麼其他廠商設計好的執行類,我們也許直接就在繼承接口的命令對象中實現execute的邏輯,而不用再調用其他接收者執行。

這就是“聰明”命令對象,上面我們實現的是“傻瓜”命令對象。這個稍後再說,我們先看命令模式定義和畫出類圖。

命令模式:將“請求”封裝成對象,以便使用不同的請求、隊列或日誌來參數化其他對象。命令模式也支持撤銷的操作。

3、完成多開關遙控器和撤銷操作

假設遙控器現在有五個開關。我們已經有簡單遙控器的經驗,那麼其他4個開關我們也將對應的命令對象設置上去就行了。定義兩個數組用來記錄開關對應的命令對象。

    public class RemoteControl
    {
        Command[] onCommands;
        Command[] offCommands;
        public RemoteControl()
        {
            onCommands = new Command[5];
            offCommands = new Command[5];
            Command noCommand = new NoCommand();
            for (int i = 0; i < 5; i++)
            {
                onCommands[i] = noCommand;
                offCommands[i] = noCommand;
            }
        }
        public void setCommand(int slot,Command commandOn, Command commandOff)
        {
            onCommands[slot] = commandOn;
            offCommands[slot] = commandOff;
        }

        //按下開關
        public void OnButtonWasPressed(int slot)
        {
            onCommands[slot].execute();
        }
        //關閉開關
        public void OffButtonWasPressed(int slot)
        {
            offCommands[slot].execute();
        }

        //打印出數組命令對象
        public override string ToString() {
            var sb = new StringBuilder("\n------------Remote Control-----------\n");
            for (int i = 0; i < onCommands.Length; i++)
            {
                sb.Append($"[slot{i}] {onCommands[i].GetType()}\t{offCommands[i].GetType()} \n");
            }
            return sb.ToString();
        }

    }

  在遙控器中我們定義了一個Nocommand類,是為了對遙控器對應的開關初始化命令對象,避免為空報錯或者消除開關調用命令對象時檢查對象是否為空的判斷。 

     public void OnButtonWasPressed(int slot)
        {
            if(onCommand[slot]!=null))
                onCommands[slot].execute();
        }

  在許多設計模式中我們都能看到這種初始值或者空對象的使用。甚至有時候,空對象本身也被視為一種設計模式。(感覺這樣代碼比較優雅O(∩_∩)O)

遙控器完成了,我們還有做一項工作,就是撤銷操作。

撤銷操作我們同樣在命令接口裡面定義一個undo 方法。

    public interface Command
    {
        //執行命令的方法
        public void execute();
        //撤銷命令方法
        public void undo();
    }

  然後我們讓LightOnCommand實現undo方法,添加LightOffCommand命令對象。

    public class LightOnCommand : Command
    {
        Light light;

        public LightOnCommand(Light light)
        {
            this.light = light;
        }
        public void execute()
        {
            light.on();
        }
        public void undo() {
            light.off();
        }
    }


    class LightOffCommand : Command
    {
        Light light;

        public LightOffCommand(Light light)
        {
            this.light = light;
        }
        public void execute()
        {
            light.off();
        }

        public void undo()
        {
            light.on();
        }
    }

 遙控器裏面添加撤銷按鈕操作UndoButtonWasPressed並用undoCommand屬性存儲上一次操作。

    public class RemoteControl
    {
        Command[] onCommands;
        Command[] offCommands;
        Command undoCommand;
        public RemoteControl()
        {
            onCommands = new Command[5];
            offCommands = new Command[5];
            Command noCommand = new NoCommand();
            for (int i = 0; i < 5; i++)
            {
                onCommands[i] = noCommand;
                offCommands[i] = noCommand;
            }
        }
        public void setCommand(int slot,Command commandOn, Command commandOff)
        {
            onCommands[slot] = commandOn;
            offCommands[slot] = commandOff;
        }

        //按下開關
        public void OnButtonWasPressed(int slot)
        {
            onCommands[slot].execute();
            undoCommand = onCommands[slot];
        }
        //關閉開關
        public void OffButtonWasPressed(int slot)
        {
            offCommands[slot].execute();
            undoCommand = offCommands[slot];
        }

        public void UndoButtonWasPressed() {
            undoCommand.undo();
        }
        //打印出數組命令對象
        public override string ToString() {
            var sb = new StringBuilder("\n------------Remote Control-----------\n");
            for (int i = 0; i < onCommands.Length; i++)
            {
                sb.Append($"[slot{i}] {onCommands[i].GetType()}\t{offCommands[i].GetType()} \n");
            }
            return sb.ToString();
        }

    }

測試:

4、補充總結

補充:

①命令模式的接收者不一定要存在,之前提到過“聰明”和“傻瓜”命令對象,如果以“聰明”命令對象設計,調用者和接收者之間解耦程度比不上“傻瓜”命令對象,但是我們在使用比較簡單的時候仍然可以使用“聰明”命令對象設計。

②撤銷例子我們只做了返回最後一次操作,如果要撤銷許多次我們可以對操作記錄進行保存到堆棧,不管什麼時候撤銷,我們都可以從堆棧中取出最上層命令對象執行撤銷操作。

命令模式常被用於隊列請求,日誌請求。當隊列按照順序取到存放的命令對象后調用執行方法就行了而不用去管具體執行什麼。

日誌請求在某些場合可以用來將所有動作記錄在日誌中,並能在系統死機后通過日誌記錄進行恢復到之前的狀態(撤銷)。對於更高級的的應用而言,這些技巧可以應用到事務(transaction)處理中。

 通過簡單到更進一步的實現講解了命令模式和一些靈活點和需要注意的點,有什麼理解不到位的歡迎指正。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

※專營大陸快遞台灣服務

台灣快遞大陸的貨運公司有哪些呢?

Asciinema文章勘誤及Web端使用介紹

欠下的債遲早是要還的,查文檔,重驗證,出結果,不誤導

文章勘誤

在上一篇文章中有兩個地方表述有錯誤或瑕疵,這裏更正一下

第一個地方為錄製時的參數--stdin,參數的意思是啟用標準輸入錄製,原文中說看不到效果,可能官方還未支持,實際上官方已經支持了,且查看錄製文件內容時可以看到區別,以下兩個對比的例子來說明

例一:執行下方的命令進行錄製,錄製開始之後執行ssh命令輸入密碼連接另一台主機

asciinema rec ops-coffee.cast

執行asciinema cat命令查看執行命令

# asciinema cat ops-coffee.cast 
root@onlinegame:~# ssh root@192.168.106.192 ls ops-coffee.cn
root@192.168.106.192's password: 
ops-coffee.cn
root@onlinegame:~# exit
exit

打印錄製的文件內容如下:

# cat ops-coffee.cast 
{"version": 2, "width": 237, "height": 55, "timestamp": 1574060513, "env": {"SHELL": "/bin/bash", "TERM": "linux"}}
[0.012221, "o", "root@onlinegame:~# "]
[0.607184, "o", "exit"]
[1.07092, "o", "\b\b\b\bssh root@192.168.106.192 ls ops-coffee.cn"]
[1.703405, "o", "\r\n"]
[1.762974, "o", "root@192.168.106.192's password: "]
[4.550759, "o", "\r\n"]
[4.558138, "o", "ops-coffee.cn\r\n"]
[4.559187, "o", "root@onlinegame:~# "]
[5.182817, "o", "e"]
[5.582643, "o", "x"]
[5.838648, "o", "i"]
[6.03067, "o", "t"]
[6.759346, "o", "\r\nexit\r\n"]

例二:執行同樣的命令,加上--stdin參數

asciinema rec --stdin ops-coffee.1.cast

執行asciinema cat命令查看執行命令

# asciinema cat ops-coffee.1.cast 
root@onlinegame:~# ssh root@192.168.106.192 ls ops-coffee.cn
root@192.168.106.192's password: 
ops-coffee.cn
root@onlinegame:~# exit
exit

這次再看錄製文件的內容:

# cat ops-coffee.1.cast
{"version": 2, "width": 237, "height": 55, "timestamp": 1574060808, "env": {"SHELL": "/bin/bash", "TERM": "linux"}}
[0.01012, "o", "root@onlinegame:~# "]
[1.654752, "i", "\u001b[A"]
[1.654971, "o", "exit"]
[2.014568, "i", "\u001b[A"]
[2.014727, "o", "\b\b\b\bssh root@192.168.106.192 ls ops-coffee.cn"]
[3.7185, "i", "\r"]
[3.719167, "o", "\r\n"]
[3.781231, "o", "root@192.168.106.192's password: "]
[5.198467, "i", "s"]
[5.542343, "i", "m"]
[5.774451, "i", "i"]
[5.85435, "i", "l"]
[5.990628, "i", "e"]
[6.342587, "i", "\r"]
[6.342817, "o", "\r\n"]
[6.351245, "o", "ops-coffee.cn\r\n"]
[6.351475, "o", "root@onlinegame:~# "]
[7.182384, "i", "e"]
[7.182585, "o", "e"]
[7.461976, "i", "x"]
[7.462183, "o", "x"]
[7.543019, "i", "i"]
[7.543306, "o", "i"]
[7.686868, "i", "t"]
[7.68703, "o", "t"]
[7.87045, "i", "\r"]
[7.871348, "o", "\r\nexit\r\n"]

會發現在實際執行命令完全一致的情況下,錄像文件與上一個沒有加--stdin時的不一樣,其中就多了輸入密碼的記錄smile

且在asciinema文件IO流信息的第二個字段不僅有了o,還有i的出現,上一篇文章講到o是一個固定字符串不知道作用,經過深入查詢確認,IO信息流的第二個字段就是固定string字符串,且只會是io之間的一種,分別表示stdin標準輸入或stdout標準輸出

--stdin的效果無論是通過asciinema play命令播放或是asciinema cat命令查看都是無法察覺的,在實現WebSSH錄像回放時又對錄像文件進行了深入研究,最終發現問題,這裏查漏補缺,予以更正,對於之前的錯誤,深表歉意

Web端使用

asciinema錄製文件在web端播放是通過asciinema-player組件來實現的,使用也是非常的簡單

分別引入css和js文件,添加一個asciinema-player的標籤即可播放標籤內文件的錄像

<html>
<head>
  ...
  <link rel="stylesheet" type="text/css" href="/asciinema-player.css" />
  ...
</head>
<body>
  ...
  <asciinema-player src="/ops-coffee.cast"></asciinema-player>
  ...
  <script src="/asciinema-player.js"></script>
</body>
</html>

asciinema-player標籤內可以添加如下一些屬性:

cols: 播放終端的列數,默認為80,如果cast文件的header頭有設置width,這裏無需設置

rows: 播放終端的行數,默認為24,如果cast文件的header頭有設置height,這裏無需設置

autoplay: 是否自動開始播放,默認不會自動播放

preload: 預加載,如果你想為錄像配音,這裏可以預加載聲音

loop: 是否循環播放,默認不循環

start-at: 從哪個地方開始播放,可以是123這樣的秒數或者是1:06這樣的時間點

speed: 播放的速度,類似於play命令播放時的-s參數

idle-time-limit: 最大空閑秒數,類似於play命令播放時的-i參數

poster: 播放之前的預覽,可以是npt:1:06這樣給定時間點的畫面,也可以是data:text/plain,ops-coffee.cn這樣給定的文字,其中文字支持ANSI編碼,例如可以給文字加上顏色data:text/plain,\x1b[1;32mops-coffee.cn\x1b[1;0m

font-size: 文字大小,可以是smallmediumbig或者直接是14px這樣的css樣式大小

theme: 終端顏色主題,默認是asciinema,也提供有tangosolarized-darksolarized-light或者monokai可選擇,當然你也可以自定義主題

還有幾個參數titleauthorauthor-urlauthor-img-url分別表示了錄像的標題、作者、作者的主頁、作者的頭像,這些配置會在全屏觀看錄像時显示在標題欄中,像下邊這樣

最後使用以下參數設置asciinema-player,看看播放的效果

<asciinema-player id="play" 
    title="WebSSH Record" 
    author="ops-coffee.cn" 
    author-url="https://ops-coffee.cn" 
    author-img-url="/static/img/logo.png" 
    src="/static/record/ops-coffee.cast" 
    speed="3" idle-time-limit="2" 
    poster="data:text/plain,\x1b[1;32m2019-11-18 16:26:18\x1b[1;0m用戶\x1b[1;32madmin\x1b[1;0m連接主機\x1b[1;32m192.168.106.101:22\x1b[1;0m的錄像記錄">
</asciinema-player>

播放效果如下

同時asciinema-player播放時還支持以下快捷鍵的使用

  • space 空格,播放或暫停
  • f 全屏播放,可以看到title等設置
  • / 快進或快退,每次5秒
  • 0,1,6 ... 9 跳轉到錄像的0%,10%,60% … 90%
  • < / > 增加或降低播放速度,play的-s參數

相關文章推薦閱讀:

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

台灣海運大陸貨務運送流程

兩岸物流進出口一站式服務

碎片化的時代,如何學習

今天周末,和大家聊聊學習這件事情。

在如今這個社會,我們的時間被各類 APP 撕的粉碎。

刷知乎、刷微博、刷朋友圈;

看論壇、看博客、看公號;

等等形形色色的信息和知識獲取方式一個都不錯過。

貌似學了很多,但是卻感覺沒什麼用。

要解決上面這些問題,首先要分清楚一點,什麼是信息,什麼是知識。

  • 那什麼是信息呢?

你一切聽到的、看到的,都是信息,比如微博上的明星出軌、微信中的表情大戰、抖音上的段子視頻。

  • 那什麼是知識呢?

就是指那些被驗證過的、正確的、被人們相信的概念、規律、方法論。

概念是什麼很好理解,我們上學的時候做的最多的一件事情就是背概念。

規律是事物背後的運行法則,例如當市場上某一種貨物供應量減少后,就會導致價格的上升。

方法論俗稱【套路】,解決某一類問題的時候,有效的解決方案。

信息有真假,有時效,而知識有積累、有迭代。我們要學習的是知識,而不是信息。

時間複利

再說一個概念,複利

這個大家都應該知道,比較多的應該是在銀行存錢或者是購買理財產品的時候,比如下圖的銀行複利增長曲線:

或者是下面這個公式:

1.01^365 = 37.8

隨公式還附贈一句雞湯:

如果一個人每天都能進步 1%,一年之後他的能力會提升 38 倍。

這句話雖然聽起來很雞湯,但是卻想不到有什麼問題,對吧?

反過來想一下這句話的前提,一個人都想要每天都能進步 1% ,這可能么?當然排除一些極端情況,對於看到我文章的大多數人來說,這是一件不可能的事情。

每天會形形色色的事情去阻止我們進步這 1% ,比如:

  • 今天上班被領導批了,心情不好,不想學習了
  • 今天工作太忙了,下班后時間都比較晚了,想要休息了
  • 今天和朋友一起出去玩了,玩的很開心

等等,然後我們看到身邊優秀的人的時候:

  • 看到他們隨隨便便就考試考出來好成績
  • 看到他們隨手寫的文章就是網絡爆文
  • 看到他們對於某一項技術非常精通的時候

是不是會有一種無力感,好像他們平時和我們一樣,該吃吃該喝喝該玩玩,但是人家就是很厲害的樣子。

於是,就產生了強烈的焦慮感,要學習,要提高自己,開始看更多的信息,關注更多的學習圈子,焦慮感加重,負向循環開始了。

但是,如果你肯相信時間複利的效應,就不會焦慮。

Why?

你現在看到身邊的人的成績,看到他輕輕鬆松做到的事情,即使你拼盡全力也不可能做到。

從一開始你就錯了,他們已經完成了自己的原始積累,他們已經到達了這件事情的複利拐點

什麼是複利拐點?

圖中這個小人站的位置就是複利拐點,

如果一個人到達複利拐點(圖中小人站的地方),那他的收益,會急劇增長,可能比之前所有時間的收益總和還要多。

這裏的收益可以是錢,是能力,是認知。

知識

上面我們介紹了如何區分信息和知識,這裏再分享一個個人理解,有效知識。

判斷一個知識是否有效,就要看這個知識和你之前的已有的知識是否能產生聯繫。

如果可以產生聯繫,那麼你對這個新的知識理解速度會非常的快。

就好比編程語言,小編的本職工作是一名 Java 軟件工程師,但是當小編去學習 Python 的相關基礎知識的時候,速度是非常快的,用旁人的視角看起來就好像小編的學習效率非常的高效。

當然,我們不可能只學習和自己已有知識相關方向的知識,可能會接觸一些完全陌生的領域,那麼這個時候如何還能保持效率較高的學習?

在進入一個新的領域的時候,所有的知識都是一個點一個點的,好像是散落在沙子里的石頭,中間是毫無關聯的,這個時間段的學習是非常痛苦的,小編一般稱為原始積累階段。

很多人在原始積累的階段因為過程過於痛苦,就慢慢的放棄了。

在痛苦的原始積累階段,很多時候看不到盡頭,之前的學習感覺都學過,但是仔細一想,又好像什麼都沒學。

這時,我們可以藉助工具去加強知識之間的關聯和加深自己的記憶——思維導圖。

這個工具小編也經常在用的,比如很多人可能都見過我的公眾號上小編自己整理的 Java 進階相關的思維導圖。

當學習一個新的領域的時候,每當一些基礎的概念能產生聯繫的時候,就可以去畫這麼一張思維導圖,思維導圖能讓我們更清晰直觀的理解不同的事物之間的內在聯繫。

長時間的原始積累太過痛苦怎麼辦?

這個是所有人都會遇到的問題,做一件事情,尤其是學習,當我們無法取得一些成果的時候,對興趣和自信的打擊都會非常的大。

首先在做這件事情之前,你需要為自己找到足夠開始這件事情的理由,也就是要有強大的內驅力。

就好比考研這件事情,如果是身邊的人要考,所以你也要考,那麼我覺得你到底要不要考研這件事情值得再思考一下。

但如果是說你想通過考研,來改變自己的人生軌跡,想要獲得更高的起點,那麼,我覺得這個事兒十有八九你是能堅持下去的。

好比學習 Python ,好像身邊的人都在學,那我也要了解一下,和那種我想要學 Python 來換一份工作,擺脫目前的工作狀態,獲取更高的薪水。

大家可能看着沒什麼感覺,但是想一想,自己的人生中,到底有沒有過堅持某一件事情,並最終獲得了一個還不錯的結果,最後收穫的這份快感,是不是無與倫比的。

當然,除了強大的內驅力以外,小編還可以友情提供一點小技巧。

獲取階段性的正向反饋。

如果一件事情需要耗費較長的時間,那麼再大的內驅力也可能會被時間的流逝給磨平了。

靜靜思考下放棄的原因,沒有獲得成就感。

還是拿考研舉例子,一般考研需要準備大半年到一年左右的時間,很多人都堅持不過半年時間就放棄掉了,為什麼,因為他們看不到成效,看不到曙光。但是高三的高考很多人還是能堅持下來的,為什麼?

因為高三有月考啊,每次月考完,都能準確的知道自己的水平提升了多少,自己一個月的努力是沒有白費的,自己一個月的努力是真真實實的化成了卷子上的分數。

所以,做一件需要長時間奮戰的事情,最好能提前為自己設定一些階段性的成果檢驗方式。

時間管理

時間管理是一個繞不開的話題,這裏小編其實也沒資格談這件事情,因為小編本身的時間管理也做的並不好,小編也是人,加完班也會感覺到累,回到家也會只想着休息,人非聖賢,對吧。

還是分享一些經驗吧。

嘗試將自己一天做的事情和耗費的時間列一個表格出來(小編之前列舉過,時間有些久遠,找不到了,這裏就不放圖了)。

當這個表格列出來以後,不管多麼自律的人,肯定會發現,一天之中,有相當部分的時間是被浪費掉的,比如刷朋友圈,刷抖音,刷微博。

在生活中,肯定會經常性的出現這樣事情。

“再玩5分鐘手機就睡覺!”

結果12點了還在玩手機。

周末早晨起來,“先玩一局遊戲,在做xxx”。

結果就是玩到了下午。

相信我,那些墮落的人,並不是一開始就想墮落的。

他們只是在被生活中形形色色的誘惑給誘惑到了,因為現在的社會,各個 APP 在掏空了心思去搶佔用戶的留存時長,它們費勁心力的去討好用戶,讓用戶用最簡單最不需要付出的方式去獲得這種毫無意義的低成本的快樂。

所以,開始記錄自己的時間,是做時間管理的第一步。

當然,並不是要我們完全的放棄娛樂時間,這不可能,人不是機器,不可能是只要有電,就能工作,人也是需要休息的,記錄時間只是為了讓我們在面臨選擇的時候,做出正確的選擇。

第一次寫這種長篇內容分享,有內容不當的地方請各位同學海涵。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包”嚨底家”

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

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

小三通海運與一般國際貿易有何不同?

小三通快遞通關作業有哪些?

SpEL + AOP實現註解的動態賦值.

一、自定義註解

先聊聊這個需求,我需要根據用戶的權限對數據進行一些處理,但是痛點在哪裡呢?用戶的權限是在請求的時候知道的,我怎麼把用戶的權限傳遞給處理規則呢?想了以下幾種方案:

  1. Mybatis 攔截器:如果你的權限參數可以滲透到 Dao 層,那麼這是最好的處理方式,直接在 Dao 層數據返回的時候,根據權限做數據處理。
  2. Dubbo 過濾器:如果 Dao 層沒辦法實現的話,只好考慮在 service 層做數據處理了。
  3. ResponseBodyAdvice :要是 service 層也沒辦法做到,只能在訪問層數據返回的時候,根據權限做數據處理。(以下介紹的正是這種方式)

那麼現在有個難點就是:我怎麼把 request 的權限參數傳遞到 response 中呢?當然可以在 Spring 攔截器中處理,但是我不想把這段代碼侵入到完整的鑒權邏輯中。突然想到,我能不能像 spring-data-redis 中 @Cacheable 一樣,利用註解和 SpEL 表達式動態的傳遞權限參數呢?然後在 ResponseBodyAdvice 讀取這個註解的權限參數,進而對數據進行處理。

首先,我們需要有個自定義註解,它有兩個參數:key 表示 SpEL 表達式;userType 表示權限參數。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ResponseSensitiveOverride {

    /**
     * SPEL 表達式
     *
     * @return
     */
    String key() default "";

    /**
     * 1:主賬號、2:子賬號
     */
    int userType() default 1;
}

然後,把這個註解放在路由地址上,key 寫入獲取權限參數的 SpEL 表達式:

    @ResponseSensitiveOverride(key = "#driverPageParam.getUserType()")
    @RequestMapping(value = "/queryPage", method = RequestMethod.POST)
    public ResponseData<PageVo<AdminDriverVo>> queryPage(@RequestBody AdminDriverPageParam driverPageParam) {
        return driverService.queryPageAdmin(driverPageParam);
    }

二、SpEl + AOP 註解賦值

現在 SpEL 表達式是有了,怎麼把 SpEL 表達式的結果賦值給註解的 userType 參數呢?這就需要用 、 和 的知識。

@Aspect
@Component
public class SensitiveAspect {

    private SpelExpressionParser spelParser = new SpelExpressionParser();

    /**
     * 返回通知
     */    
    @AfterReturning("@annotation(com.yungu.swift.base.model.annotation.ResponseSensitiveOverride) && @annotation(sensitiveOverride)")
    public void doAfter(JoinPoint joinPoint, ResponseSensitiveOverride sensitiveOverride) throws Exception {
        //獲取方法的參數名和參數值
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        List<String> paramNameList = Arrays.asList(methodSignature.getParameterNames());
        List<Object> paramList = Arrays.asList(joinPoint.getArgs());

        //將方法的參數名和參數值一一對應的放入上下文中
        EvaluationContext ctx = new StandardEvaluationContext();
        for (int i = 0; i < paramNameList.size(); i++) {
            ctx.setVariable(paramNameList.get(i), paramList.get(i));
        }

        // 解析SpEL表達式獲取結果
        String value = spelParser.parseExpression(sensitiveOverride.key()).getValue(ctx).toString();
        //獲取 sensitiveOverride 這個代理實例所持有的 InvocationHandler
        InvocationHandler invocationHandler = Proxy.getInvocationHandler(sensitiveOverride);
        // 獲取 invocationHandler 的 memberValues 字段
        Field hField = invocationHandler.getClass().getDeclaredField("memberValues");
        // 因為這個字段是 private final 修飾,所以要打開權限
        hField.setAccessible(true);
        // 獲取 memberValues
        Map memberValues = (Map) hField.get(invocationHandler);
        // 修改 value 屬性值
        memberValues.put("userType", Integer.parseInt(value));

    }
}

通過這種方式,我們就實現了為註解動態賦值。

三、ResponseBodyAdvice 處理數據

現在要做的事情就是在 ResponseBody 數據返回前,對數據進行攔截,然後讀取註解上的權限參數,從而對數據進行處理,這裏使用的是 SpringMVC 的 ResponseBodyAdvice 來實現:

@Slf4j
@RestControllerAdvice
@Order(-1)
public class ResponseBodyAdvice implements org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice {

    private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return SysUserDto.USER_TYPE_PRIMARY;
        }
    };

    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        if (returnType.hasMethodAnnotation(ResponseSensitiveOverride.class)) {
            ResponseSensitiveOverride sensitiveOverride = returnType.getMethodAnnotation(ResponseSensitiveOverride.class);
            threadLocal.set(sensitiveOverride.userType());
            return true;
        }
        return false;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        if (body != null && SysUserDto.USER_TYPE_SUB.equals(threadLocal.get())) {
            // 業務處理
        }
        return body;
    }
}

題外話,其實我最後還是擯棄了這個方案,選擇了 Dubbo 過濾器的處理方式,為什麼呢?因為在做數據導出的時候,這種方式沒辦法對二進制流進行處理呀!汗~ 但是該方案畢竟耗費了我一個下午的心血,還是在此記錄一下,可能有它更好的適用場景!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

小三通物流營運型態?

※快速運回,大陸空運推薦?

[UWP]通過自定義XamlCompositionBrushBase實現圖片平鋪

1. 什麼是XamlCompositionBrushBase

我早就想試試自定義XamlCompositionBrushBase,但一直沒機會。上一篇文章介紹到,原理很簡單,但每次都要寫這些代碼很繁瑣,正好就用這個作為例子試試XamlCompositionBrushBase。

CompositionBrush靈活多變,它的基本用法如下:

  1. 通過Compositor創建CompositionBrush;
  2. 配置CompositionBrush;
  3. 創建SpriteVisual並將它的Brush設置為CompositionBrush;
  4. 使用 將SpriteVisual設置到某個UIElement的可視化層里。

這些步驟很繁瑣,而且不能用在XAML中。XamlCompositionBrushBase提供了將CompositionBrush用在XAML中一個橋樑,他繼承自Brush類,可以直接像普通的XAML 畫筆(如SolidColorBrush)那樣直接用在XAML中。

如上圖所示,中已經提了很不少XamlCompositionBrushBase的實現,它們的使用方式已經有很多文章介紹,這裏不一一列舉。

2. 自定義XamlCompositionBrushBase

這篇文章將介紹一個自定義的畫筆:TiledImageBrush,它的主要目標是實現ImageBrush沒有的圖片平鋪功能,並且它可以在XAML中使用,使用方式如下:

<Rectangle IsHitTestVisible="False">
    <Rectangle.Fill>
        <controls:TiledImageBrush Source="ms-appx:///Assets/flutter.png"/>
    </Rectangle.Fill>
</Rectangle>

順便複習下普通的ImageBrush的用法:

<Rectangle >
    <Rectangle.Fill>
        <ImageBrush ImageSource="ms-appx:///Assets/flutter.png"/>
    </Rectangle.Fill>
</Rectangle>

看起來TiledImageBrush的用法是不是和ImageBrush很像?接下來講解TiledImageBrush的實現步驟。TiledImageBrush繼承自XamlCompositionBrushBase,而實現XamlCompositionBrushBase的一般步驟如下:

protected override void OnConnected()
{
    // Delay creating composition resources until they're required.
    if (CompositionBrush == null)
    {
         CompositionBrush = CreateCompositionBrush();//Create A CompositionBrush.
    }
}

protected override void OnDisconnected()
{
    // Dispose of composition resources when no longer in use.
    if (CompositionBrush != null)
    {
        CompositionBrush.Dispose();
        CompositionBrush = null;
    }
}

首先重寫,當畫筆在屏幕上首次用於繪製元素時會調用這個函數。在這個函數里創建CompositionBrush並賦值給。

然後重寫,它在畫筆不再用於繪製任何元素時被調用。在這個函數里盡可能地釋放各種資源,例如CompositionBrush。這兩步就是實現XamlCompositionBrushBase的基本步驟。

創建CompositionBrush有很多種玩法,我之前寫過兩篇文章分別介紹 及 。這裏使用這篇文章里介紹到的代碼,首先使用LoadedImageSurface.StartLoadFromUri創建CompositionSurfaceBrush,然後加入到BorderEffect里實現圖片平鋪,然後把產生的CompositionEffectBrush賦值給XamlCompositionBrushBase.CompositionBrush

TiledImageBrush中添加了Source屬性用於設置圖片Uri(實際上是個ImageSource類型),模仿ImageBrush,這裏的Source也是一個ImageSource類型的屬性,雖然實際上使用的是它的UriSource。詳細代碼如下:

public ImageSource Source
{
    get => (ImageSource)GetValue(SourceProperty);
    set => SetValue(SourceProperty, value);
}

private void UpdateSurface()
{
    if (Source != null && _surfaceBrush != null)
    {
        var uri = (Source as BitmapImage)?.UriSource ?? new Uri("ms-appx:///");
        _surface = LoadedImageSurface.StartLoadFromUri(uri);
        _surfaceBrush.Surface = _surface;
    }
}

OnConnected的詳細代碼如下:

protected override void OnConnected()
{
    base.OnConnected();

    if (CompositionBrush == null)
    {
        _surfaceBrush = Compositor.CreateSurfaceBrush();
        _surfaceBrush.Stretch = CompositionStretch.None;

        UpdateSurface();

        _borderEffect = new BorderEffect()
        {
            Source = new CompositionEffectSourceParameter("source"),
            ExtendX = Microsoft.Graphics.Canvas.CanvasEdgeBehavior.Wrap,
            ExtendY = Microsoft.Graphics.Canvas.CanvasEdgeBehavior.Wrap
        };

        _borderEffectFactory = Compositor.CreateEffectFactory(_borderEffect);
        _borderEffectBrush = _borderEffectFactory.CreateBrush();
        _borderEffectBrush.SetSourceParameter("source", _surfaceBrush);
        CompositionBrush = _borderEffectBrush;
    }
}

這樣一個基本的XamlCompositionBrush就完成了,完整的代碼可以在這裏查看:

3. 參考

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

【其他文章推薦】

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

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

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

台灣寄大陸海運貨物規則及重量限制?

大陸寄台灣海運費用試算一覽表

台中搬家,彰化搬家,南投搬家前需注意的眉眉角角,別等搬了再說!