學習下ElasticSearch

ElasticSearch基礎概念

使用場景:比如分庫的情況下,你想統計所有數據的報表,就把所有數據都放在ElasticSearch上

關係型數據庫 ElasticSearch
數據庫Database 索引index,支持全文檢索
表Table 類型Type
數據行Row 文檔Document
數據列Column 字段Field
模式Schema 映射Mapping

用關係型數據庫就會想到建立一張User表,再建字段等,

而在Elasticsearch的文件存儲,Elasticsearch是面向文檔型數據庫,一條數據在這裏就是一個文檔,用JSON作為文檔序列化的格式

在ES6.0之後,已經不允許在一個index下建不同的Type了,一個index下只有一個Type(以後版本中Type概念會去掉,可以直接把index類比成Table)

節點Node:

  一個ElasticSearch運行的實列,集群構成的單元

集群Cluster:

  由一個或多個節點組成,對外提供服務  

Elasticsearch實現原理-倒排索引

ElasticSearch是基於倒排索引實現的

倒排索引(Inverted Index)也叫反向索引,有反向索引必有正向索引。

通俗地來講,正向索引是通過key找value,反向索引則是通過value找key。

倒排索引—單詞詞典

單詞詞典(Term Dictionary)是倒排索引的重要組成部分。

——記錄所有文檔的單詞,一般都比較大

——記錄單詞到倒排列表的關聯信息(這個單詞關聯了哪些文檔)

倒排索引—排序列表

倒排列表(Posting List)記錄了單詞對應文檔的集合,由倒排索引項(Posting)組成

倒排索引項(Posting)主要包含如下信息

—文檔Id,用於獲取原始信息

—單詞頻率(TF,Term Frequency),記錄該單詞在文檔中出現的次數,用於後序相關算分

—位置(Position),記錄單詞在文檔中的分詞位置,用於做詞語搜索(Phrase Query)

—偏移(Offset),記錄單詞在文檔的開始和結束位置,用於高亮显示

分詞

搜索引擎的核心是倒排索引,而倒排索引的基礎就是分詞。所謂分詞可以簡單理解為將一個完整的句子切割為一個個單詞的過程。也可以叫文本分析,在es稱為Analysis。

如文本:elasticSearch是最流行的搜索引擎

分詞結果:elasticSearch 流行 搜索引擎

分詞器是es中專門處理分詞的組件,英文為Analyzer,它的組成如下

Character Filters:針對原始文本特殊處理,比如除html特殊符

Tokenizer:將原始文本按照一定規則切分為單詞

TokenFilters:針對tokenizer處理的單詞就行在加工,比如轉小寫,刪除或新增處理(比如中文中的  這 呢 無實意的詞)

Analyze API

es提供了一個測試分詞的API接口,方便驗證分詞效果,endpoint是_analyze

—可以直接指定analyze測試

—可以直接指定索引中的字段進行測試

—可以自定義分詞器進行測試

文檔映射Mapping

Mapping類似數據庫中的表結構定義,主要作用如下:

—定義Index下的字段名(Field Name)

—定義字段的類型,比如數值型、字符串型、布爾型等

—定義倒排索引相關的配置,比如是否索引、記錄position等

Dynamic Mapping

es可以自動識別文檔字段類型,從而降低用戶使用成本

SearchAPI介紹和相關性算分

es中存儲的數據進行查詢分析,endpoint為_search

查詢主要有兩種形式

1)URI Search

操作簡單,方便通過命令進行測試

但 僅包含部分查詢語法

GET /indexname/_search?q=user:xx

2)Request Body Search

es提供的完備查詢語法Query DSL(Domain Specific Language)

GET /indexname/_search

{
  ”query”: {
    ”term”: {
      ”user”: “xx”
    }
  }
}

相關算分

相關算分是指文檔與查詢語句直接的相關度,英文為relevance

  通過倒排索引可以獲取與查詢語句相匹配的文檔列表,那麼如何將最符合用戶查詢的文檔放到前列呢

  本質是一個排序問題,排序的依據是相關算分

ES目前主要有兩個相關性算分模型

  TF/IDF模型

  BM25模型 5.x之後的默認模型

BM25相比TF/IDF的一大優化是降低了TF(Term Frequency單詞頻率)在過大時的權重

相關算分是shard與shard間是相互獨立的,也就意味着一個Term的IDF等值在不同shard上是不同的。文檔的相關算分和它所處的shard有關

在文檔數量不多時 會導致相關算分嚴重不準的情況發生

解決辦法

  —設置分片數是一個,從根本排除問題,在文檔數據量不多時可以考慮該方法,(百萬到千萬)

  —二是使用DFS Query Then Ftech查詢方式

Elasticsearch分佈式特性

es支持集群模式,是一個分佈式系統,好處是

—1)增加系統容量:內存、磁盤,使es集群可以支持PB級的數據

如何將數據分佈在所有節點上

  —引入分片 Shard解決問題

分片是ES支持PB級數據的基石

  —分片存儲了部分數據,可以分部在任意節點上

  —分片數在索引創建時指定且後序不允許再更改(即使你後面新增了也用不到),默認5個

  —分片有主分片和副本分片之分,以實現數據的高可用

es集群由多個es實列組成

  —不同集群通過集群名字來區分,可通過cluster.name修改,默認為elasticSearch

  —每個ES實列本質是一個JVM進程,且有自己的名字,通過node.name修改

Master Node:Master節點通過集群中所有的節點選舉產生,可以被選舉的節點稱為master-eligible節點,

      相關配置如下:node.master:true

Coordinating Node:處理請求的節點為Coordinating節點,該節點為所有節點默認角色,不能取消

            作用是把請求路由到正確的節點處理,比如創建索引請求到master節點

Data Node:存儲數據的節點即為data節點,默認節點都是data類型,相關配置如下:node.data.true

—2)提供系統可用性:即部分節點停止服務,整個集群依然可以正常服務

提高系統可用性

服務可用性

  —兩個節點情況下,允許其中一個節點停止服務  

數據可用性

  —引入副本(Replication)解決

  —每個節點上都有完備的數據

複製分片的意義在於容錯性,當一個節點掛了,另一個節點上的分片可以代替掛掉節點上的分片

故障轉移

一:

 二:

 三:

文檔到分片的映射算法

es通過如下公式計算文檔到對應的分片 -shard=hash(routing)%number_of_primary_shards

hash算法保證可以將數據均勻的分散在分片中

routing是一個關鍵參數,默認是文檔id,也可以自行指定

number_of_primary_shards是主片分數(該算法與主片分數相關,這也是分片數量一旦確定就不能修改的原因)

腦裂問題

在上述第一步的時候 node2和node3選舉node2為master節點了時候,此時會更新cluster state

此時node1節點網絡恢復了,node1自己組成集群后,也會更新cluster state

此時:同一個集群有兩個master,而且維護不同的cluster state,網絡恢復后 無法選擇正確的master

解決方案:僅在可選舉master-eligible節點數大於等於quorum時才可以進行master選舉

        即使node1節點恢復了 ,可選節點數未達到quorum,不選舉

 

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

【其他文章推薦】

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

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

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

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

卜時明餐|記一次項目開發經歷

卜時明餐:校園餐廳人流量實時監測系統

做了一兩周,這個項目應該要告一段落了
這幾周每天肝到深夜,也挺開心的啊,和小夥伴完美配合,做出成品后還是很有成就感。
把這學期學的數據庫啊、java、計算機網絡都實際用到了
簡單記錄一下這次項目經歷。

大概是這樣子的

P的視頻

編造的背景

校園餐廳 中午、晚餐吃飯學生驚人的多!
每次排隊時間超長煩惱至極,心情不愉。。
校園兩餐廳距離遙遠,美食遙不可及,無法實時觀測人數
排隊浪費零碎時間

虛構的功能

幫助學生和在校教師合理規劃用餐時間,避開人流高峰。
(校園餐廳各個樓層人流實時監測,實時刷新每個窗口人數,實時通過小程序查看每個窗口的用餐情況)

花里胡哨的技術棧

深度學習:Tensorflow + yolov3
後端:java、SpringBoot
前端:微信小程序
數據庫:Mysql
服務器:ubuntu

前端

用戶端:微信小程序
展示餐廳、熱門窗口實時人數,菜譜推薦。
[]

深度學習算法

Tensorflow + yolov3物體識別
小夥伴開發的
二餐二樓自助餐窗口人流識別

數據庫

第一次真正意義上玩linux系統,部署了mysql數據庫

還設計了很正經的數據庫ER圖,真的把這學期學到的知識用上了。。

後端

最後就是後端了,用java寫的(也是這學期學的呀。。)
用了SpringBoot框架 + JDBC操作數據庫;
其實和現在學的Tomcat+sevlert+JDBC一樣的操作。

架構圖

怎麼把前端、後端、深度學習物體識別、數據庫連接起來呢?
首先是,攝像頭記錄餐廳實時視頻狀況,傳給yolov3物體識別服務器(小夥伴的筆記本),模型識別出實時人數,(通過TCP/IP協議)連接阿里雲服務器部署的數據庫,寫入實時數據
然後是用戶端,用戶玩手機打開微信小程序,會發起Http請求給後端,後端拿到並解析Http數據報后,向服務器上的數據庫獲取實時人數數據,返回給微信小程序,微信小程序渲染數據就可以了。

差不多就這樣

最後放上倆小夥伴丑照,和我帥氣的壁紙屏保,留念。

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

【其他文章推薦】

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

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

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

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

深入理解java多態沒有烤山藥的存在,java就不香了嗎?

目錄

@

我不想知道各位理解java多態沒有烤山藥的存在,java香不香的問題了,我不要你們認為,我只要我覺得 (感覺要被打….)

在博主認為多態絕對是面向對象的第三大特性中讓很多小白同學以及初學者難以跨越的鴻溝,因為多態有很多細節性的知識,不花點時間,還真不好理解多態。這麼說吧,如果你覺得你已經完全理解了多態,你不妨做做下面的程序,如果你能全都答對,那沒問題了,多態對你來說真的不是問題!如果在第四個就趴下了,那可以看看這篇文章,或許對你有所幫助,可能會讓你重新見識到多態的魅力。

package Polymorphic;
//爺爺類
class Ye {
    public String show(Sun obj) {
        return ("Ye and Sun");
    }

    public String show(Ye obj) {
        return ("Ye and Ye");
    }

}
//爸爸類
class Fu extends Ye {
    public String show(Fu obj) {
        return ("Fu and Fu");
    }

    public String show(Ye obj) {
        return ("Fu and Ye");
    }
}
//兒子類
class Zi extends Fu {

}
//孫子類
class Sun extends Fu {

}

public class PolymorphicTest {
    public static void main(String[] args) {
         Ye y = new Ye();
        Ye y2 = new Fu(); //向上
        Fu f = new Fu();
        Zi z = new Zi();
        Sun s = new Sun();


        System.out.println("第一題 " + y.show(f));
        System.out.println("第二題 " + y.show(z));
        System.out.println("第三題 " + y.show(s));
        System.out.println("第四題 " + y2.show(f));  //到這裏掛了???
        System.out.println("第五題 " + y2.show(z));
        System.out.println("第六題 " + y2.show(s));
        System.out.println("第七題 " + f.show(f));
        System.out.println("第八題 " + f.show(z));
        System.out.println("第九題 " + f.show(s));
     
    }
}

先把答案記在小本本上吧,再對照下面結果看看

第一題 Ye and Ye
第二題 Ye and Ye
第三題 Ye and Sun
第四題 Fu and Ye
第五題 Fu and Ye
第六題 Ye and Sun
第七題 Fu and Fu
第八題 Fu and Fu
第九題 Ye and Sun

如果你對上面的結果很意外,或者不解,那麼恭喜你,你又能學到新知識了,成功的向架構師前進了一步!好了,讓我們一起重新見識見識多態的魅力吧!

1、 從吃烤山藥重新認識多態

最近不是正火着吃烤山藥么,學習就要走有趣化路線,畢竟興趣永遠最好的老師,咋們放開點,怎麼有趣怎麼來。

小明媽媽的情緒非常不穩定,心情好的時候巴不得給小明花一個億,,心情不好的時候巴不得把小明打成麻瓜,可是小明永遠不知道媽媽的情緒變化。這不,今天一位老大爺在賣烤山藥,邊烤還邊跳激光雨,嗨得不行,小明特別喜歡激光雨,馬上就忍不住了,心裏默默想着,剛烤的山藥它不香嘛,激光雨烤的山藥它不香嘛。於是忍不住對媽媽說:“媽媽,我想吃烤山藥”,這個時候,來了,來了,他來了,它真的來了….你激動個鎚子啊……是代碼來了:

package Polymorphic;


     class  Matcher{
        public void matcherSpeak(){
            System.out.println("想吃烤山藥?");
        }
    }

     class HappyMother extends Matcher {
        public void matcherSpeak(){
            System.out.println("開心的媽媽說:吃,吃大塊的,一火車夠嗎");
        }
    }

     class SadMother extends Matcher {
        public void matcherSpeak(){
            System.out.println("不開心的媽媽說:吃你個憨皮,看我回家扎不扎你就完事了");
        }
    }

     class VeryHappyMother extends Matcher {
        public void matcherSpeak(){
            System.out.println("異常開心的媽媽說:買買買,烤山藥咱全買了,順便把大爺也買回家,天天給你表演激光雨(大爺懵逼中)");
        }
    }

    public class UnderstandPolymorphic{
        public static void main(String[] args) {
            Matcher m = new HappyMother();
            m.matcherSpeak();

            m = new SadMother();
            m.matcherSpeak();

            m = new VeryHappyMother();
            m.matcherSpeak();

        }
    }
運行結果:

開心的媽媽說:吃,吃大塊的,一火車夠嗎
不開心的媽媽說:吃你個憨皮,看我回家扎不扎你就完事了
異常開心的媽媽說:買買買,烤山藥咱全買了,順便把大爺也買回家,天天給你表演激光雨(大爺懵逼中)

媽媽聽到小明想吃烤山藥這同一行為,表現出不同的表現形式,這就是多態。多態專業定義則是:程序中定義的引用變量所指向的具體類型和通過該引用變量發出的方法調用在編程時並不確定,而是在程序運行期間才確定,這種情況叫做多態沒錯是沒錯就是腦殼有點大,所以我選擇簡單點定義多態: 多態指同一行為,具有多個不同表現形式。為何會有如此微妙的變化呢,那我們就必須了解進行多態的前提了。

2、 多態前提條件【重點】

如果多態不能滿足以下三個前提條件,那還玩犢子的多態【構不成多態,缺一不可】

  1. 繼承或者實現【二選一】
  2. 方法的重寫【意義體現:不重寫,無意義】
    子類對父類中某些方法進行重新定義,在調用這些方法時就會調用子類的方法。
  3. 父類引用指向子類對象(也可以說向上轉型)【體現在格式上】

回過頭來看烤山藥例子,確實都有繼承,同樣都重寫了motherSpeak()方法,最關鍵的代碼則是

 Matcher m = new HappyMother();

也就是所謂的 父類引用指向子類對象,這其實就是向上轉型!對向上轉型概念不清晰沒事,下面會詳細講解。

3、 多態的體現

多態體現的格式: 父類/父接口類型 變量名 = new 子類對象變量名.方法名();

當使用多態方式調用方法時,首先檢查父類中是否有該方法,如果沒有,則編譯錯誤 ,如果有,執行的是子類重寫后的方法,也就是向上轉型時, 子類單獨定義的方法丟失問題。編譯報錯。 代碼如下:

package Demo;

class  Matcher{
    public void matcherSpeak(){//=========================父類matcherSpeak()方法
        System.out.println("吃烤山藥?");
    }
}

class HappyMother extends Matcher {
    public void matcherSpeak(){//=========================子類matcherSpeak()方法
        System.out.println("開心的媽媽說:吃,吃大塊的,一蛇皮袋夠嗎");
    }

    public void fatherSpeak(){//=========================子類獨有的fatherSpeak()方法
        System.out.println("開心的媽媽說:吃,吃大塊的,一麻袋夠嗎");
    }
}
public class Test {
    public static void main(String[] args) {
        Matcher m=new HappyMother();
        m.matcherSpeak();
        m.fatherSpeak();  //編譯失敗,無法解析fatherSpeak方法
    }
}

分析如下:

當然這個例子只是入門級的,接下來看個有點水平的例子

package Demo;

class  Matcher{
    public void matcherSpeak(){
        System.out.println("想吃烤山藥?");
    }

}

class HappyMother extends Matcher {
    public void matcherSpeak(){
        System.out.println("開心的媽媽說:吃,吃大塊的,一火車夠嗎");
    }
}
class SadMother extends HappyMother{
    public void tt(){
        System.out.println("ttttttt");
    }
}
public class Test {
    public static void main(String[] args) {
        Matcher mm=new SadMother();
        mm.matcherSpeak();
    }

運行結果:開心的媽媽說:吃,吃大塊的,一火車夠嗎
}

有了第一個基礎這個相信不難理解,接着看

package Demo;

class  Matcher{
    public void matcherSpeak(){
        System.out.println("想吃烤山藥?");
    }
}

class HappyMother extends Matcher {
    
}
class SadMother extends HappyMother{
    public void tt(){
        System.out.println("ttttttt");
    }
}
public class Test {
    public static void main(String[] args) {
        Matcher mm=new SadMother();
        mm.matcherSpeak();
    }
    
運行結果:想吃烤山藥?

}

到這裏,再來回味下這句話:

當使用多態方式調用方法時,首先檢查父類中是否有該方法,如果沒有,則編譯錯誤 ,如果有,執行的是子類重寫后的方法

你可能會說子類中都沒有這些個方法啊,何來執行子類重寫后的方法一說?它好像是去父類中找該方法了。事實上,子類中是有這些方法的,這個方法繼承自父類,只不過沒有覆蓋該方法,所以沒有在子類中明確寫出來而已,看起來像是調用了父類中的方法,實際上調用的還是子類中的。同學繼承方面的知識該補補了,可以參考下面這篇

4、 多態動態綁定與靜態綁定

講之前博主先來談談“綁定”的概念:
綁定指的是一個方法的調用與方法所在的類(方法主體)關聯起來,大致可以理解為一個方法調用另一個方法。對java來說,綁定分為靜態綁定和動態綁定;或者分別叫做前期綁定和後期綁定。

4、1.靜態綁定(前期綁定)

在程序執行前方法已經被綁定,==針對java靜態綁定簡單的可以理解為程序編譯期的綁定==;java當中的方法只有finalstaticprivate(不會被繼承)構造方法是前期綁定【當然可能不止】

4、2.動態綁定(後期綁定)

後期綁定:在運行時根據具體對象的類型進行綁定。
若一種語言實現了後期綁定,同時必須提供一些機制,可在運行期間判斷對象的類型,並分別調用適當的方法。也就是說,編譯器此時依然不知道對象的類型,但方法調用機制能自己去調查,找到正確的方法主體。不同的語言對後期綁定的實現方法是有所區別的。但我們至少可以這樣認為:它們都要在對象中安插某些特殊類型的信息。==簡明的說動態綁定就是指編譯器在編譯階段不知道要調用哪個方法,運行期才能確定==

4、3.靜態、動態綁定本質區別

1、靜態綁定是發生在編譯階段;而動態綁定是在運行階段;
2、靜態綁定使用的是類信息,而動態綁定使用的是對象信息
3、重載方法(overloaded methods)使用的是靜態綁定,而重寫方法(overridden methods)使用的是動態綁定

4、4.靜態、動態綁定在程序中運行區別

這個靜態綁定例子以static方法為例,代碼程序如下:

package demoee;

class Father5{
    public void StaticMethod(){
        System.out.println("粑粑:我是父類粑粑靜態方法");
    }
}
class Son5 extends Father5{
    public void StaticMethod(){
        System.out.println("熊孩子:我是子類熊孩砸靜態方法");
    }
}
public class demooo {
    public static void main(String[] args) {
        Father5 fat=new Father5();
        Father5 son=new Son5(); //特別注意這裡是向上轉型  也就是多態!

        fat.StaticMethod();//同時調用StaticMethod方法!
        son.StaticMethod();
    }
}

運行結果

粑粑:我是父類粑粑靜態方法
熊孩子:我是子類熊孩砸靜態方法

根據上面的運行結果,我們也很好理解!子類重寫了父類的一個叫做StaticMethod()的方法,由於是動態綁定,因此最後執行的是子類重寫后的StaticMethod()方法。

嗯哼?為了更好的理解靜態、動態綁定在程序中運行區別,我們還是得看看下面這個程序:

class Father5{
    public static void StaticMethod(){
        System.out.println("粑粑:我是父類粑粑靜態方法");
    }
}
class Son5 extends Father5{
    public static void StaticMethod(){
        System.out.println("熊孩子:我是子類熊孩砸靜態方法");
    }
}
public class demooo {
    public static void main(String[] args) {
        Father5 fat=new Father5();
        Father5 son=new Son5(); //特別注意這裡是向上轉型  也就是多態!

        fat.StaticMethod();//同時調用StaticMethod方法!
        son.StaticMethod();
    }
}

千萬注意哦,這個程序與第一個程序唯一不同之處就在於這個程序父類和子類的方法都是static的!

運行結果:

粑粑:我是父類粑粑靜態方法
粑粑:我是父類粑粑靜態方法

從運行結果來看,我們可以很清楚的知道,子類靜態方法語法上是做到了重寫的作用,但實際上並沒有做到真正意義上重寫作用!只因為該方法是靜態綁定!

OK,get到了咩?如果get到了請點個讚唄,謝謝你~

5、 多態特性的虛方法(virtual)

虛方法出現在Java的多態特性中。

父類與子類之間的多態性,對父類的函數進行重新定義。如果在子類中定義某方法與其父類有相同的名稱和參數,我們說該方法被重寫 (Overriding)。在Java中,子類可繼承父類中的方法,而不需要重新編寫相同的方法。但有時子類並不想原封不動地繼承父類的方法,而是想作一定的修改,這就需要採用方法的重寫。方法重寫又稱方法覆蓋。

當設計類時,被重寫的方法的行為怎樣影響多態性。方法的重寫使得子類能夠重寫父類的方法。當子類對象調用重寫的方法時,調用的是子類的方法,而不是父類中被重寫的方法。

因此簡單明了的理解Java虛方法方式你可以理解為java里所有父類中被重寫的方法都是虛方法(virtual)差不多的意思就是該方法不會被子類使用到,使用到的都是子類中重寫父類的方法,子類中的重寫方法代替了它,因此也就有種名存實亡的感覺!

在JVM字節碼執行引擎中,方法調用會使用invokevirtual字節碼指令來調用所有的虛方法。

小白童鞋千萬需要注意虛方法和抽象方法並不是同一個概念!

# 6、 重載屬於多態嗎?

縱觀重載與重寫,重寫是多態的特徵體現無疑了!但是對於重載是不是多態的體現網上卻議論紛紛!

多態是基於對抽象方法的覆蓋來實現的,用統一的對外接口來完成不同的功能。重載也是用統一的對外接口來完成不同的功能。那麼兩者有什麼區別呢?

重載
重載是指允許存在多個同名方法,而這些方法的參數不同。重載的實現是:編譯器根據方法不同的參數表,對同名方法的名稱做修飾。對於編譯器而言,這些同名方法就成了不同的方法。它們的調用地址在編譯期就綁定了。

多態
多態是指子類重新定義父類的虛方法(virtual,abstract)。當子類重新定義了父類的虛方法后,父類根據賦給它的不同的子類,動態調用屬於子類的該方法,這樣的方法調用在編譯期間是無法確定的。
不難看出,兩者的區別在於編譯器何時去尋找所要調用的具體方法,對於重載而言,在方法調用之前,編譯器就已經確定了所要調用的方法,這稱為“早綁定”或“靜態綁定”;而對於多態,只有等到方法調用的那一刻,編譯器才會確定所要調用的具體方法,這稱為“晚綁定”或“動態綁定”。

所以,你可以大可認為重載不屬於多態,多態是對父類虛函數的重定義,不改變原虛函數的參數列表。重載是函數名相同,但參數列表不同。

實際上這種問題沒有嚴格的答案,就連教材書上都沒提及。嚴格來說或狹義來講,重載算多態還是有點牽強,傳統的多態就是指父類和子類關係,但實際開發中都是理解重載是多態。這就是一個概念 你子類擁有你很多隱式父類的功能 那麼你當然能扮演它們之中的某一個角色。

總的來說,在博主認為,重載是不是多態這個問題以及不重要了,首當其沖的重要任務我覺得還是好好保護頭髮,然後就是養生了….

7、 向上轉型

向上轉型:多態本身是子類類型向父類類型向上轉換的過程,其中,這個過程是默認的。你可以把這個過程理解為基本類型的小類型轉大類型自動轉換,不需要強制轉換。 當父類引用指向一個子類對象時,便是向上轉型。 向上轉型格式:

父類類型 變量名 = new 子類類型(); 如:Father f= new Son();

例子的話,烤山藥的例子就是一個典型的向上轉型例子

8、 向下轉型

向下轉型:父類類型向子類類型向下轉換的過程,這個過程是強制的。同樣可以把這個過程理解為基本類型的自動轉換,大類型轉小類型需要強制轉換。一個已經向上轉型的子類對象,將父類引用轉為子類引用,可以使用強制類型轉換的格式,向下轉使用格式:

Father father = new Son();
子類類型 變量名 = (子類類型) 父類變量名; 如:Son s =(Son) father;

不知道你們有沒有發現,向下轉型的前提是父類對象指向的是子類對象(也就是說,在向下轉型之前,它得先向上轉型),當然,向下轉型還是有它的意義所在,下面就講解向下轉型的意義。

到這裏,我們講解一下為什麼要向下轉型?上面已經講到過當使用多態方式調用方法時,首先檢查父類中是否有該方法,如果沒有,則編譯錯誤。也就是說,不能調用子類擁有,而父類沒有的方法。編譯都錯誤,更別說運行了。這也是多態給我們帶來的一點”小麻煩”。所以,想要調用子類特有的方法,必須做向下轉型

package Demo;

class  Matcher{
    public void eat(){
        System.out.println("想吃烤山藥?");
    }
}

class XiongHaiZi extends Matcher {
    public void eat(){
        System.out.println("媽媽,我想吃烤山藥");
    }

    public void eatSuLi(){//============================子類特有的eatSuLi方法
        System.out.println("麻麻,我想吃酥梨,要吃麻瓜那麼大的酥梨");
    }
}

public class Test {
    public static void main(String[] args) {
        
        Matcher m = new XiongHaiZi();//向上轉型
        
        XiongHaiZi x = (XiongHaiZi)m;//向下轉型
        
        x.eatSuLi();//執行子類特有方法

    }
    
    運行結果:麻麻,我想吃酥梨,要吃麻瓜那麼大的酥梨
}

好了向下轉型就講到這裏…等等,你真的以為就講完了?肯定不行嘍,向下轉型還有一個要說的知識,講之前先來看個程序先

package Demo;

class  Matcher{
    public void eat(){
        System.out.println("想吃烤山豬?");
    }

}

class Boy extends Matcher {
    public void eatKaoYang(){
        System.out.println("媽媽,我想吃烤山豬");
    }
}

class Girl extends Matcher {
    public void eatKaoYang(){
        System.out.println("媽媽,我想吃烤山豬2333");
    }
}

public class Test {
    public static void main(String[] args) {

        Matcher g = new Girl();//向上轉型編譯通過

        Boy x = (Boy)g;//向下轉型

        x.eatKaoYang();//編譯通過,但運行報ClassCastException

    }
    
 運行結果:  運行報ClassCastException

}

這段代碼可以通過編譯,但是運行時,卻報出了 ClassCastException ,類型轉換異常!這是因為,明明創建了Girl類型對象,運行時,當然不能轉換成Boy對象的。這兩個類型並沒有任何繼承關係,不符合類型轉換的定義。 為了避免ClassCastException的發生,Java提供了 instanceof 關鍵字,給引用變量做類型的校驗。

8、1. instanceof的使用

instanceof 的格式:
變量名 instanceof 數據類型

instanceof 的使用
如果變量屬於該數據類型,返回true。
如果變量不屬於該數據類型,返回false。

所以,轉換前,我們最好使用instanceof 先做一個判斷,代碼如下:

package Demo;

class  Matcher{
    public void eat(){
        System.out.println("想吃烤山藥?");
    }

}

class Boy extends Matcher {
    public void eatKaoYang(){
        System.out.println("Boy:媽媽,我想吃烤羊");
    }
}

class Girl extends Matcher {
    public void eatKaoYang(){
        System.out.println("Girl:媽媽,我想吃烤全羊2333");
    }
}

public class Test {
    public static void main(String[] args) {

        Matcher g = new Girl();//向上轉型

        if(g instanceof Girl){
            Girl x = (Girl)g;//向下轉型
            x.eatKaoYang();  //=====================調用Girl的eatKaoYang()方法
        }else if(g instanceof Boy){ //不執行
            Boy x = (Boy)g;//向下轉型
            x.eatKaoYang();  //=====================調用Boy的eatKaoYang()方法
        }
    }
}

運行結果: Girl:媽媽,我想吃烤全羊2333

好了到這裏,你get到了咩?

9、 向上向下轉型再次分析【加餐不加價】

看完之後是不是還是不夠清晰向上向下轉型?多態轉型問題其實並不複雜,只要記住一句話:父類引用指向子類對象。那什麼叫父類引用指向子類對象?看下面例子吧

有兩個類,Father 是父類,Son 類繼承自 Father

第 1 個例子:

//  f1 引用指向一個Son對象
Father f1 = new Son();   // 這就叫 upcasting (向上轉型)
// f1 還是指向 Son對象
Son s1 = (Son)f1;   // 這就叫 downcasting (向下轉型)

第 2 個例子:

// f1現在指向father對象
Father f2 = new Father();
Son s2 = (Son)f2;       // 出錯,子類引用不能指向父類對象

你或許會問,第1個例子中:Son s1 = (Son)f1; 為什麼是正確的呢。很簡單因為 f1 指向一個子類對象,Father f1 = new Son(); 子類 s1 引用當然可以指向子類對象了。

f2 被傳給了一個 Father 對象,Father f2 = new Father(); 子類 s2 引用不能指向父類對象。

10、 多態與構造器之間的微妙

直接上代碼:

package Polymorphic;

class EatKaoShanYao {
    EatKaoShanYao () {
        System.out.println("吃烤山藥之前...");
        eat();
        System.out.println("吃烤山藥之後(熊孩子懵逼中)....");
    }
    public void eat() {
        System.out.println("7歲半就喜歡吃烤山藥");
    }
}
public class KaoShanYao extends EatKaoShanYao {
    private String Weight = "110斤";
    public KaoShanYao(String Weight) {
        this.Weight = Weight;
        System.out.println("熊孩子的體重:" + this.Weight);
    }

    public void eat() { // 子類覆蓋父類方法
        System.out.println("熊孩子吃烤山藥之前的體重是:" + this.Weight);
    }

    //Main方法
    public static void main(String[] args) {
           EatKaoShanYaok = new KaoShanYao("250斤");
                      
    }
}

童鞋們可以試想一下運行結果,再看下面的輸出結果

 運行結果:
                吃烤山藥之前...
                熊孩子吃烤山藥之前的體重是:null
                吃烤山藥之後(熊孩子懵逼中)....
                熊孩子的體重:250斤

是不是很疑惑?結果為啥是這樣?你看,熊孩子又懵逼了,Why?

原因其實很簡單,因為在創建子類對象時,會先去調用父類的構造器,而父類構造器中又調用了被子類覆蓋的多態方法,由於父類並不清楚子類對象中的屬性值是什麼(先初始化父類的時候還沒開始初始化子類),於是把String類型的屬性暫時初始化為默認值null,然後再調用子類的構造器(這個時子類構造器已經初始Weight屬性,所以子類構造器知道熊孩子的體重Weight是250)。

如果有什麼不理解的可以及時告訴我,樓主一直都在,還有如果樓主哪裡寫錯了或者理解錯了,請及時告訴我,一定要告訴我!!!

11、 多態的優點

講了這麼久的多態,我覺得其優點已經不明覺厲了。但是還是來聊聊多態在實際開發的過程中的優點。在實際開發中父類類型作為方法形式參數,傳遞子類對象給方法,進行方法的調用,更能體現出多態的擴展 性與便利。
為了更好的對比出多態的優點,下面程序不使用多態,代碼如下:

package Demo;
//父類:動物類
class Animal{
    public void eat(){
        System.out.println("eat");
    }
}
//貓類
class Cat {
    //方法重寫
    public void eat(){
        System.out.println("貓吃貓骨頭");
    }
    public void call(){
        System.out.println("貓叫");
    }
}
//狗類
class Dog {
    public void eat(){
        System.out.println("狗吃狗骨頭");
    }
    public void call(){
        System.out.println("狗叫");
    }
}

//針對動物操作的工具類
class AnimalTool{

    private AnimalTool(){}//把工具類的構造方法私有,防止別人創建該類的對象。

    //調用貓的功能
    public static void catLife(Cat c){  //工具類,方法就寫成static的,然後直接在測試類:工具類名.方法 使用。
        c.eat();
        c.call();
    }
    //調用狗的功能
    public static void dogLife(Dog d){
        d.eat();
        d.call();
    }
}

public class Test{
    public static void main(String[] args){

        Cat c= new Cat();
        AnimalTool.catLife(c);

        Dog d= new Dog();
        AnimalTool.dogLife(d);

    }
}
運行結果:
        貓吃貓骨頭
        貓叫
        狗吃狗骨頭
        狗叫

這裏只寫了兩隻動物,如果再來一種動物豬,則需要定義個豬類,提供豬的兩個方法,再到工具類中添加對應的XXLife方法,這三步都是必須要做的,而且每多一種動物就需要在工具類中添加一種一個對應的XXLife方法,這樣維護起來就很麻煩了,畢竟動物種類成千上萬!崩潰吧,沒事多態來拯救你,如下使用多態代碼:

package Demo;
//父類:動物類
class Animal{
    public void eat(){
        System.out.println("eat");
    }
    public void call(){
        System.out.println("call");
    }
}
//貓類
class Cat extends Animal {
    //方法重寫
    public void eat(){
        System.out.println("貓吃貓骨頭");
    }
    public void call(){
        System.out.println("貓叫");
    }
}
//狗類
class Dog extends Animal {
    public void eat(){
        System.out.println("狗吃狗骨頭");
    }
    public void call(){
        System.out.println("狗叫");
    }
}

//針對動物操作的工具類
class AnimalTool{

    private AnimalTool(){}//最好把工具類的構造方法私有,防止別人創建該類的對象。該類是工具類。

    //調用所以動物的功能
    public static void animalLife(Animal a){  //工具類,方法就寫成static的,然後直接在測試類:工具類名.方法 使用。
        a.eat();
        a.call();
    }

}

public class Test{
    public static void main(String[] args){

        Cat c= new Cat();
        AnimalTool.animalLife(c);

        Dog d= new Dog();
        AnimalTool.animalLife(d);
運行結果:
        貓吃貓骨頭
        貓叫
        狗吃狗骨頭
        狗叫
    }
}

注意: 上面動物類都繼承了animal父類

這個時候再分析,如果再來一種動物豬,則需要定義個豬類,提供豬的兩個方法,再繼承Animal父類,這個時候就不需要在工具類中添加對應的XxLife方法,只寫一個animalLife方法即可,而且每多一種動物都不需要在工具類中添加對應的XxLife方法,這樣維護起來就很樂觀了。

由於多態特性的支持,animalLife方法的Animal類型,是CatDog的父類類型,父類類型接收子類對象,當 然可以把Cat對象和Dog對象傳遞給方法。 當eatcall方法執行時,多態規定,執行的是子類重寫的方法,那麼效果自然與Animal的子類中的eatcall方法一致, 所以animalLife完全可以替代以上兩方法。 不僅僅是替代,在擴展性方面,無論之後再多的子類出現,我們都不需要編寫XxLife方法了,直接使用 animalLife就可以完成。 所以,多態的好處,體現在可以使程序編寫的更簡單,並有良好的擴展。

12、 分析開篇的九個問題

看到這裏,相信童鞋們多多少少都應該對多態都一定的了解了,都應該很有信心解決開篇的難題了吧,我可以很負責的告訴你,文章看到這裏,你依舊解決不了這幾個問題,不要問我為啥知道,你可以試着再做一遍,代碼貼在下面:

package Polymorphic;
//爺爺類
class Ye {
    public String show(Sun obj) {
        return ("Ye and Sun");
    }

    public String show(Ye obj) {
        return ("Ye and Ye");
    }

}
//爸爸類
class Fu extends Ye {
    public String show(Fu obj) {
        return ("Fu and Fu");
    }

    public String show(Ye obj) {
        return ("Fu and Ye");
    }
}
//兒子類
class Zi extends Fu {

}
//孫子類
class Sun extends Fu {

}

public class PolymorphicTest {
    public static void main(String[] args) {
         Ye y = new Ye();
        Ye y2 = new Fu(); //向上
        Fu f = new Fu();
        Zi z = new Zi();
        Sun s = new Sun();


        System.out.println("第一題 " + y.show(f));
        System.out.println("第二題 " + y.show(z));
        System.out.println("第三題 " + y.show(s));
        System.out.println("第四題 " + y2.show(f));  //到這裏掛了???
        System.out.println("第五題 " + y2.show(z));
        System.out.println("第六題 " + y2.show(s));
        System.out.println("第七題 " + f.show(f));
        System.out.println("第八題 " + f.show(z));
        System.out.println("第九題 " + f.show(s));
     
    }

打印結果:
    第一題 Ye and Ye
    第二題 Ye and Ye
    第三題 Ye and Sun
    第四題 Fu and Ye
    第五題 Fu and Ye
    第六題 Ye and Sun
    第七題 Fu and Fu
    第八題 Fu and Fu
    第九題 Ye and Sun
}

要想理解上面這個例子,童鞋們必須讀懂這句話:當父類對象引用變量引用子類對象時,被引用對象的類型決定了調用誰的成員方法,引用變量類型決定可調用的方法。首先會先去可調用的方法的父類中尋找,找到了就執行子類中覆蓋的該方法,就算子類中有現成的該方法,它同樣也會去父類中尋找,早到后未必執行子類中有現成的方法,而是執行重寫在父類中找到的方法的子類方法(這裏的子類也就是最後決定調用的類方法)。你是不是暈了?讀着都覺得拗口,要理解可就拗的不是口了而是拗頭 ~啥玩意沒聽過這個詞~ 咳咳,問題不大,樓主來通俗的給大家講解,讓大家理解。

還記不記得樓主之前定義向上轉型是怎麼定義的?

【v8提示】 向上轉型:多態本身是子類類型向父類類型向上轉換的過程,其中,這個過程是默認的。你可以把這個過程理解為基本類型的小類型轉大類型自動轉換,不需要強制轉換。 當父類引用指向一個子類對象時,便是向上轉型

可是,你真的理解了咩?什麼叫做父類對象引用變量引用子類對象?其實還得從下面這句話找頭緒

向上轉型定義:多態本身是子類類型向父類類型向上轉換的過程,其中,這個過程是默認的

就好比Father f = new Son();有的童鞋就會說這個f也算是父類的對象引用?如果按字面理解是子類的引用只不過該引用的類型為Father類型?這時你就大錯特錯了。

我們把向上轉型定義簡化一下理解一下,簡化如下:

子類類型默認向父類類型向上轉換的過程

現在明白了咩?這句話可以理解為Father f = new Son()這句代碼原本是Father f = (Father )new Son()這樣子的只是這個轉換過程是默認自動轉的,總的來說也就是 new Son()其實本質就是new Father,所以f其實就是父類對象引用!這個時候再來拆開理解下面這段話

當父類對象引用變量引用子類對象時

其中父類對象引用變量指的就是f子類對象指的就是new Son(),所以加起來就是當f引用new Son()

被引用對象的類型決定了調用誰的成員方法,引用變量類型決定可調用的方法。

這裏的 被引用對象的類型則是指new Son()對象中的Son類型, 引用變量類型則是指f的類型Father類型

好了總結關聯起來就是當:f引用new Son()時,Son決定了調用它的成員方法,Father決定可調用Father中的方法。所以以Father f = new Son()舉例,簡單來說就是

13、 最後我們一起來正式分析那九個題

前三個並沒有涉及到多態(向上轉型),所以只會調用yeye本類的方法,這裏只要掌握繼承的知識就OK了。

講解第四題之前,你的答案是不是"Fu and Fu"?來了喔,馬上讓你巔覆對多態的人生觀!

分析第四題,首先Ye y2 = new Fu(); 向上轉型了,所以首先會去Fu類的父類Ye類中找show(f)方法,找到了show(Ye obj)方法,之後回到Fu類中看是否有show(Ye obj)重寫方法,發現Fu類有show(Ye obj)方法(重寫),所以最後執行了"Fu and Ye",你get了咩?

分析第五題,其實第五題和第四題基本差不多,第四題是y2.show(f);第五題是y2.show(z);只是show的方法參數不同,相同的是fzYe類中找的都是show(Ye obj)方法,所以,最終第四題和第五題結果一致!

分析第六題,第六題其實挺有趣,首先y2.show(s),到Ye類中找到show(Sun obj),之後在子類中看有沒有重寫,發現並沒有show(Sun obj)重寫方法,確定沒有咩?別忘了這是繼承,子類Fu中默認有着父類Ye的方法,只是沒有表面表示出來,從另一角度出發,Fu類中默認重寫了一個show(Sun obj)方法,就算不寫也是存在的,所以運行結果為"Ye and Sun"

第七、八題就不分析了,畢竟也沒有涉及到向上轉型(多態)。

最後分析一下第九題,有的童鞋就要打樓主了,第九題不也是沒有涉及到向上轉型(多態)嗎,樓主你個星星(**),當然,樓主就算背着黑鍋也要分析第九題~就是這麼傲嬌~,確實沒有涉及到向上轉型(多態),我要講的原因很簡單,因為我覺得還是很有必要!首先f.show(s)不涉及多態,它只會調用自己類(Fu)的方法,但是你會發現Fu中並沒有show(s),唉唉唉,我運行你重新組織下語言,又忘了?這是繼承啊,它有默認父類Ye中的show(Sun obj)方法鴨!好了到這裏,我總結出一點,你多態以及沒得問題了,不過你繼承方面知識薄弱啊,不行不行樓主得給你補補,還在猶豫什麼鴨,快來補補繼承知識!!!

本文的最後,我只是個人對多態的理解,樓主只是個java小白,叫我老白也行,不一定全部正確,如果有什麼錯誤請一定要告訴我,感激不盡感激不盡感激不盡!!!歡迎指正~

最後的最後,如果本文對你有所幫助就點個愛心支持一下吧 ~佛系報大腿~

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

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

【其他文章推薦】

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

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

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

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

如何使用24行JavaScript代碼實現Redux

作者:Yazeed Bzadough
譯者:小維FE
原文:freecodecamp

為了保證文章的可讀性,本文採用意譯而非直譯。

90%的規約,10%的庫。
Redux是迄今為止創建的最重要的JavaScript庫之一,靈感來源於以前的藝術比如和,Redux通過引入一個包含三個簡單要點的可伸縮體繫結構,使得JavaScript函數式編程成為可能。如果你是初次接觸Redux,可以考慮先閱讀。

1. Redux大多是規約

考慮如下這個使用了Redux架構的簡單的計數器應用。如果你想跳過的話可以直接查看。

1.1 State存儲在一棵樹中

該應用程序的狀態看起來如下:

const initialState = { count: 0 };

1.2 Action聲明狀態更改

根據Redux規約,我們不直接修改(突變)狀態。

// 在Redux應用中不要做如下操作
state.count = 1;

相反,我們創建在應用中用戶可能用到的所有行為。

const actions = {
  increment: { type: 'INCREMENT' },
  decrement: { type: 'DECREMENT' }
};

1.3 Reducer解釋行為並更新狀態

在最後一個架構部分我們叫做Reduer,其作為一個純函數,它基於以前的狀態和行為返回狀態的新副本。

  • 如果increment被觸發,則增加state.count
  • 如果decrement被觸發,則減少state.count
const countReducer = (state = initialState, action) => {
  switch (action.type) {
    case actions.increment.type:
      return {
        count: state.count + 1
      };

    case actions.decrement.type:
      return {
        count: state.count - 1
      };

    default:
      return state;
  }
};

1.4 目前為止還沒有Redux

你注意到了嗎?到目前為止我們甚至還沒有接觸到Redux庫,我們僅僅只是創建了一些對象和函數,這就是為什麼我稱其為”大多是規約”,90%的Redux應用其實並不需要Redux。

2. 開始實現Redux

要使用這種架構,我們必須要將它放入到一個store當中,我們將僅僅實現一個函數:createStore。使用方式如下:

import { createStore } from 'redux'

const store = createStore(countReducer);

store.subscribe(() => {
  console.log(store.getState());
});

store.dispatch(actions.increment);
// logs { count: 1 }

store.dispatch(actions.increment);
// logs { count: 2 }

store.dispatch(actions.decrement);
// logs { count: 1 }

下面這是我們的初始化樣板代碼,我們需要一個監聽器列表listeners和reducer提供的初始化狀態。

const createStore = (yourReducer) => {
    let listeners = [];
    let currentState = yourReducer(undefined, {});
}

無論何時某人訂閱了我們的store,那麼他將會被添加到listeners數組中。這是非常重要的,因為每次當某人在派發(dispatch)一個動作(action)的時候,所有的listeners都需要在此次事件循環中被通知到。調用yourReducer函數並傳入一個undefined和一個空對象將會返回一個initialState,這個值也就是我們在調用store.getState()時的返回值。既然說到這裏了,我們就來創建這個方法。

2.1 store.getState()

這個函數用於從store中返回最新的狀態,當用戶每次點擊一個按鈕的時候我們都需要最新的狀態來更新我們的視圖。

const createStore = (yourReducer) => {
    let listeners = [];
    let currentState = yourReducer(undefined, {});
    
    return {
        getState: () => currentState
    };
}

2.2 store.dispatch()

這個函數使用一個action作為其入參,並且將這個actioncurrentState反饋給yourReducer來獲取一個新的狀態,並且dispatch方法還會通知到每一個訂閱了當前store的監聽者。

const createStore = (yourReducer) => {
  let listeners = [];
  let currentState = yourReducer(undefined, {});

  return {
    getState: () => currentState,
    dispatch: (action) => {
      currentState = yourReducer(currentState, action);

      listeners.forEach((listener) => {
        listener();
      });
    }
  };
};

2.3 store.subscribe(listener)

這個方法使得你在當store接收到一個action的時候能夠被通知到,可以在這裏調用store.getState()來獲取最新的狀態並更新UI。

const createStore = (yourReducer) => {
  let listeners = [];
  let currentState = yourReducer(undefined, {});

  return {
    getState: () => currentState,
    dispatch: (action) => {
      currentState = yourReducer(currentState, action);

      listeners.forEach((listener) => {
        listener();
      });
    },
    subscribe: (newListener) => {
      listeners.push(newListener);

      const unsubscribe = () => {
        listeners = listeners.filter((l) => l !== newListener);
      };

      return unsubscribe;
    }
  };
};

同時subscribe函數返回了另一個函數unsubscribe,這個函數允許你當不再對store的更新感興趣的時候能夠取消訂閱。

3. 整理代碼

現在我們添加按鈕的邏輯,來看看最後的源代碼:

// 簡化版createStore函數
const createStore = (yourReducer) => {
  let listeners = [];
  let currentState = yourReducer(undefined, {});

  return {
    getState: () => currentState,
    dispatch: (action) => {
      currentState = yourReducer(currentState, action);

      listeners.forEach((listener) => {
        listener();
      });
    },
    subscribe: (newListener) => {
      listeners.push(newListener);

      const unsubscribe = () => {
        listeners = listeners.filter((l) => l !== newListener);
      };

      return unsubscribe;
    }
  };
};

// Redux的架構組成部分
const initialState = { count: 0 };

const actions = {
  increment: { type: 'INCREMENT' },
  decrement: { type: 'DECREMENT' }
};

const countReducer = (state = initialState, action) => {
  switch (action.type) {
    case actions.increment.type:
      return {
        count: state.count + 1
      };

    case actions.decrement.type:
      return {
        count: state.count - 1
      };

    default:
      return state;
  }
};

const store = createStore(countReducer);

// DOM元素
const incrementButton = document.querySelector('.increment');
const decrementButton = document.querySelector('.decrement');

// 給按鈕添加點擊事件
incrementButton.addEventListener('click', () => {
  store.dispatch(actions.increment);
});

decrementButton.addEventListener('click', () => {
  store.dispatch(actions.decrement);
});

// 初始化UI視圖
const counterDisplay = document.querySelector('h1');
counterDisplay.innerHTML = parseInt(initialState.count);

// 派發動作的時候跟新UI
store.subscribe(() => {
  const state = store.getState();

  counterDisplay.innerHTML = parseInt(state.count);
});

我們再次看看最後的視圖效果:

原文:

4. 交流

本篇主要簡單了解下Redux的三個架構組成部分以及如何實現一個簡化版的Redux,對Redux能有進一步的了解,希望能和大家相互討論技術,一起交流學習。

文章已同步更新至,若覺文章尚可,歡迎前往star!

你的一個點贊,值得讓我付出更多的努力!

逆境中成長,只有不斷地學習,才能成為更好的自己,與君共勉!

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

【其他文章推薦】

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

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

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

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

每天3分鐘操作系統修鍊秘籍(13):兩個緩衝空間Kernel Buffer和IO Buffer

兩個緩衝空間:kernel buffer和io buffer

先看一張圖,稍後將圍繞這張圖展開描述。圖中的fd table、open file table以及兩個inode table都可以不用理解,只需要知道它們體現出來的文件描述符和磁盤文件之間的對應關係:文件描述符fd(例如圖中的fd=3)是對應磁盤上文件的。

在Linux下,我們經常會在IO操作時不可避免的涉及到文件描述符,因為Linux下的所有IO操作都是通過文件描述符來完成的。但是,文件描述符是一個非常底層的概念,通過它操作的數據,都是二進制數據,所以通過文件描述符完成IO的模式通常也稱為裸IO(Raw IO)。而且,直接通過底層的文件描述符進行編程會比較麻煩,因為是二進制數據,它缺少很多功能,比如無法指定編碼,無法指定換行符(換行符有多種:\n、\n\r、\r)等等。注意fd是用戶空間的,它僅僅是一個數值而已,並不是想象中感覺比較底層就在內核空間。

所以,現代高級語言(比如C、Python、Java、Golang)都提供了比文件描述符更高一層次的標準IO庫,比如C的標準IO庫是stdio,Python的標準IO庫是IO模塊,等等。使用這些標準IO庫中的函數進行IO操作時,都會使用比文件描述符更高一層次的對象,例如C中稱為IO流(io stream),其它面向對象的語言中一般稱為IO對象,為了方便說明,這裏統稱為IO對象。上圖中的F就是文件對象。

標準IO庫可以看作是文件描述符的更高層次的封裝,提供了比文件描述符操作IO更多的功能。例如,可以在IO對象上指定編碼、指定換行符,此外還在用戶空間提供了一個標準IO庫的緩衝空間,通常可稱為stdio bufferIO buffer,而這些功能在文件描述符上都是沒有的。另外,標準IO庫既然是高層封裝,當然也會提供用戶不使用這些功能(比如不使用IO Buffer),而是直接使用文件描述符,那麼這時候的文件對象就相當於是文件描述符了,這時候的IO操作模式也就是裸IO模式。

所有從硬件讀取或寫入到硬件的數據,默認都會經過操作系統維護的這個Kernel Buffer。正如上圖中描述的是讀數據過程。

例如,cat進程想要讀取a.log文件,cat進程是用戶空間進程,它自身沒有權限打開文件以及讀文件數據,它只能通過系統調用的方式陷入內核,請求操作系統幫助讀取數據,操作系統讀取數據後會將數據放入到page cache(剛才已說明,對於普通文件維護的Kernel buffer稱為page cache或buffer cache)。然後還要將內核空間page cache中的數據拷貝到用戶空間的IO Buffer緩衝空間(因為cat程序的源代碼中使用了標準IO庫stdio),然後cat進程從自己的IO Buffer中讀取數據。這就是整個讀數據的過程。

需要注意的是,雖然這兩段緩衝空間都在內存中,但仍然有拷貝操作,因為內核的內存空間和用戶進程的虛擬內存空間是隔離的,用戶空間進程沒有權限訪問到內核空間的內存,但是內核具有最高權限,允許訪問任何內存地址。換句話說,在將Kernel Buffer的數據拷貝到IO Buffer空間的過程中,需要陷入到內核,OS需要掌控CPU。

此外,Linux也提供了所謂的直接IO模式,只需使用一個稱為O_DIRECT的標記即可,這時會繞過Kernel Buffer,直接將硬件數據拷貝到用戶空間。雖然看上去直接IO少了一個層次的參與感覺性能會更優秀,但實際上並非如此,操作系統為內核緩衝空間做了非常多的優化,使得並不會因此而降低性能。最典型且常見的一個優化是預讀功能,它表示在讀數據時,會比所請求要讀取的數據量多讀一點放入到Kernel Buffer,這樣在下次讀取接下來的一段數據時可以直接從Kernel Buffer中取數據,而無需再和硬件IO交互。所以,使用直接IO模式的場景是非常少的,一般只會在自帶了完整緩衝模型的大型軟件(比如數據庫系統)上可能會使用直接IO模式。

上面所描述的都是讀操作,關於寫操作,這裏不再多花篇幅去描述,整體過程和讀是類似的,都會經過IO Buffer和Kernel Buffer,只是其中一些細節有所不同,如果感興趣,可以閱讀《Linux/Unix系統編程手冊》的第13章。

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

【其他文章推薦】

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

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

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

Magicodes.Pay,打造開箱即用的統一支付庫,已提供ABP模塊封裝

Magicodes.Pay,打造開箱即用的統一支付庫,已提供ABP模塊封裝

簡介

Magicodes.Pay,是心萊科技團隊提供的統一支付庫,相關庫均使用.NET標準庫編寫,支持.NET Framework以及.NET Core。目前已提供Abp模塊的封裝,支持開箱即用。

Nuget

新的包

 

 

已棄用的包,不再更新

 

 

主要功能

Magicodes.Pay,是心萊科技團隊提供的統一支付庫,相關庫均使用.NET標準庫編寫,支持.NET Framework以及.NET Core。目前已提供Abp模塊的封裝,支持開箱即用。目前支持以下支付方式和功能:

  • 支付寶支付

    • APP支付

    • Wap支付

  • 支付寶國際支付

    • 支持分賬

  • 微信支付

    • 小程序支付

    • APP支付

    • 訂單查詢

    • 企業付款(提現)

    • 退款申請

    • 普通紅包

  • 通聯支付

    • 小程序支付

  • 統一支付回調處理

  • 支持日誌函數注入(不依賴支付庫)

  • 支持支付配置函數注入,以便於支持自定義配置獲取邏輯,以應用於不同的場景(比如從配置文件、用戶設置獲取配置,或者多租戶支持)

  • 針對ABP提供模塊封裝,添加模塊依賴即可立即使用。主要包括:

    • 支付渠道註冊(IPaymentRegister)

    • 支付回調邏輯處理(IPaymentCallbackAction)

    • 統一支付服務實現(IToPayService)

    • 統一支付服務封裝(見IPayAppService)

    • 支付管理器封裝(IPaymentManager),包含:

    • 交易日誌封裝,自動記錄客戶端信息以及自動異常處理和記錄

    • 僅需編寫一次回調邏輯,即可支持多個支付渠道

    • 業務參數支持更大長度(500)

開始使用

如果使用Abp相關模塊,則使用起來比較簡單,具體您可以參考相關單元測試的編寫。主要有以下步驟:

  1. 引用對應的Abp支付的Nuget包 如果僅需某個支付,僅需引用該支付的包。下面以通聯支付為例,我們需要在工程中引用此包:

  2. 添加模塊依賴 在對應工程的Abp的模塊(AbpModule)中,添加對“AbpAllinpayModule”的依賴,如:

 [DependsOn(typeof(AbpAllinpayModule))]
  1. 在DbContext中添加名為“TransactionLogs”的DbSet 整個支付過程中(無論是支付成功還是出現異常),均會記錄交易日誌。交易日誌會記錄交易過程中的一些信息,比如客戶端信息、交易參數、自定義參數以及異常信息。因此我們需要針對EF添加對TransactionLog的支持。需要在DbContext中添加的完整代碼如下所示:

public DbSet<TransactionLog> TransactionLogs { get; set; }
  1. 註冊回調邏輯 我們需要實現“IPaymentCallbackAction”接口來編寫自定義的回調邏輯。如以下示例所示:

public class TestPaymentCallbackAction : IPaymentCallbackAction
    {
        /// <summary>
        /// 業務Key
        /// </summary>
        public string Key { get; set; } = "繳費支付";

        /// <summary>
        /// 執行回調
        /// </summary>
        /// <returns></returns>
        public async Task Process(IUnitOfWorkManager unitOfWork, TransactionLog transactionLog)
        {
            var data = transactionLog.CustomData.FromJsonString<JObject>();
            //業務處理

            await Task.FromResult(0);
        }
    }

注意Key不要重複。

  1. 向容器中註冊回調邏輯

我們可以將回調邏輯寫在一個公共的程序集,然後使用以下代碼進行註冊:

 IocManager.IocContainer.Register(
                //註冊自定義支付回調邏輯
                Classes.FromAssembly(typeof(ApplicationCoreModule).GetAssembly())
                    .BasedOn<IPaymentCallbackAction>()
                    .LifestyleTransient()
                    .Configure(component => component.Named(component.Implementation.FullName))
                    .WithServiceFromInterface()
            );

除了上面的方式,我們還可以通過注入IPaymentManager對象,通過其RegisterCallbackAction方法來註冊自定義的回調邏輯。

  1. 發起支付

通過容器獲得IPayAppService,然後調用Pay方法即可。也可以自行封裝:

public async Task<object> Payment(PaymentInput input)
        {
            return await _payAppService.Pay(new PayInputBase()
            {
                Body = $"{input.Name} {input.ChargeProjectName}",
                CustomData = input.ToJsonString(),
                Key = "繳費支付",
                OpenId = input.OpenId,
                Subject = input.ChargeProjectName,
                TotalAmount = input.Amount,
                PayChannel = input.PayChannel
            });
        }

通過IPayAppService統一支付有如下好處:

  • 統一支付(無論支付寶還是微信各種端的支付,均可統一)

  • 自動記錄交易日誌以及進行相關邏輯處理

  • 自定義數據依賴交易日誌進行存儲,而不依賴支付渠道,因此支持無業務參數的支付渠道,也支持存儲更多自定義數據

非ABP集成

請參考Abp相關模塊的封裝或者歷史代碼。

官方訂閱號

關注“麥扣聊技術”訂閱號免費獲取:

  • 最新文章、教程、文檔

  • 視頻教程

  • 基礎版免費授權

  • 模板

  • 解決方案

  • 編程心得和理念

官方博客/文檔站

其他開源庫地址

 

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

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

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

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

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

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

詳解Spring Security的formLogin登錄認證模式

一、formLogin的應用場景

在本專欄之前的文章中,已經給大家介紹過Spring Security的HttpBasic模式,該模式比較簡單,只是進行了通過攜帶Http的Header進行簡單的登錄驗證,而且沒有定製的登錄頁面,所以使用場景比較窄。
對於一個完整的應用系統,與登錄驗證相關的頁面都是高度定製化的,非常美觀而且提供多種登錄方式。這就需要Spring Security支持我們自己定製登錄頁面,也就是本文給大家介紹的formLogin模式登錄認證模式。

準備工作

  • 新建一個Spring Boot 的web應用,引入Spring Security Starter。
  • 準備一個login.html登錄頁面,頁面內容非常簡單,一個from表單、用戶名和密碼輸入框,一個提交按鈕
  • 準備一個首頁index.html,在登錄成功之後需要進入index.html首頁
  • 首頁可以看到syslog(日誌管理)、sysuer(用戶管理)、biz1(業務一)、biz2(業務二)四個頁面超文本鏈接選項。通過controller控制層跳轉頁面,並在對應頁面寫一些標誌性文字即可,不需寫具體業務。

需求

  • 我們希望biz1(業務一)、biz2(業務二)普通的操作用戶user就可以訪問
  • 我們希望管理員可以訪問包括syslog(日誌管理)和sysuser(用戶管理)在內的所有資源

以上就是本文介紹formLogin模式需要進行的準備工作及需求,下面我們就來實現其中的核心的登錄驗證邏輯,準備工作非常簡單請自行實現。(新建spring boot應用,登錄頁面、首頁、四個業務頁面都寫成非常簡單的html即可,不用寫實際業務和樣式。)

二、說明

formLogin模式的三要素:

  • 登錄驗證邏輯
  • 資源訪問控制規則,如:資源權限、角色權限
  • 用戶信息

一般來說,使用權限認證框架的的業務系統登錄驗證邏輯是固定的,而資源訪問控制規則和用戶信息是從數據庫或其他存儲介質靈活加載的。但本文所有的用戶、資源、權限信息都是代碼配置寫死的,旨在為大家介紹formLogin認證模式,如何從數據庫加載權限認證相關信息我還會結合RBAC權限模型再寫文章的。

三、實現formLogin模式基礎配置

首先,我們要繼承WebSecurityConfigurerAdapter ,重寫configure(HttpSecurity http) 方法,該方法用來配置登錄驗證邏輯。請注意看下文代碼中的註釋信息。

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable() //禁用跨站csrf攻擊防禦,後面的章節會專門講解
            .formLogin()
                .loginPage("/login.html")//用戶未登錄時,訪問任何資源都轉跳到該路徑,即登錄頁面
                .loginProcessingUrl("/login")//登錄表單form中action的地址,也就是處理認證請求的路徑
                .usernameParameter("uname")///登錄表單form中用戶名輸入框input的name名,不修改的話默認是username
                .passwordParameter("pword")//form中密碼輸入框input的name名,不修改的話默認是password
                .defaultSuccessUrl("/index")//登錄認證成功后默認轉跳的路徑
                .and()
            .authorizeRequests()
                .antMatchers("/login.html","/login").permitAll()//不需要通過登錄驗證就可以被訪問的資源路徑
                .antMatchers("/biz1").hasAnyAuthority("biz1")  //前面是資源的訪問路徑、後面是資源的名稱或者叫資源ID
                .antMatchers("/biz2").hasAnyAuthority("biz2")
                .antMatchers("/syslog").hasAnyAuthority("syslog")
                .antMatchers("/sysuser").hasAnyAuthority("sysuser")
                .anyRequest().authenticated();
    }
    
}

上面的代碼分為兩部分:

  • 第一部分是formLogin配置段,用於配置登錄驗證邏輯相關的信息。如:登錄頁面、登錄成功頁面、登錄請求處理路徑等。
  • 第二部分是authorizeRequests配置端,用於配置資源的訪問權限。如:開發登錄頁面的permitAll開放訪問,“/biz1”(業務一頁面資源)需要有資源ID為”biz1″的用戶才可以訪問。

這時候,我們通過瀏覽器訪問,隨便測試一個沒有訪問權限的資源,都會跳轉到login.html頁面。

四、實現資源訪問限制的需求

在上文中,我們配置了登錄驗證及資源訪問的權限規則,我們還沒有具體的用戶,下面我們就來配置具體的用戶。重寫WebSecurityConfigurerAdapter的 configure(AuthenticationManagerBuilder auth)方法

public void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication()
        .withUser("user").password(passwordEncoder().encode("123456")).authorities("biz1","biz2")
        .and()
        .passwordEncoder(passwordEncoder());//配置BCrypt加密
}

@Bean
public PasswordEncoder passwordEncoder(){
    return new BCryptPasswordEncoder();
}
  • inMemoryAuthentication指的是在內存裏面存儲用戶的身份認證和授權信息。
  • withUser("user")用戶名是user
  • password(passwordEncoder().encode("123456"))密碼是加密之後的123456
  • authorities("biz1","biz2")指的是user用戶擁有資源ID為biz1(業務一)和biz2(業務二)資源的權限

這樣,我們就實現了文首提出的普通用戶只能訪問biz1(業務一)和biz2(業務二)資源的需求。那麼管理員用戶可以訪問所有的資源的配置方式,你會不會呢?同樣的配方、同樣的方式、自己可以嘗試一下哦!

五、靜態資源訪問

在我們的實際開發中,登錄頁面login.html和控制層Controller登錄驗證’/login’都必須無條件的開放。除此之外,一些靜態資源如css、js文件通常也都不需要驗證權限,我們需要將它們的訪問權限也開放出來。下面就是實現的方法:重寫WebSecurityConfigurerAdapter類的configure(WebSecurity web) 方法


   @Override
    public void configure(WebSecurity web) {
        //將項目中靜態資源路徑開放出來
        web.ignoring().antMatchers("/config/**", "/css/**", "/fonts/**", "/img/**", "/js/**");
    }

期待您的關注

  • 博主最近新寫了一本書:
  • 本文轉載註明出處(必須帶連接,不能只轉文字):。

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

【其他文章推薦】

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

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

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

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

008.Kubernetes二進制部署Nginx實現高可用

一 Nginx代理實現kube-apiserver高可用

1.1 Nginx實現高可用


基於 nginx 代理的 kube-apiserver 高可用方案。

控制節點的 kube-controller-manager、kube-scheduler 是多實例部署,所以只要有一個實例正常,就可以保證高可用;

集群內的 Pod 使用 K8S 服務域名 kubernetes 訪問 kube-apiserver, kube-dns 會自動解析出多個 kube-apiserver 節點的 IP,所以也是高可用的;

在每個節點起一個 nginx 進程,後端對接多個 apiserver 實例,nginx 對它們做健康檢查和負載均衡;

kubelet、kube-proxy、controller-manager、scheduler 通過本地的 nginx(監聽 127.0.0.1)訪問 kube-apiserver,從而實現 kube-apiserver 的高可用;

從而基於 nginx 4 層透明代理功能實現 K8S 節點( master 節點和 worker 節點)高可用訪問 kube-apiserver 。

1.2 下載編譯Nginx

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# wget http://nginx.org/download/nginx-1.15.3.tar.gz
  3 [root@k8smaster01 work]# tar -xzvf nginx-1.15.3.tar.gz
  4 [root@k8smaster01 ~]# cd /opt/k8s/work/nginx-1.15.3/
  5 [root@k8smaster01 nginx-1.15.3]# mkdir nginx-prefix
  6 [root@k8smaster01 nginx-1.15.3]# ./configure --with-stream --without-http --prefix=$(pwd)/nginx-prefix --without-http_uwsgi_module --without-http_scgi_module --without-http_fastcgi_module
  7 [root@k8smaster01 ~]# cd /opt/k8s/work/nginx-1.15.3/
  8 [root@k8smaster01 nginx-1.15.3]# make && make install



解釋:

  • –with-stream:開啟 4 層透明轉發(TCP Proxy)功能;
  • –without-xxx:關閉所有其他功能,這樣生成的動態鏈接二進製程序依賴最小。
  • [root@k8smaster01 ~]# cd /opt/k8s/work/nginx-1.15.3/
  • [root@k8smaster01 nginx-1.15.3]# ./nginx-prefix/sbin/nginx -v

1.3 驗證編譯后的Nginx

  1 [root@k8smaster01 ~]# cd /opt/k8s/work/nginx-1.15.3
  2 [root@k8smaster01 nginx-1.15.3]# ./nginx-prefix/sbin/nginx -v
  3 nginx version: nginx/1.15.3
  4 [root@k8smaster01 nginx-1.15.3]# ldd ./nginx-prefix/sbin/nginx	#查看 nginx 動態鏈接的庫
  5         linux-vdso.so.1 =>  (0x00007ffdda980000)
  6         libdl.so.2 => /lib64/libdl.so.2 (0x00007feb37300000)
  7         libpthread.so.0 => /lib64/libpthread.so.0 (0x00007feb370e4000)
  8         libc.so.6 => /lib64/libc.so.6 (0x00007feb36d17000)
  9         /lib64/ld-linux-x86-64.so.2 (0x00007feb37504000)



提示:由於只開啟了 4 層透明轉發功能,所以除了依賴 libc 等操作系統核心 lib 庫外,沒有對其它 lib 的依賴(如 libz、libssl 等),以便達到精簡編譯的目的。

1.4 安裝和部署Nginx

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for master_ip in ${MASTER_IPS[@]}
  4   do
  5     echo ">>> ${master_ip}"
  6     mkdir -p /opt/k8s/kube-nginx/{conf,logs,sbin}
  7   done						#創建Nginx目錄
  8 [root@k8smaster01 ~]# cd /opt/k8s/work
  9 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
 10 [root@k8smaster01 work]# for master_ip in ${MASTER_IPS[@]}
 11   do
 12     echo ">>> ${master_ip}"
 13     scp /opt/k8s/work/nginx-1.15.3/nginx-prefix/sbin/nginx  root@${master_ip}:/opt/k8s/kube-nginx/sbin/kube-nginx
 14     ssh root@${master_ip} "chmod a+x /opt/k8s/kube-nginx/sbin/*"
 15     ssh root@${master_ip} "mkdir -p /opt/k8s/kube-nginx/{conf,logs,sbin}"
 16   done						#分發Nginx二進制


1.5 配置Nginx 四層透明轉發

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# cat > kube-nginx.conf <<EOF
  3 worker_processes 1;
  4 
  5 events {
  6     worker_connections  1024;
  7 }
  8 
  9 stream {
 10     upstream backend {
 11         hash $remote_addr consistent;
 12         server 172.24.8.71:6443        max_fails=3 fail_timeout=30s;
 13         server 172.24.8.72:6443        max_fails=3 fail_timeout=30s;
 14         server 172.24.8.73:6443        max_fails=3 fail_timeout=30s;
 15     }
 16 
 17     server {
 18         listen 127.0.0.1:8443;
 19         proxy_connect_timeout 1s;
 20         proxy_pass backend;
 21     }
 22 }
 23 EOF
 24 [root@k8smaster01 ~]# cd /opt/k8s/work
 25 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
 26 [root@k8smaster01 work]# for master_ip in ${MASTER_IPS[@]}
 27   do
 28     echo ">>> ${master_ip}"
 29     scp kube-nginx.conf  root@${master_ip}:/opt/k8s/kube-nginx/conf/kube-nginx.conf
 30   done						#分發Nginx四層透明代理配置文件


1.6 配置Nginx system

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# cat > kube-nginx.service <<EOF
  3 [Unit]
  4 Description=kube-apiserver nginx proxy
  5 After=network.target
  6 After=network-online.target
  7 Wants=network-online.target
  8 
  9 [Service]
 10 Type=forking
 11 ExecStartPre=/opt/k8s/kube-nginx/sbin/kube-nginx -c /opt/k8s/kube-nginx/conf/kube-nginx.conf -p /opt/k8s/kube-nginx -t
 12 ExecStart=/opt/k8s/kube-nginx/sbin/kube-nginx -c /opt/k8s/kube-nginx/conf/kube-nginx.conf -p /opt/k8s/kube-nginx
 13 ExecReload=/opt/k8s/kube-nginx/sbin/kube-nginx -c /opt/k8s/kube-nginx/conf/kube-nginx.conf -p /opt/k8s/kube-nginx -s reload
 14 PrivateTmp=true
 15 Restart=always
 16 RestartSec=5
 17 StartLimitInterval=0
 18 LimitNOFILE=65536
 19 
 20 [Install]
 21 WantedBy=multi-user.target
 22 EOF


1.7 分發Nginx systemd

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for master_ip in ${MASTER_IPS[@]}
  4   do
  5     echo ">>> ${master_ip}"
  6     scp kube-nginx.service  root@${master_ip}:/etc/systemd/system/
  7   done


二 啟動並驗證

2.1 啟動Nginx

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for master_ip in ${MASTER_IPS[@]}
  4   do
  5     echo ">>> ${master_ip}"
  6     ssh root@${master_ip} "systemctl daemon-reload && systemctl enable kube-nginx && systemctl restart kube-nginx"
  7   done


2.2 檢查Nginx服務

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for master_ip in ${MASTER_IPS[@]}
  4   do
  5     echo ">>> ${master_ip}"
  6     ssh root@${master_ip} "systemctl status kube-nginx |grep 'Active:'"
  7   done


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

【其他文章推薦】

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

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

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

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

VueRouter爬坑第一篇-簡單實踐

VueRouter系列的文章示例編寫時,項目是使用vue-cli腳手架搭建。

項目搭建的步驟和項目目錄專門寫了一篇文章:

後續VueRouter系列的文章的示例編寫均基於該項目環境。

 VueRouter系列文章鏈接

  

  《VueRouter爬坑第二篇》-動態路由

  《VueRouter爬坑第三篇》-嵌套路由

 本篇文章完整代碼請移步:

  

 

 

閱讀目錄

一.前言

二.安裝VueRouter

  1.npm 安裝VueRouter

  2.如何使用VueRouter

三.組件和路由之間的映射

  1.<router-link>編寫可以跳轉的url

  2.編寫跳轉的目的組件

  3.定義路由

  4.入口文件main.js配置路由

  5.配置組件渲染的位置

四.總結

 

一.前言

  關於項目的搭建我們在文章最前面已經給了鏈接,在項目搭建這篇文章的最底部有一個注意事項,為了能清楚的理解本篇文章,我再把注意事項貼在這裏:

  

  我們的項目在使用vue-cli初始化的時候是沒有安裝vue-router的,所以如果你的在初始化的時候安裝了vue-router,那這篇文章的有些部分可能不太適用了。

  請大家謹慎觀看。

二.安裝VueRouter

1.npm 安裝VueRouter

  安裝命令:npm install vue-router

  

2.如何使用VueRouter

  安裝完成之後,需要有兩個步驟才能使用

  第一步:引入vue-router

    

  第二步:將組件註冊到全局(全局註冊后,在別的組件中可以直接使用,無需單獨引入)

     

   這塊先貼出步驟,暫時不寫代碼,後面做組件和路由映射的時候在把代碼寫上。

三.組件和路由之間的映射

  接着我們的疑問就來了:頁面中的一個URL如何匹配到我們編寫的組件?下面我們就一步一步揭開這個疑惑。

1.<router-link>編寫可以跳轉的url

   關於router-link的官方文檔說明可查看

  關於上面的疑問,我們第一步需要先編寫一個可以跳轉的URL。我們先把項目中默認生成的HelloWorld.vue組件中div.hello中的內容刪除,然後在加上我們自己的內容。

 

E:\MyStudy\test\VueDemo\src\components\HelloWorld.vue

 

<template>
  <div class="hello">
    <p>這裡是HelloWorld組件</p>
    <!-- 可跳轉的URL -->
    <router-link to="/index">點擊這裏渲染出另外一個組件</router-link>   
  </div>
</template>
// 後面的script和style沒有修改,因此省略

 

App組件不做修改,這裏就不貼代碼了,接着我們使用npm run dev啟動項目查看一下瀏覽器效果。

web頁面效果

 

<template> <div class=”hello”> <p>這裡是HelloWorld組件</p> <!– 可跳轉的URL –> <router-link to=”/index”>點擊這裏去另外一個組件</router-link> </div> </template> // 後面的script和style沒有修改,因此省

2.編寫跳轉的目的組件

  因為我們要實現url跳轉,因此需要編寫一個跳轉的目的組件。在這裏我們創建一個組件Index。

E:\MyStudy\test\VueDemo\src\components\Index.vue

<template>
    <div>  
        <h1>這裡是Index.vue組件</h1>
    </div>
</template>
<script>
export default {
    name: 'Index'
}
</script>

3.定義路由

  現在可點擊的URL準備好了,跳轉的目標組件也準備好了。接下來就是定義路由配置:將URL映射到組件。

  一般項目中為了後續好維護,會將路由配置單獨寫在一個文件中。因此我們先在E:\MyStudy\test\VueDemo\src 目錄下創建一個rotuer目錄,在該目錄下創建一個router.js文件,後面所有的路由配置均在該文件中編寫。

  創建路由配置文件

  

E:\MyStudy\test\VueDemo\src\router\router.js

// vue-router使用第一步:引入vue-router
import Vue from "vue"
import Router from "vue-router"

// vue-router使用第二步:組件註冊到全局
Vue.use(Router)

// 第三步:定義路由配置
// 引入路由需要映射的組件
import Index from '@/components/Index.vue'
const routes = [
    {
        path: '/index',   // 匹配的URL
        component: Index  //映射的目標組件
    }
]

// 第四步:創建vue-router實例,傳遞前面定義的路由配置
const router = new Router({
    routes: routes
})

// 第五步:將vue-router實例使用export導出(供其他模塊使用)
export default router 

  步驟一和步驟二在前面我們已經說過,步驟四和步驟五基本都是固定的配置不可缺少。

  第三個步驟中的routes就是稱為路由配置,可以看到routes是一個數組,它可以包含多個字典對象。每一個字典對象就是一條單個的路由。

  我們寫的這個路由只有最簡單的兩個配置項:path和component,註釋中有說明這兩個配置項的含義。

 

4.入口文件main.js配置路由

  前面的三個部分完成后,還需要在入口文件處把這個路由實例注入到跟組件中,這樣整個應用都可以擁有路由功能。

E:\MyStudy\test\VueDemo\src\main.js

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router/router'
Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  components: { App },
  template: '<App/>',
  //將路由實例註冊到根組件中
  router: router
})

 

大功告成,我們現在點擊界面的鏈接試一下

 

 

 

 

   在這裏呢,賣了個關子少了一個步驟:沒有告訴vue-router把匹配到的組件渲染到哪裡。

5.配置組件渲染的位置

<!-- 使用下面的這個標籤可以告訴vue-router將匹配到的組件渲染到這個位置 -->
<router-view>  </router-view>

 

然後我們將該段代碼加入到HelloWorld.vue這個組件中。

E:\MyStudy\test\VueDemo\src\components\HelloWorld.vue

<template>
  <div class="hello">
    <p>這裡是HelloWorld組件</p>
    <!-- 可跳轉的URL -->
    <router-link to="/index">點擊這裏去另外一個組件</router-link>
    
    <!-- 使用下面的這個標籤可以告訴vue-router將匹配到的組件渲染到這個位置 -->
    <router-view></router-view>
  </div>
</template>
// 後面的script和style沒有修改,因此省略

 

現在我們重新試一下效果

 

 

 

 

 

現在可以看到點擊鏈接后界面發生了變化:

index.vue中的內容显示到了HelloWorld.vue中router-view配置的位置

url由localhost:8080/#/變為localhost:8080/#/index

四.總結

  至此,一個簡單的url路由到組件的實例就完成了,現在總結一下前面梳理過的內容

  1.需要使用npm安裝vue-router。

注意:在使用veue init 初始化項目的時候會提示是否安裝vue-router,如果選擇是,那後續就不需要再次手動安裝了。

  2.可以使用<router-link/>編寫可跳轉的url,並使用<router-view/>指定組件的渲染位置

  3.創建Vue-Router實例,傳入配置的路由:最簡單的路由配置就是path和component

 

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

【其他文章推薦】

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

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

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

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

[ PyQt入門教程 ] PyQt5中數據表格控件QTableWidget使用方法

 如果你想讓你開發的PyQt5工具展示的數據顯得整齊、美觀、好看,顯得符合你的氣質,可以考慮使用QTableWidget控件。之前一直使用的是textBrowser文本框控件,數據展示還是不太美觀。其中QTableWidget是PyQt5程序中常用的显示數據表格的控件,显示的基本效果如下,有點素。。

  下面開始介紹QTableWidget常用的方法以及如何使用。既然是數據表格形式,經常使用的場景其實跟excel我覺得差不多。開始學習使用QTableWidget之前,我們帶着如下幾個問題再開始本文的閱讀。

  1、如何在GUI界面上創建QTableWidget表格?指定N行M列,列名稱等等

  2、如何在表格里添加每一個單元格的數據?

  3、如何排版數據表格的數據,比如你想單元格內容居中、左對齊、右對齊等。

  4、如何設置文字显示顏色、如何設置單元格背景色?

  5、如何合併指定單元格,讓排版更合理?

  基本上使用數據表格展示數據常見的問題就是這些,能夠熟練使用QTableWidget解決上述問題,QTableWidget基本使用方法應該就會了。下面開始學習本文內容。

QTableWidget常用方法

  setROwCount(int row)     設置QTableWidget表格控件的行數

  setColumnCount(int col)     設置QTableWidget表格控件的列數

  setHorizontalHeaderLabels()     設置QTableWidget表格控件的水平標籤

  setVerticalHeaderLabels()     設置QTableWidget表格控件的垂直標籤

  setItem(int ,int ,QTableWidgetItem)     在QTableWidget表格控件的每個選項的單元控件內添加控件

  horizontalHeader()     獲得QTableWidget表格控件的表格頭,以便執行隱藏

  rowCount()     獲得QTableWidget表格控件的行數

  columnCount()     獲得QTableWidget表格控件的列數

  setEditTriggers(EditTriggers triggers)     設置表格是否可以編輯,設置表格的枚舉值

  setSelectionBehavior     設置表格的選擇行為

  setTextAlignment()     設置單元格內文本的對齊方式

  setSpan(int row,int column,int rowSpanCount,int columnSpanCount)     合併單元格,要改變單元格的第row行,column列,要合併rowSpancount行數和columnSpanCount列數。其中row表示要改變的行數, column表示要改變的列數,rowSpanCount表示需要合併的行數,columnSpanCount表示需要合併的列數。

  setShowGrid()     在默認情況下錶格的显示是有網格的,可以設置True或False用於是否显示,默認True

  setColumnWidth(int column,int width)     設置單元格行的寬度

  setRowHeight(int row,int height)     設置單元格列的高度

  這裏的函數有些有點不好理解,比如setSpan()合併單元格函數,你可能一下不知道它的使用方法以及實現效果。沒關係,下面會通過實例講解以及效果演示展示這些函數的使用方法。

QTableWidget使用實例

 1、使用designer實現一個包含QTableWidget數據展示控件的窗體。界面設計一般都會採用designer工具,因為要考慮控件間的布局,純代碼實現會增加難度。界面實現如下

 雙擊在窗體界面上的QTableWidget控件,分別選擇Edit Table Widget中Columns、Rows、Items進行編輯。可以分別完成行、列標題以及單元格內容的添加。

 完成后,效果圖如下

 2、使用pyuic5工具將.ui文件轉換為.py文件。

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'cc.ui'
#
# Created by: PyQt5 UI code generator 5.11.3
#
# WARNING! All changes made in this file will be lost!

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_widget(object):
    def setupUi(self, widget):
        widget.setObjectName("widget")
        widget.resize(730, 574)
        self.tableWidget = QtWidgets.QTableWidget(widget)
        self.tableWidget.setGeometry(QtCore.QRect(10, 130, 701, 192))
        self.tableWidget.setObjectName("tableWidget")
        self.tableWidget.setColumnCount(4)
        self.tableWidget.setRowCount(3)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setVerticalHeaderItem(0, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setVerticalHeaderItem(1, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setVerticalHeaderItem(2, item)
        item = QtWidgets.QTableWidgetItem()
        item.setTextAlignment(QtCore.Qt.AlignCenter)
        self.tableWidget.setHorizontalHeaderItem(0, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setHorizontalHeaderItem(1, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setHorizontalHeaderItem(2, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setHorizontalHeaderItem(3, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setItem(0, 0, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setItem(0, 1, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setItem(0, 2, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setItem(0, 3, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setItem(1, 0, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setItem(1, 1, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setItem(1, 2, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setItem(1, 3, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setItem(2, 0, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setItem(2, 1, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setItem(2, 2, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setItem(2, 3, item)
        self.tableWidget.horizontalHeader().setCascadingSectionResizes(True)

        self.retranslateUi(widget)
        QtCore.QMetaObject.connectSlotsByName(widget)

    def retranslateUi(self, widget):
        _translate = QtCore.QCoreApplication.translate
        widget.setWindowTitle(_translate("widget", "員工信息表"))
        item = self.tableWidget.verticalHeaderItem(0)
        item.setText(_translate("widget", "1"))
        item = self.tableWidget.verticalHeaderItem(1)
        item.setText(_translate("widget", "2"))
        item = self.tableWidget.verticalHeaderItem(2)
        item.setText(_translate("widget", "3"))
        item = self.tableWidget.horizontalHeaderItem(0)
        item.setText(_translate("widget", "姓名"))
        item = self.tableWidget.horizontalHeaderItem(1)
        item.setText(_translate("widget", "年齡"))
        item = self.tableWidget.horizontalHeaderItem(2)
        item.setText(_translate("widget", "性別"))
        item = self.tableWidget.horizontalHeaderItem(3)
        item.setText(_translate("widget", "基本信息"))
        __sortingEnabled = self.tableWidget.isSortingEnabled()
        self.tableWidget.setSortingEnabled(False)
        item = self.tableWidget.item(0, 0)
        item.setText(_translate("widget", "張三"))
        item = self.tableWidget.item(0, 1)
        item.setText(_translate("widget", "23"))
        item = self.tableWidget.item(0, 2)
        item.setText(_translate("widget", "Male"))
        item = self.tableWidget.item(0, 3)
        item.setText(_translate("widget", "家裡有礦"))
        item = self.tableWidget.item(1, 0)
        item.setText(_translate("widget", "王八"))
        item = self.tableWidget.item(1, 1)
        item.setText(_translate("widget", "25"))
        item = self.tableWidget.item(1, 2)
        item.setText(_translate("widget", "Female"))
        item = self.tableWidget.item(1, 3)
        item.setText(_translate("widget", "拆二代"))
        item = self.tableWidget.item(2, 0)
        item.setText(_translate("widget", "趙四"))
        item = self.tableWidget.item(2, 1)
        item.setText(_translate("widget", "28"))
        item = self.tableWidget.item(2, 2)
        item.setText(_translate("widget", "Male"))
        item = self.tableWidget.item(2, 3)
        item.setText(_translate("widget", "勤奮努力的程序員"))
        self.tableWidget.setSortingEnabled(__sortingEnabled)

附designer工具設計完成的.ui文件代碼。不作展示,方便有需要的讀者下載使用。

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>widget</class>
 <widget class="QWidget" name="widget">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>730</width>
    <height>286</height>
   </rect>
  </property>
  <property name="font">
   <font>
    <pointsize>10</pointsize>
   </font>
  </property>
  <property name="windowTitle">
   <string>員工信息表</string>
  </property>
  <widget class="QTableWidget" name="tableWidget">
   <property name="geometry">
    <rect>
     <x>10</x>
     <y>51</y>
     <width>701</width>
     <height>211</height>
    </rect>
   </property>
   <attribute name="horizontalHeaderCascadingSectionResizes">
    <bool>true</bool>
   </attribute>
   <row>
    <property name="text">
     <string>1</string>
    </property>
   </row>
   <row>
    <property name="text">
     <string>2</string>
    </property>
   </row>
   <row>
    <property name="text">
     <string>3</string>
    </property>
   </row>
   <column>
    <property name="text">
     <string>姓名</string>
    </property>
    <property name="textAlignment">
     <set>AlignCenter</set>
    </property>
   </column>
   <column>
    <property name="text">
     <string>年齡</string>
    </property>
   </column>
   <column>
    <property name="text">
     <string>性別</string>
    </property>
   </column>
   <column>
    <property name="text">
     <string>基本信息</string>
    </property>
   </column>
   <item row="0" column="0">
    <property name="text">
     <string>張三</string>
    </property>
   </item>
   <item row="0" column="1">
    <property name="text">
     <string>23</string>
    </property>
   </item>
   <item row="0" column="2">
    <property name="text">
     <string>Male</string>
    </property>
   </item>
   <item row="0" column="3">
    <property name="text">
     <string>家裡有礦</string>
    </property>
   </item>
   <item row="1" column="0">
    <property name="text">
     <string>王八</string>
    </property>
   </item>
   <item row="1" column="1">
    <property name="text">
     <string>25</string>
    </property>
   </item>
   <item row="1" column="2">
    <property name="text">
     <string>Female</string>
    </property>
   </item>
   <item row="1" column="3">
    <property name="text">
     <string>拆二代</string>
    </property>
   </item>
   <item row="2" column="0">
    <property name="text">
     <string>趙四</string>
    </property>
   </item>
   <item row="2" column="1">
    <property name="text">
     <string>28</string>
    </property>
   </item>
   <item row="2" column="2">
    <property name="text">
     <string>Male</string>
    </property>
   </item>
   <item row="2" column="3">
    <property name="text">
     <string>勤奮努力的程序員</string>
    </property>
   </item>
  </widget>
  <widget class="QLabel" name="label">
   <property name="geometry">
    <rect>
     <x>250</x>
     <y>20</y>
     <width>111</width>
     <height>21</height>
    </rect>
   </property>
   <property name="font">
    <font>
     <family>YaHei Consolas Hybrid</family>
     <pointsize>10</pointsize>
    </font>
   </property>
   <property name="text">
    <string>員工信息展示</string>
   </property>
  </widget>
 </widget>
 <resources/>
 <connections/>
</ui>

View Code

 3、編寫主程序,調用界面.py文件。使業界面和邏輯程序分離,這樣的好處就是後面界面任何改動程序邏輯幾乎不會有什麼大的影響。

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'connect_me.ui'
#
# Created by: PyQt5 UI code generator 5.11.3
#
# WARNING! All changes made in this file will be lost!
#導入程序運行必須模塊
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import QColor, QBrush
from cc import Ui_widget


class MyMainForm(QMainWindow, Ui_widget):
    def __init__(self, parent=None):
        super(MyMainForm, self).__init__(parent)
        self.setupUi(self)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    myWin = MyMainForm()
    myWin.show()
    sys.exit(app.exec_())

 運行程序結果如下:

 4、程序運行可以正常显示界面,但是界面並不是很美觀(比如行、列沒有鋪滿整個显示窗口、列標題文字沒有加粗等)。下面通過QTableWidget相關函數的使用來實現優化。

QTableWidget函數使用方法

  上述基本例子實現了員工信息表格展示的初始化,單元格內容手工添加其實在實現場景不合適,只是方便展示,下面講解如何通過代碼實現方式添加單元格內容以及實現單元格內容顏色、字體變化的實現。說明,下述所有代碼添加都在主程序中完成。目的是使程序實現更靈活,方便維護。

 (1)初始化QTableWidget數據展示窗口對象。一般來說該步驟通常是通過designer實現。

self.tableWidget = QtWidgets.QTableWidget(widget) self.tableWidget.setColumnCount(4) self.tableWidget.setRowCount(3)

  設置表格的行、列標題,如下:

self.tableWidget.setHorizontalHeaderLabels(['姓名','年齡']) self.tableWidget.setVerticalHeaderLabels(['1','2'])

 當然也可以通過上面例子中的方式去添加,如下:

item = QtWidgets.QTableWidgetItem()
self.tableWidget.setVerticalHeaderItem(1, item)

item = QtWidgets.QTableWidgetItem()
self.tableWidget.setHorizontalHeaderItem(1, item)

(2)設置表格頭的伸縮模式,也就是讓表格鋪滿整個QTableWidget控件。

self.tableWidget.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
效果如下:

 (2)將表格變為禁止編輯。默認情況下錶格中的字符串是可以更改的,比如雙擊一個單元格,就可以修改運來的內容。如果想禁止這種操作,讓表格對用戶是只讀的,可以添加如下代碼。

self.tableWidget.setEditTriggers(QAbstractItemView.NoEditTriggers)

運行效果(雙擊單元格不可編輯,提前觀察一下鼠標單擊選中的只是單元格):

(3)設置表格整行選中。表格默認選中的是單個單元格。通過下面代碼可以設置成選中整行。添加代碼如下

self.tableWidget.setSelectionBehavior(QAbstractItemView.SelectRows)

運行效果如下:

  另外,單元格選中的類型還可以修改成如下方式:

  QAbstractItemView.SelectColumns 選中1列

  QAbstractItemView.SelectRows  選中1行

  QAbstractItemView.SelectItems 選中1個單元格

(4)行、列標題的显示與隱藏。

  對於列標題的显示或隱藏設置,可以通過添加如下代碼實現。默認是显示的。

self.tableWidget.horizontalHeader().setVisible(False)

運行效果如下(行標題不再显示):

對於行標題,可以使用如下代碼進行隱藏或显示設置。默認是显示
self.tableWidget.verticalHeader().setVisible(

 (5)在表格中添加內容。代碼如下:

        items = [['燕十三','21','Male','武林大俠'],['蕭十一郎','21','Male','武功好']]

        for i in range(len(items)):
            item = items[i]
            row = self.tableWidget.rowCount()
            self.tableWidget.insertRow(row)
            for j in range(len(item)):
                item = QTableWidgetItem(str(items[i][j]))
                self.tableWidget.setItem(row,j,item)

運行結果如下:

(6) 設置表格標題字體加粗。添加代碼如下:

font = self.tableWidget.horizontalHeader().font()
font.setBold(True)
self.tableWidget.horizontalHeader().setFont(font)

運行效果如下

(7) 設置表格指定列寬。添加代碼如下:

self.tableWidget.horizontalHeader().resizeSection(0,100)
self.tableWidget.horizontalHeader().resizeSection(1,100)
self.tableWidget.horizontalHeader().resizeSection(1,100)
self.tableWidget.horizontalHeader().resizeSection(3,400)
#self.tableWidget.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) 一定要註釋,否則上述設置不生效

說明,表頭的自適應模式要註釋,否則上述設置不生效。運行效果如下:

 (8)合併單元格,添加如下代碼

self.tableWidget.setSpan(0,3,2,1)

運行結果如下:

 這裏再補充描述一下setSpan()函數。setSpan(0,3,2,1)函數中(0,3)會指定到單元格第1行第4列(下標都是從0開始計算),(2,1)分別表示合併2行(把下一行合併了),1列(1列,列其實沒有合併)。我們把代碼修改為setSpan(1,2,3,2)。看效果如下(很直觀了吧):

(9)對齊單元格中的內容。添加代碼如下:

for i in range(len(items)):
    each_item = items[i]
    row = self.tableWidget.rowCount()
    self.tableWidget.insertRow(row)
    for j in range(len(each_item)):
        item = QTableWidgetItem(str(items[i][j]))
        if j != len(each_item) -1:
            item.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        self.tableWidget.setItem(row,j,item)

  關鍵代碼就是item.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)。前面3列居中显示,最後一列靠左显示。運行效果如下:

  另外,單元格內容對齊還有如下幾種方式。

  Qt.AlignLeft 將單元格內容沿單元格左邊緣對齊

  Qt.AlignRight 將單元格內容沿單元格右邊緣對齊

  Qt.AlignHCenter 將單元格內容居中显示在水平方向上。

  Qt.AlignJustify 將文本在可用的空間中對齊,默認是從左到右的

  Qt.AlignTop 與頂部對齊

  Qt.AlignBottom 與底部對齊

  Qt.AlignVCenter 在可用的空間中,居中显示在垂直方向上

  Qt.AlignBaseline 與基線對齊

(10)設置單元格字體顏色和背景顏色。添加代碼如下:

for i in range(len(items)):
    each_item = items[i]
    row = self.tableWidget.rowCount()
    self.tableWidget.insertRow(row)
    for j in range(len(each_item)):
        item = QTableWidgetItem(str(items[i][j]))
        if j != len(each_item) -1:
            item.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
            item.setForeground(QBrush(QColor(255,0,0))) #設置除最後一列外的文字顏色為紅色 else:
            item.setBackground(QBrush(QColor(0,255,0))) #設置最後一列的背景色為綠色
        self.tableWidget.setItem(row,j,item)

運行結果如下所示:

 QTableWidget控件的基本使用方法就到這裏,後續有其他更好的使用技巧會繼續更新,為方便自己及大家閱讀本文,附本文使用到的代碼如下:

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'connect_me.ui'
#
# Created by: PyQt5 UI code generator 5.11.3
#
# WARNING! All changes made in this file will be lost!
#導入程序運行必須模塊
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import QColor, QBrush
from PyQt5.QtCore import Qt
from cc import Ui_widget


class MyMainForm(QMainWindow, Ui_widget):
    def __init__(self, parent=None):
        super(MyMainForm, self).__init__(parent)
        self.setupUi(self)
        #self.tableWidget.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.tableWidget.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.tableWidget.setSelectionBehavior(QAbstractItemView.SelectRows)
        #self.tableWidget.horizontalHeader().setVisible(False)
        self.tableWidget.verticalHeader().setVisible(False)
        font = self.tableWidget.horizontalHeader().font()
        font.setBold(True)
        self.tableWidget.horizontalHeader().setFont(font)
        self.tableWidget.horizontalHeader().resizeSection(0,100)
        self.tableWidget.horizontalHeader().resizeSection(1,100)
        self.tableWidget.horizontalHeader().resizeSection(1,100)
        self.tableWidget.horizontalHeader().resizeSection(3,400)
        #self.tableWidget.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)

        items = [['燕十三','21','Male','武林大俠'],['蕭十一郎','21','Male','武功好']]
        for i in range(len(items)):
            each_item = items[i]
            row = self.tableWidget.rowCount()
            self.tableWidget.insertRow(row)
            for j in range(len(each_item)):
                item = QTableWidgetItem(str(items[i][j]))
                if j != len(each_item) -1:
                    item.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
                    item.setForeground(QBrush(QColor(255,0,0)))
                else:
                    item.setBackground(QBrush(QColor(0,255,0)))
                self.tableWidget.setItem(row,j,item)

        self.tableWidget.setSpan(1,2,3,2)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    myWin = MyMainForm()
    myWin.show()
    sys.exit(app.exec_())

 總結

  本文描述PyQt5的QTableWidget控件的基本使用方法和效果,界面實現建議還是desiger設計工具實現,同時使用業務和邏輯相分離的方式編寫程序,都揉在一塊後面程序就不好維護,畢竟沒有誰的工具實現是可以一蹴而就的,還是要考慮可維護性。讀完本文內容相信讀者就可以上手使用QTableWidget控件了。。

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

【其他文章推薦】

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

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

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

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