博越最高性價比車型來了 不是渦輪還值得買嗎

博越這款2。0L自然吸氣發動機,峰值扭矩為178牛米,最大功率141千瓦,傳動系統,配備將會是6速手動變速箱。在動力輸出變現,也是足夠滿足日常使用。現在市面上,暫時還沒有2。0L手動智聯型,只有1。8T和2。0L低配車型,那我們應該等不等這款2。

面對吉利博越賣得水深火熱時,吉利又將放出大招!

我們得到消息,吉利汽車將針對吉利博越新增一款2.0L 手動智聯型,這款車型定位於2.0L手動車型頂配,售價為11.28萬元,其餘,消費者購買這款車型的同時,將會享受到吉利官方4000元的補貼,即是108800元就能購買到這款車型。

那麼,新增這款手動智聯型,都有什麼亮點,值不值得購買呢?

至於外觀上我相信大家已經非常了解,我在這裏就不闡述了。2.0L手動智聯型配置上增加了G-Netlink吉利智能車載系統和7英寸全液晶儀錶盤,包括“你好,博越”智能語音交互系統、GpS導航、多媒體影音、3G+車載Wi-Fi、車載藍牙、iOS+Android手機智能互聯等功能。

值得一提的是,這款2.0L手動智聯型配備了真皮多功能方面盤、上坡輔助、倒車影像、胎壓監測、ESp車身穩定系統、自動駐車、無鑰匙進入/啟動、陡坡緩降、皮質座椅、全景天窗等。可以看出,博越新增2.0L手動智聯型在性價比上說是超高的,其實一點也不過分,畢竟價格只是10萬出頭,還擁有這麼多配置,真是能甩同級別車型幾條街。

雖然很多消費者購買1.8T車型,但是2.0L手動車型也是有不少消費者青睞。博越這款2.0L自然吸氣發動機,峰值扭矩為178牛米,最大功率141千瓦,傳動系統,配備將會是6速手動變速箱。在動力輸出變現,也是足夠滿足日常使用。

現在市面上,暫時還沒有2.0L手動智聯型,只有1.8T和2.0L低配車型,那我們應該等不等這款2.0L手動智聯型進入市場呢,編者認為是值得,因為這款車型性價比非常高,配置多到10個手指數不完,而且售價也很親民。相對來說這款車型還是非常值得購買!小編都覺得要動心了!

競品對手:

上汽集團-榮威RX5

指導價:9.98-18.68萬

榮威RX5可以說是博越最直接競爭對手之一,真是冤家路窄啊!不可否認,榮威底盤功底非常深厚,紮實,行駛質感也很高級,但唯一不足就是榮威RX5入門車型,配置非常低,連最基本的ESp車身穩定系統也沒配備到,這可能只是為了拉低售價。但博越新增這款2.0L手動智聯型正好瞄準這一點上,在配置上、售價上給榮威RX5,狠狠開了一槍!這樣直接的競爭,真的好嗎?本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

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

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

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

※教你寫出一流的銷售文案?

※超省錢租車方案

※回頭車貨運收費標準

它或許是2017最值得期待的自主品牌SUV

0T的柴油和汽油兩種動力模塊,但是具體的賬面參數和傳動系統的搭載暫且沒有消息。並且為了適應不同的城市訴求,上汽大通D90還將會推出純電動、插電式魂動、燃料電池助力等等多種的驅動方式。編者有話說:上汽大通D90的概念車外觀確實很漂亮,但是小編並不希望實車下地的時候上汽大通會重蹈長安CS95的覆轍。

2016年年底中國自主品牌汽車市場最為驚艷的車型,給廣汽傳祺GS8投上一票相信沒有什麼人會反對,漂亮霸氣的外觀造型,再配合上正式上市后相對親民的售價,讓很大一部分還在躊躇着合資緊湊型SUV的消費者轉向了國產的陣營。然而在剛剛結束的廣州車展上,還有一台SUV車型引起了小編的注意,如無意外,它或許是廣汽傳祺GS8的直接競爭對手——上汽大通D90。

同樣的驚艷,不同的性格

上汽大通D90現在僅僅是處於概念車階段,曝光於今年早些時候的北京車展,整體風格使用了大量的凌厲線條勾勒出運動感,與廣汽傳祺GS8的穩重中略帶粗獷的性格不同,上汽大通D90顯得更加的精緻而且富有活力。

內飾層面的設計顯得十分后現代,畢竟是一款概念車,所以參考價值在小編個人看來並不算太大,但是根據官方透露的消息,該車的配置會十分豐富,6安全氣囊,主動安全系統,自適應巡航等等配置一應俱全。

動力層面據悉會採用2.0T的柴油和汽油兩種動力模塊,但是具體的賬面參數和傳動系統的搭載暫且沒有消息。並且為了適應不同的城市訴求,上汽大通D90還將會推出純電動、插電式魂動、燃料電池助力等等多種的驅動方式。

編者有話說:

上汽大通D90的概念車外觀確實很漂亮,但是小編並不希望實車下地的時候上汽大通會重蹈長安CS95的覆轍。CS95的概念車發布的時候可謂是震驚中外,但是量產版本公布之時卻顯得有些平庸。

作為已經上市的車型,廣汽傳祺GS8可謂是樹立自主品牌中大型SUV全新的標杆,而上汽大通D90的售價預計也是在20萬元左右,同樣為多座位的布局方式,所以可以很自然的聯想到上汽大通D90與廣汽傳祺GS8形成的直接競爭關係,據悉上汽大通D90的量產車型在2017年9月就將正式上市,希望屆時所出現的車型是一款與概念車相比出入不那麼大的高顏值SUV。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

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

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

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

※別再煩惱如何寫文案,掌握八大原則!

網頁設計最專業,超強功能平台可客製化

※回頭車貨運收費標準

CHR還沒出生,繽智又多一個對手?全新小型SUV專利圖曝光

88-18。98萬本田XR-V指導價格:12。78-16。28萬豐田C-HR指導價格:暫

SUV產品的豐富並不是僅僅存在自主品牌當中,自本田推出繽智和XRV以來,豐田CHR的曝光量越來越大,而作為日系品牌在中國銷量也非常出色的日產,進來曝光了一款國內的小型SUV專利圖,這或許意味着,日產品牌旗下的小型SUV誕生之日就在不久的將來。

日產先期在2014年巴西聖保羅車展已經曝光一款小型SUV概念車——Nissan-Kicks,據悉在今年年底就會量產在拉丁美洲銷售,根據日產的計劃,這款小型SUV有投放全球計劃的構想,從曝光的專利圖上看,未來國產的日產品牌小型SUV非常有可能與Kicks衍生於同一平台。

Nissan—Kicks的外觀造型顯得非常精緻,四個輪拱處都使用了肌肉紋理,彰顯出了一定的力量感,而整車包圍裙邊顯得運動風格十足,色差較大的車身與車頂的差異讓kicks看上去更顯得年輕動感。

我們不難從專利圖上看出這款全新的日產小型SUV與kicks的相似程度極高,前臉造型較為犀利,整車線條同樣是以緊湊動感為主要設計語言,關於該車的動力、配置、等等消息暫時還沒有獲得較為確切的證據,但從定位上我們或許可以猜測其將會搭載與逍客相同的1.2T發動機。

競品車型:

本田繽智

指導價格:12.88-18.98萬

本田XR-V

指導價格:12.78-16.28萬

豐田C-HR

指導價格:暫本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

都是13萬能買到 這兩款大眾轎車誰更好

乘坐感受速騰雖然用上了后獨立懸架,但是舒適性方面還是有所欠缺,關鍵是當顛簸比較多的時候,會發現懸架有點處理不過來,餘震較多。但是有一點比較好,速騰的底盤滲進車廂內的細碎震動不多。另外有一點值得表揚的就是,後排空間較大且座墊的長度足夠。

前言速騰和朗逸可以說是大眾家族中的兩張銷量王牌,那麼在14萬左右想買這兩者中的一輛,該如何來選呢,下面編者我就來對比一下。

一汽-大眾-速騰

2017款 230TSI 自動舒適型

指導價:16.08萬

優惠幅度:3萬元左右(僅供參考)

上汽大眾-朗逸

2015款 230TSI DSG舒適版

指導價:14.69萬

優惠幅度:1.3萬左右(僅供參考)

駕駛感受

兩輛車的動力總成都一樣,均為1.4T發動機+7速DSG變速箱,最大輸出131馬力和225牛米。略有區別的是最大扭矩轉速,朗逸在1400轉便爆發,而速騰要到1500轉,不過兩者都在3500轉以後開始衰減。

懸架的選用上兩者也有很大差別,速騰后懸架採用的是多連桿獨立懸架,而朗逸用的是扭力梁式非獨立懸架。儘管如此,一切還是要從實際表現出發。

速騰的發動機艙

速騰的動力還是很充足的,無論平時超車,還是高速上開,都不會覺得車子有乏力的情況。變速箱的換擋動作也很平順,不會給駕駛員帶來什麼滋擾。然而,懸架總體來說還是偏軟,高速過彎時的側傾會比較大。轉向則略微有點重,特別是在不停挪車時,轉向的力度有點讓人受不了。

朗逸的發動機艙

朗逸的駕駛感受其實也和速騰大同小異,1.4T的動力隨傳隨到,不拖泥帶水。懸架抑制側傾的能力依舊有限,不算很出色。轉向上會比速騰來得略輕一點點。

乘坐感受

速騰雖然用上了后獨立懸架,但是舒適性方面還是有所欠缺,關鍵是當顛簸比較多的時候,會發現懸架有點處理不過來,餘震較多。但是有一點比較好,速騰的底盤滲進車廂內的細碎震動不多。另外有一點值得表揚的就是,後排空間較大且座墊的長度足夠。

朗逸的表現就沒有速騰那麼好了,首先座墊偏短,乘客的大腿大部分位置得不到有效承托。不過,座椅靠背角度較斜,坐進去感覺還是挺放鬆。其次就是,從底盤傳入車廂內的細碎震動較多,過大坑時會顯得較為單薄,幸好在過小顛簸時懂得如何去以柔克剛。

配置對比

這個環節可以說是兩者差別最大的,因為速騰的優惠幅度更高,所以配置會更為豐富。不僅多了無鑰匙啟動與進入,還多了不少配置,詳情可看下錶。

油耗對比

由於動力總成完全一致,且車身尺寸差距不大,所以兩者的油耗很相近。速騰的百公里綜合油耗為7.3L,而朗逸則為7.2L。

編者總結:

其實,速騰與朗逸這兩台車的調性是很相似的,都是偏居家的風格,而速騰在舒適性與配置上會優於朗逸。但是,每次看到速騰,我腦海中都閃現出一句歌詞“有多少愛可以重來”,曾經的速騰斷軸門讓大家都傷透了心。儘管現在速騰已更換了后獨立懸架,但是心中的芥蒂是否就此放下呢?如果已經放下了,那速騰確是一款適合家用的車。如若依舊介懷,就不必買輛車讓自己提心吊膽。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※回頭車貨運收費標準

台中搬家公司費用怎麼算?

3分鐘了解清楚持續集成、持續交付、持續部署

近些年來,持續集成、持續交付以及持續部署這幾個熱詞總是在大家的眼前晃來晃去!在招聘信息和面試過程中也會經常提及!在這裏我就用三分鐘時間來帶大家了解他們!

 

1. 持續集成(CI:Continuous Integration)

持續集成強調開發人員提交了新代碼之後,立刻進行構建然後進行單元測試。根據測試結果,我們可以確定新代碼和原有代碼能否正確地集成在一起。

注意:這裏的測試重點是指開發人員進行的代碼級別測試!

  

2. 持續交付(CD:Continuous Delivery)

 

持續交付在持續集成的基礎上,將集成后的代碼部署到更貼近真實運行環境的類生產環境中。如果測試沒有問題,可以繼續手動部署到生產環境中。

注意:這裏的測試重點是指測試人員進行的產品級別的測試!往往在這個測試過程中普遍都會引入測試腳本進行自動化回歸測試,主要是進行接口測試和UI測試,

當然部分公司也會引入安全測試和性能測試。持續交付能夠以較短地周期完成需求的小粒度頻繁交付。

頻繁的交付周期帶來了更迅速的對軟件的反饋,並且在這個過程中,各個角色密切協作,相比於傳統的瀑布式軟件團隊更少浪費資源。

 

 

3. 持續部署(CD:Continuous Deployment)

 

持續部署則是在持續交付的基礎上,把部署到生產環境的過程自動化。整個過程無需人工參与!

 

4. 總結

簡單地說:

  • 持續集成主要是在開發範圍,包括:構建>單元測試;
  • 持續交付涉及開發、測試、運維合作,包括:構建>單元測試>測試環境部署>測試(不涉及生產環境的自動化部署)
  • 持續部署是在持續交付的基礎上的延伸:包括:構建>單元測試>測試環境部署>測試>生產環境部署>生產環境測試(全流程自動化)

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

【其他文章推薦】

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

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

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

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※回頭車貨運收費標準

除了FastJson,你也應該了解一下Jackson(一)

在上月末的時候收到一條關於fastjson安全漏洞的消息,突然想到先前好像已經有好多次這樣的事件了(在fastjson上面)。關於安全方面,雖然中槍的機率微小,但是在這個信息越來越複雜的時代,安全性也變得越來越重要,就像DevSecOps的誕生,在軟件交付的整個價值流中我們也需要注重安全這方面。當然我們現在不談關於FastJson的優劣,因為我們本文的目標是讓大家了解和掌握Jackson。

概覽

Jackson是一個非常流行和高效的基於Java的庫,它可以序列化java對象或將java對象映射到JSON,反之亦然。當然除了Jackson,在Java中同類型的優秀的庫也有很多,比如:

  • Gson
  • json-io
  • Genson

關於哪一個最好或者哪一個最流行,沒有明確的答案。技術的種類繁多,每個人對與不同技術的態度也不一樣。言歸正傳,文章主要還是討論Jackson的。本文主要講解我們處理Json中最常見的兩個操作:

  • 將Java對象序列化為JSON
  • JSON字符串反序列化為Java對象

引入依賴

由於在Spring/SpringBoot中很多組件已經自帶了Jackson庫,所以很多情況下不需要手動引入Jackson的依賴。

手動引入依賴:

<dependency> 
  <groupId>com.fasterxml.jackson.core</groupId> 
  <artifactId>jackson-databind</artifactId> 
  <version>2.9.8</version>
</dependency>

這個依賴關係還將傳遞地向類路徑添加以下庫:

  1. jackson-annotations-2.9.8.jar
  2. jackson-core-2.9.8.jar
  3. jackson-databind-2.9.8.jar

JavaObject to Json

ObjectMapper

ObjectMapper是一個映射器(或數據綁定器或編解碼器),提供了在Java對象(bean的實例)和JSON之間進行轉換的功能。

首先定義一個簡單的Java類

public class Car {
    private String color;
    private String type;
    // standard getters setters
}

將Java對象轉換成Json

我們使用ObjectMapper的writeValue相關Api來對Java對象進行序列化操作

ObjectMapper objectMapper = new ObjectMapper();
Car car = new Car("blue","c1");
System.out.println(objectMapper.writeValueAsString(car));

此時輸出

{"color":"blue","type":"c1"}

更多

ObjectMapper的writeValue相關Api還提供了很多便利的Json序列化操作方法,比如:將對象序列化成Json字節數組的writeValueAsBytes()方法、自定義輸出源的writeValue()方法…

ObjectMapper objectMapper = new ObjectMapper();
Car car = new Car("blue","c1");
objectMapper.writeValue(new File("./xxx.txt"),car);

運行上述代碼,Java對象的序列化Json將被輸出到xxx.txt文件。

Json to JavaObject

將Json String轉換成Java Object

ObjectMapper objectMapper = new ObjectMapper();
String json = "{\"color\":\"blue\",\"type\":\"c1\"}";
Car car = objectMapper.readValue(json, Car.class);

readValue()方法也接受其他形式的輸入,比如包含JSON字符串的文件:

ObjectMapper objectMapper = new ObjectMapper();
Car car = objectMapper.readValue(new File("./xxx.txt"), Car.class);
System.out.println(car);

JSON to Jackson JsonNode

JsonNode

一個JSON可以被解析成一個JsonNode對象,用來從一個特定的節點檢索數據.

使用readTree()方法,我們可以將Json字符串轉換成JsonNode

ObjectMapper objectMapper = new ObjectMapper();
String json = "{ \"color\" : \"Black\", \"type\" : \"FIAT\" }";
JsonNode jsonNode = objectMapper.readTree(json);
System.out.println(jsonNode.findValue("type").asText());
// 打印出“FAIT”

JSONArrayString to JavaList

ObjectMapper objectMapper = new ObjectMapper();
String jsonCarArray =
    "[{ \"color\" : \"Black\", \"type\" : \"BMW\" }, { \"color\" : 3. \"Red\", \"type\" : \"FIAT\" }]";
List<Car> listCar = objectMapper.readValue(jsonCarArray, new TypeReference<List<Car>>() {});

JSONString to JavaMap

ObjectMapper objectMapper = new ObjectMapper();
String json = "{ \"color\" : \"Black\", \"type\" : \"BMW\" }";
Map<String, Object> map = objectMapper.readValue(json, new TypeReference<Map<String, Object>>() {
});

Jackson庫最大的優點之一是高度可定製的序列化和反序列化過程。接下來將介紹一些高級特性,其中輸入或輸出JSON響應可以與生成或使用響應的對象不同。

配置序列化和反序列化特性

String jsonString = "{ \"color\" : \"Black\", \"type\" : \"Fiat\", \"year\" :\"1970\" }";

假設使用如上json字符串來反序列化成Java對象,按照默認解析過程將導致UnrecognizedPropertyException異常,因為其中存在Car類中未包含的新字段year。

通過配置序列化和反序列化特性來解決此問題:

ObjectMapper objectMapper = new ObjectMapper();
String jsonString = "{ \"color\" : \"Black\", \"type\" : \"Fiat\", \"year\" :\"1970\" }";
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Car car = objectMapper.readValue(jsonString, Car.class);

如上,我們在ObjectMapper中配置了DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES=false,從而實現忽略新的字段。

類似:另一個選項FAIL_ON_NULL_FOR_PRIMITIVES,它定義了是否允許原始值的空值;FAIL_ON_NUMBERS_FOR_ENUM控制是否允許enum值被序列化/反序列化為数字……

自定義序列化器或反序列化器

自定義序列化器

public static class CustomCarSerializer extends StdSerializer<Car> {
    public CustomCarSerializer() {
        this(null);
    }

    public CustomCarSerializer(Class<Car> t) {
        super(t);
    }

    @Override
    public void serialize(Car car, JsonGenerator jsonGenerator, SerializerProvider serializer) throws IOException {
        jsonGenerator.writeStartObject();
        jsonGenerator.writeStringField("car_brand", car.getType());
        jsonGenerator.writeEndObject();
    }
}

如上,通過繼承StdSerializer類,我們實現了一個自定義的序列化器。

使用自定義的序列化器:

ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("CustomCarSerializer", new Version(1, 0, 0, null, null, null));
module.addSerializer(Car.class, new CustomCarSerializer());
mapper.registerModule(module);
Car car = new Car("yellow", "enault");
System.out.println(mapper.writeValueAsString(car));
//輸出{"car_brand":"enault"}

自定義反序列化器

public static class CustomCarDeserializer extends StdDeserializer<Car> {

        public CustomCarDeserializer() {
            this(null);
        }

        protected CustomCarDeserializer(Class<?> vc) {
            super(vc);
        }

        @Override
        public Car deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
            Car car = new Car();
            ObjectCodec codec = p.getCodec();
            JsonNode node = codec.readTree(p);
            // try catch block
            JsonNode colorNode = node.get("color");
            String color = colorNode.asText();
            car.setColor(color);
            return car;
        }
    }

如上,通過繼承StdDeserializer類,我們實現了一個自定義的序列化器。

使用自定義的反序列化器:

String json = "{ \"color\" : \"Black\", \"type\" : \"BMW\"}";
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("CustomCarDeserializer", new Version(1, 0, 0, null, null, null));
module.addDeserializer(Car.class, new CustomCarDeserializer());
mapper.registerModule(module);
Car car = mapper.readValue(json, Car.class);
//此時的car {color='Black', type='null'}

處理時間格式

️:此處僅展示對於Java8的LocalDate&LocalDateTime的處理

首先創建一個帶日期時間字段的Car類

public class Car {
    private String color;
    private String type;
  	@JsonFormat(pattern = "yyyy-MM-dd")
    private LocalDateTime produceTime;
    // standard getters setters
}

自定義時間格式處理

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.findAndRegisterModules();
Car car = new Car().setColor("blue").setType("c1").setProduceTime(LocalDateTime.now());
String carAsString = objectMapper.writeValueAsString(car);
System.out.println(carAsString);
//此時輸出:{"color":"blue","type":"c1","produceTime":"2020-06-06"}

處理集合

DeserializationFeature類提供的另一個小但有用的特性是能夠從JSON數組響應生成我們想要的集合類型。

String jsonCarArray = "[{ \"color\" : \"Black\", \"type\" : \"BMW\"}, { \"color\" : \"Red\", \"type\" : \"FIAT\"}]";
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY, true);
Car[] cars = objectMapper.readValue(jsonCarArray, Car[].class);

如上,我們將一個JsonArray字符串轉換成了對象數組。

我們也可以將其轉換成集合:

String jsonCarArray = "[{ \"color\" : \"Black\", \"type\" : \"BMW\"}, { \"color\" : \"Red\", \"type\" : \"FIAT\"}]";
ObjectMapper objectMapper = new ObjectMapper();
List<Car> listCar = objectMapper.readValue(jsonCarArray, new TypeReference<List<Car>>(){});

總結

Jackson是一個可靠而成熟的用於Java的JSON序列化/反序列化庫。ObjectMapper API提供了一種簡單的方法來解析和生成JSON響應對象,具有很大的靈活性。

歡迎訪問筆者博客:blog.dongxishaonian.tech

關注筆者公眾號,推送各類原創/優質技術文章 ⬇️

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

【其他文章推薦】

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

網頁設計最專業,超強功能平台可客製化

※產品缺大量曝光嗎?你需要的是一流包裝設計!

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

Kubernetes-PV和PVC的原理和實踐

一、什麼是PV和PVC?

PV的全稱是Persistent Volume,翻譯過來為持久化存儲卷,是對底層的共享存儲的一種抽象,PV由管理員進行創建和配置,主要含存儲能力、訪問模式、存儲類型、回收策略、後端存儲類型等主要信息,它和具體的底層的共享存儲技術的實現方式有關,比如NFS、Hostpath、RBD等。

PVC的全稱是: PersistenVolumeClaim (持久化卷聲明),PVC是用戶存儲的一種聲明,PVC和Pod類似,Pod是消耗節點node資源,PVC消耗的是PV資源,Pod可以請求CPU的內存,而PVC可以請求特定的存儲空間和訪問模式。

二、PV和PVC的使用場景

 

 

 配圖來自K8S權威指南第四版

 

 存儲工程師把分佈式存儲系統上的總空間劃分成一個一個小的存儲塊,K8S的集群管理員將存儲塊和PV進行一一對應,用戶通過PVC對對存儲進行申請,比如可以指定具體容量的大小,訪問模式或者存儲類型,這樣的好處是用戶不需要關心底層的存儲實現細節,只需要直接申請使用PVC即可,若申請的PVC所對應的PV不能滿足用戶的要求,不會生效,直到有合適的PV生成,PVC會自動與PV完成綁定,存儲工程師、K8S管理員,用戶之間業務解耦,靈活性更強。

 

三、創建PV

PV支持多種不同類型的存儲,如:NFS、hostpath、RBD、ICCSI,本文以hostpath為例介紹如何創建PV

第一步:現在宿主機data目錄下data/pod/volume1,volume1將作為PV對應的hostpath本地存儲的目錄

第二步:通過yaml文件創建PV

 1 [root@k8s-master zhanglei]# cat pv-hostpath.yaml  2 kind: PersistentVolume #指定為PV類型  3 apiVersion: v1  4 metadata:  5   name: pv-statefulset #指定PV的名稱  6  labels: #指定PV的標籤  7  release: stable  8 spec:  9  capacity: 10     storage: 0.1Gi #指定PV的容量 11  accessModes: 12     - ReadWriteOnce #指定PV的訪問模式,簡寫為RWO,只支持掛在1個Pod的讀和寫 13  persistentVolumeReclaimPolicy: Recycle #指定PV的回收策略,Recycle表示支持回收,回收完成后支持再次利用 14  hostPath: #指定PV的存儲類型,本文是以hostpath為例 15     path: /data/pod/volume1 #指定PV對應後端存儲hostpath的目錄

說明:

 accessModes支持多種訪問模式

1)ReadWriteOnce(RWO):讀寫權限,但是只支持掛載在1個Pod

2)ReadOnlyMany(ROX):只讀權限,支持掛載在多個Pod

3)ReadWriteMany(RW):讀寫權限,支持掛載在多個Pod上

persistentVolumeReclaimPolicy的策略,指的是如果PVC被釋放掉后,PV的處理,這裏所說的釋放,指的是用戶刪除PVC后,與PVC對應的PV會被釋放掉,PVC個PV是一一對應的關係

1)Retain,PV的數據不會清理,會保留volume,如果需要清理,需要手動進行

2)Recycle,會將數據進行清理,即 rm -rf /thevolume/*(只有 NFS 和 HostPath 支持),清理完成后,PV會呈available狀態,支持再次的bound

3)Delete,刪除存儲資源,會刪除PV及後端的存儲資源,比如刪除 AWS EBS 卷(只有 AWS EBS, GCE PD, Azure Disk 和 Cinder 支持)

四、創建PVC

[root@k8s-master zhanglei]# cat pvc-hostpath.yaml 
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mppvc-01                  # 指定PVC的名稱
  namespace: default
spec:
 accessModes: ["ReadWriteOnce"]    # 指定PVC的訪問模式
 resources:
   requests: 
     storage: 0.05Gi               # PVC申請的容量

說明:

1)PVC聲明了accessModes訪問類型為ReadWriteOnce,創建后,系統會自動去找能夠支持ReadWriteOnce訪問類型的PV,若無符合條件的PV,則不會進行綁定,

2)PVC聲明了storage的大小為0.05Gi,創建后,系統會自動去找能夠支持此容量的PV,通常PV的容量至少要大於或者等於0.05Gi才會去進行綁定

從這裏來看,對於用戶來說,即只需要聲明訪問類型、容量、另外還可通過StorageClass聲明具體的PV類型即可完成對持久化存儲卷的申請,而不需要去維護和關注後端存儲

五、查詢PV和PVC的

經過前面的步驟我們創建了PV和PVC,現在來看下兩者是否已經完成了綁定,在PV的創建已經指定了其名稱為pv-statefulset,PVC的名稱為mppvc-01

 

 

[root@k8s-master zhanglei]# kubectl get pv |grep pv-statefulset
pv-statefulset      107374182400m   RWO            Recycle          Bound    default/mppvc-01 

 

[root@k8s-master zhanglei]# kubectl get pvc |grep mppvc-01
mppvc-01                            Bound    pv-statefulset      107374182400m   RWO                           13d

可以看到pv-statefulset這個PV已經和mppvc-01的PVC進行了綁定(Bound),RWO和Recycle也是之前PV和PVC聲明的狀態,說明綁定成功


[root@k8s-master zhanglei]# kubectl describe pv pv-statefulset
Name:            pv-statefulset
Labels:          release=stable
Annotations:     pv.kubernetes.io/bound-by-controller: yes
Finalizers:      [kubernetes.io/pv-protection]
StorageClass:    
Status:          Bound
Claim:           default/mppvc-01
Reclaim Policy:  Recycle
Access Modes:    RWO
VolumeMode:      Filesystem
Capacity:        107374182400m
Node Affinity:   <none>
Message:         
Source:
    Type:          HostPath (bare host directory volume)
    Path:          /data/pod/volume1
    HostPathType:  
Events:            <none>
[root@k8s-master zhanglei]# kubectl describe pvc mppvc-01
Name:          mppvc-01
Namespace:     default
StorageClass:  
Status:        Bound
Volume:        pv-statefulset
Labels:        <none>
Annotations:   pv.kubernetes.io/bind-completed: yes
               pv.kubernetes.io/bound-by-controller: yes
Finalizers:    [kubernetes.io/pvc-protection]
Capacity:      107374182400m
Access Modes:  RWO
VolumeMode:    Filesystem
Mounted By:    <none>
Events:        <none>

再來看下PV的詳細(describe)信息,可以看到type是hostpath類型,显示了數據卷在宿主機的/data/pod/volume1的目錄。

 

六、總結

創建PV和PVC分為二步:

第一步:創建PV,支持自定義不同的存儲大小和訪問模式(RWX,RWO)、存放路徑、後端服務server(如hostpath、或NAS盤的數據盤的掛載點)

第二步:創建PVC,綁定到PV。創建PVC的時候可以指定PVC的request storage,即申請的存儲的容量,會根據申請的storage和訪問模式自動匹配符合要求的PV

創建完PV和PVC主要是為了使用它來達到實現持久化存儲的目的,如何進行使用請看本作者後續與statufulset有關的文章,謝謝閱讀~

 

作者簡介:雲計算容器\K8S方向產品經理,學點技術,為更好地設計產品。

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

【其他文章推薦】

※教你寫出一流的銷售文案?

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

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※推薦台中搬家公司優質服務,可到府估價

機器學習——十大數據挖掘之一的決策樹CART算法

本文始發於個人公眾號:TechFlow,原創不易,求個關注

今天是機器學習專題的第23篇文章,我們今天分享的內容是十大數據挖掘算法之一的CART算法。

CART算法全稱是Classification and regression tree,也就是分類回歸樹的意思。和之前介紹的ID3和C4.5一樣,CART算法同樣是決策樹模型的一種經典的實現。決策樹這個模型一共有三種實現方式,前面我們已經介紹了ID3和C4.5兩種,今天剛好補齊這最後一種。

算法特點

CART稱為分類回歸樹,從名字上我們也看得出來,它既能支持分類又可以支持回歸。的確如此,決策樹的確支持回歸操作,但是我們一般不會用決策樹來進行回歸。這裏面的原因很多,除了樹模型擬合能力有限效果不一定好之外,還與特徵的模式有關係,樹回歸模型受到特徵的影響非常大。這個部分我們不做太多深入,之後會在回歸樹的文章當中詳細探討。

正因為回歸樹模型效果表現都不太理想,所以CART算法實現決策樹基本都是用來做分類問題。那麼在分類問題上,它與之前的ID3算法和C4.5算法又有什麼不同呢?

主要細究起來大約有兩點,第一點是CART算法使用Gini指數而不是信息增益來作為劃分子樹的依據,第二點是CART算法每次在劃分數據的時候,固定將整份數據拆分成兩個部分,而不是多個部分。由於CART每次將數據拆分成兩個部分,所以它對於拆分的次數沒有限制,而C4.5算法對特徵進行了限制,限制了每個特徵最多只能使用一次。因為這一點,同樣CART對於剪枝的要求更高,因為不剪枝的話很有可能導致樹過度膨脹,以至於過擬合。

Gini指數

在ID3和C4.5算法當中,在拆分數據的時候用的是信息增益和信息增益比,這兩者都是基於信息熵模型。信息熵模型本身並沒有問題,也是非常常用的模型。唯一的問題是,在計算熵的時候需要涉及到log運算,相比於四則運算來說,計算log要多耗時很多

Gini指數本質上也是基於信息熵模型,只是我們在計算的時候做了一些轉化,從而避免了使用log進行計算,加速了計算的過程。兩者的內在邏輯是一樣的。那怎麼實現的加速計算呢?這裏用到了高等數學當中的泰勒展開,我們將log運算通過泰勒公式展開,轉化成多項式的計算,從而加速信息熵的計算。

我們來做一個簡單的推導:

\[\begin{aligned} \ln(x) \approx \ln(x_0) + (x-x_0)\ln'(x_0) + o(x) \end{aligned} \]

我們把\(x_0 =1\)代入,可以得到:\(\ln(x)=x – 1 + o(x)\),其中o(x)是關於x的高階無窮小。我們把這個式子套入信息熵的公式當中:

\[\begin{aligned} H(x) &= -\sum_{i=1}^k p_i\ln p_i \\ &\approx \sum_{i=1}^k p_i(1-p_i) \end{aligned} \]

這個就是Gini指數的計算公式,這裏的pi表示類別i的概率,其實就是類別i的樣本佔全體樣本的比例。那麼上面的式子也可以看成是從數據集當中抽取兩條樣本,它們類別不一致的概率。

因此Gini指數越小,說明數據集越集中,也就是純度越高。它的概念等價於信息熵,熵越小說明信息越集中,兩者的概念是非常近似的。所以當我們使用Gini指數來作為劃分依據的時候,選擇的是切分之後Gini指數盡量小的切分方法,而不是盡量大的。

從上面的公式當中,我們可以發現相比於信息熵的log運算,Gini指數只需要簡單地計算比例和基礎運算就可以得到結果了,顯然運算速度要快得多。並且由於是通過泰勒展開逼近的,整體的性能也並不差,我們可以看下下面這張經典的圖感受一下:

從上圖當中可以看出來,Gini指數和信息熵的效果非常接近,一樣可以非常好地反應數據劃分的純度。

拆分與剪枝

剛才我們介紹CART算法特性的時候提到過,CART算法每次拆分數據都是二分的,這點和C4.5處理連續性特徵的邏輯很像。但有兩點不同,第一點是CART對於離散型和連續性特徵都如此操作,另外一點是,CART算法當中一個特徵可以重複使用。

舉個例子,在之前的算法當中,比如說西瓜的直徑是一個特徵。那麼當我們判斷過西瓜的直徑小於10cm之後,西瓜的直徑這個特徵就會從數據當中移除,之後再也不會用到。但是在CART算法當中不是如此,比如當我們先後根據西瓜的直徑以及西瓜是否有藤這兩個特徵對數據進行拆分之後,對於ID3和C4.5算法來說,西瓜的直徑這個特徵已經不可以再用來作為劃分的依據了,但是CART算法當中可以,我們仍然可以繼續使用之前已經用過的特徵。

我們用一張圖來展示,大概是下面這個樣子:

我們觀察一下最左側的子樹,直徑這個特徵出現了不止一次,這其實是很合理的。然而這也會有一個問題,就是由於沒有了特徵只能用一次這個限制,這樣會導致這棵樹無限膨脹,尤其是在連續性特徵很多的情況下,很容易陷入過擬合。為了放置過擬合,增加模型的泛化能力,我們需要對生成的這棵樹進行剪枝。

剪枝的方案主流的有兩種,一種是預剪枝,一種是后剪枝。所謂的預剪枝,即是在生成樹的時候就對樹的生長進行限制,防止過度擬合。而後剪枝則是在樹已經生成之後,對過擬合的部分進行修剪。其中預剪枝比較容易理解,比如我們可以限制決策樹在訓練的時候每個節點的數據只有在達到一定數量的情況下才會進行分裂,否則就成為恭弘=叶 恭弘子節點保留。或者我們可以限制數據的比例,當節點中某個類別的佔比超過閾值的時候,也可以停止生長。

后剪枝相對來說複雜一些,需要我們在生成樹之後通過一些機制尋找可以剪枝的部分,對整棵樹進行修剪。比如在CART算法當中常用的剪枝策略是CCP,它的英文全寫是Cost-Complexity Pruning,即代價複雜度剪枝。這個策略設計了一個指標來衡量一棵子樹的複雜度代價,我們可以對這個代價設置閾值來進行剪枝。

這個策略的精髓在於下面這個式子:

\[c = \frac{R(t) – R(T_t)}{|N_t| – 1} \]

這個式子當中的c就是指的剪枝帶來的代價,t代表剪枝之後的子樹,\(T_t\)表示剪枝之前的子樹。R(t)表示剪枝之後的誤差代價\(R(T_t)\)表示剪枝之前的誤差代價。其中誤差代價的定義是:\(R(t) = r(t) * p(t)\),r(t)是節點t的誤差率,p(t)是t上數據占所有數據的比例。

我們來看個例子:

假設我們知道所有數據一共有100條,那麼我們代入公式算一下,可以得到\(R(t) = r(t) * p(t) = \frac{11}{23} * \frac{23}{100} = \frac{11}{100}\)

子樹的誤差代價是:

\[R(T_t) = \sum R(i)=(\frac{2}{6}*\frac{6}{100})+ (\frac{0}{3}*\frac{3}{100}) + (\frac{2}{8}*\frac{8}{100})=\frac{4}{100} \]

所以可以得到\(c=\frac{11/100 – 4/100}{3 – 1}=\frac{7}{200}\)

c越大說明剪枝帶來的偏差越大,也就是說越不能剪,相反c很小說明偏差不大,可以減掉。我們只需要設置閾值,然後計算每一棵子樹的c來判斷是否能夠剪枝即可。

代碼實現

我們之前已經實現過了C4.5算法,再來實現CART可以說是非常簡單了,因為它相比於C4.5還少了離散類型這種情況,可以全部當做是連續型類型來處理。

我們只需要把之前的信息增益比改成Gini指數即可:

from collections import Counter

def gini_index(dataset):
    dataset = np.array(dataset)
    n = dataset.shape[0]
    if n == 0:
        return 0
    # sigma p(1-p) = 1 - sigma p^2
    counter = Counter(dataset[:, -1])
    ret = 1.0
    for k, v in counter.items():
        ret -= (v / n) ** 2
    return ret

def split_gini(dataset, idx, threshold):
    left, right = [], []
    n = dataset.shape[0]
    # 根據閾值拆分,拆分之後計算新的Gini指數
    for data in dataset:
        if data[idx] < threshold:
            left.append(data)
        else:
            right.append(data)
    left, right = np.array(left), np.array(right)
   	# 拆分成兩半之後,乘上所佔的比例
    return left.shape[0] / n * gini_index(left) + right.shape[0] / n * gini_index(right)

然後選擇拆分的函數稍微調整一下,因為Gini指數越小越好,之前的信息增益和信息增益比都是越大越好。代碼的框架基本上也沒有變動,只是做了一些微調:

def choose_feature_to_split(dataset):
    n = len(dataset[0])-1
    m = len(dataset)
    # 記錄最佳Gini,特徵和閾值
    bestGini = 1.0
    feature = -1
    thred = None
    for i in range(n):
        threds = get_thresholds(dataset, i)
        for t in threds:
            # 遍歷所有的閾值,計算每個閾值的信息增益比
            ratio = split_gini(dataset, i, t)
            if ratio < bestGini:
                bestGini, feature, thred = ratio, i, t
    return feature, thred

建樹和預測的部分都和之前C4.5算法基本一致,只需要去掉離散類型的判斷即可,大家可以參考一下之前文章當中的代碼。

總結

到這裏,我們關於決策樹模型的內容就算是結束了,我們從基本的決策樹原理,再到ID3、C4.5以及CART算法,都已經囊括了。這些知識儲備足以應對面試當中關於決策樹模型的問題了

雖然在實際的生產過程當中,我們已經用不到決策樹了,還不是基本用不到,幾乎是完全用不到。但是它的思想非常重要,是後續很多模型的基礎,比如隨機森林、GBDT等模型,都是在決策樹的基礎上建立起來的。所以我們深入理解決策樹的原理對於我們後續的進階學習非常重要。

最後, 我把完整的代碼發在了paste.ubuntu上,需要的同學可以在公眾號後台回復“決策樹”獲取。

如果喜歡本文,可以的話,請點個關注,給我一點鼓勵,也方便獲取更多文章。

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

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

※產品缺大量曝光嗎?你需要的是一流包裝設計!

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

網頁設計最專業,超強功能平台可客製化

從“中英文思維回譯法”看中英思維差異

1、中英文在表達意思的方式上的不同 

中英文表達的差異很重要一部分體現在思維上。例如中文說:

“不知道怎麼回事,我筆記本連不上網了”

地道的英文不是:

My laptop can’t connect to the internet for no reason.

而是:

I couldn’t access the internet from my laptop for some reason.

這個句子至少體現了以下幾處中英文思維的不同:

  • 中文用“我的電腦”做主語,英語用“我”做主語。
  • “上網”是 access the internet 而不是 connect to the internet.
  • 不知道怎麼回事是 for some reason 而不是 for no reason.
  • 中文先說“不知道怎麼回事”,英文後說 for some reason.

 

2、中英文思維的差異

作為跨文化交流的翻譯不僅只是兩種語言的簡單轉換,也是兩種思維方式之間的轉換。 拿英漢兩種語言來說.由於文化背景的差異等諸多因素,這兩種語言的思維方式也不盡相同,概括來說主要就是:

  • 句子重心的差異
  • 語態的差異
  • 否定視角的差異

0x1:句子重心的差異

句子重心包括兩個層面:

  • 一個是語義重心
  • 另一個是結構重心

英語和漢語的語義重點基本沒有大的區別,這兩種語言句子重心的差異主要體現在結構重心的差異上。

西方人說話喜歡直截了當、開門見山,他們在表達思想時喜歡先安排主要內容,然後再藉助各種連接手段補充其他信息,因而英語句子的結構重心通常在前,句子常常頭短尾長。

而中國人則喜歡水到渠成、漸入佳境。所以他們在表達思想時喜歡先對所述的事物進行鋪墊和渲染,然後再點出中心意思,因而漢語句子的結構重心往往靠後,句子頭長尾短。

我們來看下面的例句:

我們再來看一個例句:

這句話的結構重心為後半句“許多小船載着各種雜貨快速向客輪駛來”。通過上面的分析,這句話在翻譯時應首先將結構重心置前,先說主要內容:

0x2:語態的差異

雖然英漢兩種語言在句式上都有主動和被動之分,但表現形式和使用場合卻不盡相同。

  • 英語中被動語態的使用範圍非常廣,除了大量的及物動詞使用被動語態外,不少相當於及物動詞的短語也可以使用被動語態。
  • 相比之下,漢語中被動語態的使用就低得多了,大多數情況下漢語都使用主動形式來表達被動意義。

以“He is loved by all of us.”這句話為例,這句話在英語中司空見慣,而如果要譯成“他被我們大家喜歡着”就很彆扭,必須要使用主動語態,譯成“我們大家都喜歡他”才通順。

我們來看下面的例句:

 

被動語態能使句子的核心詞“an interesting discovery”更為突出。

我們再來看一個例句:

中文原句是主動語態,但由於“開挖隧道”和“派狗到雪地里”這兩個動作的發出者在中文里並沒有出現,所以在譯成英語時就只能使用被動語態了。

中文中有大量的無主句,在將這些句子譯成英文時都可以使用被動語態。

0x3:否定視角的差異

英語的否定方式非常豐富,大體可分為:

  • 顯形否定:通常是藉助否定詞或,含否定意義的句型來表達(如never、unless、hardly、rather than、in the absence of、too …. to …)
  • 隱形否定:沒有明顯的否定詞,其形式上是否定的,它的否定含義要通過上下文或語境來推定

與英語比起來,漢語的否定結構就單一多了,一般是在相應的詞語前面加上“不”、“沒有”等進行“詞的否定”。

所以,在漢譯英的過程中,對於漢語中相對單一的否定結構,英文中可以有多種不同的表達方式。

我們來看下面的例句:

可以看到,譯文採用了隱形否定的形式來表達。

我們再來看一個例句:

 

中文原文是肯定句,而譯文中使用了否定結構“never fail to”不僅表達了肯定的意思,還顯得更為精彩,更有力量。

3、中英文回譯法

在翻譯練習中,有一種非常有效翻譯方法叫“回譯法”,也就是對譯文進行再次翻譯后與原文進行對照,在對比中找到差異所在。 概括來說就是:

  • 從地道英語直譯成中文
  • 由直譯的中文再譯成地道的中文
  • 仔細分析記錄地道英語和地道中文的區別
  • 由地道中文再回譯成地道英文

0x1:英譯中 – 從 A 到 B

我們先徹底理解英文原文的意思,然後嘗試着翻譯成中文。如果覺得直譯很彆扭的話,那就說明這裡有值得一學的英語思維! 在這一步,我們要做到讓中文譯文符合英文原文的表述,盡量直譯,基本通順即可。 舉個例子: 一位商務人士因為電腦有問題連不上網,沒能及時回復重要郵件,等修好了,他趕緊給別人發郵件解釋,郵件開頭是這麼說的:  

這句話直譯過來就是:

我很抱歉沒能更早回復你,出於某些原因,我用筆記本電腦無法連上網。

0x2:重建中文 – 從 B 到 C

這一步,我們要引入中文思維,展開想象:

在真實的中文語境中,相同的場景中,人們會怎麼說這句話?

這一步需要你完全拋棄英文原文的影響,去想中國人真正會使用出來的中文,不能有任何的翻譯痕迹。你可以假象在同樣的場景中,你自己會如何表達。例如:

不好意思啊,這麼晚回復你,我的本子一直連不上網,不知道怎麼回事。

到這一步我們發現,經過重建的中文,已經和之前的譯文在表達順序和結構上有明顯區別了。

0x3:連接通過 – 找到 A 和 C 之間的關聯

這一步也叫“埋鈎子”,就是說,埋下一些幫助記憶的“小鈎子”,把兩種思維掛上,讓自己以後能回想起來。

我們要觀察的內容主要是三方面:

  • 多了什麼?
  • 少了什麼?
  • 改了什麼?

比如:

  • 句子結構是怎麼變的?
  • 哪些詞被捨棄了?
  • 哪些表達式被徹底替換了?
  • 為什麼有些信息乾脆就不說了?
  • 有些信息反而要被加上去?

以這句話為例,我會注意到,

  • 我們中國人通常說的“這麼晚回復你”,英文的表達思路不一定用so late,還可以用not sooner,即否定句式。
  • 連上網,這句話里用的是access這個詞,而不是我們更熟悉的connect to。
  • 句子的主語也變了,一般中文會說“我的筆記本連不上網”,主語是筆記本,但是英文原文的說法是“i coudn’t access the internet”,主要是 I,也就是我。是我沒法通過本子來連網,而不是說本子連不上網。

0x4:思維逆轉 – 把 C 譯回 A

最後一步,我們要把整個過程逆推回去,在只看到 C 的情況下,回想起 A 是怎麼說的。如果你能做到準確完成回譯,說明你已經成功地建立了通道,從中文思維跳到了英文思維。

 

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

【其他文章推薦】

網頁設計最專業,超強功能平台可客製化

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

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

※教你寫出一流的銷售文案?

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家公司費用怎麼算?

Spring 源碼學習 – 單例bean的實例化過程

本文作者:geek,一個聰明好學的同事

1. 簡介

開發中我們常用@Commpont,@Service,@Resource等註解或者配置xml去聲明一個類,使其成為spring容器中的bean,以下我將用從源碼角度看以AnnotationConfigApplicationContext為例看spring如何把帶有註解的類生成spring中bean。

2. 示例代碼

public class TestContext {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
		SingleBean singleBean = context.getBean(SingleBean.class);
		System.out.println("<=====>"+singleBean.getTestStr());
	}
}
@ComponentScan("com.geek")
public class AppConfig {
}
@Component
public class SingleBean {
	private String testStr = "testStr";

	public String getTestStr() {
		return testStr;
	}
}

注意:以上代碼僅需要引入spring-context依賴即可。

3. 源碼分析

​ 上面的demo在調用AnnotationConfigApplicationContext構造函數的時候,AppConfig類會被註冊到AnnotatedBeanDefinitionReader,由這個reader把AppConfig解釋為beanDefination,從而被spring獲取到要實例化的類信息,以下為bean生產的源碼及其註釋。(源碼基於springFramework 5.1.X)

3.1 創建入口

​ 單例bean的創建的入口為DefaultListableBeanFactory.java#preInstantiateSingletons,下面源碼可見創建的bean條件為非抽象,非@LazyInit註解,Scope為singleTon(默認為singleTon)。

@Override
	public void preInstantiateSingletons() throws BeansException {
		if (logger.isTraceEnabled()) {
			logger.trace("Pre-instantiating singletons in " + this);
		}
		// Iterate over a copy to allow for init methods which in turn register new bean definitions.
		// While this may not be part of the regular factory bootstrap, it does otherwise work fine.
		//所有可能需要去實例化的class(lazy,scope)
		List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

		// Trigger initialization of all non-lazy singleton beans...
		for (String beanName : beanNames) {
			RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
			/**
			 * 非抽象,非懶初始化,單例bean
			 */
			if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
				if (isFactoryBean(beanName)) {
					Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
					if (bean instanceof FactoryBean) {
						final FactoryBean<?> factory = (FactoryBean<?>) bean;
						boolean isEagerInit;
						if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
							isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
											((SmartFactoryBean<?>) factory)::isEagerInit,
									getAccessControlContext());
						}
						else {
							isEagerInit = (factory instanceof SmartFactoryBean &&
									((SmartFactoryBean<?>) factory).isEagerInit());
						}
						if (isEagerInit) {
							getBean(beanName);
						}
					}
				}
				//非工廠bean實例化
				else {
					getBean(beanName);
				}
			}
		}
		/**
		 * 實例化完成后觸發實現了SmartInitializingSingleton方法的bean
		 * 的afterSingletonsInstantiated方法
 		 */
		// Trigger post-initialization callback for all applicable beans...
		for (String beanName : beanNames) {
			Object singletonInstance = getSingleton(beanName);
			if (singletonInstance instanceof SmartInitializingSingleton) {
				final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
				if (System.getSecurityManager() != null) {
					AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
						smartSingleton.afterSingletonsInstantiated();
						return null;
					}, getAccessControlContext());
				}
				else {
					smartSingleton.afterSingletonsInstantiated();
				}
			}
		}
	}

3.2 創建前doGetBean代碼邏輯

getBean方法進來后便是直接調用doGetBean,doGetBean執行的源碼解釋如下:
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
			@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
		/**
		 * 獲取beanName
		 * 1,去掉factortoryBean前綴&
		 * 2,帶有別名的bean轉換為原來名字
		 */
		final String beanName = transformedBeanName(name);
		Object bean;
		// Eagerly check singleton cache for manually registered singletons.
		/**
		 * 從DefaultSingletonBeanRegistry的singletonObjects
		 * (spring內部用來緩存單例bean的currentHashMap)檢查是否存在該bean,不存在則創建
		 * 涉及單例模式下的循環依賴解決
		 */
		Object sharedInstance = getSingleton(beanName);
		if (sharedInstance != null && args == null) {
			if (logger.isTraceEnabled()) {
				if (isSingletonCurrentlyInCreation(beanName)) {
					logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
							"' that is not fully initialized yet - a consequence of a circular reference");
				}
				else {
					logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
				}
			}
			/**
			 *獲取給定bean實例的對象,如果是Factory Bean,
			 * 則可以是bean實例本身或其創建的對象。
			 */
			bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
		}
		else {
			// Fail if we're already creating this bean instance:
			// We're assembly within a circular reference.
			/**
			 * 原型模式下的bean存在循環依賴則會拋異常
			 */
			if (isPrototypeCurrentlyInCreation(beanName)) {
				throw new BeanCurrentlyInCreationException(beanName);
			}
			// Check if bean definition exists in this factory.
			/**
			 * 找不到則從父容器中查找
			 */
			BeanFactory parentBeanFactory = getParentBeanFactory();
			if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
				// Not found -> check parent.
				String nameToLookup = originalBeanName(name);
				if (parentBeanFactory instanceof AbstractBeanFactory) {
					return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
							nameToLookup, requiredType, args, typeCheckOnly);
				}
				else if (args != null) {
					// Delegation to parent with explicit args.
					return (T) parentBeanFactory.getBean(nameToLookup, args);
				}
				else if (requiredType != null) {
					// No args -> delegate to standard getBean method.
					return parentBeanFactory.getBean(nameToLookup, requiredType);
				}
				else {
					return (T) parentBeanFactory.getBean(nameToLookup);
				}
			}
			if (!typeCheckOnly) {
				markBeanAsCreated(beanName);
			}
			try {
				/**
				 * 父容器中也找不到該bean,則需要重新實例化
				 * 1,獲取要實例化bean的beanDefinition,
				 * 2,檢查bean的實例化需要依賴的其他bean
				 */
				final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
				checkMergedBeanDefinition(mbd, beanName, args);

				// Guarantee initialization of beans that the current bean depends on.
				String[] dependsOn = mbd.getDependsOn();
				if (dependsOn != null) {
					for (String dep : dependsOn) {
						/**
						 * 若給定的依賴 bean 已經註冊為依賴給定的bean
						 */
						if (isDependent(beanName, dep)) {
							throw new BeanCreationException(mbd.getResourceDescription(), beanName,
									"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
						}
						registerDependentBean(dep, beanName);
						try {
							/**
							 * 遞歸調用getBean,優先創建依賴的bean
							 */
							getBean(dep);
						}
						catch (NoSuchBeanDefinitionException ex) {
							throw new BeanCreationException(mbd.getResourceDescription(), beanName,
									"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
						}
					}
				}
				// Create bean instance.
				if (mbd.isSingleton()) {
					/**
					 * 通過調用DefaultSingletonBeanRegistry的getSingleton,從而調用核心方法createBean創建
					 */
					sharedInstance = getSingleton(beanName, () -> {
						try {
							return createBean(beanName, mbd, args);
						}
						catch (BeansException ex) {
							// Explicitly remove instance from singleton cache: It might have been put there
							// eagerly by the creation process, to allow for circular reference resolution.
							// Also remove any beans that received a temporary reference to the bean.
							destroySingleton(beanName);
							throw ex;
						}
					});
					bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
				}

				else if (mbd.isPrototype()) {
					// It's a prototype -> create a new instance.
					Object prototypeInstance = null;
					try {
						beforePrototypeCreation(beanName);
						prototypeInstance = createBean(beanName, mbd, args);
					}
					finally {
						afterPrototypeCreation(beanName);
					}
					bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
				}

				else {
					String scopeName = mbd.getScope();
					final Scope scope = this.scopes.get(scopeName);
					if (scope == null) {
						throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
					}
					try {
						Object scopedInstance = scope.get(beanName, () -> {
							beforePrototypeCreation(beanName);
							try {
								return createBean(beanName, mbd, args);
							}
							finally {
								afterPrototypeCreation(beanName);
							}
						});
						bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
					}
					catch (IllegalStateException ex) {
						throw new BeanCreationException(beanName,
								"Scope '" + scopeName + "' is not active for the current thread; consider " +
								"defining a scoped proxy for this bean if you intend to refer to it from a singleton",
								ex);
					}
				}
			}
			catch (BeansException ex) {
				cleanupAfterBeanCreationFailure(beanName);
				throw ex;
			}
		}
		/**
		 * 檢查需要的bean類型是否符合
		 */
		// Check if required type matches the type of the actual bean instance.
		if (requiredType != null && !requiredType.isInstance(bean)) {
			try {
				T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);
				if (convertedBean == null) {
					throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
				}
				return convertedBean;
			}
			catch (TypeMismatchException ex) {
				if (logger.isTraceEnabled()) {
					logger.trace("Failed to convert bean '" + name + "' to required type '" +
							ClassUtils.getQualifiedName(requiredType) + "'", ex);
				}
				throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
			}
		}
		return (T) bean;
	}

doGetBean的操作流程如下:

1,執行transformedBeanName方法轉換beanName

​ 傳遞的參數可能是bean的alias或者為FactoryBean,transformedBeanName執行的操作:(1)若傳進來的FactoryBean(FactoryBean以&作為前綴標記),去掉&修飾符。(2)經過(1)的處理后,有alias的bean則從aliasMap中獲取bean的原始beanName。

2,從容器的緩存中獲取bean

​ getSingleton先從spring三級緩存中的第一級singletonObjects(Map結構)中獲取,若不存在,則檢查該bean是否正在創建isSingletonCurrentlyInCreation(beanName)) ,正在創建的bean會從二級緩存earlySingletonObjects(Map結構)獲取。獲取到緩存的bean後會調用AbstractBeanFactory#getObjectForBeanInstance轉換bean的實例本身返回。因為從緩存中拿到的可能是factoryBean,所以getObjectForBeanInstance需要把是通過從緩存factoryBeanObjectCache獲取或通過factory.getObject()獲得相應的bean返回。

3,bean實例化前檢查

​ (1)先檢查是否原型模式下的bean是否存在循環依賴,是則會拋異常。

​ (2)檢查父類工廠(parentBeanFactory)是否存在,存在則從parentBeanFactory中遞歸調用doGetBean。

​ (3)獲取改bean的beanDefinition,檢查該bean實例化過程中是否涉及依賴了其他的bean,若是則遞歸調用getBean,優先創建依賴的bean。(涉及單例下的循環以來解決,下篇文章詳細介紹)。

​ (4)對創建bean代碼加synchronized和執行beforeSingletonCreation(beanName)前置處理。

3.3 創建前createBean邏輯

​ 經過前面的doGetBean的一輪檢查與準備后,便在AbstractAutowireCapableBeanFactory#createBean中開始bean的創建。

/**
	 * Central method of this class: creates a bean instance,
	 * populates the bean instance, applies post-processors, etc.
	 * 解析指定 BeanDefinition 的 class
	 * 處理 override 屬性
	 * 實例化的前置處理
	 * 創建 bean
	 * @see #doCreateBean
	 */
	@Override
	protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {

		if (logger.isTraceEnabled()) {
			logger.trace("Creating instance of bean '" + beanName + "'");
		}
		RootBeanDefinition mbdToUse = mbd;

		// Make sure bean class is actually resolved at this point, and
		// clone the bean definition in case of a dynamically resolved Class
		// which cannot be stored in the shared merged bean definition.
		/**
		 * 解釋bean的class,看beanDefinition是否有class,否則load class
		 */
		Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
		if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
			mbdToUse = new RootBeanDefinition(mbd);
			mbdToUse.setBeanClass(resolvedClass);
		}
		/**
		 * 對bean不存在lookup-method 和 replace-method
		 * 標記其方法的overloaded為false
		 */
		// Prepare method overrides.
		try {
			mbdToUse.prepareMethodOverrides();
		}
		catch (BeanDefinitionValidationException ex) {
			throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
					beanName, "Validation of method overrides failed", ex);
		}

		try {
			/**
			 * 調用實現實現BeanPostProcessor的bean後置處理生成代理對象,
			 * 有代理對象則直接返回代理對象
			 */
			// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
			Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
			if (bean != null) {
				return bean;
			}
		}
		catch (Throwable ex) {
			throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
					"BeanPostProcessor before instantiation of bean failed", ex);
		}

		try {
			/**
			 * 無需代理的bean實例化
			 */
			Object beanInstance = doCreateBean(beanName, mbdToUse, args);
			if (logger.isTraceEnabled()) {
				logger.trace("Finished creating instance of bean '" + beanName + "'");
			}
			return beanInstance;
		}
		catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
			// A previously detected exception with proper bean creation context already,
			// or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.
			throw ex;
		}
		catch (Throwable ex) {
			throw new BeanCreationException(
					mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
		}
	}

createBean的操作流程如下:

1,resolveBeanClass

​ 解釋beanDefinition的class,並且保存在beanDefinition中。

2,prepareMethodOverrides

​ 處理bean中的lookup-method (在單例bean用 @Lookup註解標記的方法,註解的方法返回的對象是原型)和 replace-method( 標記的方法,標記bean中A的方法被實現被另外一個實現MethodReplacer接口的B方法替代)。

3,resolveBeforeInstantiation

​ 調用實現實現BeanPostProcessor的bean後置處理生成代理對象,有代理對象則直接返回代理對象。(spring AOP則是基於此處實現)

3.4 bean的真正實例化createBeanInstance

​ 在AbstractAutowireCapableBeanFactory#createBeanInstance中,真正創建bean,源碼及註釋如下:

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
		// Make sure bean class is actually resolved at this point.
		Class<?> beanClass = resolveBeanClass(mbd, beanName);
		if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {
			throw new BeanCreationException(mbd.getResourceDescription(), beanName,
					"Bean class isn't public, and non-public access not allowed: " + beanClass.getName());
		}
		/**
		 * 通過提供supplier回調方法創建
		 */
		Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
		if (instanceSupplier != null) {
			return obtainFromSupplier(instanceSupplier, beanName);
		}
		/**
		 * 通過工廠方法創建 bean 實例,可以是靜態工廠方法或者實例工廠
		 */
		if (mbd.getFactoryMethodName() != null) {
			return instantiateUsingFactoryMethod(beanName, mbd, args);
		}
		// Shortcut when re-creating the same bean...
		boolean resolved = false;
		boolean autowireNecessary = false;
		if (args == null) {
			synchronized (mbd.constructorArgumentLock) {
				/**
				 * 查找已經bean已經緩存解析的構造函數或者工廠方法
				 */
				if (mbd.resolvedConstructorOrFactoryMethod != null) {
					resolved = true;
					autowireNecessary = mbd.constructorArgumentsResolved;
				}
			}
		}
		/**
		 * 已經有緩存的構造函數或者工廠方法,直接實例化
		 */
		if (resolved) {
			if (autowireNecessary) {
				return autowireConstructor(beanName, mbd, null, null);
			}
			else {
				return instantiateBean(beanName, mbd);
			}
		}
		/**
		 * 通過實現BeanPostProcessor的代理類autowired的構造函數實例化
		 */
		// Candidate constructors for autowiring?
		Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
		if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
				mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
			return autowireConstructor(beanName, mbd, ctors, args);
		}
		/**
		 * 通過本身帶有autowired的構造函數實例化,通過調用反射newInstance實現
		 */
		// Preferred constructors for default construction?
		ctors = mbd.getPreferredConstructors();
		if (ctors != null) {
			return autowireConstructor(beanName, mbd, ctors, null);
		}
		/**
		 * 無參構造函數實例化,通過調用反射newInstance實現
		 */
		// No special handling: simply use no-arg constructor.
		return instantiateBean(beanName, mbd);
	}

由上述源碼可以看出,實例化bean操作流程如下:

1,如果存在 Supplier 回調,則通過提供supplier回調方法創建,如以下方式定義的bean:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = Spring5Application.class)
public class BeanRegistrationTest {
    @Autowired
    private GenericWebApplicationContext context;
    
    context.registerBean(A.class, () -> new A());
}

2,如果存在工廠方法,則通過工廠方法創建 bean 實例,可以是靜態工廠方法或者實例工廠,如以下方式定義的bean:

public class AFactory implements FactoryBean<A> {
	@Override
	public A getObject() throws Exception {
		return new A();
	}
	@Override
	public Class<?> getObjectType() {
		return A.class;
	}
}
//或:
@Configuration
public class BeanConfigration {
	@Bean
	public A a(){
		return new A();
	}

}

3,已經有緩存的構造函數或者工廠方法,直接實例化。

4,以上三點都不存在,則使用帶參構造函數與無參構造函數實例化。如以下方式定義的bean:

@Commponet
public class A{}

4.總結

​ spring單例bean的實例化流程大概就是這樣,很多細節地方,包括循環依賴處理,bean屬性填充等細節點下一章介紹。

參考

  • 《Spring 源碼深度解析》- 郝佳
  • 《死磕spring源碼》-chenssy

查看更多文章關注公眾號:好奇心森林

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

【其他文章推薦】

※產品缺大量曝光嗎?你需要的是一流包裝設計!

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

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

※超省錢租車方案

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

※推薦台中搬家公司優質服務,可到府估價