不要小看這些買車細節,去4S店裝老司機全靠這幾招…

總結:買車畢竟是一件大事,最怕就是花了大價錢結果掉進了別人一早挖好的陷阱里,起不來活不起,當了冤大頭還要替別人數錢,所以在我們買車的時候還是多加留意,多留個心眼,將主動權抓在自己的手上。

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

【其他文章推薦】

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

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

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

※超省錢租車方案

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

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

大發現!國3汽車的PM2.5居然比國5低?

接下來是國三的車。所以僅從pM2。5的指數上來看,老車的成績不一定差,新車的成績也不一定好,反而車況才是更重要的因素。而那些所謂的專家。其實之前其他網友測試汽車尾氣pM2。5指數得出驚人的結果。(汽車尾氣比空氣乾淨,聽着很扯,畢竟你不可能整天去吸尾氣),就有專家出來表明,汽車的排放導致的污染並非只是直接的pM排放污染,而是“二次反應”。

一到冬季,pM指數和霧霾就成了熱門話題,這個時候汽車就會慣例被推上輿論焦點,近期國六排放標準的出台也進一步表明了相關部門的觀點—“霧霾的很大成因就是因為汽車的排放”。每次說到空氣污染提得最多的就是pM2.5指數。

pM指數那麼多,為什麼偏偏是pM2.5?

pM的英文全稱為particulate Matter (顆粒物),而数字2.5是指這種顆粒物的空氣動力學直徑(aerodynamic diameter)為2.5微米( pM10則是指粒徑等於、小於10微米的顆粒物)。粒徑越小、密度越低,顆粒沉降得越慢。如果再有一些外力引起空氣微小的擾動,這些細小的顆粒即使在無風的條件下,最終也很難沉降到地面上,它們會一直在空中“遊盪”,成為危害人體健康的罪魁禍首。

而我們常說的pM2.5的濃度或其他污染氣體的濃度是指每立方米空氣中這種污染物的質量含量,它反映了空氣的污染程度,這個值越高,就代表空氣污染越嚴重。由於空氣中污染物的種類很多,如:可吸入顆粒物、二氧化硫、氮氧化物、一氧化碳及臭氧等,為了統一評估,我國的環保部門,將每一種污染物的濃度都換算成統一的空氣污染指數,然後對外發布。俗話說病從口入,那麼我們今天就從源頭-用儀器來測試一下汽車尾氣的pM2.5指數。

在此之前我們先看下香煙的pM指數,直接爆表,所以能戒煙朋友趕緊戒煙了。

接着測試一下國4的車。(所測汽車均為公司同事車輛)

然後是國5的車!

接下來是國三的車。

所以僅從pM2.5的指數上來看,老車的成績不一定差,新車的成績也不一定好,反而車況才是更重要的因素。

而那些所謂的專家。。。

其實之前其他網友測試汽車尾氣pM2.5指數得出驚人的結果。。。

(汽車尾氣比空氣乾淨,聽着很扯,畢竟你不可能整天去吸尾氣),就有專家出來表明,汽車的排放導致的污染並非只是直接的pM排放污染,而是“二次反應”。

這樣一群吃瓜群眾就懵逼了,你是博士后,你說什麼都對,就像一直聽說F1產生的下壓力足以讓其貼在牆上跑,但是從來沒有見過,不相信這件事的人也找不到反駁的理由。

不管汽車的排放是不是元兇(因為現在霧霾這個鍋汽車已經背定了),相關部門對新車排放的要求越來越高並沒錯,畢竟排放再少也是排放。假設之前國三標準滿分是100分,當年生產的汽車再優秀,也只能得100。

現在國五標準出來了,直接就說滿分是150,100分不及格,也不給你重考的機會(限制進京、限制遷入等),這種把能夠達到要求的老車也一刀切那就有點想不通了,對於車迷來說最大的損失就是中國就永遠不會有老爺車文化,因為在相關部門眼中只有報廢車。

最後通過數據計算可以知道廣州的車輛密度比北京還要高,但是空氣質量卻要好不少,所以愛吃烤鴨的小夥伴趕緊把烤鴨吃個夠,說不定哪天烤鴨也分黃標鴨!!!

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

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

為什麼朋友說他夢想的車是艾母雞?看完本文後終於懂了

說到奔馳許多人最初的印象都是寬、大、貴外加後排坐着一個人肥,牙黃,地中海的老闆似乎奔馳一向跟年輕、運動、操控這些詞扯不上什麼關係所以坊間也流傳這麼一個說法——甚至有一些“自信狂人”遇上大奔時會抑制不住腎上腺

說到奔馳

許多人最初的印象都是

寬、大、貴

外加後排坐着一個

人肥,牙黃,地中海的老闆

似乎奔馳一向跟

年輕、運動、操控這些詞

扯不上什麼關係

所以坊間也流傳這麼一個說法——

甚至有一些“自信狂人”

遇上大奔時

會抑制不住腎上腺本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

我很驚訝!十幾萬的國產SUV竟有人幹得過眾泰?

對於我們汽車媒體來說,這是個很方面的功能。榮威RX5配備了車身穩定系統、定速巡航、一鍵啟動、電動駐車、前撞預警系統、陡坡緩降、電動後備廂、車道保持、道路限速識別、四驅系統鎖止等豐富配置。動力系統方面,RX5將搭載1。

國內緊湊型SUV的熱度只增不減,2016年,榮威帶來了他們潛心打造,並得到馬雲的阿里技術支持的榮威RX5,主打互聯網概念。

榮威RX5外觀走大氣沉穩范,中規中矩但又精緻的造型給人不錯的印象。前臉“展翼格柵”將兩側前大燈融貫一體,前大燈為矩陣式LED大燈,其由24顆LED光源組成,燈組內部還集成了“如意形”LED日間行車燈。矩陣式全LED大燈更顯科技感。

一進車內最吸睛的當屬中控10.4英寸大屏。由於大屏幕的使用,一些傳統按鍵被取消,整體設計簡潔時尚,僅在屏幕下方保留了五個實體按鍵。同時RX5在內飾用料上也多處使用了軟質皮革包裹,凸顯質感。

RX5配備了榮威和阿里聯合開發的Yun OS,功能較為強大,可以通過大數據為用戶提供個性化服務。還可以支持綁定航拍機和運動相機,旅途中拍攝的畫面可以呈現在中控的大屏幕上。對於我們汽車媒體來說,這是個很方面的功能。

榮威RX5配備了車身穩定系統、定速巡航、一鍵啟動、電動駐車、前撞預警系統、陡坡緩降、電動後備廂、車道保持、道路限速識別、四驅系統鎖止等豐富配置。

動力系統方面,RX5將搭載1.5T和2.0T兩款渦輪增壓發動機,其中1.5T發動機最大功率為169馬,峰值扭矩250N·m,與之匹配的是手動/TST 7速雙離合變速箱;2.0T發動機最大功率為220馬力,峰值扭矩350N·m,與之匹配的是TST 6速濕式雙離合變速箱。

整車開起來動力表現不錯,車輛的轉向是比較精準的,底盤偏向柔軟和舒適,但是又保留了一些路感。很符合家用SUV得定位。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

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

一網打盡枚舉操作 .net core

本文介紹如何使用枚舉以及,如何將枚舉類型更好的應用於項目中,看完本文可以有序的將項目中的枚舉更容易的使用到每個角落。

1,分析枚舉

 /// <summary>
    /// 性別
    /// </summary>
    public enum Gender
    {
        /// <summary>
        ////// </summary>
        
        Man = 0,
        /// <summary>
        ////// </summary>
        
        Women = 1
    }

 

如1所示,這是一個非常普通的枚舉類,在項目中使用的話,一般都會將它作為某實體的一個屬性,這個時候問題就來了,在頁面裡邊我們是需要拿到與之相關的描述信息和對應的值作為一個下拉框或者checkbox讓用戶去選擇,同時也不可以將Disable和enable作為給用戶最終展示的信息,需要去手動去拼,於是有了如下的方式

2,枚舉類信息完善,增加描述信息

 /// <summary>
    /// 性別
    /// </summary>
    public enum Gender
    {
        /// <summary>
        ////// </summary>
        [Description("")]
        Man = 0,
        /// <summary>
        ////// </summary>
        [Description("女")]
        Women = 1
    }

再給枚舉增加一個擴展方法

 /// <summary>
        /// 獲取到對應枚舉的描述-沒有描述信息,返回枚舉值
        /// </summary>
        /// <param name="enum"></param>
        /// <returns></returns>
        public static string GetDescription(this Enum @enum)
        {
            Type type = @enum.GetType();
            string name = Enum.GetName(type, @enum);
            if (name == null)
            {
                return null;
            }
            FieldInfo field = type.GetField(name);
            if (!(Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) is DescriptionAttribute attribute))
            {
                return name;
            }
            return attribute?.Description;
        }

 好像到這一步的時候問題可以得到解決,通過getdescroption()這類的方法可以去獲取到與枚舉相應的描述信息用於展示,但是這也僅限於在mvc模式下,通過viewbag將枚舉的類中的每一項都加到枚舉集合中返回給頁面,在頁面裡邊遍歷,如果枚舉類型很多,那麼這類型的重複邏輯就會很多,非常的心煩。

如果可以將這些操作做一個封裝,用一個接口可以返回所有枚舉類型的相關信息,就好了,於是有了如下的做法

1 創建一個描述枚舉的類 

 public class EnumModel
    {
        /// <summary>
        ///枚舉原始值
        /// </summary>
        public ValueType Source { get; set; }
        /// <summary>
        /// 描述信息
        /// </summary>
        public string Description { get; set; }
        /// <summary>
        /// value
        /// </summary>
        public int Value { get; set; }
    }

2,寫一個方法通過傳遞一個枚舉類類型去得到List<EnumModel>

public static List<EnumModel> GetEnumListModels<T>()
        {
            var model = default(T);
            FieldInfo[] fieldinfo = typeof(T).GetFields();
            List<EnumModel> result = new List<EnumModel>();
            foreach (FieldInfo field in fieldinfo)
            {
                EnumModel enumModel = new EnumModel();
                if (!(Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) is DescriptionAttribute attribute))
                {
                    enumModel.Description = field.GetValue(model).ToString();
                }
                else
                {
                    enumModel.Description = attribute.Description;
                }
                enumModel.Value = field.GetValue(model).GetHashCode();
                enumModel.Source = field.GetValue(model) as ValueType;
                if (field.GetValue(model).ToString() != "0")
                {
                    result.Add(enumModel);
                }
                
            }
            return result;
        }

3,寫一個接口,輸入枚舉的類的名稱,調用2中的方法,得到具體的返回信息

[Route("[controller]/{name}")]
        public IActionResult GetEnumList(string name)
        {
            Assembly assembly = Assembly.Load("Ftw");
            Type t = assembly.GetType(string.Concat("Ftw.Enum.", name), throwOnError: false, ignoreCase: true);
            if (t == null)
            {
                throw new ServiceException(string.Concat("請確保枚舉[", name, "]在 Ftw.Enum 中定義"));
            }
            Type enumhelp = typeof(EnumHelper);
            var obj = enumhelp.GetMethod("GetEnumListModels").MakeGenericMethod(t);
            return Json(obj.Invoke(t, null));
        }

  解釋一下:Ftw是類庫的名稱,Enum是Ftw類庫下的一個文件夾,所有的枚舉類都在Enum下邊放着,EnumHelper是 2 中方法【GetEnumListModels】所在的類,通過反射程序集得到枚舉類型,通過反射程序集將類型傳入GetEnumListModels作為 T 最後執行方法的到list.

比如  Gender的調用,假如 GetEnumList所在的controller是EnumController,那麼調用就是通過  Enum/Gender ,對於.net core, mvc .net core api這類項目這種方式是非常有幫助的。

 

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

【其他文章推薦】

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

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

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

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

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

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

一個生物專業學生的內心獨白:我為什麼能去互聯網大廠?

這回,考慮到近期關注了許多新朋友,並且大多都是學生黨,可能對我還不是特別熟悉。因此我決定重新把我從非科班如何通過自學(狗屎運)進入大廠的經歷分享出來,希望能夠給予一些將要面臨秋招,或者將要準備進入互聯網行業的同學一丟丟的幫助。

早期關注我的讀者可能都約莫着記得,我是在華科讀的本碩,專業是生物醫學工程。雖說專業的的確確是帶了生物二字,但是我老實交代,實際上並不是純粹的生物技術方向上的專業。簡單科普一下:

生物醫學工程是屬於交叉程度非常高,主要面向生物醫學領域的工程學科。我們在學校的主要課程包含硬件設計、軟件設計、生化基礎以及儀器科學等。

所以呢,原諒本文的些許標題黨。但是說實話,由於學科高度交叉,我們實際上所習得的技能就是一團漿糊。在計算機科學方面的系統知識積累,與純生物專業相比,也不過是五十步笑百步。

一 啟智階段

雖說大學期間學的都不是計算機,但是我對計算機的興趣萌芽卻是十分的早。那估計都得有二十年前了,嗯誇張了點。不過應該是在小學時候了。

我是在老家鄉下上的小學,你們可能想象不到,那個時候的鄉下小學竟然都有電腦課。不過那時候不叫電腦課,叫做“微機課”。

微機課是幹嘛的呢?就是一群小朋友排排隊,拖鞋進到一個乾淨的房間里,然後幾個人圍着一台那種屁股又大又方的電腦,玩紙牌。沒錯,就是windows上流行多年的紙牌遊戲。

怎麼說呢。我玩紙牌賊溜。 大概天生對這一類東西的接受能力比較強,所以上手很快,並且除了紙牌,還有空當接龍,掃雷,3D彈球。這麼說可能有點暴露年齡了。

最早windows系統普及的這一批遊戲真的讓我對“微機”這麼個東西產生了懵懂的概念和持續的嚮往。

二 本科階段

之後的成長道路中,一直對計算機耍的挺溜。但是有點尷尬的是,沒人領進門。所以一直都是在娛樂,並沒有真正意義上接觸到計算機內部或者代碼編程的世界。

初識代碼

所以在上大學之前,基本上對編程一無所知,但是卻有一種強烈的對計算機的熱愛。不過那時對於計算機的理解僅限於【裝系統】,【裝軟件】和【拆洗主機風扇】。不要問我為什麼沒有去學計算機,要怪就只能怪考試時坐在我旁邊的那位,大概天生就得了瘋狂抖腿病的二貨。氣!

一不小心,還進了生科院。但是緣分這種東西,真的妙不可言。這一切的開始源於一場面試。

所在的大學是一所以工科著稱的高校,其中創業氣氛十分濃厚。學校因此有許多小有名氣的科創團隊,基本上是由老師主導,各專業學生組成的小團體。可不要小看這些小團隊,世界級程序設計大賽的獎牌獲得者經常就出於此類團隊。剛上大學的我們單純稚嫩,自然會被被這些團隊的大幅宣傳報和滿目的獎牌稱號所吸引膜拜。我也不例外。

當時我便懷着澎湃的心情申請了一家曾多次在微軟創新杯奪得金獎的團隊。一個從鄉下來的小伙,第一次參加面試,第一次單獨和碼農小姐姐夜晚座談,第一次參加所謂的通宵測試。

也就是在那一個晚上,開啟了我新世界的大門。

那天晚上給我一群編程小白的任務,是模仿百度首頁,實現整個網頁的設計、布局和基本鏈接。提前給出的提示是w3c的教程網址。

從來沒有接觸過編程的我在此之前,連編程的流程都不清楚,更不必說編譯環境、編譯語言甚至是源代碼閱讀(當然這個任務也涉及不到這些)。

但是也就是那一個晚上,讓我真正意義上的接觸了敲代碼這個事兒。

沒錯,我的編程起點是HTML+CSS。可惜的是,我並沒有通過那個團隊最終的面試。不過從那以後,我就開始了網站開發的自學之路。

個人自學的堅持很大程度上基於興趣,源於在室友面前一頓裝逼后的成就感。但這就是一個生科院的學生在課後的最大樂趣所在。

小試牛刀

在接觸到網站開發之後,從最初的HTML+CSS到後來的HTML5+CSS3+JS+ASP,從靜態頁面的布局到動態網頁的請求。

雖然感覺技術的成長也就是從博客的複製粘貼走向了文檔的複製粘貼。但是終於迎來了小試牛刀的機會。

學校某大型學校組織需要做一個展示網站,朋友拉上了我和幾個人承擔了這個事情。這算是第一次真正意義上的項目開發,不過整個網站的功能不多,主要還是展示為主,後台也直接通過學校網絡平台整合就行了。所以整體下來做的事情並不多。

迷茫困惑

一直以來,都是自己通過博客自學,東拼西湊的建立起知識架構。但是其實技術基礎十分不牢固,不懂計算機基礎、計算機網絡,更別說編譯原理、操作系統等基本的知識。因此在很長一段時間內,覺得好像也沒有會什麼,一直對自己的能力保持懷疑。

在這段時間內,有過跑去搞嵌入式,硬件開發,甚至是產品設計、界面設計,視頻製作。但都不一而終。

一段迷茫,一段頹廢。一不小心就成為校園裡的學長,於是開始緊緊茫茫的尋找實驗室。

新的方向

好在我們專業與生科院其它專業相比,還是帶有工科氣質的。醫學影像學本身就是這個專業最為重要的一個方向。因此懷着對編程的興趣,加入了一個專門做醫學影像的實驗室。

進入實驗室,不再是之前的漫無目的,隨處拾荒,但卻也並不會接觸到前沿新興的技術。畢竟在實驗室里,工程技術更多的只是工具。

不過值得一提的是,在實驗室里,我接觸到了算法思想。醫學圖像本來就是圖像處理領域的一大分支,因此圖像處理算法的了解同樣也是至關重要。

從這個時候開始,我已經不再搞網站開發那一套東西,專心使用C++寫我的算法。那時候深度學習還沒那麼熱門,大多數的圖像處理方式還是基於傳統的算法進行。雖然算法能力薄弱,但是倒也是我所求。

同時,為了獲得數據,還接觸到Linux系統以及一些腳本,在做研究的過程中也大大增加自己的技術面。

與此同時,為了鞏固自己的一些技術基礎,也為了督促自己的學習,我報考了計算機等級考試和國家軟考。可能對於計算機專業的學生來說,沒有什麼含金量,輕而易舉。但是對於一個非科班的學生而言,這樣的考試可以很大程度上幫助自己去重新組織零碎的知識。畢竟不是所有人都有足夠的精力去跟着計算機系的人上課。

本科階段的經歷讓我知道自己與計算機系的差距,雖然接觸了許多東西,但是也都不夠精,甚至沒有底氣去獨立承擔一個小的開發任務。

因此最終選擇了讀研。希望讓自己的技術都夠在某一個點上精進,能夠獨立的做出點東西來。當然最終的結果可能並不是自己當初所想,但是說到底還是得感謝在研究生期間所做出的努力。

三 碩士階段

我研究生的生活是從大四就開始的。因為是本校保研生,所以在大三下確定好實驗室后,基本就直接開始搬磚的苦逼生活了。當然舍友和同學在為畢業而慶祝歡快的時候,我就開始在實驗室早出晚歸。

讀研的開始

進入實驗室之後,剛開始一段時間也是沒有人帶。這能靠自己去多多實驗室專業方向的文章,然後學一點基本技能。更重要的其實是為了在實驗室混個臉熟。

實驗室的研究方向主要是核醫學成像系統,整個系統的搭建涉及到核科學研究、物理數學模型的建立、电子信息的數據獲取、自動化控制設計、机械結構設計、軟件和算法處理以及醫學實驗驗證等一系列的過程。

但對我們個人來說,主要是做某一個方向即可。當時我還是自認有一些編程基礎,於是計劃去做軟件開發或者數據處理的方向。但是最初期是沒有人帶的,所以就花三天時間自學了python3,把廖雪峰的python教程從頭到尾走了一遍。

可惜的是,學完之後也並沒有派上用場。

之後實驗室新項目確立,我被導師安排到了一個新項目組裡。在項目組中,主要負責的是圖像重建的部分。圖像重建是醫學影像領域非常重要的一個研究方向,目的是將影像系統採集到的多維多尺度的信息根據成像原理,依照不同算法還原成二維或三維的斷層圖像。所以本質上來說,圖像重建的重點也是在於算法設計和優化。

方向的多變

開始所在的項目組內,主要的工作還是基於軟件編程和算法開發,涉及到的技術棧主要還是以C++為主,然後加上一些圖型庫,如QtopenGLVTK等等。不過都是處於調用函數調包的階段,也沒有很深入的研究算法原理和實現機制。實話說其實個人提升不大。

過了一段時間后,實驗室師兄拉我去幫他做事。師兄本身能力非常出眾,科研水平不容置疑,所以當時就過去了。之後在他手下做的事就很多變了,不只是軟件開發,還涉及到数字電路編程,PCB制板,系統仿真等。

那個時候還來實驗室沒多久,沒有仔細的想過自己之後的就業方向,只想着能夠在研究生期間多做些成果,多發文章。因此,跟着師兄搬磚的時候也沒有過多考慮對之後的求職是否有幫助,只是想把事情做好,做出有意義的成果。

不過幸運的是,在大四暑假前,也就是研究生入學之前。根據師兄的指導,將一個軟件開發的工作給擴充,投了一篇領域內的頂會。雖然覺得所作的工作沒什麼特別的,但是最終文章被錄還是非常開心的。也正因為這個事,之後更想着能夠多發文章多出成果。同時,師兄也比較會熬雞湯,各種說辭讓我放佛感覺到即將成為科研巨人,登上學校官網,走上人生巔峰。

綜合能力鍛煉

在實驗室,除了完成自己的科研任務,還有很重要的一部分,就是寫本子。也就是申請項目基金等等文字工作。

我估計有參与過的項目申請或結題工作不下十餘項,每次都會涉及到大量文字的整理、表達、排版等工作。一般的學生對於這樣的工作都是極度排斥的,覺得很煩又沒什麼用。當然我也是這樣覺得的。

只不過在寫了大量的本子後會發現,其實這也是綜合能力的鍛煉。我們導師常說,博士生一定要會寫項目,碩士生的話盡量寫。說明他把這也是當作一種能力的培養。在師兄手下,我也經常性的寫本子,甚至於超過了敲的代碼。那時候也沒有很明確的技術培養規劃,所以倒也覺得還行。

獨自前行

只不過好景不長。帶我的師兄由於過於優秀,將要離開實驗室去其它高校就職。這對當時的我來說,處境有些許尷尬。因為我的研究方向是與師兄的工作有交叉的,同時一些設備和材料也需要師兄的支持。

實驗室本身的氛圍是以博士帶頭,碩士輔助進行項目開展的,而我的研究方向是我自己獨立出來的一個題目。說白了就是實驗室就我一個人在搞這個,但是這個方向不是導師關注的方向。

因此,在這之後,我又回歸到了一個人做事的階段。大概是研一下開始,我就只能自己一個人去想自己的研究課題,偶爾會跟老師討論,但是也沒有很確定的工作路線。具體的工作內容也只能靠自己去思索。

在這段期間,我寫過verilog,調過FPGA,畫過板子,畫過工程圖,當然也寫過軟件做過算法。一個人做事的話,可以說是很自由,沒有老師管,也沒有報告的壓力。只是很多時候發現有一些想法沒辦法實現,也無法對前沿領域有很深的洞察。空有一腔發文章報效實驗室的熱血,卻發現像是站在水中的浮木上,搖搖欲墜。當然這其中,個人的問題佔有很大比重。

摸魚的日子

不敢說自己的有多麼自律,多麼出色。可能世上大多人都是芸芸眾生,我也不過一個普通人。在自己做研究的同時,每次到臨近頂會收集文章摘要之時,我都會很积極的熬個一兩個月,將自己的想法和結果寫成一到兩篇的會議文章。我自己也大概知道其實工作沒有太大的突破點,但也是希望能夠賭一把。

只不過兩年來,一共整理出數篇會議摘要,都沒有被同意投稿。每一次還是會有些灰心,也有些不服。會時刻回想起導師的反饋,而後想到一堆反駁的理由。只不過都沒用。

每一次之後,都會有一段時間的消沉。不太想去實驗室,不太想看論文,在實驗室可能也就是摸摸魚罷了。很多時候覺得鬥志滿滿,又會瞬間像泄了氣的皮球,沒有精力去完成任何事。

職業方向

到如今,算是大四的時光,在實驗室已經呆了快四年。平心而論,沒有做過什麼突出的成果,也沒有練就什麼出色的技術。研究生活高開低走,一度以為自己是什麼科研巨星,後來僅剩的一絲熱情也在導師的「也沒看出你有什麼天分」中黯然消逝。不過還好,在這之前,我找到了自己的職業方向。

我本身就是一個比較後知后覺的人,一方面是懶,另一方面又對自己有一種迷之自信。我真正意義上開始準備校招還是在去年六月底七月初的時候,也就是研二下學期快結束的時候吧。我們實驗室的碩士生出去基本就是兩個方向,軟件或者硬件。搞電路的基本都選擇去做硬件開發,其它的大部分都會選擇做軟件。我也沒啥可想的,雖然做過硬件,但是技術水平根本不敢出手。所以求職的方向直接就定在了軟件開發相關。

當時還不知道應聘互聯網還要刷題,還要複習,以為上去介紹下自己,講講自己做的跟互聯網沒有半毛錢關係的項目就可以顯得很有想法的樣子。後來真正開始準備的時候才發現,自己原來還差得有點遠。

老實說在研究生期間,主要使用C++Matlab,主要的項目就是用Qt寫了一個客戶端,裏面有網絡通信的模塊可以對數據做一些處理,並且能夠显示圖譜。用Matlab主要就是做了一些比較基礎的圖像處理和機器學習的算法。光這項目再加上非科班帶「生物」二字的專業,的確讓不清楚的人會十動然拒。

不過其實也沒得選擇。生醫專業對口的公司都是做醫療器械,比如聯影、邁瑞等。但是其實去這些公司也是做軟件開發工程師。相對來說,互聯網公司技術好,待遇好,發展好,自然就成為大部分的選擇去向。

說是跨行,但是其實也沒有別的更好的選擇。我們老師常說希望我們學生以後能夠在我們這個領域發光發熱。這倒是真的,國家的發展還是離不開這些能夠在某個領域深耕的人,而不是為了一昧追求熱點和高薪而忘記了初心。

四 求職經歷

講講我的秋招經歷吧。我的秋招雖然準備的晚,但是其實還是挺順利的。從19年8月開始接到面試,2個月內已經拿到了15+的offer,基本平均薪資都在30w+。包括抖音美團華為小米等等。那段時間真的是狀態上來了,就是佛擋殺佛,神擋殺神。

不過剛開始的時候,由於實驗室的原因,沒有辦法出去實習。甚至由於一些原因,一直拖到暑期前才開始準備複習。

那時已經快七月初了。急匆匆的登上各種學習網站,發現這也太多要看了的吧,還得刷題。關鍵一看面經,這都啥呀。還要手撕代碼的嗎?

這一看當時不要緊,關鍵晚上就焦慮的快睡不着了。每天都在想應該怎樣複習,怎麼寫簡歷,沒有項目該怎麼辦。

剛開始的時候連簡歷都不敢投,因為老覺得簡歷一過去就會安排面試。後來發現這完全就是多慮了。大概從七月初我就開始投簡歷,因為七八月是一些公司提前批招聘的階段。

許多非科班的學生,在投遞簡歷的時候才能發現自己的無助。我在簡歷投遞初期,基本沒有任何反饋。提前批階段,許多公司都會去爭奪更優秀的簡歷候選者,對於生物專業的學生真的沒有什麼優勢。

但是沒有關係,既然選擇了這條路,那麼就要堅持下去。投一家無人應答,那麼就投十家,投五十家。我在整個秋招階段,總共投遞過近一百家公司。許多在提前批沒有給予反饋的公司,後期大部分都有電話聯繫重新開啟面試流程。所以,就算認為自己的簡歷再不夠出色,也要相信總會有瞎了眼的HR(誤)。

我當時一直在堅持投簡歷,只要看到的招聘信息都會去投遞。還記得第一次做測評題的時候都非常緊張,以為這就是筆試題。非常認真的拿紙筆在計算,慌的不行。

後來直到八月初才收到第一份面試邀請,多益網絡。當時約了面試之後簡直怕死了,雖然說複習了一個月大部分的 C++ 基礎知識都看得差不多了,但是肯定是不夠的。面試的時候面試小哥全程就低頭照着題庫念,也不看我。我這邊的音頻信號也不太行,他那邊說話都聽不清楚。兩個人就在無數次的重複和確認。關鍵是面試完之後我自我感覺還非常良好。

最終結果還是掛了,說實話打擊挺大的,感覺因此對面試產生了恐懼。不過後來試着自己跟自己講解,慢慢的也習慣了面試的感覺。

隨後在八月底的時候,終於收穫了第一家 ihandy 的 offer。並且在進行總管面的時候,跟面試官進行了深入的交談。面完之後讓我有一種都不好意思拒絕這家公司的念頭。這也是第一次感受到了面試官的認可。

隨後,便一發不可收拾。這個時候,已經準備了大概兩個月,基本的技術知識我都看完了,劍指 offer 上的66題以及 leetcode 上基本的題也大概刷了兩次。同時獲得了一個 offer 之後,對自己的認可度非常高,使得面試的狀態非常好。

後來給了面試機會的公司基本都拿到了 offer。像字節跳動、美團、虎牙等等公司,面完的感覺就是基本穩了。不過可惜的是阿里簡歷面后,內推人開始說通過了,後來不知道什麼情況流程就拖到結束了。

騰訊也一直沒有撈過我面試。感覺如果在狀態頂峰的情況下能有面試機會的話,還是很有希望的。不過也說明簡歷依然不夠出色,非科班沒有實習經歷,項目也比較水,導致AT大廠連面試機會都沒給。這也是秋招比較遺憾的地方。

另外,記住面試過程,跟投遞簡歷一樣,一定要多面多總結。

如果你的表達能力不好,沒有別的好的辦法,只能多練。自己在面試前問自己問題,然後用自己的話陳述出來。甚至是錄音自己聽,感覺一下面試官聽到你的回答是做何感想。

心態要好。面試官也是人,不可能所有人都能夠絕對公平的跟你面試,所以遇到人品不好的面試官,做好自己就行了。

要善於總結。每一次的面試都可以做好記錄,錄音或者筆記都可以。面試完之後需要多回顧,發現自己的錯誤,感受面試官對你的引導,然後下次面試注意。我一般喜歡用印象筆記記錄東西,每一次的面試記錄我都記錄在印象筆記上。電腦手機都可以看,即使是出門現場面試也不怕。

最後就是一定要堅持下去。金九銀十,金三銀四。把握好機會,要善於規劃自己的成功。

五 複習準備

想進互聯網的技術崗,基本都是要提前準備的。當然某些大佬及大大佬除外。無論是校招還是社招,都需要針對自己的求職崗位進行必要的理論知識複習、項目經歷反思和算法能力訓練。只不過校招會偏向於基礎和算法能力,社招可能都會重點考察。

除項目經歷外,複習的階段主要分為語言基礎、數據結構和算法、計算機網絡、操作系統、數據庫以及算法刷題。

語言基礎:

以 C++ 為主。我不喜歡看又厚又重的語言書,因此複習全程是以博客、開發文檔和實踐相結合的方式進行技術點複習。C++ 的技術點相對於其它語言來說,不算多,也不算難。技術重點的篩選可以從面經中提取而來。當然每一屆都差不多,所以找找別人總結好的資料看就行。(想要我複習資料的,可關注公眾號後台回復秋招領取)

數據結構和算法:

這應該是編程的基礎,重要需要了解的數據結構不出10種。花點時間弄清楚它們的原理、結構和使用方法,常用的操作也需要掌握。最難不過紅黑樹。

這裏的算法指的是常用的算法,比如排序、遍歷,與數據結構相結合的數據操作方式。需要保證手寫才行。

計算機網絡:

網絡部分的內容其實可以算是最重要的,無論是前端後端都需要掌握網絡通信過程中的操作和機制。技術點可參考網絡服務器的請求和響應過程。將其中所有涉及到的協議、機制了解清楚,就可以掌握大部分了。

操作系統:

如果有Linux使用經歷和腳本編程基礎在面試中會很加分。對於操作系統的理解建議按照Linuxwindos系統的區分進行。

數據庫:

SQL基本操作必須要掌握,還包括一些關係型數據庫的基本原理和機制,內容不多,多看看就可以掌握。

Redis同樣也是加分項,有能力的可去研究下源碼。

算法刷題

這一部分不多說,普通人只能勤能補拙。無論是劍指offer66題還是leetcode都可以,劍指刷兩遍,leetcode兩百題,基本沒有問題了。刷題時不要死刷,可以根據類型刷,比如鏈表操作、二叉樹操作、動態規劃等。相同類型重複做,能夠更好的培養算法思想。

六 感想體會

說起求職的過程,其實真要我來評價的話,估計運氣是佔了一大部分,連我這樣都行,你們也可以的。不過最終能夠獲得一些互聯網公司認可的原因,我認為主要有以下幾點原因吧:

1.本科階段接觸互聯網行業比較早,對於這個行業有自己的見解。

2.本科階段有考過一些計算機水平的證書,大概系統的學了一下計算機相關的基礎知識。

3.用C++比較多,對語言基礎的理解比較好。

4.面試狀態比較好,比較會表達自己的想法。

5.學習能力還行,能夠在面試官的引導下找到他想問的技術點。

6.準備過程比較有規劃,能夠快速的掌握面試的重點。

7.人長得老實,比較容易獲得信任感。(這個你們可能學不了^_^)

8.比較幸運。

前天剛碩士答辯完,這两天也把學位申請的各種材料提交上去了。一不小心,七年的大學校園時光真的要結束了,兜兜轉轉感覺好像依舊是一無是處,一事無成。但是依舊希望以後能夠:

二龍騰飛、三羊開泰、四季平安、 五福臨門、六六大順、七星高照、八方來財、九九同心、十全十美、百事亨通、千事吉祥、萬事如意

人生無常,活在當下,且行且珍惜!

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

【其他文章推薦】

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

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

※台北網頁設計公司全省服務真心推薦

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

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

機器學習——手把手教你用Python實現回歸樹模型

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

今天這篇是機器學習專題的第24篇文章,我們來聊聊回歸樹模型。

所謂的回歸樹模型其實就是用樹形模型來解決回歸問題,樹模型當中最經典的自然還是決策樹模型,它也是幾乎所有樹模型的基礎。雖然基本結構都是使用決策樹,但是根據預測方法的不同也可以分為兩種。第一種,樹上的恭弘=叶 恭弘子節點就對應一個預測值和分類樹對應,這一種方法稱為回歸樹。第二種,樹上的恭弘=叶 恭弘子節點對應一個線性模型,最後的結果由線性模型給出。這一種方法稱為模型樹。

今天我們先來看看其中的回歸樹。

回歸樹模型

回歸樹模型的核心算法,也就是構建決策樹的算法,就是我們上篇文章所講的CART算法。如果有生疏或者是遺漏的同學,可以通過下方傳送門回顧一下:

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

CART算法的核心精髓就是我們每次選擇特徵對數據進行拆分的時候,永遠對數據集進行二分。無論是離散特徵還是連續性特徵,一視同仁。CART還有一個特點是使用GINI指數而不是信息增益或者是信息增益比來選擇拆分的特徵,但是在回歸問題當中用不到這個。因為回歸問題的損失函數是均方差,而不是交叉熵,很難用熵來衡量連續值的準確度。

在分類樹當中,我們一個恭弘=叶 恭弘子節點代表一個類別的預測值,這個類別的值是落到這個恭弘=叶 恭弘子節點當中訓練樣本的類別的眾數,也就是出現頻率最高的類別。在回歸樹當中,恭弘=叶 恭弘子節點對應的自然就是一個連續值。這個連續值是落到這個節點的訓練樣本的均值,它的誤差就是這些樣本的均方差。

另外,之前我們在選擇特徵的劃分閾值的時候,對閾值的選擇進行了優化,只選擇了那些會引起預測類別變化的閾值。但是在回歸問題當中,由於預測值是一個浮點數,所以這個優化也不存在了。整體上來說,其實回歸樹的實現難度比分類樹是更低的。

實戰

我們首先來加載數據,我們這次使用的是scikit-learn庫當中經典的波士頓房價預測的數據。關於房價預測,kaggle當中也有一個類似的比賽,叫做:house-prices-advanced-regression-techniques。不過給出的特徵更多,並且存在缺失等情況,需要我們進行大量的特徵工程。感興趣的同學可以自行研究一下。

首先,我們來獲取數據,由於sklearn庫當中已經有數據了,我們可以直接調用api獲取,非常簡單:

import numpy as np
import pandas as pd
from sklearn.datasets import load_boston
boston = load_boston()

X, y = boston.data, boston.target

我們輸出前幾條數據查看一下:

這個數據質量很高,sklearn庫已經替我們做完了數據篩選與特徵工程,直接拿來用就可以了。為了方便我們傳遞數據,我們將X和y合併在一起。由於y是一維的數組形式是不能和二維的X合併的,所以我們需要先對y進行reshape之後再進行合併。

y = y.reshape(-1, 1)
X = np.hstack((X, y))

hstack函數可以將兩個np的array橫向拼接,與之對應的是vstack,是將兩個array縱向拼接,這個也是常規操作。合併之後,y作為新的一列添加在了X的後面。數據搞定了,接下來就要輪到實現模型了。

在實現決策樹的主體部分之前,我們先來實現兩個輔助函數。第一個輔助函數是計算一批樣本的方差和,第二個輔助函數是獲取樣本的均值,也就是子節點的預測值。

def node_mean(X):
    return np.mean(X[:, -1])


def node_variance(X):
    return np.var(X[:, -1]) * X.shape[0]

這個搞定了之後,我們繼續實現根據閾值拆分數據的函數。這個也可以復用之前的代碼:

from collections import defaultdict
def split_dataset(X, idx, thred):
    split_data = defaultdict(list)
    for x in X:
        split_data[x[idx] < thred].append(x)
    return list(split_data.values()), list(split_data.keys())

接下來是兩個很重要的函數,分別是get_thresholds和split_variance。顧名思義,第一個函數用來獲取閾值,前面說了由於我們做的是回歸模型,所以理論上來說特徵的每一個取值都可以作為切分的依據。但是也不排除可能會存在多條數據的特徵值相同的情況,所以我們對它進行去重。第二個函數是根據閾值對數據進行拆分,返回拆分之後的方差和。

def get_thresholds(X, i):
    return set(X[:, i].tolist())

# 每次迭代方差優化的底線
MINIMUM_IMPROVE = 2.0
# 每個恭弘=叶 恭弘子節點最少樣本數
MINIMUM_SAMPLES = 10

def split_variance(dataset, idx, threshold):
    left, right = [], []
    n = dataset.shape[0]
    for data in dataset:
        if data[idx] < threshold:
            left.append(data)
        else:
            right.append(data)
    left, right = np.array(left), np.array(right)
    # 預剪枝
    # 如果拆分結果有一邊過少,則返回None,防止過擬合
    if len(left) < MINIMUM_SAMPLES or len(right) < MINIMUM_SAMPLES:
        return None
    # 拆分之後的方差和等於左子樹的方差和加上右子樹的方差和
    # 因為是方差和而不是均方差,所以可以累加
    return node_variance(left) + node_variance(right)

這裏我們用到了MINIMUM_SAMPLES這個參數,它是用來預剪枝用的。由於我們是回歸模型,如果不對決策樹的生長加以限制,那麼很有可能得到的決策樹的恭弘=叶 恭弘子節點和訓練樣本的數量一樣多。這顯然就陷入了過擬合了,對於模型的效果是有害無益的。所以我們要限制每個節點的樣本數量,這個是一個參數,我們可以根據需要自行調整。

接下來,就是特徵和閾值篩選的函數了。我們需要開發一個函數來遍歷所有可以拆分的特徵和閾值,對數據進行拆分,從所有特徵當中找到最佳的拆分可能。

def choose_feature_to_split(dataset):
    n = len(dataset[0])-1
    m = len(dataset)
    # 記錄最佳方差,特徵和閾值
    var_ = node_variance(dataset)
    bestVar = float('inf')
    feature = -1
    thred = None
    for i in range(n):
        threds = get_thresholds(dataset, i)
        for t in threds:
            # 遍歷所有的閾值,計算每個閾值的variance
            v = split_variance(dataset, i, t)
            # 如果v等於None,說明拆分過擬合了,跳過
            if v is None:
                continue
            if v  < bestVar:
                bestVar, feature, thred = v, i, t
    # 如果最好的拆分效果達不到要求,那麼就不拆分,控制子樹的數量
    if var_ - bestVar < MINIMUM_IMPROVE:
        return None, None
    return feature, thred

和上面一樣,這個函數當中也用到了一個預剪枝的參數MINIMUM_IMPROVE,它衡量的是每一次生成子樹帶來的收益。當某一次生成子樹帶來的收益小於某個值的時候,說明收益很小,並不划算,所以我們就放棄這次子樹的生成。這也是預剪枝的一種。

這些都搞定了之後,就可以來建樹了。建樹的過程和之前類似,只是我們這一次的數據當中沒有特徵的name,所以我們去掉特徵名稱的相關邏輯。

def create_decision_tree(dataset):
    dataset = np.array(dataset)
    
    # 如果當前數量小於10,那麼就不再繼續劃分了
    if dataset.shape[0] < MINIMUM_SAMPLES:
        return node_mean(dataset)
    
    # 記錄最佳拆分的特徵和閾值
    fidx, th = choose_feature_to_split(dataset)
    if fidx is None:
        return th
    
    node = {}
    node['feature'] = fidx
    node['threshold'] = th
    
    # 遞歸建樹
    split_data, vals = split_dataset(dataset, fidx, th)
    for data, val in zip(split_data, vals):
        node[val] = create_decision_tree(data)
    return node

我們來完整測試一下建樹,首先我們需要對原始數據進行拆分。將原始數據拆分成訓練數據和測試數據,由於我們的場景比較簡單,就不設置驗證數據了。

拆分數據不用我們自己實現,sklearn當中提供了相應的工具,我們直接調用即可:

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=23)

我們一般用到的參數就兩個,一個是test_size,它可以是一個整數也可以是一個浮點數。如果是整數,代表的是測試集的樣本數量。如果是一個0-1.0的浮點數,則代表測試集的佔比。random_state是生成隨機數的時候用到的隨機種子。

我們輸出一下生成的樹,由於數據量比較大,可以看到一顆龐大的樹結構。建樹的部分實現了之後,最後剩下的就是預測的部分了。

預測部分的代碼和之前分類樹相差不大,整體的邏輯完全一樣,只是去掉了feature_names的相關邏輯。

def classify(node, data):
    key = node['feature']
    pred = None
    thred = node['threshold']

    if isinstance(node[data[key] < thred], dict):
        pred = classify(node[data[key] < thred], data)
    else:
        pred = node[data[key] < thred]
            
    # 放置pred為空,挑選一個恭弘=叶 恭弘子節點作為替補
    if pred is None:
        for key in node:
            if not isinstance(node[key], dict):
                pred = node[key]
                break
    return pred

由於這個函數一次只能接受一條數據,如果我們想要批量預測的話還不行,所以最好的話再實現一個批量預測的predict函數比較好。

def predict(node, X):
    y_pred = []
    for x in X:
        y = classify(node, x)
        y_pred.append(y)
    return np.array(y_pred)

后剪枝

后剪枝的英文原文是post-prune,但是翻譯成事後剪枝也有點奇怪。anyway,我們就用后剪枝這個詞好了。

在回歸樹當中,我們利用的思想非常樸素,在建樹的時候建立一棵盡量複雜龐大的樹。然後在通過測試集對這棵樹進行修剪,修剪的邏輯也非常簡單,我們判斷一棵子樹存在分叉和沒有分叉單獨成為恭弘=叶 恭弘子節點時的誤差,如果修剪之後誤差更小,那麼我們就減去這棵子樹。

整個剪枝的過程和建樹的過程一樣,從上到下,遞歸執行。

整個邏輯很好理解,我們直接來看代碼:

def is_dict(node):
    return isinstance(node, dict)


def prune(node, testData):
    testData = np.array(testData)
    if testData.shape[0] == 0:
        return node
 
    # 拆分數據
    split_data, _ = split_dataset(testData, node['feature'], node['threshold'])
    # 對左右子樹遞歸修剪
    if is_dict(node[0]):
        node[0] = prune(node[0], split_data[0])
    if is_dict(node[1]) and len(split_data) > 1:
        node[1] = prune(node[1], split_data[1])

    # 如果左右都是恭弘=叶 恭弘子節點,那麼判斷當前子樹是否需要修剪
    if len(split_data) > 1 and not is_dict(node[0]) and not is_dict(node[1]):
        # 計算修剪前的方差和
        baseError = np.sum(np.power(np.array(split_data[0])[:, -1] - node[0], 2)) + np.sum(np.power(np.array(split_data[1])[:, -1] - node[1], 2))
        # 計算修剪后的方差和
        meanVal = (node[0] + node[1]) / 2
        mergeError = np.sum(np.power(meanVal - testData[:, -1], 2))
        if mergeError < baseError:
            return meanVal
        else:
            return node
    return node

最後,我們對修剪之後的效果做一下驗證:

從圖中可以看到,修剪之前我們在測試數據上的均方差是19.65,而修剪之後降低到了19.48。從數值上來看是有效果的,只是由於我們的訓練數據比較少,同時進行了預剪枝,影響了后剪枝的效果。但是對於實際的機器學習工程來說,一個方法只要是有明確效果的,在代價可以承受的範圍內,它就是有價值的,千萬不能覺得提升不明顯,而隨便否定一個方法。

這裏計算均方差的時候用到了sklearn當中的一個庫函數mean_square_error,從名字當中我們也可以看得出來它的用途,它可以對兩個Numpy的array計算均方差

總結

關於回歸樹模型的相關內容到這裏就結束了,我們不僅親手實現了模型,而且還在真實的數據集上做了實驗。如果你是親手實現的模型的代碼,相信你一定會有很多收穫。

雖然從實際運用來說我們幾乎不會使用樹模型來做回歸任務,但是回歸樹模型本身是非常有意義的。因為在它的基礎上我們發展出了很多效果更好的模型,比如大名鼎鼎的GBDT。因此理解回歸樹對於我們後續進階的學習是非常重要的。在深度學習普及之前,其實大多數高效果的模型都是以樹模型為基礎的,比如隨機森林、GBDT、Adaboost等等。可以說樹模型撐起了機器學習的半個時代,這麼說相信大家應該都能理解它的重要性了吧。

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

本文使用 mdnice 排版

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

【其他文章推薦】

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

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

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

※回頭車貨運收費標準

【C#】AutoMapper 使用手冊

目錄

  • 1 入門例子
  • 2 註冊
    • 2.1 Profile
  • 3 配置
    • 3.1 命名約定
    • 3.2 配置可見性
    • 3.3 全局屬性/字段過濾
    • 3.4 識別前綴和後綴
    • 3.5 替換字符
  • 4 調用構造函數
  • 5 數組和列表映射
    • 5.1 處理空集合
    • 5.2 集合中的多態
  • 6 方法到屬性映射
  • 7 自定義映射
  • 8 扁平化映射
    • 8.1 IncludeMembers
  • 9 嵌套映射

本文基於 AutoMapper 9.0.0

AutoMapper 是一個對象-對象映射器,可以將一個對象映射到另一個對象。

官網地址:http://automapper.org/

官方文檔:https://docs.automapper.org/en/latest/

1 入門例子

public class Foo
{
    public int ID { get; set; }

    public string Name { get; set; }
}

public class FooDto
{
    public int ID { get; set; }

    public string Name { get; set; }
}

public void Map()
{
    var config = new MapperConfiguration(cfg => cfg.CreateMap<Foo, FooDto>());

    var mapper = config.CreateMapper();

    Foo foo = new Foo { ID = 1, Name = "Tom" };

    FooDto dto = mapper.Map<FooDto>(foo);
}

2 註冊

在使用 Map 方法之前,首先要告訴 AutoMapper 什麼類可以映射到什麼類。

var config = new MapperConfiguration(cfg => cfg.CreateMap<Foo, FooDto>());

每個 AppDomain 只能進行一次配置。這意味着放置配置代碼的最佳位置是在應用程序啟動中,例如 ASP.NET 應用程序的 Global.asax 文件。

從 9.0 開始 Mapper.Initialize 方法就不可用了。

2.1 Profile

Profile 是組織映射的另一種方式。新建一個類,繼承 Profile,並在構造函數中配置映射。

public class EmployeeProfile : Profile
{
    public EmployeeProfile()
    {
        CreateMap<Employee, EmployeeDto>();
    }
}

var config = new MapperConfiguration(cfg =>
{
    cfg.AddProfile<EmployeeProfile>();
});

Profile 內部的配置僅適用於 Profile 內部的映射。應用於根配置的配置適用於所有創建的映射。

AutoMapper 也可以在指定的程序集中掃描從 Profile 繼承的類,並將其添加到配置中。

var config = new MapperConfiguration(cfg =>
{
    // 掃描當前程序集
    cfg.AddMaps(System.AppDomain.CurrentDomain.GetAssemblies());
    
    // 也可以傳程序集名稱(dll 名稱)
    cfg.AddMaps("LibCoreTest");
});

3 配置

3.1 命名約定

默認情況下,AutoMapper 基於相同的字段名映射,並且是 不區分大小寫 的。

但有時,我們需要處理一些特殊的情況。

  • SourceMemberNamingConvention 表示源類型命名規則
  • DestinationMemberNamingConvention 表示目標類型命名規則

LowerUnderscoreNamingConventionPascalCaseNamingConvention 是 AutoMapper 提供的兩個命名規則。前者命名是小寫並包含下劃線,後者就是帕斯卡命名規則(每個單詞的首字母大寫)。

我的理解,如果源類型和目標類型分別採用了 蛇形命名法駝峰命名法,那麼就需要指定命名規則,使其能正確映射。

public class Foo
{
    public int Id { get; set; }

    public string MyName { get; set; }
}

public class FooDto
{
    public int ID { get; set; }

    public string My_Name { get; set; }
}

public void Map()
{
    var config = new MapperConfiguration(cfg =>
    {
        cfg.CreateMap<Foo, FooDto>();

        cfg.SourceMemberNamingConvention = new PascalCaseNamingConvention();
        cfg.DestinationMemberNamingConvention = new LowerUnderscoreNamingConvention();
    });

    var mapper = config.CreateMapper();

    Foo foo = new Foo { Id = 2, MyName = "Tom" };

    FooDto dto = mapper.Map<FooDto>(foo);
}

3.2 配置可見性

默認情況下,AutoMapper 僅映射 public 成員,但其實它是可以映射到 private 屬性的。

var config = new MapperConfiguration(cfg =>
{
    cfg.ShouldMapProperty = p => p.GetMethod.IsPublic || p.SetMethod.IsPrivate;
    cfg.CreateMap<Source, Destination>();
});

需要注意的是,這裏屬性必須添加 private set,省略 set 是不行的。

3.3 全局屬性/字段過濾

默認情況下,AutoMapper 嘗試映射每個公共屬性/字段。以下配置將忽略字段映射。

var config = new MapperConfiguration(cfg =>
{
	cfg.ShouldMapField = fi => false;
});

3.4 識別前綴和後綴

var config = new MapperConfiguration(cfg =>
{
    cfg.RecognizePrefixes("My");
    cfg.RecognizePostfixes("My");
}

3.5 替換字符

var config = new MapperConfiguration(cfg =>
{
    cfg.ReplaceMemberName("Ä", "A");
});

這功能我們基本上用不上。

4 調用構造函數

有些類,屬性的 set 方法是私有的。

public class Commodity
{
    public string Name { get; set; }

    public int Price { get; set; }
}

public class CommodityDto
{
    public string Name { get; }

    public int Price { get; }

    public CommodityDto(string name, int price)
    {
        Name = name;
        Price = price * 2;
    }
}

AutoMapper 會自動找到相應的構造函數調用。如果在構造函數中對參數做一些改變的話,其改變會反應在映射結果中。如上例,映射后 Price 會乘 2。

禁用構造函數映射:

var config = new MapperConfiguration(cfg => cfg.DisableConstructorMapping());

禁用構造函數映射的話,目標類要有一個無參構造函數。

5 數組和列表映射

數組和列表的映射比較簡單,僅需配置元素類型,定義簡單類型如下:

public class Source
{
    public int Value { get; set; }
}

public class Destination
{
    public int Value { get; set; }
}

映射:

var config = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<Source, Destination>();
});
IMapper mapper = config.CreateMapper();

var sources = new[]
{
    new Source { Value = 5 },
    new Source { Value = 6 },
    new Source { Value = 7 }
};

IEnumerable<Destination> ienumerableDest = mapper.Map<Source[], IEnumerable<Destination>>(sources);
ICollection<Destination> icollectionDest = mapper.Map<Source[], ICollection<Destination>>(sources);
IList<Destination> ilistDest = mapper.Map<Source[], IList<Destination>>(sources);
List<Destination> listDest = mapper.Map<Source[], List<Destination>>(sources);
Destination[] arrayDest = mapper.Map<Source[], Destination[]>(sources);

具體來說,支持的源集合類型包括:

  • IEnumerable
  • IEnumerable
  • ICollection
  • ICollection
  • IList
  • IList
  • List
  • Arrays

映射到現有集合時,將首先清除目標集合。如果這不是你想要的,請查看AutoMapper.Collection。

5.1 處理空集合

映射集合屬性時,如果源值為 null,則 AutoMapper 會將目標字段映射為空集合,而不是 null。這與 Entity Framework 和 Framework Design Guidelines 的行為一致,認為 C# 引用,數組,List,Collection,Dictionary 和 IEnumerables 永遠不應該為 null

5.2 集合中的多態

這個官方的文檔不是很好理解。我重新舉個例子。實體類如下:

public class Employee
{
    public int ID { get; set; }

    public string Name { get; set; }
}

public class Employee2 : Employee
{
    public string DeptName { get; set; }
}

public class EmployeeDto
{
    public int ID { get; set; }

    public string Name { get; set; }
}

public class EmployeeDto2 : EmployeeDto
{
    public string DeptName { get; set; }
}

數組映射代碼如下:

var config = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<Employee, EmployeeDto>().Include<Employee2, EmployeeDto2>();
    cfg.CreateMap<Employee2, EmployeeDto2>();
});
IMapper mapper = config.CreateMapper();

var employees = new[]
{
    new Employee { ID = 1, Name = "Tom" },
    new Employee2 { ID = 2, Name = "Jerry", DeptName = "R & D" }
};

var dto = mapper.Map<Employee[], EmployeeDto[]>(employees);

可以看到,映射后,dto 中兩個元素的類型,一個是 EmployeeDto,一個是 EmployeeDto2,即實現了父類映射到父類,子類映射到子類。

如果去掉 Include 方法,則映射后 dto 中兩個元素的類型均為 EmployeeDto

6 方法到屬性映射

AutoMapper 不僅能實現屬性到屬性映射,還可以實現方法到屬性的映射,並且不需要任何配置,方法名可以和屬性名一致,也可以帶有 Get 前綴。

例如下例的 Employee.GetFullName() 方法,可以映射到 EmployeeDto.FullName 屬性。

public class Employee
{
    public int ID { get; set; }

    public string FirstName { get; set; }

    public string LastName { get; set; }

    public string GetFullName()
    {
        return $"{FirstName} {LastName}";
    }
}

public class EmployeeDto
{
    public int ID { get; set; }

    public string FirstName { get; set; }

    public string LastName { get; set; }

    public string FullName { get; set; }
}

7 自定義映射

當源類型與目標類型名稱不一致時,或者需要對源數據做一些轉換時,可以用自定義映射。

public class Employee
{
    public int ID { get; set; }

    public string Name { get; set; }

    public DateTime JoinTime { get; set; }
}

public class EmployeeDto
{
    public int EmployeeID { get; set; }

    public string EmployeeName { get; set; }

    public int JoinYear { get; set; }
}

如上例,IDEmployeeID 屬性名不同,JoinTimeJoinYear 不僅屬性名不同,屬性類型也不同。

var config = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<Employee, EmployeeDto>()
        .ForMember("EmployeeID", opt => opt.MapFrom(src => src.ID))
        .ForMember(dest => dest.EmployeeName, opt => opt.MapFrom(src => src.Name))
        .ForMember(dest => dest.JoinYear, opt => opt.MapFrom(src => src.JoinTime.Year));
});

8 扁平化映射

對象-對象映射的常見用法之一是將複雜的對象模型並將其展平為更簡單的模型。

public class Employee
{
    public int ID { get; set; }

    public string Name { get; set; }

    public Department Department { get; set; }
}

public class Department
{
    public int ID { get; set; }

    public string Name { get; set; }
}

public class EmployeeDto
{
    public int ID { get; set; }

    public string Name { get; set; }

    public int DepartmentID { get; set; }

    public string DepartmentName { get; set; }
}

如果目標類型上的屬性,與源類型的屬性、方法都對應不上,則 AutoMapper 會將目標成員名按駝峰法拆解成單個單詞,再進行匹配。例如上例中,EmployeeDto.DepartmentID 就對應到了 Employee.Department.ID

8.1 IncludeMembers

如果屬性命名不符合上述的規則,而是像下面這樣:

public class Employee
{
    public int ID { get; set; }

    public string Name { get; set; }

    public Department Department { get; set; }
}

public class Department
{
    public int DepartmentID { get; set; }

    public string DepartmentName { get; set; }
}

public class EmployeeDto
{
    public int ID { get; set; }

    public string Name { get; set; }

    public int DepartmentID { get; set; }

    public string DepartmentName { get; set; }
}

Department 類中的屬性名,直接跟 EmployeeDto 類中的屬性名一致,則可以使用 IncludeMembers 方法指定。

var config = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<Employee, EmployeeDto>().IncludeMembers(e => e.Department);
    cfg.CreateMap<Department, EmployeeDto>();
});

9 嵌套映射

有時,我們可能不需要展平。看如下例子:

public class Employee
{
    public int ID { get; set; }

    public string Name { get; set; }

    public int Age { get; set; }

    public Department Department { get; set; }
}

public class Department
{
    public int ID { get; set; }

    public string Name { get; set; }

    public string Heads { get; set; }
}

public class EmployeeDto
{
    public int ID { get; set; }

    public string Name { get; set; }

    public DepartmentDto Department { get; set; }
}

public class DepartmentDto
{
    public int ID { get; set; }

    public string Name { get; set; }
}

我們要將 Employee 映射到 EmployeeDto,並且將 Department 映射到 DepartmentDto

var config = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<Employee, EmployeeDto>();
    cfg.CreateMap<Department, DepartmentDto>();
});

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

環境篇:嘔心瀝血@CDH線上調優

環境篇:嘔心瀝血@CDH線上調優

  • 為什麼出這篇文章?

近期有很多公司開始引入大數據,由於各方資源有限,並不能合理分配服務器資源,和服務器選型,小恭弘=叶 恭弘這裏將工作中的總結出來,給新入行的小夥伴帶個方向,不敢說一定對,但是本人親自測試,發現集群使用率穩定提高了3分之1,最高可達到2分之1,有不對的地方歡迎留言指出。

注:可能有些服務沒有設計,使用到的小夥伴可以參照這種方式去規劃。

0 資源:集群服務安排

服務名稱 子服務 CM-64G ZK-Kafka(3台)-12G DataNode(3台)-64G NameNode1-64G NameNode2-64G Resourcemanager1-32G Resourcemanager2-32G hive-hbase-16G hive-hbase-16G
MySQL MySQL
CM Activity Monitor
Alert Publisher
Event Server
Host Monitor
Service Monitor




HDFS NameNode
DataNode
Failover Controller
JournalNode
X

X
X

X


X

X
X
X
Yarn NodeManager
Resourcemanager
JobHisoryServer

X
X
X

X

Zookeeper Zookeeper Server
Kafka Kafka Broker
Hive Hive Metastore Server
HiveServer2
Gateway(安裝對應應用服務器)
X


X

X
Hbase HMaster
HRegionServer
Thrift Server
X


X

X
Oozie Oozie Server
Hue Hue Server
Load Balancer
X

X
Spark History Server
Gateway(安裝對應應用服務器)

X
Flume Flume Agent (安裝對應應用服務器)
Sqoop Sqoop(安裝對應應用服務器)

1 優化:Cloudera Management

1.1 Cloudera Management Service

這些服務主要是提供監控功能,目前的調整主要集中在內存放,以便有足夠的資源 完成集群管理。

服務 選項 配置值
Activity Monitor Java Heap Size 2G
Alert Publisher Java Heap Size 2G
Event Server Java Heap Size 2G
Host Monitor Java Heap Size 4G
Service Monitor Java Heap Size 4G
Reports Manager Java Heap Size 2G
Navigator Metadata Server Java Heap Size 8G

2 優化:Zookeeper

服務 選項 配置值
Zookeeper Java Heap Size (堆棧大小) 4G
Zookeeper maxClientCnxns (最大客戶端連接數) 1024
Zookeeper dataDir (數據文件目錄+數據持久化路徑) /hadoop/zookeeper (建議獨立目錄)
Zookeeper dataLogDir (事務日誌目錄) /hadoop/zookeeper_log (建議獨立目錄)

3 優化:HDFS

3.1 磁盤測試

3.1.1 讀測試

hdparm 用於查看硬盤的相關信息或對硬盤進行測速、優化、修改硬盤相關參數設定

#安裝hdparm
yum install hdparm
#獲取硬盤符
fdisk -l
#讀測試(讀取上一步獲取硬盤符)
hdparm -t /dev/vda

三次測試結果:

Timing buffered disk reads: 500 MB in 0.84 seconds = 593.64 MB/sec

Timing buffered disk reads: 500 MB in 0.93 seconds = 538.80 MB/sec

Timing buffered disk reads: 500 MB in 0.74 seconds = 672.95 MB/sec

說明:接近1s秒讀取了500MB磁盤,讀速度約 500 MB/秒

3.1.2 寫測試

dd 這裏使用 time + dd 簡單測試寫速度,不要求很精確

查看內存緩存情況
free -m
 
清除緩存
sync; echo 3 > /proc/sys/vm/drop_caches
 
查block size
blockdev --getbsz /dev/vda
 
寫測試
echo 3 > /proc/sys/vm/drop_caches; time dd if=/dev/zero of=/testdd bs=4k count=100000

三次測試結果:

記錄了100000+0 的讀入
記錄了100000+0 的寫出

409600000 bytes (410 MB) copied, 0.574066 s, 714 MB/s –410MB複製,用時0.57秒,評估714M/s

409600000 bytes (410 MB) copied, 1.84421 s, 222 MB/s –410MB複製,用時1.84秒,評估222 M/s

409600000 bytes (410 MB) copied, 1.06969 s, 383 MB/s –410MB複製,用時1.06秒,評估383M/s

3.1.3 網絡帶寬

iperf3測量一個網絡最大帶寬

#安裝iperf3
yum -y install iperf3
 
#服務端
iperf3 -s
 
#客戶端
iperf3 -c 上調命令執行的服務機器IP

測試結果:

[ ID]–>線程id Interva–>傳輸時間 Transfer–>接收數據大小 Bandwidth–>帶寬每秒大小 Retr 角色
[ 4] 0.00-10.00 sec 17.0 GBytes 14.6 Gbits/sec 0 sender–>發送
[ 4] 0.00-10.00 sec 17.0 GBytes 14.6 Gbits/sec receiver–>接收

3.2 官方壓測

3.2.1 用戶準備

由於只能使用yarn配置了允許用戶,故這裏選擇hive用戶,如果su hive不能進入,則需要配置該步驟

usermod -s /bin/bash  hive
su hive

3.2.2 HDFS 寫性能測試

  • 測試內容:HDFS集群寫入10個128M文件(-D指定文件存儲目錄)
hadoop  jar /opt/cloudera/parcels/CDH-6.2.0-1.cdh6.2.0.p0.967373/jars/hadoop-mapreduce-client-jobclient-3.0.0-cdh6.2.0-tests.jar  TestDFSIO  -D test.build.data=/test/benchmark -write -nrFiles 10 -fileSize 128

INFO fs.TestDFSIO: —– TestDFSIO —– : write
INFO fs.TestDFSIO: Date & time: Thu Jun 11 10:30:36 CST 2020
INFO fs.TestDFSIO: Number of files: 10 –十個文件
INFO fs.TestDFSIO: Total MBytes processed: 1280 –總大小1280M
INFO fs.TestDFSIO: Throughput mb/sec: 16.96 –吞吐量 每秒16.96M
INFO fs.TestDFSIO: Average IO rate mb/sec: 17.89 –平均IO情況17.89M
INFO fs.TestDFSIO: IO rate std deviation: 4.74 –IO速率標準偏差
INFO fs.TestDFSIO: Test exec time sec: 46.33 –總運行時間

3.2.3 HDFS 讀性能測試

  • 測試內容:HDFS集群讀取10個128M文件
hadoop jar /opt/cloudera/parcels/CDH-6.2.0-1.cdh6.2.0.p0.967373/jars/hadoop-mapreduce-client-jobclient-3.0.0-cdh6.2.0-tests.jar TestDFSIO  -D test.build.data=/test/benchmark -read -nrFiles 10 -fileSize 128

INFO fs.TestDFSIO: —– TestDFSIO —– : read
INFO fs.TestDFSIO: Date & time: Thu Jun 11 10:41:19 CST 2020
INFO fs.TestDFSIO: Number of files: 10 –文件數
INFO fs.TestDFSIO: Total MBytes processed: 1280 –總大小
INFO fs.TestDFSIO: Throughput mb/sec: 321.53 –吞吐量 每秒321.53M
INFO fs.TestDFSIO: Average IO rate mb/sec: 385.43 –平均IO情況385.43M
INFO fs.TestDFSIO: IO rate std deviation: 107.67 –IO速率標準偏差
INFO fs.TestDFSIO: Test exec time sec: 20.81 –總運行時間

3.2.4 刪除測試數據

hadoop jar /opt/cloudera/parcels/CDH-6.2.0-1.cdh6.2.0.p0.967373/jars/hadoop-mapreduce-client-jobclient-3.0.0-cdh6.2.0-tests.jar TestDFSIO  -D test.build.data=/test/benchmark -clean

3.3 參數調優

服務 選項 配置值
NameNode Java Heap Size (堆棧大小) 56G
NameNode dfs.namenode.handler.count (詳見3.3.2) 80
NameNode dfs.namenode.service.handler.count (詳見3.3.2) 80
NameNode fs.permissions.umask-mode (使用默認值022) 027(使用默認值022)
DataNode Java Heap Size (堆棧大小) 8G
DataNode dfs.datanode.failed.volumes.tolerated (詳見3.3.3) 1
DataNode dfs.datanode.balance.bandwidthPerSec (DataNode 平衡帶寬) 100M
DataNode dfs.datanode.handler.count (服務器線程數) 64
DataNode dfs.datanode.max.transfer.threads (最大傳輸線程數) 20480
JournalNode Java Heap Size (堆棧大小) 1G

3.3.1 數據塊優化

dfs.blocksize = 128M

  • 文件以塊為單位進行切分存儲,塊通常設置的比較大(最小6M,默認128M),根據網絡帶寬計算最佳值。
  • 塊越大,尋址越快,讀取效率越高,但同時由於MapReduce任務也是以塊為最小單位來處理,所以太大的塊不利於於對數據的并行處理。
  • 一個文件至少佔用一個塊(如果一個1KB文件,佔用一個塊,但是佔用空間還是1KB)
  • 我們在讀取HDFS上文件的時候,NameNode會去尋找block地址,尋址時間為傳輸時間的1%時,則為最佳狀態。
    • 目前磁盤的傳輸速度普遍為100MB/S
    • 如果尋址時間約為10ms,則傳輸時間=10ms/0.01=1000ms=1s
    • 如果傳輸時間為1S,傳輸速度為100MB/S,那麼一秒鐘我們就可以向HDFS傳送100MB文件,設置塊大小128M比較合適。
    • 如果帶寬為200MB/S,那麼可以將block塊大小設置為256M比較合適。

3.3.2 NameNode 的服務器線程的數量

  • dfs.namenode.handler.count=20*log2(Cluster Size),比如集群規模為16 ,8以2為底的對數是4,故此參數設置為80
  • dfs.namenode.service.handler.count=20*log2(Cluster Size),比如集群規模為16 ,8以2為底的對數是4,故此參數設置為80

NameNode有一個工作線程池,用來處理不同DataNode的併發心跳以及客戶端併發的元數據操作。該值需要設置為集群大小的自然對數乘以20,。

3.3.3 DataNode 停止提供服務前允許失敗的卷的數量

DN多少塊盤損壞后停止服務,默認為0,即一旦任何磁盤故障DN即關閉。 對盤較多的集群(例如DN有超過2塊盤),磁盤故障是常態,通常可以將該值設置為1或2,避免頻繁有DN下線。

4 優化:YARN + MapReduce

服務 選項 配置值 參數說明
ResourceManager Java Heap Size (堆棧大小) 4G
ResourceManager yarn.scheduler.minimum-allocation-mb (最小容器內存) 2G 給應用程序 Container 分配的最小內存
ResourceManager yarn.scheduler.increment-allocation-mb (容器內存增量) 512M 如果使用 Fair Scheduler,容器內存允許增量
ResourceManager yarn.scheduler.maximum-allocation-mb (最大容器內存) 32G 給應用程序 Container 分配的最大內存
ResourceManager yarn.scheduler.minimum-allocation-vcores (最小容器虛擬 CPU 內核數量) 1 每個 Container 申請的最小 CPU 核數
ResourceManager yarn.scheduler.increment-allocation-vcores (容器虛擬 CPU 內核增量) 1 如果使用 Fair Scheduler,虛擬 CPU 內核允許增量
ResourceManager yarn.scheduler.maximum-allocation-vcores (最大容器虛擬 CPU 內核數量) 16 每個 Container 申請的最大 CPU 核數
ResourceManager yarn.resourcemanager.recovery.enabled true 啟用后,ResourceManager 中止時在群集上運行的任何應用程序將在 ResourceManager 下次啟動時恢復,備註:如果啟用 RM-HA,則始終啟用該配置。
NodeManager Java Heap Size (堆棧大小) 4G
NodeManager yarn.nodemanager.resource.memory-mb 40G 可分配給容器的物理內存數量,參照資源池內存90%左右
NodeManager yarn.nodemanager.resource.cpu-vcores 32 可以為容器分配的虛擬 CPU 內核的數量,參照資源池內存90%左右
ApplicationMaster yarn.app.mapreduce.am.command-opts 右紅 傳遞到 MapReduce ApplicationMaster 的 Java 命令行參數 “-Djava.net.preferIPv4Stack=true
ApplicationMaster yarn.app.mapreduce.am.resource.mb (ApplicationMaster 內存) 4G
JobHistory Java Heap Size (堆棧大小) 2G
MapReduce mapreduce.map.memory.mb (Map 任務內存) 4G 一個MapTask可使用的資源上限。如果MapTask實際使用的資源量超過該值,則會被強制殺死。
MapReduce mapreduce.reduce.memory.mb (Reduce 任務內存) 8G 一個 ReduceTask 可使用的資源上限。如果 ReduceTask 實際使用的資源量超過該值,則會被強制殺死
MapReduce mapreduce.map.cpu.vcores 2 每個 MapTask 可使用的最多 cpu core 數目
MapReduce mapreduce.reduce.cpu.vcores 4 每個 ReduceTask 可使用的最多 cpu core 數目
MapReduce mapreduce.reduce.shuffle.parallelcopies 20 每個 Reduce 去 Map 中取數據的并行數。
MapReduce mapreduce.task.io.sort.mb(Shuffle 的環形緩衝區大小) 512M 當排序文件時要使用的內存緩衝總量。注意:此內存由 JVM 堆棧大小產生(也就是:總用戶 JVM 堆棧 – 這些內存 = 總用戶可用堆棧空間)
MapReduce mapreduce.map.sort.spill.percent 80% 環形緩衝區溢出的閾值
MapReduce mapreduce.task.timeout 10分鐘 Task 超時時間,經常需要設置的一個參數,該參數表 達的意思為:如果一個 Task 在一定時間內沒有任何進 入,即不會讀取新的數據,也沒有輸出數據,則認為 該 Task 處於 Block 狀態,可能是卡住了,也許永遠會 卡住,為了防止因為用戶程序永遠 Block 住不退出, 則強制設置了一個該超時時間。如果你的程序對每條輸入數據的處理時間過長(比如會訪問數據庫,通過網絡拉取數據等),建議將該參數調大,該參數過小常出現的錯誤提示是 :AttemptID:attempt_12267239451721_123456_m_00 0335_0 Timed out after 600 secsContainer killed by the ApplicationMaster。

5 優化:Impala

服務 選項 配置值 參數說明
Impala Daemon mem_limit (內存限制) 50G 由守護程序本身強制執行的 Impala Daemon 的內存限制。
如果達到該限制,Impalad Daemon 上運行的查詢可能會被停止
Impala Daemon Impala Daemon JVM Heap 512M 守護進程堆棧大小
Impala Daemon scratch_dirs 節點上多塊獨立磁盤(目錄) Impala Daemon 將溢出信息等數據寫入磁盤以釋放內存所在的目錄。這可能是大量數據
Impala Catalog Server Java Heap Size 8G 堆棧大小

6 優化:Kafka

6.1 官方壓測

6.1.1 Kafka Producer 壓力測試

  • record-size 是一條信息有多大,單位是字節。
  • num-records 是總共發送多少條信息。
  • throughput 是每秒多少條信息,設成-1,表示不限流,可測出生產者最大吞吐量。
bash /opt/cloudera/parcels/CDH-6.2.0-1.cdh6.2.0.p0.967373/lib/kafka//bin/kafka-producer-perf-test.sh --topic test --record-size 100 --num-records 100000 --throughput -1 --producer-props bootstrap.servers=cdh01.cm:9092,cdh02.cm:9092,cdh03.cm:9092

100000 records sent, 225733.634312 records/sec (21.53 MB/sec),

8.20 ms avg latency, 66.00 ms max latency,

3 ms 50th, 28 ms 95th, 30 ms 99th, 30 ms 99.9th.

參數解析:一共寫入 10w 條消息,吞吐量為 21.53 MB/sec,每次寫入的平均延遲

為 8.20 毫秒,最大的延遲為 66.00 毫秒。

6.1.2 Kafka Consumer 壓力測試

  • zookeeper 指定 zookeeper 的鏈接信息
  • topic 指定 topic 的名稱
  • fetch-size 指定每次 fetch 的數據的大小
  • messages 總共要消費的消息個數
bash /opt/cloudera/parcels/CDH-6.2.0-1.cdh6.2.0.p0.967373/lib/kafka//bin/kafka-consumer-perf-test.sh --broker-list cdh01.cm:9092,cdh02.cm:9092,cdh03.cm:9092 --topic test --fetch-size 10000 --messages 10000000 --threads 1

start.time, end.time, data.consumed.in.MB, MB.sec, data.consumed.in.nMsg, nMsg.sec, rebalance.time.ms, fetch.time.ms, fetch.MB.sec, fetch.nMsg.sec

2020-06-11 17:53:48:179, 2020-06-11 17:54:04:525, 57.2205, 3.5006, 600000, 36706.2278, 3051, 13295, 4.3039, 45129.7480

start.time:2020-06-11 17:53:48:179 開始時間

end.time:2020-06-11 17:54:04:525 結束時間(用時16秒)

data.consumed.in.MB:57.2205 消費57M數據

MB.sec:3.5006 3.5M/S

data.consumed.in.nMsg:600000 消費60萬消息

nMsg.sec:36706.2278 36706條消息/S

rebalance.time.ms:3051 平衡時間3S

fetch.time.ms:13295 抓取時間13S

fetch.MB.sec:4.3039 一秒抓取4.3M

fetch.nMsg.sec:45129.7480 一秒抓取45129條消息

開始測試時間,測試結束數據,共消費數據57.2205MB,吞吐量 3.5M/S,共消費600000條,平均每秒消費36706.2278條。

6.1.3 Kafka 機器數量計算

Kafka 機器數量(經驗公式)= 2 X(峰值生產速度 X 副本數 /100)+ 1

先拿到峰值生產速度,再根據設定的副本數,就能預估出需要部署 Kafka 的數量。

比如我們的峰值生產速度是 50M/s。副本數為 2。

Kafka 機器數量 = 2 X( 50 X 2 / 100 )+ 1 = 3 台

6.2 參數調優

服務 選項 配置值 參數說明
Kafka Broker Java Heap Size of Broker 2G Broker堆棧大小
Kafka Broker Data Directories 多塊獨立磁盤
Kafka 服務 Maximum Message Size 10M 服務器可以接收的消息的最大大小。此屬性必須與使用者使用的最大提取大小同步。否則,不守規矩的生產者可能會發布太大而無法消費的消息
Kafka 服務 Replica Maximum Fetch Size 20M 副本發送給leader的獲取請求中每個分區要獲取的最大字節數。此值應大於message.max.bytes。
Kafka 服務 Number of Replica Fetchers 6 用於複製來自領導者的消息的線程數。增大此值將增加跟隨者代理中I / O并行度。

7 優化:HBase

服務 選項 配置值 參數說明
HBase Java Heap Size 18G 客戶端 Java 堆大小(字節)主要作用來緩存Table數據,但是flush時會GC,不要太大,根據集群資源,一般分配整個Hbase集群內存的70%,16->48G就可以了
HBase hbase.client.write.buffer 512M 寫入緩衝區大小,調高該值,可以減少RPC調用次數,單數會消耗更多內存,較大緩衝區需要客戶端和服務器中有較大內存,因為服務器將實例化已通過的寫入緩衝區並進行處理,這會降低遠程過程調用 (RPC) 的數量。
HBase Master Java Heap Size 8G HBase Master 的 Java 堆棧大小
HBase Master hbase.master.handler.count 300 HBase Master 中啟動的 RPC 服務器實例數量。
HBase RegionServer Java Heap Size 31G HBase RegionServer 的 Java 堆棧大小
HBase RegionServer hbase.regionserver.handler.count 100 RegionServer 中啟動的 RPC 服務器實例數量,根據集群情況,可以適當增加該值,主要決定是客戶端的請求數
HBase RegionServer hbase.regionserver.metahandler.count 60 用於處理 RegionServer 中的優先級請求的處理程序的數量
HBase RegionServer zookeeper.session.timeout 180000ms ZooKeeper 會話延遲(以毫秒為單位)。HBase 將此作為建議的最長會話時間傳遞給 ZooKeeper 仲裁
HBase RegionServer hbase.hregion.memstore.flush.size 1G 如 memstore 大小超過此值,Memstore 將刷新到磁盤。通過運行由 hbase.server.thread.wakefrequency 指定的頻率的線程檢查此值。
HBase RegionServer hbase.hregion.majorcompaction 0 合併周期,在合格節點下,Region下所有的HFile會進行合併,非常消耗資源,在空閑時手動觸發
HBase RegionServer hbase.hregion.majorcompaction.jitter 0 抖動比率,根據上面的合併周期,有一個抖動比率,也不靠譜,還是手動好
HBase RegionServer hbase.hstore.compactionThreshold 6 如在任意一個 HStore 中有超過此數量的 HStoreFiles,則將運行壓縮以將所有 HStoreFiles 文件作為一個 HStoreFile 重新寫入。(每次 memstore 刷新寫入一個 HStoreFile)您可通過指定更大數量延長壓縮,但壓縮將運行更長時間。在壓縮期間,更新無法刷新到磁盤。長時間壓縮需要足夠的內存,以在壓縮的持續時間內記錄所有更新。如太大,壓縮期間客戶端會超時。
HBase RegionServer hbase.client.scanner.caching 1000 內存未提供數據的情況下掃描儀下次調用時所提取的行數。較高緩存值需啟用較快速度的掃描儀,但這需要更多的內存且當緩存為空時某些下一次調用會運行較長時間
HBase RegionServer hbase.hregion.max.filesize 50G HStoreFile 最大大小。如果列組的任意一個 HStoreFile 超過此值,則託管 HRegion 將分割成兩個
HBase Master hbase.master.logcleaner.plugins 日誌清除器插件 org.apache.hadoop.hbase.master.cleaner.TimeToLiveLogCleaner
HBase hbase.replication false 禁用複製
HBase hbase.master.logcleaner.ttl 10min 保留 HLogs 的最長時間,加上如上兩條解決oldWALs增長問題

8 優化:Hive

服務 選項 配置值 參數說明
HiveServer2 Java Heap Size 4G
Hive MetaStore Java Heap Size 8G
Hive Gateway Java Heap Size 2G
Hive hive.execution.engine Spark 執行引擎切換
Hive hive.fetch.task.conversion more Fetch抓取修改為more,可以使全局查找,字段查找,limit查找等都不走計算引擎,而是直接讀取表對應儲存目錄下的文件,大大普通查詢速度
Hive hive.exec.mode.local.auto(hive-site.xml 服務高級配置,客戶端高級配置) true 開啟本地模式,在單台機器上處理所有的任務,對於小的數據集,執行時間可以明顯被縮短
Hive hive.exec.mode.local.auto.inputbytes.max(hive-site.xml 服務高級配置,客戶端高級配置) 50000000 文件不超過50M
Hive hive.exec.mode.local.auto.input.files.max(hive-site.xml 服務高級配置,客戶端高級配置) 10 個數不超過10個
Hive hive.auto.convert.join 開啟 在join問題上,讓小表放在左邊 去左鏈接(left join)大表,這樣可以有效的減少內存溢出錯誤發生的幾率
Hive hive.mapjoin.smalltable.filesize(hive-site.xml 服務高級配置,客戶端高級配置) 50000000 50M以下認為是小表
Hive hive.map.aggr 開啟 默認情況下map階段同一個key發送給一個reduce,當一個key數據過大時就發生數據傾斜。
Hive hive.groupby.mapaggr.checkinterval(hive-site.xml 服務高級配置,客戶端高級配置) 200000 在map端進行聚合操作的條目數目
Hive hive.groupby.skewindata(hive-site.xml 服務高級配置,客戶端高級配置) true 有數據傾斜時進行負載均衡,生成的查詢計劃會有兩個MR Job,第一個MR Job會將key加隨機數均勻的分佈到Reduce中,做部分聚合操作(預處理),第二個MR Job在根據預處理結果還原原始key,按照Group By Key分佈到Reduce中進行聚合運算,完成最終操作
Hive hive.exec.parallel(hive-site.xml 服務高級配置,客戶端高級配置) true 開啟并行計算
Hive hive.exec.parallel.thread.number(hive-site.xml 服務高級配置,客戶端高級配置) 16G 同一個sql允許的最大并行度,針對集群資源適當增加

9 優化:Oozie、Hue

服務 選項 配置值 參數說明
Oozie Java Heap Size 1G 堆棧大小
Hue Java Heap Size 4G 堆棧大小

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

【其他文章推薦】

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

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

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

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

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

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

Android官方新推的DI庫 Hilt

Android官方新推的DI庫 Hilt

Hilt是Google Android官方新推薦的依賴注入工具.
已加入到官方文檔: Dependency injection with Hilt. 目前是alpha release階段.

Hilt是在Dagger之上, Hilt單詞的意思是: 刀把, 柄.
代碼庫還是這個google/dagger.

Hilt的出現, 讓我想起了曾經曇花一現的dagger.android, 不知道hilt能不能經得住時間的考驗.

本文介紹Hilt的基本使用. 熟悉dagger的朋友可能會發現很容易.

Hilt是花里胡哨的小打小鬧? 還是下一個主流工具? 讓我們拭目以待.

Codelab練習例子

  • Codelab: Using Hilt in your Android app.

我的Fork: https://github.com/mengdd/android-hilt.

這個項目最開始是一個用ServiceLocator手動注入的例子, 功能是記錄用戶點擊button操作, 显示log. 有兩個Fragment和Activity.

通過Codelab中一系列步驟的改造, 最終改成用Hilt做依賴注入.

本文舉例就用其中的代碼片段了.

原repo還貼心地附上了改造前後的對比diff: Full codelab comparison.

Hilt依賴添加

project root: build.gradle:

buildscript {
    ext.hilt_version = '2.28-alpha'
    ...
    dependencies {
        ...
        classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
    }
}

需要Android 4.0以上.

app/build.gradle:

apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'

android {
    ...
}

dependencies {
    // Hilt dependencies
    implementation "com.google.dagger:hilt-android:$hilt_version"
    kapt "com.google.dagger:hilt-android-compiler:$hilt_version"

    // Hilt testing dependency
    androidTestImplementation "com.google.dagger:hilt-android-testing:$hilt_version"
    // Make Hilt generate code in the androidTest folder
    kaptAndroidTest "com.google.dagger:hilt-android-compiler:$hilt_version"
}

後面兩個是測試的依賴.

概念, 用詞解釋

  • Container, 對應Component.
  • Bindings, 對應依賴.
The information Hilt has about how to provide instances of different types are also called bindings.
  • Component hierarchy是指依賴在當前component中可以用, 也可以在它包含的child component中用. 舉例: application容器中的依賴activity可以用, 但是反過來不行.

Hilt基本使用

  • @HiltAndroidApp: 標記在Application類上, 觸發代碼生成, application container是整個app的parent container.
@HiltAndroidApp
class LogApplication : Application()
  • @AndroidEntryPoint: 標記在Activity和Fragment上. 創建了一個和當前Activity/fragment生命周期相關的container. 目前支持的類型是: Activity, Fragment, View, Service, BroadcastReceiver.
@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() { ... }

每次Fragment(或Activity等)創建都會有它對應的container實例.

  • 字段注入: 用@Inject標記字段, 注意字段不能是private的.
@AndroidEntryPoint
class LogsFragment : Fragment() {

    @Inject
    lateinit var dateFormatter: DateFormatter
    ...
}


Component lifetimes可以看到每種類型的component創建和注入的時間.

Activity是onCreate(), Fragment是onAttach().

對Fragment來說, 在onAttach()的時候完成了對象的注入, 之後訪問對象都沒有問題.

  • 提供依賴: 用@Inject標記構造器.
class DateFormatter @Inject constructor() {
    ...
}

它的依賴可以在構造參數中標明.

Hilt的Scope支持

默認所有的依賴都是沒有scope的, 每次注入依賴都創建新的實例.
Hilt自動在對應的生命周期創建/銷毀對象: Component lifetimes.

也可以把依賴scope到某個component, 這樣在這個component內, 依賴就是單例.

  • scope: @Singleton: application container的scope, 說明是application範圍內的單例. @ActivityScoped對應activity component.

所有可用的scope: Component scopes.

module

module的使用基本和dagger一樣, 用來提供一些無法用構造@Inject的依賴, 比如接口, 第三方庫類型, Builder模式構造的對象等.

  • @Module: 標記這是一個module. 在Kotlin代碼中, module可以是一個object.
  • @Provides: 標記方法, 提供返回值類型的依賴.
  • @Binds: 標記抽象方法, 返回接口類型, 實現是方法的唯一參數.

Hilt多了一個註解:

  • @InstallIn: 指明module對應哪個container, 也即Component.
    Generated components for Android classes
@InstallIn(ApplicationComponent::class)
@Module
object DatabaseModule {

    @Provides
    @Singleton
    fun provideDatabase(@ApplicationContext appContext: Context): AppDatabase {
        return Room.databaseBuilder(
            appContext,
            AppDatabase::class.java,
            "logging.db"
        ).build()
    }

    @Provides
    fun provideLogDao(database: AppDatabase): LogDao {
        return database.logDao()
    }
}

module的拆分

module的名字最好能傳達它提供了什麼類型的依賴, 所以多種依賴拆分多個modules寫比較好.

@InstallIn(ActivityComponent::class)
@Module
abstract class NavigationModule {
    @Binds
    abstract fun bindNavigator(impl: AppNavigatorImpl): AppNavigator
}

@Binds@Provides不能放在同一個module里.
因為@Binds需要module是一個abstract class, 而@Provides需要module是一個object.
放一起會報錯: error: A @Module may not contain both non-static and abstract binding methods.

默認依賴

有一些默認依賴是已經有的, 比如:

  • @ApplicationContext.
  • @ActivityContext.

可以直接作為@Provides方法或@Inject構造的參數.

默認依賴是和component對應的.

Component default bindings

Qualifier

要提供同一個接口的不同實現, 可以用不同的註解來標記. (dagger之前用的是@Named).

A qualifier is an annotation used to identify a binding.

舉例: LoggerDataSource接口提供了內存和數據庫兩種實現.

定義兩個註解:

@Qualifier
annotation class InMemoryLogger

@Qualifier
annotation class DatabaseLogger

module中提供的時候用來標記相應的依賴:

@InstallIn(ApplicationComponent::class)
@Module
abstract class LoggingDatabaseModule {

    @DatabaseLogger
    @Singleton
    @Binds
    abstract fun bindDatabaseLogger(impl: LoggerLocalDataSource): LoggerDataSource
}

@InstallIn(ActivityComponent::class)
@Module
abstract class LoggingInMemoryModule {

    @InMemoryLogger
    @ActivityScoped
    @Binds
    abstract fun bindInMemoryLogger(impl: LoggerInMemoryDataSource): LoggerDataSource
}

這裏用了兩個module因為它們對應兩個不同的component, 一個是application一個是activity, 依賴也是相應的scope.

注入的時候也對應加上:

@InMemoryLogger
@Inject
lateinit var logger: LoggerDataSource

@EntryPoint

Hilt支持最常用的Android組件, 對於默認不支持的類型, 如果還想做字段注入, 需要用@EntryPoint.

注意這裏只是限制了字段注入的情況, 自定義類型一般用構造注入比較方便(如果能用的話).

@EntryPoint的意思就是一個邊界點, 這裏可以通往Hilt的世界, 得到容器提供的依賴對象們.

Codelab中的例子是一個ContentProvider.

關鍵的部分是這段代碼:

@InstallIn(ApplicationComponent::class)
@EntryPoint
interface LogsContentProviderEntryPoint {
    fun logDao(): LogDao
}

private fun getLogDao(appContext: Context): LogDao {
    val hiltEntryPoint = EntryPointAccessors.fromApplication(
        appContext,
        LogsContentProviderEntryPoint::class.java
    )
    return hiltEntryPoint.logDao()
}

Hilt and Dagger

當初的dagger.android已被放棄, 它是為了簡化dagger在Android上的使用而單獨推出的. (根據Activity和Fragment的生命周期, 開發者不用手動調用inject方法, 但是確實最開始的setup code比較多.)

Hilt相對於Dagger來說, 簡化了幾個點:

  • 不用自己創建Component.
  • 不用手動調用inject()方法來完成字段注入.
  • 不用自己在Application中保存component.
  • 提供了一些Scope, 不用自己定義和寫.
  • 提供了一些默認依賴, 比如Context.

總體來說Hilt就是針對Android做的定製, 讓依賴注入框架用起來更方便. 畢竟dagger是一個java注入庫, 它的應用範圍不限於Android.

因為Hilt和Dagger可以共存, 可以逐步遷移. 既然官方推薦了, 可以在項目內小範圍地先試試.

最後推薦這個cheat sheet

Reference

  • Dependency Injection on Android with Hilt
  • Dependency injection with Hilt
  • Dagger Hilt

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

【其他文章推薦】

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

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

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

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

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