【從今天開始好好學數據結構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 是電子產業重要的元件?

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

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

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

※專營大陸快遞台灣服務

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

計算機網絡原理超詳解

計算機網絡重磅來襲——一文讓你撥開迷霧,直擊網絡原理

前言

為了保持學習網絡編程的連貫性和系統性,我在之前已經更新了《》《》,我只能說寫的確實很詳細,能讓你全盤吸收,不過你要沒看也不要緊,我會在此篇再來一個前情回顧,不會像上兩篇那樣詳細,但也能讓你理解。

一、前情回顧——計算機網絡概述

時代不同了,大家現在生活都好了,家家戶戶都有電腦了,而且我們都習慣了使用電腦辦公,打打遊戲,聊聊天。那我們來想一個事兒:如果沒有了網絡,我們是不是就不能使用電腦進行聊天了啊。那網絡到底是如何幫助我們來完成網絡聊天的?此篇我就跟大家繼續聊計算機網絡到底是怎麼回事兒。

這回我和以往反着來,先說官方定義,再解釋:

計算機網絡是由通信介質地理位置不同的且相互獨立的計算機連接起來,實現數據通信資源共享

我們知道兩個獨立且毫不相關的計算機,一台在青海,一台在河南,想要進行數據傳輸(聊天),沒網是不行的。這個網指的就是互聯網(Internet)。這個Internet它是一個通信協議。什麼是協議?打個比方,就好比我們打電話,在中國,中國有十幾億人,地大物博,全國各地都有自己的方言,還有些地方使用自己的民族語言,這時候想要良好的溝通就必須使用統一的一個標準,就是普通話。大家都講普通話,溝通起來就沒有問題了。那如果是不同國家進行溝通呢?我們可以選擇使用英語進行交流,那英語就是全世界國家通用的一個標準,計算機就好比是分佈在全世界各個角落的人,計算機之間通話也要找一個統一的標準,這個標準就是Internet標準,又叫做Internet協議。

二、網絡介紹及分類之隔壁老王的故事

先給大家講一個虛構版隔壁老王的故事:
有一個人叫隔壁老王,他有一個愛好就是看電影。有一天,這個隔壁老王想看一部電影,可是電腦裏面存儲的電影太多了,他費了老大勁才从里面找到,覺得很不爽。於是他想,我能不能把所有電影做個分類,把同一種類型的電影放在同一個文件夾下,然後把所有的文件夾整合在一起,自己寫個瀏覽器軟件,把文件夾信息放到瀏覽器上,到時候找電影的時候就好找了,只要找到相應的文件夾直接點進去就能找到電影了,老王說干就干,沒多久就把所有的文件夾整理好了,然後把所有文件夾放到同一個頁面上,到時候他想點哪個就點哪個,So easy!(那些網站可能就是這樣來的)。

這回出來一個新人物,就叫小王吧,不是小王八,老王就是住在小王隔壁,有一天小王看到老王電腦上有那麼多電影,就跟老王商量,能不能在他電腦上也弄個跟老王一樣的,讓他也看看。老王也不是個小氣的人,好東西就是要分享的嘛,想都沒想就答應了,給小王說在你電腦上插根線接到我電腦上,然後下載我這個軟件,直接訪問我的電腦就行了。小王很高興,他馬上回家按老王說的做,沒多久他的電腦上也能看老王電腦上的東西了。有了小王,就還會有小張,小李,互相插根網線,他們都能互相共享電影了。這樣,局域網也就產生了,就比如在一個公司或者一個學校用的網絡,都稱為局域網。那學校有很多呀,不同的學校用的局域網是不同的,不同城市的有不同的局域網,以局域網為單位,散布在一個區或一個城市的各個局域網加一起叫城域網,然後全世界所有城市的城域網加一起就叫廣域網

過了一段時間,老王去小王家,看到小王電腦里的電影比自己的還多,而且還好看,然後老王跟小王說,把你的這些電影給我一份吧,我也想看看。不用想,小王肯定立馬答應了,說你自己弄吧,想看哪個隨便看。姜還是老的辣,老王想到一個好辦法,他跟小王說,我再找一台電腦,把我倆電腦上的資源全部放到那台電腦里,我倆只要在那個電腦上插根線連到自己電腦上,就都能訪問那電腦上的東西了,這樣豈不是很方便。小王一拍腦門,呀!我咋就沒想到,那還不趕緊的。老王另外找了一台電腦,然後他倆把自己的電腦和那台電腦用線連起來,再把自己電腦里的所有資源全傳進那台電腦中,最後他倆就能共享資源了。(那台電腦就是服務器)(附加內容)

總結一下就是:

 

網絡按地域分類:根據參照物不同、類型不同分為

  • 局域網:一個公司、一個家庭、一個學校······
  • 城域網:一個地區、一個城市······
  • 廣域網:一個國家、全世界······

三、互聯網協議是如何分佈和設計的

我在上面說了Internet協議,互聯網協議按照功能的不同,分為osi七層tcp/ip五層tcp/ip四層協議。如下圖:

osi的七層協議體繫結構的概念清楚,理論也比較完善,但它既複雜又不實用,ISO制定的osi協議參考模型的過於龐大、複雜招致了許多批評。於此對照,由技術人員自己開發的TCP/IP協議獲得了更為廣泛的應用。因此,我們只需要弄明白TCP/IP五層協議就能了解和明白計算機最底層的通信是怎麼回事。

 

四、TCP/IP五層協議

如圖,從最下方的物理層到最上方的應用層,對於我們用戶而言,最直接的是應用層。從上到下每一層都依賴於下一層,所以我從最下一層開始給大家講解:

 

注意:每一層都運行着一個特定的協議,共同組合成互聯網協議

一、物理層

物理層主要是由雙絞線光纜電纜無線電波組成,其作用很簡單,就是連接不同的計算機,並傳遞底層電信號,高電壓:1,低電壓:0。

二、數據鏈路層

我們從物理層上接收或者發送單純的0、1是沒有意義的,為什麼呢?想想哈,我想給女朋友發送一句話:“你好漂亮”,那我們要把“你好漂亮”轉換成01之後,交給網卡,網卡就懵逼了,發給誰???不知道。那怎麼辦?必須要確定數據發給誰。就像我們的快遞一樣,是不是在外層包裝上有商家地址和個人地址,這樣我們不管是還是,都能準確定位了。網絡傳送數據也一樣,我們就在數據前面加上目標地址,為了能接收到回信,也要把自己的地址也加上。但是,如果數據和地址放在一起,又亂了,比如,我給你一堆01,1010101000101010101,你也分不清哪裡是數據,哪裡是地址。這時我們就要對要發送的01進行分組,規定前面xxx位是地址,後面xxx位是數據,並且,大家想互相都能通信,就必須都遵守這樣的規則(協議),這個協議叫以太網協議。在以太網協議出現之前,各個公司都有自己的分組規則,後來都統一使用以太網協議了。

以太網協議規定:一組電信號構成一個數據包,叫,每一幀分為報頭(head)和數據(data)兩部分。


報頭(head):固定18個字節

發送者/
源地址: 6個字節

接收者/
目標地址: 6個字節


數據類型:6個字節


數據(data):最短46個字節,最長1500字節

數據包的具體內容(發送給女朋友的話/快遞貨物)

 

以太網協議中的地址叫MAC地址,MAC地址是每台計算機唯一的物理地址,是被寫在網卡上的。以太網協議規定,每一台接收和發送數據的設備必須要裝有網卡,負責發送和接收數據的設備,發送端和接收端的地址,指的就是網卡的地址,即MAC地址

MAC地址

MAC地址是每個網卡在出廠的時候,由各個廠商直接燒錄在網卡上的,而且,這個地址必須是全世界唯一的。
MAC地址是由12位16進制的數字錶示(前六位是廠商編號,后六位是流水線號),這樣不同的廠商之間就不會產生衝突了,自己生產自己的就好了。

交換機

在這給大家介紹一個東西,我們說兩個電腦要通信要先連根線,但是如果電腦多了之後,電腦間通信連的線也就多了,這樣太亂了,我介紹的這個東西就是交換機,它是負責組件局域網,研究的是MAC地址,它有什麼用你看下面圖片中的接口就知道了

有了MAC地址,以太網就可以進行工作了.理論上講,我們可以和世界上的每一台連接了互聯網的計算機進行通信了,此時通信的方案是:廣播

廣播

廣播又是怎麼一回事?其實廣播的方式很原始,基本通信就是靠吼。就像你想跟女朋友求婚一樣,你會大喊:“xxx,嫁給我吧”,旁邊能聽到這句話的人有很多,但是只有你女朋友會回復你。其他人會把你當傻逼一樣看待。
沒錯,廣播就是這樣進行通信的。首先組織好了一個數據包之後,把這個數據包通過電信號發出去,這時整個網絡上所有的人都會收到你發的這條數據,然後看看這個數據是不是自己的。如果不是就當他不存在,如果是,就接收。雖然效率低點,但畢竟能通信了。

廣播帶來什麼問題呢?如果是在一個小的網絡環境里。比方說,你們宿舍幾個人,一起玩CS,沒問題,你喊一嗓子,你室友也能回應你,也就效率低點。但是如果你連接到全世界的互聯網上,還使用廣播的方式來通信,就不是效率問題了,而是一個巨大的通信災難。全世界60多億人,每個人吼一嗓子,每個人發送一條信息,那每個人都會收到60多億條信息,網絡瞬間癱瘓,這種問題被稱為廣播風暴,那如何解決呢?

三、網絡層

首先,我們要了解一個事情,世界大網絡(廣域網)是由一個一個的互相隔離的小型局域網(子網)組成的,不同的局域網之間使用路由來連接。

 

路由器

上面說的交換機是負責組建局域網,研究的是MAC地址,而路由器是負責組件廣域網,研究的是IP地址,這個IP地址下面我再解釋。

剛才說的廣播,只能在一個局域網內進行通信,不可以在大網絡上進行廣播,有了路由器,就避免了廣播風暴的問題。每個局域網被稱為一個廣播域,局域網和局域網之間使用路由的方式進行通信(向不同的廣播域/子網發送數據包),用路由器把一個局域網裡的所有計算機劃分成一個個子網。

 

現在大家先想一想生活中,如果是之前說的廣播,就相當於在一間教室里你正在上課,你要給某個女孩傳紙條,那你要在紙條外面寫上你和你要傳的女孩的名字,再在裏面寫上你要寫的話,寫好后折起來,你和女孩的名字在外,內容在內,然後讓同學一個一個幫你傳,每個同學在傳的時候都會看外面寫的名字,如果不是他,就繼續傳,直到傳到的人是你寫的那個女孩為止。那這間教室就相當於一個局域網。好,現在我們要實現局域網和局域網之間的通信,比如此時位於青海的你要給河南的朋友送東西,你不可能用廣播的方式了,你只能快遞,這個青海和河南以及還有很多不同的省份是我國的土地劃分,每一個省也可以看作是一個個局域網,那在網絡中,我國就相當於廣域網,那不同的局域網是如何劃分的呢?MAC地址是沒辦法區分的,因為MAC地址上只有廠商的流水號,這就引出我要說的網絡層,網絡層引出了一套新的地址來區分不同的局域網/子網,這一套地址就是網絡地址。

規定網絡地址的協議叫IP協議,它定義的地址叫IP地址。其實跟我國的省份名差不多,繼續說送快遞,你要把送的東西包裝好,在外面寫上你自己的地址和省份地址,還有朋友的地址和省份地址,交給青海的快遞公司,然後青海的快遞公司轉交給河南的快遞公司,最後由河南的快遞公司分配給你朋友,你朋友就收到快遞了。在這有兩點需要注意:

  • 你要同時寫兩個地址,自己的地址(MAC地址)和省份地址(IP地址),這樣就能確定所要接收和發送人的具體的位置了。
  • 青海的快遞公司和河南的快遞公司就相當於路由器
子網掩碼

在這給大家普及一下子網掩碼,我們上面說有了IP地址MAC地址,我們就能讓任何計算機之間進行通信了,那現在再想想,如果我要用我的計算機給另外一台計算機實現通信,我是不是要判斷要通信的計算機是否和我的計算機在同一個IP地址中,相當於上面例子中我和我要送東西的朋友是否在一個省,這個時候就需要用子網掩碼,我拿着我的IP地址和對方的子網掩碼通過計算,判斷是否在同一個IP地址下,如果在同一個IP地址下,我可以用廣播的形式進行通信,如果不在同一個IP地址下,我可以先把數據傳給我方的路由器,再由我方的路由器把數據傳給對方的路由器,最後由對方的路由器把數據傳給要接收數據的計算機。這樣說,再聯繫上面已經說過的,我想不難理解了。

IP地址

目前我們普遍使用的是IPV4,它規定,一個網絡地址由32位二進制組成,把32位平均分成四份,每一份8位,8位最大能表示的數據是255,所以IP地址的範圍:0.0.0.0-255.255.255.255.

一個IP地址分為兩部分,分別是網絡位主機位
網絡位用來標識不同的子網
主機位用來標識子網下主機的編號

為什麼要分兩部分呢?很簡單,就好比你想寫信給你的女朋友,假設你女朋友的地址是西寧市平安路128號,那麼網絡位就會直接找到西寧市,主機位幫你找到你的女朋友。

網絡位和主機位是如何劃分的?使用子網掩碼來劃分。子網掩碼和IP地址差不多,都是由32位二進制數來表示,子網掩碼也分為網絡部分主機部分,網絡部分由1組成,主機部分由0組成。

那說了這麼多,IP協議是如何發送數據的?協議規定,IP協議使用IP數據包進行發送數據。IP數據包同樣把數據分為了兩部分,headdata,並且在發送數據的時候,直接用IP數據包直接裝載以太網的data部分。

head:長度為20到600字節
data:最長為65515字節
而以太網數據包的“數據”部分,最長只有1500字節,因此,如果IP數據包超過了1500字節,它就需要分割成幾個以太網數據包,分開發送了。

 

再回顧一下啊,以太網的頭是包含了自己的MAC地址目標MAC地址的,那如何查找目標MAC地址呢?前輩們弄了一個叫ARP協議的東西專門來解析目標MAC地址。它是如何工作的?首先,它是數據鏈路層的東西,在我們發送一個數據包的時候是包含着對方的IP地址的。例如我(172.13.4.58)想發送一條數據給女朋友(172.13.4.90),首先,我得先拿到女朋友的MAC地址才可以通信。此時,我們會先計算一下我和女朋友是否在一個子網內(子網掩碼)

  • 在一個子網內,直接廣播發送一個數據包

    子網內的計算機發現了這個包之後會返回一個數據包並且帶有MAC地址,這樣就通過IP地址找到了目標主機的MAC地址,接下來就可以進行數據傳輸了。

  • 不在一個子網內,單純的用廣播就不行了,因為廣播只是針對自己內網而言。那怎麼辦,此時會把數據包發給網關,由網關發給其它路由,這樣在整個萬維網裡就可以找到你想要的那個計算機的MAC地址了。
總結
  • ARP就是通過IP地址來查找MAC地址的一套固定協議,它是數據鏈路層的內容。
  • 網絡層的意義:定義了子網, 區分各個局域網
  • IP地址:網絡地址
  • 子網掩碼:計算是否是同一個子網

四、傳輸層

到目前為止,前三層內容已經可以進行數據傳輸了。但是,我們的一台計算機上可以一次性運行多個網絡應用程序,比如,QQ、微信、LOL三個軟件一起運行,都要進行網絡傳輸,但是就前面學習的這三層內容,是沒辦法區分開數據是要發送給哪一個軟件的。那怎麼辦?引入第四層,傳輸層,傳輸層定義了端口的概念,每一個網絡應用程序佔用一個網絡端口,不同的程序就用端口把數據隔離,兩兩互相不影響。

  • 端口:應用程序和網卡的關聯編號
  • 傳輸層:建立端口到端口的通信。
  • 傳輸層有兩種協議:TCPUDP
TCP協議

TCP協議:可靠傳輸,TCP數據包沒有長度限制,理論上可以無限長,但是為了保證網絡的效率,通常TCP數據包的長度不會超過IP數據包的長度,以確保單個TCP數據包不必再分割。

TCP頭放的主要是
源端口
目標端口

 

UDP協議

UDP協議:不可靠傳輸,“報頭”部分一共只有8個字節,總長度不超過65535字節,正好放進一個IP數據包。

其實和郵信是一樣的,寫好地址,寫好接收人,直接裝進信封里,丟進郵箱里就不用你管了,對方什麼時候收,收沒收到,你不知道。

 

五、應用層

用戶使用的都是應用程序,均工作於應用層,大家都可以開發自己的應用程序,數據多種多樣,必須規定好數據的組織形式。對於用於而言最直觀的就是應用層。

  • 應用層:規定應用程序的數據格式

:TCP協議可以為各種各樣的程序傳遞數據,比如Email、WWW、FTP等,那麼,必須有不同協議規定电子郵件、網頁、FTP數據的格式,這些應用程序協議就構成了“應用層”。

總結

以上是對TCP/IP5層協議的解讀,總結一下:

發送數據其實就是一個封裝數據的過程

最後從物理層發出,對方接收到了之後再自下而上一層一層打開拿到數據,以上內容就是一個網絡傳輸的大致過程,其中還有好多細節沒有闡述,但大家知道和了解以上內容,對開發而言足夠了。

 

結束

本來還想加上TCP協議的三次握手四次斷開,奈何考慮到篇幅過大,對讀者不利,權衡再三,決定單獨去寫這部分。

看完有收穫?那麼希望老鐵別吝嗇你的三連擊哦
1、點個推薦,讓更多的人看到這篇文章
2、關注我的原創微信公眾號【泰斗賢若如】,第一時間閱讀我的文章
3、歡迎關注我的博客

 【原創聲明】:本人原創:

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

【其他文章推薦】

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

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

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

大陸寄台灣空運注意事項

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

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

[ASP.NET Core 3框架揭秘] 文件系統[2]:總體設計

在《》中,我們通過幾個簡單的實例演示從編程的角度對文件系統做了初步的體驗,接下來我們繼續從設計的角度來進一步認識它。這個抽象的文件系統以目錄的形式來組織文件,我們可以利用它讀取某個文件的內容,還可以對目錄或者文件實施監控並及時得到變化的通知。由於IFileProvider對象提供了針對文件系統變換的監控功能,在.NET Core下里類似的功能大都利用一個IChangeToken對象來實現,所以我們在對IFileProvider進行深入介紹之前有必要先來了解一下IChangeToken。

一、IChangeToken

從字面上理解的IChangeToken對象就是一個與某組監控數據關聯的“令牌(Token)”,它能夠在檢測到數據改變的時候及時地對外發出一個通知。如果IChangeToken關聯的數據發生改變,它的HasChanged屬性將變成True。我們可以調用其RegisterChangeCallback方法註冊一個在數據發生改變時可以自動執行的回調,該方法會返回一個IDisposable對象,我們通過用其Dispose方法解除註冊的回調。至於IChangeToken接口的另一個屬性ActiveChangeCallbacks,它表示當數據發生變化時是否需要主動執行註冊的回調操作。

public interface IChangeToken
{
    bool HasChanged { get; }
    bool ActiveChangeCallbacks { get; }
    IDisposable RegisterChangeCallback(Action<object> callback, object state);
}

.NET Core提供了若干原生的IChangeToken實現類型,我們最常使用的是一個名為CancellationChangeToken的實現。CancellationChangeToken的實現原理很簡單,它基本上就是按照如下的形式藉助我們熟悉的CancellationToken對象來發送通知。

public class CancellationChangeToken : IChangeToken
{
    private readonly CancellationToken _token;
    public CancellationChangeToken(CancellationToken token)  => _token = token;
    public bool HasChanged  => _token.IsCancellationRequested; 
    public bool ActiveChangeCallbacks  => true;    
    public IDisposable RegisterChangeCallback(Action<object> callback, object state)  => _token.Register(callback, state);
}

除了CancellationChangeToken,有時也我們也會使用到一個名為CompositeChangeToken的實現。顧名思義,CompositeChangeToken代表由多個IChangeToken組合而成的複合型IChangeToken對象。如下面的代碼片段所示,我們在調用構造函數創建一個CompositeChangeToken對象的時候,需要提供這些IChangeToken對象。對於一個CompositeChangeToken對象來說,只要組成它的任何一個IChangeToken發生改變,其HasChanged屬性將會變成True,而註冊的回調自然會被執行。至於ActiveChangeCallbacks屬性,只要任何一個IChangeToken的同名屬性返回True,該屬性就會返回True。

public class CompositeChangeToken : IChangeToken
{
    public bool  ActiveChangeCallbacks { get; }
    public IReadOnlyList<IChangeToken>  ChangeTokens { get; }
    public bool  HasChanged { get; }
   
    public CompositeChangeToken(IReadOnlyList<IChangeToken> changeTokens);   
    public IDisposable RegisterChangeCallback(Action<object> callback, object state);   
}

我們可以直接調用IChangeToken提供的RegisterChangeCallback方法來註冊在接收到數據變化通知后的回調操作,但是更常用的方式則是直接調用靜態類型ChangeToken提供的如下兩個OnChange方法重載來進行回調註冊,這兩個方法的第一個參數需要被指定為一個用來提供IChangeToken對象的Func<IChangeToken>委託。

public static class ChangeToken
{
    public static IDisposable OnChange(Func<IChangeToken> changeTokenProducer,  Action changeTokenConsumer) ;
    public static IDisposable OnChange<TState>(Func<IChangeToken> changeTokenProducer,  Action<TState> changeTokenConsumer, TState state) ;
}

二、IFileProvider

在了解了IChangeToken是怎樣一個對象之後,我們將關注轉移到文件系統的核心接口IFileProvider上,該接口定義在NuGet包“Microsoft.Extensions.FileProviders.Abstractions”中。我們在《》做了幾個簡單的實例演示,它們實際上體現了文件系統承載的三個基本功能,而這三個基本功能分別體現在IFileProvider接口如下所示的三個方法中。

public interface IFileProvider
{    
    IFileInfo GetFileInfo(string subpath);
    IDirectoryContents GetDirectoryContents(string subpath);
    IChangeToken Watch(string filter);
}

三、IFileInfo

雖然文件系統採用目錄來組織文件,但是不論是目錄還是文件都通過一個IFileInfo對象來表示,至於具體是目錄還是文件則通過IFileInfo的IsDirectory屬性來確定。對於一個IFileInfo對象,我們可以通過只讀屬性Exists判斷指定的目錄或者文件是否真實存在。至於另外兩個屬性NamePhysicalPath,它們分別表示文件或者目錄的名稱和物理路徑。屬性LastModified返回一個時間戳,表示目錄或者文件最終一次被修改的時間。對於一個表示具體文件的IFileInfo對象來說,我們可以利用屬性Length得到文件內容的字節長度。如果我們希望讀取文件的內容,可以藉助於CreateReadStream方法返回的Stream對象來完成。

public interface IFileInfo
{
    bool Exists { get; }
    bool  IsDirectory { get; }
    string Name { get; }
    string PhysicalPath { get; }
    DateTimeOffset LastModified { get; }
    long Length { get; }

    Stream CreateReadStream();
}

IFileProvider接口的GetFileInfo方法會根據指定的路徑得到表示所在文件的IFileInfo對象。換句話說,雖然一個IFileInfo對象可以用於描述目錄和文件,但是GetFileInfo方法的目的在於得到指定路徑返回的文件而不是目錄(我個人不太認同這種令人產生歧義的API設計)。一般來說,不論指定的文件是否存在,該方法總會返回一個具體的IFileInfo對象,因為目標文件的存在與否是由該對象的Exists屬性來確定的。

四、IDirectoryContents

如果希望得到某個目錄的內容,比如需要查看多少文件或者子目錄包含在這個目錄下,我們可以調用IFileProvider對象的GetDirectoryContents方法並將所在目錄的路徑作為參數。目錄內容通過該方法返回的IDirectoryContents對象來表示。如下面的代碼片段所示,一個IDirectoryContents對象實際上是一組IFileInfo對象的集合,組成這個集合的所有IFileInfo自然就是對包含在這個目錄下的所有文件和子目錄的描述。和GetFileInfo方法一樣,不論指定的目錄是否存在,GetDirectoryContents方法總是會返回一個具體的IDirectoryContents對象,它的Exists屬性會幫助我們確定指定目錄是否存在。

public interface IDirectoryContents : IEnumerable<IFileInfo>
{
    bool Exists { get; }
}

五、監控目錄或者文件更新

如果我們希望監控IFileProvider所在目錄或者文件的變化,我們可以調用它的Watch方法,當然前提是對應的IFileProvider對象提供了這樣的監控功能。這個方法接受一個字符串類型的參數filter,我們可以利用這個參數指定一個針對“文件匹配模式(File Globing Pattern)”表達式(以下簡稱Globing Pattern表達式)來篩選需要監控的目標目錄或文件。

Globing Pattern表達式比正則表達式簡單多了,它只包含“*”一種“通配符”,如果硬說它包含兩種通配符的話,那麼另一個通配符是“**”。Globing Pattern表達式體現為一個文件路徑,其中“*”代表所有不包括路徑分隔符(“/”或者“\”)的所有字符,而“**”則代表包含路徑分隔符在內的所有字符。下錶給出了幾個典型的Globing Pattern表達式和它們代碼的文件匹配語義。






















Globing Pattern表達式

匹配的文件

src/foobar/foo/settings.*

 

子目錄“src/foobar/foo/”(不含其子目錄)下名為“settings”的所有文件,比如settings.jsonsettings.xmlsettings.ini等。

src/foobar/foo/*.cs

 

子目錄“src/foobar/foo/”(不含其子目錄)下的所有.cs文件。

src/foobar/foo/*.*

子目錄“src/foobar/foo/”(不含其子目錄)下所有文件。

src/**/*.cs

子目錄“src”(含其子目錄)下的所有.cs文件。

一般來說,不論是調用IFileProvider對象的GetFileInfo或GetDirectoryContents方法所指定的目標文件或目錄的路徑,還是調用Watch方法指定的篩選表達式,都是一個針對當前IFileProvider對象映射根目錄的相對路徑。指定的這個路徑可以採用“/”字符作為前綴,但是這個前綴是不必要的。換句話說,如下所示的這兩組程序是完全等效的。

路徑不包含前綴“/”

var dirContents = fileProvider.GetDirectoryContents("foobar");
var fileInfo = fileProvider.GetFileInfo("foobar/foobar.txt");
var changeToken = fileProvider.Watch("foobar/*.txt");

路徑包含前綴“/”

var dirContents = fileProvider.GetDirectoryContents("/foobar");
var fileInfo = fileProvider.GetFileInfo("/foobar/foobar.txt");
var changeToken = fileProvider.Watch("/foobar/*.txt");

總的來說,以IFileProvider對象為核心的文件系統在設計上看是非常簡單的。除了IFileProvider接口之外,文件系統還涉及到其他一些對象,比如IDirectoryContents、IFileInfo和IChangeToken等,下圖所示的UML展示了這些接口以及它們之間的關係。

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

【其他文章推薦】

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

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

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

大陸寄台灣空運注意事項

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

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

印尼省長要整治世界最髒河流 目標可飲用

摘錄自2018年9月20日中央社雅加達報導

西大魯河(Citarum River)全長300公里,是西爪哇省最長河流,是逾3500萬民眾生活用水來源,也是印尼重要的灌溉河流。美國布萊克史密斯研究所(Blacksmith Institute)研究顯示,每天排入西大魯河的化學廢料多達280公噸,有毒化學物質已達驚人程度,河水的鉛含量比美國的安全飲用水標準超出至少1000倍,因此被稱為「全世界最髒的河流」。

西爪哇省新任省長利德宛(Ridwan Kamil)向印尼國家通訊社安塔拉(Antara)表示,他有多管齊下的計畫,可以實現總統佐科威日前訂下的目標,讓西大魯河的水質在2025年前達到飲用標準。

整體計畫先期將先由印尼國營的軍武公司賓達(PT Pindad)設計的疏浚機開路,清理河中數量龐大的垃圾。中程計畫則將引進較佳的系統,用來遏阻和處理大批垃圾被傾倒至西大魯河的問題。

利德宛指出,長期而言,西爪哇省政府希望把周遭工業區及工廠遷移至其他地方。同時也要關注位於雅加達近郊勿加西(Bekasi)的瑪琅河(Malang River)等水路。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※試算大陸海運運費!

南極沉睡巨人熱醒 研究:持續暖化恐融最大冰層

摘錄自2018年9月20日中央社巴黎報導

科學雜誌「自然」(Nature)19日刊登的研究發現,被稱為南極洲「沉睡的巨人」的東南極冰蓋也受到暖化影響。研究分析澳洲南方的威爾克斯冰下盆地(Wilkes Subglacial Basin)上次融化、約12萬5000年前留下的海床沉積層。

專家發現,只要氣溫持續上升攝氏2度,就會使威爾克斯盆地開始又融化。增幅攝氏2度是2015年具里程碑意義的巴黎氣候協定為避免全球暖化失控所設定的上限,最好控制在攝氏1.5度以下。

研究主筆、倫敦帝國學院(Imperial College)的威爾森(David Wilson)表示:「這是我們首次看到直接證據顯示,從過去地質看到這部分的冰層如何受微幅暖化影響。」

團隊發現,如果全球平均氣溫控制在較工業革命前水平上升攝氏2度,這整個盆地就會融化,導致全球海平上升大約4公尺,危及小島國家,影響沿海地區數以億計居民。

照目前溫室氣體排放速度,到了本世紀末,全球氣溫升幅將遠遠超過攝氏2度,可能多達攝氏4至5度。

威爾森表示:「由於目前全球氣溫已較工業時代前高出攝氏1度,如果我們無法減少碳排放,未來冰層融化似乎無可避免。」

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

※專營大陸快遞台灣服務

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

霧霾瀰漫87天 南加州人三個月沒能好好呼吸

摘錄自2018年9月22日蘋果日報美國加州報導

南加州人對今夏的記憶大概就是熱浪、野火、髒空氣。該地區空氣的臭氧含量連續87天超標,據《洛杉磯時報》報導,這創出至少20年來最長的紀錄。環保官員表示,日期長短不是判斷空污程度的指標,但這確實對健康有影響。

臭氧是導致氣喘和其他呼吸疾病發作的原因之一。加州空氣監測數據顯示,該州南部包含洛杉磯郡、橘郡、河濱郡等地,自6月19日至9月13日,空氣中臭氧含量都高於標準值70ppb;上周末雖略有緩和,但並未散退,本周又持續超標。

加州南岸空氣品質管理局(South Coast Air Quality Management District)表示,用連續日期數據衡量臭氧抑制的進展,並不恰當。今年的霧霾季比去年晚開始,目前為止,霧霾狀況沒有去年嚴重,臭氧含量最高值和「非常不健康」的日數都比較少。

去年臭氧超標的總日數為145天,前年為132天。該管理局無法確定最長的連續超標日紀錄,發言人表示,因為那不是美國環保局規定的空氣品質測量方式,所以沒追蹤。監管機構將空氣品質劣化歸咎於天氣變熱,使污染物質被光分解的速度加快;逆溫層較往年持久,污染物不易散開。

環保團體和監督機構都不認同此番解釋。南岸空氣品質委員會成員喬瑟夫盧(Joseph Lyou)表示,無論法律是否以此作為標準,長期空氣不良的趨勢都令人不安。環保團體譴責,官員應該可以有更多政策取締污染源,例如煉油廠或排放廢氣的「烏賊車」。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

大陸寄台灣空運注意事項

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

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

020.掌握Pod-Pod基礎使用

一 Pod定義詳解

1.1 完整Pod定義文件

  1 apiVersion: v1			#必選,版本號,例如v1,版本號必須可以用 kubectl api-versions 查詢到
  2 kind: Pod				#必選,Pod
  3 metadata:				#必選,元數據
  4   name: string			#必選,Pod名稱,需符合RFC 1035規範
  5   namespace: string			#必選,Pod所屬的命名空間,默認為"default"
  6   labels:				#自定義標籤
  7     - name: string			#自定義標籤名字
  8   annotations:			#自定義註釋列表
  9     - name: string
 10 spec:				#必選,Pod中容器的詳細定義
 11   containers:			#必選,Pod中容器列表
 12   - name: string			#必選,容器名稱,需符合RFC 1035規範
 13     image: string			#必選,容器的鏡像名稱
 14     imagePullPolicy: [ Always|Never|IfNotPresent ]	#獲取鏡像的策略,Alawys表示每次都嘗試下載鏡像,IfnotPresent表示優先使用本地鏡像,否則下載鏡像,Nerver表示僅使用本地鏡像
 15     command: [string]		#容器的啟動命令列表,如不指定,使用打包時使用的啟動命令
 16     args: [string]			#容器的啟動命令參數列表
 17     workingDir: string		#容器的工作目錄
 18     volumeMounts:			#掛載到容器內部的存儲卷配置
 19     - name: string			#引用pod定義的共享存儲卷的名稱,需用volumes[]部分定義的的卷名
 20       mountPath: string		#存儲卷在容器內mount的絕對路徑,應少於512字符
 21       readOnly: boolean		#是否為只讀模式,默認為讀寫模式
 22     ports:				#需要暴露的端口庫號列表
 23     - name: string			#端口的名稱
 24       containerPort: int		#容器需要監聽的端口號
 25       hostPort: int		        #容器所在主機需要監聽的端口號,默認與Container相同
 26       protocol: string		#端口協議,支持TCP和UDP,默認TCP
 27     env:				#容器運行前需設置的環境變量列表
 28     - name: string			#環境變量名稱
 29       value: string		        #環境變量的值
 30     resources:			#資源限制和請求的設置
 31       limits:			#資源限制的設置
 32         cpu: string		        #CPU的限制,單位為core數,將用於docker run --cpu-shares參數
 33         memory: string		#內存限制,單位可以為Mib/Gib,將用於docker run --memory參數
 34       requests:			#資源請求的設置
 35         cpu: string		        #CPU請求,容器啟動的初始可用數量
 36         memory: string		#內存請求,容器啟動的初始可用數量
 37     livenessProbe:			#對Pod內各容器健康檢查的設置,當探測無響應幾次后將自動重啟該容器,檢查方法有exec、httpGet和tcpSocket,對一個容器只需設置其中一種方法即可
 38       exec:			        #對Pod容器內檢查方式設置為exec方式
 39         command: [string]		#exec方式需要制定的命令或腳本
 40       httpGet:			#對Pod內個容器健康檢查方法設置為HttpGet,需要制定Path、port
 41         path: string
 42         port: number
 43         host: string
 44         scheme: string
 45         HttpHeaders:
 46         - name: string
 47           value: string
 48       tcpSocket:			#對Pod內個容器健康檢查方式設置為tcpSocket方式
 49          port: number
 50        initialDelaySeconds: 0	#容器啟動完成后首次探測的時間,單位為秒
 51        timeoutSeconds: 0		#對容器健康檢查探測等待響應的超時時間,單位秒,默認1秒
 52        periodSeconds: 0		#對容器監控檢查的定期探測時間設置,單位秒,默認10秒一次
 53        successThreshold: 0
 54        failureThreshold: 0
 55        securityContext:
 56          privileged: false
 57     restartPolicy: [Always | Never | OnFailure]	#Pod的重啟策略,Always表示一旦不管以何種方式終止運行,kubelet都將重啟,OnFailure表示只有Pod以非0退出碼退出才重啟,Nerver表示不再重啟該Pod
 58     nodeSelector: obeject		#設置NodeSelector表示將該Pod調度到包含這個label的node上,以key:value的格式指定
 59     imagePullSecrets:		#Pull鏡像時使用的secret名稱,以key:secretkey格式指定
 60     - name: string
 61     hostNetwork: false		#是否使用主機網絡模式,默認為false,如果設置為true,表示使用宿主機網絡
 62     volumes:			#在該pod上定義共享存儲卷列表
 63     - name: string			#共享存儲卷名稱 (volumes類型有很多種)
 64       emptyDir: {}			#類型為emtyDir的存儲卷,與Pod同生命周期的一個臨時目錄。為空值
 65       hostPath: string		#類型為hostPath的存儲卷,表示掛載Pod所在宿主機的目錄
 66         path: string		#Pod所在宿主機的目錄,將被用於同期中mount的目錄
 67       secret:			#類型為secret的存儲卷,掛載集群與定義的secre對象到容器內部
 68         scretname: string
 69         items:
 70         - key: string
 71           path: string
 72       configMap:			#類型為configMap的存儲卷,掛載預定義的configMap對象到容器內部
 73         name: string
 74         items:
 75         - key: string
 76           path: string

二 Pod的基本用法

2.1 創建Pod


Pod可以由1個或多個容器組合而成,通常對於緊耦合的兩個應用,應該組合成一個整體對外提供服務,則應該將這兩個打包為一個pod。

屬於一個Pod的多個容器應用之間相互訪問只需要通過localhost即可通信,這一組容器被綁定在一個環境中。

  1 [root@k8smaster01 study]# vi frontend-localredis-pod.yaml
  2 apiVersion: v1
  3 kind: Pod
  4 metadata:
  5   name: redis-php
  6   label:
  7     name: redis-php
  8 spec:
  9   containers:
 10   - name: frontend
 11     image: kubeguide/guestbook-php-frontend:localredis
 12     ports:
 13     - containersPort: 80
 14   - name: redis-php
 15     image: kubeguide/redis-master
 16     ports:
 17     - containersPort: 6379
 18 
 19 [root@k8smaster01 study]# kubectl create -f frontend-localredis-pod.yaml
 20 


2.2 查看Pod

  1 [root@k8smaster01 study]# kubectl get pods	                #READY為2/2,表示此Pod中運行了yaml定義的兩個容器
  2 NAME        READY   STATUS    RESTARTS   AGE
  3 redis-php   2/2     Running   0          7m45s
  4 [root@k8smaster01 study]# kubectl describe pod redis-php	#查看詳細信息
  5 


三 靜態Pod

3.1 靜態Pod概述


靜態pod是由kubelet進行管理的僅存在於特定Node的Pod上,他們不能通過API Server進行管理,無法與ReplicationController、Deployment或者DaemonSet進行關聯,並且kubelet無法對他們進行健康檢查。靜態Pod總是由kubelet進行創建,並且總是在kubelet所在的Node上運行。

創建靜態Pod有兩種方式:配置文件或者HTTP方式。

3.2 配置文件方式創建

  1 [root@k8snode01 ~]# mkdir -p /etc/kubelet.d
  2 [root@k8snode01 ~]# vi /etc/kubelet.d/static-web.yaml
  3 apiVersion: v1
  4 kind: Pod
  5 metadata:
  6   name: static-web
  7   label:
  8     name: static-web
  9 spec:
 10   containers:
 11   - name: static-web
 12     image: nginx
 13     ports:
 14     - name: web
 15       containersPort: 80
 16 
 17 [root@k8snode01 ~]# vi /etc/systemd/system/kubelet.service
 18 ……
 19   --config=/etc/kubelet.d/ \·				#加入此參數
 20 ……
 21 [root@k8snode01 ~]# systemctl daemon-reload
 22 [root@k8snode01 ~]# systemctl restart kubelet.service	#重啟kubelet
 23 [root@k8snode01 ~]# docker ps				#查看創建的pod



提示:由於靜態pod不能通過API Server進行管理,因此在Master節點執行刪除操作後會變為Pending狀態,且無法刪除。刪除該pod只能在其運行的node上,將定義POD的yaml刪除。

3.3 HTTP方式


通過設置kubelet的啟動參數–mainfest-url,會定期從該URL下載Pod的定義文件,並以.yaml或.json文件的格式進行解析,從而創建Pod。

四 Pod容器共享Volume

4.1 共享Volume


在同一個Pod中的多個容器能夠共享Pod級別的存儲就Volume。Volume可以被定義為各種類型,多個容器各自進行掛載操作,將一個Volume掛載為容器內部需要的目錄。


示例1:

Pod級別設置Volume “app-logs”,同時Pod包含兩個容器,Tomcat向該Volume寫日誌,busybox讀取日誌文件。

  1 [root@k8smaster01 study]# vi pod-volume-applogs.yaml
  2 apiVersion: v1
  3 kind: Pod
  4 metadata:
  5   name: volume-pod
  6 spec:
  7   containers:
  8   - name: tomcat
  9     image: tomcat
 10     ports:
 11     - containerPort: 8080
 12     volumeMounts:
 13     - name: app-logs
 14       mountPath: /usr/local/tomcat/logs
 15   - name: logreader
 16     image: busybox
 17     command: ["sh","-c","tail -f /logs/catalina*.log"]
 18     volumeMounts:
 19     - name: app-logs
 20       mountPath: /logs
 21   volumes:
 22   - name: app-logs
 23     emptyDir: {}

解釋:

Volume名:app-logs;

emptyDir:為Pod分配到Node的時候創建。無需指定宿主機的目錄文件,為Kubernetes自動分配的目錄。

  1 [root@k8smaster01 study]# kubectl create -f pod-volume-applogs.yaml	#創建
  2 [root@k8smaster01 study]# kubectl get pods				#查看
  3 [root@k8smaster01 study]# kubectl logs volume-pod -c busybox	#讀取log




  1 [root@k8smaster01 study]# kubectl exec -it volume-pod -c tomcat -- ls /usr/local/tomcat/logs
  2 catalina.2019-06-29.log      localhost_access_log.2019-06-29.txt
  3 host-manager.2019-06-29.log  manager.2019-06-29.log
  4 localhost.2019-06-29.log
  5 [root@k8smaster01 study]# kubectl exec -it volume-pod -c tomcat -- tail /usr/local/tomcat/logs/catalina.2019-06-29.log



提示:通過tomcat容器可查看日誌,對比busybox通過共享Volume查看的日誌是否一致。

五 Pod配置管理

5.1 Pod配置概述


應用部署的一個最佳實踐是將應用所需的配置信息與程序進行分離,使程序更加靈活。將相應的應用打包為鏡像,可以通過環境變量或者外掛volume的方式在創建容器的時候進行配置注入,從而實現更好的復用。

Kubernetes提供一種統一的應用配置管理方案:ConfigMap。

5.2 ConfigMap概述


ConfigMap供容器使用的主要場景:

  • 生成容器內部的環境變量;
  • 設置容器的啟動命令的參數(需設置為環境變量);
  • 以volume的形式掛載為容器內部的文件或者目錄。


ConfigMap以一個或多個key:value的形式定義。value可以是string也可以是一個文件內容,可以通過yaml配置文件或者通過kubectl create configmap 的方式創建configMap。

5.3 創建ConfigMap資源對象——yaml方式

  1 [root@k8smaster01 study]# vi cm-appvars.yaml
  2 apiVersion: v1
  3 kind: ConfigMap
  4 metadata:
  5   name: cm-appvars
  6 data:
  7   apploglevel: info
  8   appdatadir: /var/data
  9 
 10 [root@k8smaster01 study]# kubectl create -f cm-appvars.yaml
 11 configmap/cm-appvars created
 12 [root@k8smaster01 study]# kubectl get configmaps
 13 NAME         DATA   AGE
 14 cm-appvars   2      8s
 15 [root@k8smaster01 study]# kubectl describe configmaps cm-appvars



  1 [root@k8smaster01 study]# kubectl get configmaps cm-appvars -o yaml


5.4 創建ConfigMap資源對象——命令行方式


語法1

  1 # kubectl create configmap NAME --from-file=[key=]source --from-file=[key=]source



解釋:通過–from-file參數從文件中創建,可以指定key名稱,也可以制定多個key。

語法2

  1 # kubectl create configmap NAME --from-file=config-files-dir



解釋:通過–from-file參數從目錄中創建,該目錄下的每個配置文件名都被設置為key,文件的內容被設置為value。

語法3

  1 # kubectl create configmap NAME --from-literal=key1=value1 --from-literal=key2=value2



解釋:通過–from-literal參數從文本中創建,直接將指定的key#=value#創建為ConfigMap的內容。

5.5 Pod使用ConfigMap


容器應用使用ConfigMap有兩種方式:

  • 通過環境變量獲取ConfigMap中的內容;
  • 通過Volume掛載的方式將ConfigMap中的內容掛載為容器內容的文件或目錄。

  1 [root@k8smaster01 study]# vi cm-test-pod.yaml
  2 apiVersion: v1
  3 kind: Pod
  4 metadata:
  5   name: cm-test-pod
  6 spec:
  7   containers:
  8   - name: cm-test
  9     image: busybox
 10     command: ["/bin/sh","-c","env|grep APP"]	#容器里執行查看環境變量的命令
 11     env:
 12     - name: APPLOGLEVEL				#定義容器環境變量名稱
 13       valueFrom:
 14         configMapKeyRef:			#環境變量的值來自ConfigMap
 15           name: cm-appvars			#指定來自cm-appvars的ConfigMap
 16           key: apploglevel			#key為apploglevel
 17     - name: APPDATADIR
 18       valueFrom:
 19         configMapKeyRef:
 20           name: cm-appvars
 21           key: appdatadir
 22 
 23 [root@k8smaster01 study]# kubectl create -f cm-test-pod.yaml
 24 [root@k8smaster01 study]# kubectl get pods
 25 NAME          READY   STATUS      RESTARTS   AGE
 26 cm-test-pod   0/1     Completed   2          24s



【掛載形式-待補充】

5.6 ConfigMap限制


  • Configmap必須在pod創建之間創建;
  • ConfigMap受到namespace的限制,只有同一個命名空間下才能引用;
  • ConfigMap暫時無法配置配額;
  • 靜態的pod無法使用ConfigMap;
  • 在使用volumeMount掛載的時候,configMap基於items創建的文件會整體的將掛載數據卷的容器的目錄下的文件全部覆蓋。

六 Pod獲取自身信息

6.1 Downward API


pod擁有唯一的名字、IP地址,並且處於某個Namespace中。pod的容器內獲取pod的信息科通過Downward API實現。具體有以下兩種方式:

  • 環境變量:用於單個變量,可以將pod信息和container信息注入容器內部;
  • volume掛載:將數組類信息生成為文件,掛載至容器內部。


舉例1:通過Downward API將Pod的IP、名稱和所在的Namespace注入容器的環境變量。

  1 [root@k8smaster01 study]# vi dapi-test-pod.yaml
  2 apiVersion: v1
  3 kind: Pod
  4 metadata:
  5   name: dapi-test-pod
  6 spec:
  7   containers:
  8     - name: test-container
  9       image: busybox
 10       command: [ "/bin/sh", "-c", "env" ]
 11       env:
 12         - name: MY_POD_NAME
 13           valueFrom:
 14             fieldRef:
 15               fieldPath: metadata.name
 16         - name: MY_POD_NAMESPACE
 17           valueFrom:
 18             fieldRef:
 19               fieldPath: metadata.namespace
 20         - name: MY_POD_IP
 21           valueFrom:
 22             fieldRef:
 23               fieldPath: status.podIP
 24   restartPolicy: Never



提示:Downward API提供如下變量:

metadata.name:Pod的名稱,當Pod通過RC生成時,其名稱是RC隨機產生的唯一名稱;

status.podIP:Pod的IP地址,POd的IP屬於狀態數據,而非元數據;

metadata.namespace:Pod所在的namespace。

  1 [root@k8smaster01 study]# kubectl create -f dapi-test-pod.yaml
  2 [root@k8smaster01 study]# kubectl logs dapi-test-pod | grep MY_POD
  3 MY_POD_NAMESPACE=default
  4 MY_POD_IP=172.30.240.4
  5 MY_POD_NAME=dapi-test-pod
  6 



舉例2:通過Downward API將Container的自願請求和限制信息注入容器的環境變量。

  1 [root@k8smaster01 study]# vi dapi-test-pod-container-vars.yaml
  2 apiVersion: v1
  3 kind: Pod
  4 metadata:
  5   name: dapi-test-pod-container-vars
  6 spec:
  7   containers:
  8     - name: test-container
  9       image: busybox
 10       imagePullPolicy: Never
 11       command: [ "/bin/sh", "-c" ]
 12       args:
 13       - while true; do
 14           echo -en '\n';
 15           printenv MY_CPU_REQUEST MY_CPU_LIMIT;
 16           printenv MY_MEM_REQUEST MY_MEM_LIMIT;
 17           sleep 3600;
 18         done;
 19       resources:
 20         requests:
 21           memory: "32Mi"
 22           cpu: "125m"
 23         limits:
 24           memory: "64Mi"
 25           cpu: "250m"
 26       env:
 27         - name: MY_CPU_REQUEST
 28           valueFrom:
 29             resourceFieldRef:
 30               containerName: test-container
 31               resource: requests.cpu
 32         - name: MY_CPU_LIMIT
 33           valueFrom:
 34             resourceFieldRef:
 35               containerName: test-container
 36               resource: limits.cpu
 37         - name: MY_MEM_REQUEST
 38           valueFrom:
 39             resourceFieldRef:
 40               containerName: test-container
 41               resource: requests.memory
 42         - name: MY_MEM_LIMIT
 43           valueFrom:
 44             resourceFieldRef:
 45               containerName: test-container
 46               resource: limits.memory
 47   restartPolicy: Never



提示:Downward API提供如下變量:

requests.cpu:容器的CPU請求值;

limits.cpu:容器的CPU限制值;

requests.memory:容器的內存請求值;

limits.memory:容器的內存限制值。

  1 [root@k8smaster01 study]# kubectl create -f dapi-test-pod-container-vars.yaml
  2 [root@k8smaster01 study]# kubectl logs dapi-test-pod-container-vars
  3 1
  4 1
  5 33554432
  6 67108864



舉例3:通過Downward API將Pod的Label、Annotation列表通過Volume掛載為容器內的一個文件。

  1 [root@k8smaster01 study]# vi dapi-test-pod-volume.yaml
  2 apiVersion: v1
  3 kind: Pod
  4 metadata:
  5   name: dapi-test-pod-volume
  6   labels:
  7     zone: us-est-coast
  8     cluster: test-cluster1
  9     rack: rack-22
 10   annotations:
 11     build: two
 12     builder: john-doe
 13 spec:
 14   containers:
 15     - name: test-container
 16       image: busybox
 17       imagePullPolicy: Never
 18       command: [ "/bin/sh", "-c" ]
 19       args:
 20       - while true; do
 21           if [[ -e /etc/labels ]]; then
 22             echo -en '\n\n'; cat /etc/labels; fi;
 23           if [[ -e /etc/annotations ]]; then
 24             echo -en '\n\n'; cat /etc/annotations; fi;
 25           sleep 3600;
 26         done;
 27       volumeMounts:
 28         - name: podinfo
 29           mountPath: /etc
 30           readOnly: false
 31   volumes:
 32     - name: podinfo
 33       downwardAPI:
 34         items:
 35           - path: "labels"
 36             fieldRef:
 37               fieldPath: metadata.labels
 38           - path: "annotations"
 39             fieldRef:
 40               fieldPath: metadata.annotations



注意:Volume中的ddownwardAPI的items語法,將會以path的名稱生成文件。如上所示,會在容器內生產/etc/labels和/etc/annotations兩個文件,分別包含metadata.labels和metadata.annotations的全部Label。

  1 [root@k8smaster01 study]# kubectl create -f dapi-test-pod-volume.yaml
  2 [root@k8smaster01 study]# kubectl logs dapi-test-pod-volume
  3 



提示:DownwardAPI意義:

在某些集群中,集群中的每個節點需要將自身的標識(ID)及進程綁定的IP地址等信息事先寫入配置文件中,進程啟動時讀取此類信息,然後發布到某個類似註冊服務中心。此時可通過DowanwardAPI,將一個預啟動腳本或Init Container,通過環境變量或文件方式獲取Pod自身的信息,然後寫入主程序配置文件中,最後啟動主程序。本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※試算大陸海運運費!

SpringBoot基本配置詳解

SpringBoot項目有一些基本的配置,比如啟動圖案(banner),比如默認配置文件application.properties,以及相關的默認配置項。

示例項目代碼在:

一、啟動圖案banner

編寫banner.txt放入resources文件夾下,然後啟動項目即可修改默認圖案。

關於banner的生成,可以去一些專門的網站。

比如:https://www.bootschool.net/ascii

二、配置文件application

2.1 application.properties/yml

resources下通常會默認生成一個application.properties文件,這個文件包含了SpringBoot項目的全局配置文件。裏面的配置項通常是這樣的:

server.port=8080

在這個文件里我們可以添加框架支持的配置項,比如項目端口號、JDBC連接的數據源、日誌級別等等。

現在比較流行的是將properties文件改為yml文件。yml文件的格式yaml是這樣的:

server:
    port: 8080

yml和properties的作用是一樣的。而yml的好處是顯而易見的——更易寫易讀。

屬性之間互相調用使用${name}:

eknown:
    email: eknown@163.com
    uri: http://www.eknown.cn
    title: 'hello, link to ${eknown.uri} or email to ${eknown.email}'

鏈接:

2.2 多環境配置文件

通常開發一個應用會有多個環境,常見如dev/prod,也會有test,甚至其他一些自定義的環境,SpringBoot支持配置文件的靈活切換。

定義新配置文件需要遵循以下格式:application-{profile}.properties 或者application-{profile}.yml

比如現在有dev和prod兩個環境,我需要在application.yml文件之外新建兩個文件:

  1. application-dev.yml

    server:
       port: 8080
  2. application-prod.yml

    server:
      port: 8081

然後在application.yml中通過application.profiles.active={profile}指明啟用那個配置:

application:
    profiles:
      active: dev

除了在application.yml中指定配置文件外,還可以通過啟動命令指定:java -jar xxx.jar --spring.profiles.active=dev

2.2 自定義配置項並獲取它

主要介紹兩種方式,獲取單個配置項和獲取多個配置項。

舉例:

eknown:
    email: eknown@163.com
    uri: http://www.eknown.cn

2.2.1 使用@Value註解獲取單個配置項

@Value("${eknown.email}")
private String email;

@Value("${eknown.uri}")
private String url;

注意:使用@Value註解的時候,所在類必須被Spring容器管理,也就是被@Component、@Controller、@Service等註解定義的類。

2.2.2 獲取多個配置項

第一種,定義一個bean類,通過@Value獲取多個配置項:

@Component
public class MyConfigBean {
  
}

然後我們通過get方法來獲取這些值:

@RestController
public class BasicAction {
  
  @Autowired
  private MyConfigBean myConfigBean;

}

第二種,使用註解@ConfigurationProperties:

@Component
@ConfigurationProperties(perfix="eknown")
public class MyConfigBean {

  private String email;
  private String uri;
}

這裏只需要通過prefix指定前綴即可,後面的值自動匹配。

這裏我們還使用了@Component註解來讓spring容器管理這個MyConfigBean。

此外,我們可以不需要引入@Component,轉而在Application啟動類上加上@EnableConfigurationProperties({MyConfigBean.class})來啟動這個配置。

注意:我們這裡是從主配置文件,也就是SpringBoot默認的application-profile文件中獲取配置數據的。

而從自定義的配置文件,比如test.yml這種形式中獲取配置項時,情況是有點不大一樣的。

三、自定義配置文件

上面介紹的配置文件都是springboot默認的application開頭的文件。如果要自定義一個配置文件呢,比如test.yml或test.properties,怎麼獲取其中的配置項呢?

使用@PageResource註解即可。

首先我們來看一下讀取自定義的properties文件里的內容:

test.properties

hello.time=2019.11.19
hello.name=eknown

定義Configuration類:

@Configuration
@PropertySource("classpath:test.properties")
//@PropertySource("classpath:test.yml") // 注意,yml文件不能直接這樣寫,會讀不出數據
@ConfigurationProperties(prefix = "hello")
public class TestConfiguration {
    private String name;
    private String time;

    // hide get and set methods
}

測試一下:

@RestController
@RequestMapping(value = "test")
public class TestAction {

    @Autowired
    private TestConfiguration testConfiguration;

    @GetMapping(value = "config")
    public String test() {
        return testConfiguration.getName() + "<br/>" + testConfiguration.getTime();
    }
}

如果將properties文件換成yml文件呢?

我們嘗試一下,發現:

讀不出數據?

分析一下@PropertySource註解,發現其使用的PropertySourceFactory是DefaultPropertySourceFactory.

這個類的源碼如下:

public class DefaultPropertySourceFactory implements PropertySourceFactory {
    public DefaultPropertySourceFactory() {
    }

    public PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException {
        return name != null ? new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource);
    }
}

這個類只能處理properties文件,無法處理yml文件。所以我們需要自定義一個YmlSourceFactory。

public class YamlSourceFactory extends DefaultPropertySourceFactory {

    @Override
    public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
        return new YamlPropertySourceLoader().load(resource.getResource().getFilename()
                , resource.getResource()).get(0);
    }
}

然後定義test.yml文件的config類:

@Configuration
@PropertySource(value = "classpath:test.yml", encoding = "utf-8", factory = YamlSourceFactory.class)
@ConfigurationProperties(prefix = "yml.hello")
public class TestYamlConfiguration {
    private String name;
    private String time;

    // hide get and set methods
}

注:為了區分test.properties和test.yml,這裏的test.yml中的屬性以yml.hello開頭。

編寫一下測試:

    @Autowired
    private TestYamlConfiguration ymlConfiguration;

    @GetMapping(value = "yml")
    public String testYml() {
        return "yml config: <br/>" + ymlConfiguration.getName() + "<br/>" + ymlConfiguration.getTime();
    }

訪問:

四、補充@ConfigurationProperties

網上一些資料中,為配合使用@ConfigurationProperties,還使用了@EnableConfigurationProperties註解。

經過測試發現:

  1. 從SpringBoot默認配置文件讀取配置信息,使用@ConfigurationProperties + @Component/@Configuration,或者@ConfigurationProperties + 在啟動類添加@EnableConfigurationProperties({class})。這兩種方式都能解決問題

  2. 從非默認配置文件讀取配置信息,需要利用@PropertySource註解。同樣兩種方式:

    2.1 @PropertySource + @ConfigurationProperties + @Component/@Configuration

    2.2 @PropertySource + @ConfigurationProperties + @Component/@Configuration + @EnableConfigurationProperties,第二種方式存在一個問題,即還是必須要使用@Component註解,如果不使用,則會導致讀取配置信息為null,但程序不會報錯;而如果採用了,則會導致bean類的set方法被執行兩次(也就是生成了兩個同樣類型的bean類)。這種方式不建議!

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

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

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

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

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

※專營大陸快遞台灣服務

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

吸髒空氣容易失智 空污物濃度高 50歲以上患病風險增四成

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※試算大陸海運運費!