玩車的人都在捧的本田發動機,真的這麼神?

然而,這種熟悉的“爆tec”感覺沒有出現在最新的謳歌TLX-L上。謳歌是本田的豪華品牌,一切的用料,技術,做工等都用上高標準。就連本田被人詬病的隔音謳歌也能做得安靜。被拉高轉的本田發動機“爆tec”時的感覺聲音和動力都有着明顯變化,讓人為之興奮,但開着謳歌TLX-L卻沒有這種感覺。

本田,一個無人不知無人不曉的一個汽車品牌。尤其是在廣東地區,更是本田粉活躍的地方,大街上跑的10輛車中,有4輛或5輛是本田車型。

買本田車,除了買質量可靠性外,更多的是為了“買發動機送車”,而坊間也流傳着本田金句VTEC is the best。可想而知,本田發動機技術含量和民望有多高。

本田發動機的看家技術VTEC和i-VTEC技術外,在近年也推出VTEC Turbo這渦輪組合技術。

那本田技術牛在哪?主要除了有可變氣門正時外,還有氣門升程這一技術。氣門升程技術並不是很多發動機有,除了寶馬和英菲尼迪VQ37系列外,還真是寥寥無幾。

開過本田的人未必知道什麼叫“爆tec”,但拉過高轉的人就知道。當發動機轉速拉到5000轉左右的時候,聲音突變,發動機也感覺顫抖一下。這就是“爆tec”給人的感覺。

然而,這種熟悉的“爆tec”感覺沒有出現在最新的謳歌TLX-L上。謳歌是本田的豪華品牌,一切的用料,技術,做工等都用上高標準。就連本田被人詬病的隔音謳歌也能做得安靜。

被拉高轉的本田發動機“爆tec”時的感覺聲音和動力都有着明顯變化,讓人為之興奮,但開着謳歌TLX-L卻沒有這種感覺。難道就因為高端車型而被“技術”隔絕?

其實這種“爆tec”的感覺在自主品牌的渦輪車上有着“更好的體現”。只不過我們吐槽它而已,這一技術就是渦輪遲滯。體驗過大部分自主品牌渦輪車,輕踩油門不去,踩重一點像是被踢出去的感覺。這種感覺比“爆tec”還爽快。

總結:高轉“爆tec”還是低轉渦輪介入明顯,都使得發動機動力輸出沒那麼“線性”。只是拉高轉的時候,亢奮的聲音和動力的凸顯使得“爆tec”的感覺被神化。渦輪介入明顯,其實就是“爆tec”時動力凸顯的一部分,只是沒有亢奮的聲音罷了。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

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

Python機器學習筆記:SVM(4)——sklearn實現,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實現

  對SVM的概念理清楚后,下面我們對其使用sklearn進行實現。

1,Sklearn支持向量機庫概述

  我們知道SVM相對感知器而言,它可以解決線性不可分的問題,那麼它是如何解決的呢?其思想很簡單就是對原始數據的維度變換,一般是擴維變換,使得原樣本空間中的樣本點線性不可分,但是在變維之後的空間中樣本點是線性可分的,然後再變換后的高維空間中進行分類。

  上面將SVM再贅述了一下,下面學習sklearn中的SVM方法,sklearn中SVM的算法庫分為兩類,一類是分類的算法庫,主要包含LinearSVC,NuSVC和SVC三個類,另一類是回歸算法庫,包含SVR,NuSVR和LinearSVR三個類,相關模塊都包裹在sklearn.svm模塊中。

  對於SVC,NuSVC和LinearSVC 三個分類的庫,SVC和NuSVC差不多,區別僅僅在於對損失的度量方式不同,而LinearSVC從名字就可以看出,他是線性分類,也就是不支持各種低維到高維的核函數,僅僅支持線性核函數,對線性不可分的數據不能使用。

  同樣的對於SVR,NuSVR和LinearSVR 三個回歸的類,SVR和NuSVR差不多,區別也僅僅在於對損失的度量方式不同。LinearSVR是線性回歸,只能使用線性核函數。

  我們使用這些類的時候,如果有經驗知道數據是線性可以擬合的,那麼使用LinearSVC去分類或者LinearSVR去回歸,他們不需要我們去慢慢的調參選擇各種核函數以及對應的參數,速度也快。如果我們對數據分佈沒有什麼經驗,一般使用SVC去分類或者SVR去回歸,這就需要我們選擇核函數以及對核函數調參了。

2,回顧SVM分類算法和回歸算法

  我們這裏仍然先對SVM算法進行回顧,首先對於SVM分類算法,其原始形式如下:

  其中 n 為樣本個數,我們的樣本為(x1,  y1),(x2,y2),….(xn,  yn),w,b是我們的分離超平面的 wT*xi + b = 0的係數,ξi 為第 i 個樣本的鬆弛係數,C 為懲罰係數,xi (也有時候寫為Φ(xi) 為低維到高維的映射函數)為樣本數。

  通過拉格朗日以及對偶化后的形式為:

  其中和原始形式不同的 α 為拉格朗日係數向量,<xi,  xj> 為我們要使用的核函數。

  對於SVM回歸算法,(我自己沒有總結,借用劉建平老師的博客),其原始形式如下:

  其中 m 為樣本個數,我們的樣本為(x1, y1),(x2,  y2),….,(xm, ym),w,b是我們回歸超平面 wT*xi + b = 0 的係數,ξv, ξ^ 為第 i 個樣本的鬆弛係數, C為懲罰係數,ε 為損失邊界,到超平面距離小於 ε 的訓練集的點沒有損失,Φ(xi) 為低維到高維的映射函數

  通過拉格朗日函數以及對偶后的形式為:

  其中和原始形式不同的 αv, α^ 為拉格朗日係數向量,K(xi, xj) 為我們要使用的核函數。

3,SVM核函數概述

  我在第二篇SVM中學習了核函數,有好幾種,最常用的就是線性核函數,多項式核函數,高斯核函數和Sigmoid核函數,在scikit-learn中,內置的核函數也剛好有這四種。

3.1,線性核函數(Linear Kernel)

  線性核函數表達式為:

  就是普通的內積,LinearSVC和LinearSVR只能使用它。

3.2,多項式核函數(Polynomial Kernel)

  多項式核函數是線性不可分SVM常用的核函數之一,表達式為:

  參數都需要自己調參定義,比較麻煩。

3.3,高斯核函數(Gaussian Kernel)

  高斯核函數,在SVM中也稱為 徑向基核函數(Radial Basisi Function,RBF),它是libsvm默認的核函數,當然也是sklearn默認的核函數,表達式為:

  其中 r 大於0,需要自己調參定義,不過一般情況,我們都使用高斯核函數。

3.4,Sigmoid核函數(Sigmoid Kernel)

  Sigmoid核函數也是線性不可分SVM常用的核函數之一,表示為:

  其中 beta, t 都需要自己調參定義。

  一般情況下,對於非線性數據使用默認的高斯核函數會有比較好的效果,如果你不是SVM調參高手的話,建議使用高斯核來做數據分析。

4,SVM分類算法庫參數小結

  下面我們將具體介紹這三種分類方法都有那些參數值以及不同參數值的含義。

4.1, LinearSVC

  其函數原型如下:

class sklearn.svm.LinearSVC(self, penalty='l2', loss='squared_hinge', dual=True, tol=1e-4,
             C=1.0, multi_class='ovr', fit_intercept=True,
             intercept_scaling=1, class_weight=None, verbose=0,
             random_state=None, max_iter=1000)

  參數說明

  • penalty :正則化參數,L1 和L2兩種參數可選,僅LinearSVC有。默認是L2 正則化,如果我們需要產生稀疏的話,可以選擇L1正則化,這和線性回歸裏面的Lasso回歸類似
  • loss:損失函數,有“hinge” 和“squared_hinge” 兩種可選,前者又稱為L1損失,後者稱為L2損失,默認是“squared_hinge”,其中hinge是SVM的標準損失,squared_hinge是hinge的平方。
  • dual:是否轉化為對偶問題求解,默認是True。這是一個布爾變量,控制是否使用對偶形式來優化算法。
  • tol:殘差收斂條件,默認是0.0001,與LR中的一致。
  • C:懲罰係數,用來控制損失函數的懲罰係數,類似於LR中的正則化係數。默認為1,一般需要通過交叉驗證來選擇一個合適的C,一般來說,噪點比較多的時候,C需要小一些
  • multi_class:負責多分類問題中分類策略制定,有‘ovr’和‘crammer_singer’ 兩種參數值可選,默認值是’ovr’,’ovr’的分類原則是將待分類中的某一類當作正類,其他全部歸為負類,通過這樣求取得到每個類別作為正類時的正確率,取正確率最高的那個類別為正類;‘crammer_singer’ 是直接針對目標函數設置多個參數值,最後進行優化,得到不同類別的參數值大小。
  •  fit_intercept:是否計算截距,與LR模型中的意思一致。
  • class_weight:與其他模型中參數含義一樣,也是用來處理不平衡樣本數據的,可以直接以字典的形式指定不同類別的權重,也可以使用balanced參數值。如果使用“balanced”,則算法會自己計算權重,樣本量少的類別所對應的樣本權重會高,當然,如果你的樣本類別分佈沒有明顯的偏倚,則可以不管這個係數,選擇默認的None
  • verbose:是否冗餘,默認為False
  • random_state:隨機種子的大小
  • max_iter:最大迭代次數,默認為1000.

懲罰係數:

  錯誤項的懲罰係數。C越大,即對分錯樣本的懲罰程度越大,因此在訓練樣本中準確率越高,但是泛化能力降低,也就是對測試數據的分類準確率降低。相反,減少C的話,容許訓練樣本中有一些誤分類錯誤樣本,泛化能力強。對於訓練樣本帶有噪音的情況,一般採用後者,把訓練樣本集中錯誤分類的樣本作為噪音。

4.2,NuSVC

  其函數原型如下:

class sklearn.svm.NuSVC(self, nu=0.5, kernel='rbf', degree=3, gamma='auto_deprecated',
             coef0=0.0, shrinking=True, probability=False, tol=1e-3,
             cache_size=200, class_weight=None, verbose=False, max_iter=-1,
             decision_function_shape='ovr', random_state=None)

   參數說明

  • nu:訓練誤差部分的上限和支持向量部分的下限,取值在(0,1)之間,默認是0.5,它和懲罰係數C類似,都可以控制懲罰的力度。
  • kernel:核函數,核函數是用來將非線性問題轉化為線性問題的一種方法,默認是“rbf”核函數

      常用的核函數有以下幾種:

  • degree:當核函數是多項式核函數(“poly”)的時候,用來控制函數的最高次數。(多項式核函數是將低維的輸入空間映射到高維的特徵空間),這個參數只對多項式核函數有用,是指多項式核函數的階數 n。如果給的核函數參數是其他核函數,則會自動忽略該參數。
  • gamma:核函數係數,默認是“auto”,即特徵維度的倒數。核函數係數,只對rbf  poly  sigmoid 有效。
  • coef0:核函數常數值( y = kx + b 的b值),只有“poly”和“sigmoid” 函數有,默認值是0.
  • max_iter:最大迭代次數,默認值是 -1 ,即沒有限制。
  • probability:是否使用概率估計,默認是False。
  • decision_function_shape:與“multi_class”參數含義類似,可以選擇“ovo” 或者“ovr”(0.18版本默認是“ovo”,0.19版本為“ovr”) OvR(one vs rest)的思想很簡單,無論你是多少元分類,我們都可以看做二元分類,具體的做法是,對於第K類的分類決策,我們把所有第K類的樣本作為正例,除第K類樣本以外的所有樣本作為負類,然後在上面做二元分類,得到第K類的分類模型。 OvO(one vs one)則是每次在所有的T類樣本裏面選擇兩類樣本出來,不妨記為T1類和T2類,把所有的輸出為T1 和 T2的樣本放在一起,把T1作為正例,T2 作為負例,進行二元分類,得到模型參數,我們一共需要T(T-1)/2 次分類。從上面描述可以看出,OvR相對簡單,但是分類效果略差(這裡是指大多數樣本分佈情況,某些樣本分佈下OvR可能更好),而OvO分類相對精確,但是分類速度沒有OvR快,一般建議使用OvO以達到較好的分類效果
  • chache_size:緩衝大小,用來限制計算量大小,默認是200M,如果機器內存大,推薦使用500MB甚至1000MB

4.3,SVC

  其函數原型如下:

class sklearn.svm.SVC(self, C=1.0, kernel='rbf', degree=3, gamma='auto_deprecated',
             coef0=0.0, shrinking=True, probability=False,
             tol=1e-3, cache_size=200, class_weight=None,
             verbose=False, max_iter=-1, decision_function_shape='ovr',
             random_state=None)

  參數說明:

  • C:懲罰係數(前面有詳細學習)

  SVC和NuSVC方法基本一致,唯一區別就是損失函數的度量方式不同(NuSVC中的nu參數和SVC中的C參數)即SVC使用懲罰係數C來控制懲罰力度,而NuSVC使用nu來控制懲罰力度。

5,SVM回歸算法庫參數小結

  下面我們將具體介紹這三種分類方法都有那些參數值以及不同參數值的含義。

5.1, LinearSVR

  其函數原型如下:

class sklearn.svm.LinearSVR(self, epsilon=0.0, tol=1e-4, C=1.0,
             loss='epsilon_insensitive', fit_intercept=True,
             intercept_scaling=1., dual=True, verbose=0,
             random_state=None, max_iter=1000)

  參數說明

  • epsilon:距離誤差epsilon,即回歸模型中的 epsilon,訓練集中的樣本需要滿足:
  • loss:損失函數,有“hinge” 和“squared_hinge” 兩種可選,前者又稱為L1損失,後者稱為L2損失,默認是“squared_hinge”,其中hinge是SVM的標準損失,squared_hinge是hinge的平方。
  • dual:是否轉化為對偶問題求解,默認是True。這是一個布爾變量,控制是否使用對偶形式來優化算法。
  • tol:殘差收斂條件,默認是0.0001,與LR中的一致。
  • C:懲罰係數,用來控制損失函數的懲罰係數,類似於LR中的正則化係數。默認為1,一般需要通過交叉驗證來選擇一個合適的C,一般來說,噪點比較多的時候,C需要小一些
  •  fit_intercept:是否計算截距,與LR模型中的意思一致。
  • verbose:是否冗餘,默認為False
  • random_state:隨機種子的大小
  • max_iter:最大迭代次數,默認為1000.

5.2,NuSVR

  其函數原型如下:

class sklearn.svm.NuSVR(self, nu=0.5, C=1.0, kernel='rbf', degree=3,
             gamma='auto_deprecated', coef0=0.0, shrinking=True,
             tol=1e-3, cache_size=200, verbose=False, max_iter=-1)

   參數說明

  • nu:訓練誤差部分的上限和支持向量部分的下限,取值在(0,1)之間,默認是0.5,它和懲罰係數C類似,都可以控制懲罰的力度。
  • kernel:核函數,核函數是用來將非線性問題轉化為線性問題的一種方法,默認是“rbf”核函數

      常用的核函數有以下幾種:

  • degree:當核函數是多項式核函數(“poly”)的時候,用來控制函數的最高次數。(多項式核函數是將低維的輸入空間映射到高維的特徵空間),這個參數只對多項式核函數有用,是指多項式核函數的階數 n。如果給的核函數參數是其他核函數,則會自動忽略該參數。
  • gamma:核函數係數,默認是“auto”,即特徵維度的倒數。核函數係數,只對rbf  poly  sigmoid 有效。
  • coef0:核函數常數值( y = kx + b 的b值),只有“poly”和“sigmoid” 函數有,默認值是0.
  • chache_size:緩衝大小,用來限制計算量大小,默認是200M,如果機器內存大,推薦使用500MB甚至1000MB

5.3,SVR

  其函數原型如下:

class sklearn.svm.SVC(self, kernel='rbf', degree=3, gamma='auto_deprecated',
             coef0=0.0, tol=1e-3, C=1.0, epsilon=0.1, shrinking=True,
             cache_size=200, verbose=False, max_iter=-1)

  參數說明:

  SVR和NuSVR方法基本一致,唯一區別就是損失函數的度量方式不同(NuSVR中的nu參數和SVR中的C參數)即SVR使用懲罰係數C來控制懲罰力度,而NuSVR使用nu來控制懲罰力度。

6,SVM的方法與對象

6.1 方法

  三種分類的方法基本一致,所以一起來說:

  • decision_function(x):獲取數據集X到分離超平面的距離
  • fit(x , y):在數據集(X,y)上使用SVM模型
  • get_params([deep]):獲取模型的參數
  • predict(X):預測數值型X的標籤
  • score(X,y):返回給定測試集合對應標籤的平均準確率

6.2  對象

  • support_:以數組的形式返回支持向量的索引
  • support_vectors_:返回支持向量
  • n_support_:每個類別支持向量的個數
  • dual_coef:支持向量係數
  • coef_:每個特徵係數(重要性),只有核函數是LinearSVC的是可用,叫權重參數,即w
  • intercept_:截距值(常數值),稱為偏置參數,即b

  加粗的三個屬性是我們常用的,後面會舉例說明 support_vectors_。

 

7,SVM類型算法的模型選擇

7.1 PPT總結

  這裏使用(http://staff.ustc.edu.cn/~ketang/PPT/PRLec5.pdf)的PPT進行整理。

7.2 SVM算法庫其他調參要點

  下面再對其他調參要點做一個小結:

  • 1,一般推薦在做訓練之前對數據進行歸一化,當然測試集的數據也要做歸一化
  • 2,在特徵數非常多的情況下,或者樣本數遠小於特徵數的時候,使用線性核,效果就很好了,並且只需要選擇懲罰係數C即可
  • 3,在選擇核函數的時候,如果線性擬合效果不好,一般推薦使用默認的高斯核(rbf),這時候我們主要對懲罰係數C和核函數參數 gamma 進行調參,經過多輪的交叉驗證選擇合適的懲罰係數C和核函數參數gamma。
  • 4,理論上高斯核不會比線性核差,但是這個理論就建立在要花費更多的時間上調參上,所以實際上能用線性核解決的問題我們盡量使用線性核函數

   在SVM中,其中最重要的就是核函數的選取和參數選擇了,當然這個需要大量的經驗來支撐,這裏幾個例子只是自己網上找的SVM的小例子。

8,SVM調參實例1

  下面學習支持向量機的使用方法以及一些參數的調整,支持向量機的原理就是將低維不可分問題轉換為高維可分問題。這裏不再贅述。

8.1  線性可分支持向量機

  首先做一個簡單的線性可分的例子,這裏直接使用sklearn.datasets.make_blobs 生成數據。生成數據代碼如下:

# 生成數據集
from sklearn.datasets.samples_generator import make_blobs
from matplotlib import pyplot as plt

# n_samples=50 表示取50個點,centers=2表示將數據分為兩類
X, y = make_blobs(n_samples=50, centers=2, random_state=0, cluster_std=0.6)
# 畫圖形
plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
plt.show()

   我們畫圖展示如下:

  我們嘗試繪製分離兩組數據的直線,從而創建分類模型,對於這裏所示的數據,這是我們可以手動完成的任務。但是立馬可以看出有很多分界線可以完美的區分兩個類。

   下面畫出決策邊界。

# 生成數據集
from sklearn.datasets.samples_generator import make_blobs
from matplotlib import pyplot as plt
import numpy as np

# n_samples=50 表示取50個點,centers=2表示將數據分為兩類
X, y = make_blobs(n_samples=50, centers=2, random_state=0, cluster_std=0.6)

# 畫圖形
plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
# 線性等分詳細
xfit = np.linspace(-1, 3.5)
plt.plot([0.6], [2.1], 'x', color='red', markeredgewidth=2, markersize=10)

for m, b in [(1, 0.65), (0.5, 1.6), (-0.2, 2.9)]:
    plt.plot(xfit, m * xfit + b, '-k')

plt.show()

   圖如下:

  (注意:這三條直線是我隨便畫的,其實你可以使用Logistic回歸,線性回歸等分類,畫出線,我這裡是為了方便)

   這裡是三條不同的分割直線,並且這些分割直線能夠完全區分這些樣例。但是根據支持向量機的思想,哪一條直線是最優的分割線呢?支持向量機並不是簡單的繪製一條直線,而是畫出邊距為一定寬度的直線,直到最近的點。

  下面我們對直線進行加粗,代碼如下:

# 生成數據集
from sklearn.datasets.samples_generator import make_blobs
from matplotlib import pyplot as plt
import numpy as np

# n_samples=50 表示取50個點,centers=2表示將數據分為兩類
X, y = make_blobs(n_samples=50, centers=2, random_state=0, cluster_std=0.6)

# 畫圖形
plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
# 線性等分詳細
xfit = np.linspace(-1, 3.5)
plt.plot([0.6], [2.1], 'x', color='red', markeredgewidth=2, markersize=10)

for m, b, d in [(1, 0.65, 0.33), (0.5, 1.6, 0.55), (-0.2, 2.9, 0.2)]:
    yfit = m * xfit + b
    plt.plot(xfit, yfit, '-k')
    plt.fill_between(xfit, yfit - d, yfit + d, edgecolor='none',
                     color='#AAAAAA', alpha=0.4)  # alpha為透明度
plt.show()

   如圖所示:

   在支持向量機中,邊距最大化的直線是我們將選擇的最優模型。支持向量機是這種最大邊距估計器的一個例子。

  接下來,我們訓練一個基本的SVM,我們使用sklearn的支持向量機,對這些數據訓練SVM模型。目前我們將使用一個線性核並將C參數設置為一個默認的數值。如下:

from sklearn.svm import SVC  # Support Vector Classifier

model = SVC(kernel='linear') # 線性核函數
model.fit(X, y)

   我們順便看看SVC的所有參數情況:

SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
    decision_function_shape='ovr', degree=3, gamma='auto_deprecated',
    kernel='linear', max_iter=-1, probability=False, random_state=None,
    shrinking=True, tol=0.001, verbose=False)

   為了更好展現這裏發生的事情,下面我們創建一個輔助函數,為我們繪製SVM的決策邊界。

def plot_SVC_decision_function(model, ax=None, plot_support=True):
    '''Plot the decision function for a 2D SVC'''
    if ax is None:
        ax = plt.gca()  #get子圖
    xlim = ax.get_xlim()
    ylim = ax.get_ylim()

    # create grid to evaluate model
    x = np.linspace(xlim[0], xlim[1], 30)
    y = np.linspace(ylim[0], ylim[1], 30)
    # 生成網格點和坐標矩陣
    Y, X = np.meshgrid(y, x)
    # 堆疊數組
    xy = np.vstack([X.ravel(), Y.ravel()]).T
    P = model.decision_function(xy).reshape(X.shape)

    # plot decision boundary and margins
    ax.contour(X, Y, P, colors='k', levels=[-1, 0, 1],
               alpha=0.5, linestyles=['--', '-', '--'])  # 生成等高線 --

    # plot support vectors
    if plot_support:
        ax.scatter(model.support_vectors_[:, 0],
                   model.support_vectors_[:, 1],
                   s=300, linewidth=1, facecolors='none')

    ax.set_xlim(xlim)
    ax.set_ylim(ylim)

   下面繪製決策邊界:

def train_SVM():
    # n_samples=50 表示取50個點,centers=2表示將數據分為兩類
    X, y = make_blobs(n_samples=50, centers=2, random_state=0, cluster_std=0.6)

    # 線性核函數
    model = SVC(kernel='linear')
    model.fit(X, y)
    plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
    plot_SVC_decision_function(model)
    plt.show()
    return X, y

   結果如圖所示:

   這是最大化兩組點之間的間距的分界線,那中間這條線就是我們最終的決策邊界了。請注意:一些訓練點碰到了邊緣,如圖所示,在兩個邊界上包含兩個紅點和一個黃點,所以這三個點又稱為支持向量,是 alpha 值不為零的,這些點是這種擬合的關鍵要素,被稱為支持向量。在sklearn中,這些點存儲在分類器的 support_vectors_ 屬性中。

  我們通過下面代碼可以得出支持向量的結果。

    print(model.support_vectors_)
    '''
    [[0.44359863 3.11530945]
     [2.33812285 3.43116792]
     [2.06156753 1.96918596]]
    '''

  在支持向量機只有位於支持向量上面的點才會對決策邊界有影響,也就是說不管有多少的點是非支持向量,那對最終的決策邊界都不會產生任何影響。我們可以看到這一點,例如,如果我們繪製該數據集的前 60個點和前120個點獲得的模型:

def plot_svm(N=10, ax=None):
    X, y = make_blobs(n_samples=200, centers=2, random_state=0, cluster_std=0.6)
    X, y = X[:N], y[:N]
    model = SVC(kernel='linear')
    model.fit(X, y)

    ax = ax or plt.gca()
    ax.scatter(X[:, 0], X[:, 1], c=y, cmap='autumn')
    ax.set_xlim(-1, 4)
    ax.set_ylim(-1, 6)
    plot_SVC_decision_function(model, ax)

if __name__ == '__main__':
    # train_SVM()
    fig, ax = plt.subplots(1, 2, figsize=(16, 6))
    fig.subplots_adjust(left=0.0625, right=0.95, wspace=0.1)
    for axi, N in zip(ax, [60, 120]):
        plot_svm(N, axi)
        axi.set_title('N = {0}'.format(N))

   結果如圖所示:

  上面就是我們繪製的該數據集前60個點和前120個點獲得的模型,可以發現無論使用60,還是使用120個數據點,決策邊界都沒有發生變換,所有隻要支持向量沒變,其他的數據怎麼加都無所謂。

   這個分類器成功的關鍵在於:為了擬合,只有支持向量的位置是最重要的;任何遠離邊距的點都不會影響擬合的結果,邊界之外的點無論有多少都不會對其造成影響,也就是說不管有多少點是非支持向量,對最終的決策邊界都不會產生任何影響。

8.2 線性不可分支持向量機

  下面引入核函數,來看看核函數的威力,首先我們導入一個線性不可分的數據集。

def train_svm_plus():
    # 二維圓形數據 factor 內外圓比例(0, 1)
    X, y = make_circles(100, factor=0.1, noise=0.1)
    
    clf = SVC(kernel='linear')
    clf.fit(X, y)
    
    plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
    plot_SVC_decision_function(clf, plot_support=False)

   數據集如圖所示:

   很明顯,用線性分類器無論怎麼畫線也不能分好,那咋辦呢?下面試試高斯核變換吧。在進行核變換之前,先看看數據在高維空間下的映射:

def plot_3D(X, y, elev=30, azim=30):
    # 我們加入了新的維度 r
    r = np.exp(-(X ** 2).sum(1))
    ax = plt.subplot(projection='3d')
    ax.scatter3D(X[:, 0], X[:, 1], r, c=y, s=50, cmap='autumn')
    ax.view_init(elev=elev, azim=azim)
    ax.set_xlabel('x')
    ax.set_ylabel('y')
    ax.set_zlabel('z')


if __name__ == '__main__':
    X, y = train_svm_plus()
    plot_3D(elev=30, azim=30, X=X, y=y)

   畫出三維圖形,如圖所示:

   見證核變換威力的時候到了,引入徑向基函數(也叫高斯核函數),進行核變換:

def train_svm_plus():
    # 二維圓形數據 factor 內外圓比例(0, 1)
    X, y = make_circles(100, factor=0.1, noise=0.1)
    # 加入徑向基函數
    clf = SVC(kernel='rbf')
    clf.fit(X, y)

    plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
    plot_SVC_decision_function(clf, plot_support=False)
    return X, y

   得到的SVM模型為:

SVC(C=1000000.0, cache_size=200, class_weight=None, coef0=0.0,
    decision_function_shape='ovr', degree=3, gamma='auto_deprecated',
    kernel='rbf', max_iter=-1, probability=False, random_state=None,
    shrinking=True, tol=0.001, verbose=False)

   再次進行分類任務,代碼如下:

def train_svm_plus():
    # 二維圓形數據 factor 內外圓比例(0, 1)
    X, y = make_circles(100, factor=0.1, noise=0.1)
    # 加入徑向基函數
    clf = SVC(kernel='rbf')
    clf.fit(X, y)

    plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
    plot_SVC_decision_function(clf, plot_support=False)
    plt.scatter(clf.support_vectors_[:, 0], clf.support_vectors_[:, 1],
                s=300, lw=1, facecolors='none')
    return X, y

   分類結果如圖所示:

   可以清楚的看到效果很好,我們將線性不可分的兩對數據分割開來。使用這種核支持向量機,我們學習一個合適的非線性決策邊界。這種核變換策略在機器學習中經常被使用。

8.3 線性近似可分支持向量機——軟間隔問題

  SVM模型有兩個非常重要的參數C與gamma,其中C是懲罰係數,即對誤差的寬容忍,C越高,說明越不能容忍出現誤差,容易過擬合。C越小,容易欠擬合。C過大或過小,泛化能力變差。

  gamma 是選擇 RBF 函數作為kernel后,該函數自帶的一個參數。隱含的決定了數據映射到新的特徵空間后的分佈,gamma越大,支持向量越小,gamma值越小,支持向量越多。

  下面我們分別調劑一下C和gamma來看一下對結果的影響。

  首先我們調節C,先做一個有噪音的數據分佈

# n_samples=50 表示取50個點,centers=2表示將數據分為兩類
X, y = make_blobs(n_samples=100, centers=2, random_state=0, cluster_std=0.8)

plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')

   結果如圖所示:

   上面的分佈看起來要劃分似乎有點困難,所以我們可以進行軟件各調整看看。

# n_samples=50 表示取50個點,centers=2表示將數據分為兩類
X, y = make_blobs(n_samples=100, centers=2, random_state=0, cluster_std=0.8)

fig, ax = plt.subplots(1, 2, figsize=(16, 6))
fig.subplots_adjust(left=0.0625, right=0.95, wspace=0.1)

for axi, C in zip(ax, [10.0, 0.1]):
    model = SVC(kernel='linear', C=C)
    model.fit(X, y)
    axi.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
    plot_SVC_decision_function(model, axi)
    axi.scatter(model.support_vectors_[:, 0],
                model.support_vectors_[:, 1],
                s=300, lw=1, facecolors='none')
    axi.set_title('C={0:.1f}'.format(C), size=14)

   結果如圖所示:

   可以看到左邊這幅圖C值比較大,要求比較嚴格,不能分錯東西,隔離帶中沒有進入任何一個點,但是隔離帶的距離比較小,泛化能力比較差。右邊這幅圖C值比較小,要求相對來說比較松一點,隔離帶較大,但是隔離帶中進入了很多的黃點和紅點。那麼C大一些好還是小一些好呢?這需要考慮實際問題,可以進行K折交叉驗證來得到最合適的C值。

   下面再看看另一個參數gamma值,這個參數值只是在高斯核函數裏面才有,這個參數控制着模型的複雜程度,這個值越大,模型越複雜,值越小,模型就越精簡。

  代碼如下:

# n_samples=50 表示取50個點,centers=2表示將數據分為兩類
X, y = make_blobs(n_samples=100, centers=2, random_state=0, cluster_std=0.8)

fig, ax = plt.subplots(1, 3, figsize=(16, 6))
fig.subplots_adjust(left=0.0625, right=0.95, wspace=0.1)

for axi, gamma in zip(ax, [10.0, 1.0, 0.1]):
    model = SVC(kernel='rbf', gamma=gamma)
    model.fit(X, y)
    axi.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
    plot_SVC_decision_function(model, axi)
    axi.scatter(model.support_vectors_[:, 0],
                model.support_vectors_[:, 1],
                s=300, lw=1, facecolors='none')
    axi.set_title('gamma={0:.1f}'.format(gamma), size=14)

   結果如下:

   可以看出,當這個參數較大時,可以看出模型分類效果很好,但是泛化能力不太好。當這個參數較小時,可以看出模型裏面有些分類是錯誤的,但是這個泛化能力更好,一般也應有的更多。

  通過這個簡單的例子,我們對支持向量機在SVM中的基本使用,以及軟間隔參數的調整,還有核函數變換和gamma值等一些參數的比較。

  完整代碼請參考我的GitHub(地址:https://github.com/LeBron-Jian/MachineLearningNote)。

9,SVM調參實例2

  下面我們用一個實例學習SVM RBF分類調參(此例子是劉建平老師的博客內容,鏈接在文後)。

  首先,我們生成一些隨機數據,為了讓數據難一點,我們加入了一些噪音,代碼如下:

# _*_coding:utf-8_*_
import numpy as np
import matplotlib.pyplot as plt
from sklearn.svm import SVC
from sklearn.datasets import make_moons, make_circles
from sklearn.preprocessing import StandardScaler
from matplotlib.colors import ListedColormap

X, y = make_circles(noise=0.2, factor=0.5, random_state=1)
# 對數據進行標準化
X = StandardScaler().fit_transform(X)

# 下面看看數據長什麼樣子
cm = plt.cm.RdBu
cm_birght = ListedColormap(['#FF0000', '#0000FF'])
ax = plt.subplot()

ax.set_title('Input data')
# plot the training points
ax.scatter(X[:, 0], X[:, 1], c=y, cmap=cm_birght)
ax.set_xticks([])
ax.set_yticks([])
plt.tight_layout()
plt.show()

  上面代碼對數據做了標準化,注意做標準化和不做標準化的差異(不一定所有的數據標準化后的效果更好,但是絕大多數確實更好)。比如下圖:

  我們看,當不做數據標準化,我們x1的取值範圍由0~90不等,當做了數據標準化之後,其取值範圍就在-2~2之間了。說明標準化的作用還是很明顯的,不多贅述,下面繼續。

   生成的數據如下(可能下一次運行,就變了哈):

   知道數據長什麼樣了,下面我們要對這個數據集進行SVM RBF分類了,分類時我們採用了網格搜索,在C=(0.1, 1, 10)和 gamma=(1, 0.1, 0.01)形成的9種情況中選擇最好的超參數,我們用了4折交叉驗證。這裏只是一個例子,實際運用中,可能需要更多的參數組合來進行調參。

  代碼及其結果如下:

# 網格搜索尋找最佳參數
grid = GridSearchCV(SVC(), param_grid={'C': [0.1, 1, 10], 'gamma': [1, 0.1, 0.01]}, cv=4)
grid.fit(X, y)
print("The best parameters are %s with a score of %0.2f"
      % (grid.best_params_, grid.best_score_))
# The best parameters are {'C': 10, 'gamma': 0.1} with a score of 0.91

   就是說,我們通過網格搜索,在我們給定的9組超參數組合中,C=10, gamma=0.1 分數最高,這就是我們最終的參數候選。

  下面我們看看SVM分類后的可視化,這裏我們把上面九種組合各個訓練后,通過對網格里的點預測來標色,觀察分類的效果圖,代碼如下:

# SVM 分類後進行可視化
x_min, x_max = X[:, 0].min(), X[:, 0].max() + 1
y_min, y_max = X[:, 1].min(), X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02),
                     np.arange(y_min, y_max, 0.02))

for i, C in enumerate((0.1, 1, 10)):
    for j, gamma in enumerate((1, 0.1, 0.01)):
        # plt.subplot()
        clf = SVC(C=C, gamma=gamma)
        clf.fit(X, y)
        Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])

        # put the result into a color plot
        Z = Z.reshape(xx.shape)
        plt.contourf(xx, yy, Z, cmap=plt.cm.coolwarm)

        # Plot also the training points
        plt.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.coolwarm)

        plt.xlim(xx.min(), xx.max())
        plt.ylim(yy.min(), yy.max())
        plt.xticks(())
        plt.yticks(())
        plt.xlabel(" gamma=" + str(gamma) + " C=" + str(C))
        plt.show()

   結果如下:

   從我測試的結果來看,劉老師的代碼還是有一點點問題,显示不出九個,所以這裏我打算重新學習一個例子。

   完整代碼請參考我的GitHub(地址:https://github.com/LeBron-Jian/MachineLearningNote)。

10,SVM調參實例3(非線性支持向量機)

  非線性的話,我們一方面可以利用核函數構造出非線性,一方面我們可以自己構造非線性。下面首先學習自己構造非線性。

10.1 自己構造非線性數據

  我們構造非線性數據的代碼如下:

# _*_coding:utf-8_*_
import numpy as np
import matplotlib.pyplot as plt
from sklearn.svm import SVC
from sklearn.datasets import make_moons, make_circles
from sklearn.preprocessing import StandardScaler

X1D = np.linspace(-4, 4, 9).reshape(-1, 1)
# np.c_是按行連接兩個矩陣,就是把兩矩陣左右相加,要求行數相等。
X2D = np.c_[X1D, X1D ** 2]
y = np.array([0, 0, 1, 1, 1, 1, 1, 0, 0])

plt.figure(figsize=(11, 4))

plt.subplot(121)
plt.grid(True, which='both')
plt.axhline(y=0, color='k')
plt.plot(X1D[:, 0][y == 0], np.zeros(4), 'bs')
plt.plot(X1D[:, 0][y == 1], np.zeros(5), 'g*')
plt.gca().get_yaxis().set_ticks([])
plt.xlabel(r'$x_1$', fontsize=20)
plt.axis([-4.5, 4.5, -0.2, 0.2])

plt.subplot(122)
plt.grid(True, which='both')
plt.axhline(y=0, color='k')
plt.axvline(x=0, color='k')
plt.plot(X2D[:, 0][y == 0], X2D[:, 1][y == 0], 'bs')
plt.plot(X2D[:, 0][y == 1], X2D[:, 1][y == 1], 'g*')
plt.xlabel(r'$x_1$', fontsize=20)
plt.ylabel(r'$x_2$', fontsize=20, rotation=0)
plt.gca().get_yaxis().set_ticks([0, 4, 8, 12, 16])
plt.plot([-4.5, 4.5], [6.5, 6.5], 'r--', linewidth=3)
plt.axis([-4.5, 4.5, -1, 17])

plt.subplots_adjust(right=1)
plt.show()

   圖如下:

  從這個圖可以看到,我們利用對數據的變換,可以對數據的維度增加起來,變成非線性。

   假設我們不使用核函數的思想,先對數據做變換,看能不能達到一個比較好的結果,首先我們做一個測試的數據,代碼如下:

# _*_coding:utf-8_*_
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_moons

X, y = make_moons(n_samples=100, noise=0.15, random_state=42)


def plot_dataset(X, y, axes):
    plt.plot(X[:, 0][y == 0], X[:, 1][y == 0], 'bs')
    plt.plot(X[:, 0][y == 1], X[:, 1][y == 1], 'g*')
    plt.axis(axes)
    plt.grid(True, which='both')
    plt.xlabel(r'$x_1$', fontsize=20)
    plt.ylabel(r'$x_2$', fontsize=20, rotation=0)

plot_dataset(X, y, [-1.5, 2.5, -1, 1.5])
plt.show()

   生成的圖如下:

   下面代碼將兩類數據分出來了:

Polynomial_svm_clf = Pipeline((('poly_features', PolynomialFeatures(degree=3)),
                               ('scaler', StandardScaler()),
                               ('svm_clf', LinearSVC(C=10))
                               ))
Polynomial_svm_clf.fit(X, y)

def plot_predictions(clf, axes):
    x0s = np.linspace(axes[0], axes[1], 100)
    x1s = np.linspace(axes[2], axes[3], 100)
    x0, x1 = np.meshgrid(x0s, x1s)
    X = np.c_[x0.ravel(), x1.ravel()]
    y_pred = clf.predict(X).reshape(x0.shape)
    # 下面填充一個等高線, alpha表示透明度
    plt.contourf(x0, x1, y_pred, cmap=plt.cm.brg, alpha=0.2)

plot_predictions(Polynomial_svm_clf, [-1.5, 2.5, -1, 1.5])
plot_dataset(X, y, [-1.5, 2.5, -1, 1.5])
plt.show()

   結果如下:

   從結果來看,我們使用線性支持向量機將兩類數據區分開是沒有問題的。而最重要的是我們如何使用核函數呢?下面繼續學習

10.2 如何對非線性數據進行核函數的變換

   我們首先看svm的官方文檔:

   核函數默認是 rbf,也就是徑向基核函數。下面分別演示核函數。

  我們依舊拿上面的數據,首先取核函數為 多項式核 看看效果(這裏對比的是多項式核的degree,也就是多項式核的維度):

# _*_coding:utf-8_*_
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_moons
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.preprocessing import StandardScaler
from sklearn.svm import LinearSVC, SVC

X, y = make_moons(n_samples=100, noise=0.15, random_state=42)

def plot_dataset(X, y, axes):
    plt.plot(X[:, 0][y == 0], X[:, 1][y == 0], 'bs')
    plt.plot(X[:, 0][y == 1], X[:, 1][y == 1], 'g*')
    plt.axis(axes)
    plt.grid(True, which='both')
    plt.xlabel(r'$x_1$', fontsize=20)
    plt.ylabel(r'$x_2$', fontsize=20, rotation=0)

# 展示圖像
def plot_predictions(clf, axes):
    x0s = np.linspace(axes[0], axes[1], 100)
    x1s = np.linspace(axes[2], axes[3], 100)
    x0, x1 = np.meshgrid(x0s, x1s)
    X = np.c_[x0.ravel(), x1.ravel()]
    y_pred = clf.predict(X).reshape(x0.shape)
    # 下面填充一個等高線, alpha表示透明度
    plt.contourf(x0, x1, y_pred, cmap=plt.cm.brg, alpha=0.2)


Poly_kernel_svm_clf = Pipeline((('scaler', StandardScaler()),
                                ('svm_clf', SVC(kernel='poly', degree=3, coef0=1, C=5))
                                ))
Poly_kernel_svm_clf.fit(X, y)
# 下面做一個對比試驗,看看degree的值的變換
Poly_kernel_svm_clf_plus = Pipeline((('scaler', StandardScaler()),
                                     ('svm_clf', SVC(kernel='poly', degree=10, coef0=1, C=5))
                                     ))
Poly_kernel_svm_clf_plus.fit(X, y)

plt.subplot(121)
plot_predictions(Poly_kernel_svm_clf, [-1.5, 2.5, -1, 1.5])
plot_dataset(X, y, [-1.5, 2.5, -1, 1.5])
plt.title(r'$d=3, r=1, C=5$', fontsize=18)

plt.subplot(122)
plot_predictions(Poly_kernel_svm_clf, [-1.5, 2.5, -1, 1.5])
plot_dataset(X, y, [-1.5, 2.5, -1, 1.5])
plt.title(r'$d=10, r=100, C=5$', fontsize=18)
plt.show()

   結果如下:

   我們是把數據映射到高維空間,然後再拿回來看效果,實際上並沒有去高維空間做運算。。這就是我們想要展示的多項式核函數,下面學習高斯核函數。

  高斯核函數:利用相似度來變換特徵

  我們選擇一份一維數據,並在 x1=-2,  x1=1 處為其添加兩個高斯函數,接下來讓我門將相似度函數定義為 gamma=0.3 的徑向基核函數(RBF):

  例如: x1 = -1:它位於距第一個地標距離為1的地方,距離第二個地標距離為2。因此其新特徵為 x2 = exp(-0.3*1^2)=0.74 ,並且  x3 = exp(-0.3 * 2^2)=0.3。

  圖如下:

   這裏說一下,就是假設 X2和 X3為兩個高斯函數,我們看 x這個點距離兩個地標的距離。離高斯分佈的中心越近,就越發生什麼。。經過計算出來距離兩個地標的距離,我們就可以依此類推,來計算所有一維坐標相對應的二維坐標。(二維坐標就是距離兩個高斯函數的距離)。

  我們這裏用相似度特徵來替換原本的特徵。

  下面我們做一個實驗,我們只看 gamma的變換,高斯函數的開口變化:

X1D = np.linspace(-4, 4, 9).reshape(-1, 1)
X2D = np.c_[X1D, X1D ** 2]

X, y = make_moons(n_samples=100, noise=0.15, random_state=42)


def gaussian_rbf(x, landmark, gamma):
    return np.exp(-gamma * np.linalg.norm(x - landmark, axis=1) ** 2)


gamma = 0.3

# 下面進行訓練,得到一個支持向量機的模型(這裏我們沒有訓練,直接畫出來了)
# 因為測試的數據是我們自己寫的,為了方便,我們自己畫出來,當然你也可以自己做
xls = np.linspace(-4.5, 4.5, 200).reshape(-1, 1)
x2s = gaussian_rbf(xls, -2, gamma)
x3s = gaussian_rbf(xls, 1, gamma)

XK = np.c_[gaussian_rbf(X1D, -2, gamma), gaussian_rbf(X2D, 1, gamma)]
yk = np.array([0, 0, 1, 1, 1, 1, 1, 0, 0])

plt.figure(figsize=(11, 4))

# plt.subplot(121)
plt.grid(True, which='both')
plt.axhline(y=0, color='k')
plt.scatter(x=[-2, 1], y=[0, 0], s=150, alpha=0.5, c='red')
plt.plot(X1D[:, 0][yk == 0], np.zeros(4), 'bs')
plt.plot(X1D[:, 0][yk == 1], np.zeros(5), 'g*')
plt.plot(xls, x2s, 'g--')
plt.plot(xls, x3s, 'b:')
plt.gca().get_yaxis().set_ticks([0, 0.25, 0.5, 0.75, 1])
plt.xlabel(r'$x_1$', fontsize=20)
plt.ylabel(r'Similarity', fontsize=14)

plt.annotate(r'$\mathbf{x}$',
             xy=(X1D[3, 0], 0),
             xytest=(-0.5, 0.20),
             ha='center',
             arrowprops=dict(facecolor='black', shrink=0.1),
             fontsize=18,
             )
plt.text(-2, 0.9, "$x_2$", ha='center', fontsize=20)
plt.text(1, 0.9, "$x_3$", ha='center', fontsize=20)
plt.axis([-4.5, 4.5, -0.1, 1.1])

   結果如下(下面我們分別調試gamma,分為0.3   0.8):

   理論情況下,我們會得到怎麼維特徵呢?可以對每一個實例(樣本數據點)創建一個地標,此時會將mn 的訓練集轉換成 mm 的訓練集(m表示樣本個數,n表示特徵維度個數)。

  SVM中利用核函數的計算技巧,大大降低了計算複雜度

  • 增加gamma 使高斯曲線變窄,因此每個實例的影響範圍都較小,決策邊界最終變得不規則,在個別實例周圍擺動
  • 減少gamma 使高斯曲線變寬,因此實例具有更大的影響範圍,並且決策邊界更加平滑

   下面做一個對比試驗(gamma值(0.1  0.5), C值(0.001, 1000)):

rbf_kernel_svm_clf = Pipeline((('scaler', StandardScaler()),
                               ('svm_clf', SVC(kernel='rbf', gamma=5, C=0.001))
                               ))

gamma1, gamma2 = 0.1, 5
C1, C2 = 0.001, 1000
hyperparams = (gamma1, C1), (gamma1, C2), (gamma2, C1), (gamma2, C2)

svm_clfs = []
for gamma, C in hyperparams:
    rbf_kernel_svm_clf.fit(X, y)
    svm_clfs.append(rbf_kernel_svm_clf)

plt.figure(figsize=(11, 7))

for i, svm_clfs in enumerate(svm_clfs):
    plt.subplot(221 + i)
    plot_predictions(svm_clfs, [-1.5, 2.5, -1, 1.5])
    plot_dataset(X, y, [-1.5, 2.5, -1, 1.5])
    gamma, C = hyperparams[i]
    plt.title(r'$\gamma={}, C={}$'.format(gamma, C), fontsize=16)
plt.show()

   結果如下:

   我們看第一幅圖,邊界比較平穩,沒有過擬合的風險,我們看當 gamma比較大的時候,過擬合的風險卻比較大了。所以說最終我們看的還是高斯函數的開口大還是小,大一點,也就是gamma小,過擬合風險小,反之同理。

  完整代碼請參考我的GitHub(地址:https://github.com/LeBron-Jian/MachineLearningNote)。

完整代碼及其數據,請移步小編的GitHub

  傳送門:請點擊我

  如果點擊有誤:https://github.com/LeBron-Jian/MachineLearningNote

 

 

參考文獻:https://blog.csdn.net/BIT_666/article/details/79979580

https://www.cnblogs.com/tonglin0325/p/6107114.html

https://cloud.tencent.com/developer/article/1146077

https://www.cnblogs.com/xiaoyh/p/11604168.html

https://www.cnblogs.com/pinard/p/6126077.html

https://www.cnblogs.com/pinard/p/6117515.html

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

【其他文章推薦】

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

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

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

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

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

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

Spring AOP學習筆記03:AOP的核心實現之獲取增強器

  上文講了spring是如何開啟AOP的,簡單點說就是將AnnotationAwareAspectJAutoProxyCreator這個類註冊到容器中,因為這個類最終實現了BeanPostProcessor接口,並且在其postProcessAfterInitialization()方法中完成了AOP代理對象的創建,創建時機則是在bean的init方法被執行之後即bean初始化完成之後。postProcessAfterInitialization()方法是重點,本文及下一篇文章都是圍繞着這個方法來的。

  該方法是實現在其父類中的AbstractAutoProxyCreator,我們先來看一下其實現:

public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    if (bean != null) {
            // 根據給定的bean的class和name構件key,格式:beanClassName_beanName
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (!this.earlyProxyReferences.containsKey(cacheKey)) {
                    // 如果它適合被代理,則在這裏面生成代理類
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    // 如果已經處理過則不再處理
    if (beanName != null && this.targetSourcedBeans.containsKey(beanName)) {
        return bean;
    }
    // 無需增強則直接返回
    if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
        return bean;
    }
    // 指定的bean類是否代表一個基礎設施類,基礎設施類不應被代理,或者配置了指定bean也不需要自動代理
    if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }

    // 如果存在Advice則創建代理
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    // 如果獲取到了增強則需要針對增強創建代理
    if (specificInterceptors != DO_NOT_PROXY) {
        this.advisedBeans.put(cacheKey, Boolean.TRUE);
            // 創建代理
        Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
        this.proxyTypes.put(cacheKey, proxy.getClass());
        return proxy;
    }

    this.advisedBeans.put(cacheKey, Boolean.FALSE);
    return bean;
}

  在上面的函數中我們已經可以看到代理創建的雛形,當然,在真正開始之前還需要經過一系列判斷,比如是否已經處理過或者是否是需要跳過的bean,而真正創建代理是從getAdvicesAndAdvisorsForBean開始的。
  創建代理主要包含了兩個步驟:

  • 獲取增強方法或者增強器;
  • 根據獲取的增強進行代理創建;

  這裏雖然看起來只有簡單的兩步,但是每一步中都有着大量複雜的邏輯。本文先來看看獲取增強方法的實現邏輯。

protected Object[] getAdvicesAndAdvisorsForBean(Class beanClass, String beanName, TargetSource targetSource) {
    List advisors = findEligibleAdvisors(beanClass, beanName);
    if (advisors.isEmpty()) {
        return DO_NOT_PROXY;
    }
    return advisors.toArray();
}

protected List<Advisor> findEligibleAdvisors(Class beanClass, String beanName) {
    List<Advisor> candidateAdvisors = findCandidateAdvisors();
    List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
    extendAdvisors(eligibleAdvisors);
    if (!eligibleAdvisors.isEmpty()) {
        eligibleAdvisors = sortAdvisors(eligibleAdvisors);
    }
    return eligibleAdvisors;
}

  對於某個bean的增強方法的獲取也是包含兩個步驟的,獲取所有的增強以及尋找所有增強中適用於bean的增強並應用,而findCandidateAdvisors()與findAdvisorsThatCanApply()便是做了這兩件事情。這裏如果無法找到對應的增強器則直接返回DO_NOT_PROXY,也就是null。

1. 獲取增強器

  這裏我們分析的是註解方式的AOP,對於findCandidateAdvisors的實現其實是由AnnotationAwareAspectJAutoProxyCreator類來完成的,繼續跟蹤:

protected List<Advisor> findCandidateAdvisors() {
    // 使用註解方式配置AOP的時候並不會丟棄對XML配置的支持,在這裡會調用父類方法去加載配置文件中的AOP聲明
    List<Advisor> advisors = super.findCandidateAdvisors();
    // Build Advisors for all AspectJ aspects in the bean factory.
    advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
    return advisors;
}

  AnnotationAwareAspectJAutoProxyCreator間接繼承了AbstractAdvisorAutoProxyCreator,在實現獲取增強的方法中除了保留父類的獲取配置文件中定義的增強外,同時增加了獲取Bean的註解增強的功能,這部分實現是由this.aspectJAdvisorsBuilder.buildAspectJAdvisors()來完成的。

  這裏其實可以自己先嘗試想象一下解析思路,看看自己的實現與Spring是否有差別?我們可以先在自己頭腦中嘗試實現一下獲取增強這個功能點,看看是否有思路。實際上,Spring在實現的時候主要分成了四步:

  • 獲取所有beanName,這一步驟中所有在beanFactory中註冊的Bean都會被提取出來;
  • 遍歷所有beanName,並找出聲明AspectJ註解的類,進行進一步的處理;
  • 對標記為AspectJ註解的類進行增強器的提取;
  • 將提取結果加入緩存;

  我們來看看Spring如何實現對所有的類進行分析並提取Advisor:

public List<Advisor> buildAspectJAdvisors() {
    List<String> aspectNames = null;

    synchronized (this) {
        aspectNames = this.aspectBeanNames;
        if (aspectNames == null) {
            List<Advisor> advisors = new LinkedList<Advisor>();
            aspectNames = new LinkedList<String>();
            // 獲取所有的beanName
            String[] beanNames =
                    BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Object.class, true, false);
            // 遍歷所有的beanName找出對應的增強方法
            for (String beanName : beanNames) {
                // 不合法的bean則略過,由子類定義規則,默認返回true
                if (!isEligibleBean(beanName)) {
                    continue;
                }
                // 獲取對應的bean的類型
                Class beanType = this.beanFactory.getType(beanName);
                if (beanType == null) {
                    continue;
                }
                // 如果該bean上存在Aspect註解
                if (this.advisorFactory.isAspect(beanType)) {
                    aspectNames.add(beanName);
                    AspectMetadata amd = new AspectMetadata(beanType, beanName);
                    if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
                        MetadataAwareAspectInstanceFactory factory =
                                new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
                        // 解析增強方法
                        List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
                        if (this.beanFactory.isSingleton(beanName)) {
                            this.advisorsCache.put(beanName, classAdvisors);
                        }
                        else {
                            this.aspectFactoryCache.put(beanName, factory);
                        }
                        advisors.addAll(classAdvisors);
                    }
                    else {
                        // Per target or per this.
                        if (this.beanFactory.isSingleton(beanName)) {
                            throw new IllegalArgumentException("Bean with name '" + beanName +
                                    "' is a singleton, but aspect instantiation model is not singleton");
                        }
                        MetadataAwareAspectInstanceFactory factory =
                                new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
                        this.aspectFactoryCache.put(beanName, factory);
                        advisors.addAll(this.advisorFactory.getAdvisors(factory));
                    }
                }
            }
            this.aspectBeanNames = aspectNames;
            return advisors;
        }
    }

    if (aspectNames.isEmpty()) {
        return Collections.EMPTY_LIST;
    }
    // 記錄緩存
    List<Advisor> advisors = new LinkedList<Advisor>();
    for (String aspectName : aspectNames) {
        List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);
        if (cachedAdvisors != null) {
            advisors.addAll(cachedAdvisors);
        }
        else {
            MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName);
            advisors.addAll(this.advisorFactory.getAdvisors(factory));
        }
    }
    return advisors;
}

  到這裏已經完成了Advisor的提取,在上面步驟中最為重要也最為複雜的就是增強器的獲取,這一功能是委託給getAdvisors方法來實現(this.advisorFactory.getAdvisors(factory))。 

public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory maaif) {
    // 獲取標記為AspectJ的類及其名字,並驗證
    final Class<?> aspectClass = maaif.getAspectMetadata().getAspectClass();
    final String aspectName = maaif.getAspectMetadata().getAspectName();
    validate(aspectClass);

    // We need to wrap the MetadataAwareAspectInstanceFactory with a decorator
    // so that it will only instantiate once.
    final MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =
            new LazySingletonAspectInstanceFactoryDecorator(maaif);

    final List<Advisor> advisors = new LinkedList<Advisor>();
    for (Method method : getAdvisorMethods(aspectClass)) {
        // 獲取增強器
        Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName);
        if (advisor != null) {
            advisors.add(advisor);
        }
    }

    // If it's a per target aspect, emit the dummy instantiating aspect.
    if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
        Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory);
        advisors.add(0, instantiationAdvisor);
    }

    // Find introduction fields.
    for (Field field : aspectClass.getDeclaredFields()) {
        Advisor advisor = getDeclareParentsAdvisor(field);
        if (advisor != null) {
            advisors.add(advisor);
        }
    }

    return advisors;
}

  上面函數中首先完成了對增強器的獲取,包括獲取註解以及根據註解生成增強的步驟,然後考慮到在配置中可能會將增強配置成延遲初始化,那麼需要在首位加入同步實例化增強器以保證增強使用之前的實例化,最後是對DeclareParents註解的獲取,這裏着重分析對增強器的獲取。
  對普通增強器的獲取邏輯是實現在getAdvisor方法中的,實現步驟包括對切點的註解的獲取以及根據註解信息生成增強。

public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aif,
        int declarationOrderInAspect, String aspectName) {

    validate(aif.getAspectMetadata().getAspectClass());
    // 切點信息的獲取
    AspectJExpressionPointcut ajexp =
            getPointcut(candidateAdviceMethod, aif.getAspectMetadata().getAspectClass());
    if (ajexp == null) {
        return null;
    }
    // 根據切點信息生成增強器
    return new InstantiationModelAwarePointcutAdvisorImpl(
            this, ajexp, aif, candidateAdviceMethod, declarationOrderInAspect, aspectName);
}

1.1 切點信息的獲取

  所謂獲取切點信息就是指定註解的表達式信息的獲取,如@Before(“test()”)。

private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {
    // 獲取方法上的註解
    AspectJAnnotation<?> aspectJAnnotation =
            AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
    if (aspectJAnnotation == null) {
        return null;
    }
    // 使用AspectJExpressionPointcut實例封裝獲取的信息
    AspectJExpressionPointcut ajexp =
            new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class[0]);
    // 提取得到的註解中的表達式,如:
    // @Pointcut("execution(* *.*test*(..))")中的execution(* *.*test*(..))")
    ajexp.setExpression(aspectJAnnotation.getPointcutExpression());
    return ajexp;
}

protected static AspectJAnnotation findAspectJAnnotationOnMethod(Method method) {
    // 設置敏感的註解類
    Class<? extends Annotation>[] classesToLookFor = new Class[] {
            Before.class, Around.class, After.class, AfterReturning.class, AfterThrowing.class, Pointcut.class};
    for (Class<? extends Annotation> c : classesToLookFor) {
        AspectJAnnotation foundAnnotation = findAnnotation(method, c);
        if (foundAnnotation != null) {
            return foundAnnotation;
        }
    }
    return null;
}

// 獲取指定方法上的註解並使用AspectJAnnotation封裝
private static <A extends Annotation> AspectJAnnotation<A> findAnnotation(Method method, Class<A> toLookFor) {
    A result = AnnotationUtils.findAnnotation(method, toLookFor);
    if (result != null) {
        return new AspectJAnnotation<A>(result);
    }
    else {
        return null;
    }
}

1.2 根據切點信息生成增強

  所有的增強都是由Advisor的實現類InstantiationModelAwarePointcutAdvisorImpl統一封裝的。

public InstantiationModelAwarePointcutAdvisorImpl(AspectJAdvisorFactory af, AspectJExpressionPointcut ajexp,
        MetadataAwareAspectInstanceFactory aif, Method method, int declarationOrderInAspect, String aspectName) {

    this.declaredPointcut = ajexp;
    this.method = method;
    this.atAspectJAdvisorFactory = af;
    this.aspectInstanceFactory = aif;
    this.declarationOrder = declarationOrderInAspect;
    this.aspectName = aspectName;

    if (aif.getAspectMetadata().isLazilyInstantiated()) {
        // Static part of the pointcut is a lazy type.
        Pointcut preInstantiationPointcut =
                Pointcuts.union(aif.getAspectMetadata().getPerClausePointcut(), this.declaredPointcut);

        // Make it dynamic: must mutate from pre-instantiation to post-instantiation state.
        // If it's not a dynamic pointcut, it may be optimized out
        // by the Spring AOP infrastructure after the first evaluation.
        this.pointcut = new PerTargetInstantiationModelPointcut(this.declaredPointcut, preInstantiationPointcut, aif);
        this.lazy = true;
    }
    else {
        // A singleton aspect.
        this.instantiatedAdvice = instantiateAdvice(this.declaredPointcut);
        this.pointcut = declaredPointcut;
        this.lazy = false;
    }
}

  在封裝過程中只是簡單地將信息封裝在類的實例中,所有的信息單純地賦值,在實例初始化的過程中還完成了對於增強器的初始化。因為不同的增強所體現的邏輯是不同的,比如@Before(“test()”)與@After(“test()”)標籤的區別就是增強器增強的位置不同,所以就需要不同的增強器來完成不同的邏輯,而根據註解中的信息初始化對應的增強器就是在instantiateAdvice函數中實現的。

private Advice instantiateAdvice(AspectJExpressionPointcut pcut) {
    return this.atAspectJAdvisorFactory.getAdvice(
            this.method, pcut, this.aspectInstanceFactory, this.declarationOrder, this.aspectName);
}

    public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut ajexp,
        MetadataAwareAspectInstanceFactory aif, int declarationOrderInAspect, String aspectName) {

    Class<?> candidateAspectClass = aif.getAspectMetadata().getAspectClass();
    validate(candidateAspectClass);

    AspectJAnnotation<?> aspectJAnnotation =
            AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
    if (aspectJAnnotation == null) {
        return null;
    }

    // If we get here, we know we have an AspectJ method.
    // Check that it's an AspectJ-annotated class
    if (!isAspect(candidateAspectClass)) {
        throw new AopConfigException("Advice must be declared inside an aspect type: " +
                "Offending method '" + candidateAdviceMethod + "' in class [" +
                candidateAspectClass.getName() + "]");
    }

    if (logger.isDebugEnabled()) {
        logger.debug("Found AspectJ method: " + candidateAdviceMethod);
    }

    AbstractAspectJAdvice springAdvice;
    // 根據不同的註解類型封裝不同的增強器
    switch (aspectJAnnotation.getAnnotationType()) {
        case AtBefore:
            springAdvice = new AspectJMethodBeforeAdvice(candidateAdviceMethod, ajexp, aif);
            break;
        case AtAfter:
            springAdvice = new AspectJAfterAdvice(candidateAdviceMethod, ajexp, aif);
            break;
        case AtAfterReturning:
            springAdvice = new AspectJAfterReturningAdvice(candidateAdviceMethod, ajexp, aif);
            AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation();
            if (StringUtils.hasText(afterReturningAnnotation.returning())) {
                springAdvice.setReturningName(afterReturningAnnotation.returning());
            }
            break;
        case AtAfterThrowing:
            springAdvice = new AspectJAfterThrowingAdvice(candidateAdviceMethod, ajexp, aif);
            AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation();
            if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {
                springAdvice.setThrowingName(afterThrowingAnnotation.throwing());
            }
            break;
        case AtAround:
            springAdvice = new AspectJAroundAdvice(candidateAdviceMethod, ajexp, aif);
            break;
        case AtPointcut:
            if (logger.isDebugEnabled()) {
                logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'");
            }
            return null;
        default:
            throw new UnsupportedOperationException(
                    "Unsupported advice type on method " + candidateAdviceMethod);
    }

    // Now to configure the advice...
    springAdvice.setAspectName(aspectName);
    springAdvice.setDeclarationOrder(declarationOrderInAspect);
    String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod);
    if (argNames != null) {
        springAdvice.setArgumentNamesFromStringArray(argNames);
    }
    springAdvice.calculateArgumentBindings();
    return springAdvice;
}

  從上面可以看到,Spring會根據不同的註解生成不同的增強器,例如AtBefore會對應AspectJMethodBeforeAdvice,在AspectJMethodBeforeAdvice中完成了增強方法的邏輯。本文嘗試分析幾個常用的增強器實現。

AspectJMethodBeforeAdvice

  這是前置增強器,在Spring中,它會封裝到MethodBeforeAdviceInterceptor類的內部,這個是一個攔截器,會被放在攔截器鏈中,在創建代理bean時會用到:

public class MethodBeforeAdviceInterceptor implements MethodInterceptor, Serializable {

    private MethodBeforeAdvice advice;

    /**
     * Create a new MethodBeforeAdviceInterceptor for the given advice.
     * @param advice the MethodBeforeAdvice to wrap
     */
    public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
        Assert.notNull(advice, "Advice must not be null");
        this.advice = advice;
    }

    public Object invoke(MethodInvocation mi) throws Throwable {
        this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis() );
        return mi.proceed();
    }
}

  其中的屬性MethodBeforeAdvicce就代表着前置增強的AspectJMethodBeforeAdvice,跟蹤其before()方法:

public void before(Method method, Object[] args, Object target) throws Throwable {
    invokeAdviceMethod(getJoinPointMatch(), null, null);
}

protected Object invokeAdviceMethod(JoinPointMatch jpMatch, Object returnValue, Throwable ex) throws Throwable {
    return invokeAdviceMethodWithGivenArgs(argBinding(getJoinPoint(), jpMatch, returnValue, ex));
}

protected Object invokeAdviceMethodWithGivenArgs(Object[] args) throws Throwable {
    Object[] actualArgs = args;
    if (this.aspectJAdviceMethod.getParameterTypes().length == 0) {
        actualArgs = null;
    }
    try {
        ReflectionUtils.makeAccessible(this.aspectJAdviceMethod);
        // 激活增強方法
        return this.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), actualArgs);
    }
    catch (IllegalArgumentException ex) {
        throw new AopInvocationException("Mismatch on arguments to advice method [" +
                this.aspectJAdviceMethod + "]; pointcut expression [" +
                this.pointcut.getPointcutExpression() + "]", ex);
    }
    catch (InvocationTargetException ex) {
        throw ex.getTargetException();
    }
}

   invokeAdviceMethodWithGivenArgs方法中的aspectJAdviceMethod正是對應於前置增強的方法,在這裏實現了調用。

AspectJAfterAdvice

  後置增強與前置增強有稍許不一致的地方。回顧上面講過的前置增強,大致的結構是在攔截器鏈中放置MethodBeforeAdviceInterceptor,並在MethodBeforeAdviceInterceptor中又放置了AspectJMethodBeforeAdvice,在調用inovke時首先串聯調用。但是在後置增強的時候卻是不一樣的,沒有提供如MethodBeforeAdviceInterceptor的中間類,而是直接實現MethodInterceptor接口,並在攔截器鏈中使用了中間的AspectAfterAdvice。

public class AspectJAfterAdvice extends AbstractAspectJAdvice implements MethodInterceptor, AfterAdvice {

    public AspectJAfterAdvice(Method aspectJBeforeAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) {
        super(aspectJBeforeAdviceMethod, pointcut, aif);
    }

    public Object invoke(MethodInvocation mi) throws Throwable {
        try {
            return mi.proceed();
        }
        finally {
            // 激活增強方法
            invokeAdviceMethod(getJoinPointMatch(), null, null);
        }
    }
}

2. 尋找匹配的增強器

  前面已經把所有增強器的獲取分析完了,但是對於所有增強來說,並不一定都適用於當前的Bean,還要挑選出適合的增強器,也就是滿足我們配置的通配符的增強器。這部分的具體實現在findAdvisorsThatCanApply中。

protected List<Advisor> findAdvisorsThatCanApply(
        List<Advisor> candidateAdvisors, Class beanClass, String beanName) {

    ProxyCreationContext.setCurrentProxiedBeanName(beanName);
    try {
        // 過濾已經得到的advisors
        return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
    }
    finally {
        ProxyCreationContext.setCurrentProxiedBeanName(null);
    }
}

public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
    if (candidateAdvisors.isEmpty()) {
        return candidateAdvisors;
    }
    List<Advisor> eligibleAdvisors = new LinkedList<Advisor>();
    for (Advisor candidate : candidateAdvisors) {
        if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
            eligibleAdvisors.add(candidate);
        }
    }
    boolean hasIntroductions = !eligibleAdvisors.isEmpty();
    for (Advisor candidate : candidateAdvisors) {
        if (candidate instanceof IntroductionAdvisor) {
            // already processed
            continue;
        }
        if (canApply(candidate, clazz, hasIntroductions)) {
            eligibleAdvisors.add(candidate);
        }
    }
    return eligibleAdvisors;
}

  findAdvisorsThatCanApply函數的主要功能是尋找所有增強器中適用於當前class的增強器,而對於真正的匹配其實是在canApply中實現的。

public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {
    if (advisor instanceof IntroductionAdvisor) {
        return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
    }
    else if (advisor instanceof PointcutAdvisor) {
        PointcutAdvisor pca = (PointcutAdvisor) advisor;
        return canApply(pca.getPointcut(), targetClass, hasIntroductions);
    }
    else {
        // It doesn't have a pointcut so we assume it applies.
        return true;
    }
}

public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
    Assert.notNull(pc, "Pointcut must not be null");
    if (!pc.getClassFilter().matches(targetClass)) {
        return false;
    }

    MethodMatcher methodMatcher = pc.getMethodMatcher();
    IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
    if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
        introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
    }

    Set<Class> classes = new LinkedHashSet<Class>(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
    classes.add(targetClass);
    for (Class<?> clazz : classes) {
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            if ((introductionAwareMethodMatcher != null &&
                    introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions)) ||
                    methodMatcher.matches(method, targetClass)) {
                return true;
            }
        }
    }

    return false;
}

  這裏的邏輯也不是很複雜,無非就是根據advisor中封裝的classFilter來判斷是否match對應的類。

 3. 總結

  本文主要學習了Spring AOP核心實現原理中的對增強方法的獲取,主要包含兩個步驟:

  • 獲取所有增強;
  • 尋找所有增強中適用於bean的增強;

  下文會聚焦在AOP核心實現原理的另一部分–代理的創建。

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

【其他文章推薦】

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

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

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

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

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

C++ Primer Plus(一)

完整閱讀C++ Primer Plus 

  系統重新學習C++語言部分,記錄重要但易被忽略的,關鍵但易被遺忘的。

 

預備

  1、C++相對於C增加了最關鍵的兩項,面向對象和范型編程。

 

處理數據

  2、對於變量明,C++沒有長度限制;同時,以兩個下劃線或一個下劃線和大寫字母開頭的名稱被保留給實現(編譯器及其使用的資源)使用;以一個下劃線開頭的名稱被保留給實現,用作全局標識符。

  3、C++11提供一種大括號初始化器,可以用它初始化任何類型。

1 int ham = {24};
2 int ems{7};
3 int roc = {}; // 為0
4 int rhs{};

  4、對於整型字面值,如果第一位為1~9則為十進制;如果第一位為0,第二位為1~7,則是八進制;如果前兩位為0x或0X,則為十六進制。如果希望使用cout輸出八進制或十六進制格式的整數,可以使用cout的控制符(這三個控制符都被包含在std命名空間)。

1 cout << dec << a; // 10進制,默認的
2 cout << oct << b; // 8進制
3 cout << hex << c; // 16進制

  5、cout.put()、cin.get()的用法,可參照C語言中get()與put()的用法。

  6、C++有一種表示特殊字符的機制,他獨立於鍵盤,被稱作通用字符名。通用字符名以\u或\U開頭,前者後面是8個十六進制位,後者後面則是16個十六進制位。這些位表示的是ISO10646碼點。

  7、對於寬字符類型wcha_t,cin和cout無法很好的處理,此時應該使用wcinwcout

  8、C++11新增了char16_t和char32_t類型,C++11使用前綴u表示前者,U表示後者;並與形式為\u00F6和\U0000222B的通用字符名匹配。

1 u'C' u“be good”   U'R' U”dirty rat”

  

複合類型

  9、cin使用空白(空格、製表符、換行符)來確定字符串結束的位置,而cin.getline()可以依據換行符來讀取整行,並且可以制定最多讀取字符的數量。

  10、可以使用沒有名稱的結構類型,方法是省略名稱,同時定義一個結構類型和一個這種類型的變量,不常用,可用作臨時變量。

  11、C++允許對一個整數強制類型轉換為一個枚舉值,並參与賦值操作;同時可以有多個值相同的枚舉值,目前枚舉值也可以使用long,long long類型的值。對於較小的值編譯器會使用一個字節甚至更少的的字節,對於包含long類型的枚舉,會使用4個字節。

  12、在C中允許給指針直接賦字面值,但C++不允許,必須進行類型轉換。

 

循環和關係表達式

  13、前綴遞增,前綴遞減,解除引用運算符的優先級相同,以從右往左的方式進行結合;後綴遞增,後綴遞減的優先級相同,但比前綴運算符的優先級高,以從左往右的方式結合。

  14、 cin.get(ch)和cin.get()的區別。

屬性 cin.get(ch) cin.get()
傳遞輸入字符的方式 賦值給參數ch 將函數返回值賦給ch
用於字符輸入時函數的返回值  istream對象(執行bool轉換後為true)  int類型的字符編碼
到達EOF時函數的返回值   istream對象(執行bool轉換後為false) EOF 

 

函數——C++編程模塊

  15、如果數據類型本身並不是指針,則可以將const數據或者非const數據的地址賦給指向const的指針,但只能將非const數據的地址賦給非const指針

 

函數探幽

  16、函數重載后,在調用函數時,如果沒有完全相同的參數類型,編譯器會做強制類型轉換進行匹配,但如果有多個可轉換的類型(也就是說有多個重載過的函數),C++將拒絕這種函數調用。

  17、函數重載的關鍵是函數的參數列表,也成為特徵標,以下兩個聲明互斥:

1 long gronk(int n, float m);
2 double gronk(int n, float m);

  C++不允許這種方式的重載,返回類型可以不同,但特徵標必須也不同。

  18、對於重載了引用參數的函數,C++將選擇調用最匹配的版本,這使得能夠根據參數是左值引用,const類型的左值引用還是右值引用來定製函數的行為。

  19、 函數模板並非函數定義,在使用模板時,編譯器會針對特定的類型生成函數實例,這種實例化方式被稱為隱式實例化

  20、C++允許顯式實例化,也就是說可以針對特定類型使用模板生成特定的實例。

1 template void Swap<int>(int, int); // 用<>指定類型,在聲明前加template

  它的語義為“使用Swap()模板生成int類型的函數定義”。

  與顯式實例化不同的是,顯式具體化使用下面兩個等價的聲明之一:

1 template<> void Swap<int>(int, int);
2 template<> void Swap(int, int);

  它們的語義是,“不要使用Swap模板來生產函數定義,而應使用專門為int類型显示地定義的函數定義”。

  21、還可以在程序中創建顯式實例化:

1 template <class T>
2 T Add(T a, T b){ return a + b; }
3 int m = 6;
4 double n = 9.8;
5 cout << Add<double>(m, n) << endl;

  由於這裏显示實例化中的特定類型為double,所以變量m會被強制類型轉換成double類型。

  22、對於函數重載函數模板函數模板重載,C++將選擇哪個版本?

  請看這裏———>   C++ 函數重載,函數模板和函數模板重載,選擇哪一個?

  23、C++11,在函數模板中,當無法得知一個變量的值時,可以使用decltype關鍵字來決定返回值類型:

1 template<class T1, class T2>
2 void ft(T1 x, T2 y)
3 {
4     decltype(x+y) xpy = x + y; // 此時xpy的類型就是x+y后的類型
5 }

  24、decltype關鍵字本質上更複雜一些,編譯器必須遍歷一個核對錶去確定類型,現在有如下聲明:

1 decltype(expression) var;

  第一,expression是一個沒有用括號括起來的標識符,則var的類型與該標識符相同,包括const等限定符。

  第二,如果expression是一個函數調用,則於與函數的返回值相同,這裏並不執行函數,只是查看返回值類型。

  第三,如果expression是一個左值,並且expression是被括號括起的,var會是引用類型,否則第一步就會處理。

  第四,到了這裏,var的類型只能與expression相同。

  25、C++11,在函數模板中,當無法得知返回值類型時,一般不可以直接使用關鍵字decltype來得到返回值類型,因為此時往往decltype後面表達式中的變量還不在作用域內,此時,需要使用後置返回類型

1 template<class T1, class T2>
2 auto gt(T1 x, T2 y) -> decltype(x+y) // 此時x,y已在作用域內
3 {
4     return x + y;
5 }

  auto表示是一個佔位符,表示後置返回類型提供的類型。

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

【其他文章推薦】

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

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

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

※超省錢租車方案

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

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

一篇文章搞懂filebeat(ELK)

本文使用的filebeat是7.7.0的版本
本文從如下幾個方面說明:

  • filebeat是什麼,可以用來幹嘛
  • filebeat的原理是怎樣的,怎麼構成的
  • filebeat應該怎麼玩

一、filebeat是什麼

1.1、filebeat和beats的關係

  首先filebeat是Beats中的一員。
  Beats在是一個輕量級日誌採集器,其實Beats家族有6個成員,早期的ELK架構中使用Logstash收集、解析日誌,但是Logstash對內存、cpu、io等資源消耗比較高。相比Logstash,Beats所佔系統的CPU和內存幾乎可以忽略不計。
目前Beats包含六種工具:

  • Packetbeat:網絡數據(收集網絡流量數據)
  • Metricbeat:指標(收集系統、進程和文件系統級別的CPU和內存使用情況等數據)
  • Filebeat:日誌文件(收集文件數據)
  • Winlogbeat:windows事件日誌(收集Windows事件日誌數據)
  • Auditbeat:審計數據(收集審計日誌)
  • Heartbeat:運行時間監控(收集系統運行時的數據)

1.2、filebeat是什麼

  Filebeat是用於轉發和集中日誌數據的輕量級傳送工具。Filebeat監視您指定的日誌文件或位置,收集日誌事件,並將它們轉發到Elasticsearch或 Logstash進行索引。

  Filebeat的工作方式如下:啟動Filebeat時,它將啟動一個或多個輸入,這些輸入將在為日誌數據指定的位置中查找。對於Filebeat所找到的每個日誌,Filebeat都會啟動收集器。每個收集器都讀取單個日誌以獲取新內容,並將新日誌數據發送到libbeat,libbeat將聚集事件,並將聚集的數據發送到為Filebeat配置的輸出。

       工作的流程圖如下:

 

1.3、filebeat和logstash的關係

  因為logstash是jvm跑的,資源消耗比較大,所以後來作者又用golang寫了一個功能較少但是資源消耗也小的輕量級的logstash-forwarder。不過作者只是一個人,加入http://elastic.co公司以後,因為es公司本身還收購了另一個開源項目packetbeat,而這個項目專門就是用golang的,有整個團隊,所以es公司乾脆把logstash-forwarder的開發工作也合併到同一個golang團隊來搞,於是新的項目就叫filebeat了。

 

二、filebeat原理是什麼

2.1、filebeat的構成

  filebeat結構:由兩個組件構成,分別是inputs(輸入)和harvesters(收集器),這些組件一起工作來跟蹤文件並將事件數據發送到您指定的輸出,harvester負責讀取單個文件的內容。harvester逐行讀取每個文件,並將內容發送到輸出。為每個文件啟動一個harvester。harvester負責打開和關閉文件,這意味着文件描述符在harvester運行時保持打開狀態。如果在收集文件時刪除或重命名文件,Filebeat將繼續讀取該文件。這樣做的副作用是,磁盤上的空間一直保留到harvester關閉。默認情況下,Filebeat保持文件打開,直到達到close_inactive

關閉harvester可以會產生的結果:

  • 文件處理程序關閉,如果harvester仍在讀取文件時被刪除,則釋放底層資源。
  • 只有在scan_frequency結束之後,才會再次啟動文件的收集。
  • 如果該文件在harvester關閉時被移動或刪除,該文件的收集將不會繼續

  一個input負責管理harvesters和尋找所有來源讀取。如果input類型是log,則input將查找驅動器上與定義的路徑匹配的所有文件,併為每個文件啟動一個harvester。每個input在它自己的Go進程中運行,Filebeat當前支持多種輸入類型。每個輸入類型可以定義多次。日誌輸入檢查每個文件,以查看是否需要啟動harvester、是否已經在運行harvester或是否可以忽略該文件

2.2、filebeat如何保存文件的狀態

  Filebeat保留每個文件的狀態,並經常將狀態刷新到磁盤中的註冊表文件中。該狀態用於記住harvester讀取的最後一個偏移量,並確保發送所有日誌行。如果無法訪問輸出(如Elasticsearch或Logstash),Filebeat將跟蹤最後發送的行,並在輸出再次可用時繼續讀取文件。當Filebeat運行時,每個輸入的狀態信息也保存在內存中。當Filebeat重新啟動時,來自註冊表文件的數據用於重建狀態,Filebeat在最後一個已知位置繼續每個harvester。對於每個輸入,Filebeat都會保留它找到的每個文件的狀態。由於文件可以重命名或移動,文件名和路徑不足以標識文件。對於每個文件,Filebeat存儲唯一的標識符,以檢測文件是否以前被捕獲。

2.3、filebeat何如保證至少一次數據消費

  Filebeat保證事件將至少傳遞到配置的輸出一次,並且不會丟失數據。是因為它將每個事件的傳遞狀態存儲在註冊表文件中。在已定義的輸出被阻止且未確認所有事件的情況下,Filebeat將繼續嘗試發送事件,直到輸出確認已接收到事件為止。如果Filebeat在發送事件的過程中關閉,它不會等待輸出確認所有事件后再關閉。當Filebeat重新啟動時,將再次將Filebeat關閉前未確認的所有事件發送到輸出。這樣可以確保每個事件至少發送一次,但最終可能會有重複的事件發送到輸出。通過設置shutdown_timeout選項,可以將Filebeat配置為在關機前等待特定時間

三、filebeat怎麼玩

3.1、壓縮包方式安裝

本文採用壓縮包的方式安裝,linux版本,filebeat-7.7.0-linux-x86_64.tar.gz

curl-L-Ohttps://artifacts.elastic.co/downloads/beats/filebeat/filebeat-7.7.0-linux-x86_64.tar.gz
tar -xzvf filebeat-7.7.0-linux-x86_64.tar.gz

配置示例文件:filebeat.reference.yml(包含所有未過時的配置項)
配置文件:filebeat.yml

3.2、基本命令

詳情見官網:https://www.elastic.co/guide/en/beats/filebeat/current/command-line-options.html

export   #導出
run      #執行(默認執行)
test     #測試配置
keystore #秘鑰存儲
modules  #模塊配置管理
setup    #設置初始環境

例如:./filebeat test config  #用來測試配置文件是否正確

3.3、輸入輸出

支持的輸入組件:

Multilinemessages,Azureeventhub,CloudFoundry,Container,Docker,GooglePub/Sub,HTTPJSON,Kafka,Log,MQTT,NetFlow,Office365ManagementActivityAPI,Redis,s3,Stdin,Syslog,TCP,UDP(最常用的額就是log)

支持的輸出組件:

Elasticsearch,Logstash,Kafka,Redis,File,Console,ElasticCloud,Changetheoutputcodec(最常用的就是Elasticsearch,Logstash)

3.4、keystore的使用

keystore主要是防止敏感信息被泄露,比如密碼等,像ES的密碼,這裏可以生成一個key為ES_PWD,值為es的password的一個對應關係,在使用es的密碼的時候就可以使用${ES_PWD}使用

創建一個存儲密碼的keystore:filebeat keystore create
然後往其中添加鍵值對,例如:filebeatk eystore add ES_PWD
使用覆蓋原來鍵的值:filebeat key store add ES_PWD–force
刪除鍵值對:filebeat key store remove ES_PWD
查看已有的鍵值對:filebeat key store list

例如:後期就可以通過${ES_PWD}使用其值,例如:

output.elasticsearch.password:”${ES_PWD}”

3.5、filebeat.yml配置(log輸入類型為例)

詳情見官網:https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-input-log.html

type: log #input類型為log
enable: true #表示是該log類型配置生效
paths:     #指定要監控的日誌,目前按照Go語言的glob函數處理。沒有對配置目錄做遞歸處理,比如配置的如果是:
- /var/log/* /*.log  #則只會去/var/log目錄的所有子目錄中尋找以".log"結尾的文件,而不會尋找/var/log目錄下以".log"結尾的文件。
recursive_glob.enabled: #啟用全局遞歸模式,例如/foo/**包括/foo, /foo/*, /foo/*/* encoding:#指定被監控的文件的編碼類型,使用plain和utf-8都是可以處理中文日誌的
exclude_lines: ['^DBG'] #不包含匹配正則的行
include_lines: ['^ERR', '^WARN']  #包含匹配正則的行
harvester_buffer_size: 16384 #每個harvester在獲取文件時使用的緩衝區的字節大小
max_bytes: 10485760 #單個日誌消息可以擁有的最大字節數。max_bytes之後的所有字節都被丟棄而不發送。默認值為10MB (10485760)
exclude_files: ['\.gz$']  #用於匹配希望Filebeat忽略的文件的正則表達式列表
ingore_older: 0 #默認為0,表示禁用,可以配置2h,2m等,注意ignore_older必須大於close_inactive的值.表示忽略超過設置值未更新的
文件或者文件從來沒有被harvester收集
close_* #close_ *配置選項用於在特定標準或時間之後關閉harvester。 關閉harvester意味着關閉文件處理程序。 如果在harvester關閉
後文件被更新,則在scan_frequency過後,文件將被重新拾取。 但是,如果在harvester關閉時移動或刪除文件,Filebeat將無法再次接收文件
,並且harvester未讀取的任何數據都將丟失。
close_inactive  #啟動選項時,如果在制定時間沒有被讀取,將關閉文件句柄
讀取的最後一條日誌定義為下一次讀取的起始點,而不是基於文件的修改時間
如果關閉的文件發生變化,一個新的harverster將在scan_frequency運行后被啟動
建議至少設置一個大於讀取日誌頻率的值,配置多個prospector來實現針對不同更新速度的日誌文件
使用內部時間戳機制,來反映記錄日誌的讀取,每次讀取到最後一行日誌時開始倒計時使用2h 5m 來表示
close_rename #當選項啟動,如果文件被重命名和移動,filebeat關閉文件的處理讀取
close_removed #當選項啟動,文件被刪除時,filebeat關閉文件的處理讀取這個選項啟動后,必須啟動clean_removed
close_eof #適合只寫一次日誌的文件,然後filebeat關閉文件的處理讀取
close_timeout #當選項啟動時,filebeat會給每個harvester設置預定義時間,不管這個文件是否被讀取,達到設定時間后,將被關閉
close_timeout 不能等於ignore_older,會導致文件更新時,不會被讀取如果output一直沒有輸出日誌事件,這個timeout是不會被啟動的,
至少要要有一個事件發送,然後haverter將被關閉
設置0 表示不啟動
clean_inactived #從註冊表文件中刪除先前收穫的文件的狀態
設置必須大於ignore_older+scan_frequency,以確保在文件仍在收集時沒有刪除任何狀態
配置選項有助於減小註冊表文件的大小,特別是如果每天都生成大量的新文件
此配置選項也可用於防止在Linux上重用inode的Filebeat問題
clean_removed #啟動選項后,如果文件在磁盤上找不到,將從註冊表中清除filebeat
如果關閉close removed 必須關閉clean removed
scan_frequency #prospector檢查指定用於收穫的路徑中的新文件的頻率,默認10s
tail_files:#如果設置為true,Filebeat從文件尾開始監控文件新增內容,把新增的每一行文件作為一個事件依次發送,
而不是從文件開始處重新發送所有內容。
symlinks:#符號鏈接選項允許Filebeat除常規文件外,可以收集符號鏈接。收集符號鏈接時,即使報告了符號鏈接的路徑,
Filebeat也會打開並讀取原始文件。
backoff: #backoff選項指定Filebeat如何积極地抓取新文件進行更新。默認1s,backoff選項定義Filebeat在達到EOF之後
再次檢查文件之間等待的時間。
max_backoff: #在達到EOF之後再次檢查文件之前Filebeat等待的最長時間
backoff_factor: #指定backoff嘗試等待時間幾次,默認是2
harvester_limit:#harvester_limit選項限制一個prospector并行啟動的harvester數量,直接影響文件打開數

tags #列表中添加標籤,用過過濾,例如:tags: ["json"]
fields #可選字段,選擇額外的字段進行輸出可以是標量值,元組,字典等嵌套類型
默認在sub-dictionary位置
filebeat.inputs:
fields:
app_id: query_engine_12
fields_under_root #如果值為ture,那麼fields存儲在輸出文檔的頂級位置

multiline.pattern #必須匹配的regexp模式
multiline.negate #定義上面的模式匹配條件的動作是 否定的,默認是false
假如模式匹配條件'^b',默認是false模式,表示講按照模式匹配進行匹配 將不是以b開頭的日誌行進行合併
如果是true,表示將不以b開頭的日誌行進行合併
multiline.match # 指定Filebeat如何將匹配行組合成事件,在之前或者之後,取決於上面所指定的negate
multiline.max_lines #可以組合成一個事件的最大行數,超過將丟棄,默認500
multiline.timeout #定義超時時間,如果開始一個新的事件在超時時間內沒有發現匹配,也將發送日誌,默認是5s
max_procs #設置可以同時執行的最大CPU數。默認值為系統中可用的邏輯CPU的數量。
name #為該filebeat指定名字,默認為主機的hostname
 

3.6、實例一:logstash作為輸出

filebeat.yml配置

#=========================== Filebeat inputs =============================

filebeat.inputs:

# Each - is an input. Most options can be set at the input level, so
# you can use different inputs for various configurations.
# Below are the input specific configurations.

- type: log

  # Change to true to enable this input configuration.
  enabled: true

  # Paths that should be crawled and fetched. Glob based paths.
  paths:  #配置多個日誌路徑
    - /var/logs/es_aaa_index_search_slowlog.log
    - /var/logs/es_bbb_index_search_slowlog.log
    - /var/logs/es_ccc_index_search_slowlog.log
    - /var/logs/es_ddd_index_search_slowlog.log
    #- c:\programdata\elasticsearch\logs\*

  # Exclude lines. A list of regular expressions to match. It drops the lines that are
  # matching any regular expression from the list.
  #exclude_lines: ['^DBG']

  # Include lines. A list of regular expressions to match. It exports the lines that are
  # matching any regular expression from the list.
  #include_lines: ['^ERR', '^WARN']

  # Exclude files. A list of regular expressions to match. Filebeat drops the files that
  # are matching any regular expression from the list. By default, no files are dropped.
  #exclude_files: ['.gz$']

  # Optional additional fields. These fields can be freely picked
  # to add additional information to the crawled log files for filtering
  #fields:
  #  level: debug
  #  review: 1

  ### Multiline options

  # Multiline can be used for log messages spanning multiple lines. This is common
  # for Java Stack Traces or C-Line Continuation

  # The regexp Pattern that has to be matched. The example pattern matches all lines starting with [
  #multiline.pattern: ^\[

  # Defines if the pattern set under pattern should be negated or not. Default is false.
  #multiline.negate: false

  # Match can be set to "after" or "before". It is used to define if lines should be append to a pattern
  # that was (not) matched before or after or as long as a pattern is not matched based on negate.
  # Note: After is the equivalent to previous and before is the equivalent to to next in Logstash
  #multiline.match: after


#================================ Outputs =====================================

#----------------------------- Logstash output --------------------------------
output.logstash:
  # The Logstash hosts #配多個logstash使用負載均衡機制
  hosts: ["192.168.110.130:5044","192.168.110.131:5044","192.168.110.132:5044","192.168.110.133:5044"]  
  loadbalance: true  #使用了負載均衡

  # Optional SSL. By default is off.
  # List of root certificates for HTTPS server verifications
  #ssl.certificate_authorities: ["/etc/pki/root/ca.pem"]

  # Certificate for SSL client authentication
  #ssl.certificate: "/etc/pki/client/cert.pem"

  # Client Certificate Key
  #ssl.key: "/etc/pki/client/cert.key"

./filebeat -e   #啟動filebeat

logstash的配置

input {
  beats {
    port => 5044   
  }
}

output {
  elasticsearch {
    hosts => ["http://192.168.110.130:9200"] #這裏可以配置多個
    index => "query-%{yyyyMMdd}" 
  }
}

 

3.7、實例二:elasticsearch作為輸出

filebeat.yml的配置:

###################### Filebeat Configuration Example #########################

# This file is an example configuration file highlighting only the most common
# options. The filebeat.reference.yml file from the same directory contains all the
# supported options with more comments. You can use it as a reference.
#
# You can find the full configuration reference here:
# https://www.elastic.co/guide/en/beats/filebeat/index.html

# For more available modules and options, please see the filebeat.reference.yml sample
# configuration file.

#=========================== Filebeat inputs =============================

filebeat.inputs:

# Each - is an input. Most options can be set at the input level, so
# you can use different inputs for various configurations.
# Below are the input specific configurations.

- type: log

  # Change to true to enable this input configuration.
  enabled: true

  # Paths that should be crawled and fetched. Glob based paths.
  paths:
    - /var/logs/es_aaa_index_search_slowlog.log
    - /var/logs/es_bbb_index_search_slowlog.log
    - /var/logs/es_ccc_index_search_slowlog.log
    - /var/logs/es_dddd_index_search_slowlog.log
    #- c:\programdata\elasticsearch\logs\*

  # Exclude lines. A list of regular expressions to match. It drops the lines that are
  # matching any regular expression from the list.
  #exclude_lines: ['^DBG']

  # Include lines. A list of regular expressions to match. It exports the lines that are
  # matching any regular expression from the list.
  #include_lines: ['^ERR', '^WARN']

  # Exclude files. A list of regular expressions to match. Filebeat drops the files that
  # are matching any regular expression from the list. By default, no files are dropped.
  #exclude_files: ['.gz$']

  # Optional additional fields. These fields can be freely picked
  # to add additional information to the crawled log files for filtering
  #fields:
  #  level: debug
  #  review: 1

  ### Multiline options

  # Multiline can be used for log messages spanning multiple lines. This is common
  # for Java Stack Traces or C-Line Continuation

  # The regexp Pattern that has to be matched. The example pattern matches all lines starting with [
  #multiline.pattern: ^\[

  # Defines if the pattern set under pattern should be negated or not. Default is false.
  #multiline.negate: false

  # Match can be set to "after" or "before". It is used to define if lines should be append to a pattern
  # that was (not) matched before or after or as long as a pattern is not matched based on negate.
  # Note: After is the equivalent to previous and before is the equivalent to to next in Logstash
  #multiline.match: after


#============================= Filebeat modules ===============================

filebeat.config.modules:
  # Glob pattern for configuration loading
  path: ${path.config}/modules.d/*.yml

  # Set to true to enable config reloading
  reload.enabled: false

  # Period on which files under path should be checked for changes
  #reload.period: 10s

#==================== Elasticsearch template setting ==========================


#================================ General =====================================

# The name of the shipper that publishes the network data. It can be used to group
# all the transactions sent by a single shipper in the web interface.
name: filebeat222

# The tags of the shipper are included in their own field with each
# transaction published.
#tags: ["service-X", "web-tier"]

# Optional fields that you can specify to add additional information to the
# output.
#fields:
#  env: staging

#cloud.auth:

#================================ Outputs =====================================


#-------------------------- Elasticsearch output ------------------------------
output.elasticsearch:
  # Array of hosts to connect to.
  hosts: ["192.168.110.130:9200","92.168.110.131:9200"]

  # Protocol - either `http` (default) or `https`.
  #protocol: "https"

  # Authentication credentials - either API key or username/password.
  #api_key: "id:api_key"
  username: "elastic"
  password: "${ES_PWD}"   #通過keystore設置密碼

./filebeat -e   #啟動filebeat

查看elasticsearch集群,有一個默認的索引名字filebeat-%{[beat.version]}-%{+yyyy.MM.dd}

 

 

 

3.8、filebeat模塊

官網:https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-modules.html

這裏我使用elasticsearch模式來解析es的慢日誌查詢,操作步驟如下,其他的模塊操作也一樣:

前提: 安裝好Elasticsearch和kibana兩個軟件,然後使用filebeat

具體的操作官網有:https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-modules-quickstart.html

第一步,配置filebeat.yml文件

#============================== Kibana =====================================

# Starting with Beats version 6.0.0, the dashboards are loaded via the Kibana API.
# This requires a Kibana endpoint configuration.
setup.kibana:

  # Kibana Host
  # Scheme and port can be left out and will be set to the default (http and 5601)
  # In case you specify and additional path, the scheme is required: http://localhost:5601/path
  # IPv6 addresses should always be defined as: https://[2001:db8::1]:5601
  host: "192.168.110.130:5601"  #指定kibana
  username: "elastic"   #用戶
  password: "${ES_PWD}"  #密碼,這裏使用了keystore,防止明文密碼

  # Kibana Space ID
  # ID of the Kibana Space into which the dashboards should be loaded. By default,
  # the Default Space will be used.
  #space.id:

#================================ Outputs =====================================

# Configure what output to use when sending the data collected by the beat.

#-------------------------- Elasticsearch output ------------------------------
output.elasticsearch:
  # Array of hosts to connect to.
  hosts: ["192.168.110.130:9200","192.168.110.131:9200"]

  # Protocol - either `http` (default) or `https`.
  #protocol: "https"

  # Authentication credentials - either API key or username/password.
  #api_key: "id:api_key"
  username: "elastic"  #es的用戶
  password: "${ES_PWD}" # es的密碼
  #這裏不能指定index,因為我沒有配置模板,會自動生成一個名為filebeat-%{[beat.version]}-%{+yyyy.MM.dd}的索引

第二步:配置elasticsearch的慢日誌路徑

cd filebeat-7.7.0-linux-x86_64/modules.d

vim  elasticsearch.yml

 

 

 

第三步:生效es模塊

./filebeat modules elasticsearch

查看生效的模塊

./filebeat modules list

 

 

 

第四步:初始化環境

./filebeat setup -e

 

 

 

 

 第五步:啟動filebeat

./filebeat -e

查看elasticsearch集群,如下圖所示,把慢日誌查詢的日誌都自動解析出來了:

 

 到這裏,elasticsearch這個module就實驗成功了

 

參考

官網:https://www.elastic.co/guide/en/beats/filebeat/current/index.html

 

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

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

MySQL 性能優化之慢查詢

性能優化的思路

  1. 首先需要使用慢查詢功能,去獲取所有查詢時間比較長的SQL語句
  2. 其次使用explain命令去查詢由問題的SQL的執行計劃(腦補鏈接:點我直達1,點我直達2)
  3. 最後可以使用show profile[s] 查看由問題的SQL的性能使用情況
  4. 優化SQL語句

介紹

  數據庫查詢快慢是影響項目性能的一大因素,對於數據庫,我們除了要優化SQL,更重要的是得先找到需要優化的SQL語句

  MySQL數據庫有一個“慢查詢日誌”功能,用來記錄查詢時間超過某個設定值的SQL,這將極大程度幫助我們快速定位到問題所在,以便對症下藥

至於查詢時間的多少才算慢,每個項目、業務都有不同的要求。
    比如傳統企業的軟件允許查詢時間高於某個值,但是把這個標準方在互聯網項目或者訪問量大的網站上,估計就是一個Bug,甚至可能升級為一個功能缺陷。

  MySQL的慢查詢日誌功能,默認是關閉的,需要手動開啟

開啟慢查詢功能

查看是否開啟慢查詢功能

 

 

參數說明:

  • slow_query_log:是否開啟慢查詢,on為開啟,off為關閉;
  • log-slow-queries:舊版(5.6以下版本)MySQL數據庫慢查詢存儲路徑,可以不設置該參數,系統則會給一個缺省的文件:host_name-slow.log
  • long_query_time:慢查詢閥值,當查詢時間多於設置的閥值時,記錄日誌,單位為秒。

臨時開啟滿查詢功能

  在MySQL執行SQL語句設置,但是如果重啟MySQL的話會失效。

set global slow_query_log=on;
set global long_query_time=1;

永久性開啟慢查詢

  修改:/etc/my.cnf,添加以下內容,然後重啟MySQL服務

[mysqld]
lower_case_table_names=1
slow_query_log=ON
slow_query_log_file=/usr/local/mysql/data/chenyanbindeMacBook-Pro-slow.log
long_query_time=1

 

查看滿查詢啟動狀態

演示慢查詢

  為了演示方便,我們讓sql睡眠3秒!

格式說明:

  • 第一行,SQL查詢執行的具體時間
  • 第二行,執行SQL查詢的連接信息,用戶和連接IP
  • 第三行,記錄了一些我們比較有用的信息,
    • Query_timme,這條SQL執行的時間,越長則越慢
    • Lock_time,在MySQL服務器階段(不是在存儲引擎階段)等待表鎖時間
    • Rows_sent,查詢返回的行數
    • Rows_examined,查詢檢查的行數,越長就越浪費時間
  • 第四行,設置時間戳,沒有實際意義,只是和第一行對應執行時間。
  • 第五行,執行的SQL語句記錄信息

分析滿查詢日誌

MySQL自帶的mysqldumpslow

 

 

 

參數說明:

  • -s, 是表示按照何種方式排序,c、t、l、r分別是按照記錄次數、時間、查詢時間、返回的記錄數來排序,ac、at、al、ar,表示相應的倒敘;
  • -t, 是top n的意思,即為返回前面多少條的數據;
  • -g, 後邊可以寫一個正則匹配模式,大小寫不敏感的;

MySQL性能fenix語句show profile(重要

介紹

  • Query Profiler是MySQL自帶的一種query診斷分析工具,通過它可以分析出一條SQL語句性能瓶頸在什麼地方。
  • 通常使用explain,以及slow query log都無法做到精確分析,但是Query profiler卻可以定位出一條SQL執行的各種資源消耗情況,比如CPU、IO等,以及該SQL執行所耗費的時間等。不過該工具只有在MySQL5.0.37以上版本中才有實現
  • 默認的情況下,MySQL的該功能沒有打開,需要自己手動打開

語句使用

  • show profileshow profiles語句可以展示當前會話(退出session后,profiling重置為0)中執行語句的資源使用情況。
  • show profiles:以列表形式显示最近發送到服務器上執行的語句的資源使用情況,显示的記錄數由變量:profiling_history_size控制,默認15條
  • show profile:只是最近一條語句執行的消息資源佔用信息,默認實現Status和Duration兩列

開啟Profile功能

  • Profile功能由MySQL會話變量:profiling控制,默認是OFF關閉狀態。
  • 查看是否開啟了Profile功能
select @@profiling;

show variables like '%profil%';

 

打開profiling功能

set profiling=1;

 

show profile用法

SHOW PROFILE [type [, type] …… ] [FOR QUERY n] [LIMIT row_count [OFFSET offset]]

type: { ALL | BLOCK IO | CONTEXT SWITCHES | CPU | IPC | MEMORY | PAGE FAULTS | SOURCE | SWAPS }

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

【Spring】BeanDefinition&PostProcessor不了解一下嗎?

水稻:這两天看了BeanDefinition和BeanFactoryPostProcessor還有BeanPostProcessor的源碼。要不要了解一下

菜瓜:six six six,大佬請講

水稻:上次我們說SpringIOC容器是一個典型的工廠模式

  • 假如我們把Spring比作一個生產模型的大工廠,那麼.class文件就是原材料。而BeanDefinition就是創建模型的模具。不管是傳統的XML還是後面的註解,Spring在啟動的時候都會創建一個掃描器去掃描指定目錄下的.class文件,並根據文件的註解,實現的接口以及成員變量將其封裝一個個的BeanDefinition。
    • 比較重要的屬性有id,class,構造函數封裝類,屬性封裝類,factoryMethod等
  • 在對象初始化之前Spring會完成BeanDefinition對象的解析並將其裝入List容器beanDefinitionNames中,然後開始遍歷該容器並根據BeanDefinition創建對象

菜瓜:sodasinei,BeanDefinition我了解了。它是創建bean的模板,類似於java創建對象依賴的class一樣。那還有兩個很長的單詞是啥呢?

水稻:忽略掉後面老長的後綴,我們看BeanFactory和Bean是不是很親切。PostProcessor被翻譯成後置處理器,暫且我們把它看成是處理器就行

  • BeanFactory是bean工廠,它可以獲取並修改BeanDefinition的屬性,進而影響後面創建的對象。
  • Bean就是Spring的對象,這些個處理器才是真正處理bean對象的各個環節的工序,包括屬性,註解,方法

菜瓜:有了模糊的概念,不明覺厲

水稻:來,看demo

package com.vip.qc.postprocessor;

import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;

/**
 * 獲取初始化好的BeanFactory,此時還未進行bean的實例化
 *
 * @author QuCheng on 2020/6/14.
 */
@Component
public class BeanFactoryPostProcessorT implements BeanFactoryPostProcessor {

    public static final String BEAN_NAME = "processorT";

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        BeanDefinition initializingBeanT = beanFactory.getBeanDefinition(BEAN_NAME);
        MutablePropertyValues propertyValues = initializingBeanT.getPropertyValues();
        String pName = "a";
        System.out.println("BeanFactoryPostProcessor a " + propertyValues.getPropertyValue(pName) + " -> 1");
        propertyValues.addPropertyValue(pName, "1");
    }
}


package com.vip.qc.postprocessor;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

/**
 * @author QuCheng on 2020/6/14.
 */
@Component
public class BeanPostProcessorT implements BeanPostProcessor {

    public static final String beanNameT = "processorT";

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (beanNameT.equals(beanName)) {
            ProcessorT processorT = ((ProcessorT) bean);
            System.out.println("BeanPostProcessor BeforeInitialization  a:" + processorT.getA() + "-> 3");
            processorT.setA("3");
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (beanNameT.equals(beanName)){
            ProcessorT processorT = ((ProcessorT) bean);
            System.out.println("BeanPostProcessor AfterInitialization  a:" + processorT.getA() + "-> 4");
            processorT.setA("4");
        }
        return bean;
    }

}


package com.vip.qc.postprocessor;

import org.springframework.stereotype.Component;

/**
 * @author QuCheng on 2020/6/14.
 */
@Component
public class ProcessorT {

    public ProcessorT() {
        System.out.println("ProcessorT 無參構造 a:" + a + "-> 2" );
        a = "2";
    }

    private String a;

    public String getA() {
        return a;
    }

    public void setA(String a) {
        this.a = a;
    }

    @Override
    public String toString() {
        return "ProcessorT{" +
                "a='" + a + '\'' +
                '}';
    }
}

// 測試類
@Test
public void test() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.vip.qc.postprocessor");
    ProcessorT processorT = (ProcessorT) context.getBean("processorT");
    System.out.println(processorT);
}

// 結果
BeanFactoryPostProcessor a null -> 1
ProcessorT 無參構造 a:null-> 2
BeanPostProcessor BeforeInitialization a:1-> 3
BeanPostProcessor AfterInitialization a:3-> 4
ProcessorT{a='4'}
  • BeanFactoryPostProcessor在對象還未初始化前可以拿到對象的BeanDefinition對其設置屬性值
  • 過程中我們分別對屬性a設置了1,2,3,4的值。最後我們拿到的值為4

菜瓜:好像看懂了。BeanFactoryPostProcessor可以拿到BeanFactory對象,獲取裏面所有的BeanDefinition並可對其進行干預。BeanPostProcessor其實是在bean已經被創建完成之後進行加工操作

水稻:沒錯。這是我們自己進行干預的demo。限於篇幅有限,你可以去看一下Spring自己對於這兩個接口的實現源碼。比較重要的推薦下面幾個

  • ConfigurationClassPostProcessor 實現BeanFactoryPostProcessor子接口
    • 完成對@Configuration、@Component、@ComponentScan、@Bean、@Import、@ImportSource註解的搜集和解析
    • @Bean註解會被封裝成所在Bean的BeanDefinition中的factoryMethod屬性中,單獨進行實例化
  • CommonAnnotationBeanPostProcessor 實現 BeanPostProcessor
    • 完成@PostConstruct@PreDestroy@Resource註解的搜集和解析工作
    • @PostConstruct會在對象初始化且屬性渲染完成後進行
    • @Resource註解(參照下面)
  • AutowiredAnnotationBeanPostProcessor 實現 BeanPostProcessor
    • 完成@Autowired@Value註解的搜集和解析工作
    • 在對象初始化完成之後會先進行註解的搜集,然後進行屬性渲染調用populateBean方法,使用策略模式調用實現接口對註解進行解析,有@Autowired和@Value註解會調用getBean方法發起對依賴屬性的注入
  • AbstractAutoProxyCreator的入口類也是實現的BeanPostProcessor

菜瓜:你放心,我不會看的。這麼複雜的東西,聽着都費勁

水稻:不愧是你!有機會聊bean的生命周期的時候咱們還會說到這些東西。到時候再刷一遍

 

總結:

  • BeanDefinition是spring容器創建對象的模板,定義了bean創建的細節
  • BeanFactoryPostProcessor可以拿到整個容器對象,當然也能修改BeanDefinition,所以能直接操作bean的創建
  • BeanPostProcessor執行的時候bean已經創建完成了,我們可以拿到想要的對象進行干預和設值等操作

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

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

您的單例模式,真的單例嗎?

      單例模式,大家恐怕再熟悉不過了,其作用與實現方式有多種,這裏就不啰嗦了。但是,咱們在使用這些方式實現單例模式時,程序中就真的會只有一個實例嗎?

      聰明的你看到這樣的問話,一定猜到了答案是NO。這裏筆者就不賣關子了,開門見山吧!實際上,在有些場景下,如果程序處理不當,會無情地破壞掉單例模式,導致程序中出現多個實例對象。

      下面筆者介紹筆者已知的三種破壞單例模式的方式以及避免方法。

1、反射對單例模式的破壞

      我們先通過一個例子,來直觀感受一下

    (1)案例

  DCL實現的單例模式:

 1 public class Singleton{
 2     private static volatile Singleton mInstance;
 3     private Singleton(){}
 4     public static Singleton getInstance(){
 5         if(mInstance == null){
 6             synchronized (Singleton.class) {
 7                 if(mInstance == null){
 8                     mInstance = new Singleton();
 9                 }
10             }
11         }
12         return mInstance;
13     }
14 }

  測試代碼:

 1 public class SingletonDemo {
 2 
 3     public static void main(String[] args){
 4         Singleton singleton = Singleton.getInstance();
 5         try {
 6             Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
 7             constructor.setAccessible(true);
 8             Singleton reflectSingleton = constructor.newInstance();
 9             System.out.println(reflectSingleton == singleton);
10         } catch (Exception e) {
11             // TODO Auto-generated catch block
12             e.printStackTrace();
13         }
14     }
15 }

  執行結果:

false

      運行結果說明,採用反射的方式另闢蹊徑實例了該類,導致程序中會存在不止一個實例。

    (2)解決方案

      其思想就是採用一個全局變量,來標記是否已經實例化過了,如果已經實例化過了,第二次實例化的時候,拋出異常。實現代碼如下:

 1 public class Singleton{
 2     private static volatile Singleton mInstance;
 3     private static volatile boolean mIsInstantiated = false;
 4     private Singleton(){
 5         if (mIsInstantiated){
 6             throw new RuntimeException("Has been instantiated, can not do it again!");
 7         }
 8         mIsInstantiated = true;
 9     }
10     public static Singleton getInstance(){
11         if(mInstance == null){
12             synchronized (Singleton.class) {
13                 if(mInstance == null){
14                     mInstance = new Singleton();
15                 }
16             }
17         }
18         return mInstance;
19     }
20 }

執行結果:

 

     這種方式看起來比較暴力,運行時直接拋出異常。

 

2、clone()對單例模式的破壞 

       當需要實現單例的類允許clone()時,如果處理不當,也會導致程序中出現不止一個實例。

    (1)案例

  一個實現了Cloneable接口單例類:

 1 public class Singleton implements Cloneable{
 2     private static volatile Singleton mInstance;
 3     private Singleton(){
 4     }
 5     public static Singleton getInstance(){
 6         if(mInstance == null){
 7             synchronized (Singleton.class) {
 8                 if(mInstance == null){
 9                     mInstance = new Singleton();
10                 }
11             }
12         }
13         return mInstance;
14     }
15     @Override
16     protected Object clone() throws CloneNotSupportedException {
17         // TODO Auto-generated method stub
18         return super.clone();
19     }
20 }

  測試代碼:

 1 public class SingletonDemo {
 2 
 3     public static void main(String[] args){
 4         try {
 5             Singleton singleton = Singleton.getInstance();
 6             Singleton cloneSingleton;
 7             cloneSingleton = (Singleton) Singleton.getInstance().clone();
 8             System.out.println(cloneSingleton == singleton);
 9         } catch (CloneNotSupportedException e) {
10             e.printStackTrace();
11         }
12     }
13 }

執行結果:

false

  (2)解決方案:

     解決思想是,重寫clone()方法,調clone()時直接返回已經實例的對象

 1 public class Singleton implements Cloneable{
 2     private static volatile Singleton mInstance;
 3     private Singleton(){
 4     }
 5     public static Singleton getInstance(){
 6         if(mInstance == null){
 7             synchronized (Singleton.class) {
 8                 if(mInstance == null){
 9                     mInstance = new Singleton();
10                 }
11             }
12         }
13         return mInstance;
14     }
15     @Override
16     protected Object clone() throws CloneNotSupportedException {
17         return mInstance;
18     }
19 }

執行結果:

true

 

3、序列化對單例模式的破壞

   在使用序列化/反序列化時,也會出現產生新實例對象的情況。

  (1)案例

      一個實現了序列化接口的單例類:

 1 public class Singleton implements Serializable{
 2     private static volatile Singleton mInstance;
 3     private Singleton(){
 4     }
 5     public static Singleton getInstance(){
 6         if(mInstance == null){
 7             synchronized (Singleton.class) {
 8                 if(mInstance == null){
 9                     mInstance = new Singleton();
10                 }
11             }
12         }
13         return mInstance;
14     }
15 }

    測試代碼:

 1 public class SingletonDemo {
 2 
 3     public static void main(String[] args){
 4         try {
 5             Singleton singleton = Singleton.getInstance();
 6             FileOutputStream fos = new FileOutputStream("singleton.txt");
 7             ObjectOutputStream oos = new ObjectOutputStream(fos);
 8             oos.writeObject(singleton);
 9             oos.close();
10             fos.close();
11 
12             FileInputStream fis = new FileInputStream("singleton.txt");
13             ObjectInputStream ois = new ObjectInputStream(fis);
14             Singleton serializedSingleton = (Singleton) ois.readObject();
15             fis.close();
16             ois.close();
17             System.out.println(serializedSingleton==singleton);
18         } catch (Exception e) {
19             e.printStackTrace();
20         }
21 
22     }
23 }

     運行結果:

false

    (2)解決方案

    在反序列化時的回調方法 readResolve()中返回單例對象。

 1 public class Singleton implements Serializable{
 2     private static volatile Singleton mInstance;
 3     private Singleton(){
 4     }
 5     public static Singleton getInstance(){
 6         if(mInstance == null){
 7             synchronized (Singleton.class) {
 8                 if(mInstance == null){
 9                     mInstance = new Singleton();
10                 }
11             }
12         }
13         return mInstance;
14     }
15 
16     protected Object readResolve() throws ObjectStreamException{
17         return mInstance;
18     }
19 }

結果:

true

 

       以上就是筆者目前已知的三種可以破壞單例模式的場景以及對應的解決辦法,讀者如果知道還有其他的場景,記得一定要分享出來噢,正所謂“獨樂樂不如眾樂樂”!!!

       單例模式看起來是設計模式中最簡單的一個,但“麻雀雖小,五臟俱全”,其中有很多細節都是值得深究的。即便是本篇介紹的這幾個場景,也只是介紹了一些梗概而已,很多細節還需要讀者自己去試驗和推敲的,比如:通過枚舉方式實現單例模式,就不存在上述問題,而其它的實現方式似乎都存在上述問題!

 

       後記

       本篇參(剽)考(竊)了如下資料:

       高洪岩的《Java 多線程編程核心技術》

       博文:https://blog.csdn.net/fd2025/article/details/79711198

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

【其他文章推薦】

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

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

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

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

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

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

都是30萬德系轎車,頂配邁騰和奧迪A4L到底差多少?

其實邁騰多出來的配置不止這些,還有二十多項,全部寫出來太長,故略去。低配的奧迪A4L採用了低功率的2。0T發動機,最大馬力是190匹,最大扭矩是320牛•米。頂配的邁騰使用的2。0T發動機賬面數據更好,220匹的最大馬力和350牛•米的最大扭矩均優於A4L。

隨着平台化,規模化生產的普及,很多大集團都會在自己的普通品牌和豪華品牌上通用零件,但是定價卻差了幾萬,那麼這幾萬的差價真的如大家所說,是LOGO的差異嗎?同一個集團,應該買TA的普通品牌高配車型還是豪華品牌的低配車型比較划算?

今天,我們來對比一下大眾邁騰的380TSI 旗艦版和奧迪A4L的40TFSI 進取版,看看哪個更值得買。

邁騰和奧迪A4L的外觀是同一個風格的兩份答卷。邁騰的橫貫式前臉把大燈連接起來,從視覺上來看顯得更寬更低矮。今天對比的邁騰380TSI 旗艦版配備的是18寸輪轂,所以邁騰的側面看上去更加大氣一些。

奧迪A4L則是在原有的六邊形格柵的家族設計元素上再下功夫,把大燈、格柵、車身線條設計得再銳利一些,所以奧迪A4L的外觀顯得更加年輕。不過這17寸的輪轂嘛,說實話不是很能襯托出A4L的檔次,建議後期更換。

這一局,邁騰略勝。

這一代的邁騰是歐版帕薩特直接拿來生產的,所以這一代邁騰的風格反而是繼承了上一代帕薩特的商務風。在邁騰上,木紋飾板貫穿整个中控台,空調出風口被設計得更高,所以空間更加寬闊。奧迪A4L的內飾以科技感為主要設計導向,奧迪A4L 30TSI 進取版採用中控台採用了銀色拉絲面板,中控屏採用懸浮式設計。可惜的是低配車沒有使用全液晶显示屏,空調面板也不是高配的那款金屬質感控制面板。所以質感稍顯不足。

這一局,也是邁騰勝出。

無論是邁騰還是奧迪A4L,引進國內后,都進行了不同程度的加長,邁騰的軸距為2871mm,比海外版增加了80mm,奧迪A4L的軸距是2908mm,比海外版增加了88mm。雖然奧迪A4L的軸距比邁騰要長一點,但是乘坐空間方面,邁騰比奧迪A4L要寬敞一點。這是因為邁騰是MQB(發動機橫置平台)的產物,而奧迪A4L是MLB(發動機縱置平台)的產物。發動機橫置的結構比較緊湊,占乘坐空間較少,所以邁騰的乘坐空間比奧迪A4L稍微大一點。

這一局,因為兩者的差距微乎其微,所以判定兩者打成平手。

在配置上,邁騰完勝沒什麼懸念。其實邁騰多出來的配置不止這些,還有二十多項,全部寫出來太長,故略去。

低配的奧迪A4L採用了低功率的2.0T發動機,最大馬力是190匹,最大扭矩是320牛•米。頂配的邁騰使用的2.0T發動機賬面數據更好,220匹的最大馬力和350牛•米的最大扭矩均優於A4L。從加速成績來看,邁騰比奧迪A4L更好,但也只是半秒的差距。奧迪A4L的發動機有AVS可變氣門升程技術和混合噴射技術。這兩項技術都可以提高燃油經濟性,所以從用戶反饋的數據來看,奧迪A4L比邁騰節油不少。

所以這局,也只能判五五開。

說到舒適性方面奧迪A4L的前後懸挂都使用了五連桿獨立懸挂,而邁騰則採用了前麥弗遜獨立懸挂后多連桿獨立懸挂的組合。這不僅是因為成本,還因為邁騰使用的MQB佔據了機艙空間,所以無法布局多連桿懸挂。麥弗遜式獨立懸挂有個缺點,那就是上下運動的時候對車輪外傾角變化明顯,而車輪外傾角變化會影響操縱的穩定性。所以,為了安全性考慮,麥弗遜懸挂一般都會比較硬。而使用五連桿的奧迪A4L可以把懸挂調得軟一些,所以奧迪A4L的濾震更好。在加上在隔音材料方面,豪華品牌比普通品牌的投入會更大。

所以舒適性方面,奧迪A4L完勝邁騰是沒什麼懸念的。

總結:縱觀兩車各個方面的對比,互有勝負。豪華品牌的車不是只掛着一個LOGO就賣這麼貴,如果你坐過邁騰后再坐A4L,你確確實實能夠感受到豪華車帶給你的那種舒適感,高級感。但是在靜態體驗的時候,邁騰的頂配版可以說是完虐奧迪A4L了。所以,我建議,如果你打算購買一台商務車,邁騰的頂配版更加合適。如果只是買來自用,奧迪A4L值得考慮。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

要成為合格的女司機,最大的挑戰是什麼?

可是,要完成這一次挑戰,我必須要有一台車。這次和我一起挑戰的,是全新昂科威28T頂配車型,2。0T發動機搭配9AT變速箱,擁有260匹馬力,還配備了CDC主動懸挂。2。0T發動機符合我對動力跟經濟性的需求。相比以前,全新的9AT變速箱也足夠聰明,足以應付多種路況。

大家好,我是小喬,說真的,我有點害怕。

作為一個女司機,我覺得我的駕駛技術算挺好了,但為什麼,我的知名程度,遠遠不如那個長得跟我差不多的Jacky呢?

到底是“7200干它”成就了他,還是他成就了“7200干它”,這個問題,在我腦海中迴旋,如同先有雞還是先有蛋的生物史拷問,令我疑惑不已。終於,我決定卧薪嘗膽,直接跟他當面對質爆紅的真諦!

Jacky面對我的質疑,嘴角微微上揚,說到:“既然你誠心誠意想要爆紅,那我就大發慈悲地成全你, 別克SUV強者挑戰之旅,這個對精神以及體力都有極大考驗的活動,就決定讓你參加了!“

挑戰內容

挑戰一:體能挑戰:限時挑戰3公里高空棧道。優秀的司機必須擁有強壯的體能!

挑戰二:耐力測試,獨自跑完4個小時高速全程,鍛煉作為司機的集中力和耐久力。

挑戰三:膽量挑戰:攀登懸崖天梯,鍛煉司機膽大心細的危急情況處理能力。

挑戰四:險中求勝:考驗司機越野路面的駕駛能力。

手拿這份挑戰清單,我感到一絲恐懼,但誓要成為女車神的我,不會輕易認輸!

可是,要完成這一次挑戰,我必須要有一台車。這次和我一起挑戰的,是全新昂科威28T頂配車型,2.0T發動機搭配9AT變速箱,擁有260匹馬力,還配備了CDC主動懸挂。

2.0T發動機符合我對動力跟經濟性的需求;相比以前,全新的9AT變速箱也足夠聰明,足以應付多種路況;它的懸架還能主動變化,公路、越野我都能舒舒服服。這一次挑戰,我勢在必得。

想知道到底Jacky給小喬的挑戰有多可怕?讓小喬這個沒心沒肺的女人都感到驚慌,而她又能否順利完成任務?趕緊點開視頻看一看!

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

【其他文章推薦】

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

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

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

※超省錢租車方案

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

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