分類: 3C資訊

  • .NET進階篇06-async異步、thread多線程2

    .NET進階篇06-async異步、thread多線程2

    知識需要不斷積累、總結和沉澱,思考和寫作是成長的催化劑

    內容目錄

    一、線程Thread

    .NET中線程操作封裝為了Thread類,可以讓開發者對線程進行直觀操作。Thread提供了實例方法用於管理線程的生命周期和靜態方法用於控制線程的一些訪問存儲等一些外在的屬性,相當於工作空間環境變量了

    1、生命周期

    線程的生命周期有創建、啟動、可能掛起、等待、恢復、異常、然後結束。用Thread類可以容易控制一個線程的全生命周期

    Thread類的構造函數重載可以接受ThreadStart無參數和ParameterizedThreadStart有參數的委託,然後調用實例的Start()方法啟動線程。Thread的構造函數的帶有參數的委託,參數是一個object類型,因為我們可以傳入任何信息

    Thread t1 = new Thread(() => {
        Console.WriteLine($"新線程  {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
    });
    t1.Start();
    Thread t2 = new Thread((obj) => {
        Console.WriteLine($"新線程  {Thread.CurrentThread.ManagedThreadId.ToString("00")},參數 {obj.ToString()}");
    });
    t2.Start("hello kitty");

    線程啟動后,可以調用線程的Suspend()掛起線程,線程就會處於休眠狀態(不繼續執行線程內代碼),調用Resume()喚醒線程,還有一個不太建議使用的Abort()通過拋出異常的方式來銷毀線程,隨後線程的狀態就會變為AbortRequested

    常用的還有線程的等待,在主線程上啟用工作線程后,有時需要等待工作線程的完成后,主線程才繼續工作。可以調用實例方法Join(),當然我們可以傳入時間參數來表明我主線程最多等你多久

    2、後台線程

    上一章我們知道Thread默認創建的是前台線程,前台線程會阻止系統進程的退出,就是啟動之後一定要完成任務的後台線程會伴隨着進程的退出而退出。通過設置屬性IsBackground=true改為後台線程。另外還可以通過設置Priority指定線程的優先級。但這個並不總會如你所想設置了高優先級就一定最先執行。操作系統會優化調度,這也是線程不太好控制的原因之一

    3、靜態方法

    上面介紹的都是Tread的實例方法,Thread還有一些常用靜態方法。有時線程設置不當,會有些意想不到的的bug

    1.線程本地存儲

    AllocateDataSlot和AllocateNamedDataSlot用於給所有線程分配一個數據槽。像下面例子所示,如果不在子線程中給數據槽中放入數據,是獲取不到其他線程往裡面放的數據。

    var slot= Thread.AllocateNamedDataSlot("testSlot");
    //Thread.FreeNamedDataSlot("testSlot");
    Thread.SetData(slot, "hello kitty");
    Thread t1 = new Thread(() => {
        //Thread.SetData(slot, "hello kitty");
        var obj = Thread.GetData(slot);
        Console.WriteLine($"子線程:{obj}");//obj沒有值
    });
    t1.Start();

    var obj2 = Thread.GetData(slot);
    Console.WriteLine($"主線程:{obj2}");

    在聲明數據槽的時候.NET提醒我們如果要更好的性能,請使用ThreadStaticAttribute標記字段。什麼意思?我們來看下面這個例子

    //
    // 摘要:
    //     在所有線程上分配未命名的數據槽。 為了獲得更好的性能,請改用以 System.ThreadStaticAttribute 特性標記的字段。
    //
    // 返回結果:
    //     所有線程上已分配的命名數據槽。
    public static LocalDataStoreSlot AllocateDataSlot();

    例子中的如果不在靜態字段上標記ThreadStatic輸出結果就會一致。ThreadStatic標記指示各線程的靜態字段值是否唯一

    [ThreadStatic]
    static string name = string.Empty;
    public void Function()
    {
        name = "kitty";
        Thread t1 = new Thread(() => {
            Console.WriteLine($"子線程:{name}");//輸出空
        });
        t1.Start();
        Console.WriteLine($"主線程:{name}");//輸出kitty
    }

    還有一個ThreadLocal提供線程數據的本地存儲,用法和上面一樣,在每個線程中聲明數據僅供自己使用

    ThreadLocal<string> local = new ThreadLocal<string>() { };
    local.Value = "hello kitty";
    Thread t = new Thread(() => {
        Console.WriteLine($"子線程:{local.Value}");
    });
    t.Start();
    Console.WriteLine($"主線程:{local.Value}");

    上面的靜態方法用於線程的本地存儲TLS(Thread Local Storage),Thread.Sleep方法在開發調試時也是經常用的,讓線程掛起指定的時間來模擬耗時操作

    2.內存柵欄

    先說一個常識問題,為什麼我們發布版本時候要用Release發布?Release更小更快,做了很多優化,但優化對我們是透明的(計算機里透明認為是個黑盒子,內部邏輯細節對我們不開放,和生活中透明意味着完全掌握了解不欺瞞剛好相反),一般優化不會影響程序的運行,我們先借用網上的一個例子

    bool isStop = false;
    Thread t = new Thread(() => {
        bool isSuccess = false;
        while (!isStop)
        {
            isSuccess = !isStop;
        }
    });
    t.Start();
    Thread.Sleep(1000);
    isStop = true;
    t.Join();
    Console.WriteLine("主線程執行結束");

    上面例子如果在debug下能正確執行完直到輸出“主程序執行結束”,然而在release下卻一直會等待子線程的完成。這裏子線程中isStop一直為false。首先這是一個由多線程共享變量引起的問題,所以我們建議最好的解決辦法就是盡量不共享變量,其次可以使用Thread.MemoryBarrier和VolatileRead/Write以及其他鎖機制犧牲一點性能來換取數據的安全。(上面例子測試如果在子線程while中進行Console.writeLine操作,奇怪的發現release下也能正常輸出了,猜測應該是進行了內存數據的更新)

    release優化會將t線程中的isStop變量的值加載到CPU Cache中,而主線程修改了isStop值在內存中,所以子線程拿不到更新后的值,造成數據不一致。那麼解決辦法就是取值時從內存中獲取。Thread.MemoryBarrier()就可以讓在此方法之前的內存寫入都及時的從CPU Cache中更新到內存中,在此之後的內存讀取都要從內存中獲取,而不是CPU Cache。在例子中的while內增加Thread.MemoryBarrier()就能避免數據不一致問題。VolatileRead/Write是對MemoryBarrier的分開解釋,從處理器讀取,從處理器寫入。

    4、返回值

    前面聲明線程時,可以傳遞參數,那麼想要有返回值該如何去做呢?Thread並沒有提供返回值的操作,後面.NET給出的對Thead的高級封裝給出了解決方案,直接使用即可。那目前我們使用thread類就要自己實現下帶有返回值的線程操作,都是通過委託實現的,這裏簡單介紹一種,(共享外部變量也是可以,不建議)

    private Func<T> ThreadWithReturn<T>(Func<T> func)
    {
        T t = default(T);
        Thread thread = new Thread(() =>
        {
            t = func.Invoke();
        });
        thread.Start();
        return () =>

        {
            thread.Join();
            return t;
        };
    }
    //調用
    Func<intfunc = this.ThreadWithReturn<int>(() =>
    {
        Thread.Sleep(2000);
        return DateTime.Now.Millisecond;
    });
    int iResult = func.Invoke();

    二、線程池ThreadPool

    .NET起初提供Thread線程類,功能很豐富,API也很多,所以使用起來比較困難,況且線程還不都是很像理想中運行,所以從2.0開始提供了ThreadPool線程池靜態類,全是靜態方法,隱藏了諸多Thread的接口,讓線程使用起來更輕鬆。線程池可用於執行任務、發送工作項、處理異步 I/O、代表其他線程等待以及處理計時器

    1、工作隊列

    常用ThreadPool線程池靜態方法QueueUserWorkItem用於將方法排入線程池隊列中執行,如果線程池中有閑置線程就會執行,QueueUserWorkItem方法的參數可以指定一個回調函數委託並且傳入參數,像下面這樣

    ThreadPool.QueueUserWorkItem((obj) => {
                    Console.WriteLine($"線程池中線程  {Thread.CurrentThread.ManagedThreadId.ToString("00")} ,傳入 {obj.ToString()}");
                },"hello kitty");

    2、工作線程和IO線程

    一般異步任務的執行,不涉及到網絡文件等IO操作的,計算密集型,開發者來調用。而IO線程一般用在文件網絡上,是CLR調用的,開發者無需管。工作線程發起文件訪問調用,由驅動器完成后通知IO線程,IO線程則執行異步任務的回調函數

    獲取和設置最小最大的工作線程和IO線程

    ThreadPool.GetMaxThreads(out int workerThreads, out int completionPortThreads);
    ThreadPool.GetMinThreads(out int workerThreads, out int completionPortThreads);
    ThreadPool.SetMaxThreads(1616);
    ThreadPool.SetMinThreads(88);

    3、和Thread區別

    如果計算機只有8個核,同時可以有8個任務運行。現在我們有10個任務需要運行,用Thread就需要創建10個線程,用ThreadPool可能只需要利用8個線程就行,節約了空間和時間。線程池中的線程默認先啟動最小線程數量的線程,然後根據需要增減數量。線程池使用起來簡單,但也有一些限制,線程池中的線程都是後台線程,不能設置優先級,常用於耗時較短的任務。線程池中線程也可以阻塞等待,利用ManualResetEvent去通知,但一般不會使用。

    4、定時器

    .NET中有很多可以實現定時器的功能,在ThreadPool中,我們可以利用RegisterWaitForSingleObject來註冊一個指定時間的委託等待。像下面這樣,將每隔一秒就輸出消息

    ThreadPool.RegisterWaitForSingleObject(new AutoResetEvent(true), new WaitOrTimerCallback((obj, b) =>
    {
        Console.WriteLine($"obj={obj},tid={Thread.CurrentThread.ManagedThreadId},datetime={DateTime.Now}");
    }),"hello kitty",1000,false);

    我們平常見過比較多的還是timer類,timer類在.net內是好幾個地方都有的,在System.Threading、
    System.Timer、System.Windows.Form、System.Web.UI等裏面都有Timer,後面都是在第一個System.Threading里的Timer擴展

    System.Threading.Timer timer = new System.Threading.Timer((obj) =>
    {
        Console.WriteLine($"obj={obj},tid={Thread.CurrentThread.ManagedThreadId},datetime={DateTime.Now}");
    },"hello kitty",1000,1000);

    timer的底層有一個TimerQueue,利用ThreadPool.UnsafeQueueUserWorkItem來完成定時功能,和上面我們使用的ThreadPool定時器有一點區別

    實際開發中,簡單定時timer就夠用,但一般業務場景比較複雜,需要定製個性化的定時器,比如每月幾號執行,每月第幾個星期幾,幾點執行,工作日執行等。因此我們使用Quarz.NET定時框架,後面框架整合時會用到,用起來也是很簡單的

    先就啰嗦這兩點吧,下一篇應該是Task、Parallel以及Async/Await,然後總結介紹下C#的線程模式、線程同步鎖機制、異常處理,線程取消,線程安全集合和常見的線程問題

    天長水闊,見字如面,隨緣更新,拜了個拜~

    可關注主頁公號獲取更多哈

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

    【其他文章推薦】

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

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

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

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

  • 二叉搜索樹BST(C語言實現可用)

    二叉搜索樹BST(C語言實現可用)

    1:概述

    搜索樹是一種可以進行插入,搜索,刪除等操作的數據結構,可以用作字典或優先級隊列。二叉搜索樹是最簡單的搜索樹。其左子樹的鍵值<=根節點的鍵值,右子樹的鍵值>=根節點的鍵值。

    如果共有n個元素,那麼每次操作需要的O(log n)的時間.

     

     

     

    常用知識點

    • 滿二叉樹 : 一棵深度為k,且有2^k-1個節點的二叉樹,稱為滿二叉樹。這種樹的特點是每一層上的節點數都是最大節點數。
    • 完全二叉樹 : 而在一棵二叉樹中,除最後一層外,若其餘層都是滿的,並且最後一層要麼是滿的,要麼在右邊缺少連續若干節點,則此二叉樹為完全二叉樹。具有n個節點的完全二叉樹的深度為floor(log2n)+1。深度為k的完全二叉樹,至少有2^(k-1)個恭弘=叶 恭弘子節點,至多有2^k-1個節點。

    2.基本操作

    1. 查找(search)
    2. 插入(insert)
    3. 刪除(remove)

    3:操作原理

      

    查找

    假設查找的值為x,從根節點的值開始比對,如果小於根節點的值,則往左兒子繼續查找,如果大於根節點的值,則往右兒子繼續查找.依次類推.直到當前節點的值等於要查找的值.

     

      以查找數值10為例

    插入

    按照查找的步驟即可找到插入值應該在的位置

     

     

    以插入數值6為例

    刪除:

    有四種情況:

    1: // 當前節點無左節點 ,右字節點7覆蓋5, 

    : 3: // 當前節點無右節點 ,右字節點7覆蓋5, 

     

     : 4: // 刪除節點5的左節點沒有右節點, 只需要8作為3的右節點 ,3節點覆蓋5

     

     

    : 2:  如果以上3中情況都沒有,只需要尋找當前節點的左節點的所有字節點的最大值,用最大值填充5節點 4填充5

     

     

     

     

    5:完整代碼

    #include <stdio.h> 
    #include <stdlib.h>
    struct TNode{
        int data;
        struct TNode *lt;
        struct TNode *rt;    
    };
    struct TNode* insrtTree(struct TNode *t,int key,int i);
    void printTree(struct TNode *root);
    struct TNode* delTree(struct TNode* t,int key);
    int find(struct TNode* t,int key); 
    int arr[1000]={0};
    int main(){
        int n,m;
        int i,t;
        scanf("%d%d",&n,&m); 
        struct TNode *root=NULL;
        for(i=0;i<n;i++){
            scanf("%d",&arr[i]); 
            root=insrtTree(root,arr[i],i);
        }
        //t=arr[m-1];
        
        /*
        if(arr[m-1]==0){
            printf("Right child");
        }else{
            printf("Light child");
        }*/
        root=delTree(root,10);
        printTree(root);
        return 0;
    }
    
    int find(struct TNode* pt,int key){
        if(pt==NULL)return NULL;
        else if(pt->data==key)return 1;
        else if(pt->data>key) return find(pt->lt,key);
        else if(pt->data<key) return find(pt->rt,key);
    }
    // 刪除節點 
    struct TNode* delTree(struct TNode* pt,int key){
        if(pt==NULL)return NULL;
        else if(pt->data>key) pt->lt=delTree(pt->lt,key);//尋找左節點 
        else if(pt->data<key) pt->rt=delTree(pt->rt,key);//尋找右節點
        //  找到節點 處理四種情況  
        else if(pt->lt==NULL){ // 當前節點無左節點 
            struct TNode* curt=pt->rt;
            free(pt);
            return curt;
        }else if(pt->rt==NULL){// 當前節點無右節點 
            struct TNode* curt=pt->lt;
            free(pt);
            return curt;
        }else if(pt->lt->rt==NULL){// 當前節點的左節點無右節點 
            struct TNode* curt=pt->lt;
            curt->rt=pt->rt;
            free(pt);
            return curt;
        }else{ 
        // 以上不滿足就把左兒子的子孫中最大的節點, 即右子樹的右子樹的...右子樹, 
        //提到需要刪除的節點位置
                struct TNode* p;
                for(p=pt->lt;p->rt->rt!=NULL;p=p->rt);
                struct TNode* curt=p->lt;
                p->rt=curt->rt;
                curt->lt=pt->lt;
                curt->rt=pt->rt;
                free(p);
                return curt;
        }
        return pt;
    }
    struct TNode* insrtTree(struct TNode *t,int key,int i){
        if(t==NULL){ //處理第一個節點 以及子節點為NULL情況 
            t=(struct TNode*)malloc(sizeof(struct TNode));
            t->lt=t->rt=NULL;
            t->data=key;
            return t;
        }
        if(t->data>key){// 插入左子樹情況 
             arr[i]=1;
            t->lt=insrtTree(t->lt,key,i);
        }else{         // 插入右子樹情況
            arr[i]=0;
            t->rt=insrtTree(t->rt,key,i);
        }
        return t;
    }
    void printTree(struct TNode *root){
        if(root==NULL)return;
        printf("%d ",root->data);
        printTree(root->lt);
        printTree(root->rt);
    }

     

    說明: 本身學習了 https://blog.csdn.net/li_l_il/article/details/88677927 但是完善了代碼 

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

    【其他文章推薦】

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

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

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

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

  • 區塊鏈和分佈式数字賬本正火?如何在7天內快速掌握這些必備的知識

    前言

    讀研期間,幾次被問到:“什麼是區塊鏈?”“我怎麼學習區塊鏈”。甚至跟“上至九十九”(爺爺奶奶),“下至剛會走的”(小學生的弟弟妹妹)解釋過我的研究內容。因此想總結一份7天的學習計劃(大量資料警告),讓那些對區塊鏈感興趣的人快速入門。區塊鏈技術的被運用到各行各業,尤其是各界(商界和政府)都對區塊鏈技術創新高度重視。本文總結了諸多入門區塊鏈的主要資源,博客、書籍和視頻,希望幫你用7天時間獲取必要的區塊鏈基礎知識。

    作者宇宙之一粟,轉載請先聲明出處
    公眾號宇宙之一粟,關注公眾號獲取更多相關資源

    介紹

    區塊鏈,剛開始聽起來會很新穎,因為是一個英文組合詞彙( Block + Chain )。區塊:存放數據的載體(想象成箱子),鏈:把這些箱子首尾相連(類似數據結構中的單鏈表)。

    區塊鏈的本質:一個去中心化的分佈式賬本數據庫。其本身是一串使用密碼學相關聯所產生的數據塊,每一個數據塊中包含了多次比特幣網絡交易有效確認的信息。

    區塊鏈技術從比特幣中發展而來,是比特幣的底層技術,和比特幣是相伴相生的關係。目前這項技術已經不僅僅限於比特幣——青出於藍而勝於藍。各行各業開始利用這項技術尋求突破。

    2019年6月,Facebook宣布了其加密貨幣項目——。

    我們國家央行也將發行自己的数字貨幣——( Digital Currency Electronic Payment ),也採用了區塊鏈技術,目前仍處於研究測試過程中。

    2019年10月24日下午,。一時間區塊鏈技術又衝上了風口。

    因此,我們有必要對區塊鏈技術做一個基本的了解。

    如何獲得知識

    如果想快速學習區塊鏈知識:收聽音頻課,閱讀書籍,看論文或在線文章,學習白皮書以及參与區塊鏈的相關學習計劃或在線課程。其他針對想從事更加專業的學習人員——參加編程或智能合約編碼課程。

    應該學習到什麼程度

    在區塊鏈領域工作或打算工作的每個人至少應該能夠理解並編寫一個非常簡單的智能合約,例如以程序語言Solidity通過幾行代碼實現的託管過程。儘管其他人會說,在學習區塊鏈和DLT時應該把重點放在另一個方面,但在我們看來,這是一條清晰的聲明。

    在我們看來,理解簡單的智能合約(例如5-10行代碼)的需求不僅對於信息系統領域的員工很重要,而且在商業,經濟和法律領域也很重要。但是,關於如何配置節點的更深入的技術知識通常不是緊要的,但以後可以獲取。

    任何尋求參与區塊鏈技術並考慮以下步驟的人都可能擁有光榮的職業生涯。我們認為,區塊鏈技術及其日益重要的潛力有可能對感興趣的人的職業道路產生积極影響,因為在這個動態且快速增長的細分市場中,未來幾年甚至數十年內還有很多工作要做。這樣,可以將由於数字化和自動化程度提高而失業(例如在金融部門)的風險降到最低。

    但是感興趣的人如何獲得足夠的知識呢?存在哪些研究區塊鏈技術的機會?本文總結了區塊鏈教育的一些不同策略。我們基本上設計了一個特定的程序,以在大約7個工作日內實現“區塊鏈入門”。

    一、購買或轉讓加密貨幣

    所需時間:0.5天

    除了獲得一些理論知識之外,理解此技術非常重要的是您在操作上“弄髒手”。 它的工作方式如下,為感興趣的人提供有關什麼是區塊鏈技術的最重要學習。

    因此,開始的最佳練習是:購買加密貨幣(例如比特幣)並在全球範圍內進行轉移。 如下所示,請找到分步指南:

    • 在例如或比特派上開設一個帳戶;其他選擇:Bitstamp,Bitrex,Binance。
    • 購買比特幣或者購買以太幣。請注意,萬一使用公鑰或私鑰出錯,這筆錢將不可避免地丟失。因此,遵循此步驟當然是每個人自己的決定。
    • 開設第二個帳戶(上面的示例)。
    • 從第二個帳戶獲取一個錢包地址,該地址類似於以太坊的0xd42899dcC146d4788649e6aa5B09f129fC269127。
    • 第一步,將您購買的一部分以太幣或比特幣轉移到該地址。請注意,以太幣和比特幣的地址不同,因此請謹慎行事,切勿混淆。
    • 現在,您可以看到可以在幾秒鐘(以太)或幾分鐘(比特幣)內將價值發送到世界各地。
    • 一個更高級的步驟,但對理解區塊鏈非常有幫助:將插件添加到Google Chrome互聯網瀏覽器中,以直接訪問以太坊網絡並將您的以太幣存儲在您自己的錢包中。之後,開設一個帳戶並生成您自己的錢包地址。然後,將您的一些以太幣從之前的步驟轉移到這個新生成的錢包中。

    二、音頻課

    所需時間:1.5天

    如果你喜歡聽音頻課,選擇一個合適的音頻可以快速入門:

    • Kryptoshow: Julian Hosp博士很好地介紹了區塊鏈技術和加密貨幣這一主題。播客還涵蓋了高級方面,例如在密碼學和編程方面。在訪問播客。
    • BTC Echo:此播客面向更高級的受眾,重點關注比特幣和加密資產。在訪問播客。
    • 從0到1全面學透區塊鏈音頻教程,在學習

    三、閱讀重要的白皮書

    所需時間:1天

    為了了解區塊鏈技術的起源和概念,有必要研究兩種最重要的加密貨幣比特幣和以太坊的白皮書。其他補充Filecoin白皮書。

    最重要的論文:

    • 比特幣白皮書:中本聰(2008):比特幣:點對點电子現金系統,。
    • 以太坊白皮書:Buterin,V.(2013):以太坊白皮書:下一代智能合約和去中心化應用平台, 。
    • Filecoin白皮書: Protocol Labs(2017):Filecoin: A Decentralized Storage Network,
      (待完善)

    四、觀看視頻

    所需時間:2天

    在通過閱讀有關比特幣和以太坊的原始著作從“科學”角度看待區塊鏈技術之後,您可以使用視頻進一步熟悉該技術並更深入地了解該技術的不同組成部分。我們推薦以下視頻:

    最重要的視頻:

    五、更多文章和網站

    所需時間:1天

    通過遵循建議的步驟,您現在應該已經對技術有了基本的高級知識。現在,是時候更深入地研究區塊鏈技術了。我們建議閱讀以下文章:

    文章或文檔:

    需要了解的項目的網站:

    • 以太坊:
    • EOS:
    • NULS:
    • FISCO BCOS:

    六、更廣泛概述的書籍

    所需時間:2天閱讀

    其他書籍:

    • 中文版(第2版)
    • Andreas M. Antonopoulos / Gavin Wood(2018年):

    Ps: 關注公眾號 宇宙之一粟 回復關鍵字 “區塊鏈” 獲取电子版

    七、認識志同道合之人

    所需時間:參加2次聚會(主要是免費)

    下一步,我們建議您進入區塊鏈社區,並與區塊鏈專家進行首次討論。

    科學論文

    所需時間:0天,因為這是可選的

    如果您打算從科學的角度更多地探討區塊鏈的話題,那麼就有可能研究有關區塊鏈技術的論文。我們將進一步擴大此列表。

    文件:

    • Eyal I , Gencer A E , Sirer E G , et al.
    • Gilad, Yossi & Hemo, Rotem & Micali, Silvio & Vlachos, Georgios & Zeldovich, Nickolai. (2017).
    • Li C , Li P , Zhou D , et al.
    • Yin M , Malkhi D , Reiter M K , et al.
    • Wang, Jiaping & Wang, Hao. (2019).
    • 更多區塊鏈相關論文

    現在?

    重要的是“閱讀”有關區塊鏈和DLT中的教育如何工作的信息。歸根結底,只有一件重要的事情:那就去做。不要只是想,而要行動。

    歡迎評論給我,無論您是否遵循我的建議–並向我提供反饋。如果您還有其他想法,請告訴我們如何學習和使用區塊鏈。如果您使用其他來源來獲取或增加您的區塊鏈知識,那麼如果您能與我分享這些知識,我將非常感激。

    備註

    在Medium看到Education in Blockchain and DLT: How to Acquire the Necessary Knowledge with a Workload of 10 Working Days的一篇博客,但是有很多網站和學習資料使我們不能訪問的。基於此博客,製作了方便我們快速學習並掌握區塊鏈必備知識的這篇文章。

    如果您喜歡本文,將其轉發或分享(附上原文出處),我將非常高興並表示感謝。
    也歡迎關注我的微信公眾號:宇宙之一粟,分享更多資料,與你讀書學習精進。

    參考博客: Authors: Philipp Sandner, Jonas Groß

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

    【其他文章推薦】

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

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

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

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

  • JVM原理速記複習Java虛擬機總結思維導圖面試必備

    JVM原理速記複習Java虛擬機總結思維導圖面試必備

    良心製作,右鍵另存為保存

    喜歡可以點個贊哦

    Java虛擬機

    一、運行時數據區域

    線程私有

    • 程序計數器

      • 記錄正在執行的虛擬機字節碼指令的地址(如果正在執行的是Native方法則為空),是唯一一個沒有規定OOM(OutOfMemoryError)的區域。
    • Java虛擬機棧

      • 每個Java方法在執行的同時會創建一個棧楨用於存儲局部變量表、操作數棧、動態鏈接、方法出口等信息。從方法調用直到執行完成的過程,對應着一個棧楨在Java虛擬機棧中入棧和出棧的過程。(局部變量包含基本數據類型、對象引用reference和returnAddress類型)
    • 本地方法棧

      • 本地方法棧與Java虛擬機棧類似,它們之間的區別只不過是本地方法棧為Native方法服務。

    線程公有

    • Java堆(GC區)(Java Head)

      • 幾乎所有的對象實例都在這裏分配內存,是垃圾收集器管理的主要區域。分為新生代和老年代。對於新生代又分為Eden空間、From Survivor空間、To Survivor空間。
    • JDK1.7 方法區(永久代)

      • 用於存放已被加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據。
        對這塊區域進行垃圾回收的主要目的是對常量池的回收和對類的卸載,但是一般難以實現。
        HotSpot虛擬機把它當做永久代來進行垃圾回收。但很難確定永久代的大小,因為它受到很多因素的影響,並且每次Full GC之後永久代的大小都會改變,所以經常拋出OOM異常。
        從JDK1.8開始,移除永久代,並把方法區移至元空間。
      • 運行時常量池

        • 是方法區的一部分
          Class文件中的常量池(編譯器生成的字面量和符號引用)會在類加載后被放入這個區域。
          允許動態生成,例如String類的intern()
    • JDK1.8 元空間

      • 原本存在方法區(永久代)的數據,一部分移到了Java堆裏面,一部分移到了本地內存裏面(即元空間)。元空間存儲類的元信息,靜態變量和常量池等放入堆中。
    • 直接內存

      • 在NIO中,會使用Native函數庫直接分配堆外內存。

    二、HotSpot虛擬機

    對象的創建

    • 當虛擬機遇到一條new指令時
    1. 檢查參數能否在常量池中找到符號引用,並檢查這個符號引用代表的類是否已經被加載、解析和初始過,沒有的話先執行相應的類加載過程。
    2. 在類加載檢查通過之後,接下來虛擬機將為新生對象分配內存。
    3. 內存分配完成之後,虛擬機需要將分配到的內存空間都初始化為零值(不包括對象頭)。
    4. 對對象頭進行必要的設置。
    5. 執行構造方法按照程序員的意願進行初始化。

    對象的內存布局

      1. 對象頭
        1. 第一部分用於存儲對象自身的運行時數據,如哈希碼、GC分代年齡、鎖狀態標識、線程持有的鎖、偏向線程ID、偏向實現戳等。
        1. 第二部分是類型指針,即對象指向它的類元數據的指針(如果使用直接對象指針訪問),虛擬機通過這個指針來確定這個對象是哪個類的實例。
        1. 如果對象是一個Java數組的話,還需要第三部分記錄數據長度的數據。
      1. 實例數據
      • 是對象真正存儲的有效信息,也就是在代碼中定義的各種類型的字段內容。
      1. 對齊填充
      • 不是必然存在的,僅僅起着佔位符的作用。
        HotSpot需要對象的大小必須是8字節的整數倍。

    對象的訪問定位

    • 句柄訪問

      • 在Java堆中劃分出一塊內存作為句柄池。
        Java棧上的對象引用reference中存儲的就是對象的句柄地址,而句柄中包含了到對象實例數據的指針和到對象類型數據的指針。
        對象實例數據在Java堆中,對象類型數據在方法區(永久代)中。
        優點:在對象被移動時只會改變句柄中的實例數據指針,而對象引用本身不需要修改。
    • 直接指針訪問(HotSpot使用)

      • Java棧上的對象引用reference中存儲的就是對象的直接地址。
        在堆中的對象實例數據就需要包含到對象類型數據的指針。
        優點:節省了一次指針定位的時間開銷,速度更快。

    三、垃圾收集

    概述

    • 垃圾收集主要是針對Java堆和方法區。
      程序計數器、Java虛擬機棧個本地方法棧三個區域屬於線程私有,線程或方法結束之後就會消失,因此不需要對這三個區域進行垃圾回收。

    判斷對象是否可以被回收

    • 第一次標記(緩刑)

      • 引用計數算法

        • 給對象添加一個引用計數器,當對象增加一個引用時引用計數值++,引用失效時引用計數值–,引用計數值為0時對象可以被回收。

    但是它難以解決對象之間的相互循環引用的情況,此時這個兩個對象引用計數值為1,但是永遠無法用到這兩個對象。

    - 可達性分析算法(Java使用)
    
        - 以一系列GC Roots的對象作為起始點,從這些節點開始向下搜索,搜索所走過的路徑稱為引用鏈,當一個對象到GC Roots沒有任何引用鏈相連是,則證明此對象不可用,可以被回收。

    GC Roots對象包括

    1. 虛擬機棧(棧楨中的本地變量表)中引用的對象。
    2. 方法區中共類靜態屬性引用的對象。
    3. 方法區中常量引用的對象。
    4. 本地方法棧中JNI(即一般說的Native方法)引用的對象。
    • 第二次標記

      • 當對象沒有覆蓋finalize()方法,或者finalize()方法已經被虛擬機調用過。
        如果對象在finalize方法中重新與引用鏈上的任何一個對象建立關聯則將不會被回收。
      • finalize()

        • 任何一個對象的finalize()方法都只會被系統調用一次。
          它的出現是一個妥協,運行代價高昂,不確定性大,無法保證各個對象的調用順序。
          finalize()能做的所有工作使用try-finally或者其他方式都可以做的更好,完全可以忘記在這個函數的存在。

    方法區的回收

    • 在方法區進行垃圾回收的性價比一般比較低。
      主要回收兩部分,廢棄常量和無用的類。

    滿足無用的類三個判斷條件才僅僅代表可以進行回收,不是必然關係,可以使用-Xnoclassgc參數控制。

    1. 該類的所有實例都已經被回收,也就是Java堆中不存在該類的任何實例。
    2. 加載該類的ClassLoader已經被回收。
    3. 該類對應的java.lang.Class對象沒有在任何地方被引用,無法在任何地方通過反射訪問到該類的方法。

    引用類型

      1. 強引用
      • 使用new一個新對象的方式來創建強引用。
        只要強引用還存在,被引用的對象則永遠不會被回收。
      1. 軟引用
      • 使用SoftReference類來實現軟引用。
        用來描述一些還有用但是並非必須的對象,被引用的對象在將要發生內存溢出異常之前會被回收。
      1. 弱引用
      • 使用WeakReference類來實現弱引用。
        強度比軟引用更弱一些,被引用的對象在下一次垃圾收集時會被回收。
      1. 虛引用
      • 使用PhantomReference類來實現虛引用。
        最弱的引用關係,不會對被引用的對象生存時間構成影響,也無法通過虛引用來取得一個對象實例。
        唯一目的就是能在這個對象被收集器回收時收到一個系統通知。

    垃圾收集算法

      1. 標記 – 清除
      • 首先標記出所有需要回收的對象,在標記完成后統一回收被標記的對象並取消標記。

    不足:

    1. 效率問題,標記和清除兩個過程的效率都不高。
    2. 空間問題,標記清除之後會產生大量不連續的內存碎片,沒有連續內存容納較大對象而不得不提前觸發另一次垃圾收集。
      1. 標記 – 整理
      • 和標記 – 清除算法一樣,但標記之後讓所有存活對象都向一段移動,然後直接清理掉端邊界以外的內存。
        解決了標記 – 清除算法的空間問題,但需要移動大量對象,還是存在效率問題。
      1. 複製
      • 將可用內存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着的對象複製到另外一塊上面,然後再把已使用多的內存空間一次清理掉。
        代價是將內存縮小為原來的一般,太高了。

    現在商業虛擬機都採用這種算法用於新生代。
    因為新生代中的對象98%都是朝生暮死,所以將內存分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor空間。
    當回收時,如果另外一塊Survivor空間沒有足夠的空間存放存活下來的對象時,這些對象將直接通過分配擔保機制進入老年代。

      1. 分代收集
      • 一般把Java堆分為新生代和老年代。
        在新生代中使用複製算法,在老年代中使用標記 -清除 或者 標記 – 整理 算法來進行回收。

    HotSpot的算法實現

    • 枚舉根節點(GC Roots)

      • 目前主流Java虛擬機使用的都是準確式GC。
        GC停頓的時候,虛擬機可以通過OopMap數據結構(映射表)知道,在對象內的什麼偏移量上是什麼類型的數據,而且特定的位置記錄著棧和寄存器中哪些位置是引用。因此可以快速且準確的完成GC Roots枚舉。
    • 安全點

      • 為了節省GC的空間成本,並不會為每條指令都生成OopMap,只是在“特定的位置”記錄OopMap,這些位置稱為安全點。

    程序執行只有到達安全點時才能暫停,到達安全點有兩種方案。

    1. 搶斷式中斷(幾乎不使用)。GC時,先把所有線程中斷,如果有線程不在安全點,就恢復該線程,讓他跑到安全點。
    2. 主動式中斷(主要使用)。GC時,設置一個標誌,各個線程執行到安全點時輪詢這個標誌,發現標誌為直則掛起線程。

    但是當線程sleep或blocked時無法響應JVM的中斷請求走到安全點中斷掛起,所以引出安全區域。

    • 安全區域

      • 安全區域是指在一段代碼片段之中,引用關係不會發生變化,是擴展的安全點。

    線程進入安全區域時表示自己進入了安全區域,這個發生GC時,JVM就不需要管這個線程。
    線程離開安全區域時,檢查系統是否完成GC過程,沒有就等待可以離開安全區域的信號為止,否者繼續執行。

    垃圾收集器

    • 新生代

        1. serial收集器
        • 它是單線程收集器,只會使用一個線程進行垃圾收集工作,更重要的是它在進行垃圾收集時,必須暫停其他所有的工作線程。

    優點:對比其他單線程收集器簡單高效,對於單個CPU環境來說,沒有線程交互的開銷,因此擁有最高的單線程收集效率。

    它是Client場景下默認新生代收集器,因為在該場景下內存一般來說不會很大。

    - 2. parnew收集器
    
        - 它是Serial收集器的多線程版本,公用了相當多的代碼。

    在單CPU環境中絕對不會有比Serial收集器更好的效果,甚至在2個CPU環境中也不能百分之百超越。

    它是Server場景下默認的新生代收集器,主要因為除了Serial收集器,只用它能與CMS收集器配合使用。

    - 3. parallel scavenge收集器
    
        - “吞吐優先”收集器,與ParNew收集器差不多。

    但是其他收集器的目標是盡可能縮短垃圾收集時用戶線程停頓的時間,而它的目標是達到一個可控制的吞吐量。這裏的吞吐量指CPU用於運行用戶程序的時間佔總時間的比值。

    • 老年代

        1. serial old收集器
        • 是Serial收集器老年代版本。

    也是給Client場景下的虛擬機使用的。

    - 5. parallel old收集器
    
        - 是Parallel Scavenge收集器的老年代版本。

    在注重吞吐量已經CPU資源敏感的場合,都可以優先考慮Parallel Scavenge和Parallel Old收集器。

    - 6. cms收集器
    
        - Concurrent Mark Sweep收集器是一種以獲取最短回收停頓時間為目標的收集器。
        - 運作過程
    
            - 1. 初始標記(最短)。仍需要暫停用戶線程。只是標記一下GC Roots能直接關聯到的對象,速度很快
    1. 併發標記(耗時最長)。進行GC Roots Tracing(根搜索算法)的過程。
    2. 重新標記。修正併發標記期間因用戶程序繼續運作而導致標記產生變動的那一部分對象的標記記錄。比初始標記長但遠小於併發標記時間。
    3. 併發清除

    1 和4 兩個步驟並沒有帶上併發兩個字,即這兩個步驟仍要暫停用戶線程。

        - 優缺點
    
            - 併發收集、低停頓。
    1. CMS收集器對CPU資源非常敏感。雖然不會導致用戶線程停頓,但是佔用CPU資源會使應用程序變慢。
    2. 無法處理浮動垃圾。在併發清除階段新垃圾還會不斷的產生,所以GC時要控制“-XX:CMSinitiatingOccupancyFraction參數”預留足夠的內存空間給這些垃圾,當預留內存無法滿足程序需要時就會出現”Concurrent Mode Failure“失敗,臨時啟動Serial Old收集。
    3. 由於使用標記 – 清除算法,收集之後會產生大量空間碎片。
      1. g1收集器
      • Garbage First是一款面向服務端應用的垃圾收集器
      • 運作過程

          1. 初始標記
    1. 併發標記
    2. 最終標記
    3. 刪選標記

    五、類加載機制

    概述

    • 虛擬機把描述類的數據從Class問價加載到內存,並對數據進行校驗、轉換解析和初始化,最終形成可以被虛擬機直接使用的Java類型。
      Java應用程序的高度靈活性就是依賴運行期動態加載和動態連接實現的。

    類的生命周期

    • 加載 -> 連接(驗證 -> 準備 -> 解析) -> 初始化 -> 使用 – >卸載

    類初始化時機

    • 主動引用

      • 虛擬機規範中沒有強制約束何時進行加載,但是規定了有且只有五種情況必須對類進行初始化(加載、驗證、準備都會隨之發生)
    1. 遇到new、getstatic、putstatic、invokestatic這四條字節碼指令時沒有初始化。
    2. 反射調用時沒有初始化。
    3. 發現其父類沒有初始化則先觸發其父類的初始化。
    4. 包含psvm(mian()方法)的那個類。
    5. 動態語言支持時,REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄。
    • 被動引用

      • 除上面五種情況之外,所有引用類的方式都不會觸發初始化,稱為被動引用。
    1. 通過子類引用父類的靜態字段,不會導致子類的初始化。
    2. 通過數組定義來引用類,不會觸發此類的初始化。該過程會對數組類進行初始化,數組類是一個由虛擬機自動生成的、直接繼承Object的子類,其中包含數組的屬性和方法,用戶只能使用public的length和clone()。
    3. 常量在編譯階段會存入調用類的常量池中,本質上並沒有直接引用到定義常量的類,因此不會觸發定義常量的類的初始化。

    類加載過程

      1. 加載
        1. 通過類的全限定名來獲取定義此類的二進制字節流。
    1. 將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構。
    2. 在內存中生成一個代表這個類的java.lang.Class對象(HotSpot將其存放在方法區中),作為方法區這個類的各種數據的訪問入口。
      1. 驗證
      • 為了確保Class文件的字節類中包含的信息符合當前虛擬機的要求,並且不會危害虛擬機自身的安全。可以通過-Xverify:none關閉大部分類驗證。
    1. 文件格式驗證。確保輸入字節流能正確的解析並存儲於方法區,後面的3個驗證全部基於方法區的存儲結構進行,不會再操作字節流。
    2. 元數據驗證。對字節碼描述信息進行語義分析,確保其符合Java語法規範。(Java語法驗證)
    3. 字節碼驗證。最複雜,通過數據流和控制流分析,確定程序語義時合法的、符合邏輯的。可以通過參數關閉。(驗證指令跳轉範圍,類型轉換有效等)
    4. 符號引用驗證。將符號引用轉化為直接引用,發生在第三個階段——解析階段中發生。
      1. 準備
      • 類變量是被static修飾的變量,準備階段為類變量分配內存並設置零值(final直接設置初始值),使用的是方法區的內存。
      1. 解析
      • 將常量池內的符號引用替換為直接引用的過程。
        其中解析過程在某些情況下可以在初始化階段之後再開始,這是為了支持Java的動態綁定。
        解析動作主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄、和調用點限定符。
      1. 初始化
      • 初始化階段才真正執行類中定義的Java程序代碼,是執行類構造器 ()方法的過程。
        在準備階段,類變量已經給過零值,而在初始化階段,根據程序員通過程序制定的主觀計劃去初始化類變量和其他資源。

        • ()

          • 類構造器方法。是由編譯器自動收集類中的所有類變量的賦值動作和靜態語句塊中的的語句合併產生的。
    1. 不需要顯式調用父類構造器,JVM會保證在子類clinit執行之前,父類的clinit已經執行完成。
    2. 接口中不能使用靜態語句塊但仍可以有類變量的賦值操作。當沒有使用父接口中定義的變量時子接口的clinit不需要先執行父接口的clinit方法。接口的實現類也不會執行接口的clinit方法。
    3. 虛擬機會保證clinit在多線程環境中被正確的加鎖、同步。其他線性喚醒之後不會再進入clinit方法,同一個類加載器下,一個類型只會初始化一次。

       - <init>()
      
           - 對象構造器方法。Java對象被創建時才會進行實例化操作,對非靜態變量解析初始化。
    4. 會顯式的調用父類的init方法,對象實例化過程中對實例域的初始化操作全部在init方法中進行。

    類(加載) 器

    • 類與類加載器

      • 類加載器實現類的加載動作。
        類加載器和這個類本身一同確立這個類的唯一性,每個類加載器都有獨立的類命名空間。在同一個類加載器加載的情況下才會有兩個類相等。
        相等包括類的Class對象的equals()方法、isAssignableFrom()方法、isInstance()、instanceof關鍵字。
    • 類加載器分類

      • 啟動類加載器

        • 由C++語言實現,是虛擬機的一部分。負責將JAVA_HOME/lib目錄中,或者被-Xbootclasspath參數指定的路徑,但是文件名要能被虛擬機識別,名字不符合無法被啟動類加載器加載。啟動類加載器無法被Java程序直接引用。
      • 擴展類加載器

        • 由Java語言實現,負責加載JAVA_HOME/lib/ext目錄,或者被java.ext.dirs系統變量所指定的路徑中的所有類庫,開發者可以直接使用擴展類加載器。
      • 應用程序類加載器

        • 由於這個類加載器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也稱他為系統類加載器。負責加載用戶類路徑(ClassPath)上所指定的類庫,一般情況下這個就是程序中默認的類加載器。
      • 自定義類加載器

        • 由用戶自己實現。
    1. 如果不想打破雙親委派模型,那麼只需要重寫findClass方法即可。
    2. 否則就重寫整個loadClass方法。
    • 雙親委派模型

      • 雙親委派模型要求除了頂層的啟動類加載器外,其餘的類加載器都應該有自己的父類加載器。父子不會以繼承的關係類實現,而是都是使用組合關係來服用父加載器的代碼。
        在java.lang.ClassLoader的loadClass()方法中實現。
      • 工作過程

        • 一個類加載器首先將類加載請求轉發到父類加載器,只有當父類加載器無法完成(它的搜索範圍中沒有找到所需要的類)時才嘗試自己加載
      • 好處

        • Java類隨着它的類加載器一起具備了一種帶有優先級的層次關係,從而使得基礎類庫得到同意。

    四、內存分配與回收策略

    Minor GC 和 Full GC

    • Minor GC

      • 發生在新生代的垃圾收集動作,因為新生代對象存活時間很短,因此Minor GC會頻繁執行,執行速度快。
      • 時機

        • Eden不足
    • Full GC

      • 發生在老年區的GC,出現Full GC時往往伴隨着Minor GC,比Minor GC慢10倍以上。
      • 時機

          1. 調用System.gc()
          • 只是建議虛擬機執行Full GC,但是虛擬機不一定真正去執行。
            不建議使用這種方式,而是讓虛擬機管理內存。
          1. 老年代空間不足
          • 常見場景就是大對象和長期存活對象進入老年代。
            盡量避免創建過大的對象以及數組,調大新生代大小,讓對象盡量咋新生代中被回收,不進入老年代。
          1. JDK1.7 之前方法區空間不足
          • 當系統中要加載的類、反射的類和常量較多時,永久代可能會被佔滿,在未配置CMS GC的情況下也會執行Full GC,如果空間仍然不夠則會拋出OOM異常。
            可採用增大方法區空間或轉為使用CMS GC。
          1. 空間分配擔保失敗
          • 發生Minor GC時分配擔保的兩個判斷失敗
          1. Concurrent Mode Failure
          • CMS GC 併發清理階段用戶線程還在執行,不斷有新的浮動垃圾產生,當預留空間不足時報Concurrent Mode Failure錯誤並觸發Full GC。

    內存分配策略

      1. 對象優先在Eden分配
      • 大多數情況下,對象在新生代Eden上分配,當Eden空間不夠時,發起Minor GC,當另外一個Survivor空間不足時則將存活對象通過分配擔保機制提前轉移到老年代。
      1. 大對象直接進入老年代
      • 配置參數-XX:PretenureSizeThreshold,大於此值得對象直接在老年代分配,避免在Eden和Survivor之間的大量內存複製。
      1. 長期存活對象進入老年代
      • 虛擬機為每個對象定義了一個Age計數器,對象在Eden出生並經過Minor GC存活轉移到另一個Survivor空間中時Age++,增加到默認16則轉移到老年代。
      1. 動態對象年齡綁定
      • 虛擬機並不是永遠要求對象的年齡必須到達MaxTenuringThreshold才能晉陞老年代,如果在Survivor中相同年齡所有對象大小總和大於Survivor空間的一半,則年齡大於或等於該年齡的對象直接進入老年代。
      1. 空間分配擔保
      • 在發生Minor GC之前,虛擬機先檢查老年代最大可用的連續空間是否大於新生代的所有對象,如果條件成立,那麼Minor GC可以認為是安全的。
        可以通過HandlePromotionFailure參數設置允許冒險,此時虛擬機將與歷代晉陞到老年區對象的平均大小比較,仍小於則要進行一次Full GC。
        在JDK1.6.24之後HandlePromotionFailure已無作用,即虛擬機默認為true。

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

    【其他文章推薦】

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

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

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

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

  • 痞子衡嵌入式:串行EEPROM接口事實標準及SPI EEPROM簡介

    痞子衡嵌入式:串行EEPROM接口事實標準及SPI EEPROM簡介

      大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家介紹的是EEPROM接口標準及SPI EEPROM

      痞子衡之前寫過一篇文章 ,介紹過并行NOR Flash基本概念。眾所周知,現如今嵌入式非易失性存儲器基本被NOR Flash一統江湖了,但在Flash技術發明之前,EEPROM才是非易失性存儲器的霸主。EEPROM的全稱是”電可擦除可編程只讀存儲器”,即Electrically Erasable Programmable Read-Only Memory,EEPROM技術的發明可是拯救過一大批嵌入式工程師的,畢竟在這之前非易失性存儲器技術的演進分別是ROM(只讀), PROM(只能寫一次), EPROM(紫外線可擦除),擦除方式都不太友好,直到EEPROM的出現才變得人性化。雖說現在Flash是主流,但在較低容量(2Mb以下)尤其是超低容量(1Kb以下)的市場,EEPROM仍然有其不可替代的應用場合。今天痞子衡就來好好聊一聊EEPROM:

    一、EEPROM背景簡介

      聊到EEPROM發展史,不得不提浮柵MOSFET,這是一項發明於1967年的技術,它是所有閃存的基礎。1970年,第一款成功的浮柵型器件-EPROM被發明。1979年,大名鼎鼎的SanDisk(閃迪)創始人Eli Harari,發明了世界上首個電可擦除的浮柵型器件即EEPROM。
      講到EEPROM必然要將它和與其相愛相殺的Flash一起對比。關於Flash大家都很熟悉,但其實Flash全稱應該叫Flash EEPROM,它屬於廣義的EEPROM。而本文主角EEPROM,指的是狹義的EEPROM,Flash和EEPROM最大的區別是:Flash按扇區操作,EEPROM按字節操作。Flash的特點是結構簡單,容量可以做得比較大且在大數據量下的操作速度更快,但缺點是操作過程麻煩,所以Flash適於當不需頻繁改寫的程序存儲器。而在有些應用中往往需要頻繁的改寫某些小量數據且需掉電非易失,傳統結構的EEPROM則非常適合。
      EEPROM不像NOR, NAND Flash技術演進得那麼複雜,因此實際上關於EEPROM並沒有成文的標準,即使最知名的电子行業標準之一JEDEC也沒有關於EEPROM的標準出台,不過各大廠商生產的EEPROM似乎都遵從某種約定的事實標準,這在後面介紹的EEPROM接口命令里顯得尤為明顯。

    二、Serial EEPROM原理

    2.1 Serial EEPROM分類

      從軟件驅動開發角度而言,Serial EEPROM可以從以下幾個方面進一步細分:

    地址碼長度:1byte / 2byte / 3byte
    通信接口類型:I2C / SPI / Microwire / UNIO Bus / Single-Wire

      本文的主要研究對象是SPI接口的EEPROM。

    2.2 SPI EEPROM內存模型

      EEPROM內存單元從大到小一般分為如下4層:Device、Sector、Page、Byte,其中Sector不是必有的,並且Page也只是個結構概念,跟NOR Flash里的Page/Sector意義不一樣,因為Byte就是EEPROM讀寫的最小單元(即可以任意地址隨機訪問),所以你可以把EEPROM當做一個非易失性的RAM。當然有些高端EEPROM中集成了Page/Sector操作命令,這隻是為了讓EEPROM操作效率更高而已。

    2.3 SPI EEPROM信號與封裝

      SPI EEPROM一般有8個腳,除去電源Vcc,地GND/Vss,以及SPI四根信號線(CS#, SCK, SI, SO)不言而喻之外,還有兩根特殊的控制信號,即WP#(寫保護)和HOLD#(掛起)。WP#信號主要是從硬件層面上對EEPROM內存進行保護,防止電路上的噪聲干擾篡改了EEPROM里的內容;而HOLD#則提供EEPROM寫操作暫停的功能,當該信號有效的時候,SI信號輸入將被忽略,因此主機可以做其他更高優先級的事情。

      SPI EEPROM雖然只有8pin,但是封裝種類還是比較齊全的,這其中最經典的當屬JEDEC定義的8-lead SOIC,此外還有TSSOP8, UDFN8, WLCSP8,下圖羅列了常見封裝:

    2.4 SPI EEPROM接口命令

    2.4.1 事實標準

      痞子衡在文章開頭的時候講過,SPI EEPROM並沒有什麼成文的接口命令標準,但是各大廠商生產的SPI EEPROM無一例外都支持下錶的6條命令,即READ(讀內存)、WRITE(寫內存)、WREN(寫使能)、WRDI(寫禁止)、RDSR(讀狀態寄存器)、WRSR(寫狀態寄存器),所以從軟件接口層面而言,這6條命令就是SPI EEPROM事實上的接口命令標準。

      除了6條標準命令外,SPI EEPROM內部還有一個8bit的狀態寄存器,用於反饋命令執行狀態,這8bit狀態寄存器的位定義也是存在如下錶所示的事實標準的:

      不考慮寫保護特性的話,bit0 – RDY#和bit1 – WEL是比較常用的,RDY#位主要用於標示所有涉及改變內存或狀態寄存器的命令的執行結果,WEL位則保存了上一次WREN和WRDI命令的執行結果。狀態寄存器中的其他兩處定義bit7 – WPEN, bit[3:2] – BP[1:0]則主要與寫保護特性有關,它們的具體作用如下:

    2.4.2 廠商個性化

      除了6條事實標準的命令外,有些廠商還實現了一些自定義的命令,這些命令並不一定通用,一般用於較大容量(3byte地址碼,512Kb以上)的EEPROM上。痞子衡找了一款非常經典的EEPROM,來自Microchip的25AA系列(25AA1024),讓我們看看它有啥個性化的命令。這顆EEPROM容量為1Mb,屬於大容量EEPROM,為了提高EEPROM操作效率,Microchip為這顆EEPROM增加了Page/Sector/Chip Erase命令,使得擦除操作效率變高了,如果沒有這些個性化擦除命令,那麼只能通過標準WRITE命令去手動實現擦除操作,既麻煩又低效。

    2.5 SPI EEPROM數據速率

      數據存取速率是個重要的技術指標,咱們來看看SPI EEPROM的讀寫時序,前面痞子衡在講EEPROM分類的時候提到過EEPROM地址碼有1byte/2byte/3byte之分,地址碼的區別主要體現了EEPROM讀寫時序上。對於讀時序,在SPI總線發完READ(0x03)命令后,緊接着要發送想要讀取的內存地址,地址碼不同,發送的地址字節數也不同。對於容量大於512Kb的EEPROM(即地址碼為3byte),顯然要發送3byte的地址,才能確定要讀的數據所在地址,然後才能進行讀數據操作。

      而對於容量小於等於512Kb的EEPROM,關於1byte和2byte地址碼區分,有一個特殊的設計,即對於512byte容量的EEPROM,按容量來說其屬於2byte地址碼範疇,READ命令后需要發送2byte地址,但實際上只需要發送1byte地址(A7-A0),而最高地址位A8放在了READ命令碼bit3里,這樣可以節省1個字節的地址碼。因此1Kb – 512Kb容量的EEPROM地址碼為2byte,512byte及以下容量的EEPROM地址碼為1byte,如下圖所示:

      從上面讀時序可以看出,READ命令碼和地址碼發完之後幾乎沒有等待周期,就可以直接讀取EEPROM中數據,因此EEPROM讀數據速率完全取決於SPI總線速率,所以我們只需要打開EEPROM數據手冊,看看它最高能支持多高的SPI總線速率即可(常見的有2MHz/5MHz/10MHz/20MHz)。
      對於寫時序,就稍微複雜一些了,這裏不考慮地址碼區別,以2byte地址為例。首先在發送WRITE命令之前需要發送一個WREN命令使能寫操作,因為默認EEPROM在執行完上一次寫操作後會恢複寫禁止狀態,在發送WRITE命令進行寫操作之前必須保證EEPROM處於寫使能狀態。

      確保EEPROM進入寫使能狀態后,開始發送WRITE命令,然後是地址碼,接着是要寫入的數據,痞子衡前面講過Page在EEPROM是個結構概念,但其實也跟WRITE命令有關,因為EEPROM既可以按byte去寫,也可以按Page去寫,如果需要存入連續的數據,顯然按Page去寫效率比按Byte寫入更高。這裏需要注意的是,WRITE命令後面跟的字節數不能超過要寫入的首地址所在Page剩餘的字節數。下圖示例的Page寫時序最大byte數為16/32,是因為示例EEPROM的page size即16/32 byte。

      當一次WRITE時序內要寫入的數據全部發送完成之後,底下便進入等待周期,與READ時序不同的是,WRITE時序有等待周期,因為EEPROM內部要將緩存在page buffer里的數據編程到真正的內存空間里,這需要時間。用戶只能通過不斷地發送如下RDSR命令去讀取狀態寄存器bit0 – RDY#來判斷WRITE等待周期是否結束。因此寫時序速率不僅僅取決於SPI總線速率,還取決於等待周期時長。

      如果想快捷地了解SPI EEPROM的性能,最簡單的就是打開SPI EEPROM手冊,看首頁的feature介紹,如下是25AA080的簡要feature:

    • Max. Clock 10 MHz
    • 1024 x 8-bit Organization
    • 16 Byte Page (‘C’ version devices)
    • 32 Byte Page (‘D’ version devices)
    • Self-Timed Erase and Write Cycles (5 ms max.)
    • Block Write Protection:
      - Protect none, 1/4, 1/2 or all of array
    • Built-In Write Protection:
      - Power-on/off data protection circuitry
      - Write enable latch
      - Write-protect pin
    • Sequential Read
    • High Reliability:
      - Endurance: > 1M erase/write cycles
      - Data retention: > 200 years

    三、SPI EEPROM產品

      最後痞子衡收集了可以售賣SPI EEPROM芯片的廠商及產品系列:

    廠商 芯片系列 官方網址
    Microchip
    Atmel
    25AA, 25LC
    AT25
    ST M95
    Onsemi CAT25
    Renesas R1EX25
    Rohm BR25A, BR25G, BR25H, BR25S
    Fudan Micro FM25

      至此,EEPROM接口標準及SPI EEPROM痞子衡便介紹完畢了,掌聲在哪裡~~~

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

    【其他文章推薦】

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

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

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

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

  • 玩轉VSCode-完整構建VSCode開發調試環境

    玩轉VSCode-完整構建VSCode開發調試環境

    隨着VSCode的不斷完善和強大,是時候將部分開發遷移到VS Code中了。

    目前使用VS2019開發.NET Core應用,一直有一個想法,在VS Code中復刻VS的開發環境,同時遷移到VS Code。

    那麼現在就開始吧。

    首先,安裝最新版的VS Code:,安裝完成后可能會提示升級,升級即可,升級后的版本信息:

    版本: 1.40.1 (system setup)
    提交: 8795a9889db74563ddd43eb0a897a2384129a619
    日期: 2019-11-13T16:49:35.976Z
    Electron: 6.1.2
    Chrome: 76.0.3809.146
    Node.js: 12.4.0
    V8: 7.6.303.31-electron.0
    OS: Windows_NT x64 10.0.16299

    接下來的操作分為幾個步驟:

    1. 安裝各種強大VS Code插件

    2. 創建.NET Core解決方案和工程

    3. 調試運行

    好的,那我們開始吧。

    一、安裝各種強大的VS Code插件

    1. C# extension for Visual Studio Code

    這個插件最重要的功能:

    • Lightweight development tools for .
    • Great C# editing support, including Syntax Highlighting, IntelliSense, Go to Definition, Find All References, etc.
    • Debugging support for .NET Core (CoreCLR). NOTE: Mono debugging is not supported. Desktop CLR debugging has .
    • Support for project.json and csproj projects on Windows, macOS and Linux.

    2. C# Extensions

    這個插件最有用的功能是可以右鍵新建C#類和C#接口,同時支持各種code snippets,例如 ctor 、prop等,具體功能特性,可以查看插件的說明。

     3. Auto-Using for C#

    這個插件自動添加using引用。

    4. vscode-solution-explorer

    這個插件給VS Code增加了解決方案tab, 支持新建解決方案、新建工程、添加引用、Nuget包,這個插件非常有用

    Adds a Solution Explorer panel where you can find a Visual Studio Solution File Explorer.

    • Can load any .sln version

    • Supports csproj, vcxproj, fsproj and vbproj (from vs2017 and before)

    • Supports dotnet core projects

    • You can create, delete, rename or move project folders and files.

    • You can create, delete, rename or move solution, solution folders and projects.

    • You can add or remove packages and references when the project is of kind CPS (dotnet core).

     

    5. Code Runner(韓俊老師出品,必屬精品)

    Run code snippet or code file for multiple languages: C, C++, Java, JavaScript, PHP, Python, Perl, Perl 6, Ruby, Go, Lua, Groovy, PowerShell, BAT/CMD, BASH/SH, F# Script, F# (.NET Core), C# Script, C# (.NET Core), VBScript, TypeScript, CoffeeScript, Scala, Swift, Julia, Crystal, OCaml Script, R, AppleScript, Elixir, Visual Basic .NET, Clojure, Haxe, Objective-C, Rust, Racket, Scheme, AutoHotkey, AutoIt, Kotlin, Dart, Free Pascal, Haskell, Nim, D, Lisp, Kit, and custom command

    即選中一段代碼,直接run

    6. vscode-icons

    通過這個插件,給各個文件和文件夾一個你更熟悉的圖標

    7. Visual Studio IntelliCode

    VS代碼智能提示,根據上下文語境,自動推薦你下一步用到的代碼,後台基於AI的

    8. NuGet Package Manager

    Nuget包管理,快速查詢定位Nuget包,並安裝。不過嘗試了一下午自定義Nuget源,沒搞定,估計是URL不對

    9. Docker

    10. Kubernetes

    其他的還需要配置GitHub、TFS類似的源代碼管理,TFS搞了兩個插件,都不好使,後續搞定后再更新一次。

     

    二、創建.NET Core解決方案和工程

    此時,VS Code的環境基本配置差不多了,接下來有兩種模式,創建解決方案和工程。

    1. 通過vscode-solution-explorer

    解決方案有了,很熟悉的感覺。

    我們可以繼續創建工程:右鍵sln,Add new project:

    此時會彈出工程模板,此時我們選擇ASP.NET Core Web API工程

    選擇C#

    然後繼續輸入工程名稱:例如 TestWebApi

    熟悉的感覺來了。此時就可以開始coding了。

    以上是我們通過vscode-solution-explorer新建解決方案和工程。同時我們可以通過命令行來搞定。

    2. 通過Dotnet CLI命令行

    新建sln:

    dotnet "new" "sln" "-n" "EricTest" "-o" "e:\Work\ServiceDependency"

    新建ASP.NET Core WebAPI工程

    dotnet "new" "webapi" "-lang" "C#" "-n" "TestWebApi" "-o" "TestWebApi"

    將TestWebApi工程添加到解決方案EricTest

    dotnet "sln" "e:\Work\ServiceDependency\EricTest.sln" "add" "e:\Work\ServiceDependency\TestWebApi\TestWebApi.csproj"

    三、調試運行

    在Debug選項卡中新增調試配置,重點設置要調試的program

    保存后,啟動調試:

     

    程序中增加斷點,然後

    輸入URL:https://localhost:5001/WeatherForecast

     既可以調試了。

     

    以上是今天集中配置VS Code開發調試環境的總結,分享給大家。

     

    周國慶

    2019//11/16

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

    【其他文章推薦】

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

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

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

  • Ansibile之playbook初識

    Ansibile之playbook初識

      一、playbook簡介

      ansiblie的任務配置文件被稱為playbook,俗稱“劇本”,每一個劇本(playbook)中都包含了一系列的任務,這每個任務在ansible中又被稱為“戲劇”(play),一個劇本中包含多齣戲劇。。

      前文我們了解了ansible有兩種執行方式ad-hoc和ansible-playbook,ad-hoc主要用於臨時命令的執行,而playbook我們可以理解為ad-hoc的集合,有點類似shell腳本,ad-hoc就相當於shell腳本里的某條任務語句,playbook就相當於整個shell腳本。playbook是由一個或多個“play”組成的列表,play的主要功能在於將預定義的一組主機,裝扮成事先通過ansible中的task定義好的角色。task實際是調用ansible的一個模塊,將多個play組織在一個playbook中,即可以讓他們聯合起來,按事先編排的機制執行預定義的動作。

     如以上圖示,用戶可以把多條任務(ad-hoc任務)寫到playbook中,用戶用ansible-playbook命令調用執行編排好的playbook,ansible會讀取playbook中的每一條play和task,並按照playbook中的順序從上至下依次執行,ansible會調用每個task中定義的模塊去依次執行相應的任務,並按照playbook中指定的主機去主機清單里匹配對應的主機,然後通過ssh認證,把編譯好的相應的任務文件發送到對應的主機或網絡設備上執行,最後返回執行的狀態。

      二、YAML簡介

      playbook採用yaml語言編寫,yaml是一個可讀性高的用來表達資料序列格式的語言,它參考了其他很多種語言,包括:XML、C語言、python、perl以及电子郵箱格式RFC2822等。Clark Evans在2001年首次發表了這種語言,另外Ingy döt Net與Oren Ben-Kiki也是這語言的共同設計者。YAML( YAML Ain’t Markup Language),即yaml不是標記語言。不過在開發這種語言時,yaml的意思其實是:”Yet Another Markup Language”(仍是一種標記語言)

      ymal特性

      1)YAML的可讀性好

      2)YAML和腳本語言的交互性好

      3)YAML使用實現語言的數據類型

      4)YAML有一個一致的信息模板

      5)YAML易於實現,可以基於流程處理,表達能力強,擴展性好

    更多的內容及規範請參考官方文檔

      三、playbook語法簡介

      1)需要以“—”(3個減號)開始,且需頂行首寫。另外還有選擇性的連續三個點號(…)用來表示文件的結尾。

      2)次行開始正常寫playbook的內容,建議次行寫該playbook的功能,當然不寫也是可以的。

      3)使用“#”號註釋代碼。

      4)縮進必須統一,不能空格tab混用。

      5)縮進的級別必須是一致的,同樣的縮進代表同樣級別,程序判別配置的級別是通過縮進結合換行來實現的。

      6)YAML文件內容和Linux系統大小寫判斷方式一直,區分大小寫(大小寫敏感),k/v的值均大小寫敏感。

      7)k/v的值可同行寫也可換行寫。同行使用“:”分隔,換行寫需要以“-”分隔。

      8)v可以是字符串,也可以另外一個列表,當然也可以是字典。

      9)一個完整的代碼塊功能最少需要有name:xxx(對任務的描述)。

      10)一個name只能包括一個task

      11)yaml文件擴展名通常為yml或yaml

    list:列表,其所有元素均使用“-”開頭

    示例:

    ---
    # A list of tasty fruits
    
    - apple
    - orange
    - strawberry
    - mango
    ~           

    dictionary:字典,通常由多個key與value構成

    示例:

    ---
    #An employee record
    name: example developer
    job: developer
    skill: elite  

    當然也可以將key:value放置於{}中進行表示,用“,”分隔多個key:value

    示例:

    ---
    #An employee record
    {name: example developer,job: developer, skill: elite}
    ~                                                         

      YAML的語法和其他高階語言類似,並且可以簡單表達清單、散列表、標量等數據結構。其結構(Structure)通過空格來展示,序列(Sequence)里的項用”-“來代表,Map里的鍵值對用”:”分隔。

    示例:

    ---
    name: John Smith
    age: 41
    gender: Male
    spouse:
      name: Jane Smith
      age: 37
      gender: Female
    children:
      - name: Jimmy Smith
        age: 17
        gender: Male
      - name: Jenny Smith
        age: 13
        gender: Female
    ~                    
    

      四、playbook核心元素

      1)hosts  :指定執行任務的遠程主機列表(主機清單定義的主機組或單個主機,支持前面的說的主機模式匹配)

      2)tasks  :任務集

      3)varniables  :內置變量或自定義變量在playbook中調用

      4)templates  :模板,可替換模板文件中的變量並實現一些簡單邏輯的文件

      5)handlers  和  notity結合使用,由特定條件出發的操作,滿足條件方才執行,否則不執行

      6)tags標籤  :給指定的任務貼上標籤,我們在執行playbook的時候可以根據標籤選擇性的挑選部分代碼執行,如 ansible-playbook -t tagsname useradd.yml  ,這條命令的意思就是在useradd.yml中挑選標籤名為tagsname的任務執行

       五、playbook基礎組件

      1)hosts:

        playbook中的每一個play的目的都是為了讓特定主機以某個指定的用戶身份執行任務。hosts用於指定要執行任務的主機,須事先定義在主機清單中。hosts指定主機的形式同樣支持像主機清單中定義的那樣,支持通配,支持主機模式匹配與或非,支持IP地址,當然也支持混合匹配與或非。

    示例:在websers組,但不再dbsers組,可以這樣定義hosts

    ---
    - hosts: websers:!dbsers
    

      2)remote_user:可用於host和task中,也可以通過指定其通過sudo的方式在遠程執行任務,其可用於play全局或某個任務;此外,甚至可以在sudo時使用用sudo_user指定sudo時切換的用戶,如下所示

    ---
    - hosts: websers:!dbsers
      remote_user: root
    
      tasks:
        - name: test connection
          ping:
          remote_user: qiuhom
          sudo: yes
          sudo_user: qiuping
    

      說明:默認sudo 為root,上例指定了sudo_user 為qiuping,上述任務同sudo -u qiuping ping xxxx(代表某主機)命令一樣的意思,當然在使用sudo 時 我們還需要在目標主機上對qiuhom授權,要讓qiuhom這個用戶具有代表qiuping的權限去執行ping命令。

      3)task列表和action:play的主體部分是task list,task list 中的各任務按次序逐個在hosts中指定的所有主機上執行,即在所有主機上完成第一個任務后,再開始第二個任務;task的目的是使用指定的參數執行模塊,而在模塊參數中可以使用變量,模塊執行時是冪等的,這意味着多次執行時是安全的,其結果均一致;每個task都應該有其name,用於playbook的執行結果輸出,建議其內容能清晰地描述任務步驟,如未提供name,則action的結果將用於輸出。

      tasks:任務列表,它有兩種格式如下

        (1)action: module arguments

        (2)module: arguments        ##建議使用

       注意:shell模塊和command模塊後面跟的是命令,而非key=value

    如果某項任務的狀態在運行後為changed時,可通過“notify”通知給相應的handlers;當然任務可以通過“tags”打標籤,可以在ansible-playbook命令上使用-t指定進行指定其標籤名調用。

    示例:

    [qiuhom@test ~]$cat test.yml 
    ---
    - hosts: websers:!dbsers
      remote_user: root
    
      tasks:
        - name: test connection
          ping:
          remote_user: qiuhom
          sudo: yes
          sudo_user: qiuping 
          tags: test
        - name: test command
          shell: /bin/ls /home/qiuhom/
    [qiuhom@test ~]$ansible-playbook -t test test.yml 
    

      說明:用-t 指定標籤名,表示只運行所指定標籤所在的任務,當然同名的標籤可以在多條任務中,一個任務也可以有多個標籤。

    如果命令或腳本的退出碼不為零,可用使用如下方式忽略或跳過繼續執行以下代碼

    ---
    - hosts: websers:!dbsers
      remote_user: root
    
      tasks:
        - name: run this command and ignore the result
          shell: /usr/sbin/ip addr show eth0 || /bin/true
        - name: run this command and ignore the result
          shell: /usr/sbin/ip addr show eth0
          ignore_errors: True
    

      說明:兩種方式都可以跳過出錯的命令而不打斷playbook,繼續執行以下的代碼,前者使用的短路或的特性,後者使用ignore_errors參數來控制

      六、playbook運行的方式

    ansible-playbook <filename.yml> ... [options]
    

    常用選項:

      -C , –check  : 只檢查可能會發生的改變,但不真正執行操作,相當於空跑一遍playbook,測試下是否和自己預想的結果一樣,但它不會真正的去遠端主機上執行。常用於測試寫的playbook語法是否有誤。

      –list-hosts  :列出playbook指定運行任務所匹配的主機

      –list-tags    :列出playbook中所有標籤名稱列表

      –list-tasks  :列出playbook中所有任務名稱及標籤名稱

      –limit 主機列表   :只針對指定主機列表中的主機執行當前playbook(指定主機列表必須是在playbook里定義的主機列表範圍內)

      -v,-vv,-vvv       :  显示執行playbook的過程,-v,显示較簡單,-vv显示較詳細,-vvv显示整個過程(非常詳細)

    [root@test ~]#cat test.yml 
    ---
    - hosts: websers
      remote_user: root
    
      tasks:
        - name: run this command 
          shell: hostname
          tags: hostname
          ignore_errors: True
        - name: show ip addr
          shell: /sbin/ip addr show
          tags: showip
    [root@test ~]#ansible-playbook test.yml --list-hosts
    
    playbook: test.yml
    
      play #1 (websers): websers    TAGS: []
        pattern: [u'websers']
        hosts (2):
          192.168.0.128
          192.168.0.218
    [root@test ~]#ansible-playbook test.yml --list-tags
    
    playbook: test.yml
    
      play #1 (websers): websers    TAGS: []
          TASK TAGS: [hostname, showip]
    [root@test ~]#ansible-playbook test.yml --list-tasks
    
    playbook: test.yml
    
      play #1 (websers): websers    TAGS: []
        tasks:
          run this command  TAGS: [hostname]
          show ip addr      TAGS: [showip]
    [root@test ~]#ansible-playbook test.yml --limit 192.168.0.218
    
    PLAY [websers] ********************************************************************************************************
    
    TASK [Gathering Facts] ************************************************************************************************
    ok: [192.168.0.218]
    
    TASK [run this command] ***********************************************************************************************
    changed: [192.168.0.218]
    
    TASK [show ip addr] ***************************************************************************************************
    changed: [192.168.0.218]
    
    PLAY RECAP ************************************************************************************************************
    192.168.0.218              : ok=3    changed=2    unreachable=0    failed=0   
    
    [root@test ~]#ansible-playbook test.yml --limit 192.168.0.218 -v
    Using /etc/ansible/ansible.cfg as config file
    
    PLAY [websers] ********************************************************************************************************
    
    TASK [Gathering Facts] ************************************************************************************************
    ok: [192.168.0.218]
    
    TASK [run this command] ***********************************************************************************************
    changed: [192.168.0.218] => {"changed": true, "cmd": "hostname", "delta": "0:00:00.002139", "end": "2019-11-16 23:11:02.996962", "rc": 0, "start": "2019-11-16 23:11:02.994823", "stderr": "", "stderr_lines": [], "stdout": "localhost.localdomain", "stdout_lines": ["localhost.localdomain"]}
    
    TASK [show ip addr] ***************************************************************************************************
    changed: [192.168.0.218] => {"changed": true, "cmd": "/sbin/ip addr show", "delta": "0:00:00.002604", "end": "2019-11-16 23:11:03.733004", "rc": 0, "start": "2019-11-16 23:11:03.730400", "stderr": "", "stderr_lines": [], "stdout": "1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN \n    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00\n    inet 127.0.0.1/8 scope host lo\n    inet6 ::1/128 scope host \n       valid_lft forever preferred_lft forever\n2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000\n    link/ether 00:0c:29:e8:f6:7b brd ff:ff:ff:ff:ff:ff\n    inet 192.168.0.218/24 brd 192.168.0.255 scope global eth0\n    inet6 fe80::20c:29ff:fee8:f67b/64 scope link \n       valid_lft forever preferred_lft forever\n3: pan0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN \n    link/ether d2:7a:38:cf:27:60 brd ff:ff:ff:ff:ff:ff", "stdout_lines": ["1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN ", "    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00", "    inet 127.0.0.1/8 scope host lo", "    inet6 ::1/128 scope host ", "       valid_lft forever preferred_lft forever", "2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000", "    link/ether 00:0c:29:e8:f6:7b brd ff:ff:ff:ff:ff:ff", "    inet 192.168.0.218/24 brd 192.168.0.255 scope global eth0", "    inet6 fe80::20c:29ff:fee8:f67b/64 scope link ", "       valid_lft forever preferred_lft forever", "3: pan0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN ", "    link/ether d2:7a:38:cf:27:60 brd ff:ff:ff:ff:ff:ff"]}
    
    PLAY RECAP ************************************************************************************************************
    192.168.0.218              : ok=3    changed=2    unreachable=0    failed=0   
    
    [root@test ~]#
    

      說明:–limit 所指定的主機必須是在playbook中所指定的主機範圍內。

       七、playbook vs shell scripts

      1)shell腳本如下:

    #!/bin/bash
    # 安裝Apache
    yum install --quiet -y httpd
    # 複製配置文件
    cp /tmp/httpd.conf /etc/httpd/conf/httpd.conf
    cp/tmp/vhosts.conf /etc/httpd/conf.d/
    # 啟動Apache,並設置開機啟動
    service httpd start
    chkconfig httpd on
    

      2)playbook

    ---
    - hosts: websers
      remote_user: root
    
      tasks:
        - name: create apache group
          group: name=apache gid=80 system=yes
        - name: create apache user
          user: name=apache uid=80 group=apache system=yes shell=/sbin/nologin home=/var/www/html 
        - name: install httpd
          yum: name=httpd
        - name: copy config file
          copy: src=/tmp/httpd.conf dest=/etc/httpd/conf/
        - name: copy config 2 file
          copy: src=/tmp/vhosts.conf dest=/etc/httpd/conf.d/
        - name: start httpd service
          service: name=httpd state=started enabled=yes     
    

      說明:兩者都是實現同樣的目的,很明顯playbook的優勢要比腳本的優勢多,playbook 可以針對很多台主機進行任務執行,而腳本只可以在某一台主機上執行;腳本重複執行沒有冪等性,很有可能帶來很多錯誤,而playbook卻不會有這樣的苦惱。

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

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

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

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

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

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

  • 快速搭建Jenkins集群

    快速搭建Jenkins集群

    關於Jenkins集群

    在Jenkins上同時執行多個任務時,單機性能可能達到瓶頸,使用Jenkins集群可以有效的解決此問題,讓多台機器同時處理這些任務可以將壓力分散,對單機版Jenkins的單點故障的隱患也有分散作用,今天就來實戰快速搭建Jenkins集群,Jenkins版本是2.190.2;

    如何做到快速搭建集群

    通過Docker可以省去大部分準備工作,您只需在Linux電腦上安裝docker,在輔以少量命令和操作即可完成集群搭建;

    環境信息

    本次實戰的環境一共要用三台電腦,它們的設置都是一樣的,如下:

    1. 操作系統:CentOS Linux release 7.6.1810
    2. 防火牆關閉
    3. docker:1.13.1

    三台電腦的信息如下:
    | 主機名 | IP地址 | 作用 |
    |–|–|–|
    | master | 192.168.133.131 | Jenkins集群的master節點,提供web服務 |
    | agent1 | 192.168.133.132 | Jenkins集群的一號工作接節點,標籤是maven |
    | agent2 | 192.168.133.133 | Jenkins集群的二號工作接節點,標籤是gradle |

    建議agent2節點的內存大於4G,因為下一篇的實戰操作會用agent2編譯構建spring-framework,對內存的需求略大;

    準備工作

    1. 後面的所有操作都是root賬號;
    2. 在每台電腦上創建文件夾/usr/local/jenkins

      創建Jenkins的master

    3. 登錄master機器,執行以下命令:
    docker run \
      -u root \
      -idt \
      --name master \
      -p 8080:8080 \
      -p 50000:50000 \
      -v /usr/local/jenkins:/var/jenkins_home \
      -v /var/run/docker.sock:/var/run/docker.sock \
      jenkinsci/blueocean:1.19.0
    1. 執行docker logs master,會在控制台显示jenkins的登錄秘鑰,如下圖紅框所示:
    2. 瀏覽器輸入地址: ,显示Jenkins登錄頁面,如下圖所示,在紅框位置輸入剛才複製的登錄秘鑰即可登錄:
    3. 選擇安裝推薦的插件
    4. 靜候插件在線安裝完成:
    5. 接下來是創建管理員和使用實例url的操作,這裏就不多說了,您按實際情況自行斟酌;

      至此,Jenkins的master已經搭建好,接下來將agent1和agent2作為工作節點加入集群;

      加入agent1

    6. 在Jenkins網頁上新增節點,操作如下圖,先進入節點管理頁面:
    7. 如下圖,新增一個節點,名為agent1
    8. 接下來的節點詳情信息如下圖,注意四個紅框中的內容要和圖中保持一致:
    9. 保存成功後會显示機器列表,如下圖,圖標上的紅叉表示機器不在線(此時agent1還沒有接入),點擊紅框:
    10. 如下圖所示,紅框中的命令就是agent1的啟動命令,執行該命令的機器會以agent1的身份加入集群:
    11. 注意上圖紅框中的agent.jar是個名為agent.jar的文件的下載鏈接,將此文件下載到agent1電腦的/usr/local/jenkins目錄下;
    12. ssh登錄agent1電腦,執行以下命令,即可將agent1加入Jenkins集群:
    docker run \
      -u root \
      -idt \
      --name agent \
      -v /usr/local/jenkins:/usr/local/jenkins \
      bolingcavalry/openjdk-with-sshpass:8u232 \
      java -jar /usr/local/jenkins/agent.jar \
      -jnlpUrl http://192.168.133.131:8080/computer/agent1/slave-agent.jnlp \
      -secret 44c3e8d1531754b8655b53294bbde6dd99b3aaa91a250092d0d3425534ae1058 \
      -workDir "/usr/local/jenkins"

    上述命令中的後半部分,即java -jar ……就是前面圖片紅框中的agent1啟動命令,唯一要改變的是將agent.jar改成絕對路徑/usr/local/jenkins/agent.jar

    1. 上述命令的鏡像是bolingcavalry/openjdk-with-sshpass:8u232,其Dockerfile內容如下,可見非常簡單,就是OpenJDK鏡像裏面安裝了sshpass,這樣的容器可以在執行ssh命令時帶上遠程機器的密碼,而不用等待用戶輸入密碼,這樣便於shell腳本執行ssh命令:
    FROM openjdk:8u232
    
    ARG DEBIAN_FRONTEND=noninteractive
    RUN apt-get update && apt-get install --assume-yes sshpass
    1. 去Jenkins的網頁上查看節點列表,如下圖,可見agent1已經成功加入:

      加入agent2

      agent2加入集群的方式和agent1大部分是一樣的,只有以下兩點要注意:

    2. 在Jenkins頁面上創建節點,名稱是agent2
    3. agent2的標籤是gradle,如下圖紅框所示:
    4. 此時agent2也加入成功:

      至此,Jenkins集群搭建完成,這兩個節點帶有不同的標籤,下一篇文章中,我們在這個集群環境創建pipeline任務,並通過標籤被分配到不同的節點上,實現多節點并行執行;

      歡迎關注公眾號:程序員欣宸

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

    【其他文章推薦】

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

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

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

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

  • 數據結構之隊列and棧總結分析

    一、前言:

      數據結構中隊列和棧也是常見的兩個數據結構,隊列和棧在實際使用場景上也是相輔相成的,下面簡單總結一下,如有不對之處,多多指點交流,謝謝。

    二、隊列簡介

      隊列顧名思義就是排隊的意思,根據我們的實際生活不難理解,排隊就是有先後順序,先到先得,其實在程序數據結構中的隊列其效果也是一樣,及先進先出。

         隊列大概有如下一些特性:

         1、操作靈活,在初始化時不需要指定其長度,其長度自動增加(默認長度為32)

            注:在實際使用中,如果事先能夠預估其長度,那麼在初始化時指定長度,可以提高效率

            2、泛型的引入,隊列在定義時可以指定數據類型避免裝箱拆箱操作

         3、存儲數據滿足先進先出原則

           

       c#中有關隊列的幾個常用方法:

      • Count:Count屬性返回隊列中元素個數。
      • Enqueue:Enqueue()方法在隊列一端添加一個元素。
      • Dequeue:Dequeue()方法在隊列的頭部讀取和刪除元素。如果在調用Dequeue()方法時,隊列中不再有元素,就拋出一個InvalidOperationException類型的異常。
      • Peek:Peek()方法從隊列的頭部讀取一個元素,但不刪除它。
      • TrimExcess:TrimExcess()方法重新設置隊列的容量。Dequeue()方法從隊列中刪除元素,但它不會重新設置隊列的容量。要從隊列的頭部去除空元素,應使用TrimExcess()方法。
      • Clear:Clear()方法從隊列中移除所有的元素。
      • ToArray:ToArray()複製隊列到一個新的數組中。

      下面通過隊列來實例模擬消息隊列的實現流程:

     

    using System;
    using System.Collections;
    using System.Collections.Generic;
    
    namespace dataStructureQueueTest
    {
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine("通過Queue來模擬消息隊列的實現");
                QueueTest queueTest = new QueueTest();
    
                while (true)
                {
                    Console.WriteLine("請輸入你操作的類型:1:代表生成一條消息,2:代表消費一條消息");
                    string type = Console.ReadLine();
                    if (type == "1")
                    {
                        Console.WriteLine("請輸入具體消息:");
                        string inforValue = Console.ReadLine();
                        queueTest.InformationProducer(inforValue);
                    }
                    else if (type == "2")
                    {
                        //// 在消費消息的時候,模擬一下,消費成功與消費失敗下次繼續消費的場景
    
                        object inforValue = queueTest.InformationConsumerGet();
                        if (inforValue == null)
                        {
                            Console.WriteLine("當前無可消息可消費");
                        }
                        else
                        {
                            Console.WriteLine("獲取到的消息為:" + inforValue);
    
                            Console.WriteLine("請輸入消息消費結果:1:成功消費消息,2:消息消費失敗");
                            string consumerState = Console.ReadLine();
    
                            ///// 備註:該操作方式線程不安全,在多線程不要直接使用
                            if (consumerState == "1")
                            {
                                queueTest.InformationConsumerDel();
                            }
                        }
                    }
                    else
                    {
                        Console.WriteLine("操作有誤,請重新選擇");
                    }
                }
            }
        }
    
        /// <summary>
        /// 隊列練習
        /// </summary>
        public class QueueTest
        {
            /// <summary>
            /// 定義一個隊列
            /// </summary>
            public Queue<string> queue = new Queue<string>();
    
            /// <summary>
            /// 生成消息--入隊列
            /// </summary>
            /// <param name="inforValue"></param>
            public void InformationProducer(string inforValue)
            {
                queue.Enqueue(inforValue);
            }
    
            /// <summary>
            /// 消費消息---出隊列--只獲取數據,不刪除數據
            /// </summary>
            /// <returns></returns>
            public object InformationConsumerGet()
            {
                if (queue.Count > 0)
                {
                    return queue.Peek();
                }
    
                return null;
            }
    
            /// <summary>
            /// 消費消息---出隊列---獲取數據的同時刪除數據
            /// </summary>
            /// <returns></returns>
            public string InformationConsumerDel()
            {
                if (queue.Count > 0)
                {
                    return queue.Dequeue();
                }
    
                return null;
            }
        }
    }

     

     

    三、棧簡介

      棧和隊列在使用上很相似,只是棧的數據存儲滿足先進后出原則,棧有如下一些特性:

         1、操作靈活,在初始化時不需要指定其長度,其長度自動增加(默認長度為10)

            注:在實際使用中,如果事先能夠預估其長度,那麼在初始化時指定長度,可以提高效率

            2、泛型的引入,棧在定義時可以指定數據類型避免裝箱拆箱操作

         3、存儲數據滿足先進后出原則

        c#中有關棧的幾個常用方法:

    • Count:Count屬性返回棧中的元素個數。
    • Push:Push()方法在棧頂添加一個元素。
    • Pop:Pop()方法從棧頂刪除一個元素,並返回該元素。如果棧是空的,就拋出一個InvalidOperationException類型的異常。
    • Peek:Peek()方法返回棧頂的元素,但不刪除它。
    • Contains:Contains()方法確定某個元素是否在棧中,如果是,就返回true。

         下面通過一個棧來模擬瀏覽器的回退前進操作的實現

     

    using System;
    using System.Collections.Generic;
    
    namespace dataStructureStackTest
    {
        class Program
        {
            static void Main(string[] args)
            {
                //// 通過棧來模擬瀏覽器回退前進操作
                ////   1、定義兩個棧,分別記錄回退的地址集合,和前進地址集合
                ////   2、在操作具體的回退或者前進操作時
                ////      如果和前一次操作相同,那麼就取出對應隊列的一條數據存儲到另外一個隊列
                Console.WriteLine("本練習模擬瀏覽器的回退前進操作:");
    
                /// 假設瀏覽器已瀏覽了20個網站記錄
                StackTest stackTestBack = new StackTest(20);
                StackTest stackTestGo = new StackTest(20);
                for (int i = 0; i < 20; i++)
                {
                    stackTestBack.PushStack("網站" + (i + 1).ToString());
                }
    
                //// 記錄上一次操作
                string beforOpert = "";
                while (true)
                {
                    Console.WriteLine("");
                    Console.WriteLine("請輸入你操作的類型:1:回退,2:前進");
                    string type = Console.ReadLine();
    
                    if (type == "1")
                    {
                        //// 出棧
                        if (beforOpert == type)
                        {
                            stackTestGo.PushStack(stackTestBack.GetAndDelStack());
                        }
                        string wbeSit = stackTestBack.GetStack();
                        Console.WriteLine("回退到頁面:" + wbeSit);
                        beforOpert = type;
                    }
                    else if (type == "2")
                    {
                        //// 出棧
                        if (beforOpert == type)
                        {
                            stackTestBack.PushStack(stackTestGo.GetAndDelStack());
                        }
                        string wbeSit = stackTestGo.GetStack();
    
                        Console.WriteLine("回退到頁面:" + wbeSit);
                        beforOpert = type;
                    }
                    else
                    {
                        Console.WriteLine("請輸入正確的操作方式!!");
                    }
                }
            }
        }
    
        /// <summary>
        /// 隊列練習
        /// </summary>
        public class StackTest
        {
            /// <summary>
            /// 定義一個棧
            /// </summary>
            public Stack<string> stack;
    
            /// <summary>
            ///無參數構造函數,棧初始化為默認長度
            /// </summary>
            public StackTest()
            {
                stack = new Stack<string>();
            }
    
            /// <summary>
            ///有參數構造函數,棧初始化為指定長度
            ///如果在定義隊列時,如果知道需要存儲的數據長度,那麼最好預估一個長度,並初始化指定的長度
            /// </summary>
            public StackTest(int stackLen)
            {
                stack = stackLen > 0 ? new Stack<string>(stackLen) : new Stack<string>();
            }
    
            /// <summary>
            /// 入棧
            /// </summary>
            /// <param name="inforValue"></param>
            public void PushStack(string inforValue)
            {
                stack.Push(inforValue);
            }
    
            /// <summary>
            /// 出棧(但不刪除)
            /// </summary>
            /// <returns></returns>
            public string GetStack()
            {
                if (stack.Count > 0)
                {
                    return stack.Peek();
                }
    
                return null;
            }
    
            /// <summary>
            /// 出棧(並刪除)
            /// </summary>
            /// <returns></returns>
            public string GetAndDelStack()
            {
                if (stack.Count > 0)
                {
                    return stack.Pop();
                }
    
                return null;
            }
        }
    }

     

    四、使用場景總結

      根據隊列和棧的特點,下面簡單總結一下隊列和棧的一些實際使用場景

       隊列:

        1、異步記錄日誌,此處會涉及到單例模式的使用

        2、消息隊列

        3、業務排隊,比如12306車票購買排隊等候

        4、其他符合先進先出原則的業務操作

       棧:

        1、可回退的操作記錄,比如:瀏覽器的回退操作

        2、計算表達式匹配,比如:計算器表達式計算

        3、其他符合先進后出原則的業務操作

     

    附件:

    關於這一些練習的代碼,上傳到github,有興趣的可以看一下:

     

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

    【其他文章推薦】

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

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

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

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

  • 深入理解java繼承從“我爸是李剛”講起

    深入理解java繼承從“我爸是李剛”講起

    目錄

    前言
    本文主要多方面講解java繼承,旨在讓初學者通俗易懂,至於“我爸是李剛”,反正樓主也不知道誰爸是李剛。
    @

    1、繼承的概述

    1.1、繼承的由來

    至於由來簡單一句話:多個類中存在相同屬性和行為時,將這些內容抽取到單獨一個類中,那麼多個類無需再定義這些屬性和行為。

    繼承描述的是事物之間的所屬關係,這種關係是 is-a 的關係。

    1.2、繼承的定義

    繼承:就是子類繼承父類的屬性行為,使得子類對象具有與父類相同的屬性、相同的行為。子類可以直接訪問父類中的非私有的屬性和行為。

    這裏再聲明一點,父類又稱為超類或者基類。而子類又稱為派生類這點很基礎!

    1.3、繼承的優點

    1. 提高代碼的復用性
    2. 類與類之間產生關係,為多態做了完美的鋪墊(不理解沒關係,之後我會再寫一篇多態的文章)

    雖然繼承的優點很多但是Java只支持單繼承,不支持多繼承

    1.4、繼承的格式

    通過 extends 關鍵字,可以聲明一個子類繼承另外一個父類,定義格式如下:

      class 父類 {
       ... 
       }
       class 子類 extends 父類 { 
       ... 
       } 

    2、關於繼承之後的成員變量

    當類之間產生了關係后,其中各類中的成員變量,產生了哪些影響呢? 關於繼承之後的成員變量要從兩方面下手,一是成員變量不重名方面,二是成員變量重名方面。

    2.1、成員變量不重名

    如果子類父類中出現不重名的成員變量,這時的訪問是沒有影響的。代碼如下:

      class liGang {
            // 父類中的成員變量。
           String name ="李剛";//------------------------------父類成員變量是name
        }
        class LiXiaoGang extends liGang {
            // 子類中的成員變量
            String name2 ="李小剛";//--------------------------子類成員變量是name2
            // 子類中的成員方法
            public void show() {
                // 訪問父類中的name,
                System.out.println("我爸是"+name);
                // 繼承而來,所以直接訪問。
                // 訪問子類中的name2
                System.out.println("我是"+name2);
            }
        }
    public class Demo {
            public static void main(String[] args) {
                // 創建子類對象
                LiXiaoGang z = new LiXiaoGang();
                // 調用子類中的show方法
                z.show();
            }
        }
        //演示結果: 我爸是李剛   我是李小剛

    2.2、 成員變量重名

    如果子類父類中出現重名的成員變量,這時的訪問是有影響的。代碼如下:

    class liGang {
            // 父類中的成員變量。
           String name ="李剛";//------------------------------父類成員變量是name
        }
        class LiXiaoGang extends liGang {
            // 子類中的成員變量
            String name ="李小剛";//---------------------------子類成員變量也是name
            // 子類中的成員方法
            public void show() {
                // 訪問父類中的name,
                System.out.println("我爸是"+name);
                // 繼承而來,所以直接訪問。
                // 訪問子類中的name2
                System.out.println("我是"+name);
            }
        }
    public class Demo {
            public static void main(String[] args) {
                // 創建子類對象
                LiXiaoGang z = new LiXiaoGang();
                // 調用子類中的show方法
                z.show();
            }
        }
        //演示結果: 我爸是李小剛   我是李小剛
    
    

    子父類中出現了同名的成員變量時,在子類中需要訪問父類中非私有成員變量時,需要使用 super 關鍵字,至於修飾父類成員變量,類似於之前學過的 this 。 使用格式 super.父類成員變量名

    this表示當前對象,super則表示父類對象,用法類似!

    class liGang {
            // 父類中的成員變量。
           String name ="李剛";
        }
        class LiXiaoGang extends liGang {
            // 子類中的成員變量
            String name ="李小剛";
            // 子類中的成員方法
            public void show() {
                // 訪問父類中的name,
                System.out.println("我爸是"+super.name);
                // 繼承而來,所以直接訪問。
                // 訪問子類中的name2
                System.out.println("我是"+this.name);  //當然this可省略
            }
        }
    public class Demo {
            public static void main(String[] args) {
                // 創建子類對象
                LiXiaoGang z = new LiXiaoGang();
                // 調用子類中的show方法
                z.show();
            }
        }
        //演示結果: 我爸是李剛   我是李小剛

    2.3、關於繼承中成員變量值得思考的一個問題

    同學你有沒有想過這樣一個問題。如果父類中的成員變量
    非私有:子類中可以直接訪問。
    私有:子類是不能直接訪問的。如下:

    當然,同學你要自己體驗體驗編譯報錯過程,看圖沒體驗感不得勁,~嘔,你這無處安放的魅力,無理的要求,我佛了,行吧~

      class liGang2 {
            // 父類中的成員變量。
            private String name ="李剛";
    
        }
        class LiXiaoGang2 extends liGang2 {
            // 子類中的成員變量
            String name ="李小剛";
            // 子類中的成員方法
            public void show() {
                // 訪問父類中的name,
                System.out.println("我爸是"+super.name);//------編譯失敗不能直接訪問父類私有屬性(成員變量)
                // 繼承而來,所以直接訪問。
                // 訪問子類中的name2
                System.out.println("我是"+this.name);  //當然this可省略
            }
        }
    public class PrivateVariable {
            public static void main(String[] args) {
                // 創建子類對象
                ExtendDemo.LiXiaoGang z = new ExtendDemo.LiXiaoGang();
                // 調用子類中的show方法
                z.show();
            }
        }

    通常開發中編碼時,我們遵循封裝的原則,使用private修飾成員變量,那麼如何訪問父類的私有成員變量呢?其實這個時候在父類中提供公共的getXxx方法和setXxx方法就可以了。代碼如下:

    class liGang {
            // 父類中的成員變量。
          private String name ="李剛";
    
            public String getName() {
                return name;
            }
    
            public void setName(String name) {
                this.name = name;
            }
        }
        class LiXiaoGang extends liGang {
            // 子類中的成員變量
            String name ="李小剛";
            // 子類中的成員方法
            public void show() {
                // 訪問父類中的name,
                System.out.println("我爸是"+super.getName());
                // 繼承而來,所以直接訪問。
                // 訪問子類中的name2
                System.out.println("我是"+this.name);  //當然this可省略
            }
        }
    public class Demo {
            public static void main(String[] args) {
                // 創建子類對象
                LiXiaoGang z = new LiXiaoGang();
                // 調用子類中的show方法
                z.show();
            }
        }
        //演示結果: 我爸是李剛   我是李小剛

    分析如下:

    3、關於繼承之後的成員方法

    分析完了成員變量,現在我們一起來分析分析成員方法。
    想一想,當類之間產生了關係,其中各類中的成員方法,又產生了哪些影響呢? 同樣我們依舊從兩方面分析。
    #### 3.1、成員方法不重名
    如果子類父類中出現不重名的成員方法,這時的調用是沒有影響的。對象調用方法時,會先在子類中查找有沒有對 應的方法,若子類中存在就會執行子類中的方法,若子類中不存在就會執行父類中相應的方法。代碼如下:

     class liGang3 {
            // 父類中的成員方法。
           public void zhuangRen1(){//--------------------------父類方法名zhuangRen1
               System.out.println("我叫李剛,人不是我撞的,別抓我,我不認識李小剛");
           }
        }
        class LiXiaoGang3 extends liGang3 {
    
            // 子類中的成員方法
            public void zhuangRen() {//--------------------------子類方法名zhuangRen
                System.out.println("有本事你們告去,我爸是李剛");  
            }
        }
        public class MemberMethod {
            public static void main(String[] args) {
                // 創建子類對象
                LiXiaoGang3 liXiaoGang = new LiXiaoGang3();
                // 調用子類中的show方法
                liXiaoGang.zhuangRen();
                liXiaoGang.zhuangRen1();
            }
        }
        
    打印結果:有本事你們告去,我爸是李剛
            我叫李剛,人不是我撞的,別抓我,我不認識李小剛
    

    #### 3.2、成員方法重名 【方法重寫】
    成員方法重名大體也可以分兩種情況:

    1、方法名相同返回值類型、參數列表卻不相同(優先在子類查找,沒找到就去父類)
    2、方法名、返回值類型、參數列表都相同,沒錯這就是重寫(Override)

    這裏主要講方法重寫 :子類中出現與父類一模一樣的方法時(返回值類型,方法名和參數列表都相同),會出現覆蓋效果,也稱為重寫或者複寫。聲明不變,重新實現。 代碼如下:

        class liGang3 {
            // 父類中的成員方法。
           public void zhuangRen(int a){
               System.out.println("我叫李剛,人不是我撞的,別抓我");
           }
        }
        class LiXiaoGang3 extends liGang3 {
    
            // 子類中的成員方法
            public void zhuangRen(int a) {
                System.out.println("有本事你們告去,我爸是李剛");
            }
        }
        public class MemberMethod {
            public static void main(String[] args) {
                // 創建子類對象
                LiXiaoGang3 liXiaoGang = new LiXiaoGang3();
                // 調用子類中的zhuangRen方法
                liXiaoGang.zhuangRen(1);
    
            }
        }
        結果打印:有本事你們告去,我爸是李剛

    #### 3.3、繼承中重寫方法的意義
    子類可以根據需要,定義特定於自己的行為。既沿襲了父類的功能名稱,又根據子類的需要重新實現父類方法,從而進行擴展增強。比如李剛會開車,李小剛就牛了,在父類中進行擴展增強還會開車撞人,代碼如下:

     class liGang3 {
            // 父類中的成員方法。
           public void kaiChe(){
               System.out.println("我會開車");
           }
        }
        class LiXiaoGang3 extends liGang3 {
            // 子類中的成員方法
            public void kaiChe(){
                super.kaiChe();
                System.out.println("我還會撞人");
                System.out.println("我還能一撞撞倆婆娘");
            }
        }
        public class MemberMethod {
            public static void main(String[] args) {
                // 創建子類對象
                LiXiaoGang3 liXiaoGang = new LiXiaoGang3();
                // 調用子類中的zhuangRen方法
                liXiaoGang.kaiChe();
    
    打印結果:   我會開車
               我還會撞人
               我還能一撞撞倆婆娘
            }
        }
    

    不知道同學們發現了沒有,以上代碼中在子類中使用了 super.kaiChe();super.父類成員方法,表示調用父類的成員方法。

    最後重寫必須注意這幾點:

    1、方法重寫時, 方法名與形參列表必須一致。
    2、子類方法覆蓋父類方法時,必須要保證子類權限 >= 父類權限。
    3、方法重寫時,子類的返回值類型必須要 <= 父類的返回值類型。
    4、方法重寫時,子類拋出的異常類型要 <= 父類拋出的異常類型。

    粗心的同學看黑板,look 這裏【注意:只有訪問權限是>=,返回值、異常類型都是<=

    下面以修飾權限為例,如下:

    4、關於繼承之後的構造方法

    為了讓你更好的體會,首先我先編寫一個程序

       class liGang4 {
            // 父類的無參構造方法。
            public liGang4(){
                System.out.println("父類構造方法執行了。。。");
            }
        }
        class LiXiaoGang4 extends liGang4 {
            // 子類的無參構造方法。
           public LiXiaoGang4(){
               System.out.println("子類構造方法執行了====");
           }
        }
        public class ConstructionDemo {
            public static void main(String[] args) {
                // 創建子類對象
                LiXiaoGang4 z = new LiXiaoGang4();
    
            }
        }

    用一分鐘猜想一下結果是什麼,猜好了再看下面結果:

    父類構造方法執行了。。。
    子類構造方法執行了====

    好了,看了結果之後,你可能有疑惑。父類構造器方法怎麼執行了?我們先來分析分析,首先在main方法中實例化了子類對象,接着會去執行子類的默認構造器初始化,這個時候在構造方法中默認會在第一句代碼中添加super();沒錯,他就是開掛般的存在,不寫也存在的!有的調~讀四聲“跳”~皮的同學就會說,你說存在就存在啊,無憑無據 ~呀,你這個該死的靚仔~ 如下:

    構造方法的名字是與類名一致的,所以子類是無法繼承父類構造方法的。 構造方法的作用是初始化成員變量的。所以子類的初始化過程中,必須先執行父類的初始化動作。子類的構造方法中默認會在第一句代碼中添加super(),表示調用父類的構造方法,父類成員變量初始化后,才可以給子類使用。

    當然我已經強調很多遍了 super() 不寫也默認存在,而且只能是在第一句代碼中,不在第一句代碼中行不行,答案是當然不行,這樣會編譯失敗,如下:

    5、關於繼承的多態性支持的例子

    直接上代碼了喔

    class A{
        public String show(C obj) {
            return ("A and C");
        }
    
        public String show(A obj) {
            return ("A and A");
        }
    
    }
    class B extends A{
        public String show(B obj) {
            return ("B and B");
        }
    }
    class C extends B{
        public String show(A obj) {
            return ("A and B");
        }
    }
    public class Demo1 {
        public static void main(String[] args) {
            A a=new A();
            B b=new B();
            C c=new C();
            System.out.println("第一題 " + a.show(a));
            System.out.println("第二題 " + a.show(b));
            System.out.println("第三題 " + a.show(c));
        }
    }
    運行結果:
            第一題 A and A
            第二題 A and A
            第三題 A and C

    其實吧,第一題和第三題都好理解,第二題就有點意思了,會發現A類中沒有B類型這個參數,這個時候,你就應該知道子類繼承就是父類,換句話說就是子類天然就是父類,比如中國人肯定是人,但是人不一定是中國人(可能是火星人也可能是非洲人),所以父類做為參數類型,直接傳子類的參數進去是可以的,反過來,子類做為參數類型,傳父類的參數進去,就需要強制類型轉換。

    6、super與this的用法

    了解他們的用法之前必須明確一點的是父類空間優先於子類對象產生

    在每次創建子類對象時,先初始化父類空間,再創建其子類對象本身。目的在於子類對象中包含了其對應的父類空間,便可以包含其父類的成員,如果父類成員非private修飾,則子類可以隨意使用父類成員。代碼體現在子類的構 造方法調用時,一定先調用父類的構造方法。理解圖解如下:

    #### 5.1、 super和this的含義:

    super :代表父類的存儲空間標識(可以理解為父親的引用)。

     

    this :代表當前對象的引用(誰調用就代表誰)。

    #### 5.2、 super和this訪問成員

    this.成員變量 ‐‐ 本類的
    super.成員變量 ‐‐ 父類的
    this.成員方法名() ‐‐ 本類的
    super.成員方法名() ‐‐ 父類的

    #### 5.3、super和this訪問構造方法

    this(...) ‐‐ 本類的構造方法
    super(...) ‐‐ 父類的構造方法

    #### 5.4、super()和this()能不能同時使用?

    不能同時使用,thissuper不能同時出現在一個構造函數裏面,因為this必然會調用其它的構造函數,其它的構造函數必然也會有super語句的存在,所以在同一個構造函數裏面有相同的語句,就失去了語句的意義,編譯器也不會通過。
    #### 5.5、總結一下super與this

    子類的每個構造方法中均有默認的super(),調用父類的空參構造。手動調用父類構造會覆蓋默認的super()super()this() 都必須是在構造方法的第一行,所以不能同時出現

    到這裏,java繼承你get到了咩,get到了請咩一聲,隨便隨手~點個讚唄~

    推薦閱讀本專欄的下一篇java文章

    歡迎各位關注我的公眾號,一起探討技術,嚮往技術,追求技術…

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

    【其他文章推薦】

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

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

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

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