保護環境 越南明年起加徵汽油環保稅

摘錄自2018年9月21日中央社報導

為了保護和改善環境,越南政府自明年起將調漲對汽油等燃料產品的環境保護稅,其中汽油每公升環保稅將從現行收費3000越盾(約新台幣4元)提升為4000越盾。

越南國會常務委員會昨天(20日)通過有關環境保護稅的決議,自明年起將對相關產品加徵環保稅,希望藉此保護與改善環境,減少污染物排放。

根據這項決議,汽油每公升環保稅將從現行收費3000越盾提升為4000越盾,柴油從1500越盾升到2000越盾,燃油和潤滑油從900越盾升到2000越盾等;此外,煤炭、塑膠袋與除草劑等也被列入加徵環保稅的產品名單。

越南國會官員指出,加徵對汽油等燃料產品的環保稅後,國家預算每年將增加15.7兆越盾,這是一筆很大的稅收,有利用於保護與改善環境。

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

【其他文章推薦】

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

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

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

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

小三通物流營運型態?

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

呼和浩特總投資200億元新能源汽車項目正式開工

日前,計畫總投資約200億元的內蒙古自治區呼和浩特市金山高新區新能源汽車製造專案舉行了集中開工儀式。

該項目占地2605畝、計畫總投資約200億元,其中基礎設施、廠房投資35億元,設計建築面積102.7萬平方米,擬入駐規模以上工業企業20家以上。項目建成後,年產值預計可達500億元,提供就業崗位3萬個,將成為內蒙古新能源汽車及核心零部件產業聚集區。

該專案採取PPP建設模式,分三期推進。目前集中開工的6個專案是首期落戶的項目,計畫於2017年1月實現部分產能投產,預計當年產值可達100億元左右,提供就業崗位6千餘個。

呼和浩特市金山高新區新能源汽車製造專案是該市土左旗2015年引進的億元以上重點工業專案,致力於發展新能源電池、電機、電控等核心零部件生產以及整車製造等延伸產業鏈。

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

【其他文章推薦】

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

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

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

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

小三通物流營運型態?

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

理解Spark SQL(一)—— CLI和ThriftServer

Spark SQL主要提供了兩個工具來訪問hive中的數據,即CLI和ThriftServer。前提是需要Spark支持Hive,即編譯Spark時需要帶上hive和hive-thriftserver選項,同時需要確保在$SPARK_HOME/conf目錄下有hive-site.xml配置文件(可以從hive中拷貝過來)。在該配置文件中主要是配置hive metastore的URI(Spark的CLI和ThriftServer都需要)以及ThriftServer相關配置項(如hive.server2.thrift.bind.host、hive.server2.thrift.port等)。注意如果該台機器上同時運行有Hive ThriftServer和Spark ThriftServer,則hive中的hive.server2.thrift.port配置的端口與spark中的hive.server2.thrift.port配置的端口要不一樣,避免同時啟動時發生端口衝突。

啟動CLI和ThriftServer之前都需要先啟動hive metastore。執行如下命令啟動:

[root@BruceCentOS ~]# nohup hive –service metastore &

成功啟動后,會出現一個RunJar的進程,同時會監聽端口9083(hive metastore的默認端口)。

 

 

 先來看CLI,通過spark-sql腳本來使用CLI。執行如下命令:

[root@BruceCentOS4 spark]# $SPARK_HOME/bin/spark-sql –master yarn

上述命令執行後會啟動一個yarn client模式的Spark程序,如下圖所示:

  同時它會連接到hive metastore,可以在隨後出現的spark-sql>提示符下運行hive sql語句,比如:

  其中每輸入並執行一個SQL語句相當於執行了一個Spark的Job,如圖所示:

  也就是說執行spark-sql腳本會啟動一個yarn clien模式的Spark Application,而後出現spark-sql>提示符,在提示符下的每個SQL語句都會在Spark中執行一個Job,但是對應的都是同一個Application。這個Application會一直運行,可以持續輸入SQL語句執行Job,直到輸入“quit;”,然後就會退出spark-sql,即Spark Application執行完畢。

 

另外一種更好地使用Spark SQL的方法是通過ThriftServer,首先需要啟動Spark的ThriftServer,然後通過Spark下的beeline或者自行編寫程序通過JDBC方式使用Spark SQL。

通過如下命令啟動Spark ThriftServer:

[root@BruceCentOS4 spark]# $SPARK_HOME/sbin/start-thriftserver.sh –master yarn

執行上面的命令后,會生成一個SparkSubmit進程,實際上是啟動一個yarn client模式的Spark Application,如下圖所示:

  而且它提供一個JDBC/ODBC接口,用戶可以通過JDBC/ODBC接口連接ThriftServer來訪問Spark SQL的數據。具體可以通過Spark提供的beeline或者在程序中使用JDBC連接ThriftServer。例如在啟動Spark ThriftServer后,可以通過如下命令使用beeline來訪問Spark SQL的數據。

[root@BruceCentOS3 spark]# $SPARK_HOME/bin/beeline -n root -u jdbc:hive2://BruceCentOS4.Hadoop:10003

 上述beeline連接到了BruceCentOS4上的10003端口,也就是Spark ThriftServer。所有連接到ThriftServer的客戶端beeline或者JDBC程序共享同一個Spark Application,通過beeline或者JDBC程序執行SQL相當於向這個Application提交並執行一個Job。在提示符下輸入“!exit”命令可以退出beeline。

最後,如果要停止ThriftServer(即停止Spark Application),需要執行如下命令:

[root@BruceCentOS4 spark]# $SPARK_HOME/sbin/stop-thriftserver.sh

 

 綜上所述,在Spark SQL的CLI和ThriftServer中,比較推薦使用後者,因為後者更加輕量,只需要啟動一個ThriftServer(對應一個Spark Application)就可以給多個beeline客戶端或者JDBC程序客戶端使用SQL,而前者啟動一個CLI就啟動了一個Spark Application,它只能給一個用戶使用。

 

 

 

 

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

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

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

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

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

小三通物流營運型態?

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

Gigafactory 即將正式開幕,福斯隔海叫陣

特斯拉(Tesla)位於美國內華達州的超級電池工廠 Gigafactory 已經部分營運了好一陣子,不過一直一邊生產一邊還在施工,尚未正式開幕,如今開工大典的日子出爐,特斯拉將於 2016 年 7 月 29 日舉辦開工典禮。   特斯拉 Gigafactory 將是預購熱銷的 Model 3 是否能達成出貨的重要關鍵,Gigafactory 預定 2020 年達到最大產能,1 年生產 3,500 萬度儲存容量的電池,特斯拉表示,這超過 2013 年全球鋰電池生產總量,理論上並將降低 30% 生產成本,使得 Model 3 得以壓低售價還能獲利。   在 2016 年 5 月初,Gigafactory 大約有 14% 完工,目前已經開始生產能源儲存產品 Powerpack 與 Powerwall,不過特斯拉一直未在 Gigafactory 舉辦媒體活動,只有曾經獨家邀請汽車雜誌《汽車趨勢》(Motor Trend)前往其中參觀 Model 3 原型。如今特斯拉在寄給消費者的邀請函中透露開工典禮的日期。   特斯拉的活動往往是消費者優於媒體,Model 3 發表會時,車主先受邀,之後才邀請媒體,Gigafactory 開幕式看樣子也是如此,目前特斯拉先邀請透過介紹 5 位朋友買 Model 3 而贏得參加開幕式權利的消費者參加,媒體尚未受邀,目前尚不清楚到底最後會不會邀請媒體。   就在特斯拉緊鑼密鼓籌辦開工大典時,競爭對手也不甘示弱,德國報導指出德國汽車大廠福斯(VW)打算建造與 Gigafactory 一爭雄長的「數十億歐元」工廠,用以生產電動車並自行生產鋰電池,擺脫對 Panasonic、三星與 LG 化學等電池供應商的依賴。   福斯剛經歷柴油車作弊事件,讓福斯柴油車的形象跌到谷底,很容易理解福斯如今更積極發展電動車以開拓新局,福斯將於 2016 年 6 月 22 日的年會上發表電動車與電池工廠的進一步詳情。特斯拉 Gigafactory 的開幕,可能將是全球車廠與電池廠軍備大賽的開端。

(首圖來源:)   (本文授權轉載自《》─〈〉)

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

【其他文章推薦】

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

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

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

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

小三通物流營運型態?

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

數組與鏈表

前言

數組和鏈表是兩種數據結構,數組非常簡單易用但是它有兩個非常大的缺點,一個是數組一旦創建無法擴展,另一個則是數組的查找和刪除的速度很慢.

鏈表改善了一些數組的缺點,但是同樣的鏈表自身也存在一些自己的缺點.

本篇博客將為大家介紹一下這數組和鏈表特點及各自的優缺點.

閱讀前的準備工作

,一種粗略的評價計算機算法效率的方法.後面的內容會用到表示效率的方法.

1. 數組

我們按數組中的數組是否排序對數組進行劃分,將數組分為無序數組和有序數組.無序數組中的數組是無序的,而有序數組中的數據則是升序或者降序排序的.

1.1 無序數組

因為無序數組中的數據是無序的,往數組中添加數據時不用進行比較和移動數據,所以往無序數組裡面添加數據很快.無論是添加第一個數據還是第一萬個數據所需的時間是相同的,效率為O(1).

至於查找和刪除速度就沒有那麼快了,以數組中有一萬個數據項為例,最少需要比較1次,最多則需要比較一萬次,平均下來需要比較5000次,即N/2次比較,N代表數據量,大O表示法中常數可以忽略,所以效率為O(N).

結論:

  1. 插入很快,因為總是將數據插入到數組的空餘位置.
  2. 查找和刪除很慢,假設數組的長度為N,那麼平均的查找/刪除的比較次數為N/2,並且還需要移動數據.

1.2 有序數組

無序數組中存放的數據是無序的,有序數組裡面存放的數據則是有序的(有可能是升序有可能是降序).

因為有序數組中的數據是按升序/降序排列的,所以插入的時候需要進行排序並且移動數據項,所有有序數組的插入速度比無序數組慢. 效率為O(N).

刪除速度和無序數組一樣慢 效率為O(N).

有序數組的查找速度要比無序數組快,這是因為使用了一個叫做二分查找的算法.

二分查找: 二分查找也稱折半查找(Binary Search),它是一種效率較高的查找方法。但是,折半查找要求線性表必須採用順序存儲結構,而且表中元素按關鍵字有序排列.

有一個關於二分查找的形象類比 -> 猜數遊戲

假設要在0-100之間猜一個數,那麼你第一個要猜的数字就是100的一半50的時候,你的朋友會告訴你這個数字比要猜的数字是大還是小,如果比数字大,你接下來要猜的数字就是50的一半25,你的朋友說比這個数字要大,那麼你下面要猜的数字就是25-50中間的那個數37,以此類推…

使用二分查找可極大的提高查找的效率,假設一個有序數組有十億個數據,那麼查找到所需的数字,最多只需比較30次.

有序數組使用二分查找的效率為O(logN).有序數組也可以通過二分查找來新增和刪除數據以提高效率,但是依然需要在新增/刪除后移動數據項,所以效率依然會有影響.

總結:

  1. 有序數組的查找速度比無序數組高,效率為O(logN)
  2. 有序數組的刪除和新增速度很慢,效率為O(N)

1.3 數組總結

數組雖然簡單易用,但是數組有兩個致命的缺點:

  1. 數組存儲的數量有限,創建的過大浪費資源,創建的過小溢出
  2. 數組的效率比其他數據結構低
  • 無序數組插入效率為O(1)時間,但是查找花費O(N)時間
  • 有序數組查找花費O(logN)時間,插入花費O(N)時間
  • 刪除需要移動平均半數的數據項,所以刪除都是O(N)的時間

2. 鏈表

數組一經創建大小就固定住了,無法修改,鏈表在這方面做出了改善,只要內存夠用就可以無限制的擴大.

鏈表是繼數組之後應用最廣泛的數據結構.

2.1 鏈表的特點

鏈表為什麼叫鏈表呢? 因為它保存數據的方式就像一條鎖鏈

鏈表保存數據的方式很像上面的這一條鎖鏈,每一塊鎖鏈就是一個鏈節點,鏈節點保存着自己的數據同時通過自己的next()方法指向下一個鏈節點. 鏈表通過鏈節點不斷地調用next()方法就可以遍歷鏈表中的所有數據.

在鏈表中,每個數據項都被包含在”鏈節點”(link)中,一個鏈結點是某個類的對象,這個類可以叫做Link.因為一個鏈表中有許多類似的鏈結點,所以有必要用一個不同於鏈表的類來表達鏈結點.

每個Link對象中都包含一個對下一個鏈結點引用的字段(通常叫做next).

鏈表本身的對象中有一個字段指向對第一個鏈結點的引用.

數據與鏈表查找數據的區別: 在數組中查找數據就像在一個大倉庫裏面一樣,一號房間沒有,我們去二號房間,二號房間沒有我們去三號房間,以此類推.. 按照地址找完所有房間就可以了.

而在鏈表中查找數據就像單線彙報的地下工作者,你是孤狼你想要彙報點情報給你的頂級上司毒蜂,但是你必須先報告給你的接頭人豬剛鬣,豬剛鬣在報告給它的單線接頭人土行孫,最後由土行孫報告給毒蜂.只能一個找一個,這樣最終完成任務.

2.2 Java代碼

鏈節點類:


/**
 * @author liuboren
 * @Title: 鏈節點
 * @Description:
 * @date 2019/11/20 19:30
 */
public class Link {
    //  保存的數據
    public int data;

    // 指向的下一個鏈節點
    public Link nextLink;

    public Link(int data) {
        this.data = data;
    }

    public int getData() {
        return data;
    }

    public void setData(int data) {
        this.data = data;
    }

    public Link getNextLink() {
        return nextLink;
    }

    public void setNextLink(Link nextLink) {
        this.nextLink = nextLink;
    }
}

鏈表類


/**
 * @author liuboren
 * @Title: 鏈表類
 * @Description:
 * @date 2019/11/20 19:31
 */
public class LinkList {
    private Link first;

    public LinkList() {
        first = null;
    }

    // 新增鏈節點方法
    public void insertFirst(int data) {
        Link link = new Link(data);
        link.setNextLink(first);
        first = link;
    }
}

在新增節點的時候,新增的link的next方法指向原來的first節點,並將鏈表類的first指向新增的節點.

2.4 其他鏈表

剛剛介紹的鏈表是單向鏈表,只能從后往前遍歷,其他的鏈表還有雙端鏈表、雙向鏈表、有序鏈表.

再簡單介紹一下雙端鏈表吧.

雙端鏈表就是在單向鏈表的基礎上,新增一個成員變量指向鏈表的最後一個對象.

雙端鏈表代碼:

/**
 * @author liuboren
 * @Title: 鏈表類
 * @Description:
 * @date 2019/11/20 19:31
 */
public class LinkList {
    private Link first;
    private Link last;

    public LinkList() {
        first = null;
    }

    public boolean isEmpty() {
        return first == null;
    }

    // 新增鏈節點方法
    public void insertFirst(int data) {
        Link newLink = new Link(data);
        newLink.setNextLink(first);
        if (isEmpty()) {
            last = newLink;
        }
        first = newLink;

    }
}

雙向鏈表則是可以從first和last兩個方向進行遍歷,有序鏈表的數據都是按照關鍵字的順序排列的,本文不再展開了.

2.5 鏈表的效率

鏈表的效率:

  • 表頭插入和刪除速度都很快,花費O(1)的時間.
  • 平均起來,查找&刪除&插入在制定鏈節點後面都需要搜索一半的鏈節點需要O(N)次比較,雖然數組也需要O(N)次比較,但是鏈表讓然要快一些,因為不需要移動數據(只需要改變他們的引用)

3. 總結

鏈表解決了數組大小不能擴展的問題,但是鏈表自身依然存在一些問題(在鏈表的鏈節點後面查找&刪除&插入的效率不高),那麼有沒有一種數據結構即擁有二者的優點又改善了二者的缺點呢,答案是肯定的,下篇博客將為您介紹這種優秀的數據結構,敬請期待.

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

【其他文章推薦】

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

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

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

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

小三通物流營運型態?

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

Spring Boot2 系列教程(二十四)Spring Boot 整合 Jpa

Spring Boot 中的數據持久化方案前面給大夥介紹了兩種了,一個是 JdbcTemplate,還有一個 MyBatis,JdbcTemplate 配置簡單,使用也簡單,但是功能也非常有限,MyBatis 則比較靈活,功能也很強大,據我所知,公司採用 MyBatis 做數據持久化的相當多,但是 MyBatis 並不是唯一的解決方案,除了 MyBatis 之外,還有另外一個東西,那就是 Jpa,松哥也有一些朋友在公司里使用 Jpa 來做數據持久化,本文就和大夥來說說 Jpa 如何實現數據持久化。

Jpa 介紹

首先需要向大夥介紹一下 Jpa,Jpa(Java Persistence API)Java 持久化 API,它是一套 ORM 規範,而不是具體的實現,Jpa 的江湖地位類似於 JDBC,只提供規範,所有的數據庫廠商提供實現(即具體的數據庫驅動),Java 領域,小夥伴們熟知的 ORM 框架可能主要是 Hibernate,實際上,除了 Hibernate 之外,還有很多其他的 ORM 框架,例如:

  • Batoo JPA
  • DataNucleus (formerly JPOX)
  • EclipseLink (formerly Oracle TopLink)
  • IBM, for WebSphere Application Server
  • JBoss with Hibernate
  • Kundera
  • ObjectDB
  • OpenJPA
  • OrientDB from Orient Technologies
  • Versant Corporation JPA (not relational, object database)

Hibernate 只是 ORM 框架的一種,上面列出來的 ORM 框架都是支持 JPA2.0 規範的 ORM 框架。既然它是一個規範,不是具體的實現,那麼必然就不能直接使用(類似於 JDBC 不能直接使用,必須要加了驅動才能用),我們使用的是具體的實現,在這裏我們採用的實現實際上還是 Hibernate。

Spring Boot 中使用的 Jpa 實際上是 Spring Data Jpa,Spring Data 是 Spring 家族的一個子項目,用於簡化 SQL、NoSQL 的訪問,在 Spring Data 中,只要你的方法名稱符合規範,它就知道你想幹嘛,不需要自己再去寫 SQL。

關於 Spring Data Jpa 的具體情況,大家可以參考

工程創建

創建 Spring Boot 工程,添加 Web、Jpa 以及 MySQL 驅動依賴,如下:

工程創建好之後,添加 Druid 依賴,完整的依賴如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.10</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.28</version>
    <scope>runtime</scope>
</dependency>

如此,工程就算創建成功了。

基本配置

工程創建完成后,只需要在 application.properties 中進行數據庫基本信息配置以及 Jpa 基本配置,如下:

# 數據庫的基本配置
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql:///test01?useUnicode=true&characterEncoding=UTF-8
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

# JPA配置
spring.jpa.database=mysql
# 在控制台打印SQL
spring.jpa.show-sql=true
# 數據庫平台
spring.jpa.database-platform=mysql
# 每次啟動項目時,數據庫初始化策略
spring.jpa.hibernate.ddl-auto=update
# 指定默認的存儲引擎為InnoDB
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect

注意這裏和 JdbcTemplate 以及 MyBatis 比起來,多了 Jpa 配置,Jpa 配置含義我都註釋在代碼中了,這裏不再贅述,需要強調的是,最後一行配置,默認情況下,自動創建表的時候會使用 MyISAM 做表的引擎,如果配置了數據庫方言為 MySQL57Dialect,則使用 InnoDB 做表的引擎。

好了,配置完成后,我們的 Jpa 差不多就可以開始用了。

基本用法

ORM(Object Relational Mapping) 框架表示對象關係映射,使用 ORM 框架我們不必再去創建表,框架會自動根據當前項目中的實體類創建相應的數據表。因此,我這裏首先創建一個 User 對象,如下:

@Entity(name = "t_user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    @Column(name = "name")
    private String username;
    private String address;
    //省略getter/setter
}

首先 @Entity 註解表示這是一個實體類,那麼在項目啟動時會自動針對該類生成一張表,默認的表名為類名,@Entity 註解的 name 屬性表示自定義生成的表名。@Id 註解表示這個字段是一個 id,@GeneratedValue 註解表示主鍵的自增長策略,對於類中的其他屬性,默認都會根據屬性名在表中生成相應的字段,字段名和屬性名相同,如果開發者想要對字段進行定製,可以使用 @Column 註解,去配置字段的名稱,長度,是否為空等等。

做完這一切之後,啟動 Spring Boot 項目,就會發現數據庫中多了一個名為 t_user 的表了。

針對該表的操作,則需要我們提供一個 Repository,如下:

public interface UserDao extends JpaRepository<User,Integer> {
    List<User> getUserByAddressEqualsAndIdLessThanEqual(String address, Integer id);
    @Query(value = "select * from t_user where id=(select max(id) from t_user)",nativeQuery = true)
    User maxIdUser();
}

這裏,自定義 UserDao 接口繼承自 JpaRepository,JpaRepository 提供了一些基本的數據操作方法,例如保存,更新,刪除,分頁查詢等,開發者也可以在接口中自己聲明相關的方法,只需要方法名稱符合規範即可,在 Spring Data 中,只要按照既定的規範命名方法,Spring Data Jpa 就知道你想幹嘛,這樣就不用寫 SQL 了,那麼規範是什麼呢?參考下圖:

當然,這種方法命名主要是針對查詢,但是一些特殊需求,可能並不能通過這種方式解決,例如想要查詢 id 最大的用戶,這時就需要開發者自定義查詢 SQL 了。

如上代碼所示,自定義查詢 SQL,使用 @Query 註解,在註解中寫自己的 SQL,默認使用的查詢語言不是 SQL,而是 JPQL,這是一種數據庫平台無關的面向對象的查詢語言,有點定位類似於 Hibernate 中的 HQL,在 @Query 註解中設置 nativeQuery 屬性為 true 則表示使用原生查詢,即大夥所熟悉的 SQL。上面代碼中的只是一個很簡單的例子,還有其他一些點,例如如果這個方法中的 SQL 涉及到數據操作,則需要使用 @Modifying 註解。

好了,定義完 Dao 之後,接下來就可以將 UserDao 注入到 Controller 中進行測試了(這裏為了省事,就沒有提供 Service 了,直接將 UserDao 注入到 Controller 中)。

@RestController
public class UserController {
    @Autowired
    UserDao userDao;
    @PostMapping("/")
    public void addUser() {
        User user = new User();
        user.setId(1);
        user.setUsername("張三");
        user.setAddress("深圳");
        userDao.save(user);
    }
    @DeleteMapping("/")
    public void deleteById() {
        userDao.deleteById(1);
    }
    @PutMapping("/")
    public void updateUser() {
        User user = userDao.getOne(1);
        user.setUsername("李四");
        userDao.flush();
    }
    @GetMapping("/test1")
    public void test1() {
        List<User> all = userDao.findAll();
        System.out.println(all);
    }
    @GetMapping("/test2")
    public void test2() {
        List<User> list = userDao.getUserByAddressEqualsAndIdLessThanEqual("廣州", 2);
        System.out.println(list);
    }
    @GetMapping("/test3")
    public void test3() {
        User user = userDao.maxIdUser();
        System.out.println(user);
    }
}

如此之後,即可查詢到需要的數據。

好了,本文的重點是 Spring Boot 和 Jpa 的整合,這個話題就先說到這裏。

多說兩句

在和 Spring 框架整合時,如果用到 ORM 框架,大部分人可能都是首選 Hibernate,實際上,在和 Spring+SpringMVC 整合時,也可以選擇 Spring Data Jpa 做數據持久化方案,用法和本文所述基本是一樣的,Spring Boot 只是將 Spring Data Jpa 的配置簡化了,因此,很多初學者對 Spring Data Jpa 覺得很神奇,但是又覺得無從下手,其實,此時可以回到 Spring 框架,先去學習 Jpa,再去學習 Spring Data Jpa,這是給初學者的一點建議。

相關案例已經上傳到 GitHub,歡迎小夥伴們們下載:

掃碼關注松哥,公眾號後台回復 2TB,獲取松哥獨家 超2TB 學習資源

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

【其他文章推薦】

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

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

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

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

小三通物流營運型態?

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

.NET Core 3 WPF MVVM框架 Prism系列之數據綁定

 一.安裝Prism

 

1.使用程序包管理控制台

Install-Package Prism.Unity -Version 7.2.0.1367

也可以去掉‘-Version 7.2.0.1367’獲取最新的版本

 2.使用管理解決方案的Nuget包

 

在上面或許我們有個疑問?為啥安裝prism會跟Prism.Unity有關係,我們知道Unity是個IOC容器,而Prism本身就支持IOC,且目前官方支持幾種IOC容器:

1.且unity由於是微軟官方的,且支持prism的組件化,由此我推薦使用prism.unity,在官方文檔中prism7不支持prism.Mef,Prism 7.1將不支持prism.Autofac 2.安裝完prism.unity就已經包含着所有prism的核心庫了,架構如下:

二.實現數據綁定

我們先創建Views文件夾和ViewModels文件夾,將MainWindow放在Views文件夾下,再在ViewModels文件夾下面創建MainWindowViewModel類,如下:

 

xmal代碼如下:

<Window x:Class="PrismSample.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:prism="http://prismlibrary.com/"
        xmlns:local="clr-namespace:PrismSample"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800" prism:ViewModelLocator.AutoWireViewModel="True">
    <StackPanel>
        <TextBox Text="{Binding Text}" Margin="10" Height="100" FontSize="50" Foreground="Black" BorderBrush="Black"/>
        <Button  Height="100" Width="300" Content="Click Me" FontSize="50" Command="{Binding ClickCommnd}"/>
    </StackPanel>
</Window>

 

ViewModel代碼如下:

using Prism.Commands;
using Prism.Mvvm;

namespace PrismSample.ViewModels
{
   public class MainWindowViewModel:BindableBase
    {
        private string _text;
        public string Text
        {
            get { return _text; }
            set { SetProperty(ref _text, value); }
        }

        private DelegateCommand _clickCommnd;
        public DelegateCommand ClickCommnd =>
            _clickCommnd ?? (_clickCommnd = new DelegateCommand(ExecuteClickCommnd));

        void ExecuteClickCommnd()
        {
            this.Text = "Click Me!";
        }

        public MainWindowViewModel()
        {
            this.Text = "Hello Prism!";
        }
    }
}

 

啟動程序:

  點擊 click Me 按鈕:

可以看到,我們已經成功的用prism實現數據綁定了,且View和ViewModel完美的前後端分離

但是現在我們又引出了另外一個問題,當我們不想按照prism的規定硬要將View和ViewModel放在Views和ViewModels裏面,又或許自己的項目取名規則各不相同怎麼辦,這時候就要用到另外幾種方法:

1.更改命名規則

如果,公司命名規則很變態,導致項目結構變成這樣(這種公司辭職了算了):

首先我們在App需要引入prism,修改‘Application’為‘prism:PrismApplication’且刪除StartupUri xmal代碼如下:  

<prism:PrismApplication x:Class="PrismSample.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:prism="http://prismlibrary.com/"
             xmlns:local="clr-namespace:PrismSample">
    <Application.Resources>
         
    </Application.Resources>
</prism:PrismApplication>

  cs後台代碼如下:

using Prism.Unity;
using Prism.Ioc;
using Prism.Mvvm;
using System.Windows;
using PrismSample.Viewsb;
using System;
using System.Reflection;

namespace PrismSample
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : PrismApplication
    {
        //設置啟動起始頁
        protected override Window CreateShell()
        {
            return Container.Resolve<MainWindow>();
        }

        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {

        }

        //配置規則
        protected override void ConfigureViewModelLocator()
        {
            base.ConfigureViewModelLocator();
            ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver((viewType) =>
            {
                var viewName = viewType.FullName.Replace(".Viewsb.", ".ViewModelsa.OhMyGod.");
                var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
                var viewModelName = $"{viewName}Test, {viewAssemblyName}";
                return Type.GetType(viewModelName);
            });
        }
    }
}

 

上面這兩句是關鍵:

“.Viewsb.” 表示View所在文件夾namespace,”.ViewModelsa.OhMyGod.” 表示ViewModel所在namespace

var viewName = viewType.FullName.Replace(".Viewsb.", ".ViewModelsa.OhMyGod.");

  

Test表示ViewModel後綴

var viewModelName = $"{viewName}Test, {viewAssemblyName}";

 

2.自定義ViewModel註冊

我們新建一個Foo類作為自定義類,代碼如下:

using Prism.Commands;
using Prism.Mvvm;

namespace PrismSample
{
   public class Foo:BindableBase
    {

        private string _text;
        public string Text
        {
            get { return _text; }
            set { SetProperty(ref _text, value); }
        }

        public Foo()
        {
            this.Text = "Foo";
        }

        private DelegateCommand _clickCommnd;
        public DelegateCommand ClickCommnd =>
            _clickCommnd ?? (_clickCommnd = new DelegateCommand(ExecuteClickCommnd));

        void ExecuteClickCommnd()
        {
            this.Text = "Oh My God!";
        }
    }
}

 

修改App.cs代碼:

protected override void ConfigureViewModelLocator()
        {
            base.ConfigureViewModelLocator();
            ViewModelLocationProvider.Register<MainWindow, Foo>();
            //ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver((viewType) =>
            //{
            //    var viewName = viewType.FullName.Replace(".Viewsb.", ".ViewModelsa.OhMyGod.");
            //    var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
            //    var viewModelName = $"{viewName}Test, {viewAssemblyName}";
            //    return Type.GetType(viewModelName);
            //});
        }

 

  運行:   點擊按鈕:  

就算是不註釋修改命名規則的代碼,我們發現運行結果還是一樣,因此我們可以得出結論,

這種直接的,不通過反射註冊的自定義註冊方式優先級會高點,在官方文檔也說明這種方式效率會高點

且官方提供4種方式,其餘三種的註冊方式如下:

ViewModelLocationProvider.Register(typeof(MainWindow).ToString(), typeof(MainWindowTest)); 
ViewModelLocationProvider.Register(typeof(MainWindow).ToString(), () => Container.Resolve<Foo>());
ViewModelLocationProvider.Register<MainWindow>(() => Container.Resolve<Foo>());

 

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

【其他文章推薦】

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

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

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

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

小三通物流營運型態?

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

【Leetcode 做題學算法周刊】第四期

首發於微信公眾號《前端成長記》,寫於 2019.11.21

背景

本文記錄刷題過程中的整個思考過程,以供參考。主要內容涵蓋:

  • 題目分析設想
  • 編寫代碼驗證
  • 查閱他人解法
  • 思考總結

目錄

Easy

67.二進制求和

題目描述

給定兩個二進制字符串,返回他們的和(用二進製表示)。

輸入為非空字符串且只包含数字 10

示例:

輸入: a = "11", b = "1"
輸出: "100"

輸入: a = "1010", b = "1011"
輸出: "10101"

題目分析設想

這道題又是一道加法題,所以記住下,直接轉数字進行加法可能會溢出,所以不可取。所以我們需要遍歷每一位來做解答。我這有兩個大方向:補0后遍歷,和不補0遍歷。但是基本的依據都是本位相加,逢2進1即可,類似手寫10進制加法。

  • 補0后遍歷,可以採用先算出的位數推入數組最後反轉,也可以採用先算出的位數填到對應位置后直接輸出
  • 不補0遍歷,根據短數組的長度進行遍歷,長數組剩下的数字與短數組生成的進位進行計算

查閱他人解法

Ⅰ.補0后遍歷,先算先推

代碼:

/**
 * @param {string} a
 * @param {string} b
 * @return {string}
 */
var addBinary = function(a, b) {
    let times = Math.max(a.length, b.length) // 需要遍歷次數
    // 補 0
    while(a.length < times) {
        a = '0' + a
    }
    while(b.length < times) {
        b = '0' + b
    }
    let res = []
    let carry = 0 // 是否進位
    for(let i = times - 1; i >= 0; i--) {
        const num = carry + (a.charAt(i) | 0) + (b.charAt(i) | 0)
        carry = num >= 2 ? 1 : 0
        res.push(num % 2)
    }
    if (carry === 1) {
        res.push(1)
    }
    return res.reverse().join('')
};

結果:

  • 294/294 cases passed (68 ms)
  • Your runtime beats 95.13 % of javascript submissions
  • Your memory usage beats 72.58 % of javascript submissions (35.4 MB)
  • 時間複雜度 O(n)

Ⅱ.補0后遍歷,按位運算

代碼:

/**
 * @param {string} a
 * @param {string} b
 * @return {string}
 */
var addBinary = function(a, b) {
    let times = Math.max(a.length, b.length) // 需要遍歷次數
    // 補 0
    while(a.length < times) {
        a = '0' + a
    }
    while(b.length < times) {
        b = '0' + b
    }
    let res = []
    let carry = 0 // 是否進位
    for(let i = times - 1; i >= 0; i--) {
        res[i] = carry + (a.charAt(i) | 0) + (b.charAt(i) | 0)
        carry = res[i] >= 2 ? 1 : 0
        res[i] %= 2
    }
    if (carry === 1) {
        res.unshift(1)
    }
    return res.join('')
};

結果:

  • 294/294 cases passed (60 ms)
  • Your runtime beats 99.65 % of javascript submissions
  • Your memory usage beats 65.82 % of javascript submissions (35.5 MB)
  • 時間複雜度 O(n)

Ⅲ.不補0遍歷

當然處理方式還是可以選擇上面兩種,我這就採用先算先推來處理了。

代碼:

/**
 * @param {string} a
 * @param {string} b
 * @return {string}
 */
var addBinary = function(a, b) {
    let max = Math.max(a.length, b.length) // 最大長度
    let min = Math.min(a.length, b.length) // 最大公共長度

    // 將長字符串拆成兩部分
    let left = a.length > b.length ? a.substr(0, a.length - b.length) : b.substr(0, b.length - a.length)
    let right = a.length > b.length ? a.substr(a.length - b.length) : b.substr(b.length - a.length)

    // 公共長度部分遍歷
    let rightRes = []
    let carry = 0
    for(let i = min - 1; i >= 0; i--) {
        const num = carry + (right.charAt(i) | 0) + (((a.length > b.length ? b : a)).charAt(i) | 0)
        carry = num >= 2 ? 1 : 0
        rightRes.push(num % 2)
    }

    let leftRes = []
    for(let j = max - min - 1; j >= 0; j--) {
        const num = carry + (left.charAt(j) | 0)
        carry = num >= 2 ? 1 : 0
        leftRes.push(num % 2)
    }

    if (carry === 1) {
        leftRes.push(1)
    }
    return leftRes.reverse().join('') + rightRes.reverse().join('')
};

結果:

  • 294/294 cases passed (76 ms)
  • Your runtime beats 80.74 % of javascript submissions
  • Your memory usage beats 24.48 % of javascript submissions (36.2 MB)
  • 時間複雜度 O(n)

查閱他人解法

看到一些細節上的區別,我這使用 '1' | 0 來轉数字,有的使用 ''1' - '0''。另外還有就是初始化結果數組長度為最大長度加1后,最後判斷首位是否為0需要剔除的,我這使用的是判斷最後是否還要進位補1。

這裏還看到用一個提案中的 BigInt 類型來解決的

Ⅰ.BigInt

代碼:

/**
 * @param {string} a
 * @param {string} b
 * @return {string}
 */
var addBinary = function(a, b) {
    return (BigInt("0b"+a) + BigInt("0b"+b)).toString(2);
};

結果:

  • 294/294 cases passed (52 ms)
  • Your runtime beats 100 % of javascript submissions
  • Your memory usage beats 97.05 % of javascript submissions (34.1 MB)
  • 時間複雜度 O(1)

思考總結

通過 BigInt 的方案我們能看到,使用原生方法確實性能更優。簡單說一下這個類型,目前還在提案階段,看下面的等式基本就能知道實現原理自己寫對應 Hack 來實現了:

BigInt(10) = '10n'
BigInt(20) = '20n'
BigInt(10) + BigInt(20) = '30n'

雖然這種方式很友好,但是還是希望看到加法題的時候,能考慮到遍歷按位處理。

69.x的平方根

題目描述

實現 int sqrt(int x) 函數。

計算並返回 x 的平方根,其中 x 是非負整數。

由於返回類型是整數,結果只保留整數的部分,小數部分將被捨去。

示例:

輸入: 4
輸出: 2

輸入: 8
輸出: 2
說明: 8 的平方根是 2.82842...,
     由於返回類型是整數,小數部分將被捨去。

題目分析設想

同樣,這裏類庫提供的方法 Math.sqrt(x) 就不說了,這也不是本題想考察的意義。所以這裡有幾種方式:

  • 暴力法,這裏不用考慮溢出是因為x沒溢出,所以即使加到平方根加1,也會終止循環
  • 二分法,直接取中位數運算,可以快速排除當前區域一半的區間

編寫代碼驗證

Ⅰ.暴力法

代碼:

/**
 * @param {number} x
 * @return {number}
 */
var mySqrt = function(x) {
    if (x === 0) return 0
    let i = 1
    while(i * i < x) {
        i++
    }
    return i * i === x ? i : i - 1
};

結果:

  • 1017/1017 cases passed (120 ms)
  • Your runtime beats 23 % of javascript submissions
  • Your memory usage beats 34.23 % of javascript submissions (35.7 MB)
  • 時間複雜度 O(n)

Ⅱ.二分法

代碼:

/**
 * @param {number} x
 * @return {number}
 */
var mySqrt = function(x) {
    if (x === 0) return 0
    let l = 1
    let r = x >>> 1
    while(l < r) {
        // 這裏要用大於判斷,所以取右中位數
        const mid = (l + r + 1) >>> 1

        if (mid * mid > x) {
            r = mid - 1
        } else {
            l = mid
        }
    }
    return l
};

結果:

  • 1017/1017 cases passed (76 ms)
  • Your runtime beats 96.08 % of javascript submissions
  • Your memory usage beats 59.17 % of javascript submissions (35.5 MB)
  • 時間複雜度 O(log2(n))

查閱他人解法

這裏看見了兩個有意思的解法:

  • 2的冪次底層優化
  • 牛頓法

Ⅰ.冪次優化

稍微解釋一下,二分法需要做乘法運算,他這裏改用加減法

/**
 * @param {number} x
 * @return {number}
 */
var mySqrt = function(x) {
    let l = 0
    let r = 1 << 16 // 2的16次方,這裏我猜是因為上限2^32所以取一半
    while (l < r - 1) {
        const mid = (l + r) >>> 1
        if (mid * mid <= x) {
            l = mid
        } else {
            r = mid
        }
    }
    return l
};

結果:

1017/1017 cases passed (72 ms)
Your runtime beats 98.46 % of javascript submissions
Your memory usage beats 70.66 % of javascript submissions (35.4 MB)

  • 時間複雜度 O(log2(n))

Ⅱ.牛頓法

算法說明:

在迭代過程中,以直線代替曲線,用一階泰勒展式(即在當前點的切線)代替原曲線,求直線與 xx 軸的交點,重複這個過程直到收斂。

首先隨便猜一個近似值 x,然後不斷令 x 等於 xa/x 的平均數,迭代個六七次后 x 的值就已經相當精確了。

公式可以寫為 X[n+1]=(X[n]+a/X[n])/2

代碼:

/**
 * @param {number} x
 * @return {number}
 */
var mySqrt = function(x) {
    if (x === 0 || x === 1) return x

    let a = x >>> 1
    while(true) {
        let cur = a
        a = (a + x / a) / 2
        // 這裡是為了消除浮點運算的誤差,1e-5是我試出來的
        if (Math.abs(a - cur) < 1e-5) {
            return parseInt(cur)
        }
    }
};

結果:

  • 1017/1017 cases passed (68 ms)
  • Your runtime beats 99.23 % of javascript submissions
  • Your memory usage beats 9.05 % of javascript submissions (36.1 MB)
  • 時間複雜度 O(log2(n))

思考總結

這裏就提一下新接觸的牛頓法吧,實際上是牛頓迭代法,主要是迭代操作。由於在單根附近具有平方收斂,所以可以轉換成線性問題去求平方根的近似值。主要應用場景有這兩個方向:

  • 求方程的根
  • 求解最優化問題

70.爬樓梯

題目描述

假設你正在爬樓梯。需要 n 階你才能到達樓頂。

每次你可以爬 12 個台階。你有多少種不同的方法可以爬到樓頂呢?

注意:給定 n 是一個正整數。

示例:

輸入: 2
輸出: 2
解釋: 有兩種方法可以爬到樓頂。
1.  1 階 + 1 階
2.  2 階

輸入: 3
輸出: 3
解釋: 有三種方法可以爬到樓頂。
1.  1 階 + 1 階 + 1 階
2.  1 階 + 2 階
3.  2 階 + 1 階

題目分析設想

這道題很明顯可以用動態規劃和斐波那契數列來求解。然後我們來看看其他正常思路,如果使用暴力法的話,那麼複雜度將會是 2^n,很容易溢出,但是如果能夠優化成 n 的話,其實還可以求解的。所以這道題我就從以下三個方向來作答:

  • 哈希遞歸,也就是暴力運算的改進版,通過存下算過的值降低複雜度
  • 動態規劃
  • 斐波那契數列

編寫代碼驗證

Ⅰ.哈希遞歸

代碼:

/**
 * @param {number} n
 * @return {number}
 */
var climbStairs = function(n) {
    let hash = {}
    return count(0)
    function count (i) {
        if (i > n) return 0
        if (i === n) return 1

        // 這步節省運算
        if(hash[i] > 0) {
            return hash[i]
        }

        hash[i] = count(i + 1) + count(i + 2)
        return hash[i]
    }
};

結果:

  • 45/45 cases passed (52 ms)
  • Your runtime beats 98.67 % of javascript submissions
  • Your memory usage beats 48.29 % of javascript submissions (33.7 MB)
  • 時間複雜度 O(n)

Ⅱ.動態規劃

代碼:

/**
 * @param {number} n
 * @return {number}
 */
var climbStairs = function(n) {
    if (n === 1) return 1
    if (n === 2) return 2
    // dp[0] 多一位空間,省的後面做減法
    let dp = new Array(n + 1).fill(0)
    dp[1] = 1
    dp[2] = 2
    for(let i = 3; i <= n; i++) {
        dp[i] = dp[i - 1] + dp[i - 2]
    }
    return dp[n]
};

結果:

  • 45/45 cases passed (48 ms)
  • Your runtime beats 99.48 % of javascript submissions
  • Your memory usage beats 21.49 % of javascript submissions (33.8 MB)
  • 時間複雜度 O(n)

Ⅲ.斐波那契數列

其實斐波那契數列就可以用動態規劃來實現,所以下面的代碼思路很相似。

代碼:

/**
 * @param {number} n
 * @return {number}
 */
var climbStairs = function(n) {
    if (n === 1) return 1
    if (n === 2) return 2
    let num1 = 1
    let num2 = 2
    for(let i = 3; i <= n; i++) {
        let count = num1 + num2
        num1 = num2
        num2 = count
    }
    // 相當於fib(n)
    return num2
};

結果:

  • 45/45 cases passed (56 ms)
  • Your runtime beats 95.49 % of javascript submissions
  • Your memory usage beats 46.1 % of javascript submissions (33.7 MB)
  • 時間複雜度 O(n)

查閱他人解法

查看題解發現這麼幾種解法:

  • 斐波那契公式(原來有計算公式可以直接用,尷尬)
  • Binets 方法
  • 排列組合

Ⅰ.斐波那契公式

代碼:

/**
 * @param {number} n
 * @return {number}
 */
var climbStairs = function(n) {
    const sqrt_5 = Math.sqrt(5)
    // 由於 F0 = 1,所以相當於需要求 n+1 的值
    const fib_n = Math.pow((1 + sqrt_5) / 2, n + 1) - Math.pow((1 - sqrt_5) / 2, n + 1)
    return Math.round(fib_n / sqrt_5)
};

結果:

  • 45/45 cases passed (52 ms)
  • Your runtime beats 98.67 % of javascript submissions
  • Your memory usage beats 54.98 % of javascript submissions (33.6 MB)
  • 時間複雜度 O(log(n))

Ⅱ.Binets 方法

算法說明:

使用矩陣乘法來得到第 n 個斐波那契數。注意需要將初始項從 fib(2)=2,fib(1)=1 改成 fib(2)=1,fib(1)=0 ,來達到矩陣等式的左右相等。

代碼:

/**
 * @param {number} n
 * @return {number}
 */
var climbStairs = function(n) {

    function pow(a, n) {
        let ret = [[1,0],[0,1]] // 矩陣
        while(n > 0) {
            if ((n & 1) === 1) {
                ret = multiply(ret, a)
            }
            n >> 1
            a = multiply(a, a)
        }
        return ret;
    }
    function multiply(a, b) {
        let c = [[0,0], [0,0]]
        for (let i = 0; i < 2; i++) {
            for(let j = 0; j < 2; j++) {
                c[i][j] = a[i][0] * b[0][j] + a[i][1] * b[1][j]
            }
        }
        return c
    }

    let q = [[1,1], [1, 0]]
    let res = pow(q, n)
    return res[0][0]
};

結果:

測試用例可以輸出,提交發現超時。

這個筆者還沒完全理解,所以很抱歉,暫時沒有 js 相應代碼分析,後續會補上。也歡迎您補充給我,感謝!

Ⅲ.排列組合

代碼:

/**
 * @param {number} n
 * @return {number}
 */
var climbStairs = function(n) {
    // n 個台階走 i 次1階和 j 次2階走到,推導出 i + 2*j = n
    function combine(m, n) {
        if (m < n) [m, n] = [n, m];
        let count = 1;
        for (let i = m + n, j = 1; i > m; i--) {
            count *= i;
            if (j <= n) count /= j++;
        }
        return count;
    }
    let total = 0;
    // 取出所有滿足條件的解
    for (let i = 0,j = n; j >= 0; j -= 2, i++) {
      total += combine(i, j);
    }
    return total;
};

結果:

  • 45/45 cases passed (60 ms)
  • Your runtime beats 87.94 % of javascript submissions
  • Your memory usage beats 20.72 % of javascript submissions (33.8 MB)
  • 時間複雜度 O(n^2)

思考總結

這種疊加的問題,首先就會想到動態規劃的解法,剛好這裏又滿足斐波那契數列,所以我是推薦首選這兩種解法。另外通過查看他人解法學到了斐波那契公式,以及站在排列組合的角度去解,開拓了思路。

83.刪除排序鏈表中的重複元素

題目描述

給定一個排序鏈表,刪除所有重複的元素,使得每個元素只出現一次。

示例:

輸入: 1->1->2
輸出: 1->2

輸入: 1->1->2->3->3
輸出: 1->2->3

題目分析設想

注意一下,給定的是一個排序鏈表,所以只需要依次更改指針就可以直接得出結果。當然,也可以使用雙指針來跳過重複項即可。所以這裡有兩個方向:

  • 直接運算,通過改變指針指向
  • 雙指針,通過跳過重複項

如果是無序鏈表,我會建議先得到所有值然後去重后(比如通過Set)生成新鏈表作答。

編寫代碼驗證

Ⅰ.直接運算

代碼:

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var deleteDuplicates = function(head) {
    // 複製一個用做操作,由於對象是傳址,所以改指針指向即可
    let cur = head
    while(cur !== null && cur.next !== null) {
        if (cur.val === cur.next.val) { // 值相等
            cur.next = cur.next.next
        } else {
            cur = cur.next
        }
    }
    return head
};

結果:

  • 165/165 cases passed (76 ms)
  • Your runtime beats 87.47 % of javascript submissions
  • Your memory usage beats 81.21 % of javascript submissions (35.5 MB)
  • 時間複雜度 O(n)

Ⅱ.雙指針法

代碼:

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var deleteDuplicates = function(head) {
    // 新建哨兵指針和當前遍歷指針
    if (head === null || head.next === null) return head
    let pre = head
    let cur = head
    while(cur !== null) {
        debugger
        if (cur.val === pre.val) {
            // 當前指針移動
            cur = cur.next
        } else {
            pre.next = cur
            pre = cur
        }
    }
    // 最後一項如果重複需要把head.next指向null
    pre.next = null
    return head
};

結果:

  • 165/165 cases passed (80 ms)
  • Your runtime beats 77.31 % of javascript submissions
  • Your memory usage beats 65.1 % of javascript submissions (35.7 MB)
  • 時間複雜度 O(n)

查閱他人解法

忘記了,這裏確實還可以使用遞歸來作答。

Ⅰ.遞歸法

代碼:

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var deleteDuplicates = function(head) {
    if(head === null || head.next === null) return head
    if (head.val === head.next.val) { // 值相等
        return deleteDuplicates(head.next)
    } else {
        head.next = deleteDuplicates(head.next)
    }
    return head
};

結果:

  • 165/165 cases passed (80 ms)
  • Your runtime beats 77.31 % of javascript submissions
  • Your memory usage beats 81.21 % of javascript submissions (35.5 MB)
  • 時間複雜度 O(n)

思考總結

關於鏈表的題目一般都是通過修改指針指向來作答,區分單指針和雙指針法。另外,遍歷也是可以實現的。

88.合併兩個有序數組

題目描述

給定兩個有序整數數組 nums1nums2,將 nums2 合併到 nums1 中,使得 num1 成為一個有序數組。

說明:

  • 初始化 nums1nums2 的元素數量分別為 mn
  • 你可以假設 nums1 有足夠的空間(空間大小大於或等於 m + n)來保存 nums2 中的元素。

示例:

輸入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6],       n = 3

輸出: [1,2,2,3,5,6]

題目分析設想

之前我們做過刪除排序數組中的重複項,其實這裏也類似。可以從這幾個方向作答:

  • 數組合併後排序
  • 遍曆數組並進行插入
  • 雙指針法,輪流比較

但是由於題目有限定空間都在 nums1 ,並且不要寫 return ,直接在 nums1 上修改,所以我這裏主要的思路就是遍歷,通過 splice 來修改數組。區別就在於遍歷的方式方法。

  • 從前往後
  • 從后往前
  • 合併後排序再賦值

編寫代碼驗證

Ⅰ.從前往後

代碼:

/**
 * @param {number[]} nums1
 * @param {number} m
 * @param {number[]} nums2
 * @param {number} n
 * @return {void} Do not return anything, modify nums1 in-place instead.
 */
var merge = function(nums1, m, nums2, n) {
    // 兩個數組對應指針
    let p1 = 0
    let p2 = 0
    // 這裏需要提前把nums1的元素拷貝出來,要不然比較賦值后就丟失了
    let cpArr = nums1.splice(0, m)

    // 數組指針
    let p = 0
    while(p1 < m && p2 < n) {
        // 先賦值,再進行+1操作
        nums1[p++] = cpArr[p1] < nums2[p2] ? cpArr[p1++] : nums2[p2++]
    }
    // 已經有p個元素了,多餘的元素要刪除,剩餘的要加上
    if (p1 < m) {
        // 剩餘元素,p1 + m + n - p = m + n - (p - p1) = m + n - p2
        nums1.splice(p, m + n - p, ...cpArr.slice(p1, m + n - p2))
    }
    if (p2 < n) {
        // 剩餘元素,p2 + m + n - p = m + n - (p - p2) = m + n - p1
        nums1.splice(p, m + n - p, ...nums2.slice(p2, m + n - p1))
    }
};

結果:

  • 59/59 cases passed (48 ms)
  • Your runtime beats 100 % of javascript submissions
  • Your memory usage beats 64.97 % of javascript submissions (33.8 MB)
  • 時間複雜度 O(m + n)

Ⅱ.從后往前

代碼:

/**
 * @param {number[]} nums1
 * @param {number} m
 * @param {number[]} nums2
 * @param {number} n
 * @return {void} Do not return anything, modify nums1 in-place instead.
 */
var merge = function(nums1, m, nums2, n) {
    // 避免 nums1 = [0,0,0,0], nums2 = [1,2] 這種 nums1.length > nums2.length 並且 m = 0
    nums1.splice(m, nums1.length - m)
    // 兩個數組對應指針
    let p1 = m - 1
    let p2 = n - 1
    // 數組指針
    let p = m + n - 1
    while(p1 >= 0 && p2 >= 0) {
        // 先賦值,再進行-1操作
        nums1[p--] = nums1[p1] < nums2[p2] ? nums2[p2--] : nums1[p1--]
    }
    // 可能nums2有剩餘,由於指針是下標,所以截取數量需要加1
    nums1.splice(0, p2 + 1, ...nums2.slice(0, p2 + 1))
};

結果:

  • 59/59 cases passed (52 ms)
  • Your runtime beats 99.76 % of javascript submissions
  • Your memory usage beats 78.3 % of javascript submissions (33.6 MB)
  • 時間複雜度 O(m + n)

Ⅲ.合併後排序再賦值

代碼:

/**
 * @param {number[]} nums1
 * @param {number} m
 * @param {number[]} nums2
 * @param {number} n
 * @return {void} Do not return anything, modify nums1 in-place instead.
 */
var merge = function(nums1, m, nums2, n) {
    arr = [].concat(nums1.splice(0, m), nums2.splice(0, n))
    arr.sort((a, b) => a - b)
    for(let i = 0; i < arr.length; i++) {
        nums1[i] = arr[i]
    }
};

結果:

  • 59/59 cases passed (64 ms)
  • Your runtime beats 90.11 % of javascript submissions
  • Your memory usage beats 31.21 % of javascript submissions (34.8 MB)
  • 時間複雜度 O(m + n)

查閱他人解法

這裏看到一個直接用兩次 while ,然後直接用 m/n 來計算下標的,沒有額外空間,但是本質上也是從后往前遍歷。

Ⅰ.兩次while

代碼:

/**
 * @param {number[]} nums1
 * @param {number} m
 * @param {number[]} nums2
 * @param {number} n
 * @return {void} Do not return anything, modify nums1 in-place instead.
 */
var merge = function(nums1, m, nums2, n) {
    // 避免 nums1 = [0,0,0,0], nums2 = [1,2] 這種 nums1.length > nums2.length 並且 m = 0
    // nums1.splice(m, nums1.length - m)
    // 從后開始賦值
    while(m !== 0 && n !== 0) {
        nums1[m + n - 1] = nums1[m - 1] > nums2[n - 1] ? nums1[--m] : nums2[--n]
    }
    // nums2 有剩餘
    while(n !== 0) {
        nums1[m + n - 1] = nums2[--n]
    }
};

結果:

  • 59/59 cases passed (56 ms)
  • Your runtime beats 99.16 % of javascript submissions
  • Your memory usage beats 64.26 % of javascript submissions (33.8 MB)
  • 時間複雜度 O(m + n)

思考總結

碰到數組操作,會優先考慮雙指針法,具體指針方向可以由題目邏輯來決定。

(完)

本文為原創文章,可能會更新知識點及修正錯誤,因此轉載請保留原出處,方便溯源,避免陳舊錯誤知識的誤導,同時有更好的閱讀體驗
如果能給您帶去些許幫助,歡迎 ⭐️star 或 ️ fork
(轉載請註明出處:https://chenjiahao.xyz)

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

【其他文章推薦】

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

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

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

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

國務院:外商可獨資製造新能源汽車動力電池等領域

7月19日,國務院下發第2016年第41號文件,關於在自由貿易試驗區暫時調整有關行政法規、國務院文件和經國務院批准的部門規章規定的決定。  
  相關自由貿易試驗區涉及上海市、天津市、廣東省、福建省四區域。國務院決定,在自由貿易試驗區暫時調整《中華人民共和國外資企業法實施細則》等18部行政法規、《國務院關於投資體制改革的決定》等4件國務院檔、《外商投資產業指導目錄(2015年修訂)》等4件經國務院批准的部門規章的有關規定。   據分析,此次重大調整專案涉及51項。其中放開合資門檻,允許外商以獨資形式從事生產經營活動的項目多達12項。在新能源汽車關鍵零部件及整車領域,涉及3項,具體如下:   1、允許外商以獨資形式從事能量型動力電池(能量密度≥110Wh,迴圈壽命≥2000次)的製造; 2、允許外商以獨資形式從事汽車電子匯流排網路技術、電動助力轉向系統電子控制器的製造與研發; 3、允許外商以獨資形式從事摩托車生產;   由此可見,在新能源汽車重要零部件動力電池領域,國外一線大廠終於擺脫了合資電池廠的固定模式,三星SDI、松下、LG化學等電池大頭將獲益。   文章來源:上海蓋世

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

【其他文章推薦】

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

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

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

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

深入理解Kafka必知必會(2)

Kafka目前有哪些內部topic,它們都有什麼特徵?各自的作用又是什麼?

__consumer_offsets:作用是保存 Kafka 消費者的位移信息
__transaction_state:用來存儲事務日誌消息

優先副本是什麼?它有什麼特殊的作用?

所謂的優先副本是指在AR集合列表中的第一個副本。
理想情況下,優先副本就是該分區的leader 副本,所以也可以稱之為 preferred leader。Kafka 要確保所有主題的優先副本在 Kafka 集群中均勻分佈,這樣就保證了所有分區的 leader 均衡分佈。以此來促進集群的負載均衡,這一行為也可以稱為“分區平衡”。

Kafka有哪幾處地方有分區分配的概念?簡述大致的過程及原理

  1. 生產者的分區分配是指為每條消息指定其所要發往的分區。可以編寫一個具體的類實現org.apache.kafka.clients.producer.Partitioner接口。
  2. 消費者中的分區分配是指為消費者指定其可以消費消息的分區。Kafka 提供了消費者客戶端參數 partition.assignment.strategy 來設置消費者與訂閱主題之間的分區分配策略。
  3. 分區副本的分配是指為集群制定創建主題時的分區副本分配方案,即在哪個 broker 中創建哪些分區的副本。kafka-topics.sh 腳本中提供了一個 replica-assignment 參數來手動指定分區副本的分配方案。

簡述Kafka的日誌目錄結構

Kafka 中的消息是以主題為基本單位進行歸類的,各個主題在邏輯上相互獨立。每個主題又可以分為一個或多個分區。不考慮多副本的情況,一個分區對應一個日誌(Log)。為了防止 Log 過大,Kafka 又引入了日誌分段(LogSegment)的概念,將 Log 切分為多個 LogSegment,相當於一個巨型文件被平均分配為多個相對較小的文件。

Log 和 LogSegment 也不是純粹物理意義上的概念,Log 在物理上只以文件夾的形式存儲,而每個 LogSegment 對應於磁盤上的一個日誌文件和兩個索引文件,以及可能的其他文件(比如以“.txnindex”為後綴的事務索引文件)

Kafka中有那些索引文件?

每個日誌分段文件對應了兩個索引文件,主要用來提高查找消息的效率。
偏移量索引文件用來建立消息偏移量(offset)到物理地址之間的映射關係,方便快速定位消息所在的物理文件位置
時間戳索引文件則根據指定的時間戳(timestamp)來查找對應的偏移量信息。

如果我指定了一個offset,Kafka怎麼查找到對應的消息?

Kafka是通過seek() 方法來指定消費的,在執行seek() 方法之前要去執行一次poll()方法,等到分配到分區之後會去對應的分區的指定位置開始消費,如果指定的位置發生了越界,那麼會根據auto.offset.reset 參數設置的情況進行消費。

如果我指定了一個timestamp,Kafka怎麼查找到對應的消息?

Kafka提供了一個 offsetsForTimes() 方法,通過 timestamp 來查詢與此對應的分區位置。offsetsForTimes() 方法的參數 timestampsToSearch 是一個 Map 類型,key 為待查詢的分區,而 value 為待查詢的時間戳,該方法會返回時間戳大於等於待查詢時間的第一條消息對應的位置和時間戳,對應於 OffsetAndTimestamp 中的 offset 和 timestamp 字段。

聊一聊你對Kafka的Log Retention的理解

日誌刪除(Log Retention):按照一定的保留策略直接刪除不符合條件的日誌分段。
我們可以通過 broker 端參數 log.cleanup.policy 來設置日誌清理策略,此參數的默認值為“delete”,即採用日誌刪除的清理策略。

  1. 基於時間
    日誌刪除任務會檢查當前日誌文件中是否有保留時間超過設定的閾值(retentionMs)來尋找可刪除的日誌分段文件集合(deletableSegments)retentionMs 可以通過 broker 端參數 log.retention.hours、log.retention.minutes 和 log.retention.ms 來配置,其中 log.retention.ms 的優先級最高,log.retention.minutes 次之,log.retention.hours 最低。默認情況下只配置了 log.retention.hours 參數,其值為168,故默認情況下日誌分段文件的保留時間為7天。
    刪除日誌分段時,首先會從 Log 對象中所維護日誌分段的跳躍表中移除待刪除的日誌分段,以保證沒有線程對這些日誌分段進行讀取操作。然後將日誌分段所對應的所有文件添加上“.deleted”的後綴(當然也包括對應的索引文件)。最後交由一個以“delete-file”命名的延遲任務來刪除這些以“.deleted”為後綴的文件,這個任務的延遲執行時間可以通過 file.delete.delay.ms 參數來調配,此參數的默認值為60000,即1分鐘。

  2. 基於日誌大小
    日誌刪除任務會檢查當前日誌的大小是否超過設定的閾值(retentionSize)來尋找可刪除的日誌分段的文件集合(deletableSegments)。
    retentionSize 可以通過 broker 端參數 log.retention.bytes 來配置,默認值為-1,表示無窮大。注意 log.retention.bytes 配置的是 Log 中所有日誌文件的總大小,而不是單個日誌分段(確切地說應該為 .log 日誌文件)的大小。單個日誌分段的大小由 broker 端參數 log.segment.bytes 來限制,默認值為1073741824,即 1GB。
    這個刪除操作和基於時間的保留策略的刪除操作相同。
  3. 基於日誌起始偏移量
    基於日誌起始偏移量的保留策略的判斷依據是某日誌分段的下一個日誌分段的起始偏移量 baseOffset 是否小於等於 logStartOffset,若是,則可以刪除此日誌分段。

如上圖所示,假設 logStartOffset 等於25,日誌分段1的起始偏移量為0,日誌分段2的起始偏移量為11,日誌分段3的起始偏移量為23,通過如下動作收集可刪除的日誌分段的文件集合 deletableSegments:

從頭開始遍歷每個日誌分段,日誌分段1的下一個日誌分段的起始偏移量為11,小於 logStartOffset 的大小,將日誌分段1加入 deletableSegments。
日誌分段2的下一個日誌偏移量的起始偏移量為23,也小於 logStartOffset 的大小,將日誌分段2加入 deletableSegments。
日誌分段3的下一個日誌偏移量在 logStartOffset 的右側,故從日誌分段3開始的所有日誌分段都不會加入 deletableSegments。
收集完可刪除的日誌分段的文件集合之後的刪除操作同基於日誌大小的保留策略和基於時間的保留策略相同

聊一聊你對Kafka的Log Compaction的理解

日誌壓縮(Log Compaction):針對每個消息的 key 進行整合,對於有相同 key 的不同 value 值,只保留最後一個版本。
如果要採用日誌壓縮的清理策略,就需要將 log.cleanup.policy 設置為“compact”,並且還需要將 log.cleaner.enable (默認值為 true)設定為 true。

如下圖所示,Log Compaction 對於有相同 key 的不同 value 值,只保留最後一個版本。如果應用只關心 key 對應的最新 value 值,則可以開啟 Kafka 的日誌清理功能,Kafka 會定期將相同 key 的消息進行合併,只保留最新的 value 值。

聊一聊你對Kafka底層存儲的理解

頁緩存

頁緩存是操作系統實現的一種主要的磁盤緩存,以此用來減少對磁盤 I/O 的操作。具體來說,就是把磁盤中的數據緩存到內存中,把對磁盤的訪問變為對內存的訪問。

當一個進程準備讀取磁盤上的文件內容時,操作系統會先查看待讀取的數據所在的頁(page)是否在頁緩存(pagecache)中,如果存在(命中)則直接返回數據,從而避免了對物理磁盤的 I/O 操作;如果沒有命中,則操作系統會向磁盤發起讀取請求並將讀取的數據頁存入頁緩存,之後再將數據返回給進程。

同樣,如果一個進程需要將數據寫入磁盤,那麼操作系統也會檢測數據對應的頁是否在頁緩存中,如果不存在,則會先在頁緩存中添加相應的頁,最後將數據寫入對應的頁。被修改過後的頁也就變成了臟頁,操作系統會在合適的時間把臟頁中的數據寫入磁盤,以保持數據的一致性。

用過 Java 的人一般都知道兩點事實:對象的內存開銷非常大,通常會是真實數據大小的幾倍甚至更多,空間使用率低下;Java 的垃圾回收會隨着堆內數據的增多而變得越來越慢。基於這些因素,使用文件系統並依賴於頁緩存的做法明顯要優於維護一個進程內緩存或其他結構,至少我們可以省去了一份進程內部的緩存消耗,同時還可以通過結構緊湊的字節碼來替代使用對象的方式以節省更多的空間。

此外,即使 Kafka 服務重啟,頁緩存還是會保持有效,然而進程內的緩存卻需要重建。這樣也極大地簡化了代碼邏輯,因為維護頁緩存和文件之間的一致性交由操作系統來負責,這樣會比進程內維護更加安全有效。

零拷貝

除了消息順序追加、頁緩存等技術,Kafka 還使用零拷貝(Zero-Copy)技術來進一步提升性能。所謂的零拷貝是指將數據直接從磁盤文件複製到網卡設備中,而不需要經由應用程序之手。零拷貝大大提高了應用程序的性能,減少了內核和用戶模式之間的上下文切換。對 Linux 操作系統而言,零拷貝技術依賴於底層的 sendfile() 方法實現。對應於 Java 語言,FileChannal.transferTo() 方法的底層實現就是 sendfile() 方法。

聊一聊Kafka的延時操作的原理

Kafka 中有多種延時操作,比如延時生產,還有延時拉取(DelayedFetch)、延時數據刪除(DelayedDeleteRecords)等。
延時操作創建之後會被加入延時操作管理器(DelayedOperationPurgatory)來做專門的處理。延時操作有可能會超時,每個延時操作管理器都會配備一個定時器(SystemTimer)來做超時管理,定時器的底層就是採用時間輪(TimingWheel)實現的。

聊一聊Kafka控制器的作用

在 Kafka 集群中會有一個或多個 broker,其中有一個 broker 會被選舉為控制器(Kafka Controller),它負責管理整個集群中所有分區和副本的狀態。當某個分區的 leader 副本出現故障時,由控制器負責為該分區選舉新的 leader 副本。當檢測到某個分區的 ISR 集合發生變化時,由控制器負責通知所有broker更新其元數據信息。當使用 kafka-topics.sh 腳本為某個 topic 增加分區數量時,同樣還是由控制器負責分區的重新分配。

Kafka的舊版Scala的消費者客戶端的設計有什麼缺陷?

如上圖,舊版消費者客戶端每個消費組( )在 ZooKeeper 中都維護了一個 /consumers/ /ids 路徑,在此路徑下使用臨時節點記錄隸屬於此消費組的消費者的唯一標識(consumerIdString),/consumers/ /owner 路徑下記錄了分區和消費者的對應關係,/consumers/ /offsets 路徑下記錄了此消費組在分區中對應的消費位移。

每個消費者在啟動時都會在 /consumers/ /ids 和 /brokers/ids 路徑上註冊一個監聽器。當 /consumers/ /ids 路徑下的子節點發生變化時,表示消費組中的消費者發生了變化;當 /brokers/ids 路徑下的子節點發生變化時,表示 broker 出現了增減。這樣通過 ZooKeeper 所提供的 Watcher,每個消費者就可以監聽消費組和 Kafka 集群的狀態了。

這種方式下每個消費者對 ZooKeeper 的相關路徑分別進行監聽,當觸發再均衡操作時,一個消費組下的所有消費者會同時進行再均衡操作,而消費者之間並不知道彼此操作的結果,這樣可能導致 Kafka 工作在一個不正確的狀態。與此同時,這種嚴重依賴於 ZooKeeper 集群的做法還有兩個比較嚴重的問題。

  1. 羊群效應(Herd Effect):所謂的羊群效應是指ZooKeeper 中一個被監聽的節點變化,大量的 Watcher 通知被發送到客戶端,導致在通知期間的其他操作延遲,也有可能發生類似死鎖的情況。
  2. 腦裂問題(Split Brain):消費者進行再均衡操作時每個消費者都與 ZooKeeper 進行通信以判斷消費者或broker變化的情況,由於 ZooKeeper 本身的特性,可能導致在同一時刻各個消費者獲取的狀態不一致,這樣會導致異常問題發生。

消費再均衡的原理是什麼?(提示:消費者協調器和消費組協調器)

就目前而言,一共有如下幾種情形會觸發再均衡的操作:

  • 有新的消費者加入消費組。
  • 有消費者宕機下線。消費者並不一定需要真正下線,例如遇到長時間的GC、網絡延遲導致消費者長時間未向 GroupCoordinator 發送心跳等情況時,GroupCoordinator 會認為消費者已經下線。
  • 有消費者主動退出消費組(發送 LeaveGroupRequest 請求)。比如客戶端調用了 unsubscrible() 方法取消對某些主題的訂閱。
  • 消費組所對應的 GroupCoorinator 節點發生了變更。
  • 消費組內所訂閱的任一主題或者主題的分區數量發生變化。

GroupCoordinator 是 Kafka 服務端中用於管理消費組的組件。而消費者客戶端中的 ConsumerCoordinator 組件負責與 GroupCoordinator 進行交互。

第一階段(FIND_COORDINATOR)

消費者需要確定它所屬的消費組對應的 GroupCoordinator 所在的 broker,並創建與該 broker 相互通信的網絡連接。如果消費者已經保存了與消費組對應的 GroupCoordinator 節點的信息,並且與它之間的網絡連接是正常的,那麼就可以進入第二階段。否則,就需要向集群中的某個節點發送 FindCoordinatorRequest 請求來查找對應的 GroupCoordinator,這裏的“某個節點”並非是集群中的任意節點,而是負載最小的節點。

第二階段(JOIN_GROUP)

在成功找到消費組所對應的 GroupCoordinator 之後就進入加入消費組的階段,在此階段的消費者會向 GroupCoordinator 發送 JoinGroupRequest 請求,並處理響應。

選舉消費組的leader
如果消費組內還沒有 leader,那麼第一個加入消費組的消費者即為消費組的 leader。如果某一時刻 leader 消費者由於某些原因退出了消費組,那麼會重新選舉一個新的 leader

選舉分區分配策略

  1. 收集各個消費者支持的所有分配策略,組成候選集 candidates。
  2. 每個消費者從候選集 candidates 中找出第一個自身支持的策略,為這個策略投上一票。
  3. 計算候選集中各個策略的選票數,選票數最多的策略即為當前消費組的分配策略。

第三階段(SYNC_GROUP)

leader 消費者根據在第二階段中選舉出來的分區分配策略來實施具體的分區分配,在此之後需要將分配的方案同步給各個消費者,通過 GroupCoordinator 這個“中間人”來負責轉發同步分配方案的。

第四階段(HEARTBEAT)

進入這個階段之後,消費組中的所有消費者就會處於正常工作狀態。在正式消費之前,消費者還需要確定拉取消息的起始位置。假設之前已經將最後的消費位移提交到了 GroupCoordinator,並且 GroupCoordinator 將其保存到了 Kafka 內部的 __consumer_offsets 主題中,此時消費者可以通過 OffsetFetchRequest 請求獲取上次提交的消費位移並從此處繼續消費。

消費者通過向 GroupCoordinator 發送心跳來維持它們與消費組的從屬關係,以及它們對分區的所有權關係。只要消費者以正常的時間間隔發送心跳,就被認為是活躍的,說明它還在讀取分區中的消息。心跳線程是一個獨立的線程,可以在輪詢消息的空檔發送心跳。如果消費者停止發送心跳的時間足夠長,則整個會話就被判定為過期,GroupCoordinator 也會認為這個消費者已經死亡,就會觸發一次再均衡行為。

Kafka中的冪等是怎麼實現的?

為了實現生產者的冪等性,Kafka 為此引入了 producer id(以下簡稱 PID)和序列號(sequence number)這兩個概念。

每個新的生產者實例在初始化的時候都會被分配一個 PID,這個 PID 對用戶而言是完全透明的。對於每個 PID,消息發送到的每一個分區都有對應的序列號,這些序列號從0開始單調遞增。生產者每發送一條消息就會將 <PID,分區> 對應的序列號的值加1。

broker 端會在內存中為每一對 <PID,分區> 維護一個序列號。對於收到的每一條消息,只有當它的序列號的值(SN_new)比 broker 端中維護的對應的序列號的值(SN_old)大1(即 SN_new = SN_old + 1)時,broker 才會接收它。如果 SN_new< SN_old + 1,那麼說明消息被重複寫入,broker 可以直接將其丟棄。如果 SN_new> SN_old + 1,那麼說明中間有數據尚未寫入,出現了亂序,暗示可能有消息丟失,對應的生產者會拋出 OutOfOrderSequenceException,這個異常是一個嚴重的異常,後續的諸如 send()、beginTransaction()、commitTransaction() 等方法的調用都會拋出 IllegalStateException 的異常。

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

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

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

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

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