碾壓日系的美式車才5.5L超低油耗 車主還有這些技巧

底盤離地間隙略微有點小,不太敢騎上馬路牙。隔音水平一般,如果走上一條爛路,與車內人員溝通基本上要靠喊。懸架的濾震能力一般,聲音聽起來比較脆。目前行駛里程與油耗:現在已經開了5680公里,百公里綜合油耗在5。5升左右。

前言

有一款美系小型車靜悄悄地爬到了小型車銷量榜的前列,它就是賽歐。這可能出乎很多人的意料,不過事實勝於雄辯。下面看看三位車主的說法,了解一下賽歐何德何能銷量可以越過飛度。

上汽通用雪佛蘭-賽歐

指導價:6.29-7.99萬

車主:555

購買車型:2016款 賽歐3 1.3L 手動舒適天窗版

裸車購買價:5.44萬

最滿意的點:空間很大,不會有壓抑感,而且有天窗顯得格外敞亮。天窗沒有一鍵關閉,可有效防止小孩誤操作導致夾傷。還有不少貼心提示,例如沒關大燈熄火的話,車子會滴滴聲提示;插着鑰匙開着駕駛座的車門也會滴滴聲提示。另外,這車開到120km/h也很穩,不會有發飄的感覺。

最不滿意的點:一擋和倒擋不太容易掛進去,吸入感不強。車窗的升降控制按鍵位於中控台下方,而且晚上沒有燈光提示,只能盲操,不符合人機工程學的設計。沒有中央扶手,感覺肘部欠缺了一點承托。

目前行駛里程與油耗:現在已經開了2600公里,百公里綜合油耗在6升左右。

車主:藝術家

購買車型:2015款 賽歐3 1.3L 手動理想版

裸車購買價:6.2萬

最滿意的點:乘坐空間比之前的愛麗舍大了不少,儲物空間也比較多。開機時,儀錶的各項自檢動作倍有范。喇叭的音質不錯,對得起這個價位。後排提供有安全座椅接口,使用也很便利。動力上感覺也很充足。

最不滿意的點:自動啟停並不好用,有點雞肋。底盤離地間隙略微有點小,不太敢騎上馬路牙。隔音水平一般,如果走上一條爛路,與車內人員溝通基本上要靠喊。懸架的濾震能力一般,聲音聽起來比較脆。

目前行駛里程與油耗:現在已經開了5680公里,百公里綜合油耗在5.5升左右。

車主:煎餅果子

購買車型:2015款 賽歐3 1.3L AMT理想版

裸車購買價:5.85萬

最滿意的點:AMT變速箱可以在手動與自動模式來回切換,既可以滿足駕駛樂趣,又能照顧日常所需。后尾廂的空間足夠大。帶有主副駕駛位的氣囊,還全系標配ABS和ESp,行車帶自動落鎖。方向盤的電動助力比較輕盈,沒有搶手的感覺。日常駕駛的時候,車子沒有明顯頓挫感。

最不滿意的點:後排座椅無法放倒,想放些長的物品也不可以。胎噪明顯,輪拱處缺乏隔音。后懸架偏硬,影響後排乘客的乘坐感受。后尾廂無法遙控開啟,很不方便。後輪剎車為鼓剎,顯得很廉價。後排頭枕為一體式,無法根據實際需要調節高度。

目前行駛里程與油耗:現在已經開了1700公里,百公里綜合油耗為6.2L。

編者總結:

賽歐的銷量之所以如此好,全憑其低廉的價格與良好的品質。相比起飛度動輒7、8萬的售價,賽歐顯得更為親民。不過,手動擋車型日常使用會比較麻煩。而AMT車型又會有明顯的動力中斷,所以想選擇賽歐作為家用車需要好好想想是否能接受它的這些缺點。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

※回頭車貨運收費標準

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

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

都是六萬多,這三個漂亮的、配置高的SUV哪個是你的菜

如果車長真的達到4420mm,軸距也不會只有2510mm。雖然都是“3”,但是瑞虎3和DX3是截然不同的兩種設計風格。DX3走的完全是時尚路線,瑞虎3則成熟穩重許多。沒有改款前的內飾看起來比較過時,如今2016款的造型還說的過去。變得更加有層次感,看起來也年輕許多了。

六萬多的裸車,想買一台不錯的車子,只有考慮自主品牌的車子了,因為這個價位合資車幾乎沒有什麼好的選擇,要麼就是丑到家,要麼就是配置很寒磣。

但是如果選擇自主品牌的話,那麼路一下子就會變得特別寬,可以有很多不錯的選擇,尤其是對於自主品牌密集布局的SUV領域。所以小編選出了下面三台車子,看看哪台是你的菜。

東南汽車-東南DX3

指導價:6.79-10.09萬

DX3的車身尺寸為4354*1840*1670mm,軸距為2610mm,因為DX7的外觀獲得了不少消費者的讚譽,所以這次DX3的設計還是由賓尼法利納設計中心完成。不得不說,DX3的外觀確實比較驚艷,犀利的前臉,凌厲的腰線,懸浮式車頂設計,這一切都讓DX3看起來非常時尚。

內飾和外觀一樣,不僅好看,更重要的是原創性也非常高。據DX3的設計師設計透露,DX3的內飾設計風格為“永恆之美”,反正我也不知道什麼叫做永恆之美,只知道看起來挺時尚的。不過內飾的配色也很豐富,可以提供棕+黑、紅+黑兩種配色。

DX3的空間表現很好,後排空間較為寬裕,在同級別中處於中等偏上的水準。它的動力系統為1.5L 120馬力+5擋手動,1.5T 156馬力+CVT變速箱。其實如果預算比較吃緊的話,1.5L的發動機就可以滿足日常家用了,即使是最低配,也會配備車身穩定系統、胎壓監測、上坡輔助、陡坡緩降、后視鏡電動調節。安全配置和實用配置都有,所以對於資金不充足的消費者來說,低配就夠了。

奇瑞汽車-瑞虎3

指導價:6.89-9.29萬

瑞虎3的車身尺寸為4420*1760*1670mm,軸距為2510mm。不要被數據蒙蔽了,其實虎3的車長是加上那個外掛的備胎了。如果車長真的達到4420mm,軸距也不會只有2510mm。雖然都是“3”,但是瑞虎3和DX3是截然不同的兩種設計風格。DX3走的完全是時尚路線,瑞虎3則成熟穩重許多。

沒有改款前的內飾看起來比較過時,如今2016款的造型還說的過去。變得更加有層次感,看起來也年輕許多了。尤其是中控大屏,可以立馬增加中控的檔次感。

瑞虎3的動力系統為1.6L 126馬力+5擋手動/CVT變速箱。老款的瑞虎3沒有車身穩定系統,遭到了吐槽,現在瑞虎3終於標配了ESp,性價比提升了很多。同時奇瑞的瑞虎系列也有了一定的歷史了,質量還是挺讓人放心的,不管是手動擋還是CVT車型,質量方面都有着比較穩定的表現。

凱翼汽車-凱翼X3

指導價:6.66-9.69萬

凱翼X3的車身尺寸為4335*1796*1665mm,軸距為2530mm。凱翼X3也是小型SUV,車身尺寸在同級別並不佔什麼優勢,雖然凱翼X3的外觀經過了重新的設計,變得年輕時尚許多,但是在車身側面和車尾,仍有一些瑞虎3的影子。

中控內飾極其簡約,沒有什麼複雜的線條做映襯,可以看出來設計師是盡量追求簡介的設計風格,中控台的按鍵數目屈指可數。不過中控台的用料很不錯。部分區域採用了搪塑工藝,手感較好。

凱翼X3的動力系統為1.6L 126馬力+5擋手動/CVT,動力表現和瑞虎3較為相近。其實就配置而言,凱翼X3的配置要低於DX3和瑞虎3的。不過凱翼X3也有着自己的特色,比如更加討人喜歡的外觀。

總結:相對來說DX3和瑞虎3的性價比都很高,配置很實在,DX3的外觀看起來更年輕,瑞虎3則更適合比較穩重的消費者,至於凱翼X3,如果你覺得瑞虎3的外觀有點過於成熟的話,那麼可以看看凱翼X3,雖然它的性價比沒有瑞虎3那麼高。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

※回頭車貨運收費標準

要是明年別克將這車國產 告訴我你買不買賬?

基於輕量化平台打造后,新君威不僅車重減輕,車身和軸距都相應加長了55mm和92mm,加上車尾採用的溜背設計,未來新君威的後排空間必定提升不少。新君威在未來也有望搭載1。5T/2。0T發動機,其中1。5T車型極有可能搭載7速雙離合變速箱。

相信大家對別克君威都不陌生,這輛中級轎車從以前主打舒適穩重的風格向後期年輕化的運動范轉變。而最近它的姊妹車歐寶Insignia實車也亮相了,新一代君威的外觀基本會與其相同。

可能很多人已經淡忘了歐寶這個品牌,實際上作為通用旗下的子品牌,歐寶、別克和沃克斯豪爾(Vaxuhall)都存在着換標車型。同一款車彼此換上不同的logo,在不同的國家地區發售。

而歐寶Insignia所對應的車型分別是別克的君威和霍頓的Commodore(沒錯,霍頓同樣是通用的子品牌)。這輛未來的新君威走了略帶豪華的運動風格,不論是前臉還是腰線的設計都顯得非常運動,只不過到時進來國產的時候,外觀肯定會有一些調整,而這款Insignia則有可能在明年日內瓦車展發布,而新君威則有望在明年國產。

基於輕量化平台打造后,新君威不僅車重減輕,車身和軸距都相應加長了55mm和92mm,加上車尾採用的溜背設計,未來新君威的後排空間必定提升不少。

新君威在未來也有望搭載1.5T/2.0T發動機,其中1.5T車型極有可能搭載7速雙離合變速箱。只不過在國內眾多消費者對於別克的變速箱印象都很一般,未來國產車型還是得要多花點心思好好調整下了。

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

【其他文章推薦】

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

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

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

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

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

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

※回頭車貨運收費標準

真的假的?10萬級的國產SUV已經擁有媲美合資的能力了!

東風風神AX5新車指導價:8。97-12。87萬在外觀方面東風風神AX5擁有一個極高的原創外觀,採用了雙橫幅的鍍鉻前格柵,下部進氣口採用梯形設計,同時保險杠兩側還配備有類似進氣口造型的豎條狀LED日間行車燈,此外紅色卡鉗以及使用18寸風火輪式輪轂更能彰顯AX5的運動氣質,而AX5的離地間距達到190mm,處於中上游水準。

合資水平是目前眾多國產車廠商一直追求境界。為什麼總有的人會說國產車追不上合資車型?在看來最主要的原因是個人的感受方面。

國產品牌與合資到底差在哪兒?

品牌影響力上的差距!“國產”“合資”看起來就覺得合資更高端,在這裏也存在一種崇洋媚外的心理。現在的消費者買車更看重面子!國產品牌在眾多人心中依舊是“山寨”“廉價”的代名詞,如果讓親戚朋友看到你開一台國產車,總是會覺得低人一等。在很多人看來,為什麼要買國產車?並不是因為愛過情懷,而是錢不夠。

外觀內飾設計上的差距?認為設計是一種很主觀的東西,但凡有一些比較潮流的設計出現,喜歡的人會被吸引進來,不喜歡的人只會吐槽。所以在汽車設計方面越平庸的車型越受廣大消費者所喜愛,而且中國人更喜歡“大”這一個詞語,於是現在的新款車型在尺寸方面都越做越大。至於合資和國產是否存在差距?覺得現在國產車的設計已經相當不錯,除了那幾個不爭氣堅持模仿的廠商,大多都擁有了自己的一套家族式設計語言,而且原創度都相當高!

行駛質感的差距!有的人說國產車型開上幾年底盤感覺就像要散掉一般,跟合資車型完全無法比較。決定汽車行駛整體的質感與汽車廠家的調校功力成正比關係,就像法系車能把扭力梁調得比你的獨立懸挂更牛逼一樣,幾乎所有的廠家將自己那套底盤數據當成國家機密一樣。而調校就是國產品牌的硬傷,有能力的國產品牌可以選擇直接向合資廠家買比較過時的底盤數據、或者直接給錢外包給國外的調校團隊,投入資金是相龐大的。說句難聽點的,有部分的國產廠商的車型在看來壓根就是跳過了“調校”這個步驟。

國產品牌就這麼菜?

其實並不是,相反10萬左右的購車預算,選擇國產車型性價比更高!首先是配置更高、而且車子比合資車型更為大氣!有的人說國產車質量不行,國產車型的故障率是稍高,但這是對於故障率極低的品牌而言(就像豐田這些),不是所有的合資品牌故障率都很低的!10年前的國產車都在路上跑着,也不要說現在的新車型了。

外觀夠大氣、配置夠高、內飾檔次夠高、車型質量過關!是判斷一台國產車型能否擁有與合資車競爭的最主要因素!

東風風神AX5

新車指導價:8.97-12.87萬

在外觀方面東風風神AX5擁有一個極高的原創外觀,採用了雙橫幅的鍍鉻前格柵,下部進氣口採用梯形設計,同時保險杠兩側還配備有類似進氣口造型的豎條狀LED日間行車燈,此外紅色卡鉗以及使用18寸風火輪式輪轂更能彰顯AX5的運動氣質,而AX5的離地間距達到190mm,處於中上游水準。

中控台設計層次感挺高,採用了軟性材料覆蓋給到的質感也非常不錯,而且最讓喜歡的是它的實用性配置上,像副駕駛地下的鞋盒給愛穿高跟鞋的女生提供了放鞋的地方、配備了獨立的8英寸液晶屏和一鍵啟動等配置,內置的Windlink系統支持與安卓手機的連接。

AX5提供1.4T渦輪增壓發動機,與之匹配的是一台5擋手動/6擋擋雙離合,在質量方面值得肯定,另外AX5的底盤平台源自東風與pSA集團共同研發的DF2平台,在行駛質感方面真心不錯。

榮威RX5

新車指導價:9.98-18.68萬

榮威RX5在上市之初一直以“網紅”的形式登場,極高的顏值受到廣泛消費者的關注,作為榮威“律動設計”理念下的首款車型,“展翼格柵”的中控與矩陣式LED大燈相連,整體造型時尚大氣,原創度極高,逼格十足。

採用黃金比例進行打造設計的中控台也是滿滿的逼格,大面積使用軟材料包裹,摸到哪兒都是軟的,中控10.4英寸的大显示屏更是成為了中控台的點睛之筆,整體給人的質感相當高檔。另外與阿里共同打造出來的車載互聯繫統更是將其“網紅”的定位推上了頂峰。

在動力方面,1.5T的動力更適合家用、2.0T的動力能帶給你非常不俗的駕駛樂趣,在質量方面,榮威背後有着通用這個大集團的撐腰,無論在品控還是質量方面都值得肯定。

合資的选手實力也不俗!

雪鐵龍C3-XR

新車指導價:10.88-17.18萬

法系車一直不被人看好,車頭採用了更多平直的線條,彰顯飽滿風格。大燈簡單耐看,內部線條與前格柵相連接,整體感更強。而側面的線條緊湊動感,不規則的輪圈樣式也頗為個性。儘管它擁有着同級領先的底盤調校技術,但是配置搭配不合理是它主要的缺點,不過法系車也就有一個優點:那就是底盤紮實好開。

現代ix25

新車指導價:11.98-18.68萬

韓國車一直以高顏值作為賣點,所以無論旗下哪款車型,無疑不是顏值極高的选手,當然韓系車另外一個賣點就是配置比同級別的合資車型配置更高,而且韓系車在終端優惠方面很大,所以在新車落地價上會比較便宜。也可以這麼說,現代ix25在實用性與家用兩方面是做得相當不錯的。

國產車型配置高、價格更低,性價比十足。很多人對國產車存在偏見,但是這個困境將會隨着國產車型的日益發展得到改善,如果國產車的整體質量上來了,你還會覺得國產車比不上合資嗎?本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※回頭車貨運收費標準

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

[C#.NET 拾遺補漏]04:你必須知道的反射

閱讀本文大概需要 3 分鐘。

通常,反射用於動態獲取對象的類型、屬性和方法等信息。今天帶你玩轉反射,來匯總一下反射的各種常見操作,撿漏看看有沒有你不知道的。

獲取類型的成員

Type 類的 GetMembers 方法用來獲取該類型的所有成員,包括方法和屬性,可通過 BindingFlags 標誌來篩選這些成員。

using System;
using System.Reflection;
using System.Linq;

public class Program
{
    public static voidMain()
    {
        var members = typeof(object).GetMembers(BindingFlags.Public |
            BindingFlags.Static | BindingFlags.Instance);
        foreach (var member in members)
        {
            Console.WriteLine($"{member.Name} is a {member.MemberType}");
        }
    }
}

輸出:

GetType is a Method
GetHashCode is a Method
ToString is a Method
Equals is a Method
ReferenceEquals is a Method
.ctor is a Constructor

GetMembers 方法也可以不傳 BindingFlags,默認返回的是所有公開的成員。

獲取並調用對象的方法

Type 類型的 GetMethod 方法用來獲取該類型的 MethodInfo,然後可通過 MethodInfo 動態調用該方法。

對於非靜態方法,需要傳遞對應的實例作為參數,示例:

class Program
{
    public static void Main()
    {
        var str = "hello";
        var method = str.GetType()
            .GetMethod("Substring", new[] {typeof(int), typeof(int)});
        var result = method.Invoke(str, new object[] {0, 4}); // 相當於 str.Substring(0, 4)
        Console.WriteLine(result); // 輸出:hell
    }
}

對於靜態方法,則對象參數傳空,示例:

var method = typeof(Math).GetMethod("Exp");
// 相當於 Math.Exp(2)
var result = method.Invoke(null, new object[] {2});
Console.WriteLine(result); // 輸出(e^2):7.38905609893065

如果是泛型方法,則還需要通過泛型參數來創建泛型方法,示例:

class Program
{
    public static void Main()
    {
        // 反射調用泛型方法
        MethodInfo method1 = typeof(Sample).GetMethod("GenericMethod");
        MethodInfo generic1 = method1.MakeGenericMethod(typeof(string));
        generic1.Invoke(sample, null);

        // 反射調用靜態泛型方法
        MethodInfo method2 = typeof(Sample).GetMethod("StaticMethod");
        MethodInfo generic2 = method2.MakeGenericMethod(typeof(string));
        generic2.Invoke(null, null);
    }
}

public class Sample
{
    public void GenericMethod<T>()
    {
        //...
    }
    public static void StaticMethod<T>()
    {
        //...
    }
}

創建一個類型的實例

使用反射動態創建一個類型的實例有多種種方式。最簡單的一種是用 new() 條件聲明。

使用 new 條件聲明

如果在一個方法內需要動態創建一個實例,可以直接使用 new 條件聲明,例如:

T GetInstance<T>() where T : new()
{
    T instance = newT();
    return instance;
}

但這種方式適用場景有限,比如不適用於構造函數帶參數的類型。

使用 Activator 類

使用 Activator 類動態創建一個類的實例是最常見的做法,示例:

Type type = typeof(BigInteger);
object result = Activator.CreateInstance(type);
Console.WriteLine(result); // 輸出:0
result = Activator.CreateInstance(type, 123);
Console.WriteLine(result); // 輸出:123

動態創建泛類型實例,需要先創建開放泛型(如List<>),再根據泛型參數轉換為具象泛型(如List<string>),示例:

// 先創建開放泛型
Type openType = typeof(List<>);
// 再創建具象泛型
Type[] tArgs = { typeof(string) };
Type target = openType.MakeGenericType(tArgs);
// 最後創建泛型實例
List<string> result = (List<string>)Activator.CreateInstance(target);

如果你不知道什麼是開放泛型和具象泛型,請看本文最後一節。

使用構造器反射

也可以通過反射構造器的方式動態創建類的實例,比上面使用 Activator 類要稍稍麻煩些,但性能要好些。示例:

ConstructorInfo c = typeof(T).GetConstructor(new[] { typeof(string) });
if (c == null)
    throw new InvalidOperationException("...");
T instance = (T)c.Invoke(new object[] { "test" });

使用 FormatterServices 類

如果你想創建某個類的實例的時候不執行構造函數和屬性初始化,可以使用 FormatterServices 的 GetUninitializedObject 方法。示例:

class Program
{
    static void Main()
    {
        MyClass instance = (MyClass)FormatterServices.GetUninitializedObject(typeof(MyClass));
        Console.WriteLine(instance.MyProperty1); // 輸出:0
        Console.WriteLine(instance.MyProperty2); // 輸出:0
    }
}

public class MyClass
{
    public MyClass(int val)
    {
        MyProperty1 = val < 1 ? 1 : val;
    }

    public int MyProperty1 { get; }

    public int MyProperty2 { get; set; } = 2;
}

獲取屬性或方法的強類型委託

通過反射獲取到對象的屬性和方法后,如果你想通過強類型的方法來訪問或調用,可以在中間加一層委託。這樣的好處是有利於封裝,調用者可以明確的知道調用時需要傳什麼參數。 比如下面這個方法,把 Math.Max 方法提取為一個強類型委託:

var tArgs = new Type[] { typeof(int), typeof(int) };
var maxMethod = typeof(Math).GetMethod("Max", tArgs);
var strongTypeDelegate = (Func<int, int, int>)Delegate
    .CreateDelegate(typeof(Func<int, int, int>), null, maxMethod);
Console.WriteLine("3 和 5 之間最大的是:{0}", strongTypeDelegate(3, 5)); // 輸出:5

這個技巧也適用於屬性,可以獲取強類型的 Getter 和 Setter。示例:

var theProperty = typeof(MyClass).GetProperty("MyIntProperty");

// 強類型 Getter
var theGetter = theProperty.GetGetMethod();
var strongTypeGetter = (Func<MyClass, int>)Delegate
    .CreateDelegate(typeof(Func<MyClass, int>), theGetter);
var intVal = strongTypeGetter(target); // 相關於:target.MyIntProperty

// 強類型 Setter
var theSetter = theProperty.GetSetMethod();
var strongTypeSetter = (Action<MyClass, int>)Delegate
    .CreateDelegate(typeof(Action<MyClass, int>), theSetter);
strongTypeSetter(target, 5); // 相當於:target.MyIntProperty = 5

反射獲取自定義特性

以下是四個常見的場景示例。

示例一,找出一個類中標註了某個自定義特性(比如 MyAtrribute)的屬性。

var props = type
    .GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)
    .Where(prop =>Attribute.IsDefined(prop, typeof(MyAttribute)));

示例二,找出某個屬性的所有自定義特性。

var attributes = typeof(t).GetProperty("Name").GetCustomAttributes(false);

示例三,找出程序集所有標註了某個自定義特性的類。

static IEnumerable<Type> GetTypesWithAttribute(Assembly assembly)
{
    foreach(Type type inassembly.GetTypes())
    {
        if (type.GetCustomAttributes(typeof(MyAttribute), true).Length > 0)
        {
            yield return type;
        }
    }
}

示例四,在運行時讀取自定義特性的值

public static class AttributeExtensions
{
    public static TValue GetAttribute<TAttribute, TValue>(
        this Type type,
        string MemberName,
        Func<TAttribute, TValue> valueSelector,
        bool inherit = false)
        where TAttribute : Attribute
    {
        var att = type.GetMember(MemberName).FirstOrDefault()
            .GetCustomAttributes(typeof(TAttribute), inherit)
            .FirstOrDefault() as TAttribute;
        if (att != null)
        {
            return valueSelector(att);
        }
        return default;
    }
}

// 使用:

class Program
{
    static void Main()
    {
        // 讀取 MyClass 類的 MyMethod 方法的 Description 特性的值
        var description = typeof(MyClass)
            .GetAttribute("MyMethod", (DescriptionAttribute d) => d.Description);
        Console.WriteLine(description); // 輸出:Hello
    }
}
public class MyClass
{
    [Description("Hello")]
    public void MyMethod() { }
}

動態實例化接口的所有實現類(插件激活)

通過反射來動態實例化某個接口的所有實現類,常用於實現系統的插件式開發。比如在程序啟動的時候去讀取指定文件夾(如 Plugins)中的 dll 文件,通過反射獲取 dll 中所有實現了某個接口的類,並在適當的時候將其實例化。大致實現如下:

interface IPlugin
{
    string Description { get; }
    void DoWork();
}

某個在獨立 dll 中的類:

class HelloPlugin : IPlugin
{
    public string Description => "A plugin that says Hello";
    public void DoWork()
    {
        Console.WriteLine("Hello");
    }
}

在你的系統啟動的時候動態加載該 dll,讀取實現了 IPlugin 接口的所有類的信息,並將其實例化。

public IEnumerable<IPlugin> InstantiatePlugins(string directory)
{
    var assemblyNames = Directory.GetFiles(directory, "*.addin.dll")
        .Select(name => new FileInfo(name).FullName).ToArray();

    foreach (var fileName assemblyNames)
        AppDomain.CurrentDomain.Load(File.ReadAllBytes(fileName));

    var assemblies = assemblyNames.Select(System.Reflection.Assembly.LoadFile);
    var typesInAssembly = assemblies.SelectMany(asm =>asm.GetTypes());
    var pluginTypes = typesInAssembly.Where(type => typeof (IPlugin).IsAssignableFrom(type));

    return pluginTypes.Select(Activator.CreateInstance).Cast<IPlugin>();
}

檢查泛型實例的泛型參數

前文提到了構造泛型和具象泛型,這裏解釋一下。大多時候我們所說的泛型都是指構造泛型,有時候也被稱為具象泛型。比如 List<int> 就是一個構造泛型,因為它可以通過 new 來實例化。相應的,List<> 泛型是非構造泛型,有時候也被稱為開放泛型,它不能被實例化。開放泛型通過反射可以轉換為任意的具象泛型,這一點前文有示例。

假如現在有一個泛型實例,出於某種需求,我們想知道構建這個泛型實例需要用什麼泛型參數。比如某人創建了一個 List<T> 泛型的實例,並把它作為參數傳給了我們的一個方法:

var myList = newList<int>();
ShowGenericArguments(myList);

我們的方法簽名是這樣的:

public void ShowGenericArguments(object o)

這時,作為此方法的編寫者,我們並不知道這個 o 對象具體是用什麼類型的泛型參數構建的。通過反射,我們可以得到泛型實例的很多信息,其中最簡單的就是判斷一個類型是不是泛型:

public void ShowGenericArguments(object o)
{
    if (o == null) return;
    Type t =o.GetType();
    if (!t.IsGenericType) return;
    ...
}

由於 List<> 本身也是泛型,所以上面的判斷不嚴謹,我們需要知道的是對象是不是一個構造泛型(List<int>)。而 Type 類還提供了一些有用的屬性:

typeof(List<>).IsGenericType // true
typeof(List<>).IsGenericTypeDefinition // true
typeof(List<>).IsConstructedGenericType// false

typeof(List<int>).IsGenericType // true
typeof(List<int>).IsGenericTypeDefinition // false
typeof(List<int>).IsConstructedGenericType// true

IsConstructedGenericTypeIsGenericTypeDefinition 分別用來判斷某個泛型是不是構造泛型和非構造泛型。

再結合 Type 的 GetGenericArguments() 方法,就可以很容易地知道某個泛型實例是用什麼泛型參數構建的了,例如:

static void ShowGenericArguments(object o)
{
    if (o == null) return;
    Type t = o.GetType();
    if (!t.IsConstructedGenericType) return;
    foreach (Type genericTypeArgument in t.GetGenericArguments())
        Console.WriteLine(genericTypeArgument.Name);
}

以上是關於反射的乾貨知識,都是從實際項目開發中總結而來,希望對你的開發有幫助。

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

【其他文章推薦】

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

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

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

※超省錢租車方案

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

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

※回頭車貨運收費標準

使用請求頭認證來測試需要授權的 API 接口

使用請求頭認證來測試需要授權的 API 接口

Intro

有一些需要認證授權的接口在寫測試用例的時候一般會先獲取一個 token,然後再去調用接口,其實這樣做的話很不靈活,一方面是存在着一定的安全性問題,獲取 token 可能會有一些用戶名密碼之類的測試數據,還有就是獲取 token 的話如果全局使用同一個 token 會很不靈活,如果我要測試沒有用戶信息的話還比較簡單,我可以不傳遞 token,如果token里有兩個角色,我要測試另外一個角色的時候,只能給這個測試用戶新增一個角色然後再獲取token,這樣就很不靈活,於是我就嘗試把之前寫的自定義請求頭認證的代碼,整理了一下,集成到了一個 nuget 包里以方便其他項目使用,nuget 包是 WeihanLi.Web.Extensions,源代碼在這裏 https://github.com/WeihanLi/WeihanLi.Web.Extensions 有想自己改的可以直接拿去用,目前提供了基於請求頭的認證和基於 QueryString 的認證兩種認證方式。

實現效果

基於請求頭動態配置用戶的信息,需要什麼樣的信息就在請求頭中添加什麼信息,示例如下:

再來看個單元測試的示例:

[Fact]
public async Task MakeReservationWithUserInfo()
{
    using var request = new HttpRequestMessage(HttpMethod.Post, "/api/reservations");

    request.Headers.TryAddWithoutValidation("UserId", GuidIdGenerator.Instance.NewId()); // 用戶Id
    request.Headers.TryAddWithoutValidation("UserName", Environment.UserName); // 用戶名
    request.Headers.TryAddWithoutValidation("UserRoles", "User,ReservationManager"); //用戶角色

    request.Content = new StringContent($@"{{""reservationUnit"":""nnnnn"",""reservationActivityContent"":""13211112222"",""reservationPersonName"":""謝謝謝"",""reservationPersonPhone"":""13211112222"",""reservationPlaceId"":""f9833d13-a57f-4bc0-9197-232113667ece"",""reservationPlaceName"":""第一多功能廳"",""reservationForDate"":""2020-06-13"",""reservationForTime"":""10:00~12:00"",""reservationForTimeIds"":""1""}}", Encoding.UTF8, "application/json");

    using var response = await Client.SendAsync(request);
    Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}

實現原理解析

實現原理其實挺簡單的,就是實現了一種基於 header 的自定義認證模式,從 header 中獲取用戶信息並進行認證,核心代碼如下:

protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
    if (await Options.AuthenticationValidator(Context))
    {
        var claims = new List<Claim>();
        if (Request.Headers.TryGetValue(Options.UserIdHeaderName, out var userIdValues))
        {
            claims.Add(new Claim(ClaimTypes.NameIdentifier, userIdValues.ToString()));
        }
        if (Request.Headers.TryGetValue(Options.UserNameHeaderName, out var userNameValues))
        {
            claims.Add(new Claim(ClaimTypes.Name, userNameValues.ToString()));
        }
        if (Request.Headers.TryGetValue(Options.UserRolesHeaderName, out var userRolesValues))
        {
            var userRoles = userRolesValues.ToString()
                .Split(new[] { Options.Delimiter }, StringSplitOptions.RemoveEmptyEntries);
            claims.AddRange(userRoles.Select(r => new Claim(ClaimTypes.Role, r)));
        }

        if (Options.AdditionalHeaderToClaims.Count > 0)
        {
            foreach (var headerToClaim in Options.AdditionalHeaderToClaims)
            {
                if (Request.Headers.TryGetValue(headerToClaim.Key, out var headerValues))
                {
                    foreach (var val in headerValues.ToString().Split(new[] { Options.Delimiter }, StringSplitOptions.RemoveEmptyEntries))
                    {
                        claims.Add(new Claim(headerToClaim.Value, val));
                    }
                }
            }
        }

        // claims identity 's authentication type can not be null https://stackoverflow.com/questions/45261732/user-identity-isauthenticated-always-false-in-net-core-custom-authentication
        var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, Scheme.Name));
        var ticket = new AuthenticationTicket(
            principal,
            Scheme.Name
        );
        return AuthenticateResult.Success(ticket);
    }

    return AuthenticateResult.NoResult();
}

其實就是將請求頭的信息讀取到 Claims,然後返回一個 ClaimsPrincipalAuthenticationTicket,在讀取 header 之前有一個 AuthenticationValidator 是用來驗證請求是不是滿足使用 Header 認證,是一個基於 HttpContext 的斷言委託(Func<HttpContext, Task<bool>>),默認實現是驗證是否有 UserId 對應的 Header,如果要修改可以通過 Startup 來配置

使用示例

Startup 配置,和其它的認證方式一樣,Header 認證和 Query 認證也提供了基於 AuthenticationBuilder 的擴展,只需要在 services.AddAuthentication() 后增加 Header 認證的模式即可,示例如下:


services.AddAuthentication(HeaderAuthenticationDefaults.AuthenticationSchema)
    .AddQuery(options =>
    {
        options.UserIdQueryKey = "uid";
    })
    .AddHeader(options =>
    {
        options.UserIdHeaderName = "X-UserId";
        options.UserNameHeaderName = "X-UserName";
        options.UserRolesHeaderName = "X-UserRoles";
    });

默認的 Header 是 UserId/UserName/UserRoles,你也可以自定義為符合自己需要的配置,如果只是想新增一個轉換可以配置 AdditionalHeaderToClaims 增加自己需要的請求頭 => Claims 轉換,AuthenticationValidator 也可以自定義,就是上面提到的會首先會驗證是不是需要讀取 Header,驗證通過之後才會讀取 Header 信息並認證

測試示例

有一個接口我需要登錄之後才能訪問,需要用戶信息,類似下面這樣

[HttpPost]
[Authorize]
public async Task<IActionResult> MakeReservation(
    [FromBody] ReservationViewModel model
    )
{
    // ...
}

在測試代碼里我配置使用了 Header 認證,在請求的時候直接通過 Header 來控制用戶的信息

Startup 配置:

services
    .AddAuthentication(HeaderAuthenticationDefaults.AuthenticationSchema)
    .AddHeader()
    // 使用 Query 認證
    //.AddAuthentication(QueryAuthenticationDefaults.AuthenticationSchema)
    //.AddQuery()
    ;

測試代碼:

[Fact]
public async Task MakeReservationWithUserInfo()
{
    using var request = new HttpRequestMessage(HttpMethod.Post, "/api/reservations");
    request.Headers.TryAddWithoutValidation("UserId", GuidIdGenerator.Instance.NewId());
    request.Headers.TryAddWithoutValidation("UserName", Environment.UserName);
    request.Headers.TryAddWithoutValidation("UserRoles", "User,ReservationManager");

    request.Content = new StringContent($@"{{""reservationUnit"":""nnnnn"",""reservationActivityContent"":""13211112222"",""reservationPersonName"":""謝謝謝"",""reservationPersonPhone"":""13211112222"",""reservationPlaceId"":""f9833d13-a57f-4bc0-9197-232113667ece"",""reservationPlaceName"":""第一多功能廳"",""reservationForDate"":""2020-06-13"",""reservationForTime"":""10:00~12:00"",""reservationForTimeIds"":""1""}}", Encoding.UTF8, "application/json");

    using var response = await Client.SendAsync(request);
    Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}

[Fact]
public async Task MakeReservationWithInvalidUserInfo()
{
    using var request = new HttpRequestMessage(HttpMethod.Post, "/api/reservations");

    request.Headers.TryAddWithoutValidation("UserName", Environment.UserName);

    request.Content = new StringContent($@"{{""reservationUnit"":""nnnnn"",""reservationActivityContent"":""13211112222"",""reservationPersonName"":""謝謝謝"",""reservationPersonPhone"":""13211112222"",""reservationPlaceId"":""f9833d13-a57f-4bc0-9197-232113667ece"",""reservationPlaceName"":""第一多功能廳"",""reservationForDate"":""2020-06-13"",""reservationForTime"":""10:00~12:00"",""reservationForTimeIds"":""1""}}", Encoding.UTF8, "application/json");

    using var response = await Client.SendAsync(request);
    Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
}

[Fact]
public async Task MakeReservationWithoutUserInfo()
{
    using var request = new HttpRequestMessage(HttpMethod.Post, "/api/reservations")
    {
        Content = new StringContent(
            @"{""reservationUnit"":""nnnnn"",""reservationActivityContent"":""13211112222"",""reservationPersonName"":""謝謝謝"",""reservationPersonPhone"":""13211112222"",""reservationPlaceId"":""f9833d13-a57f-4bc0-9197-232113667ece"",""reservationPlaceName"":""第一多功能廳"",""reservationForDate"":""2020-06-13"",""reservationForTime"":""10:00~12:00"",""reservationForTimeIds"":""1""}",
            Encoding.UTF8, "application/json")
    };

    using var response = await Client.SendAsync(request);
    Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
}

More

QueryString 認證和請求頭認證是類似的,這裏就不再贅述,只是把請求頭上的參數轉移到 QueryString 上了,覺得不夠好用的可以直接 Github 上找源碼修改, 也歡迎 PR,源碼地址: https://github.com/WeihanLi/WeihanLi.Web.Extensions

Reference

  • https://github.com/WeihanLi/WeihanLi.Web.Extensions
  • https://www.nuget.org/packages/WeihanLi.Web.Extensions
  • https://github.com/OpenReservation/ReservationServer/blob/dev/ActivityReservation.API.Test/TestStartup.cs
  • https://github.com/OpenReservation/ReservationServer/blob/dev/ActivityReservation.API.Test/Controllers/ReservationControllerTest.cs
  • https://www.cnblogs.com/weihanli/p/cutom-authentication-in-aspnetcore.html

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

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

Python機器學習筆記:SVM(3)——證明SVM,Python機器學習筆記:SVM(1)——SVM概述,Python機器學習筆記:SVM(2)——SVM核函數,Python機器學習筆記:SVM(3)——證明SVM,Python機器學習筆記:SVM(4)——sklearn實現

  說實話,凡是涉及到要證明的東西(理論),一般都不好惹。絕大多數時候,看懂一個東西不難,但證明一個東西則需要點數學功底,進一步,證明一個東西也不是特別難,難的是從零開始發明這個東西的時候,則顯得艱難(因為任何時代,大部分人的研究所得都不過是基於前人的研究成果,前人所做的是開創性的工作,而這往往是最艱難最有價值的,他們被稱為真正的先驅。牛頓也曾說過,他不過是站在巨人的肩上,你,我更是如此)。

  正如陳希孺院士在他的著作《數理統計學簡史》的第四章,最小二乘法中所講:在科研上諸多觀念的革新和突破是有着很多的不易的,或者某個定理在某個時期由有個人點破了,現在的我們看來一切都是理所當然,但在一切沒有發現之前,可能許許多多的頂級學者畢其功於一役,耗盡一生,努力了幾十年最終也是無功而返。

  上一節我學習了SVM的核函數內容,下面繼續對SVM進行證明,具體的參考鏈接都在第一篇文章中,SVM四篇筆記鏈接為:

Python機器學習筆記:SVM(1)——SVM概述

Python機器學習筆記:SVM(2)——SVM核函數

Python機器學習筆記:SVM(3)——證明SVM

Python機器學習筆記:SVM(4)——sklearn實現

  話休絮煩,要證明一個東西先要弄清楚它的根基在哪裡,即構成它的基礎是哪些理論。OK,以下內容基本上都是上文沒有學習到的一些定理的證明,包括其背後的邏輯,來源背景等東西。

  本文包括內容:

  • 1,線性學習器中,主要闡述感知機算法
  • 2,非線性學習器中,主要闡述 Mercer定理
  • 3,損失函數
  • 4,最小二乘法
  • 5,SMO算法的推導

  同樣,在學習這些之前,我們再複習一下SVM,這裏使用(http://staff.ustc.edu.cn/~ketang/PPT/PRLec5.pdf)的PPT來學習。

熱身:SVM的整理

  這裏直接借用別人的PPT粘貼在這裏,讓自己再梳理一遍SVM。

熱身1,Hard Margin SVM

熱身2,Soft Margin SVM

熱身3,LS-SVM

1,線性學習器

1.1 感知機算法

  這個感知器算法是在1956年提出的,年代久遠,依然影響着當今,當然,可以肯定的是,此算法亦非最優,後續會有更詳盡闡述。不過,有一點,你必須清楚,這個算法是為了干什麼的:不斷的訓練試錯以期尋找一個合適的超平面。

   下面,舉個例子。如下圖所示,憑我們的直覺可以看出,圖中紅線是最優超平面,藍線則是根據感知機算法在不斷的訓練中,最終,若藍線能通過不斷的訓練移動到紅線位置上,則代表訓練成功。

   既然需要通過不斷的訓練以讓藍線最終成為最優分類超平面,那麼到底需要訓練多少次呢?

  Novikoff 定理告訴我們當間隔是正的時候感知機算法會在有限次數的迭代中收斂,也就是說 Novikoff 定理證明了感知機算法的收斂性,即能得到一個界,不至於無窮循環下去。

  Novikoff 定理:如果分類超平面存在,僅需要在序列 S 上迭代幾次,在界為 (2R / γ)2 的錯誤次數下就可以找到分類超平面,算法停止。

  這裏的 R = max1<=i<=l||Xi|| ,γ 為擴充間隔。根據誤分次數公式可知,迭代次數與對應於擴充(包括偏置)權重的訓練集的間隔有關。

  順便再解釋下這個所謂的擴充間隔 γ , γ 即為樣本到分類間隔的距離,即從 γ 引出的最大分類間隔。之前我們推導過的內容,如下:

   在給出幾何間隔的定義之前,咱們首先來看下,如上圖所示,對於一個點 x,令其垂直投影到超平面上的對應的為 x0,由於 w 是垂直於超平面的一個向量, γ 為樣本 x 到分類間隔的距離,我們有:

   同時有一點值得注意:感知機算法雖然可以通過簡單迭代對線性可分數據生成正確分類的超平面,但不是最優效果,那怎麼才能得到最優效果呢,就是前面博文說的尋找最大分類間隔超平面。此外,Novikoff定理的證明請參考:http://www.cs.columbia.edu/~mcollins/courses/6998-2012/notes/perc.converge.pdf

2,非線性學習器

2.1 Mercer定理

   Mercer定理:如果函數 K 是 Rn *Rn – R 上的映射(也就是從兩個 n 維向量映射到實數域)。那麼如果K是一個有效核函數(也稱為 Mercer 核函數),那麼當且僅當對於訓練樣例 { x(1), x(2), …  x(m)},其相應的核函數矩陣是對稱半正定的。

  Mercer定理表明為了證明K是有效的核函數,那麼我們不用去尋找 Φ ,而只需要在訓練集上求出各個 Kij,然後判斷矩陣K是否是半正定(使用左上角主子式大於等於零等方法)即可。

  要理解這個 Mercer定理,先要了解什麼是半正定矩陣,要了解什麼是半正定矩陣,先得知道什麼是正定矩陣(矩陣理論博大精深,關於矩陣推薦一本書《矩陣分析與應用》),然後關於Mercer定理的證明參考:http://ftp136343.host106.web522.com/a/biancheng/matlab/2013/0120/648.html

  其實,核函數在SVM的分類效果中起到了重要的作用,下面鏈接有個 tutorial可以看看:https://people.eecs.berkeley.edu/~bartlett/courses/281b-sp08/7.pdf

2.2 正定矩陣

  在百度百科,正定矩陣的定義如下:在線性代數里,正定矩陣(positive definite materix)有時會簡稱為正定陣。在線性代數中,正定矩陣的性質類似於複數中的正實數。與正定矩陣相對應的線性算子是對稱的正定雙線性形式。

  廣義的定義:設 M 為 n 階方陣,如果對任何非零向量 z,都有 zTMz > 0,其中 zT 表示 z 的轉置,就稱 M  為正定矩陣。

  狹義的定義:一個 n 階的實對稱矩陣 M 是正定的條件是當且僅當對所有的非零實係數向量 z,都有 zTMz > 0,其中 zT 表示 z 的轉置。

  正定矩陣的性質:

  • 1,正定矩陣的行列式恆為正
  • 2,實對稱矩陣 A 正定當且僅當 A 與單位矩陣合同
  • 3,若 A 是正定矩陣,則 A 的逆矩陣也是正定矩陣
  • 4,兩個正定矩陣的和是正定矩陣
  • 5,正實數域正定矩陣的乘積是正定矩陣

3,損失函數

  之前提到過“支持向量機(SVM)是 90 年代中期發展起來的基於統計學習理論的一種機器學習方法,通過尋找結構化風險最小來提高學習機泛化能力,實現經驗風險和置信範圍的最小化,從而達到在統計樣本量較少的情況下,亦能獲得良好統計規律的目的。”但初次看到的人可能不了解什麼是結構化風險,什麼又是經驗風險。要了解這兩個所謂的“風險”,還得從監督學習說起。

  監督學習實際上就是一個經驗風險或者結構風險函數的最優化問題。風險函數度量平均意義下模型預測的好壞,模型每一次預測的好壞用損失函數來度量。它從假設空間 F 中選擇模型 f 作為決策函數,對於給定的輸入 X,由 f(x) 給出相應的輸出 Y,這個輸出的預測值 f(X)與真實值 Y 可能一致也可能不一致,用一個損失函數來度量預測錯誤的程度。損失函數記為 L(Y, f(X))。

  常用損失函數有以下幾種(摘抄於《統計學習方法》):

  (1) 0-1 損失函數

  (2)平方損失函數

   (3)絕對損失函數

   (4)對數損失函數

   給定一個訓練數據集

   模型 f(X) 關於訓練數據集的平均損失稱為經驗風險,如下:

   關於如何選擇模型,監督學習有兩種策略:經驗風險最小化和結構風險最小化。

  經驗風險最小化的策略認為,經驗風險最小的模型就是最優的模型,則按照經驗風險最小化求最優模型就是求解如下的最優化問題:

  當樣本容量很小時,經驗風險最小化的策略容易產生過擬合的現象。結構風險最小化可以防止過擬合。結構風險是在經驗風險的基礎上加上表示模型複雜度的正則化項或懲罰項,結構風險定義如下:

   其中 J(f) 為模型的複雜度,模型 f 越複雜,J(f) 值就越大,模型越簡單,J(f) 值就越小,也就是說J(f)是對複雜模型的乘法。λ>=0 是係數,用以衡量經驗風險和模型複雜度。結構風險最小化的策略認為結構風險最小的模型是最優的模型,所以求最優的模型就是求解下面的最優化問題:

   這樣,簡單學習問題就變成了經驗風險或結構化風險函數的最優化問題。如上式最優化問題的轉換。

   這樣一來,SVM就有第二種理解,即最優化+損失最小。如網友所言:“可以從損失函數和優化算法角度看SVM,Boosting,LR等算法,可能會有不同收穫”。

  關於損失函數:可以看看張潼的這篇《Statistical behavior and consistency of classification methods based on convex risk minimization》。各種算法中常用的損失函數基本都具有fisher一致性,優化這些損失函數得到的分類器可以看作是后驗概率的“代理”。此外,張潼還有另外一篇論文《Statistical analysis of some multi-category large margin classification methods》,在多分類情況下margin loss的分析,這兩篇對Boosting和SVM使用的損失函數分析的很透徹。

  關於統計學習方法的問題,可以參考:https://people.eecs.berkeley.edu/~bartlett/courses/281b-sp08/7.pdf

4,最小二乘法

4.1  什麼是最小二乘法?

  下面引用《正態分佈的前世今生》里的內容稍微簡單闡述一下。

  我們口頭經常經常說:一般來說,平均來說。如平均來說,不吸煙的健康優於吸煙者,之所有要加“平均” 二字,是因為凡是皆有例外,總存在某個特別的人他吸煙但由於經常鍛煉所以他的健康狀況可能會優於他身邊不吸煙的盆友。而最小二乘的一個最簡單例子便是算術平均。

  最小二乘法(又稱最小平方法)是一種數學優化技術。它通過最小化誤差的平方和尋找數據的最佳函數匹配。利用最小二乘法可以簡便的求得未知的數據,並使得這些求得的數據與實際數據之間誤差的平方和為最小。用函數表示為:

   使誤差(所謂誤差,當然是觀察值與實際真實值的差量)平方和達到最小以尋求估計值的方法,就叫做最小二乘法,用最小二乘法得到的估計,叫做最小二乘估計。當然,取平方和作為目標函數只是眾多可取的方法之一。

  最小二乘法的一般形式可表示為:

   有效的最小二乘法是勒讓得在1805年發表的,基本思想就是認為測量中有誤差,所以所有方程的累積誤差為:

   我們求解出導致累積誤差最小的參數即可:

   勒讓得在論文中對最小二乘法的優良性做了幾點說明:

  • 最小二乘的誤差平方和最小,並在各個方程的誤差之間建立了一種平衡,從而防止某個極端誤差取得支配地位。
  • 計算中只需要求偏導后求解線性方程組,計算過程明確便捷
  • 最小二乘可以導出算術平均值作為估計

  對於最後一點,從統計學的角度來看是很重要的一個性質。推理如下:假設真值為 Θ ,x1,…..xn 為 n 次測量值,每次測量的誤差為 ei = xi – Θ,按最小二乘法,誤差累積為:

  求解 Θ 使 L(Θ) 達到最小,正好是算術平均 xhat,其公式如下:

   由於算術平均是一個歷經考驗的方法,而以上的推理說明,算術平均是最小二乘的一個特例,所以從另外一個角度說明了最小二乘方法的優良性,使我們對最小二乘法更加有信息。

  最小二乘法發布之後很快得到了大家的認可接受,並迅速的在數據分析實踐中被廣泛使用。不過歷史上又有人把最小二乘法的發明歸功於高斯,這又是怎麼一回事呢?高斯在 1809 年也發表了最小二乘法,並且聲稱自己已經使用了這個方法多年。高斯發明了小行星定位的數學方法,並在數據分析中使用最小二乘方法進行計算,準確的預測了穀神星的位置。

  說了這麼多,貌似與SVM沒啥關係,但是別著急,請繼續聽,本質上說,最小二乘法即是一種參數估計方法,說到參數估計,咱們從一元線性模型說起。

4.2 最小二乘法的解法

   什麼是一元線性模型呢?我們引用(https://blog.csdn.net/qll125596718/article/details/8248249)的內容,先來梳理一下幾個基本的概念:

  • 監督學習中,如果預測的變量是離散的,我們稱其為分類(如決策樹,支持向量機等),如果預測的變量是連續的,我們稱其為回歸。
  • 回歸分析中,如果只包括一個自變量和一個因變量,且二者的關係可用一條直線近似表示,這種回歸分析稱為一元線性回歸分析。
  • 如果回歸分析中包括兩個或兩個以上的自變量,且因變量和自變量之間是線性關係,則稱為多元線性回歸分析。
  • 對於二維空間線性是一條直線;對於三維空間線性是一個平面,對於多維空間線性是一個超平面。

  對於一元線性回歸模型,假設從總體中獲取了 n 組觀察值(X1, Y1),(X2, Y2),…(Xn, Yn)。對於平面中的這 n 個點,可以使用無數條曲線來擬合。要求樣本回歸函數盡可能好的擬合這組值。綜合起來看,這條直線處於樣本數據的中心位置最合理。

  選擇最佳擬合曲線的標準可以確定為:使總的擬合誤差(即總殘差)達到最小,有以下三個標準可以選擇:

  • 1,用“殘差和最小”確定直線位置是一個途徑。但是很快發現計算“殘差和” 存在相互抵消的問題。
  • 2,用“殘差絕對值和最小”確定直線位置也是一個途徑。但絕對值的計算比較麻煩。
  • 3,最小二乘法的原則是以“殘差平方和最小” 確定直線位置。用最小二乘法除了計算比較方便外,得到的估計量還具有優良特性。這種方法對異常值非常敏感。

  最常用的是普通最小二乘法(Ordinary Least Square, OLS ):所選擇的回歸模型應該使所有觀察值的殘差平方和達到最小,即採用平方損失函數。

   我們定義樣本回歸模型為:

   得到誤差 ei (ei為樣本)為:

   接着,定義平方損失函數 Q:

   則通過Q最小確定這條直線,即確定 β0hat,  β1hat, β0hat,  β1hat為變量,把它們看做是 Q 的函數,就變成了一個求極值的問題,可以通過求導數得到。

  求 Q 對兩個待估參數的偏導數:

   根據數學知識我們知道,函數的極值點為偏導為 0 的點,解得:

   這就是最小二乘法的解法,就是求得平方損失函數的極值點。自此,我們可以看到求解最小二乘法和求解SVM是何等相似,尤其是定義損失函數,而後通過偏導求極值。

5,SMO算法

  無論Hard Margin 或 Soft Margin SVM,我們均給出了SVM的對偶問題,但並沒有說明對偶問題怎麼求解。由於矩陣Q的規模和樣本數相等,當訓練樣本數很大的時候,這個矩陣的規模很大,求解二次規劃問題的經典算法會遇到性能問題,也就是說同時求解 n 個拉格朗日乘子涉及很多次迭代,計算開銷太大,所以一般採用 Sequential Minimal Optimization(SMO)算法。

  SMO算法的基本思想每次只更新兩個乘子,迭代獲得最終解

  上文中,我們提到了求解對偶問題的序列最小最優化 SMO 算法,但並未提到其具體解法。首先看下最後懸而未決的問題:

   等價於求解:

   1998年,Microsoft Research 的John C. Platt 在論文《Sequential  Minimal Optimization:A Fast Alogrithm for Training Support Vector Machines》中提出針對上述問題的解法:SMO算法,它很快便成為最快的二次規劃優化算法,特別是針對線性SVM和數據稀疏時性能更優。這個算法的思路是每次在優化變量中挑出兩個分量進行優化,而讓其他分量固定,這樣才能保證滿足等式約束條件,這是一種分治法的思想。

  接下來,我們便參考 John C.Platt 的文章(找不到了。。。)來看看 SMO的解法。

5.1 SMO算法的推導

  首先我們來定義特徵到結果的輸出函數:

   注:這個 u 與我們之前定義的 f(x) 實質上是一樣的。

   接着,重新定義下我們原始的優化問題,權當重新回顧,如下:

   求導得到:

   代入 u 的公式中,可得:

   通過引入拉格朗日乘子轉換為對偶問題后,得:

   注:這裏得到的 min 函數與我們之前的 max 函數實質上也是一樣,因為把符號變下,即由 min 轉換為 max 的問題,且 yi也與之前的 y(i) 等價,yj 亦如此。

  經過加入鬆弛變量后,模型修改為:

   從而最終我們的問題變為:

  下面要解決的問題是:在 αi = { α1, α2, α3,……, αn} 上求上述目標函數的最小值。為了求解這些乘子,每次從中任意抽取兩個乘子 α1 和 α2,然後固定 α1 和 α2 以外的乘子 {α3, α4,….αn},使得目標函數只是關於 α1 和 α2 的函數。這樣,不斷的從一堆乘子中任意抽取兩個求解,不斷地迭代求解子問題,最終達到求解原問題的目的。

  (注意:下面均使用兩個相同的表達式,是參考了兩個方法,並且這兩個方法均易於理解,可以說我先看第一個公式的文章,然後偶爾有次看到第二個公式的文章,發現也很好理解,所以粘貼在這裏,特地說明

  我們首先給出對於這兩個常量的優化問題(稱為子問題)的求解方法。假設選取的兩個分量為 αi, αj,其他分量都固定(即當做常數)。由於:

  所以對偶問題的子問題的目標函數可以表達為:

  (更普及一點,可以寫成下面這樣)

   其中C是一個常數,前面的二次項很容易計算出來,一次項要複雜一些,並且:

  這裏的變量 α* 為變量 a 在上一輪迭代后的值。上面的目標函數是一個兩變量的二次函數,我們可以直接給出最小值的解析解(公式解)。

   為了解決這個子問題,首要問題便是每次如何選取 α1 和 α2。實際上,其中一個乘子是違反 KKT條件最嚴重的,另外一個乘子則由另一個約束條件選取。

  根據KKT條件可以得到目標函數中 αi 取值的意義:

   這裏的 αi 還是拉格朗日乘子:

  • 1,對於第一種情況,表明 αi 是正常分類,在間隔邊界內部(我們知道正確分類的點 yi * f(xi) >= 0)
  • 2,對於第二種情況,表明了 αi 是支持向量,在間隔邊界上
  • 3,對於第三種情況,表明了 αi 是在兩條間隔邊界之間

  而最優解需要滿足KKT 條件,即上述三個條件都得滿足,以下幾種情況出現將會出現不滿足:

  • 1,yiui <=1,但是 αi < C 則不是不滿足的,而原本 αi = C
  • 2,yiui >=1,但是 αi > C 則不是不滿足的,而原本 αi = C
  • 3,yiui =1,但是 αi = 0 或者  αi = C 則不是不滿足的,而原本 0  < αi < C

  也就是說,如果存在不滿足 KKT 條件的 αi ,那麼需要更新這些 αi ,這是第一個約束條件。此外,更新的同時還要受到第二個約束條件的限制,即:

   因此,如果假設選擇的兩個乘子  α1 和 α2 ,他們在更新之前分別是  α1old 和 α2old,更新之後分別是  α1new 和 α2new,那麼更新前後的值需要滿足以下等式才能保證和為 0  的約束:

   其中,ξ 是常數,(上面兩個式子都一樣,只不過第二個更容易理解)。

  兩個因子不好同時求解,所以可選求第二個乘子 α2 的解(α2new),得到 α2 的解(α2new)之後,再利用 α2 的解(α2new)表示 α1 的解(α1new).

  為了求解 α2 的解(α2new),得先確定 α2new 的取值範圍。假設它的上下邊界分別為 H 和 L,那麼有:

   接下來,綜合下面兩個約束條件,求解 α2new 的取值範圍:

   由於 yi,  yj(也可以說為 y1  y2)的取值只能為 +1 或者 -1,那麼當他們異號,也就是當 y1 != y2 時,根據:

   可得:  α1old – α2old  = ξ   (  αi – αj  = ξ),它確定的可行域是一條斜率為1的直線段,因為αi αj 要滿足約束條件

  他們的可行域如下圖所示:

  上面兩條直線分別對應於 y1為 +1 和 -1 的情況。如果是上面那條直線,則 αj 的取值範圍為 [-ξ, C]。如果是下面的那條直線,則為 [0,C-ξ]。

  對於這兩種情況 αj 的下界和上界可以統一寫成如下形式:

  因為   αi – αj  = ξ ,所以又可以寫為:  L =  max (0, -ξ),  H = min(C, C-ξ)

  下邊界是直線和 x 軸交點的 x 坐標以及 0 的較大值;上邊界是直線和的交點的 x 坐標和 C 的較小值。

   再來看第二種情況,如果 yi  yj 同號,即當 y1 = y2 時,同樣根據:

   可得:  α1old + α2old  = ξ (  αi  +  αj  = ξ ),所以有:

 

   根據   αi  +  αj  = ξ  , 上式也可寫為:L =  max (0, ξ – C),  H = min(C, ξ)

  這種情況如下圖所示:

   如此,根據這兩個變量的等式約束條件( y1 和 y2 異號或者同號),可以消掉α2old ,可得出 α2new 的上下界分別為:

   回顧下第二個約束條件:

  下面我們來計算不考慮截斷時的函數極值。為了避免分 -1 和 +1 兩種情況,我們將上面的等式約束兩邊同時乘以 y1(第二種表達是乘以yi),可得:

   其中 α1 可以用 α2 表示,α1 = w – s*α2,從而我們把子問題的目標函數轉換為只含 α2 的問題:

   對 α2 求導(即對自變量求導),並令導數為零,可得:

   由於:

   化簡下:

   然後將:

   代入上式,可得:

   下面令(其中 Ei 表示預測值與真實值之差):

   然後上式兩邊同時除以 η ,得到一個關於單變量 α2 的解:

  在求得  αj 之後,根據等式約束條件我們就可以求得另外一個變量的值:

  目標函數的二階導數為 η,前面假設二階導數 η  > 0,從而保證目標函數是凸函數即開口向上的拋物線,有極小值。如果 η  < 0 或者 η  = 0,該怎麼處理?對於線性核或正定核函數,可以證明矩陣K的任意一個上述子問題對應的二階子矩陣半正定,因此必定有 η  >= 0。無論本次迭代時的初始值是多少,通過上面的子問題求解算法得到是在可行域里的最小值,因此每次求解更新這兩個變量的值之後,都能保證目標函數值小於或者等於初始值,即函數值下降。

   這個解沒有考慮其約束條件 0 <=  α2 <= C,即是未經剪輯時的解。

  然後考慮約束 0 <=  α2 <= C 可得到經過剪輯后的 α2new 的解析解為:

  (如果用αi,αj表示,則我們求的這個二次函數的最終極值點為:)

   求出了 α2new后,便可以求出α1new ,得:

  這三種情況下的二次函數極小值如下圖所示:

  上圖中第一種情況是拋物線的最小值點在 [L, H]中;第二種情況是拋物線的最小值點大於 H,被截斷為H;第三種情況是小於L,被截斷為L。

   那麼如何選擇乘子   α1 和 α2呢?

  1. 對於 α1 ,即第一個乘子,可以通過剛剛說的那3種不滿足 KKT的條件來找
  2. 而對於第二個乘子 α2 可以尋找滿足條件: max |Ei – Ej| 的乘子

  而 b 滿足下述條件:

   下面更新 b:

   且每次更新完兩個乘子的優化后,都需要再重新計算 b,及對應的 Ei值。

  最後更新所有的 αi,y 和 b,這樣模型就出來了,從而即可求出咱們開頭提出的分類函數:

   此外,這裡有一篇類似的文章,大家可以參考下(https://www.cnblogs.com/jerrylead/archive/2011/03/18/1988419.html)。

 5.2  SMO算法的步驟

  綜上,總結下SMO的主要步驟,如下:

   意思是:

  • 1,第一步:選取一對 αi 和 αj,選取方法使用啟髮式方法
  • 2,第二步:固定除αi 和 αj 之外的其他參數,確定W 極值條件下的 αi 和 αj 由 αi 表示

  假定在某一次迭代中,需要更新 x1,x2 對應的拉格朗日乘子 α1,α2,那麼這個小規模的二次規劃問題寫為:

   那麼在每次迭代中,如何更新乘子呢?引用下面地址(http://staff.ustc.edu.cn/~ketang/PPT/PRLec5.pdf)的兩張PPT說明下:

   知道了如何更新乘子,那麼選取哪些乘子進行更新呢?具體有以下兩個步驟:

  • 步驟一:先“掃描”所有乘子,把第一個違反KKT條件的作為更新對象,令為 a1
  • 步驟二:在所有不違反KKT條件的乘子中,選擇使 |E1 – E2|最大的 a2 進行更新,使得能最大限度增大目標函數的值(類似於梯度下降,此外 Ei = ui – yi,而 u = w*x – b ,求出來的 E 代表函數 ui 對輸入 xi 的預測值與真實輸出類標記 yi 之差)

  最後,每次更細完兩個乘子的優化后,都需要再重新計算 b,及對應的 Ei 值。

  綜上,SMO算法的基本思想是把 Vapnik 在 1982年提出的 Chunking方法推到極致,SMO算法每次迭代只選出兩個分量 ai 和 aj 進行調整,其他分量則保持固定不變,在得到 解 ai 和 aj 之後,再用 ai 和 aj 改進其他分量。與通常的分解算法比較,儘管它可能需要更多的迭代次數,但每次迭代的計算量比較小,所以該算法表現出較好的快速收斂性,且不需要存儲核函數,也沒有矩陣運算。

5.3  SMO算法的實現

  行文至此,我相信,SVM理解到了一定程度后,是的確能在腦海里從頭到尾推導出相關公式的,最初分類函數,最大化分類間隔,max1/||w||,min1/2||w||^2,凸二次規劃,拉格朗日函數,轉化為對偶問題,SMO算法,都為尋找一個最優解,一個最優分類平面。一步步梳理下來,為什麼這樣那樣,太多東西可以追究,最後實現。

  至於上文中將闡述的核函數則是為了更好的處理非線性可分的情況,而鬆弛變量則是為了糾正或約束少量“不安分”或脫離集體不好歸類的因子。

      台灣的林智仁教授寫了一個封裝SVM算法的libsvm庫,大家可以看看,此外這裏還有一份libsvm的註釋文檔。在這篇論文《fast training of support vector machines using sequential minimal optimization》中platt給出了SMO算法的邏輯代碼。

5.4  SMO算法的優缺點

  優點:

  • 可保證解的全局最優解,不存在陷入局部極小值的問題
  • 分類器複雜度由支撐向量的個數,而非特徵空間(或核函數)的維數決定,因此較少因維數災難發生過擬合線性

  缺點:

  1. 需要求解二次規劃問題,其規模與訓練模式量成正比,因此計算複雜度高,且存儲開銷大,不適用於需進行在線學習/訓練的大規模分類問題  

  這篇文章主要參考:https://mp.weixin.qq.com/s/ZFWJUazMbAqeoSIkXjuG5g

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

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

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

別再重複造輪子了,幾個值得應用到項目中的 Java 開源庫送給你

我是風箏,公眾號「古時的風箏」。文章會收錄在 JavaNewBee 中,更有 Java 後端知識圖譜,從小白到大牛要走的路都在裏面。公眾號回復『666』獲取高清大圖。

風箏我作為一個野路子開發者,直到遇見下面的這幾個工具庫,才知道之前重複造了不少輪子,而且輪子還不一定有人家的圓。相信跟我一樣,沒事兒造輪子的人還不在少數,有些人就是對造輪子感興趣,這個咱也無話可說,但是,比如我,我是造輪子之前不知道這世上已經有好用的輪子了,害,無知限制了我的想象力。

比如我們在拿到一個 List 集合之後,要對這個集合進行判空操作,以前我一直是這樣寫的:

List<String> list = getList();
if (list != null && list.size() > 0) {
    //do something
}

雖然這樣也沒什麼問題,但是,我懶啊,每次敲這麼多代碼,也挺累啊。有同學說,那你包裝成一個方法不就行了,每次調用個方法就 OK 啦。這不,同學,你就在造輪子了,已經有人幫你寫好了這樣類似的一系列方法了。

來讓我們認識認識這些輪子吧。

Java 8 Stream

Stream 不算是工具庫,但是通過 stream 提供的一系列方法,可以實現集合的過濾、分組、集合轉換等諸多操作。

例如下面的方法,實現列表元素根據某個字段去重的功能。

List<User> userList = new ArrayList();
//添加元素
userList =  userList.stream().filter(distinctByKey(user->user.getUserId())).collect(Collectors.toList());

private static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
      Map<Object,Boolean> seen = new ConcurrentHashMap<>();
      return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}

apache commons

官方地址:http://commons.apache.org/

這不是一個庫,而是一系列的工具庫。

由於包含的庫過多,我就不一一列舉了,可以到官網一探。有集合處理的、數學計算的、IO 操作的等等,其中最常用的莫過於 Apache Commons Lang 和 Apache Commons Collections 這兩個。

Apache Commons Lang 包括一系列工具類,有字符串相關的、時間處理的、反射的、併發包的等等,Apache Commons Collections 專門用作集合處理。

下面舉幾個例子說明一下,更詳細的內容可以到官網查看文檔。

字符串判空操作

String s = "";
Boolean isEmpty = StringUtils.isEmpty(s);

獲取類的全名稱

ClassUtils.getName(ClassUtils.class);

判斷集合是否為空

Boolean isNotEmpty = CollectionUtils.isNotEmpty(list);

反射獲取某個類的所有 Field

Field[] fields = FieldUtils.getAllFields(User.class);

Google Guava

官方地址:https://github.com/google/guava

和 Apache Commons 有點兒類似,它也是包含了一系列的比如字符串、集合、反射、數學計算等的操作封裝,還可以用作 JVM 緩存。

舉幾個例子說明:

New 各種對象

List<String> list = Lists.newArrayList();
Set<String> set = Sets.newHashSet();
Map<String,Object> map = Maps.newConcurrentMap();

// 不可變集合
ImmutableList<String> immutableList = ImmutableList.of("1", "2", "3");

列錶轉符號分隔的字符串

List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
list.add("3");
String result = Joiner.on("-").join(list);

> 1-2-3

求交集、並集、差集等

例如下面代碼求 set1 和 set2 的交集

Set<Integer> set1 = Sets.newHashSet(1, 2, 3, 4, 5, 6);
Set<Integer> set2 = Sets.newHashSet(1,2,3,4);
       
Sets.SetView<Integer> intersection = Sets.intersection(set1, set2);

Joda Time

官方地址:https://www.joda.org/joda-time/

一個日期、時間處理的工具庫。如果你不是經常做日期處理,那差不多每次需要的時候都需要查詢相關的 API,而有了工具類就不一樣了,只要一個 “.”,你想要的方法就出現了,而 Joda Time 就是一款好用的工具庫。

比如下面這個方法,計算到新年還有多少天。

public Days daysToNewYear(LocalDate fromDate) {
  LocalDate newYear = fromDate.plusYears(1).withDayOfYear(1);
  return Days.daysBetween(fromDate, newYear);
}

OkHttp3

官方地址:https://square.github.io/okhttp/

一個 HTTP 客戶端,使用簡單,性能良好,是時候放棄 HttpClient 了。

一個 get 請求:

OkHttpClient client = new OkHttpClient();

String run(String url) throws IOException {
  Request request = new Request.Builder()
      .url(url)
      .build();

  try (Response response = client.newCall(request).execute()) {
    return response.body().string();
  }
}

一個 post 請求:

public static final MediaType JSON
    = MediaType.get("application/json; charset=utf-8");

OkHttpClient client = new OkHttpClient();

String post(String url, String json) throws IOException {
  RequestBody body = RequestBody.create(json, JSON);
  Request request = new Request.Builder()
      .url(url)
      .post(body)
      .build();
  try (Response response = client.newCall(request).execute()) {
    return response.body().string();
  }
}

Json 系列

Jackson

Spring 默認的 Json 序列化工具,其實已經足夠用了。

Gson

Google 出品,功能齊全。

FastJson

阿里出品,算法良好,性能最優。

EasyExcel

官方地址:https://www.yuque.com/easyexcel/doc/easyexcel

阿里開源的 Excel 操作工具庫,可以看做是 Apache POI 的增強封裝版、優化版。

如果你的數據量很大,那用 EasyExcel 可以節省內存,提升效率,並且沒有併發風險。

如果你的 Excel 足夠複雜,那用 EasyExcel 會比你直接用 POI 少些很多代碼。

比如我實現了下面這個 Excel 動態導出,包括動態表頭、動態合併單元格的功能,只用了很少的代碼,如果是使用 POI 的話,那可能代碼量增加不止一倍啊。

TinyPinyin

官方地址:https://github.com/promeG/TinyPinyin

中文轉拼音,把你輸入的中文轉換成拼音。比如搜索功能要實現這樣的功能,輸入 “fengzheng” 搜索,會匹配到 “風箏”這個詞語,這就需要中文轉拼音了。

有的同學說了,這不是拼音轉英文嗎?當然不是在輸入“fengzheng”的時候轉換了,而是在包含“風箏”的這條記錄中有一個拼音的額外字段,這樣搜索的時候直接匹配拼音那個字段。

chinese_name pinyin_name
風箏 fengzheng

反射工具庫 – jOOR

官方地址:https://github.com/jOOQ/jOOR

它是 JDK 反射包的友好封裝,通過一系列簡單友好的鏈式操作實現反射調用。比如下面這個例子

public interface StringProxy {
  String substring(int beginIndex);
}

String substring = on("java.lang.String")
                    .create("Hello World")
                    .as(StringProxy.class)
                    .substring(6);    

簡單的代碼實現 JDK 動態代理,節省了不少代碼。

MyBatis-Plus

官方地址:https://mp.baomidou.com/

只要你的項目中有數據庫訪問,那你肯定用過或者至少聽說過 MyBatis ,但是如果你只用 MyBatis 需要針對每個DAO方法寫對應的 SQL Statement(也就是 mapper.xml 中的代碼塊),當然有一些自動生成的工具,MyBatis 就有它提供的 MyBatis Generator,比如我也稍做加工,做過一個web 版的 MyBatis Generator,開發效率是提高了,但是每個 mapper.xml 文件的代碼量很大,於是 MyBatis-Plus 就要出場了。

官網上對他的定義如下:

  1. 只做增強不做改變,引入它不會對現有工程產生影響,如絲般順滑。
  2. 只需簡單配置,即可快速進行 CRUD 操作,從而節省大量時間。
  3. 熱加載、代碼生成、分頁、性能分析等功能一應俱全。

最後,在配上 MybatisX IDEA 插件,也是可以了。

vjtools

官方地址:https://github.com/vipshop/vjtools

這是唯品會的開源工具包,這裏主要介紹其中的 vjkit 模塊,是關於文本,集合,併發等基礎功能的核心類庫。這個庫是我很早之前搜索日期操作的時候偶然發現的,我發現裏面日期處理的 API 相當全面而且很實用,還在我的項目中用過一段時間。

最後

好用的工具庫可以提高我們的開發效率,而且也是我們學習源碼的好去處,和其他的開源框架(比如 Spring、Dubbo)一樣,看看優秀的代碼是如何實現的。

如果你還知道什麼好用、強大的開源工具包,歡迎在留言區分享,好東西不能獨享,讓更多的人受益。

各位大佬,給個推薦,讓我奮發圖強

我是風箏,公眾號「古時的風箏」。一個兼具深度與廣度的程序員鼓勵師,一個本打算寫詩卻寫起了代碼的田園碼農!你可選擇現在就關注我,或者看看歷史文章再關注也不遲。

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

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

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

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

如何優雅地向公司提加薪

二哥,你好呀,我是你鐵杆粉絲,想向你請教一個問題。我是 2019 年 3 月份入職的,當時很菜,接手的是一個要離職同事的代碼,可把我害慘了,一邊推進度,一邊修 bug,7 月份一整個月都沒有在凌晨 3 點前睡過。幸好挺了過來,截止目前在公司待了一年零兩個月了,想找領導談薪水的問題,但不知道如何開口。

以上是讀者奔三十私信我的問題,很有代表性,我計劃着好好寫一篇文章來統一回復下,結果一拖再拖,拖了快一個月時間,真的非常非常抱歉。

我之所以拖,有兩個原因。第一個原因就是這個問題確實不太好回答,因為我自己親身經歷的漲薪就那麼幾次,並且我沒去過大廠,經驗不一定具備普適性;第二個原因就是,拖延症犯了,呵呵(戰術性)。

那接下來,就談談我自己僅有的幾次加薪情況吧,希望給小夥伴們一些參考。

我是大三就出去實習的,當時工資 1200 塊一個月,一起進來的二十多名新人都這個標準。這個不算是加薪,但起點大家要了解一下。

簡單介紹一下背景,可能一些新來的讀者不太清楚。

我是一名大專生,這沒什麼可恥的,真的。經常有一些讀者私信我說,“二哥,我學歷不好,大專生,畢業后被歧視怎麼辦?”

起點低,被歧視是正常的,要學會接受。人生下來就不是平等的,我們要做的就是自尊自愛。網絡上經常遇到一些噴子,留言噴我垃圾,我就回一句話:“我與你的區別就是,我越來越強大,而你,還是那個噴子,而已。”

大二結束后,我就去蘇州一家軟件園培訓了倆月,然後入職了一家日企(江蘇富士通,讀者群體里有沒有知道這家公司的?),當時軟件部剛剛成立,急需新鮮血液的融入,而我們這些廉價的勞動力正好是壓榨的對象。

實習結束后,回學校拿了畢業證(順帶和女朋友團聚了倆月時間),然後正式入職。但記不得當時具體的工資是多少了,四五千吧。

這次加薪我是沒有資本吭聲的,因為有一份工作就不錯了。況且這家公司發展的真不錯,項目部不斷擴大,剛開始一個部門,我回洛陽的時候已經四個部門了。

關鍵是,不缺項目啊,日方那邊源源不斷地供給着項目,最重要的是,資金。公司本來還有一片地,在鄧蔚路的綠寶廣場附近,修地鐵的時候賣了,然後在一個叫什麼區的地方買了幾棟樓,名字我忘記了,當時挺偏僻的。

女朋友畢業后,去蘇州過一段時間,我們在市區租了一間房子,每次上班我要先走一段路,搭公司的大巴去上班,要一個小時左右車程呢。

由於我是大專生,所以這次加薪,要比本科學歷的同事少三四百,具體数字同事之間也不太方便交底,反正是確實少一些,但差額並沒有很離譜。

正式入職一年後,我換了一個項目組, 改做 Flex 了。之前基本上是打雜,搞過 SQL、搞過 Ruby、搞過 Spring + Hibernate + Postgresql,基本上是哪需要就往哪插。

這一年時間里,我了解到公司的發展重心將會是 Flex,就私下里研究了整個框架的源碼,並且寫了一個內部聊天工具,供幾個老資格的同事聊魔獸世界用。這點我在之前的文章里提到過。

(心機 boy,有沒有?)

正是憑藉這個不起眼的工具,我被這幾個老資格的同事推薦給了後來的直屬領導,說我這個年輕人有技術,頭腦又靈活,是塊好材料。

新成立的項目組,加上翻譯,好像是二十三個人,不算小的一個團隊了。我帶五個新人編碼。

就這樣幹了半年,領導覺得我的表現無可挑剔,確實能攻堅,就主動找我提加薪。當時激動壞了,內心告訴自己,一定要多要點,超過那幾個一塊來的本科生。

然後我就順嘴說了一個數目,自己覺得不算少了,領導也二話沒說就欣然答應了。

結果,我特么天真了,當時要太少了。後來幾個同事透露說,我提的額度和他們差不多。麻蛋,白白錯過了一次拉開差距的機會啊。

事後諸葛亮一下,要提加薪,最好了解一下公司內部的行情(想辦法)。領導他自己那是有個標準的,如果你確實表現優異,要想盡辦法知道領導這個線是多少,然後再這個基線上往上浮動一些,尤其是領導主動過來問價的時候。

第二次加薪是我離開蘇州的前一個月,2013 年底。這次加薪是公司主動加的,因為之前的合同到期了。

我當時和之前的那個領導鬧翻了,被調換到了另外一個開發部,所以動了離開的心思。再加上女朋友已經回到了洛陽,確實到了該和蘇州說再見的時候了。

拿到第一筆獎金后,我提了離職。結果公司比我精明得多,年底的獎金是分批發的,況且第二筆獎金比第一筆獎金多得多。我顯然是沒機會拿第二筆了。

過年前,領導終於批了我的離職申請,手續辦完,公積金取了出來后,我就回洛陽了——帶着不舍,畢竟在蘇州生活了三年半,有感情了。

第三次加薪是我回到洛陽工作后的第二個月,2014 年 3 月份。當時實習工資只有 2500,實在是受不了,第二個月過了一周我就迫不及待地找領導申請轉正了。

公司是一家挂名北大青鳥的培訓機構,不過我不是做講師,而是在獨立運營的開發部——承接一些政府或者個人項目。

團隊小,而我的實力確實過硬,再加上和領導、人事之間的關係好,公司就破格給我轉正了,工資上漲了 2/3,並且繳納了五險一金,加上績效獎,每個月到手的工資超過了當時洛陽的平均房價。

為什麼敢提轉正,敢提漲薪,這裏很重要的一點就是,我在團隊的表現是獨一檔的,工作上沒有解決不掉的難題,還能給領導提出建設性的意見,關係處得非常好。

每天早上,我基本上是第一個到工作崗位的,因為辦公室鑰匙我拿着,不去早也不行啊——當然了,這件事是我主動請纓的。

(心機 boy,有沒有?)

因為是指紋打卡嘛,我打卡的時間還被人事在月度總結會議上表揚過一次。上班早,下班我也不甘示弱,領導啥時候走我就啥時候走。

所以,綜合工作表現,為人處事的表現,公司不給我轉正說不過去啊,對不對?

簡單總結一下,也算是對讀者奔三十的一些建議。如何優雅地向公司提加薪?必須得做好以下三點:

第一,工作表現確實沒得說,該抗的事得能抗下來。逼着領導過來找你加薪,前提是自己一定要了解公司內部的漲薪結構,不要少要,也不要獅子大開口。

第二,如果是小公司的話,和領導的關係走得近一些,和同事之間的關係處得好一些,不要背後捅刀子。這樣提加薪的時候,領導不為難。

第三,臉皮要厚,臉皮要厚,臉皮要厚,重要的事情說三遍。

如果覺得文章對你有點幫助,請微信搜索「 沉默王二 」第一時間閱讀。回復關鍵字「簡歷」更有一份技術大佬整理的優質簡歷模板,助你一臂之力。

本文已收錄 GitHub,傳送門~ ,裏面更有大廠面試完整考點,歡迎 Star。

我是沉默王二,一枚有顏值卻靠才華苟且的程序員。關注即可提升學習效率,別忘了三連啊,點贊、收藏、留言,我不挑,嘻嘻

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

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

移動UI系列 – 簡單地使用半衰期算法來預測手勢的滑動方向與速度

前言

有一個問題, 給定一個物體的運動軌跡, 包含時間和坐標的數組, 如何使用這個數據來預測物體未來的運動走勢??

本文提供了一個很簡單的方式去實現這個算法. 效果夠用, 又簡單, 有一定的準確程度. 

 

緣由

以前做過的一些手機應用, 沒有做動畫的, 也沒做手勢. 這個做起來挺麻煩的. 

最近開始了新的手機項目(微信項目模板) , 基於 Blazor server side , 其組件化的方式可以很方便地把各種常用的通用的東西封裝成 組件/控件 

於是, 就打算讓這段時間辛苦一些, 一次過把動畫的部分補上, 讓以後的項目更加牛逼, 意思是讓客戶更加樂意地掏錢. 

 

問題

手勢的一個問題是力度/速度判斷, 劃得快, 劃得慢, 先快後慢, 先慢後快, 都得有一個合適的算法來得到一個最後速度. 

這個算法一定要滿足基本的需求後, 要盡量簡單, 要目測, 理論上沒有bug . 

這個時候, 半衰期算法就派上用場了. (不知道有沒有半衰期算法這玩意, 應該說是使用半衰期的原理)

 

原理

這是一個很簡單的權重計算, 有很多個不同時間點的坐標, 每個相鄰時間的兩個數據, 有距離差, 有時間差

關鍵是解決先慢後快, 先快後慢的數據的處理問題, 那麼我們只需要

把新的數據, 乘以更大的權重, 把老的數據, 乘以更小的權重, 最後得到這個距離權重的合成值, 就OK了. 

 

預覽效果:

注意, 因為gif的幀數不夠, 要更好效果還是複製代碼運行一遍. 

 

 

測試代碼:

<! DOCTYPE html > 
< html > 
< head > 
    < meta charset ="utf-8"  /> 
    < title > Half Life </ title > 
    < style > 
        html, body, canvas { width : 100% ; height : 100% ; margin : 0 ; padding : 0 ; box-sizing : border-box ; overflow : hidden ; }
    </ style > 
</ head > 
< body >

    < canvas ></ canvas >

    < script type ="text/javascript" >

        var CONST_HALF_LIFE_SPAN =  50 ;     //半衰期,設置得越小,就越看淡以前的速度
        var CONST_MAX_SAMPLES =  15 ;         //保留最後15個點的數據減少無意義的運算量,主要看瀏覽器觸發onmousemove的頻率來調整.

        var pts = [];
        document.onmousemove =  function (e) {

            //儲存數據
            pts.push({ time: Date.now(), x: e.clientX, y: e.clientY });
             if (pts.length > CONST_MAX_SAMPLES) pts.shift();

            //計算

            var xs =  0 , ys =  0 ;     //走過的路的長度. 
            var previtem = pts[ 0 ];
             for ( var index =  1 ; index < pts.length; index ++ ) {
                 var newitem = pts[index ];
                 var t = newitem.time - previtem.time;

                //讓這個數據衰減一次,衰減程度由時間差決定. 
                var halflifefactor = Math.pow( 0.5 , t / CONST_HALF_LIFE_SPAN);

                //注意,這裏沒有計算速度,而是直接用距離來預測將來要走過的距離.

                //走過的距離每一次都要衰減,每一段的路程都多次乘以各時間差的factor, 
                //原理是, 0.5 ^ (t1 + t2 + t3...)等於0.5 ^ t1 * 0.5 ^ t2 * 0.5 ^ t3 * ... 
                xs = xs * halflifefactor + newitem.x - previtem.x;
                ys = ys * halflifefactor + newitem.y - previtem.y;

                previtem = newitem;
            }

            //畫圖

            var CONST_EFFECT_FACTOR =  2 ;     //乘以一個因素來畫圖用,這裏數值可以充當'摩擦係數'大小的效果. 
            xs = Math.floor(xs * CONST_EFFECT_FACTOR);
            ys = Math.floor(ys * CONST_EFFECT_FACTOR);

            var x0 = e.clientX, y0 = e.clientY;

            var canvas = document.querySelector( " canvas " );
             var ctx = canvas.getContext( " 2d " );
             var w = canvas.width = canvas.offsetWidth;
             var h = canvas.height = canvas.offsetHeight;
            ctx.clearRect( 0 , 0 , w, h);
            ctx.lineWidth =  5 ;
            ctx.strokeStyle =  " blue " ;
            ctx.beginPath();
            ctx.moveTo(x0, y0);
            ctx.lineTo(x0 + xs, y0 + ys);
            ctx.closePath();
            ctx.stroke();

            console.log(xs, ys)
        }
    </ script >

</ body >

</ html >

 

簡單講解

可以看出, 代碼量非常少. 與其說這是一種”算法” , 不然說是一種”思路” 

 

代碼先是記錄每一點的數據, 然後放進 pts 數組 

在鼠標移動後, 使用pts 數組, 計算出每一點的x , y 的移動量, 讓每一段的移動量都使用 半衰期 的方式進行調整. 

最後得出的xs , ys ,是理論上“最近移動的距離.”

使用這個數值,作為“參考值” ,就可以用於更多的判斷. 

 

例如我最近做的一個簡單的 swipe 組件, 

手指滑動了1/4個屏幕,然後再加上理論的參考值,  一共超過了1/2個屏幕,就切換到下一個panel 

這個參考值很重要. 如果手指只是很慢地滑動, 那麼參考值就很小, 就不應該切換. 如果手指很快地滑動, 那麼就應該切換panel

 

可改良的方案

上面方案, 是計算’移動距離’ 的, 它有一個弊端, 無論劃得多快, 移動的總數是有上限的. 

這段代碼完全可以 除以t , 得到一個 速度值,

這更加合理, 但是注意速度的合成方式不能普通地累加. 

 

這就留給有興趣的網友自己嘗試了. 也不難. 畢竟本文傳播的是 半衰期 的思路. 不宜說太細. 

 

 

擴展思路

其實這種算法, 一直都有人用. 很奇怪的就是, 沒有看到什麼人專門寫文章介紹? 

半衰期除了計算運動軌跡, 還可以很好地去統計熱度. 

例如一篇文章, 一個視頻, 有不同時段的點擊數量. 每一天都不一樣. 

 

怎樣使用最少的儲存方式, 去儲存一個合理的熱度參考值? 

可行的方法是,儲存兩個數值 (就夠了) : 

articleRateValue 用於儲存熱度值

articleRateTime 用於儲存熱度時間

 

每一次點擊, 都使用這個公式儲存: 

articleRateValue = newclickcount + articleRateValue * POW(0.5, (NOW – articleRateTime) / HALF_LIFE_SPAN )

articleRateTime = NOW 

排序的時候麻煩點, 要實時的計算 articleRateValue * POW(0.5, (NOW – articleRateTime) / HALF_LIFE_SPAN ) 來得到每個文章的熱度值. 

(對於排序的問題, 還有兩個變通的做法, 如果以後有時間寫一篇文章細說這個熱度方案, 再詳細解說)

(最簡單的變通方法是, 每晚找服務器空閑的時候, 把表裡的數值都重新計算一次, 平時排序的時候不計算) 

 

注意這裡有一個 HALF_LIFE_SPAN 的概念. 如果 HALF_LIFE_SPAN 是一天, 那麼昨天的熱度就佔1/2 權重, 前天的就佔 1/4 權重, 大前天的佔1/8 

這樣按天算也有個弊端, 我想按週, 按月算那怎麼辦? 

再存兩組數值 :

weeklyRateValue

weeklyRateTime

monthlyRateValue

monthlyRateTime 

如此類推. 

 

這樣做的最大好處是, 無需詳細地紀錄每一次的點擊數據. 非常節省空間.  

缺點是, 每一組數據, 只適合一個半衰期時段的數值, 要儲存多個參考值得準備多組數據. 

 

最後

時間過得真快. 這段時間挖的坑太多, 在業餘時間內根本沒有時間填坑..

5月初的時候實現了BlazorCefApp , 到現在開了幾個有意義的坑, Blazor微信項目模板 算是一個. 

但是由於沒有時間寫博客, 有時只是偶爾把測試的視頻放到B站: https://space.bilibili.com/540073960 

有興趣用Blazor 來做微信項目或手機網頁項目的, 可以去了解一下. 當項目成熟後, 會發佈到github上. 

—-

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

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

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

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

【其他文章推薦】

※回頭車貨運收費標準

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

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

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

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

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

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