標籤: 電動車

  • 別翻了,這篇文章就是要讓你入門java多線程!

    別翻了,這篇文章就是要讓你入門java多線程!

    目錄

    就在前幾天,有位讀者朋友私信宜春,說期待出一篇多線程的文章,我當時內心是小鹿亂撞啊….於是這幾天茶不思飯不想,好幾天深夜皆是輾轉反側,兩目深凝,以至於這幾天走起路來格外飄飄然,左搖右晃的,魔鬼般的步伐,一般兩步,走在大馬路中央上差點被打~我承認太誇張了,感覺又要被打~。最終還是君意不可違,答應了這位讀者朋友,從這位讀者朋友的博客頭像可以看的出來,這位朋友絕bi歷經滄桑,對生活無盡的坦然浩對,看透俗世凡塵、世態炎涼、趨炎附勢,擁有着極高的安心恬盪情懷…啥?啥子?這個是系統默認頭像….嗯嗯嗯呃。。。那個那個宜春啥都沒說哈,別把什麼事都扯宜春身上,你們一天天的,我啥都沒說(理直氣壯)…

    @

    1. 理解線程與進程

    由於併發肯定涉及到多線程,因此在進入併發編程主題之前,我們先來了解一下進程和線程的由來,這對後面對併發編程的理解將會有很大的幫助。

    進程和線程的對比這一知識點由於過於基礎,正因為過於基礎,所以我們更應該透徹它!我們必須掌握什麼是線程和進程,掌握線程與進程的關係、區別及優缺點 !

    1.1、何為進程?

    首先我們來看一下進程的概念:

    進程:是指一個內存中運行的應用程序,每個進程都有一個獨立的內存空間,一個應用程序可以同時運行多個進程;進程也是程序的一次執行過程,是系統運行程序的基本單位;系統運行一個程序即是一個進程從創建、運行到消亡的過程。

    看完之後,是不是感覺很抽象?很懵bi?懵bi就對了,說明你和我智商一樣高….~開個玩笑~

    不妨先憋棄上面的概念,放鬆一下大腦,雙擊打開LOL,秒選德馬打野,輸了直接退出遊戲並且保持微笑,然後正襟危坐心平氣和的看宜春寫的博客….

    這個時候的你不僅僅是愉快的擼了一把遊戲,而且還親自體驗擼了一把進程…其實在你雙擊打開LOL的時候就已經創建了進程,此話怎講?眾所周知,我們的電腦安裝的軟件比如:LOL、微信、谷歌等等都是存儲在我們的硬盤上的,硬盤上的數據可以說是永久存儲(ORM),當我們雙擊LOL的時候,LOL程序執行就進入了內存中,所有的程序必須進入內存中才能執行,內存屬於臨時存儲(RAM),而進入內存的程序都可以叫做是進程,把LOL程序退出的時候,LOL程序就會退出內存,進程也就隨之銷毀了!因此說各位擼了一把進程也不為過吧。

    啥?字太多了,看的不夠明了,不如看圖得勁….額。。。

    上面主要是通過抽象的描述了進程,其實進程是可以很直觀的看的到的,我們可以再電腦底部任務欄,右鍵—–>打開任務管理器,可以查看當前任務的進程:

    其實,關於線程博主我完全可以一兩句話概括,但是這樣並不負責,畢竟這篇文章標題就是要讓你徹底入門java多線程。如果連進程都理解不好談何徹底理解多線程?

    1.2、何為線程?

    同樣的,我們先來看線程的概念

    線程是進程中的一個執行單位,負責當前進程中程序的執行。一個進程中至少有一個線程,也就是說一個進程可以有多個線程的,而多個線程的進程運用程序就叫做多線程程序

    線程的概念稍微好理解很多,但是想更深層次的去理解光靠上面一段文字的概述是完全不夠的!

    這不打LOL的過程中,屬實卡的一批,果然花高價998買的6手戴爾筆記本打LOL屬實像極了愛情。這個時候不得不雙擊打開電腦安全管家進行殺毒,果然2500天沒有進行過病毒查殺,我天。。。其實我相信很多人都用過電腦管家或者手機管家之類的安全軟件,我們都很清楚我們開啟病毒查殺之後一般要幾分鐘掃描查殺,這個時候我們是可以讓它後台進行的,我們不會等而是開啟另一個垃圾清理的功能,這個時候我們也不會等而是再去啟動電腦加速功能。等到 這些操作都完成之後果斷退出電腦管家,繼續LOL,果然高價998買的6手戴爾筆記本再怎麼殺毒打LOL還是照樣的卡….

    其實清楚線程必然涉及到CPU的相關概念了,將上面文字所描述的用圖片概括,大致為:

    1.3、何為多線程?

    從上一節中,我們也提到過多線程,所以理解起來應該不難。

    多線程就是多個線程同時運行交替運行

    單核CPU:交替運行。
    多核CPU:同時運行。

    其實,多線程程序並不能提高程序的運行速度,但能夠提高程序運行效率,讓CPU的使用率更高。

    1.4、何為線程調度優先級?

    說起線程調度優先級這個概念,就讓我想到現在我們大部分人投簡歷一樣。如果你的學歷或者工作經驗越高,那麼你的優先級就越高,面試官很大幾率就會讓你去面試但也不是一定只是幾率特別大,如果線程的優先級相同,那麼會隨機選擇一個(線程隨機性)!在我們每個人的電腦中線程是可以設置線程的優先級的,但是生活中沒有優先級(學歷、工作經驗)的孩子就只能靠自己的能力了~媽耶,太真實了…~

    線程優先級具有繼承特性比如A線程啟動B線程,則B線程的優先級和A是一樣的。

    線程優先級具有隨機性也就是說線程優先級高的不一定每一次都先執行完,只是被執行的可能性更大。

    在今後的多線程學習旅遊中我們會使用到getPriority()方法獲取線程的優先級。

    1.5、為什麼提倡使用多線程而不是多進程?

    線程與進程相似,但線程是一個比進程更小的執行單位,是程序執行的最小單位。一個進程在其執行的過程中可以產生多個線程。與進程不同的是同類的多個線程共享同一塊內存空間和一組系統資源,所以系統在產生一個線程,或是在各個線程之間作切換工作時,負擔要比進程小得多,也正因為如此,線程也被稱為輕量級進程。同時線程是程序執行的最小單位。使用多線程而不是用多進程去進行併發程序的設計,是因為線程間的切換和調度的成本遠遠小於進程。

    而使用多線程,多線程會將程序運行方式從串行運行變為併發運行,效率會有很大提高。

    2、理解并行和併發

    在博主認為併發和并行是兩個非常容易被混淆的概念。為了防止繞暈大家,所以我選擇長話短說!

    1. 併發:一個時間段內同時發生(並不是同時發生)。
    2. 并行:同一時刻發生(真正的同時發生)。

    它們都可以表示兩個或者多個任務一起執行,但是偏重點有些不同。

    於此同時,我們不妨回顧一下上面所提到過的CPU,並再次理解併發與并行的區別,從而溫故知新 ~我TM簡直是個天才!~

    單核CPU:交替運行【併發】
    多核CPU:同時運行【并行】

    併發給人的感覺是同時運行,那是因為分時交替運行的時間是非常短的!

    3、特殊的一個單線程:主線程(Main線程)

    我們常說的主線程就是Main線程,它是一個特殊的單線程,話不多說,直接擼碼:

    定義一個用於測試的demo類Person

    package demo;
    
    public class Person {
       public String name;
    
       public Person(String name){
           this.name=name;
       }
    
       public void run(){
           int i=1;
           while (i<5){
               System.out.println(name+i);
               i++;
           }
       }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }

    編寫Main方法

    package demo;
    
    public class MainThreadDemo {
        public static void main(String[] args) {
            Person per=new Person("常威");
            per.run();
    
            Person Per2=new Person("來福");
            Per2.run();
        }
    }

    運行結果就已經很顯而易見了,放心我不是靠你們運行結果而是單純的先分析主線程。

    運行結果:
        常威1
        常威2
        常威3
        常威4
        來福1
        來福2
        來福3
        來福4

    3.1、分析主線程原理

    3.2、 單線程的局限性

    單線程不僅效率低下,而且存在很大的局限性,惟一的優點就是安全。所以說女孩子長得安全其實也是一種優點,噗哈哈哈…

    如何體現出單線程效率低下以及它的局限性呢?其實只要一句代碼即可,還是以上面的單線程Main線程為例:

    package demo;
    
    public class MainThreadDemo {
        public static void main(String[] args) {
            Person per=new Person("常威");
            per.run();
            int a=6/0;  //=====================特別注意這行代碼
            Person Per2=new Person("來福");
            Per2.run();
        }
    }

    試想一下運行結果…

    如果對上面的運行結果有問題,或者疑問。那沒錯了,你簡直是個天(小)才(白)!真真的天(小)才(白),很有可能異常機制沒學好,好吧我給你貼出來:

    言歸正傳,效率低下何以見得?這是數據少,如果是一億條數據呢,單線程就是一個一個打印。那局限性又何以見得呢?從上面運行結果來看也能看出,只因為一行代碼而導致下面代碼不再執行。已經很明顯了。

    4、 創建多線程的四種方式

    說是說創建多線程有四種方式,但考慮到是入門文章還是主要寫入門的兩種方式,剩下的兩個暫時忽略。忽略的兩種方法有:實現Callable接口通過FutureTask包裝器來創建Thread線程、使用ExecutorServiceCallableFuture實現有返回結果的線程。現在可能對於入門的童鞋來說是接收不了的,以後再去了解也不晚!

    4.1、繼承Thread類

    Java使用java.lang.Thread類代表線程,所有的線程對象都必須是Thread類或其子類的實例。每個線程的作用是完成一定的任務,實際上就是執行一段程序流即一段順序執行的代碼。Java使用線程執行體來代表這段程序流。

    Java中通過繼承Thread類來創建啟動多線程的步驟如下:

    1. 定義Thread類的子類,並重寫該類的run()方法,該run()方法的方法體就代表了線程需要完成的任務,因此把run()方法稱為線程執行體。
    2. 創建Thread子類的實例,即創建了線程對象
    3. 調用線程對象的start()方法來啟動該線程

    代碼如下:

    測試類:

    public class Demo01 {
        public static void main(String[] args) {
            //創建自定義線程對象
            MyThread mt = new MyThread("新的線程!");
            //開啟新線程
            mt.start();
            //在主方法中執行for循環
            for (int i = 0; i < 10; i++) {
                System.out.println("main線程!"+i);
            }
        }
    }

    自定義線程類:

    public class MyThread extends Thread {
        //定義指定線程名稱的構造方法
        public MyThread(String name) {
            //調用父類的String參數的構造方法,指定線程的名稱
            super(name);
        }
        /**
         * 重寫run方法,完成該線程執行的邏輯
         */
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println(getName()+":正在執行!"+i);
            }
        }
    }

    Thread類本質上是實現了Runnable接口的一個實例,代表一個線程的實例。啟動線程的唯一方法就是通過Thread類的start()實例方法。start()方法是一個native方法,它將啟動一個新線程,並執行run()方法。這種方式實現多線程很簡單,通過自己的類直接extend Thread,並複寫run()方法,就可以啟動新線程並執行自己定義的run()方法。

    4.2、實現Runnable接口

    如果自己的類已經繼承另一個類,就無法直接繼承Thread,此時,可以實現一個Runnable接口來創建線程,顯然實現Runnable接口方式創建線程的優勢就很明顯了。

    直接擼碼:

    自定義一個類實現Runnable接口,並重寫接口中的run()方法,併為run方法添加要執行的代碼方法。

    public class RunableDemo implements Runnable{
    
        @Override
        public void run() {
            int a = 1;
            while (a<20){
                System.out.println(Thread.currentThread().getName()+ a);//Thread.currentThread().getName()為獲取當前線程的名字
                a++;
            }
        }
    }

    編寫Main方法

    為了啟動自定義類RunableDemo ,需要首先實例化一個Thread,並傳入RunableDemo 實例

    public class MainThreadDemo {
    
        public static void main(String[] args) {
            RunableDemo runn=new RunableDemo();
            
            //實例化一個Thread並傳入自己的RunableDemo 實例
            Thread thread=new Thread(runn);
            thread.start();
    
            int a = 1;
            while (a<20){
                //Thread.currentThread().getName()為獲取當前線程的名字
                System.out.println(Thread.currentThread().getName()+ a);
                a++;
            }
        }
    }

    運行結果:

    main1
    main2
    main3
    Thread-01
    Thread-02
    Thread-03
    Thread-04
    Thread-05
    Thread-06
    ....

    其實多運行幾遍,你會方法每次運行的結果順序都不一樣,這主要是由於多線程會去搶佔CPU的資源,誰搶到了誰就執行,而Main和Thread兩個線程一直在爭搶。

    實際上,當傳入一個Runnable target(目標)參數給Thread后,Threadrun()方法就會調用target.run(),參考JDK源代碼:

    public void run() {  
      if (target != null) {  
       target.run();  
      }  
    }  

    4.3、兩種入門級創建線程的區別

    採用繼承Thread類方式:

    (1)優點:編寫簡單,如果需要訪問當前線程,無需使用Thread.currentThread()方法,直接使用this,即可獲得當前線程。
    (2)缺點:因為線程類已經繼承了Thread類,所以不能再繼承其他的父類。

    採用實現Runnable接口方式:

    (1)優點:線程類只是實現了Runable接口,還可以繼承其他的類。在這種方式下,可以多個線程共享同一個目標對象,所以非常適合多個相
    同線程來處理同一份資源的情況,從而可以將CPU代碼和數據分開,形成清晰的模型,較好地體現了面向對象的思想。
    (2)缺點:編程稍微複雜,如果需要訪問當前線程,必須使用Thread.currentThread()方法。

    小結:
    如果一個類繼承Thread,則不適合資源共享。但是如果實現了Runable接口的話,則很容易的實現資源共享。

    實現Runnable接口比繼承Thread類的優勢:

    1.適合多個相同代碼的線程去處理同一個資源。

    2.可以避免java中單繼承的限制。

    3.增加代碼的健壯性,實現解耦。代碼可以被多個線程共享,代碼和數據獨立。

    4.線程池中只能放入實現Runnable或Callable類線程,不能放入繼承Thread的類【線程池概念之後會慢慢涉及】

    所以,如果選擇哪種方式,盡量選擇實現Runnable接口

    其實學到後面的線程池,你會發現上面兩種創建線程的方法實際上很少使用,一般都是用線程池的方式比較多一點。使用線程池的方式也是最推薦的一種方式,另外,《阿里巴巴Java開發手冊》在第一章第六節併發處理這一部分也強調到“線程資源必須通過線程池提供,不允許在應用中自行显示創建線程”。不過處於入門階段的童鞋博主還是強烈建議一步一個腳印比較好!

    5、使用匿名內部類方式創建線程

    談起匿名內部類,可能很多小白是比較陌生的,畢竟開發中使用的還是比較少,但是同樣是非常重要的一個知識!於此同時我就貼出關於匿名內部類的文章如果小白童鞋能看懂下面這個代碼,真的你不需要看那篇文章了,你T喵的簡直是個天才!

    package AnonymousInner;
    
    public class NiMingInnerClassThread {
        public static void main(String[] args) {
            Runnable r = new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i<5;i++){
                        System.out.println("熊孩子:"+i);
                    }
                }
            };
            new Thread(r).start();
            for (int i = 0; i < 5 ; i++){
                System.out.println("傻狍子:"+i);
            }
        }
    }

    小白童鞋還愣着幹啥呀趕緊去補補…

    6、線程安全問題

    線程安全問題主要是共享資源競爭的問題,也就是在多個線程情況下,一個或多個線程同時搶佔同一資源導致出現的一些不必要的問題,最典型的例子就是火車四個窗口售票問題了,這裏就不再舉售票例子了,已經爛大街了,這裏就簡單實現一個線程安全問題代碼….

    實現Runnable接口方式為例,主要實現過程是:實例化三個Thread,並傳入同一個RunableDemo 實例作為參數,最後開啟三條相同參數的線程,代碼如下:

    public class RunableDemo implements Runnable{
        public int a = 100;//線程共享數據
        
        @Override
        public void run() {
            while (a>0){
                System.out.println("線程"+Thread.currentThread().getName()+"執行到"+ a);
                a--;
            }
        }
    }
    public class MainThreadDemo {
    
        public static void main(String[] args) {
            RunableDemo runn=new RunableDemo();
            
            Thread thread1=new Thread(runn);
            Thread thread2=new Thread(runn);
            Thread thread3=new Thread(runn);
            thread1.start();
            thread2.start();
            thread3.start();
            }
     }

    運行結果:

    Thread-0==100
    Thread-0==99
    Thread-1==100
    Thread-1==97
    Thread-1==96
    Thread-1==95
    Thread-2==98
    ...

    根據結果可以看出,確實是三條線程(Thread-0、1、2)在執行,安全問題就出在線程會出現相同的結果比如上面的100就出現了兩次,如果循環條件更改一下可能也會出現負數的情況。這種情況該怎麼解決呢?這個時候就需要線程同步了!

    7、解決線程安全問題:線程同步

    實際上,線程安全問題的解決方法有三種:

    1、同步代碼塊
    2、同步方法
    3、鎖機制

    7.1、 synchronized同步代碼塊

    第一種方法:同步代碼塊

    格式:

    synchronized(鎖對象) {
    可能會出現線程安全問題的代碼(訪問共享數據的代碼)
    }

    使用同步代碼塊特別注意:
    1、通過代碼塊的鎖對象,可以是任意對象
    2、必須保證多個線程使用的鎖對象必須是同一個
    3、鎖對象的作用是把同步代碼快鎖住,只允許一個線程在同步代碼塊執行

    還是以上麵線程安全問題為例子,使用同步代碼塊舉例:

    public class RunableDemo implements Runnable{
        public int a = 100;//線程共享數據
    
        Object object=new Object(); //事先準備好一個鎖對象
    
        @Override
        public void run() {
            synchronized (object){  //使用同步代碼塊
            while (a>0){
                System.out.println("線程"+Thread.currentThread().getName()+"執行到"+ a);
                a--;
            }
            }
        }
    }

    Main方法沒有任何改動,運行一下結果是絕對沒問題的,數據都是正確的沒有出現重複情況這一出,各位可以自己嘗試一下!

    同步代碼塊的原理:

    使用了一個鎖對象,叫同步鎖,對象鎖,也叫同步監視器,當開啟多個線程的時候,多個線程就開始搶奪CPU的執行權,比如現在t0線程首先的到執行,就會開始執行run方法,遇到同步代碼快,首先檢查是否有鎖對象,發現有,則獲取該鎖對象,執行同步代碼塊中的代碼。之後當CUP切換線程時,比如t1得到執行,也開始執行run方法,但是遇到同步代碼塊檢查是否有鎖對象時發現沒有鎖對象,t1便被阻塞,等待t0執行完畢同步代碼塊,釋放鎖對象,t1才可以獲取從而進入同步代碼塊執行。
    同步中的線程,沒有執行完畢是不會釋放鎖的,這樣便實現了線程對臨界區的互斥訪問,保證了共享數據安全。
    缺點:頻繁的獲取釋放鎖對象,降低程序效率

    7.2、同步方法

    使用步驟:

    1、把訪問了共享數據的代碼抽取出來,放到一個方法中
    2、在該方法上添加 synchronized 修飾符

    格式:

    修飾符 synchronized 返回值類型 方法名稱(參數列表) {
      方法體...
    }

    代碼示例:

    public class RunableDemo implements Runnable{
        public int a = 100;//線程共享數據
    
        @Override
        public void run() {
            while (true){
                sell(); //調用下面的sell方法
            }
        }
        
        //訪問了共享數據的代碼抽取出來,放到一個方法sell中 
        public synchronized void sell(){
            while (a>0){
                System.out.println("線程"+Thread.currentThread().getName()+"執行到"+ a);
                a--;
            }
        }
    }

    同步方法的也是一樣鎖住同步的代碼,但是鎖對象的是Runable實現類對象,也就是this,誰調用方法,就是誰。

    說到同步方法,就不得不說一下靜態同步方法,顧名思義,就是在同步方法上加上static,靜態的同步方法,添加一個靜態static修飾符,此時鎖對象就不是this了,靜態同步方法的鎖對象是本類的class屬性,class文件對象(反射)

    public class RunableDemo implements Runnable{
        public static int a = 100;//線程共享數據     =====此時共享數據也要加上static
    
        @Override
        public void run() {
            while (true){
                sell();
            }
        }
    
        public static synchronized void sell(){  //注意添加了static關鍵字
            while (a>0){
                System.out.println("線程"+Thread.currentThread().getName()+"執行到"+ a);
                a--;
            }
        }
    }

    使用靜態同步方法時,此時共享數據也要加上static,因為static成員才能訪問static成員,如果對static關鍵字不是他別理解的可以補補了,放心,博主有信心讓你有所收穫,會讓你重新認識到static的魅力:

    當然靜態同步方法了解即可!

    7.3、Lock鎖

    Lock接口位於java.util.concurrent.locks.Lock它是JDK1.5之後出現的,Lock接口中的方法:

    void lock(): 獲取鎖

     

    void unlock(): 釋放鎖

    Lock接口的一個實現類java.util.concurrent.locks.ReentrantLock implements Lock接口

    使用方法:
    1、在Runable實現類的成員變量創建一個ReentrantLock對象
    2、在可能產生線程安全問題的代碼該對象調用lock方法獲取鎖
    3、在可能產生線程安全問題的代碼該對象調用unlock方法釋放鎖

    代碼示例:

    import java.util.concurrent.locks.ReentrantLock;
    
    public class RunableDemo implements Runnable{
        public static int a = 100;//線程共享數據
    
        //1、在Runable實現類的成員變量創建一個ReentrantLock對象============
        ReentrantLock reentrantLock = new ReentrantLock();
    
        @Override
        public void run() {
            // 2、在可能產生線程安全問題的代碼前該對象調用lock方法獲取鎖=======
            reentrantLock.lock();
            while (a>0){
                System.out.println("線程"+Thread.currentThread().getName()+"執行到"+ a);
                a--;
            }
            // 3、在可能產生線程安全問題的代碼后該對象調用unlock方法獲取鎖======
            reentrantLock.unlock();
        }
    
    }

    當然更安全的寫法是,在線程安全問題代碼中try...catchy,最後在finally語句中添加reentrantLock.unlock();,這樣方為上上策!

    7.4、三種方法小結

    第一種
    synchronized 同步代碼塊:可以是任意的對象必須保證多個線程使用的鎖對象是同一個

     

    第二種
    synchronized 同步方法: 鎖對象是this,誰調用鎖對象就是誰

     

    synchronized 靜態同步方法: 鎖對象是其class對象,該對象可以用this.getClass()方法獲取,也可以使用當前類名.class 表示。【了解即可】

     

    第三種
    Look鎖方法:該方法提供的方法遠遠多於synchronized方式,主要在Runable實現類的成員變量創建一個ReentrantLock對象,並使用該對象調用lock方法獲取鎖以及unlock方法釋放鎖!

    8、線程常用方法

    8.1、Thread類

      Thread():用於構造一個新的Thread。

      Thread(Runnable target):用於構造一個新的Thread,該線程使用了指定target的run方法。

      Thread(ThreadGroup group,Runnable target):用於在指定的線程組中構造一個新的Thread,該

      線程使用了指定target的run方法。

      currentThread():獲得當前運行線程的對象引用。

      interrupt():將當前線程置為中斷狀態。

      sleep(long millis):使當前運行的線程進入睡眠狀態,睡眠時間至少為指定毫秒數。

      join():等待這個線程結束,即在一個線程中調用other.join(),將等待other線程結束后才繼續本線程。

      yield():當前執行的線程讓出CPU的使用權,從運行狀態進入就緒狀態,讓其他就緒線程執行。

    8.2、Object類

      wait():讓當前線程進入等待阻塞狀態,直到其他線程調用了此對象的notify()或notifyAll()方法后,當前線程才被喚醒進入就緒狀態。

      notify():喚醒在此對象監控器(鎖對象)上等待的單個線程。

      notifyAll():喚醒在此對象監控器(鎖對象)上等待的所有線程。

    注意:wait()、notify()、notifyAll()都依賴於同步鎖,而同步鎖是對象持有的,且每個對象只有一個,所以這些方法定義在Object類中,而不是Thread類中。

    8.3、yield()、sleep()、wait()比較

       wait():讓線程從運行狀態進入等待阻塞狀態,並且會釋放它所持有的同步鎖。

       yield():讓線程從運行狀態進入就緒狀態,不會釋放它鎖持有的同步鎖。

       sleep():讓線程從運行狀態進入阻塞狀態,不會釋放它鎖持有的同步鎖。

    9、線程的狀態

    以上只是簡單的一個線程狀態圖,其實線程狀態博大精深,要講清楚還是要一大篇文筆,作為入門文章先了解一下吧,之後的併發編程文章將再講述吧!

    如果想要去深入了解一下的話也是可以的:

    10、線程池

    在java中只要說到池,基本都是一個套路,啥數據庫連接池、jdbc連接池等,思想基本上就是:一個容納多個要使用資源的容器,其中的資源可以反覆使用,省去了頻繁創建線程對象的操作,無需反覆創建資源而消耗過多資源。

    10.1、線程池概述

    線程池其實就是一個容納多個線程的容器,其中的線程可以反覆使用,省去了頻繁創建線程對象的操作,無需反覆創建線程而消耗過多資源。

    合理利用線程池能夠帶來三個好處:

    1. 降低資源消耗。減少了創建和銷毀線程的次數,每個工作線程都可以被重複利用,可執行多個任務。
    2. 提高響應速度。當任務到達時,任務可以不需要的等到線程創建就能立即執行。
    3. 提高線程的可管理性。可以根據系統的承受能力,調整線程池中工作線線程的數目,防止因為消耗過多的內存,而把服務器累趴下(每個線程需要大約1MB內存,線程開的越多,消耗的內存也就越大,最後死機)。

    10.2、 線程池的使用

    Java裏面線程池的最頂級接口是java.util.concurrent.Executor,但是嚴格意義上講Executor並不是一個線程池,而只是一個執行線程的工具。真正的線程池接口是java.util.concurrent.ExecutorService

    要配置一個線程池是比較複雜的,尤其是對於線程池的原理不是很清楚的情況下,很有可能配置的線程池不是較優的,因此在java.util.concurrent.Executors線程工廠類裏面提供了一些靜態工廠,生成一些常用的線程池。官方建議使用Executors工程類來創建線程池對象。

    Executors類中有個創建線程池的方法如下:

    • public static ExecutorService newFixedThreadPool(int nThreads):返回線程池對象。(創建的是有界線程池,也就是池中的線程個數可以指定最大數量)

    獲取到了一個線程池ExecutorService 對象,那麼怎麼使用呢,在這裏定義了一個使用線程池對象的方法如下:

    • public Future<?> submit(Runnable task):獲取線程池中的某一個線程對象,並執行

    Future接口:用來記錄線程任務執行完畢后產生的結果。線程池創建與使用。

    使用線程池中線程對象的步驟:

    1. 創建線程池對象。
    2. 創建Runnable接口子類對象。(task)
    3. 提交Runnable接口子類對象。(take task)
    4. 關閉線程池(一般不操作這一步)。

    Runnable實現類代碼:

    public class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("我要一個游泳教練");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("教練來了: " + Thread.currentThread().getName());
            System.out.println("教我游泳,教會後,教練又回到了游泳池");
        }
    }

    線程池測試類:

    public class ThreadPoolDemo {
        public static void main(String[] args) {
            // 創建線程池對象
            ExecutorService service = Executors.newFixedThreadPool(2);//包含2個線程對象
            // 創建Runnable實例對象
            MyRunnable r = new MyRunnable();
    
            //自己創建線程對象的方式
            // Thread t = new Thread(r);
            // t.start(); ---> 調用MyRunnable中的run()
    
            // 從線程池中獲取線程對象,然後調用MyRunnable中的run()
            service.submit(r);
            // 再獲取個線程對象,調用MyRunnable中的run()
            service.submit(r);
            service.submit(r);
            // 注意:submit方法調用結束后,程序並不終止,是因為線程池控制了線程的關閉。
            // 將使用完的線程又歸還到了線程池中
            // 關閉線程池
            //service.shutdown();
        }
    }

    以上只是簡單的使用線程池,僅僅是入門階段!道阻且長,路還很長….

    到這裏,本文章入門暫時告一段落,以後有時間盡量抽空更新….

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

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

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

    【其他文章推薦】

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

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

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

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

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

    ※試算大陸海運運費!

  • 用雲開發快速製作客戶業務需求收集小程序丨實戰

    用雲開發快速製作客戶業務需求收集小程序丨實戰

    一、導語

    ​ 如何省去企業上門(現場)搜集客戶需求的環節,節約企業人力和時間成本,將客戶的業務定製需求直接上傳至雲數據庫?雲開發為我們提供了這個便利!

    二、需求背景

    ​ 作為一名XX公司IT萌萌新,這段時間對小程序開發一直有非常濃厚的興趣,並且感慨於“雲開發·不止於快”的境界。近期工作中,剛好碰見業務部門的一個需求,目的是節約上門跟客戶收集業務定製資料的時間,以往是每變更一次,就需要上門一次,碰見地域較遠的,費時費力,且往往要求幾天內完成上線,時間非常緊迫。因此,結合一直以來對雲開發的各種優勢的了解,我說服公司領導通過小程序·雲開發來實現。

    下面是其中一項業務定製界面的展示:
    (1)業務對業務流程有簡單說明;
    (2)相關業務介紹;
    (3)不同客戶輸入個性化需求;
    (4)雲存儲後台實現需求表單的收集。

    ​ 得力於雲開發提供的API和WeUI庫的便利,本項目在我在極短的時間內就實現了比較理想的效果 。接下來,我就從本項目入手,講講我是如何依靠小程序·雲開發將想法快速實現的,其實我也是剛入門沒多久,只是想分享一下自身在學習小程序開發項目中的一些知識點和體會,代碼可能略為粗糙,邏輯也有待優化,歡迎大家在評論區多多交流。

    三、開發過程

    1、組件

    主要使用了官方WeUI擴展能力的一些組件庫來實現主要功能。

    核心的WeUI庫主要有 Msg、Picker、圖片的Upload等(以快為目的,節省自己寫CSS樣式的時間,也方便0基礎的同學上手,這裏又體會到了小程序開發的便捷)。

    2、實現代碼

    本次雲開發包括雲數據庫雲存儲兩大功能:

    (1)雲數據庫

    雲數據庫的主要就是搜集客戶提交上來的表單信息,包括客戶的聯繫方式和選擇的業務類型等,並存儲在雲數據庫中,方便業務經理搜集需求。

    我們來看簡單的實現過程:

    首先是表單,用到了 form 表單組件以及它的 bindsubmit 方法,在 wxml 中放置 form 表單:

    <form bindsubmit="formSubmit">
        <view class="form">
          <view class="section">
            <picker bindchange="bindPickerGsd" mode="selector" value="{{indexGsd}}" range="{{arrayGsd}}">
              <view class="picker">歸屬縣市</view>
              <view class="picker-content" >{{arrayGsd[indexGsd]?arrayGsd[indexGsd]:"(必填項) 請下拉選擇歸屬地"}}</view> 
            </picker>
          </view>    
          <!---中間部分詳見代碼--->
        </view>
    
        <view class="footer">
          <button class="dz-btn" formType="submit" loading="{{formStatus.submitting}}" disabled="{{formStatus.submitting}}" bindtap="openSuccess">提交</button>
        </view>
      </form>

    表單中除了普通的文本輸入,增加有下拉列表的實現(畢竟客戶有時候是比較懶的)。

    來看一下具體代碼:

    bindPickerGsd: function (e) {    
      console.log('歸屬地已選擇,攜帶值為', e.detail.value)
      console.log('歸屬地選擇:', this.data.arrayGsd[e.detail.value])    
      this.setData({
         indexGsd: e.detail.value     
       })   
       this.data.formData.home_county = this.data.arrayGsd[e.detail.value]
    },

    最後表單上傳到雲數據庫:

      // 表單提交
      formSubmit: function (e) {
        var minlength = '';
        var maxlength = '';
        console.log("表單內容",e)
        var that = this;
        var formData = e.detail.value;
        var result = this.wxValidate.formCheckAll(formData);
        
        console.log("表單提交formData", formData);
        console.log("表單提交result", result)
        wx.showLoading({
          title: '發布中...',
        })
        const db = wx.cloud.database()
        db.collection('groupdata').add({
          data: {
            time: getApp().getNowFormatDate(),
            home_county: this.data.formData.home_county,
            group_name: formData.group_name,
            contact_name: formData.contact_name,
            msisdn: formData.msisdn,
            product_name: this.data.formData.product_name,
            word: formData.word,
          },
          success: res => {
            wx.hideLoading()
            console.log('發布成功', res)
    
          },
          fail: err => {
            wx.hideLoading()
            wx.showToast({
              icon: 'none',
              title: '網絡不給力....'
            })
            console.error('發布失敗', err)
          }
        })
      },
    (2)雲存儲

    ​ 因為業務的定製需要填單客戶所在單位的授權證明材料,因此需要提單人(使用人)上傳證明文件,因此增加了使用雲存儲的功能。

    核心代碼:

        promiseArr.push(new Promise((reslove,reject)=>{
            wx.cloud.uploadFile({
                cloudPath: "groupdata/" + group_name + "/" + app.getNowFormatDate() +suffix,
                filePath:filePath
            }).then(res=>{
                console.log("授權文件上傳成功")          
                })
                reslove()
                }).catch(err=>{
                console.log("授權文件上傳失敗",err)
        })
    
        因為涉及到不同頁面的數據傳遞,即將表單頁面的group_name作為雲存儲的文件夾用於存儲該客戶在表單中上傳的圖片,因此還需要用到getCurrentPages()來進行頁面間的數據傳遞 
    
        var pages = getCurrentPages();
        var prePage = pages[pages.length - 2];//pages.length就是該集合長度 -2就是上一個活動的頁面,也即是跳過來的頁面
        var group_name = prePage.data.formData.group_name.value//取上頁data里的group_name數據用於標識授權文件所存儲文件夾的名稱

    3、待進一步優化

    ​ 基於時間關係,本次版本僅對需求進行了簡單實現,作為公司一個可靠的項目,還需要關注”客戶隱私”、“數據安全”,以及更人性化的服務。比如:

    (1)提單人確認和認證過程

    可靠性:增加驗證碼驗證(防止他人冒名登記),以及公司受理業務有個客戶本人提交憑證。

    (2)訂閱消息

    受理成功后,可以給客戶進行處理結果的反饋,增強感知。

    (3)人工客服

    進行在線諮詢等。

    四、總結

    ​ 在本次項目開發中,我深刻體會到了雲開發的“快”,特別是雲數據庫的增刪查改功能非常方便。雲開發提供的種種便利,讓我在有新創意的時候,可以迅速採用小程序雲開發快速實現,省時省力,還能免費使用騰訊雲服務器,推薦大家嘗試!

    源碼地址

    如果你想要了解更多關於雲開發CloudBase相關的技術故事/技術實戰經驗,請掃碼關注【騰訊云云開發】公眾號~

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

    【其他文章推薦】

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

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

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

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

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

    ※試算大陸海運運費!

  • Cesium坐標系及坐標轉換詳解

    Cesium坐標系及坐標轉換詳解

    前言

    Cesium項目中經常涉及到模型加載、瀏覽以及不同數據之間的坐標轉換,弄明白Cesium中採用的坐標系以及各個坐標系之間的轉換,是我們邁向三維GIS大門的前提,本文詳細的介紹了Cesium中採用的兩大坐標系以及之間轉換的各種方法。

    Cesium中的坐標系

    Cesium中常用的坐標有兩種WGS84地理坐標系和笛卡爾空間坐標系,我們平時常用的以經緯度來指明一個地點就是用的WGS84坐標,笛卡爾空間坐標系常用來做一些空間位置變換如平移旋轉縮放等等。二者的聯繫如下圖。

    其中,WGS84地理坐標系包括 WGS84經緯度坐標系(沒有實際的對象)和 WGS84弧度坐標系(Cartographic);

             笛卡爾空間坐標系包括 笛卡爾空間直角坐標系(Cartesian3)、平面坐標系(Cartesian2),4D笛卡爾坐標系(Cartesian4)。

    WGS84坐標系

    World Geodetic System 1984,是為GPS全球定位系統使用而建立的坐標系統,坐標原點為地球質心,其地心空間直角坐標系的Z軸指向BIH (國際時間服務機構)1984.O定義的協議地球極(CTP)方向,X軸指向BIH 1984.0的零子午面和CTP赤道的交點,Y軸與Z軸、X軸垂直構成右手坐標系。我們平常手機上的指南針显示的經緯度就是這個坐標系下當前的坐標,進度範圍[-180,180],緯度範圍[-90,90]。

    WGS84坐標系

    Cesium目前支持兩種坐標系WGS84和WebMercator,但是在Cesium中沒有實際的對象來描述WGS84坐標,都是以弧度的方式來進行運用的也就是Cartographic類:

    new Cesium.Cartographic(longitude, latitude, height),這裏的參數也叫longitude、latitude,就是經度和緯度,計算方法:弧度= π/180×經緯度角度。

     笛卡爾空間直角坐標系(Cartesian3)

    笛卡爾空間坐標的原點就是橢球的中心,我們在計算機上進行繪圖時,不方便使用經緯度直接進行繪圖,一般會將坐標系轉換為笛卡爾坐標系,使用計算機圖形學中的知識進行繪圖。這裏的Cartesian3,有點類似於三維繫統中的Point3D對象,new Cesium.Cartesian3(x, y, z),裏面三個分量x、y、z。

    笛卡爾空間直角坐標系

    平面坐標系(Cartesian2)

    平面坐標系也就是平面直角坐標系,是一個二維笛卡爾坐標系,與Cartesian3相比少了一個z的分量,new Cesium.Cartesian2(x, y)。Cartesian2經常用來描述屏幕坐標系,比如鼠標在電腦屏幕上的點擊位置,返回的就是Cartesian2,返回了鼠標點擊位置的xy像素點分量。

    平面坐標系

    坐標轉換

    經緯度和弧度的轉換

    var radians=Cesium.Math.toRadians(degrees);//經緯度轉弧度
    var degrees=Cesium.Math.toDegrees(radians);//弧度轉經緯度

    WGS84經緯度坐標和WGS84弧度坐標系(Cartographic)的轉換

    //方法一:
    var longitude = Cesium.Math.toRadians(longitude1); //其中 longitude1為角度
    
    var latitude= Cesium.Math.toRadians(latitude1); //其中 latitude1為角度
    
    var cartographic = new Cesium.Cartographic(longitude, latitude, height);
    
    //方法二:
    var cartographic= Cesium.Cartographic.fromDegrees(longitude, latitude, height);//其中,longitude和latitude為角度
    
    //方法三:
    var cartographic= Cesium.Cartographic.fromRadians(longitude, latitude, height);//其中,longitude和latitude為弧度

    WGS84坐標系和笛卡爾空間直角坐標系(Cartesian3)的轉換

    通過經緯度或弧度進行轉換
    var position = Cesium.Cartesian3.fromDegrees(longitude, latitude, height);//其中,高度默認值為0,可以不用填寫;longitude和latitude為角度
    
    var positions = Cesium.Cartesian3.fromDegreesArray(coordinates);//其中,coordinates格式為不帶高度的數組。例如:[-115.0, 37.0, -107.0, 33.0]
    
    var positions = Cesium.Cartesian3.fromDegreesArrayHeights(coordinates);//coordinates格式為帶有高度的數組。例如:[-115.0, 37.0, 100000.0, -107.0, 33.0, 150000.0]
    
    //同理,通過弧度轉換,用法相同,具體有Cesium.Cartesian3.fromRadians,Cesium.Cartesian3.fromRadiansArray,Cesium.Cartesian3.fromRadiansArrayHeights等方法

    注意:上述轉換函數中最後均有一個默認參數ellipsoid(默認值為Ellipsoid.WGS84)。

    通過過度進行轉換

    具體過度原理可以參考上邊的注意事項。

    var position = Cesium.Cartographic.fromDegrees(longitude, latitude, height);
    var positions = Cesium.Ellipsoid.WGS84.cartographicToCartesian(position);
    var positions = Cesium.Ellipsoid.WGS84.cartographicArrayToCartesianArray([position1,position2,position3]);

    笛卡爾空間直角坐標系轉換為WGS84

    直接轉換
    var cartographic= Cesium.Cartographic.fromCartesian(cartesian3);

    轉換得到WGS84弧度坐標系后再使用經緯度和弧度的轉換,進行轉換到目標值

    間接轉換
    var cartographic = Cesium.Ellipsoid.WGS84.cartesianToCartographic(cartesian3);
    var cartographics = Cesium.Ellipsoid.WGS84.cartesianArrayToCartographicArray([cartesian1,cartesian2,cartesian3]);

    平面坐標系(Cartesian2)和笛卡爾空間直角坐標系(Cartesian3)的轉換

    平面坐標系轉笛卡爾空間直角坐標系

    這裏注意的是當前的點(Cartesian2)必須在三維球上,否則返回的是undefined;通過ScreenSpaceEventHandler回調會取到的坐標都是Cartesian2。

    屏幕坐標轉場景坐標-獲取傾斜攝影或模型點擊處的坐標

    這裏的場景坐標是包含了地形、傾斜攝影表面、模型的坐標。

    通過viewer.scene.pickPosition(movement.position)獲取,根據窗口坐標,從場景的深度緩衝區中拾取相應的位置,返回笛卡爾坐標。

    var handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
    handler.setInputAction(function (movement) {
         var position = viewer.scene.pickPosition(movement.position);
         console.log(position);
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);

    注:若屏幕坐標處沒有傾斜攝影表面、模型時,獲取的笛卡爾坐標不準,此時要開啟地形深度檢測(viewer.scene.globe.depthTestAgainstTerrain = true; //默認為false)。

    屏幕坐標轉地表坐標-獲取加載地形后對應的經緯度和高程

    這裡是地球表面的世界坐標,包含地形,不包括模型、傾斜攝影表面。

    通過viewer.scene.globe.pick(ray, scene)獲取,其中ray=viewer.camera.getPickRay(movement.position)。

    var handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
    handler.setInputAction(function (movement) {
         var ray = viewer.camera.getPickRay(movement.position);
         var position = viewer.scene.globe.pick(ray, viewer.scene);
         console.log(position);
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);

    注:通過測試,此處得到的坐標通過轉換成wgs84后,height的為該點的地形高程值。

    屏幕坐標轉橢球面坐標-獲取鼠標點的對應橢球面位置

    這裏的橢球面坐標是參考橢球的WGS84坐標(Ellipsoid.WGS84),不包含地形、模型、傾斜攝影表面。

    通過 viewer.scene.camera.pickEllipsoid(movement.position, ellipsoid)獲取,可以獲取當前點擊視線與橢球面相交處的坐標,其中ellipsoid是當前地球使用的橢球對象:viewer.scene.globe.ellipsoid,默認為Ellipsoid.WGS84

    var handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
    handler.setInputAction(function (movement) {
         var position = viewer.scene.camera.pickEllipsoid(movement.position, viewer.scene.globe.ellipsoid);
         console.log(position);
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);

    注:通過測試,此處得到的坐標通過轉換成wgs84后,height的為0(此值應該為地表坐標減去地形的高程)。

    笛卡爾空間直角坐標系轉平面坐標系

    var cartesian2= Cesium.SceneTransforms.wgs84ToWindowCoordinates(viewer.scene,cartesian3)

    空間位置變換

    經緯度轉換到笛卡爾坐標系后就能運用計算機圖形學中的仿射變換知識進行空間位置變換如平移旋轉縮放。

    Cesium為我們提供了很有用的變換工具類:Cesium.Cartesian3(相當於Point3D)Cesium.Matrix3(3×3矩陣,用於描述旋轉變換)Cesium.Matrix4(4×4矩陣,用於描述旋轉加平移變換),Cesium.Quaternion(四元數,用於描述圍繞某個向量旋轉一定角度的變換)。

    下面舉個例子:

          一個局部坐標為p1(x,y,z)的點,將它的局部坐標原點放置到loc(lng,lat,alt)上,局部坐標的z軸垂直於地表,局部坐標的y軸指向正北,並圍繞這個z軸旋轉d度,求此時p1(x,y,z)變換成全局坐標笛卡爾坐p2(x1,y1,z1)是多少?

    var rotate = Cesium.Math.toRadians(d);//轉成弧度
    var quat = Cesium.Quaternion.fromAxisAngle(Cesium.Cartesian3.UNIT_Z, rotate); //quat為圍繞這個z軸旋轉d度的四元數
    var rot_mat3 = Cesium.Matrix3.fromQuaternion(quat);//rot_mat3為根據四元數求得的旋轉矩陣
    var v = new Cesium.Cartesian3(x, y, z);//p1的局部坐標
    var m = Cesium.Matrix4.fromRotationTranslation(rot_mat3, Cesium.Cartesian3.ZERO);//m為旋轉加平移的4x4變換矩陣,這裏平移為(0,0,0),故填個Cesium.Cartesian3.ZERO
    m = Cesium.Matrix4.multiplyByTranslation(m, v);//m = m X v
    var cart3 = ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(lng, lat, alt)); //得到局部坐標原點的全局坐標
    var m1 = Cesium.Transforms.eastNorthUpToFixedFrame(cart3);//m1為局部坐標的z軸垂直於地表,局部坐標的y軸指向正北的4x4變換矩陣
    m = Cesium.Matrix4.multiplyTransformation(m, m1);//m = m X m1
    var p2 = Cesium.Matrix4.getTranslation(m);//根據最終變換矩陣m得到p2
    console.log('x=' + p2.x + ',y=' + p2.y + ',z=' + p2.z );

    總結

    通過本文,介紹了各個坐標系間的轉換問題,在具體項目中,可結合實際需求,靈活組合解決具體的實際問題。注意,博文是參照網上相關博客及結合自己的實踐總結得來,希望本文對你有所幫助,後續會更新更多內容,感興趣的朋友可以加關注,歡迎留言交流!

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

    【其他文章推薦】

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

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

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

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

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

    ※試算大陸海運運費!

  • Tsx寫一個通用的button組件

    Tsx寫一個通用的button組件

    一年又要到年底了,vue3.0都已經出來了,我們也不能一直還停留在過去的js中,是時候學習並且在項目中使用一下Ts了。

      如果說jsx是基於js的話,那麼tsx就是基於typescript的

      廢話也不多說,讓我們開始寫一個Tsx形式的button組件,

      ts真的不僅僅只有我們常常熟知的數據類型,還包括接口,類,枚舉,泛型,等等等,這些都是特別重要的

      項目是基於vue-cli 3.0 下開發的,可以自己配置Ts,不會的話那你真的太難了

      

     

         我們再compenonts中新建一個button文件夾,再建一個unit文件夾,button放button組件的代碼,unit,放一些公共使用模塊

      我們再button文件夾下創建 ,index .tsx放的button源碼,index.less放的是樣式,css也是不可缺少的

           

     

       分析一下button需要的一些東西

      第一個當然是props,還有一個是點擊事件,所以我們第一步就定義一下這兩個類型

    type ButtonProps = {
      tag: string,
      size: ButtonSize,
      type: ButtonType,
      text: String
    }
    
    type ButtonEvents = {
      onClick?(event: Event) :void
    }
    type ButtonSize = 'large' | 'normal' | 'small' | 'mini'
    type ButtonType = 'default' | 'primary' | 'info' | 'warning' | 'danger'

      因為button是很簡單的組件,內部也沒有一些特別的狀態需要改變,所以我們用函數式組件的方式去寫(之後的render會用到這個方法)

    function Button (h: CreateElement, props: ButtonProps, slots: DefaultSlots, ctx: RenderContext<ButtonProps>) {
      const { tag, size, type } = props
      let text
      console.log(slots)
      text = slots.default ? slots.default() : props.text
      function onClick (event: Event) {
        emit(ctx, 'click', event)
      }
      let classes = [size,type]
      return (
        <tag
          onClick = {onClick}
          class = {classes}
        >
          {text}
        </tag>
      )
    }

      h 是一個預留參數,這裏並沒有用到 ,CreateElement  這個是vue從2.5之後提供的一個類型,也是為了方便在vue項目上使用ts

      props 就是button組件的傳入的屬性,slots插槽,ctx,代表的是當前的組件,可以理解為當前rendercontext執行環境this

      DefaultSlots是我們自定義的一個插槽類型

    export type ScopedSlot<Props = any> = (props?: Props) => VNode[] | VNode | undefined;
    
    export type ScopedSlots = {
      [key: string]: ScopedSlot | undefined;
    }

      插槽的內容我們都是需要從ctx中讀取的,默認插槽的key就是defalut,具名插槽就是具體的name

      button放發內部還有一個具體的點擊事件,還有一個emit方法,從名字我們也可以看的出,他是粗發自定義事件的,我們這裏當然不能使用this.emit去促發,

      所以我們需要單獨這個emit方法,我們知道組件內所以的自定義事件都是保存在listeners里的,我們從ctx中拿取到所以的listeners

    
    
    
    

      import { RenderContext, VNodeData } from ‘vue’ // 從vue中引入一些類型


    function
    emit (context: RenderContext, eventName: string, ...args: any[]) { const listeners = context.listeners[eventName] if (listeners) { if (Array.isArray(listeners)) { listeners.forEach(listener => { listener(...args) }) } else { listeners(...args) } }

      這樣我們組件內部的事件觸發就完成了

      我們的button肯定是有一些默認的屬性,所以,我們給button加上默認的屬性

    Button.props = {
      text: String,
      tag: {
        type: String,
        default: 'button'
      },
      type: {
        type: String,
        default: 'default'
      },
      size: {
        type: String,
        default: 'normal'
      }
    }

      我們定義一個通用的functioncomponent 類型

    type FunctionComponent<Props=DefaultProps, PropsDefs = PropsDefinition<Props>> = {
      (h: CreateElement, Props:Props, slots: ScopedSlots, context: RenderContext<Props>): VNode |undefined,
      props?: PropsDefs
    }

      PropsDefinition<T>  這個是vue內部提供的,對 props的約束定義

      不管怎麼樣我們最終返回的肯定是一個對象,我們把這個類型也定義一下

      ComponentOptions<Vue> 這個也是vue內部提供的

     interface DrmsComponentOptions extends ComponentOptions<Vue> {
      functional?: boolean;
      install?: (Vue: VueConstructor) => void;
    }

      最終生成一個組件對象

    function transformFunctionComponent (fn:FunctionComponent): DrmsComponentOptions {
      return {
        functional: true, // 函數時組件,這個屬性一定要是ture,要不render方法,第二個context永遠為underfine
        props: fn.props,
        model: fn.model,
        render: (h, context): any => fn(h, context.props, unifySlots(context), context)
      }
    }

      unifySlots 是讀取插槽的內容

    // 處理插槽的內容
    export function unifySlots (context: RenderContext) {
      // use data.scopedSlots in lower Vue version
      const scopedSlots = context.scopedSlots || context.data.scopedSlots || {}
      const slots = context.slots()
    
      Object.keys(slots).forEach(key => {
        if (!scopedSlots[key]) {
          scopedSlots[key] = () => slots[key]
        }
      })
    
      return scopedSlots
    }

      當然身為一個組件,我們肯定是要提供全局注入接口,並且能夠按需導入

      所以我們給組件加上名稱和install方法,install 是 vue.use() 方法使用的,這樣我們能全部註冊組件

    export function CreateComponent (name:string) {
      return function <Props = DefaultProps, Events = {}, Slots = {}> (
        sfc:DrmsComponentOptions | FunctionComponent) {
        if (typeof sfc === 'function') {
          sfc = transformFunctionComponent(sfc)
        }
        sfc.functional = true
        sfc.name = 'drms-' + name
        sfc.install = install
        return sfc 
      }
    }

      index.tsx 中的最後一步,導出這個組件

    export default CreateComponent('button')<ButtonProps, ButtonEvents>(Button)

      還少一個install的具體實現方法,加上install方法,就能全局的按需導入了

    function install (this:ComponentOptions<Vue>, Vue:VueConstructor) {
      const { name } = this
      Vue.component(name as string, this)
    }

     

       最終實現的效果圖,事件的話也是完全ok的,這個我也是測過的

     

       代碼參考的是vant的源碼:

      該代碼已經傳到git:     dev分支應該是代碼全的,master可能有些並沒有合併

     

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

    【其他文章推薦】

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

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

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

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

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

  • GM 首款自動駕駛電動車將透過租車平台展開服務

    GM 首款自動駕駛電動車將透過租車平台展開服務

    2016 年年初GM 宣佈向線上租車服務平臺Lyft 投資5 億美元,雙方將在自動駕駛汽車的應用上展開合作,近日GM 高管Pam Fletcher 透露,GM 的首款自動駕駛電動車將透過Lyft 的租車服務平臺推出,能夠為用戶提供更好的乘坐體驗。

    目前各大傳統汽車廠商紛紛進入無人駕駛領域,GM 也不例外,據GM 自動駕駛技術部門首席工程師Pam Fletcher 透露,雖然GM 還沒有正式宣佈自動駕駛車的發表日期,但這一切會比外界預期的更早到來,GM 正在和線上租車服務Lyft 展開合作,開發一個租車分享平臺,這不是一個停留在概念階段的專案,GM 的團隊已經準備好把這一服務推廣到市場。

    GM 的首款自動駕駛汽車將是電動車,目前電動車是GM 重點推進的產品,2016 年年底該公司將推出可遠程行駛的電動車BoltEV,這是一款為城市通勤設計的電動車,同時也考慮了租車服務的需求,Pam Fletcher 認為將自動駕駛技術應用在電動車上非常有意義,能夠給用戶帶來更好的乘坐體驗,電動車行駛時平穩、安靜,乘客可以在車中休息或者處理工作事務。

    2016 年3 月GM 收購自動駕駛技術研發公司Cruise Automation,以提升該公司在這一領域的技術實力,2016 年5 月Cruise 帶來的自動駕駛技術已經在Bolt 電動車上進行測試。目前GM、Lyft 共同研發的租車分享系統與Bolt 電動車的自動駕駛技術測試分屬不同的專案,但未來有可能會結合。

    據悉GM 和Lyft 有可能在2016 年年底將自動駕駛電動車帶來公路上測試,Bolt 電動車有可能成為主要的測試車款。Bolt 電動車的許多設計看起來都像是為自動駕駛而設計的,未來自動駕駛與線上租車服務結合也並不讓人感到意外。

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

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

    【其他文章推薦】

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

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

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

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

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

  • mysql 索引筆記

    mysql 索引筆記

    MyISAM引擎的B+Tree的索引

    通過上圖可以直接的看出, 在MyISAM對B+樹的運用中明顯的特點如下:

    • 所有的非恭弘=叶 恭弘子節點中存儲的全部是索引信息
    • 在恭弘=叶 恭弘子節點中存儲的 value值其實是 數據庫中某行數據的index

    MyISAM引擎 索引文件的查看:

    在 /var/lib/mysql目錄中

    .myd 即 my data , 數據庫中表的數據文件

    .myi 即 my index , 數據庫中 索引文件

    .log 即 mysql的日誌文件

    InnoDB引擎 索引文件的查看:

    同樣在 /var/lib/mysql 目錄下面

    InnoDB引擎的B+Tree的索引

    InnoDB的實現方式業內也稱其為聚簇索引, 什麼是聚簇索引呢? 就是相鄰的行的簡直被存儲到一起, 對比上面的兩幅圖片就會發現, 在InnDB中, B+樹的恭弘=叶 恭弘子節點中存儲的是數據行中的一行行記錄, 缺點: 因為索引文件被存放在硬盤上, 所以很占硬盤的空間

    一般我們會在每一個表中添加一列 取名 id, 設置它為primary key , 即將他設置成主鍵, 如果使用的存儲引擎也是InnoDB的話, 底層就會建立起主鍵索引, 也是聚簇索引, 並且會自動按照id的大小為我們排好序,(因為它的一個有序的樹)

    局部性原理

    局部性原理是指CPU訪問存儲器時,無論是存取指令還是存取數據,所訪問的存儲單元都趨於聚集在一個較小的連續區域中。 更進一步說, 當我們通過程序向操作系統發送指令讓它讀取我們指定的數據時, 操作系統會一次性讀取一頁(centos 每頁4kb大小,InnoDB存儲引擎中每一頁16kb)的數據, 它遵循局部性理論, 猜測當用戶需要使用某個數據時, 用戶很可能會使用這個數據周圍的數據,故而進行一次

    InnoDB的頁格式

    什麼是頁呢? 簡單說,就是一條條數據被的存儲在磁盤上, 使用數據時需要先將數據從磁盤上讀取到內存中, InnoDB每次讀出數據時同樣會遵循 局部性原理, 而不是一條條讀取, 於是InnoDB將數據劃分成一個一個的頁, 以頁作為和磁盤之間交互的基本單位

    通過如下sql, 可以看到,InnoDB中每一頁的大小是16kb

    show global status like 'Innodb_page_size';

    名稱 簡述
    File Header 文件頭部, 存儲頁的一些通用信息
    Page Header 頁面頭部, 存儲數據頁專有的信息
    Infinum + supremum 最大記錄和最小記錄, 這是兩個虛擬的行記錄
    User Records 用戶記錄, 用來實際存儲行記錄中的內容
    Free Space 空閑空間, 頁中尚位使用的空間
    Page Directory 頁面目錄, 存儲頁中某些記錄的位置
    File Tailer 文件尾部 , 用來校驗頁是否完整

    InnoDB的行格式 compact

    每一頁中存儲的行數據越多. 整體的性能就會越強

    compact的行格式如下圖所示

    可以看到在行格式中在存儲真正的數據的前面會存儲一些其他信息, 這些信息是為了描述這條記錄而不得不添加的一些信息, 這些額外的信息就是上圖中的前三行

    • 變長字段的長度列表

    在mysql中char是固定長度的類型, 同時mysql還支持諸如像 varchar這樣可變長度的類型, 不止varchar , 想 varbinary text blob這樣的變長數據類型, 因為 變長的數據類型的列存儲的數據的長度是不固定的, 所以說我們在存儲真正的數據時, 也得將這些數據到底佔用了多大的長度也給保存起來

    • NULL標誌位

    compact行格式會將值可以為NULL的列統一標記在 NULL標誌位中, 如果數據表中所有的字段都被標記上not null , 那麼就沒有NULL值列表

    • 記錄頭信息

    記錄頭信息, 顧名思義就是用來描述記錄頭中的信息, 記錄頭信息由固定的5個字節組成, 一共40位, 不同位代表的意思也不同, 如下錶

    名稱 單位 bit 簡介
    預留位1 1 未使用
    預留位2 1 未使用
    delete_mark 1 標記改行記錄是否被刪除了
    min_rec_mark 1 標記在 B+樹中每層的非恭弘=叶 恭弘子節點中最小的node
    n_owned 4 表示當前記錄擁有的記錄數
    heap_no 13 表示當前記錄在堆中的位置
    record_type 3 表示當前記錄的類型 , 0表示普通記錄, 1表示B+樹中非恭弘=叶 恭弘子節點記錄, 2表示最小記錄 ,3表示最大記錄
    next_record 16 表示下一條記錄的相對位置

    行溢出

    在mysql中每一行, 能存儲的最大的字節數是65535個字節數, 此時我們使用下面的sql執行時就會出現行溢出現象

    CREATE TABLE test ( c VARCHAR(65535) ) CHARSET=ascii ROW_FORMAT=Compact;

    給varchar申請最大65535 , 再加上compact行格式中還有前面三個非數據列佔用內存,所以一準溢出, 如果不想溢出, 可以適當的將 65535 – 3

    頁溢出

    前面說了, InnoDB中數據的讀取按照頁為單位, 每一頁的大小是 16kb, 換算成字節就是16384個字節, 但是每行最多存儲 65535個字節啊, 也就是說一行數據可能需要好幾個頁來存儲

    怎麼辦呢?

    • compact行格式會在存儲真實數據的列中多存儲一部分數據, 這部分數據中存儲的就是下一頁的地址
    • dynamic行格式 中直接存儲數據所在的地址, 換句話說就是數據都被存儲在了其他頁上
    • compressed行格式會使用壓縮算法對行格式進行壓縮處理

    一般我們都是將表中的id列設置為主鍵, 這就會形成主鍵索引, 於是我們需要注意了:

    主鍵的佔用的空間越小,整體的檢索效率就會越高

    為什麼這麼說呢? 這就可以結合頁的概念來解析, 在B+樹這種數據結果中, 恭弘=叶 恭弘子節點中用來存儲數據, 存儲數據的格式類似Key-value key就是索引值, value就是數據內容, 如果索引佔用的空間太大的話, 單頁16kb能存儲的索引就越小, 這就導致數據被分散在更多的頁上, 致使查詢的效率降低

    建立索引的技巧

    為某一列建立索引

    給text表中的title列創建索引, 索引名字 my_index
    alter table text add index my_index (title);

    雖然建立索引能提升查詢的效率, 根據前人的經驗看, 這並不是一定的, 建立索引本身會直接消耗內存空間, 同時索, 插入,刪除, 這種寫操作就會打破B+樹的平衡面臨索引的重建, 一般出現如下兩種情況時,是不推薦建立索引的

    1. 表中的數據本身就很少
    2. 我們計算一下索引的選擇性很低

    兼顧 – 索引的選擇性與前綴索引

    所謂選擇性,其實就是說不重複出現的索引值(基數,Cardinality) 與 表中的記錄數的比值

    即: 選擇性= 基數 / 記錄數

    選擇性的取值範圍在(0,1]之間, 選擇性越接近1 , 說明建立索引的必要性就越強, 比如對sex列進行建立索引,這裏面非男即女, 如果對它建立索引的話, 其實是沒意義的, 還不如直接進行全表掃描來的快

    如何使用sql計算選擇性呢? 嚴格遵循上面的公式

    SELECT count(DISTINCT(title))/count(*) AS Selectivity FROM employees.titles;
    count(基數/記錄數)
    DISTINCT(title) / /count(*)

    更詳細的例子看下面的連接

    索引失效問題

    注意事項

    • 索引無法存儲null值

    • 如果條件中有or, 即使條件中存在索引也不會使用索引,如果既想使用or,又想使用索引, 就給所有or條件控制的列加上索引

    • 使用like查詢時, 如果以%開頭,肯定是進行全表掃描

    • 使用like查詢時, 如果%在條件後面

      • 對於主鍵索引, 索引失效
      • 對於普通索引, 索引不失效
    • 如果列的類型是字符串類型, 那麼一定要在條件中將數據用引號引起來,不然也會是索引失效

    • 如果mysql認為全表掃描比用索引塊, 同樣不會使用索引

    聯合索引

    什麼是聯合索引

    聯合索引, 也叫複合索引,說白了就是多個字段一起組合成一個索引

    像下面這樣使用 id + title 組合在一起構成一個聯合索引

    CREATE TABLE `text` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `title` varchar(255) NOT NULL,
      `content` text NOT NULL,
      PRIMARY KEY (`id`,`title`)
    ) ENGINE=InnoDB AUTO_INCREMENT=3691 DEFAULT CHARSET=utf8
    • 如果我們像上圖那樣創建了索引,我們只要保證我們的 id+title 兩者結合起來全局唯一就ok
    • 建立聯合索引同樣是需要進行排序的,排序的規則就是按照聯合索引所有列組成的字符串的之間的先後順序進行排序, 如a比b優先

    左前原則

    使用聯合索引進行查詢時一定要遵循左前綴原則, 什麼是左前綴原則呢? 就是說想讓索引生效的話,一定要添加上第一個索引, 只使用第二個索引進行查詢的話會導致索引失效

    比如上面創建的聯合索引, 假如我們的查詢條件是 where id = ‘1’ 或者 where id = ‘1’ and title = ‘唐詩宋詞’ 索引都會不失效

    但是如果我們不使用第一個索引id, 像這樣 where title = ‘唐詩’ , 結果就是導致索引失效

    聯合索引的分組&排序

    還是使用這個例子:

    CREATE TABLE `text` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `title` varchar(255) NOT NULL,
      `content` text NOT NULL,
      PRIMARY KEY (`id`,`title`)
    ) ENGINE=InnoDB AUTO_INCREMENT=3691 DEFAULT CHARSET=utf8

    demo1: 當我們像下面這樣寫sql時, 就會先按照id進行排序, 當id相同時,再按照title進行排序

    select * form text order by id, title;

    demo2: 當我們像下面這樣寫sql時, 就會先將id相同的劃分為一組, 再將title相同的劃分為一組

    select id,title form text group by id, title;

    demo3: ASC和DESC混用, 其實大家都知道底層使用B+樹, 本身就是有序的, 要是不加限制的話,默認就是ASC, 反而是混着使用就使得索引失效

    select * form text order by id ASC, title DESC;

    如何定位慢查詢

    相關參數

    名稱 簡介
    slow_query_log 慢查詢的開啟狀態
    slow_query_log_file 慢查詢日誌存儲的位置
    long_query_time 查詢超過多少秒才記錄下來

    常用sql

    # 查看mysql是否開啟了慢查詢
    show variables like 'slow_query_log';   
    # 將全局變量設置為ON
    set global slow_query_log ='on';
    # 查看慢查詢日誌存儲的位置
    show variables like 'slow_query_log_file';
    # 查看規定的超過多少秒才被算作慢查詢記錄下來
    show variables like 'long_query_time';
    show variables like 'long_query%';
    # 超過一秒就記錄 , 每次修改這個配置都重新建立一次鏈接
    set global long_query_time=1; 

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

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

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

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

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

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

  • 如何教會女友遞歸算法?

    如何教會女友遞歸算法?

    一到周末就開始放蕩自我,這不帶着女朋友去萬達電影院看電影(其實是由於整天呆在家敲代碼硬是

    被女朋友強行拖拽去看電影,作為一個有理想的程序員,我想各位應該都能體諒我),一到電影院,

    女朋友說要買爆米花和可樂,我當時二話沒說,臣本布衣躬耕於南陽,壤中羞澀,所以單買了爆米

    花,買完都不帶回頭看老闆的那種,飲料喝多了不好,出門的時候我帶了白開水,還得虧我長得銷

    魂,乍一看就能看出是個社會精神小伙,女朋友也沒多說什麼,只是對我微了微笑(我估計是被我的

    顏值以及獨到的見解所折服),剛坐下沒多久,女朋友突然問我,咱們現在坐在第幾排啊?電影院里

    面太黑了,看不清,沒法數,這個時候,如果是你現在你怎麼辦?別忘了你我是程序員,這個可難不

    倒我,遞歸就開始排上用場了。於是我就問前面一排的人他是第幾排,你想只要在他的数字上加一,

    就知道自己在哪一排了。但是,前面的人也看不清啊,所以他也問他前面的人。就這樣一排一排往前

    問,直到問到第一排的人,說我在第一排,然後再這樣一排一排再把数字傳回來。直到你前面的人告

    訴你他在哪一排,於是你就知道答案了。這就是一個非常標準的遞歸求解問題的分解過程,去的過程

    叫“遞”,回來的過程叫“歸”。基本上,所有的遞歸問題都可以用遞推公式來表示。我們用遞推公式將

    它表示出來就是這樣的

    f ( n ) = f (n – 1) + 1 其中,f ( 1 ) = 1

    f(n)表示你想知道自己在哪一排,f(n-1)表示前面一排所在的排數,f(1)=1表示第一排的人知道自己在

    第一排。有了這個遞推公式,我們就可以很輕鬆地將它改為遞歸代碼,如下:

    int f(int n) {
      if (n == 1) return 1;
      return f(n-1) + 1;
    }

    女朋友不懂遞歸,於是我給她講遞歸需要滿足的三個條件:

    1.一個問題的解可以分解為幾個子問題的解

    何為子問題?子問題就是數據規模更小的問題。就好比,在電影院,你要知道,“自己在哪一排”的問

    題,可以分解為“前一排的人在哪一排”這樣一個子問題。

    2.這個問題與分解之後的子問題,除了數據規模不同,求解思路完全一樣

    你求解“自己在哪一排”的思路,和前面一排人求解“自己在哪一排”的思路,是一模一樣的。

    3.存在遞歸終止條件

    把問題分解為子問題,把子問題再分解為子子問題,一層一層分解下去,不能存在無限循環,這就需

    要有終止條件。就好比,第一排的人不需要再繼續詢問任何人,就知道自己在哪一排,也就是

    f(1)=1,這就是遞歸的終止條件。

    如何教女友敲遞歸代碼?

    剛剛鋪墊了這麼多,現在我們來看,如何來教女友敲遞歸代碼?個人覺得,寫遞歸代碼最關鍵的是寫

    出遞推公式,找到終止條件,剩下將遞推公式轉化為代碼就很簡單了。

    你先記住這個理論。我舉一個例子,帶你一步一步實現一個遞歸代碼,幫你理解。

    假如這裡有n個台階,每次你可以跨1個台階或者2個台階,請問走這n個台階有多少種走法?如果有7個台階,你可以2,2,2,1這樣子上去,也可以1,2,1,1,2這樣子上去,總之走法有很多,那如何用編程求得總共有多少種走法呢?

    我們仔細想下,實際上,可以根據第一步的走法把所有走法分為兩類,第一類是第一步走了1個台

    階,另一類是第一步走了2個台階。所以n個台階的走法就等於先走1階后,n-1個台階的走法 加上先

    走2階后,n-2個台階的走法。用公式表示就是:

    f ( n ) = f (n – 1) + f ( n – 2 )

    有了遞推公式,遞歸代碼基本上就完成了一半。我們再來看下終止條件。當有一個台階時,我們不需

    要再繼續遞歸,就只有一種走法。所以f(1)=1。這個遞歸終止條件足夠嗎?我們可以用n=2,n=3這樣

    比較小的數試驗一下。

    n=2時,f(2)=f(1)+f(0)。如果遞歸終止條件只有一個f(1)=1,那f(2)就無法求解了。所以除了f(1)=1這一

    個遞歸終止條件外,還要有f(0)=1,表示走0個台階有一種走法,不過這樣子看起來就不符合正常的

    邏輯思維了。所以,我們可以把f(2)=2作為一種終止條件,表示走2個台階,有兩種走法,一步走完

    或者分兩步來走。

    所以,遞歸終止條件就是f(1)=1,f(2)=2。這個時候,你可以再拿n=3,n=4來驗證一下,這個終止條

    件是否足夠並且正確。

    我們把遞歸終止條件和剛剛得到的遞推公式放到一起就是這樣的:

    f(1) = 1;
    f(2) = 2;
    f(n) = f(n-1)+f(n-2)

    有了這個公式,我們轉化成遞歸代碼就簡單多了。最終的遞歸代碼是這樣的:

    int f(int n) {
      if (n == 1) return 1;
      if (n == 2) return 2;
      return f(n-1) + f(n-2);
    }

    我總結一下,寫遞歸代碼的關鍵就是找到如何將大問題分解為小問題的規律,並且基於此寫出遞推公式,然後再推敲終止條件,最後將遞推公式和終止條件翻譯成代碼。

    如果以後再遇到類似問題,A可以分解為若干子問題B、C、D情況,你可以假設子問題B、C、D已經

    解決,在此基礎上思考如何解決問題A。而且,你只需要思考問題A與子問題B、C、D兩層之間的關

    系即可,不需要一層一層往下思考子問題與子子問題,子子問題與子子子問題之間的關係。屏蔽掉遞

    歸細節,這樣子理解起來就簡單多了。

    因此,編寫遞歸代碼的關鍵是,只要遇到遞歸,我們就把它抽象成一個遞推公式,不用想一層層的調

    用關係,不要試圖用人腦去分解遞歸的每個步驟

    如何教女友玩轉漢羅塔

    好了,講完了遞歸算法,再回到電影院,不說別的,我還真那麼做了,我真問了前面一排的人他是第

    幾排如果不清楚並讓他跟我一樣問他的上一排,顯然,沒循環到第三人,我差點被認為是神經病,差

    點沒被幾個社會精神小伙打si,座位事情暫時告一段落,話說這電影屬實夠無聊,於是我不知是趁熱

    打鐵,還是心血來潮,非要給女朋友玩一個漢羅塔遊戲,我這暴脾氣,剛實踐遞歸算法被懟,是時候

    挽回形象了,不秀一把遞歸算法我就不得勁。就是這個遊戲,至於遊戲規則,我覺得你體驗一兩把絕

    對比我說的更加記憶深刻,,別看4399覺得有點弱zhi,再怎麼說也承

    載着童年

    果然,女朋友是個哈皮,剛過第三關就撲街了,這個時候,頭冒五丈光芒的我身披金甲挺身而出(貌

    似有一點點小誇張,劇情需要嘛)一聲不吭地敲了幾行靚麗的代碼

    public class TestHanoi {
    
        public static void main(String[] args) {
            hanoi(5,'A','B','C');  //可以理解為5個圈或者第5關
        }
        
        /**
         * @param n     共有N個圈
         * @param A    開始的柱子
         * @param B 中間的柱子
         * @param C 目標的柱子
         * 無論有多少個圈,都認為只有兩個。上面的所有圈和最下面一個圈。
         */
        public static void hanoi(int n,char A,char B,char C) {
            //只有一個圈。
            if(n==1) {
                System.out.println("第1個盤子從"+A+"移到"+C);
            //無論有多少個圈,都認為只有兩個。上面的所有圈和最下面一個圈。
            }else {
                //移動上面所有的圈到中間位置
                hanoi(n-1,A,C,B);
                //移動下面的圈
                System.out.println("第"+n+"個圈從"+A+"移到"+C);
                //把上面的所有圈從中間位置移到目標位置
                hanoi(n-1,B,A,C);
            }
        }
    
    }

    只要main方法一致行,對着結果移動即可,就跟開了掛一樣的,其實漢羅塔問題核心關鍵是無論有多少個圈,都認為只有兩個。上面的所有圈和最下面一個圈。

    到這裏,教女友敲遞歸算法代碼,你學會了嗎?

    哦豁,明天還是一個晴天~老天賜給宜春一個女朋友吧~畢竟我們程序員長得又帥敲代碼又好看,是吧哥幾個~~

    如果本文對你有一點點幫助,那麼請點個讚唄,謝謝~

    最後,若有不足或者不正之處,歡迎指正批評,感激不盡!如果有疑問歡迎留言,絕對第一時間回復!

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

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

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

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

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

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

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

  • 堡壘機的核心武器:WebSSH錄像實現

    堡壘機的核心武器:WebSSH錄像實現

    WebSSH終端錄像的實現終於來了

    前邊寫了兩篇文章和深入介紹了終端錄製工具Asciinema,我們已經可以實現在終端下對操作過程的錄製,那麼在WebSSH中的操作該如何記錄並提供後續的回放審計呢?

    一種方式是文章最後介紹的自動錄製審計日誌的方法,在主機上添加個腳本,每次連接自動進行錄製,但這樣不僅要在每台遠程主機添加腳本,會很繁瑣,而且錄製的腳本文件都是放在遠程主機上的,後續播放也很麻煩

    那該如何更好處理呢?下文介紹一種優雅的方式來實現,核心思想是不通過錄製命令進行錄製,而在Webssh交互執行的過程中直接生成可播放的錄像文件

    設計思路

    通過上邊兩篇文章的閱讀,我們已經知道了Asciinema錄像文件主要由兩部分組成:header頭和IO流數據

    header頭位於文件的第一行,定義了這個錄像的版本、寬高、開始時間、環境變量等參數,我們可以在websocket連接創建時將這些參數按照需要的格式寫入到文件

    header頭數據如下,只有開頭一行,是一個字典形式

    {"version": 2, "width": 213, "height": 55, "timestamp": 1574155029.1815443, "env": {"SHELL": "/bin/bash", "TERM": "xterm-256color"}, "title": "ops-coffee"}

    整個錄像文件除了第一行的header頭部分,剩下的就都是輸入輸出的IO流數據,從websocket連接建立開始,隨着操作的進行,IO流數據是不斷增加的,直到整個websocket長連接的結束,那就需要在整個WebSSH交互的過程中不斷的往錄像文件追加輸入輸出的內容

    IO流數據如下,每一行一條,列表形式,分別表示操作時間,輸入或輸出(這裏我們為了方便就寫固定字符串輸出),IO數據

    [0.2341010570526123, "o", "Last login: Tue Nov 19 17:11:30 2019 from 192.168.105.91\r\r\n"]

    似乎很完美,按照上邊的思路錄像文件就應該沒有問題了,但還有一些細節需要處理

    首先是需要歷史連接列表,在這個列表裡可以看到什麼時間,哪個用戶連接了哪台主機,當然也需要提供回放功能,新建一張表來記錄這些信息

    class Record(models.Model):
        create_time = models.DateTimeField(auto_now_add=True, verbose_name='創建時間')
    
        host = models.ForeignKey(Host, on_delete=models.CASCADE, verbose_name='主機')
        user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='用戶')
    
        filename = models.CharField(max_length=128, verbose_name='錄像文件名稱')
    
        def __str__(self):
            return self.host

    其次還需要考慮的一個問題是header和後續IO數據流要寫入同一個文件,這就需要在整個websocket的連接過程中有一個固定的文件名可被讀取,這裏我使用了主機+用戶+當前時間作為文件名,同一用戶在同一時間不能多次連接同一主機,這樣可保證文件名不重複,同時避免操作寫入錯誤的錄像文件,文件名在websocket建立時初始化

    def __init__(self, host, user, websocket):
        self.host = host
        self.user = user
    
        self.time = time.time()
        self.filename = '%s.%s.%d.cast' % (host, user, self.time)

    IO流數據會持續不斷的寫入文件,這裏以一個獨立的方法來處理寫入

    def record(self, type, data):
        RECORD_DIR = settings.BASE_DIR + '/static/record/'
        if not os.path.isdir(RECORD_DIR):
            os.makedirs(RECORD_DIR)
    
        if type == 'header':
            Record.objects.create(
                host=Host.objects.get(id=self.host),
                user=self.user,
                filename=self.filename
            )
    
            with open(RECORD_DIR + self.filename, 'w') as f:
                f.write(json.dumps(data) + '\n')
        else:
            iodata = [time.time() - self.time, 'o', data]
            with open(RECORD_DIR + self.filename, 'a', buffering=1) as f:
                f.write((json.dumps(iodata) + '\n'))

    record接收兩個參數type和data,type標識本次寫入的是header頭還是IO流,data則是具體的數據

    header只需要執行一次寫入,所以將其放在ssh的connect方法中,只在ssh連接建立時執行一次,在執行header寫入時同時往數據庫插入新的歷史記錄數據

    調用record方法寫入header

    def connect(self, host, port, username, authtype, password=None, pkey=None,
                term='xterm-256color', cols=80, rows=24):
        ...
    
        # 構建錄像文件header
        self.record('header', {
            "version": 2,
            "width": cols,
            "height": rows,
            "timestamp": self.time,
            "env": {
                "SHELL": "/bin/bash",
                "TERM": term
            },
            "title": "ops-coffee"
        })

    IO流數據則需要與返回給前端的數據保持一致,這樣就能保證前端显示什麼錄像就播放什麼了,所以所有需要返回前端數據的地方都同時寫入錄像文件即可

    調用record方法寫入io流數據

    def connect(self, host, port, username, authtype, password=None, pkey=None,
                term='xterm-256color', cols=80, rows=24):
        ...
    
        # 連接建立一次,之後交互數據不會再進入該方法
        for i in range(2):
            recv = self.ssh_channel.recv(65535).decode('utf-8', 'ignore')
            message = json.dumps({'flag': 'success', 'message': recv})
            self.websocket.send(message)
    
            self.record('iodata', recv)
    
    ...
    
    def _ssh_to_ws(self):
        try:
            with self.lock:
                while not self.ssh_channel.exit_status_ready():
                    data = self.ssh_channel.recv(1024).decode('utf-8', 'ignore')
                    if len(data) != 0:
                        message = {'flag': 'success', 'message': data}
                        self.websocket.send(json.dumps(message))
    
                        self.record('iodata', data)
                    else:
                        break
        except Exception as e:
            message = {'flag': 'error', 'message': str(e)}
            self.websocket.send(json.dumps(message))
            self.record('iodata', str(e))
            
            self.close()

    由於命令執行與返回都是多線程的操作,這就會導致在寫入文件時出現文件亂序影響播放的問題,典型的操作有vim、top等,通過加鎖self.lock可以順利解決

    最後歷史記錄頁面,當用戶點擊播放按鈕時,調用js彈出播放窗口

    <div class="modal fade" id="modalForm">
      <div class="modal-dialog modal-lg">
        <div class="modal-content">
          <div class="modal-body" id="play">
          </div>
        </div>
      </div>
    </div>
    
    // 播放錄像
    function play(host,user,time,file) {
      $('#play').html(
        '<asciinema-player id="play" title="WebSSH Record" author="ops-coffee.cn" author-url="https://ops-coffee.cn" author-img-url="/static/img/logo.png" src="/static/record/'+file+'" speed="3" '+
        'idle-time-limit="2" poster="data:text/plain,\x1b[1;32m'+time+
        '\x1b[1;0m用戶\x1b[1;32m'+user+
        '\x1b[1;0m連接主機\x1b[1;32m'+host+
        '\x1b[1;0m的錄像記錄"></asciinema-player>'
      )
    
      $('#modalForm').modal('show');
    }

    asciinema-player標籤的詳細參數介紹可以看這篇文章

    演示與總結

    在寫入文件的方案中,考慮了實時寫入和一次性寫入,實時寫入就像上邊這樣,所有的操作都會實時寫入錄像文件,好處是錄像不丟失,且能在操作的過程中進行實時的播放,缺點也很明顯,就是會頻繁的寫文件,造成IO開銷

    一次性寫入可以在用戶操作的過程中將錄像數據寫入內存,在websocket關閉時一次性異步寫入到文件中,這種方案在最終寫入文件時可能因為種種原因而失敗,從而導致錄像丟失,還有個缺點是當你WebSSH操作時間過長時,會導致內存的持續增加

    兩種方案一種是對磁盤的消耗另一種是對內存的消耗,各有利弊,當然你也可以考慮批量寫入,例如每分鐘寫一次文件,一分鐘之內的保存在內存中,平衡內存和磁盤的消耗,期待你的實現

    相關文章推薦閱讀:

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

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

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

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

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

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

  • 電動車電池成長,帶動致茂營收倍翻

    受惠於電動車動力電池需求增加,致茂電子的七月合併營收創下歷史新高。今年前七個月的累計營收更已與去年全年相當。

    致茂電子表示,因電動車動力電池製造的關鍵技術(turnkey solutions)銷售蓬勃,帶動七月營收大漲,合併營收達新台幣16.2億元,不僅較上月成長71%、更較去年七月成長101%,營收數字創下歷史新高。

    此外,母公司的7月單月營收也有128%的月增與172%的年增,達新台幣12.6億元。強勁的需求使致茂今年前七個月的合併營收年增27%來到69.1億新台幣;母公司前七個月的累計營收新台幣45億元,也已相當於去年全年水準。

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

    【其他文章推薦】

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

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

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

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

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

  • 計算機圖形學—— 隱藏線和隱藏面的消除(消隱算法)

    計算機圖形學—— 隱藏線和隱藏面的消除(消隱算法)

     

    一、概述

    由於投影變換失去了深度信息,往往導致圖形的二義性。要消除二義性,就必須在繪製時消除被遮擋的不可見的線或面,習慣上稱作消除隱藏線和隱藏面(或可見線判定、可見面判定),或簡稱為消隱。經過消隱得到的投影圖稱為物體的真實感圖形。

    下面這個圖就很好體現了這種二義性。

    消隱后的效果圖:

    消隱算法的分類

    所有隱藏面消隱算法必須確定:
    在沿透視投影的投影中心或沿平行投影的投影方向看過去哪些邊或面是可見的

    兩種基本算法

    1、以構成圖像的每一個像素為處理單元,對場景中的所有表面,確定相對於觀察點是可見的表面,用該表面的顏色填充該像素.
    適於面消隱。

    算法步驟:

    a.在和投影點到像素連線相交的表面中,找到離觀察點最近的表面;
    b.用該表面上交點處的顏色填充該像素;

    2、以三維場景中的物體對象為處理單元,在所有對象之間進行比較,除去完全不可見的物體和物體上不可見的部分.適於面消隱也適於線消隱。

    算法步驟:
    a.判定場景中的所有可見表面;
    b.用可見表面的顏色填充相應的像素以構成圖形;

    提醒注意

    1.假定構成物體的面不能相互貫穿,也不能有循環遮擋的情況。
    2.假定投影平面是oxy平面,投影方向為z軸的負方向。

    如果構成物體的面不滿足該假定,可以把它們剖分成互不貫穿和不循環遮擋的情況。
    例如,用圖b中的虛線便可把原來循環遮擋的三個平面,分割成不存在循環遮擋的四個面。  

    二、可見面判斷的有效技術

    1、邊界盒

    指能夠包含該物體的一個幾何形狀(如矩形/圓/長方體等),該形狀有較簡單的邊界。

     

    邊界盒技術用於判斷兩條直線是否相交。

    進一步簡化判斷

    2、後向面消除(Back-face Removal)

    思路:把顯然不可見的面去掉,減少消隱過程中的直線求交數目

     

     

    如何判斷:根據定義尋找外(或內)法向,若外法向背離觀察者,或內法向指向觀察者,則該面為後向面。

     

     

     

     

     

     

     

     

     

     

    注意:如果多邊形是凸的,則可只取一個三角形計算有向面積sp。如果多邊形不是凸的,只取一個三角形計算有向面積sp可能會出現錯誤,即F所在的面為前向面也可能出現sp≥0的情況,因此,需按上式計算多邊形F的有向面積。如果sp ≥0,則F所在的面為後向面。

    3、非垂直投影轉化成垂直投影

    物體之間的遮擋關係與投影中心和投影方向有着密切的關係,因此,對物體的可見性判定也和投影方式有密切的關係。

    垂直投影的優點:進行投影時可以忽略z值,即:實物的(x,y)可直接做為投影后的二維平面上的坐標(x,y)

    上述討論說明,垂直投影比非垂直投影容易實現,並且計算量小。因此在進行消隱工作之前,首先應將非垂直投影轉換成垂直投影,從而降低算法的複雜性,提高運算速度。

    如何把透視投影變為垂直投影,其本質是把稜台變成長方體。

    三、基於窗口的子分算法(Warnack算法)

    是一種分而治之(Divide-Conquer)的算法。

     

    1、關係判斷

    2、可見性判斷

    3、分隔結束條件

    4、提高效率的有效的處理技術

    5、算法描述

    用多邊形的邊界對區域作劃分,其目的是盡量減少對區域劃分的次數--利用裁剪算法

     

    四、八叉樹算法

    為了生成真實感圖形,關鍵問題之一就是對圖像空間的每一個像素進行處理。從場景中所有的在該像素上有投影的表面中確定相對於觀察點是可見表面。為了提高算法效率,自然是希望從可能在像素上有投影的面片中尋找可見表面。八叉樹算法是快速尋找可見面的一種有效方法,是一種搜索算法。

    基本思想:將能夠包含整個場景的立方體,即八叉樹的根結點,按照x,y,z三個方向中的剖面分割成八個子立方體,稱為根結點的八個子結點。對每一個子立方體,如果它包含的表面片少於一個給定的值,則該子立方體為八叉樹的終端結點,否則為非終端結點並將其進一步分割成八個子立方體;重複上述過程,直到每個小立方體所包含的表面片少於一個給定的值,分割即告終止。

     

     

    那麼對於上述圖所示,視圖平面法向量(1,1,1)那麼此時它的一個排列是0,1,2,4,3,5,6,7,即最遠的八分體是0,與八分體0共享一個面的三個相鄰八分體是1,2和4,與最近八分體7的3個相鄰八分體是3,5和6。

     

    五、Z緩衝器算法

    1、算法描述

    z緩衝器算法是最簡單的隱藏面消除算法之一。
    基本思想:對屏幕上每一個像素點,過像素中心做一條投影線,找到此投影線與所有多邊形交點中離觀察者最近的點,此點的屬性(顏色或灰度)值即為這一屏幕像素點的屬性值。

    需要兩個緩衝器數組,即:z緩衝器數組和幀緩衝器數組,分別設為 Zdepth[ ][ ] 與  Frame[ ][ ]
    z緩衝器是一組存貯單元,其單元個數和屏幕上像素的個數相同,也和幀緩衝器的單元個數相同,它們之間一一對應。
    幀緩衝器每個單元存放對應像素的顏色值;z緩衝器每個單元存放對應像素的深度值;

    2、算法實現

     

    算法的複雜性正比於m*n*N,在屏幕大小即m*n一定的情況下,算法的計算量只和多邊形個數N成正比

    3、優缺點

     z-Buffer算法沒有利用圖形的相關性和連續性,這是z-Buffer算法的嚴重缺陷,更為嚴重的是,該算法是像素級上的消隱算法。

     六、掃描線z緩衝器算法

    1、算法描述

    將z緩衝器的單元數置為和一條掃描線上的像素數目相同。
    從最上面的一條掃描線開始工作,向下對每一條掃描線作如下處理:

     

    掃描線算法也屬於圖像空間消隱算法。該算法可以看作是多邊形區域填充里介紹過的邊相關掃描線填充算法的延伸。不同的是在消隱算法中處理的是多個面片,而多邊形填充中是對單個多邊形面進行填充。

    2、數據結構

    對每個多邊形,檢查它在oxy平面上的投影和當前掃描線是否相交?
    若不相交,則不考慮該多邊形。
    如果相交,則掃描線和多邊形邊界的交點是成對地出現
    每對交點中間的像素計算多邊形所在平面對應點的深度(即z值),並和z緩衝器中相應單元存放的深度值作比較
    若前者大於後者,則z緩衝器的相應單元內容要被求得的平面深度代替,幀緩衝器相應單元的內容也要換成該平面的屬性。
    對所有的多邊形都作上述處理后,幀緩衝器中這一行的值便反應了消隱后的圖形。
    對幀緩衝器每一行的單元都填上相應內容后就得到了整個消隱后的圖。

    每處理一條掃描線,都要檢查各多邊形是否和該線相交,還要計算多邊形所在平面上很多點的z值,需要花費很大的計算
    為了提高算法效率,採用跟多邊形掃描轉換中的掃描線算法類似的數據結構和算法.

    多邊形Y表

     

    實際上是一個指針數組 ,每個表的深度和显示屏幕行數相同.將所有多邊形存在多邊形Y表中,根據多邊形頂點中Y坐標最大值,插入多邊形Y表中的相應位置,多邊形Y表中保存多邊形的序號和其頂點的最大y坐標.

    邊Y表

     要注意:Δx是下一條掃描線與邊交點的x減去當前的掃描線與邊交點的x。

    多邊形活化表

    邊對活化表

    其實這裏最難理解的就是Δyl和Δxr了,這裏的意思就是當前掃描線所處的y值和與該掃描線相交邊的最小y值的差值。

    就比如說掃描線y=6,與第一個三角形有兩個交點,左交點(4,6),右交點(7,6)那麼Δyl=6-3  Δyr=6-3

    3、重溫算法目標

     對每一條掃描線,檢查對每個多邊形的投影是否相交,如相交則交點成對出現,對每對交點中間的每個像素計算多邊形所在平面對應點的深度(即z值),並和z緩衝器中相應單元存放的深度值作比較,若前者大於後者,則z緩衝器的相應單元內容要被求得的平面深度代替,幀緩衝器相應單元的內容也要換成該平面的屬性。
    對所有的多邊形都作上述處理后,幀緩衝器中這一行的值便反應了消隱后的圖形,對幀緩衝器每一行的單元都填上相應內容后也就得到了整個消隱后的圖。

    4、算法步驟

     

    算法描述如下

    七、優先級排序表算法

    1、算法思想

    優先級排序表算法按多邊形離觀察者的遠近來建立一個多邊形排序表,距觀察者遠的優先級低,放在表頭;近的優先級高,放在表尾
    從優先級低的多邊形開始,依次把多邊形的顏色填入幀緩衝存儲器中
    表中距觀察者近的元素覆蓋幀緩衝存儲器中原有的內容
    當優先級最高的多邊形的圖形送入幀緩衝器后,整幅圖形就形成了
    類似於油畫家繪畫過程,因此又稱為油畫家算法。

     

     

     

     

     

     

    2、算法的優缺點

    算法的優點:
    簡單,容易實現,並且可以作為實現更複雜算法的基礎;
    缺點:
    只能處理不相交的面,而且深度優先級表中面的順序可能出錯.

    該算法不能處理某些特殊情況。

     

     

    解決辦法:把P沿Q平面一分為二,從多邊形序列中把原多邊形P去掉,把分割P生成的兩個多邊形加入鏈表中。具體實現時,當離視點最遠的多邊形P和其他多邊形交換時,要對P做一標誌,當有標誌的多邊形再換成離視點最遠的多邊形時,則說明出現了上述的現象,可用分割方法進行處理 。

    用來解決動態显示問題時,可大大提高效率

    八、光線投射算法

    1、算法原理

    要處理的場景中有無限多條光線,從採樣的角度講我們僅對穿過像素的光線感興趣,因此,可考慮從像素出發,逆向追蹤射入場景的光線路徑

     

     

     

     

     

    2、算法實現

    由視點出發穿過觀察平面上一像素向場景發射一條射線
    求出射線與場景中各物體表面的交點
    離視點最近的交點的顏色即為像素要填的顏色。
    光線投射算法對於包含曲面,特別是包含球面的場景有很高的效率。

     

     

     

     

     

     

     

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

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

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

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

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

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