Python機器學習筆記:SVM(1)——SVM概述,Python機器學習筆記:SVM(1)——SVM概述,Python機器學習筆記:SVM(2)——SVM核函數,Python機器學習筆記:SVM(3)——證明SVM,Python機器學習筆記:SVM(4)——sklearn實現,Python機器學習筆記:Logistic Regression_網頁設計公司

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

搬家費用:依消費者運送距離、搬運樓層、有無電梯、步行距離、特殊地形、超重物品等計價因素後,評估每車次單

前言

  整理SVM(support vector machine)的筆記是一個非常麻煩的事情,一方面這個東西本來就不好理解,要深入學習需要花費大量的時間和精力,另一方面我本身也是個初學者,整理起來難免思路混亂。所以我對SVM的整理會分為四篇(暫定為四篇)學習,不足之處,請多多指導。

  四篇分別為:

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

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

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

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

  在整理筆記的時候,參考了不少的資料,不少網絡寫的優秀論文,博客,回答等。於是我在學習了SVM之後,加入了自己的理解,結合網友的筆記整理了自己能理解的SVM,於此寫下自己的學習筆記。本着取之於網絡,還之於網絡的想法,最後將博客公開,最後申明一下,參考的博客,知乎等地址,我均會附於文章後面,不喜勿噴,謝謝。

  下面言歸正傳,開始學習SVM。

1,SVM的基本思想

  支持向量機,因為英文名為 support vector machine,故一般簡稱為SVM。他是一種常用的判別方法,在機器學習領域是一個有監督的學習模式,通常用來進行模型識別,分類,回歸分析以及異常值檢測。

  通俗的講:支持向量機是一種兩類分類模型,其基本模型定義為特徵空間上的間隔最大的線性分類器,其學習策略(分割原則)便是間隔最大化,最終可轉換為一個凸二次規劃問題的求解

  支持向量機是許多大佬在多年研究統計學習理論基礎上對線性分類器提出的另一種設計最佳準則。其原理也從線性可分說起,然後擴展到線性不可分的情況。甚至擴展到使用非線性函數中去。

  支持向量機的提出有很深的理論背景,這個我們後面再學習,下面先說一下SVM的基本思想,SVM的主要思想可以概括為兩點:

  1 它是針對線性可分情況進行分析,對於線性不可分情況,通過使用非線性映射算法將低維輸入空間線性不可分的樣本轉化為高維特徵空間使其可分,從而使得高維特徵空間採用線性算法對樣本的非線性特徵進行線性分析成為可能。

  2 它是基於結果風險最小化理論之上在特徵空間中構建最優超平面,使得學習器得到全局最優化,並且在整個樣本空間的期望以某個概率滿足一定上界。

  支持向量機的線性分類:是給定一組訓練實例,每個訓練實例被標記為屬於兩個類別中的一個或另一個,SVM訓練算法創建一個將新的實例分配給兩個類別之一的模型,使其成為非概率二元線性分類器。SVM模型是將實例表示為空間中的點,這樣映射就使得單獨類別的實例被盡可能寬的明顯的間隔分開。然後,將新的實例映射到同一空間,並基於他們落在間隔的哪一側來預測所屬類別。

  支持向量機的非線性分類:除了進行線性分類之外,SVM還可以使用所謂的核技巧有效的進行非線性分類,將其輸入隱式映射到高維特徵空間中。當數據未被標記時,不能進行監督式學習,需要用非監督式學習,它會嘗試找出數據到簇的自然聚類,並將新數據映射到這些已形成的簇。將支持向量機改進的聚類算法被稱為支持向量聚類,當數據未被標記或者僅一些數據被標記時,支持向量聚類經常在工業應用中用作分類步驟的預處理。

   SVM(Support Vector Machines)——支持向量機是在所有知名的數據挖掘算法中最健壯,最準確的方法之一,它屬於二分類算法,可以支持線性和非線性的分類。發展到今天,SVM已經可以支持多分類了。

  首先,我們看一個例子:

   比如上圖中,我們用一條直線將兩類數據分開了,但是我畫了三條線,而且這三條都能分開,但是這三條線那條更好呢?用SVM的思想來說,就是什麼樣的決策邊界才是最好的呢?更進一步,當數據特徵更加複雜,本身如果很難分,怎麼辦呢?那特徵複雜后,計算複雜度如何呢那SVM能實際應用嗎

  讓我們帶着這些問題來學習SVM,而理解SVM,我們先得明白一個概念:線性分類器。

2,分類標準的起源:Logistic回歸

  之前整理的Logistic回歸博文地址:

Python機器學習筆記:Logistic Regression

  我們從最起源說起,雖然說之前博文已經學習過Logistic 回歸了,不懂的可以去看看,有詳細的筆記。但是這裏我還是簡單的再學習一邊別人理解的Logistic回歸筆記,溫故而知新。

  給定一些數據點,他們分別屬於兩個不同的類,現在要找到一個線性分類器把這些數據分成兩類。如果用 x 表示數據點,用 y表示類別(y可以取1或者-1,代表兩個不同的類),一個線性分類器的學習目標便是要在 n 維的數據空間中找到一個超平面(hyper plane),這個超平面的方程可以表示為(其中WT中的T表示轉置):

   Logistic回歸的目的是從特徵學習出一個 0/1 分類模型,而這個模型是將特徵的線性組合作為自變量,由於自變量的取值範圍是負無窮到正無窮。因此,使用Logistic函數(或者也稱為Sigmoid函數)將自變量映射到(0, 1)上,映射后的值被認為是屬於 y=1 的概率。

  我們假設函數如下:

   其中 x 是 n 維特徵向量,函數 g 就是Logistic函數,我們令 z = ΘTx ,則 g(z)為:

   其圖像為:

   可以看到,將無窮映射到了(0,  1),而我們假設的 hΘx 就是特徵屬於 y=1的概率:

   從而,當我們要判別一個新來的特徵屬於那個類的時候,只需要求出 hΘx 即可,若 hΘx 大於 0.5就是 y=1的類,反之屬於 y=0 的類。

   此外,hΘx 只和 θTx有關, θTx >0 ,那麼 hΘx >0.5,而 g(z) 只是用來映射,真實的類別決定權還是在於  θTx 。再者,當  θTx >> 0 時,hΘx = 1,反之 hΘx = 0。如果我們只從  θTx 出發,希望模型達到的目標就是讓訓練數據中 y=1 的特徵  θTx >> 0,而 y=0 的特徵  θTx << 0。Logistic回歸就是要學習得到  θ,使得正例的特徵遠大於0,負例的特徵遠小於0,而且要在全部訓練實例上達到這個目標。

  接下來,嘗試把 Logistic 回歸做個變形。首先,將使用的結果標籤 y=0 和 y=1 替換為 y = -1, y=1,然後將下面公式的 θ0 替換為 b。

   最後將後面的 一串替換為 WTx,即下面一串被替換:

   如此,則有了:

   也就是說除了將 y由 y=0 變為 y=-1外,線性分類函數跟 logistic回歸的形式化g表示函數沒有區別,下面是Logistic回歸的形式化表示函數:

  對於邏輯回歸我們先說到這裏,下面看線性分類和邏輯回歸的比較。

  SVM和Logistic雖然說都是尋找一個線性分類界限,但出發點不同。SVM是以訓練集兩個類的邊界(支持向量)來考慮劃分,而Logistic是從訓練集的全局來考慮劃分。這也就是為什麼Logistic受噪聲和離群點的影響比較大。當出現一個離群點時,Logistic劃定的邊界很有可能會改變。而SVM這邊劃分的邊界卻可能絲毫不動(因為離群點並不影響我支持向量)。

3,線性分類的一個例子

3.1 線性可分的二分類模型

  什麼是線性可分呢?如果一個線性函數能夠將樣本分開,稱這些數據樣本是線性可分的。那麼什麼是線性函數呢?在二維空間中就是一條直線,在三維空間中就是一個平面,依次類推,如果不考慮空位維度,這樣的線性函數就統稱為超平面。我們一般所說的線性可分支持向量機就對應着能將數據正確劃分並且間隔最大的直線。

  定理1:線性二分類模型的目標就是找到一組合適的參數(w,  b),使得:

   即:線性二分類模型希望在特徵空間找到一個劃分超平面,將屬於不同標記的樣本分開

  我們下面舉個簡單的例子。如下圖所示,現在有一個二維平面,平面上有兩種不同的數據,分別用圈和叉表示。由於這些數據是線性可分的,所以可以用一條直線將這兩類數據分開,這條直線就相當於一個超平面,超平面一邊的數據點所對應的 y 全是 -1 ,另一邊所對應的 y 全是1。

   上面的超平面可以用分類函數 f(x) 表示,f(x)如下:

   當 f(x)=0 的時候,x便是位於超平面上的點,而 f(x) > 0的點對應 y=1 的數據點,f(x) < 0 的點對應 y = -1的點,如下圖所示:

   換言之,在進行分類的時候,遇到一個新的數據點 x,將 x 代入 f(x) 中,如果 f(x) 小於0,則將 x 的類別賦予 -1,稱 xi 為負例;如果 f(x) 大於  0,則將 x 的類別賦予 1,稱xi為正例。

  所以接下來的問題是,如何確定這個超平面呢?從直觀上而言,這個超平面應該是最適合分類兩類數據的直線。而判定“最適合”的標準就是這條直線離直線兩邊的數據的間隔最大。所以,得尋找有着c最大間隔的超平面。這個問題,我們先擱置一下,下面說一下線性可分支持向量機。

3.2 線性可分支持向量機  

  線性可分支持向量機(SVM)也是一種線性二分類模型,也需要找到滿足 定理1 約束的劃分超平面,即(w,  b),由於能將樣本分開的超平面可能有很多,SVM進一步希望找到離個樣本都比較遠的劃分超平面。

  當面對樣本的隨機擾動時,離每個樣本都比較遠的劃分超平面對擾動的容忍能力比較強,即不容易因為樣本的隨機擾動使得樣本穿越到劃分超平面的另外一側而產生分類錯誤。因此這樣的劃分超平面對樣本比較穩健,不容易過擬合。另一方面,離各樣本都比較遠的劃分超平面不僅可以把正負樣本都分開,還可以比較大的確信度將所有樣本分開,包括難分的樣本,即離劃分超平面近的樣本。

  分類學習最基本的思想就是基於訓練集 D 在樣本空間中找到一個劃分超平面,將不同類別的樣本分開,但是能將訓練樣本分開的劃分超平面可能有很多,所有我們應該去找位於兩類訓練樣本“正中間”的劃分超平面。因為該劃分超平面對訓練樣本局部擾動的“容忍”性最好,例如,由於訓練集的局限性或者噪聲的因素,訓練集外的樣本可能比正中間的訓練樣本更接近於兩個類的分割界,這將使得許多劃分超平面出現錯誤。而正中間的超平面影響最小,所以這個劃分超平面所產生的結果是魯棒的。

  所以問題就變為哪一個分類超平面是最優的?而要算最優超平面,肯定先算出間隔,只有間隔最大化,才能找出最優超平面,所以下面學習函數間隔。

4,函數間隔 functional margin 與幾何間隔 geometrical  margin

  間隔(margin):每個訓練觀測點到超平面距離中的最小值。

  上面也提到了,我們的劃分超平面可以用如下線性方程來描述:

  其中W為法向量,決定了超平面的方向,b為位移量,決定了超平面與原點的距離。

  假設超平面能將訓練樣本正確的分類,即對訓練樣本(xi,  yi),滿足以下公式:

   該公式稱為最大間隔假設,yi = +1 表示樣本為正樣本,yi = -1 表示樣本為負樣本,式子前面選擇 >= +1, <= -1隻是為了方便計算,原則上可是任意常數,但無論是多少,都可以通過對 w 的變換使其為 +1 和 -1,實際上公式等價於:

  在超平面 w*x+b=0 確定的情況下, | w*x+b | 能夠表示點 x 到距離超平面的遠近,而通過觀察 w*x+b 的符號與類標記 y 的符號是否一致可以判斷分類是否正確,所以,可以用 (y * (w*x+b))的正負性來判定或表示分類的正確性。於此,我們便引出了函數間隔(functional margin)的概念。

  一般的,當樣本點被分類正確時,定義函數間隔(用 γ hat 表示)為:

   而超平面(w,  b)關於 訓練數據集T 中所有樣本點(xi , yi)的函數間隔最小值(其中, x是特徵, y是結果標籤,i表示第 i 個樣本),便為超平面(w,  b)關於訓練數據集T的函數間隔(即訓練觀測與超平面的間隔):

   但這樣定義的函數間隔有問題,即如果成比例的改變 w 和 b (假設將他們改為 2w 和 2b),則函數間隔的值 f(x) 卻變成了原來的 2 倍(雖然此時超平面沒有改變),所以引出真正定義點到超平面的距離——幾何間距(geometrical  margin)的概念。

  假定對於一個點 x,令其垂直投影到超平面上的對應點為 x0, w是垂直於超平面的一個向量,γ 為樣本 x 到超平面的距離,如下圖所示:

   根據平面幾何知識,有:

   其中 ||w|| 為 w 的二階范數(范數是一個類似於模的表示長度的概念),w / ||w|| 是單位向量(一個向量除以它的模稱之為單位向量)。

  又由於 x0 是超平面的點,滿足 f(x0) = 0,代入超平面的方差 wTx+b=0 ,可得 wTx0+b=0,即 wTx0 = -b。

  隨即讓上式(平面幾何所得公式)的兩邊同時乘以 WT,然後根據 wTx0 = -b 和 wTw = ||w||2,即可算出 γ :

   為了得到 γ 的絕對值,令 γ 乘上對應的類別 y,即可得出幾何間隔 (用 γ hat 表示)的定義:

   從上述函數間隔和幾何間隔的定義可以看出:幾何間隔就是函數間隔除以 ||w||,而且函數間隔  y * (w*x+b) = y * f(x) 實際上就是 | f(x) |,只是人為定義的一個間隔度量,而且幾何間隔 | f(x) | / || w || 才是直觀上的點到超平面的距離。

4.1 點到超平面的距離

  這裏補充一下點到超平面的距離的概念。設二維空間存在一個超平面實現二類可分如下圖所示:

  圖中的斜線表示超平面 g(x) = w*x + b = 0,二維平面上一點 X 在超平面的距離投影為 X’,則二者關係可表示為 X = X’ + λ w(w 表示超平面的梯度向量),將 X 代入到 g(x)得:

  點到超平面的距離即是 X 與 X’ 之間的距離:

  該公司為高等數學中點到平面的距離公式,只不過這裏點和平面表達式的係數都用向量表示了。而我們上面卻是使用函數間隔和幾何間隔的角度引入距離的。所以注意區分。

  我們使用PPT的內容表達如下:

5,最大間隔分類器 Maximum Margin Classifier 的定義

  最大間隔超平面:間隔最大的超平面,即使得訓練觀測到分割超平面的間隔達到最大。

  支持向量:樣本點中與分離超平面距離最近的樣本點的實例

  最大間隔超平面的選取只與支持向量有關

  對一個數據點進行分類,當超平面離數據點的“間隔”越大,分類的確信度(confidence)也越大。所以,為了使得分類的確信度盡量高,需要讓所選擇的超平面能夠最大化這個“間隔”值。這個間隔就是下面的Gap的一半。

   通過由前面的分析可知:函數間隔不適合用來最大化間隔值,因為在超平面固定以後,可以等比例地縮放 w 的長度和 b 的值,這樣可以使得 f(x) = wTx + b 的值任意大,亦即函數間隔 γ hat 可以在超平面保持不變的情況下被取得任意大。但幾何間隔因為除上了 ||w||,使得在縮放 w 和 b 的時候幾何間隔 γ hat 的值是不會改變的,它只隨着超平面的變動而變動。因此,這是更加合適的一個間隔。換言之,這裏要找的最大間隔分類超平面中的“間隔”指的是幾何間隔。

  於是最大間隔分類器(maximum  margin  classifier)的目標函數可以定義為: max  γ hat

  同時需要滿足一些條件,根據間隔的定義,有(下面兩個式子是等價的):

   其中,s.t 即subject to,它導出的是約束條件。

  即我們希望最大化超平面關於訓練集的間隔 γ ,約束條件表示的是超平面關於每個訓練樣本點的間隔至少是 γ

  回歸一下幾何間隔的定義:

   可知:如果令函數間隔 γ hat 等於1(之所以令 γ hat =1,是為了方便推導和優化,且這樣做對目標函數的優化沒有影響)則有 γ hat = 1 / || w || 且:

   從而上述目標函數轉換成了(即線性可分支持向量機模型的最優化問題):

   相當於在相應的約束條件下(約束條件為上上式子),最大化這個 1 / ||w|| 值,而 1 / ||w|| 便是幾何間隔 γ hat 。

  如下圖所示,中間的實線便是尋找到的最優超平面(optimal Hyper Plane),其到兩條虛線邊界的距離相等,這個距離便是幾何間隔 γ hat ,兩條虛線間隔邊界之間的距離等於  2*γ hat ,而虛線間隔邊界上的點則是支持向量。由於這些支持向量剛好在虛線間隔邊界上,所以他們滿足 y( wTx + b) = 1(還記得我們把functional margin 定位1了嗎?上面我們方便推導和優化的目的,我們可以令 γ hat = 1),而對於所有不是支持向量的點,則顯然有 y( wTx + b) > 1。

   所以線性可分支持向量機模型的最優化問題:

   這是一個凸二次規劃問題,若能求出(1)~(2)的解 w*, b*,那麼就可以得到最大間隔超平面 w*Tx + b = 0 t及分類決函數 f(x)

   其實到目前為止,對於只關心如何使用SVM的盆友便足夠了。可以不用深究其更深的原理了。而需要深究的話,那就接着來。

6,從線性可分到線性不可分

  支持向量機是一種二分類模型,他的目的是尋找一個超平面對樣本進行分割,分割的原則是間隔最大化,最終轉換為一個凸二次規劃問題來求解,而由簡至繁的模型包括:

  • 當訓練樣本線性可分時,通過硬間隔最大化,學習一個線性可分支持向量機
  • 當訓練樣本近似線性可分時,通過軟間隔最大化,學習一個線性支持向量機
  • 當訓練樣本線性不可分時,通過核技巧和軟間隔最大化,學習一個非線性支持向量機

  說這個的目的是什麼呢?就是我們上面一堆例子,通過線性可分的例子說明了支持向量機,要深入學習支持向量機,我們第一步肯定是從訓練樣本線性可分過渡到線性不可分。

  那我們接着從之前的目標函數說:

   由於求 1 / ||w|| 的最大值相當於求 1 /2  ||w||2 的最小值,所以上述目標函數等價於(w 由分母變為分子,從而也由原來的 max 問題變為 min 問題,很明顯,兩者問題等價):

  因為現在的目標函數是二次的,約束條件是線性的,所以它是一個凸二次規劃問題(這些我們上面均提到了)。那麼這個問題如何求解呢?

  可以用線程的QP(Quadratic Programming)優化包進行求解。簡單來說:就是在一定的約束條件下,目標最優,損失最小。

  此外,由於這個問題的特殊結構,還可以通過拉格朗日對偶性(Lagrange  Duality)變換到對偶變量(dual variable)的優化問題,即通過求解與原問題等價的對偶問題(dual problem)得到原始問題的最優解,這就是線性可分條件下支持向量機的對偶算法,這樣做的優點在於:一者對偶問題往往更容易求解;二者可以自然的引入核函數,進而推廣到非線性分類問題。

6.1整理一下我們的思緒

  這裏使用老師的PPT,整理一下我們對SVM的理解。

6.2  從原始問題到對偶問題的求解

  那麼什麼是拉格朗日對偶性呢?簡單來說,通過對每一個約束條件加上一個拉格朗日乘子(Lagrange multiplier)α,然後定義出拉格朗日函數(通過拉格朗日函數將約束條件融合到目標函數里去,從而只用一個函數表達式便能清楚的表達出我們的問題)

  然後令:

   而當所有約束條件都滿足時,則最優值為:

   上式最優值即最初要最小化的量,所以在要求約束條件得到滿足的情況下最小化  1 /2  ||w||2 ,實際上等價於直接最小化 Θ(w) (當然,這裏也有約束條件(即KKT條件的約束),就是 αi >= 0, i=1,….n),容易驗證,當某個約束條件不滿足時,例如 yi * (w*xi + b) < 1,那麼顯然有 Θ(w) = ∞(只要令 αi = ∞ 即可)

  具體寫出來,目標函數變成了:

   這裏用 P* 表示這個問題的最優解,且和最初的問題是等價的。如果直接求解,那麼一上來便得面對 w  和 b 這兩個參數,而 αi 又是不等式約束,這個求解過程不好做。不妨把最小和最大的位置交換一下,變成:

  交換以後的新問題是原始問題的對偶問題,這個新問題的最優值用 d* 來表示。而且有 d* <= p*,在滿足某些條件的情況下,這二者等價,這時候就可以通過求解對偶問題來間接地求解原始問題。

  換言之,之所以從 min  max 的原始問題 p*,轉換為 max min 的對偶問題 d*,一者是因為 d* 是 p* 的近似解,二者轉換為對偶問題后,更容易求解。

  所以下面可以先求 L 對w, b的極小,再求 L 對 α  的極大。

   上面提到了 d* <= p* 在滿足某些條件的情況下,二者等價,而要讓兩者等價需滿足 strong  duality(強對偶),而後有學者在強對偶下提出了KKT條件,且KKT條件的成立要滿足 constraint qualifications,而 constraint qualifications 之一就是  Slater條件。所謂的Slater條件,即指:凸優化問題,如果存在一個點 x,使得所有等式約束都成立,並且所有不等式約束都嚴格成立(即取嚴格不等號,而非等號),則滿足Slater條件。對於此處,Slater條件成立,所以 d* <= p* 可以取等號。

  一般的,一個最優化數學模型能夠表示成下列標準形式:

  其中f(x)是需要最小化的函數,h(x)是等式約束,g(x)是不等式約束,p和q分別為等式約束和不等式約束的數量。

  同時,得明白以下兩點:

   而KKT的條件就是指上面最優化數學模型的標準形式中的最小點 x* 必須滿足下面的條件:

   經過論證,我們這裏的問題是滿足KKT條件的(首先已經滿足Slater條件,再者 f 和 gi 也都是可微的,即 L 對 w 和 b 都可導),因此現在我們便轉換為求解第二個問題。

  也就是說原始問題通過滿足KKT條件,已經轉換成了對偶問題。而求解這個對偶學習問題,分為三個步驟:

  • 1,要讓 L(w, b, a) 關於 w 和 b 最小化,
  • 2,求 對 α 的極大
  • 3,利用SMO算法求解對偶問題中的拉格朗日乘子

6.3 對偶問題求解的三個步驟

  根據拉格朗日的對偶性,原始問題的對偶問題是極大極小問題,所以我們求解對偶問題的步驟如下:

  (1)首先固定 α,要讓 L 關於 w 和 b 最小化,我們分別對 w, b 求偏導數,即令  ∂L / ∂w 和 ∂L / ∂b 等於零:

  將求偏導數的結果,代入下式:

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

透過選單樣式的調整、圖片的縮放比例、文字的放大及段落的排版對應來給使用者最佳的瀏覽體驗,所以不用擔心有手機版網站兩個後台的問題,而視覺效果也是透過我們前端設計師優秀的空間比例設計,不會因為畫面變大變小而影響到整體視覺的美感。

  得到:

   推導過程如下:

   最後,得到:

   上面推導過程中,“倒數第4步” 推導到  “倒數第3步” 使用了線性代數的轉置運算,由於 ai 和 yi 都是實數,因此轉置后與自身一樣。“倒數第3步” 推導到“倒數第2步” 使用了(a + b + c + …)(a + b + c + …) = aa + ab + ac +ba +bb + bc + … 的乘法法則。最後一步是上一步的順序調整。

  從上面的最後一個式子,我們可以看出,此時的拉格朗日函數只包含一個變量,那就是 αi 便能求出 w 和 b,由此可見,上面提出來的核心問題:分類函數  f(x) = wTx + b 也可以輕而易舉的求出來。

  (2)對 α 的極大,即是關於對偶問題的最優化問題。經過上面第一個步驟的求 w 和 b,得到的拉格朗日函數式子已經沒有變量 w,  b ,只有 α 。從上面的式子得到:

   這樣,求出了 αi ,根據上面對 w 求偏導的式子,我們可以求出 w。然後通過下面式子,可以求出b,最終得到分離超平面和分類決策函數:

   (3)在求得 L(w,  b,  a) 關於 w 和 b 最小化,以及對  α 的極大值后,最後一步則可以利用 SMO 算法求解對偶問題中的拉格朗日乘子  α。

  我們需要構造並求解對偶約束最優化問題

   上述式子要解決的是在參數 {α1, α2, α3,…..αn} 上求最大值 W的問題,至於 x(i) 和 y(i) 都是已知數。要了解這個SMO算法如何推導,後面繼續學。

6.4 拉格朗日乘子法求解SVM

  這裏繼續使用老師的PPT對SVM的推導做一個梳理,其實就是整理上面6.2  6.3 小節的內容。

7,從線性不可分到非線性問題

  為了過渡到非線性分類情況,我們先看看上述推導過程中得到的一些有趣的形式。

  首先就是關於我們的 hyper plane,對於一個數據點 x 進行分類,實際上是通過把 x 代入到   f(x) = wTx + b 算出結果然後根據其正負號來進行類別劃分的。而前面的推導,我們得出:

  因此分類函數為:

   這裏的形式的有趣之處在於,對於新點 x 的預測,只需要計算它與訓練數據點的內積即可( < ·,  ·>表示向量內積),這一點直觀重要,是之後使用Kernel進行非線性推廣的基本前提。此外,所謂 Supporting  Vector 也在這裏显示出來——事實上,所謂非 Supporting Vector所對應的係數 α 都是等於零的,因此對於新點的內積計算實際上只需要針對少量的“支持向量” 而不是所有的訓練數據即可。

  為什麼非支持向量對應的  α 等於零呢?直觀上來看理解的話,就是這些“後方”的點——正如我們之前分析過一樣,對超平面是沒有影響的,由於分類完全由超平面決定,所以這些無關的點並不會參与分類問題的計算,因而也就不會產生任何影響了。

  首先,我們看看通過Lagrange multiplier 得到的目標函數:

   注意到如果 xi 是支持向量的話,上式中紅顏色的部分是等於0 的(因為支持向量的 functional margin 等於1),而對非支持向量來說,functional margin 會大於 1,因此紅顏色部分是大於零的,而 αi 又是非負的,為了滿足最大化,  αi 必須等於0。這也就是這些非 supporting vector 的局限性。

   至此,我們便得到了一個 maximum  margin hyper plane classifier,這就是所謂的支持向量機(Support Vector Machine)。當然,,到目前為止,我們的SVM還比較弱,只能處理線性的情況,不過,在得到了對偶 dual 形式之後,通過 Kernel推廣到非線性的情況就變成了一件非常容易的事情了(我們之前說過:通過求解對偶問題得到最優解,這就是線性可分條件下支持向量機的對偶算法,這樣做的優點在於:一者對偶問題往往更容易求解;二者可以自然的引入核函數,進而推廣到非線性分類問題)。

8,從線性不可分到特徵空間映射與核函數問題——線性不可分支持向量機

8.1 線性不可分

  線性不可分定義如下圖:

   對於上圖的二分類數據點,普通線性分類器不行,最大間隔超平面和軟間隔超平面也無能為力。面對這樣的線性不可分問題,通常的思路是找一個非線性分類邊界(比如組合分類器)來實現分類,而SVM則另闢蹊徑,將數據點從原始空間映射到特徵空間,而數據在特徵空間往往就能實現線性可分。

8.2 特徵空間映射

  對於非線性問題,線性可分支持向量機並不能有效解決,要使用非線性模型才能很好的分類。

  • 一維空間向二維空間映射

  下面我們考慮一維空間的二分類問題:

   我們將它進行一個二次變換,換到二維空間,這裏的變換為 x -> x2

   從上面的例子,我們知道變換的核心思想就是:將原始輸入空間的數據集映射到高維特徵空間中,從而使得數據集可分

  • 二維空間向二維特徵空間映射

  上圖中二維空間不可分,但是變換一下坐標空間,也能實現線性可分。

  • 二維空間向三維空間的映射

 

  二維空間的數據點不僅可以映射到二維空間,同樣也可以映射到三維,如上所示,所以同樣可以找到一個超平面能夠實現在三維空間的線性可分。

   首先將原始的輸入特徵通過函數 h(xi) 映射到高維空間,拉格朗日對偶有如下形式:

   決策函數為:

8.3  核函數

  原始空間向特徵空間的映射需要藉助映射函數 Ψ(x)。例如對於數據點 xi,映射到特徵空間就變成了Ψ(xi)。而SVM的一大巧妙之處就是映射后的特徵空間數據點內積的計算等價於低維空間數據點在映射函數對應的核函數中計算。這大大降低了運算量,因為有的時候高維空間的計算很複雜,下圖是一個將 m 維度向量的映射到特徵空間的映射函數的例子:

  映射后的維度大概是 m^2 / 2 維,則計算映射后的數據內積 Ψ(xi)T*Ψ(xj) 的時間複雜度為 O(n^2)。而如果使用核函數,則計算複雜度會降低為 O(n),例如:

   其中 a,b為 m維的向量,Ψ(a) 為 a 經過上面的映射函數后的 m^2 / 2維向量,可以看出核函數 K(a, b) = (aT*b + 1)2 計算的時間複雜度為 O(n)。

   核函數是指:設從輸入空間 Χ 到特徵空間 H的一個映射 h,對任意 x, z 屬於 X,函數 K(x,  z) 滿足 K(x,  z) = (h(x),  h(z)),則稱 K 為核函數。

  具體的核函數,我們下一節完整介紹,這裏不再贅述。

  核技巧:在學習與預測中只定義核函數,而不显示的定義映射函數 h。

   通常,直接計算 K(x, z)比較容易,而通過 h(x) 和 h(z) 計算 K(x, z)並不容易。

  而成為核函數的充要條件:設 K 是對稱函數,則 K(x, z)為核函數的充要條件是對輸入空間中任意xi,i=1,2,….m,Gram矩陣K = [K(xi, xj)] m * m 是半正定矩陣。

  對偶問題的目標函數:

   決策函數的形式:

   對SVM從簡到難的介紹就到這裏,然後下一篇文章主要學習核函數的問題,下下一篇對使用Sklearn實現SVM進行了解,最後我們證明一下SVM。這個系列就算結束。可能這一個多月的整理,我不是百分百理解它,但是我相信這是我理解它的開始。

9,從線性不可分到軟間隔問題——使用鬆弛變量處理 outliers(軟間隔支持向量機)

  軟間隔(soft-margin):有時候數據中有一些噪音點,如果我們考慮他們,那麼我們的分割超平面就不太好了。

  在之前討論支持向量機的時候,我們就假定,數據是線性可分的,即我們可以找到一個可行的超平面將數據完全分開。後來為了處理非線性數據,在下文使用 Kernel 方法對原來的線性 SVM 進行了推廣,使得非線性的情況也能處理。雖然通過映射 Φ(•) 將原始數據映射到高維空間之後,能夠線性分隔的概率大大增加,但是對於某些情況還是很難處理。

  例如可能並不是因為數據本身是非線性結構的,而只是因為數據有噪音。對於這種偏離正常位置很遠的數據點,我們稱之為 outlier,在我們原來的SVM模型里,outlier的存在有可能造成很大的影響,因為超平面本身就是只有少數幾個 support vector 組成的,如果這些 support vector里又存在 outliers 的話,其影響就很大了。例如下圖:

  用黑圈圈起來的那個藍點是一個 outlier,它偏離了自己原本應該在的那個半空間,如果直接忽略掉它的話,原來的分割超平面還是挺好的,但是由於這個 outlier 的出現,導致分割超平面不得不被擠歪了,變成途中黑色虛線所示(這隻是一個示意圖,並沒有嚴格計算精確坐標),同時 margin 也相應變小了。當然,更嚴重的情況是,如果這個 outlier 再往右上移動一些距離的話,我們將無法構造出能將數據分開的超平面來。

  為了處理這種情況,SVM 允許數據點在一定程度上偏離一下超平面。例如上圖中,黑色實線所對應的距離,就是該 outlier 偏離的距離,如果把它移動回來,就剛好落在原來的超平面藍色間隔邊界上,而不會使得超平面發生變形了。

  也就是說,在有鬆弛的情況下 outline 點也屬於支持向量SV,同時,對於不同的支持向量,拉格朗日參數的值也不同,如此篇論文 《Large  Scale Machine Learning》中的下圖所示:

  對於遠離分類平面的點值為0;對於邊緣上的點值在 [0, 1/L],其中,L為訓練數據集個數,即數據集大小;對於 outline  數據和內部的數據值為 1/L。

  OK,繼續回到問題,我們原來的約束條件為:

   現在考慮到 outlier 問題,約束條件變成了:

   其中,ξi >= 0 稱為鬆弛變量(slack  variable),對應數據點 xi 允許偏離的 functional  margin 的量。當然,如果我們運行 ξi 任意大的話,那任意的超平面都是符合條件的了。所以,我們在原來的目標函數後面加上一項,使得這些 ξi 的總和也要最小,即軟間隔支持向量機的學習問題如下(原始問題):

   其中 C是懲罰係數,用於控制目標函數中兩項(“尋找 margin 最大的超平面” 和 “保證數據點偏差量最小”)之間的權重。注意,其中 ξ 是需要優化的變量(之一),而 C 是一個事先確定好的常量C值大時對誤分類的懲罰增加(C趨於很大時,意味着分類嚴格不能有錯誤),C值小時對誤分類的懲罰減小(C趨於很小時,意味着可以有更大的錯誤容忍)。完整的寫出來是這個樣子:

  所以上式包含兩層含義,使  ||w||2/2 盡量小即間隔盡量大,同時使誤分類點的個數盡量小,C是調和兩者的係數,有了上式,就可以和線性可分支持向量機一樣考慮線性可分支持向量機一樣考慮線性支持向量機的學習過程,此時,線性不可分支持向量機的學習問題可以變為之前的凸二次規劃問題的求解。

   用之前的方法將限制或約束條件加入到目標函數中,得到新的拉格朗日函數,如下所示:

  約束如下:

   分析方法和前面一樣,轉換為另一個問題之後,解法類似,我們先讓 L 針對 w,  b 和 ξ 最小化:

   將 w 帶回 L 並化簡,得到和原來一樣的目標函數:

   不過由於我們得到 C – αi – ri = 0 而 又有 ri >= 0 (作為 Lagrange multiplier 的條件),因此有 αi <= C,所以整個 dual 問題現在寫作:

   把前後的結果對比一下:

   可以看到唯一的區別就是現在 dual  varibale α 多了一個上限 C。而 Kernel 化的非線性形式也是一樣的,只要把 <xi,  xj> 換成 k(xi,  xj)即可。這樣一來,一個完整的可以處理線性和非線性並能容忍噪音和 outliers 的支持向量機就介紹完畢了。

  所以可以做一個總結,不準確的說:SVM它本質上是一個分類方法,用 WT+b 定義分類函數,於是求 w,b為尋最大間隔,引出 1 / 2 || w || ^2,繼而引入拉格朗日因子,化為對拉格朗日乘子a 的求解(求解過程中會設計一系列的最優化或凸二次規劃等問題),如此,求 w.b 與 求 a 等價,而 a 的求解可以用一種快速學習算法 SMO,至於核函數,是為了處理非線性問題,若直接映射到高維計算恐維度爆炸,故在低維計算,等效高維表現。

10,支持向量機(SVM)的優缺點

  支持向量機(SVM)是一組用於分類,回歸和異常值檢測的監督學習方法。

10.1  支持向量機(SVM)的優點:

  • 在高維空間有效
  • 在維度數量大於樣本數量的情況下仍然有效
  • 在決策功能(稱為支持向量)中使用訓練點的子集,因此他也是內存有效的
  • 多功能:可以為決策功能指定不同的內核函數。提供通過內核,也可以指定自定義內核

10.2  支持向量機(SVM)的缺點:

  • 如果特徵數量遠遠大於樣本數量,則該方法可能會導致交叉的性能
  • 支持向量機不直接提供概率估計,這些是使用昂貴的五折交叉驗證計算的

 

知識儲備1:什麼叫凸優化?

  我們可以看到,上麵線性可分支持向量機模型的最優化問題如下:

   上面的基本型目標函數是二次的,約束條件是線性的,這是一個凸二次規劃問題。可以直接用現成的優化計算包求解。但若利用“對偶問題”來求解,會更高效。

  • 啥是凸?什麼是凸優化?

  凸優化說的是這樣一回事情:

   凸優化可以想象成給我一個凸函數,我們需要找最低點。

  • 為啥叫二次規劃問題呢?

  據了解。目標函數和約束條件都為變量的線性函數,叫做——線性規劃問題。目標函數為變量的二次函數和約束條件為變量的線性函數,叫做二次規劃問題。

   拉格朗日對偶性,即通過給每一個約束條件加上一個拉格朗日乘子。然後定義出拉格朗日函數,通過拉格朗日函數將約束條件融合進目標函數中。目的是,只需要通過一個目標函數包含約束條件,便可以解釋清楚問題。

知識儲備2:有約束最優化問題的數學模型

   SVM 問題是一個不等式約束條件下的優化問題。絕大多數模式識別教程在討論這個問題時都會加上優化算法的簡介。

2.1  有約束優化問題的幾何意向

  約束條件一般分為等式約束和不等式約束,前者表示為 g(x) = 0 ;後者表示為 g(x) <= 0、

  假設 x 屬於 Rd(就是這個向量一共有 d 個標量組成),則 g(x) = 0 則是由 d-1 維的超平面。那麼有約束優化問題就要求在這個 d-1 維的曲面或者超平面上找到能使得目標函數最小的點,這個 d-1 維的曲面就是“可行解區域”。

  對於不等式約束條件, g(x) <= 0 ,則可行解區域 d-1 維曲面擴展成 d 維空間的一個子集。我們可以從  d=2 的二維空間進行比對理解。等式約束對應的可行解空間就是一條線;不等式約束對應的則是這條線以及線的某一側對應的區域,就像下面的這幅圖(圖中的模板函數等高線其實就是等值線,在同一條等值線上的點對應的目標函數值相等)。

2.2  拉格朗日乘子法

  儘管上面我們已經想象出有約束優化問題的幾何意向。可是如何利用代數方法找到這個被約束了的最優解呢?這就需要用到拉格朗日乘子法。

  首先定義原始目標函數 f(x),拉格朗日乘子法的基本思想是把約束條件轉換為新的目標函數 L(x,  λ) 的一部分,從而使有約束優化問題變成我們習慣的無約束優化問題,那麼該如何去改造原來的目標函數 f(x),使得新的目標函數 L(x,  λ)  的最優解恰好在可行解區域中呢?這就需要我們去分析可行解區域的最優解的特點。

知識儲備3:KKT條件

   KKT條件是一個線性規劃問題能有最優解的充分和必要條件。

  對於不等式約束條件 g(x) <= 0 的情況,如下圖所示,最優解所在的位置 x* 有兩種可能,或者在邊界曲線 g(x)=0 上或者在可行解區域內部滿足不等式 g(x) < 0 的地方。

  第一種情況:最優解在邊界上,就相當於約束條件就是 g(x) = 0.參考下圖,注意此時的目標函數 f(x) 的最優解是在可行解區域外面,所以函數 f(x) 在最優解 x* 附加的變化趨勢是“在可行解區域內側較大而在區域外側較小”,與之對應的是函數 g(x) 在可行解區域內小於 0 ,在區域外大於零,所以在最優解 x* 附加的變換趨勢是內部較小而外部較大。這意味着目標函數 f(x) 的梯度方向與約束條件函數 g(x) 的梯度方向相反。因此根據下式,可以推斷出參數 λ > 0。

   一般的,一個最優化數學模型可以表示成如下形式:

   h(x) 是等式約束,g(x)是不等式約束,p, q表示約束的數量。

  而這個最優化數學模型的最優解 x* 需滿足的條件(即KTT條件)為:

  可能上面這樣說,大家還是有點模糊,我代入SVM中來看。

  對於帶等式和不等式的約束問題,在最優點處必須滿足KKT條件,將KKT條件應用於SVM原問題的拉格朗日乘子函數,得到關於所有變量的方程,對於原問題中的兩組不等式約束,根據KKT條件必須滿足(和上面的一樣):

  對於第一個方程,第一種情況:如果 αi > 0,則必須有:

   即:

  而由於 ξi  >= 0,因此必定有:

   再來看第二種情況:如果αi = 0,則對

  的值沒有約束。由於有 αi + βi = C 的約束,因此 βi = C ,又因為 βiξi = 0 的限制,如果 βi >0,則必須有 ξi = 0。由於原問題中有約束條件:

  而由於 ξi = 0,因此有

  最後第三種情況:對於 αi > 0 的情況,我們又可以細分為 αi < C  αi = C 的情況。如果 αi < C  ,由於有 αi + βi = C 的約束,因此有 βi > 0;因為有 βiξi = 0 的約束,因此有 ξi = 0。不等式約束:

   變為:

   由於 0 <  αi < C 時,既要滿足:

  又要滿足:

   因此有:

   將三種情況合併起來,在最優點,所有樣本都必須滿足:

   上面第一種情況對應的是自由變量即非支持向量,第二種情況對應的是支持向量,第三種情況對應的是違反不等式約束的樣本。在SMO算法求解中,會應用此條件來選擇優化變量。

 

 

參考文獻:

https://zhuanlan.zhihu.com/p/31652569

https://zhuanlan.zhihu.com/p/57648645

http://www.360doc.com/content/18/0727/00/7669533_773495537.shtml

https://mp.weixin.qq.com/s/ZFWJUazMbAqeoSIkXjuG5g

https://blog.csdn.net/qq_35992440/article/details/80987664

https://zhuanlan.zhihu.com/p/24638007

https://blog.csdn.net/weixin_40170902/article/details/80113128

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

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

節能減碳愛地球是景泰電動車的理念,是創立景泰電動車行的初衷,滿意態度更是服務客戶的最高品質,我們的成長來自於你的推薦。

一文帶你了解js數據儲存及深複製(深拷貝)與淺複製(淺拷貝)_租車

※超省錢租車方案

商務出差、學生出遊、旅遊渡假、臨時用車!GO 神州租賃有限公司!合法經營、合法連鎖、合法租賃小客車!

背景

在日常開發中,偶爾會遇到需要複製對象的情況,需要進行對象的複製。

由於現在流行標題黨,所以,一文帶你了解js數據儲存及深複製(深拷貝)與淺複製(淺拷貝)

理解

首先就需要理解 js 中的數據類型了
js 數據類型包含

  1. 基礎類型:StringNumbernullundefinedBoolean以及ES6引入的Symboles10中的BigInt
  2. 引用類型:Object

由於 js 對變量的儲存是棧內存堆內存完成的。

  • 基礎類型將數據保存在棧內存
  • 引用類型將數據保存在堆內存

由於 js 在數據讀取和寫入的時候,對基礎類型是直接讀寫棧內存中的數據,引用類型是將一個內存地址保存在棧內存中,讀寫都是修改棧內存中指向堆內存的地址

以如下代碼為例

let obj = {
  a:1,
  arr:[1,3,5,7,9],
  b:2,
  c:{
    num:100
  }
}
let num = 10

在內存中的表現為

我們聲明個obj1

let obj1 = obj;
console.log(obj1 == obj);//true

因為這個賦值,把內存變成了這樣

然後,內存中只是給js棧內存新增了一個指向堆內存的地址而已,這種就叫做淺複製。因為如圖可以看到,如果我們修改obj.a的話,實際修改的是堆內存0x88888888中的變量a,由於obj1也指向這個地址,所以obj1.a也被修改了

深複製是指,不單單複製引用地址,連堆內存都複製一遍,使objobj1不指向同一個地址。

代碼

分開來看深複製淺複製

淺複製

由上述圖可知,淺複製只是複製了堆內存的引用地址,通常在業務需求中出現的淺複製是指複製引用對象的第一層,也就是,基本類型複製新值,引用類型複製引用地址

淺複製可以使用的方案有循環賦值擴展運算符object.assign(),

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

有別於一般網頁架設公司,除了模組化的架站軟體,我們的營業主軸還包含:資料庫程式開發、網站建置、網頁設計、電子商務專案開發、系統整合、APP設計建置、專業網路行銷。

let obj = {
  a:1,
  arr:[1,3,5,7,9],
  b:2,
  c:{
    num:100
  }
}
function clone1(obj){ // 使用循環賦值
  let b = {};
  for(let key in obj){
    b[key] = obj[key]
  }
  return b
}
function clone2(obj){ // 使用擴展運算符
  let b = {
    ...obj
  };
  return b
}
function clone3(obj){ // 使用object.assign()
  let b = {};
  Object.assign(b,obj)
  return b
}
let obj1 = clone1(obj);
let obj2 = clone2(obj);
let obj3 = clone3(obj);

console.log(obj1 === obj); //false 代表複製成功了
console.log(obj2 === obj); //false 代表複製成功了
console.log(obj3 === obj); //false 代表複製成功了
console.log('obj0.c.num修改前',obj.c.num); //100
console.log('obj1.c.num修改前',obj1.c.num); //100
console.log('obj2.c.num修改前',obj2.c.num); //100
console.log('obj3.c.num修改前',obj3.c.num); //100
obj0.c.num = 555;
console.log('obj0.c.num修改后',obj.c.num); //555
console.log('obj1.c.num修改后',obj1.c.num); //555
console.log('obj2.c.num修改后',obj2.c.num); //555
console.log('obj3.c.num修改后',obj3.c.num); //555

由於是淺複製,所以引用類型只是複製了內存地址,修改其中一個對象的子屬性后,引用這個地址的值都會被修改。

淺克隆圖解如下

深複製

由於淺複製只是複製第一層,為了解決引用類型的複製,需要使用深複製來完成對象的複製,基本類型複製新值,引用類型開闢新的堆內存

深複製可以使用的方案有JSON.parse(JSON.stringify(obj))循環賦值

JSON.parse(JSON.stringify(obj))

let obj = {
  a:1,
  arr:[1,3,5,7,9],
  c:{
    num:100
  },
  fn:function(){
     console.log(1)
  },
  date:new Date(),
  reg:/\.*/g
}
function clone1(obj){ // 使用JSON.parse(JSON.stringify(obj))
  return JSON.parse(JSON.stringify(obj))
}
let obj1 = clone1(obj);
console.log(obj === obj1); //false 代表複製成功了
obj.c.num = 555;

console.log(obj.c.num,obj1.c.num) // 555,100

看起來是複製成功了!!~地址也變了,修改obj,obj1的引用地址不會跟着變化。

但是我們來console一下obj以及obj1

console.log(obj)
console.log(obj1)

似乎發現了離奇的事情,只有obj.a以及obj.c正確的複製了,日期類型方法正則表達式均沒有複製成功,發生了一些奇怪的事情

循環賦值 deepClone

那麼為了解決這種事情,就需要寫一個deepClone方法來完成深複製了,參考了許多開源庫的寫法,將所有的複製項單獨拆出,方便未來對特殊類型進行擴展,也防止不同功能間的變量互相干擾

 //既然是深複製,一定要傳入一個object,再return 一個新的 Object
function deepClone(obj){
    let newObj;
    if(obj instanceof Array){ // 數組的話,要new一個數組
      newObj = []
    }else if(obj instanceof Object){  // 對象的話,要new一個對象
      newObj = {}
    }
    if(obj === null) {
      return cloneNull(obj)
    }
    if(typeof obj=='function'){
        return cloneFunction(obj)
    }
    if(typeof obj!='object') {
        return cloneOther(obj)
    }
    if(obj instanceof RegExp) {
        return cloneRegExp(obj)
    }
    if(obj instanceof Date){
        return cloneDate(obj)
    }
    if(obj instanceof Array){
        for(let index in obj){
            newObj[index] = deepClone(obj[index]); // 對數組子項進行複製
        }
    }
    if(obj instanceof Object){
        for(let key in obj){
            newObj[key] = deepClone(obj[key]); // 對對象子項進行複製
        }
    }
    return newObj;
}
function cloneNull(obj){ // 複製NULL
  return obj
}
function cloneFunction(obj){ // 複製方法,
  // 複製一個新方法,將原方法轉成字符串,並new一個新的function
  return new Function('return '+obj.toString())()
}
function cloneOther(obj){ // 複製非對象的數據
  return obj
}
function cloneRegExp(obj){ // 複製正則對象
  return new RegExp(obj)
}
function cloneDate(obj){ // 複製日期對象
  return new Date(obj)
}

這樣一個基本上滿足功能的深複製就完成了。先測試一下

let obj = {
  a:1,
  arr:[1,3,5,7,9],
  c:{
    num:100
  },
  fn:function(){
     console.log(1)
  },
  date:new Date(),
  reg:/\.*/g
}

let obj1 = deepClone(obj);
console.log(obj.c === obj1.c); // false  代表複製成功
console.log(obj.fn === obj1.fn);// false  代表複製成功 
console.log(obj.date === obj1.date);// false  代表複製成功
console.log(obj.reg === obj1.reg);// false  代表複製成功

console一下

console.log(obj)
console.log(obj1)

這樣,就完成了deepClone深複製方法

經過深複製后,圖解如下

優化 deepClone

上述代碼還有優化空間,參考了lodash庫,在進行 new 對象時,可以使用 constructor構造函數 來進行創建新的實例,這樣

  1. 可以不用判斷遞歸中,是數組還是對象
  2. 如果深複製的某一項是某個原型的實例,深複製完成后,依然是該原型的實例
function deepClone(obj){
    let newObj = new obj.constructor;
    if(obj === null) {
      return cloneNull(obj)
    }
    if(typeof obj=='function'){
        return cloneFunction(obj)
    }
    if(typeof obj!='object') {
        return cloneOther(obj)
    }
    if(obj instanceof RegExp) {
        return cloneRegExp(obj)
    }
    if(obj instanceof Date){
        return cloneDate(obj)
    }
    if(obj instanceof Array){
        for(let index in obj){
            newObj[index] = deepClone(obj[index]); // 對數組子項進行複製
        }
    }
    if(obj instanceof Object){
        for(let key in obj){
            newObj[key] = deepClone(obj[key]); // 對對象子項進行複製
        }
    }
    return newObj;
}
function cloneNull(obj){ // 複製NULL
  return obj
}
function cloneFunction(obj){ // 複製方法,
  // 複製一個新方法,將原方法轉成字符串,並new一個新的function
  return new Function('return '+obj.toString())()
}
function cloneOther(obj){ // 複製非對象的數據
  return obj
}
function cloneRegExp(obj){ // 複製正則對象
  return new RegExp(obj)
}
function cloneDate(obj){ // 複製日期對象
  return new Date(obj)
}

最終版本 deepClone

然後可以有一個合併版本的,比較節省代碼,將下方區分開的複製方法,合併到deepClone中,可以極大地減少代碼體積

function deepClone(obj){ //
    let newObj = new obj.constructor;
    if(obj === null) return obj
    if(typeof obj=='function') return new Function('return '+obj.toString())()
    if(typeof obj!='object') return obj
    if(obj instanceof RegExp) return new RegExp(obj)
    if(obj instanceof Date) return new Date(obj)
    // 運行到這裏,基本上只存在數組和對象兩種類型了
    for(let index in obj){
        newObj[index] = deepClone(obj[index]); // 對子項進行遞歸複製
    }
    return newObj;
}

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

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

日本、大陸,發現這些先進的國家已經早就讓電動車優先上路,而且先進國家空氣品質相當好,電動車節能減碳可以減少空污

MyBatis緩存特性詳解_包裝設計

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

窩窩觸角包含自媒體、自有平台及其他國家營銷業務等,多角化經營並具有國際觀的永續理念。

緩存簡介

一般我們在系統中使用緩存技術是為了提升數據查詢的效率。當我們從數據庫中查詢到一批數據后將其放入到混存中(簡單理解就是一塊內存區域),下次再查詢相同數據的時候就直接從緩存中獲取數據就行了。這樣少了一步和數據庫的交互,可以提升查詢的效率。

但是一個硬幣都具有兩面性,緩存在帶來性能提升的同時也“悄悄”引入了很多問題,比如緩存同步、緩存失效、緩存雪崩等等。當然這些問題不是本文討論的重點。

本文主要討論MyBatis緩存這個比較雞肋的功能。雖然說MyBatis的緩存功能比較雞肋,但是為了全面了解MyBatis這個框架,學習下緩存這個功能還是挺有必要的。MyBatis的緩存分為一級緩存和二級緩存,下面就分別來介紹下這兩個特性。

一級緩存

在應用運行過程中,我們有可能在一次數據庫會話中,執行多次查詢條件完全相同的SQL,MyBatis提供了一級緩存的方案優化這部分場景,如果是相同的SQL語句,會優先命中一級緩存,避免直接對數據庫進行查詢,提高性能。

什麼是MyBatis一級緩存

一級緩存是 SqlSession級別 的緩存。在操作數據庫時需要構造 sqlSession 對象,在對象中有一個(內存區域)數據結構(HashMap)用於存儲緩存數據。不同的 sqlSession 之間的緩存數據區域(HashMap)是互相不影響的。

在應用運行過程中,我們有可能在一次數據庫會話中,執行多次查詢條件完全相同的SQL,MyBatis 提供了一級緩存的方案優化這部分場景,如果是相同的SQL語句,會優先命中一級緩存,避免直接對數據庫進行查詢,提高性能。

怎麼開啟一級緩存

MyBatis中一級緩存默認是開啟的,不需要我們做額外的操作。

如果你需要關閉一級緩存的話,可以在Mapper映射文件中將flushCache屬性設置為true,這種做法只會針對單個SQL操作生效

<select id="selectByPrimaryKey" parameterType="java.lang.String" resultMap="BaseResultMap" flushCache="true">
    select 
    <include refid="Base_Column_List" />
    from cbondissuer
    where OBJECT_ID = #{objectId,jdbcType=VARCHAR}
  </select>
> 還有一種做法是在MyBatis的主配置文件中,關閉所有的一級緩存
> ```xml
>   默認是SESSION,也就是開啟一級緩存
>   <setting name="localCacheScope" value="STATEMENT"/>
> ```

下面我們來寫代碼驗證下MyBatis的一級緩存。

```java
String id = "123";
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
//同一個sqlSession創建的Mapper
CbondissuerMapper cbondissuerMapper10 = sqlSession1.getMapper(CbondissuerMapper.class);
CbondissuerMapper cbondissuerMapper11 = sqlSession1.getMapper(CbondissuerMapper.class);
//另外一個sqlSession創建的Mapper
CbondissuerMapper cbondissuerMapper20 = sqlSession2.getMapper(CbondissuerMapper.class);

//同一個Mapper,同樣的SQL查了兩次
Cbondissuer cbondissuer10 = cbondissuerMapper10.selectByPrimaryKey(id);
Cbondissuer cbondissuer101 = cbondissuerMapper10.selectByPrimaryKey(id);
//同一個sqlSession創建的Mapper,又查詢了一次同樣的SQL
Cbondissuer cbondissuer11 = cbondissuerMapper11.selectByPrimaryKey(id);
//不一樣的sqlSession創建的Mapper查詢了一次同樣的SQL
Cbondissuer cbondissuer20 = cbondissuerMapper20.selectByPrimaryKey(id);

System.out.println("cbondissuer10 equals cbondissuer101 :"+(cbondissuer10==cbondissuer101));
System.out.println("cbondissuer10 equals cbondissuer11 :"+(cbondissuer10==cbondissuer11));
System.out.println("cbondissuer10 equals cbondissuer21 :"+(cbondissuer10==cbondissuer20));

sqlSession1.close();
sqlSession2.close();
System.out.println("end...");

上面進行了四次查詢,如果你觀察日誌的話。會發現只進行了兩個數據庫查詢。因為第二和第三次的查詢都查詢了一級緩存,查出的其實是緩存中的結果。所以輸出的結果是

cbondissuer10 equals cbondissuer101 :true
cbondissuer10 equals cbondissuer11 :true
cbondissuer10 equals cbondissuer21 :false

哪些因素會使一級緩存失效

上面的一級緩存初探讓我們感受到了 MyBatis 中一級緩存的存在,那麼現在你或許就會有疑問了,那麼什麼時候緩存失效呢?

  • 通過同一個SqlSession執行更新操作時,這個更新操作不僅僅指代update操作,還指插入和刪除操作;
  • 事務提交時會刪除一級緩存;
  • 事務回滾時也會刪除一級緩存;

一級緩存源碼解析

其實MyBatis一級緩存的實質就是一個Executor的一個類似Map的屬性,分析源碼的方法就是看在哪些地方從這個Map中查詢了緩存,又是在哪些清空了這些緩存。

1. 查詢時使用緩存分析

public abstract class BaseExecutor implements Executor {

  private static final Log log = LogFactory.getLog(BaseExecutor.class);

  protected Transaction transaction;
  protected Executor wrapper;

  protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
  //這個localCache變量就是一級緩存變量
  protected PerpetualCache localCache;
  protected PerpetualCache localOutputParameterCache;
  protected Configuration configuration;
  //..省略下面代碼
}

全局搜索代碼中哪些地方使用了這個變量,很容易找到BaseExecutor.query方法使用了這個緩存:

public abstract class BaseExecutor implements Executor {

// 省略其他代碼
 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      //先從緩存中查詢結果,如果緩存中已經存在結果直接使用緩存的結果
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        //緩存中沒有結果從數據庫查詢
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }
  //..省略下面代碼
}

上面的代碼展示了,BaseExecutor的query方法使用緩存的過程。需要注意的是查詢緩存時是根據cacheKey進行查詢的,我們可以將這個key簡單的
理解為sql語句,不同的sql語句能查出不同的緩存。(注意sql語句中的參數不同也會被認為是不同的sql語句)。

2. 導致一級緩存失效的代碼分析
查看BaseExecutor的代碼,我們很容易發現是下面的方法清空了一級緩存。(不要問我是怎麼發現這個代碼的,看代碼能力需要自己慢慢提升)

@Override
public void clearLocalCache() {
    if (!closed) {
        localCache.clear();
        localOutputParameterCache.clear();
    }
}

那麼我們只要查看哪些地方調用了這個方法就知道哪些情況下會導致一級緩存失效了。跟蹤下來,最後發現下面三處地方會使得一級緩存失效

BaseExecutor的update方法,使用MyBatis的接口進行增、刪、改操作都會調用到這個方法,這個也印證了上面的說法。

@Override
  public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    clearLocalCache();
    return doUpdate(ms, parameter);
  }

BaseExecutor的commit方法,事務提交會導致一級緩存失敗。如果我們使用Spring的話,一般事務都是自動提交的,所以好像MyBatis的一級緩存一直沒怎麼被考慮過

@Override
  public void commit(boolean required) throws SQLException {
    if (closed) {
      throw new ExecutorException("Cannot commit, transaction is already closed");
    }
    clearLocalCache();
    flushStatements();
    if (required) {
      transaction.commit();
    }
  }

BaseExecutor的rollback方法,事務回滾也會導致一級緩存失效。

@Override
  public void rollback(boolean required) throws SQLException {
    if (!closed) {
      try {
        clearLocalCache();
        flushStatements(true);
      } finally {
        if (required) {
          transaction.rollback();
        }
      }
    }
  }

一級緩存使用建議

平時使用MyBatis時都是和Spring結合使用的,在整個Spring容器中一般只有一個SqlSession實現類。而Spring一般都是主動提交事務的,所以說一級緩存經常失效。

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

網動廣告出品的網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上她。

還有就是我們也很少在一個事務範圍內執行同一個SQL兩遍,上面的這些原因導致我們在開發過程中很少注意到MyBatis一級緩存的存在。

不怎麼用並不是說不用,作為一個合格的開發者需要對這些心知肚明,要清楚的知道MyBatis一級緩存的工作流程。

二級緩存

什麼是MyBatis二級緩存

MyBatis 一級緩存最大的共享範圍就是一個SqlSession內部,那麼如果多個 SqlSession 需要共享緩存,則需要開啟二級緩存,開啟二級緩存后,會使用 CachingExecutor 裝飾 Executor,
進入一級緩存的查詢流程前,先在CachingExecutor 進行二級緩存的查詢,具體的工作流程如下所示:

當二級緩存開啟后,同一個命名空間(namespace) 所有的操作語句,都影響着一個 共同的 cache(一個Mapper映射文件對應一個Cache),也就是二級緩存被多個 SqlSession 共享,是一個全局的變量。當開啟緩存后,數據的查詢執行的流程就是 二級緩存 -> 一級緩存 -> 數據庫。

從上面的圖可以看出,MyBatis的二級緩存實現可以有很多種,可以是MemCache、Ehcache等。也可以是Redis等,但是需要額外的Jar包。

怎麼開啟二級緩存

二級緩存默認是不開啟的,需要手動開啟二級緩存,實現二級緩存的時候,MyBatis要求返回的POJO必須是可序列化的。開啟二級緩存的條件也是比較簡單,

step1:通過直接在 MyBatis 配置文件中通過

<settings>  
	<setting name = "cacheEnabled" value = "true" />
</settings>

step2: 在 Mapper 的xml 配置文件中加入 標籤

cache標籤下面有下面幾種可選項

  • eviction: 緩存回收策略,支持的策略有下面幾種

    • LRU – 最近最少回收,移除最長時間不被使用的對象(默認是這個策略)
    • FIFO – 先進先出,按照緩存進入的順序來移除它們
    • SOFT – 軟引用,移除基於垃圾回收器狀態和軟引用規則的對象
    • WEAK – 弱引用,更积極的移除基於垃圾收集器和弱引用規則的對象
  • flushinterval:緩存刷新間隔,緩存多長時間刷新一次,默認不清空,設置一個毫秒值;

  • readOnly: 是否只讀;true 只讀 ,MyBatis 認為所有從緩存中獲取數據的操作都是只讀操作,不會修改數據。MyBatis 為了加快獲取數據,直接就會將數據在緩存中的引用交給用戶。不安全,速度快。讀寫(默認):MyBatis 覺得數據可能會被修改

  • size : 緩存存放多少個元素

  • type: 指定自定義緩存的全類名(實現Cache 接口即可)

  • blocking:若緩存中找不到對應的key,是否會一直blocking,直到有對應的數據進入緩存。

cache-ref代表引用別的命名空間的Cache配置,兩個命名空間的操作使用的是同一個Cache。

哪些因素會使二級緩存失效

從上面的介紹可以知道MyBatis的二級緩存主要是為了SqlSession之間共享緩存設計的。但是我們平時開發過程中都是結合Spring來進行MyBatis的開發。在Spring環境下一般也只有一個SqlSession實例,所以二級緩存使用到的機會不多。所以下面就簡單描述下Mybatis的二級緩存。

還是以上面的列子為列

String id = "{0003CCCA-AEA9-4A1E-A3CC-06D884BA3906}";
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
//同一個sqlSession創建的Mapper
CbondissuerMapper cbondissuerMapper10 = sqlSession1.getMapper(CbondissuerMapper.class);
CbondissuerMapper cbondissuerMapper11 = sqlSession1.getMapper(CbondissuerMapper.class);
//另外一個sqlSession創建的Mapper
CbondissuerMapper cbondissuerMapper20 = sqlSession2.getMapper(CbondissuerMapper.class);

//同一個Mapper,同樣的SQL查了兩次
Cbondissuer cbondissuer10 = cbondissuerMapper10.selectByPrimaryKey(id);
Cbondissuer cbondissuer101 = cbondissuerMapper10.selectByPrimaryKey(id);
//同一個sqlSession創建的Mapper,又查詢了一次同樣的SQL
Cbondissuer cbondissuer11 = cbondissuerMapper11.selectByPrimaryKey(id);
//這邊需要提交事務才能讓二級緩存生效
sqlSession1.commit();
//不一樣的sqlSession創建的Mapper查詢了一次同樣的SQL
Cbondissuer cbondissuer20 = cbondissuerMapper20.selectByPrimaryKey(id);

System.out.println("cbondissuer10 equals cbondissuer101 :"+(cbondissuer10==cbondissuer101));
System.out.println("cbondissuer10 equals cbondissuer11 :"+(cbondissuer10==cbondissuer11));
System.out.println("cbondissuer10 equals cbondissuer21 :"+(cbondissuer10==cbondissuer20));
  • 二級緩存是以namespace(Mapper)為單位的,不同namespace下的操作互不影響。
  • insert,update,delete操作會清空所在namespace下的全部緩存。
  • 多表操作一定不要使用二級緩存,因為多表操作進行更新操作,一定會產生臟數據。

二級緩存使用建議

個人覺得MyBatis的二級緩存實用性不是很大。一個原因就是Spring環境下,一本只有一個SqlSession,不存在sqlSession之間共享緩存;還有就是
MyBatis的緩存都不能做到分佈式,所以對於MyBatis的二級緩存以了解為主。

簡單總結

一級緩存

  • 一級緩存的本質是Executor的一個類似Map的屬性;
  • 一級緩存默認開啟,將flushCache設置成true或者將全局配置localCacheScope設置成Statement可以關閉一級緩存;
  • 在一級緩存開啟的情況下,查詢操作會先查詢一級緩存,再查詢數據庫;
  • 增刪改操作和事務提交回滾操作會導致一級緩存失效;
  • 由於Spring中事務是自動提交的,因此Spring下的MyBatis一級緩存經常失效。(但是並不表示不生效,除非你手動關閉一級緩存)
  • 不能實現分佈式。

二級緩存

  • namesapce級別的緩存(Mapper級別或者叫做表級別的緩存),設計的主要目的是實現sqlSession之間的緩存共享;
  • 開啟二級緩存后,查詢的邏輯是二級緩存->已經緩存->數據庫;
  • insert,update,delete操作會清空所在namespace下的全部緩存;
  • 多表查詢一定不要使用二級緩存,因為多表操作進行更新操作,可能會產生臟數據。

總體來說,MyBatis的緩存功能比較雞肋。想要使用緩存的話還是建議使用spring-cache等框架。

參考

  • https://blog.csdn.net/zb313982521/article/details/79689169
  • https://mp.weixin.qq.com/s?__biz=MzI4NDY5Mjc1Mg==&mid=2247489120&idx=2&sn=4694c4a359849d17354f85206768c25b&chksm=ebf6ce1fdc81470918515ff76c41d7aea9434226ef05e930fec59ed22dcc709030a6683c0d80&mpshare=1&scene=1&srcid=&sharer_sharetime=1566873637232&sharer_shareid=2040c1b4c62e1f430c804ebd0fe79fa3#rd

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

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

上新台中搬家公司提供您一套專業有效率且人性化的辦公室搬遷、公司行號搬家及工廠遷廠的搬家服務

Java 異常(一) 異常概述及其架構_台中搬家

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

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

Java 異常(一) 異常概述及其架構

一、異常概述

(一)、概述

Java異常是Java提供的一種識別及響應錯誤的一致性機制。異常指的是程序在執行過程中,出現的非正常的情況,最終會導致JVM的非正常停止。
Java異常機制可以使程序中異常處理代碼和正常業務代碼分離,保證程序代碼更加優雅,並提高程序健壯性。在有效使用異常的情況下,異常能清晰的回答 what, where, why 這3個問題:異常類型回答了“什麼”被拋出,異常堆棧跟蹤回答了“在哪“拋出,異常信息回答了“為什麼“會拋出。

(二)、異常體系

異常機制其實是幫助我們找到程序中的問題,異常的根類是java.lang.Throwable,其下有兩個子類:java.lang.Errorjava.lang.Exception,平常所說的異常指java.lang.Exception

 

 Throwable: 有兩個重要的子類:Exception(異常)和 Error(錯誤),二者都是 Java 異常處理的重要子類,各自都包含大量子類。異常和錯誤的區別是:異常能被程序本身可以處理,錯誤是無法處理。

1、Throwable:Throwable是 Java 語言中所有錯誤或異常的超類。Throwable包含兩個子類: Error 和 Exception。它們通常用於指示發生了異常情況。

Throwable包含了其線程創建時線程執行堆棧的快照,它提供了printStackTrace()等接口用於獲取堆棧跟蹤數據等信息。

Throwable常用API:

public void printStackTrace() // 打印異常的詳細信息。包含了異常的類型,異常的原因,還包括異常出現的位置。
public String getMessage() // 獲取發生異常的原因。
public String toString() // 獲取異常的類型和異常描述信息。

2、Error:嚴重錯誤Error,無法通過處理的錯誤,只能事先避免。

3、Exception:表示異常,異常產生后程序員可以通過代碼的方式糾正,使程序繼續運行,是必須要處理的。

(三)、異常分類

我們平常說的異常就是指Exception,因為這類異常一旦出現,我們就要對代碼進行更正,修復程序。

異常主要分為兩大類:編譯期異常和運行期異常。

編譯期異常:checked異常。在編譯期,就會檢查,如果沒有處理異常,則編譯失敗(如日期格式化異常)。

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

台中搬家公司推薦超過30年經驗,首選台中大展搬家

特點: Java編譯器會檢查它。此類異常,要麼通過throws進行聲明拋出,要麼通過try-catch進行捕獲處理,否則不能通過編譯。例如,CloneNotSupportedException就屬於被檢查異常。當通過clone()接口去克隆一個對象,而該對象對應的類沒有實現Cloneable接口,就會拋出CloneNotSupportedException異常。被檢查異常通常都是可以恢復的。

運行期異常:runtime異常。在運行時期,檢查異常.在編譯時期,運行異常不會編譯器檢測(不報錯)(如數學異常)。

特點: Java編譯器不會檢查它。也就是說,當程序中可能出現這類異常時,倘若既”沒有通過throws聲明拋出它”,也”沒有用try-catch語句捕獲它”,還是會編譯通過。例如,除數為零時產生的ArithmeticException異常,數組越界時產生的IndexOutOfBoundsException異常,fail-fail機制產生的ConcurrentModificationException異常等,都屬於運行時異常。

雖然Java編譯器不會檢查運行時異常,但是我們也可以通過throws進行聲明拋出,也可以通過try-catch對它進行捕獲處理。

如果產生運行時異常,則需要通過修改代碼來進行避免。例如,若會發生除數為零的情況,則需要通過代碼避免該情況的發生!

二、異常處理

Java異常處理機制用到的幾個關鍵字:try、catch、finally、throw、throws

  • try:用於監聽。將要被監聽的代碼(可能拋出異常的代碼)放在try語句塊之內,當try語句塊內發生異常時,異常就被拋出。
  • catch:用於捕獲異常。catch用來捕獲try語句塊中發生的異常。
  • finally:finally語句塊總是會被執行。它主要用於回收在try塊里打開的物力資源(如數據庫連接、網絡連接和磁盤文件)。只有finally塊,執行完成之後,才會回來執行try或者catch塊中的return或者throw語句,如果finally中使用了return或者throw等終止方法的語句,則就不會跳回執行,直接停止。
  • throw:用於拋出異常。
  • throws:用在方法簽名中,用於聲明該方法可能拋出的異常。

三、異常注意點

(一)、finally中的異常會覆蓋(消滅)前面try或者catch中的異常

  • 不要在fianlly中使用return。
  • 不要在finally中拋出異常。
  • 減輕finally的任務,不要在finally中做一些其它的事情,finally塊僅僅用來釋放資源是最合適的。
  • 將盡量將所有的return寫在函數的最後面,而不是try … catch … finally中。

(二)、多個異常使用捕獲

  • 多個異常分別處理。
  • 多個異常一次捕獲,多次處理。
  • 多個異常一次捕獲一次處理。

一般情況下,一般我們是使用一次捕獲多次處理方式,如下代碼

try{
     // 編寫可能會出現異常的代碼
}catch(異常類型A  e){  當try中出現A類型異常,就用該catch來捕獲.
     // 處理異常的代碼
     //記錄日誌/打印異常信息/繼續拋出異常
}catch(異常類型B  e){  當try中出現B類型異常,就用該catch來捕獲.
     // 處理異常的代碼
     //記錄日誌/打印異常信息/繼續拋出異常
}

注意:這種異常處理方式,要求多個catch中的異常不能相同,並且若catch中的多個異常之間有子父類異常的關係,那麼子類異常要求在上面的catch處理,父類異常在下面的catch處理。

三、實例

(一)、try – catch用法

try – catch 必須搭配使用,不能單獨使用。

public class ExceptionDemo {
    public static void main(String[] args) {
        try {
            int i = 10 / 0;// 除數不能為0,此行會拋出 ArithmeticException 異常
            System.out.println("i = " + i);// 拋出異常后,此行不會執行
        }catch(ArithmeticException e) { // 捕獲 ArithmeticException 異常
            // 在catch 代碼塊處理異常
            e.printStackTrace(); // 異常最詳細信息
            System.out.println("e.getMessage() : " + e.getMessage());// 發生異常的原因
            System.out.println("e.toString() : " + e.toString());  // 獲取異常的類型和異常描述信息
        }
    }
}

(二)、finally 用法

try – catch – finally搭配使用,或者 try – finally 搭配使用。

public class ExceptionDemo {
    public static void main(String[] args) {
        // try-catch-finally搭配使用
        try {
            int[] arr = {1,2,3};
            int i = arr[3];// 數組索引越界,此行會拋出 ArrayIndexOutOfBoundsException 異常
            System.out.println("i = " + i);// 拋出異常后,此行不會執行
        }catch(ArithmeticException e) { // 捕獲 ArithmeticException
            System.out.println(e.getMessage());// 發生異常的原因
            System.exit(0); // 程序強制退出,finally 代碼塊不會執行
        }finally {// 除了程序強制退出,如(System。exit(0)),無論是否發生異常,finally 代碼塊總會執行
            System.out.println("this is finally");
        }
        
        // try-finally搭配使用
        try {
            int[] arr = {1,2,3};
            int i = arr[3];// 數組索引越界,此行會拋出 ArrayIndexOutOfBoundsException 異常
            System.out.println("i = " + i);// 拋出異常后,此行不會執行
        }finally { 
            // 無論是否發生異常,finally 代碼塊總會執行
            System.out.println("this is finally");
        }
    }
}

注意點:

  • try-catch-finally 搭配:這種形式捕獲異常時,開發者可以在 catch 代碼塊中處理異常(如打印日誌、日誌記錄等),異常處理權在開發者。
  • try-finally 搭配:這種形式捕獲異常時,默認拋出給 JVM 處理,JVM默認處理時調用 e.printStackTrace() 方法打印異常詳細信息。
  • finally 代碼塊:除非程序強制退出,否則無論程序是否發生異常,finally 代碼塊總會執行。
  • finally 中拋出異常會覆蓋(消滅)前面 try 或者 catch 中的異常,盡量避免在 finally 代碼塊中拋出異常。
  • 如果 try 中和 finally 中都有 return 語句,程序會先執行 finally 中的 return 語句,然後程序塊運行結束,而不會執行 try 中的 return 語句。所以盡量不在finally中使用 return 語句。

(三)、throw 用法

throw 是用於拋出異常,將這個異常對象傳遞到調用者處,並結束當前方法的執行

public static void main(String[] args) {
  try {
    int i = 10 / 0;
    System.out.println("i = " + i);
  }catch(ArithmeticException e) { 
    // 拋出異常,傳遞自定義異常信息提示
    // 默認拋出給 JVM 處理打印異常詳細信息    
    throw new ArithmeticException("除數不能為0");
  } 
}

(四)、throws 用法

throws運用於方法聲明之上,用於表示當前方法不處理異常,而是提醒該方法的調用者來處理異常(拋出異常)。

public class ExceptionDemo {
    public static void main(String[] args) {
        demo();
    }
    public static void demo() throws ArrayIndexOutOfBoundsException{
        try {
            int[] arr = {1,2,3};
            int i = arr[3];
            System.out.println("i = " + i);
        }catch(ArrayIndexOutOfBoundsException e) { 
           System.out.println(e.toString());
        } 
    }
}

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

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

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

RocketMQ系列(一)基本概念_台中搬家公司

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

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

RocketMQ是阿里出品的一款開源的消息中間件,讓其聲名大噪的就是它的事務消息的功能。在企業中,消息中間件選擇使用RocketMQ的還是挺多的,這一系列的文章都是針對RocketMQ的,咱們先從RocketMQ的一些基本概念和環境的搭建開始聊起。

RocketMQ由4部分組成,分別是:名稱服務(Name Server)、消息隊列(Brokers)、生產者(producer)和消費者(consumer)。這4部分都可以進行水平擴展,從而避免單點故障,如下圖,

這是RocketMQ官網上的一張圖,非常清晰的列出了4個部分,並且都是集群模式。下面我們就分別說一說這4部分。

名稱服務(NameServer)

Name Server扮演的角色是一個註冊中心,和Zookeeper的作用差不多。它的主要功能有兩個,如下:

  • broker的管理:broker集群將自己的信息註冊到NameServer,NameServer提供心跳機制檢測每一個broker是否正常。
  • 路由管理:每一個NameServer都有整個broker集群和隊列的信息,以便客戶端(生產者和消費者)查詢。

NameServer協調着分佈式系統中的每一個組件,並且管理着每一個Topic的路由信息。

Broker

Broker主要是存儲消息,並且提供Topic的機制。它提供推和拉兩種模式,還有一些容災的措施,比如可以配置消息副本。下面我們看一看Brokcer的主從機制。

Broker的角色分為“異步主”、“同步主”和“從”三個角色。如果你不能容忍消息的丟失,你可以配置一個“同步主”和“從”兩個Broker,如果你覺得消息丟失也無所謂,只要隊列可用就ok的話,你可以配置“異步主”和“從”兩個broker。如果你只是想簡單的搭建,只配置一個“異步主”,不配置“從”也是可以的。

上面提到的是broker之間的備份,broker里的信息也是可以保存到磁盤的,保存到磁盤的方式也有兩種,推薦的方式是異步保存磁盤,同步保存磁盤是非常損耗性能的。

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

台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家

生產者

生產者支持集群部署,它們向broker集群發送消息,而且支持多種負載均衡的方式。

當生產者向broker發送消息時,會得到發送結果,發送結果中有一個發送狀態。假設我們的配置中,消息的配置isWaitStoreMsgOK = true,這個配置默認也是true,如果你配置為false,在發送消息的過程中,只要不發生異常,發送結果都是SEND_OK。當isWaitStoreMsgOK = true,發送結果有以下幾種,

  • FLUSH_DISK_TIMEOUT:保存磁盤超時,當保存磁盤的方式設置為SYNC_FLUSH(同步),並且在syncFlushTimeout配置的時間內(默認5s),沒有完成保存磁盤的動作,將會得到這個狀態。
  • FLUSH_SLAVE_TIMEOUT:同步“從”超時,當broker的角色設置為“同步主”時,但是在設置的同步時間內,默認為5s,沒有完成主從之間的同步,就會得到這個狀態。
  • SLAVE_NOT_AVAILABLE:“從”不可用,當我們設置“同步主”,但是沒有配置“從”broker時,會返回這個狀態。
  • SEND_OK:消息發送成功。

再來看看消息重複與消息丟失,當你發現你的消息丟失時,通常有兩個選擇,一個是丟就丟吧,這樣消息就真的丟了;另一個選擇是消息重新發送,這樣有可能引起消息重複。通常情況下,還是推薦重新發送的,我們在消費消息的時候要去除掉重複的消息。

發送message的大小一般不超過512k,默認的發送消息的方式是同步的,發送方法會一直阻塞,直到等到返回的響應。如果你比較在意性能,也可以用send(msg, callback)異步的方式發送消息。

消費者

多個消費者可以組成消費者組(consumer group),不同的消費者組可以訂閱相同的Topic,也可以獨立的消費Topic,每一個消費者組都有自己的消費偏移量。

消息的消費方式一般有兩種,順序消費和併發消費。

  • 順序消費:消費者將鎖住消息隊列,確保消息按照順序一個一個的被消費掉,順序消費會引起一部分性能損失。在消費消息的時候,如果出現異常,不建議直接拋出,而是應該返回SUSPEND_CURRENT_QUEUE_A_MOMENT 這個狀態,它將告訴消費者過一段時間后,會重新消費這個消息。
  • 併發消費:消費者將併發的消費消息,這種方式的性能非常好,也是推薦的消費方式。在消費的過程中,如果出現異常,不建議直接拋出,而是返回RECONSUME_LATER 狀態,它告訴消費者現在不能正確的消費它,過一段時間后,會再次消費它。

在消費者內部,是使用ThreadPoolExecutor作為線程池的,我們可以通過setConsumeThreadMin setConsumeThreadMax 設置最小消費線程和最大消費線程。

當一個新的消費者組建立以後,它要決定是否消費之前的歷史消息,CONSUME_FROM_LAST_OFFSET將忽略歷史消息,消費新的消息。CONSUME_FROM_FIRST_OFFSET將消費隊列中的每一個消息,之前的歷史消息也會再消費一遍。CONSUME_FROM_TIMESTAMP可以指定消費消息的時間,指定時間以後的消息會被消費。

如果你的應用不能容忍重複消費,那麼在消費消息的過程中,要做好消息的校驗。

好了,今天就到這裏吧,下一篇我們將介紹RocketMQ的環境搭建。

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

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

台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家

20多萬預算 90%的人不會考慮買這些車 這到底是為什麼?_網頁設計公司

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

台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境

99萬東風日產-西瑪售價:23。48-26。78萬沃爾沃亞太-沃爾沃S60L售價:26。69-55。99萬總結:目前來說,大眾邁騰、奧迪A4L、本田雅閣等傳統中級車,在市場的統治地位仍然牢不可破。但隨着消費升級和多元化需求的高漲,有個性有實力的中級車更容易贏得一部分人的青睞,而推薦的這四款轎車恰好做到了。

現在汽車市場的同質化情況可謂是愈來愈嚴重,擺在競爭激烈的中級車市場更是如此。但是,對於購買中級車的潛在用戶來說,

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

網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

價格因素不再是首要的,產品力才是關鍵。隨着各大品牌在中級車領域各顯神通,一些有個性、有顏值以及有實力的中級車,逐漸獲得了更多用戶的認可。

一汽馬自達-阿特茲

售價:17.58-23.98萬

東風雪鐵龍C6

售價:18.99-27.99萬

東風日產-西瑪

售價:23.48-26.78萬

沃爾沃亞太-沃爾沃S60L

售價:26.69-55.99萬

總結:目前來說,大眾邁騰、奧迪A4L、本田雅閣等傳統中級車,在市場的統治地位仍然牢不可破。但隨着消費升級和多元化需求的高漲,有個性有實力的中級車更容易贏得一部分人的青睞,而推薦的這四款轎車恰好做到了。那麼問題來了,你們最喜歡哪一款呢?本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

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

以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。

月收入5000也能買車 經濟實惠的家用車買這些夠用了!_如何寫文案

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

銷售文案是什麼?A文案是廣告用的文字。舉凡任何宣傳、行銷、販賣商品時所用到的文字都是文案。在網路時代,文案成為行銷中最重要的宣傳方式,好的文案可節省大量宣傳資源,達成行銷目的。

內飾是長安家族式的設計,風格更加年輕時尚,雙炮台式的儀錶盤非常經典,無鑰匙進入/啟動、ESp、上坡輔助、倒車影像等配置應有盡有。逸動的軸距達到2660mm,空間上面令人滿意,較寬的車身寬度也很好的提升了乘坐舒適性。

一汽奔騰-奔騰B30

指導價:6.98-9.28萬

奔騰B30的誕生無非就是為了細分市場的興起,是一款定位緊湊型車的入門級產品,前兩設計非常的個性誇張,偏向於年輕運動化的設計風格,飄逸的腰線很有特點;內飾看起來很規整,實用為主,沒有太多花哨的地方,胎壓監測、上坡輔助、定速巡航、倒車影像等配置比較齊全;2630mm軸距帶來的空間感受還是蠻寬敞的,儲物空間設計也比較到位,中控台底部空間和杯架深度合理,滿足家用需求;1.6L發動機技術成熟,最大功率109馬力,搭配5擋手動或者6擋手自一體變速箱,日常駕駛很順暢,

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

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

底盤整體感強。

優惠:購置稅全免,尊享1年0零利率分期禮遇,尊享置換補貼3000元

長安汽車-逸動

指導價:8.09-24.99萬

無可否認,長安造車非常符合國人的審美要求,逸動外觀沒有過多花哨的地方,整車線條流暢優雅,門把手與其腰線有機的融合成一體,簡潔耐看,5幅式雙色輪圈視覺效果不錯;內飾是長安家族式的設計,風格更加年輕時尚,雙炮台式的儀錶盤非常經典,無鑰匙進入/啟動、ESp、上坡輔助、倒車影像等配置應有盡有;逸動的軸距達到2660mm,空間上面令人滿意,較寬的車身寬度也很好的提升了乘坐舒適性;動力系統上採用了有缸內直噴技術的1.6L發動機,最大功率128馬力,搭配5擋手動變速箱,在頭段和中段加速時動力很充足,在市區行駛來說游刃有餘,是一輛質優價廉的好車。

優惠:現金優惠5千元左右,享購置稅減半政策優本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

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

什麼是銷售文案服務?A就是幫你撰寫適合的廣告文案。當您需要販售商品、宣傳活動、建立個人品牌,撰寫廣告文案都是必須的工作。

我把勞斯萊斯開到冒煙!怎麼辦?_網頁設計公司

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

透過資料庫的網站架設建置,建立公司的形象或購物系統,並提供最人性化的使用介面,讓使用者能即時接收到相關的資訊

5,中控面板的花紋則是碳纖維鋁絲材質編製而成的,強調運動的同時卻不乏典雅。動力方面,搭載6。6L雙渦輪增壓V12發動機,最大輸出功率624馬力,峰值扭矩870牛·米,較普通版本提升了70牛。米。與之匹配的則是8速自動變速箱,儘管這是一輛更強調運動的勞斯萊斯,撥片換擋也並沒有出現,負責人表示那些東西不夠“勞斯萊斯”。

勞斯萊斯品牌自誕生之初就是為了打造世界上最奢華的汽車。所以以往我們談起勞斯萊斯,首先聯想到的就是車主肯定是一個超有錢的老頭。

可能連勞斯萊斯他們自己人也發現了這個問題,隨着全球巨富的平均年齡越來越低,勞斯萊斯於2013年正式發布魅影車型。

其英文名稱Wraith則是來自於曾經的勞斯萊斯經典車型Silver Wraith。一款定位於豪華GT型跑車的勞斯萊斯,其目標就是更年輕的消費人群。

而此次我們體驗的則是魅影的Black Badge 特別版本。

Black Badge是勞斯萊斯誕生112年以來,首次將雙R字母LOGO採用黑底色搭配銀色字體的反色設計。

車頭的“歡慶女神”在設計、材質和姿態都沒有轉變的前提下,Black Badge車型採用高光澤度的特別黑色材質,讓“歡慶女神”搖身一變化身為黑夜的“魅惑女郎”,徜徉在夜晚迷人的都市之中。

此外,進氣格柵外框、下進氣口裝飾條、後備箱把手、排氣管等原本使用的高光鍍鉻材質也被更具神秘格調的黑色材質所取代。

以複合碳纖維材質和輕質鋁材結合的新型輪轂,通過輪輞外邊緣的疊層設計,從而達到44片碳纖維式的超高強度,大大減輕了懸挂的簧下質量,保證極佳的觀感同時還擁有更加卓越的性能。

內飾採用了黑色與藍色搭配的內飾設計,顯得十分大膽張揚,

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

當全世界的人們隨著網路時代而改變向上時您還停留在『網站美醜不重要』的舊有思維嗎?機會是留給努力改變現況的人們,別再浪費一分一秒可以接觸商機的寶貴時間!

歡慶女神按鈕經過特殊處理后,始終不會染上指紋,星空車頂讓你忘了什麼是pM2.5,中控面板的花紋則是碳纖維鋁絲材質編製而成的,強調運動的同時卻不乏典雅。

動力方面,搭載6.6L雙渦輪增壓V12發動機,最大輸出功率624馬力,峰值扭矩870牛·米,較普通版本提升了70牛.米 。與之匹配的則是8速自動變速箱,儘管這是一輛更強調運動的勞斯萊斯,撥片換擋也並沒有出現,負責人表示那些東西不夠“勞斯萊斯”。

本以為勞斯萊斯的體驗活動會是在某個高端會所之類的,意想不到的是來到了賽道。低速行駛的時候安靜得像溜車,但你踩下地板油的時候,車頭裡的V12引擎傳來了美妙的引擎聲,一瞬間會讓你忘記了你是在一輛勞斯萊斯裏面。

最為令人稱道的則是那套勞斯萊斯稱為“魔毯”的懸挂系統,感覺就像是你坐在一塊巨大的黃油上被推着走,在保證如此強大的運動特性的同時還能保持如此舒適的感受,讓你不得不深深的佩服。

整個體驗過程結束後會讓你知道,勞斯萊斯的追求不僅限於精湛的手工工藝和對奢華的見解。在人工如此高昂的歐洲,每一個按鍵,每一針縫線,每一塊皮革,每一顆螺絲,都由是手工親自完成。

Black Badge的面世也表明了他們從未放棄過對激越速度、完美工程設計與舒適駕駛體驗的不懈追求,進一步挖掘和展現了其更為神秘、個性的一面。或許這對於勞斯萊斯說是一個變革的開始。

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

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

RWD(響應式網頁設計)是透過瀏覽器的解析度來判斷要給使用者看到的樣貌

奇瑞瑞虎新成員!6.39萬起居然有10幾萬SUV的高配置_網頁設計

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

窩窩以「數位行銷」「品牌經營」「網站與應用程式」「印刷品設計」等四大主軸,為每一位客戶客製建立行銷脈絡及洞燭市場先機。

而手動擋和自動擋的中高配車型配置則“好看”不少,除了常說的ESp,剎車輔助之外,還配備有上坡輔助,真皮方向盤中控彩色大屏等等,無論是安全性還是舒適性的配置都多了不少,中配車型相比低配車型只是相差4000的價格。

源於小型SUV市場的火爆,奇瑞在不久之前重磅推出瑞虎7之後,在這個月的廣州車上也將會推出一款小型SUV—瑞虎3X,預售價:6.39-7.99萬。這是奇瑞2.0戰略下第二款SUV車型。其實剛看到名字時,會以為這是不是瑞虎3的加大版本,但了解過後發現,並非如此,瑞虎3X是一輛全新車型。

外觀:形似瑞虎3,但更年輕

看命名多多少少就知道和瑞虎3有些關聯,從正臉看過去的,整體的造型設計和瑞虎3很相似,只是一些部位的造型不一樣,例如,前大燈,霧燈等等,並且瑞虎3X添加一些硬朗的元素設計,視覺上會比瑞虎3更動感和更硬朗一些。

側面,瑞虎3X比瑞虎3更有運動感,也就是顯得更加年輕,除了車身線條的不同之外,車頂尾部的小鴨尾設計,也是呈現年輕化的流行設計元素之一。尾部的造型有點類似瑞虎7,橫穿尾燈和LOGO的鍍鉻裝飾條算是特色之一,不過,個人是覺得缺了點什麼東西,有點空。不過,蘿蔔青菜各有所愛。

內飾:主打年輕

整體造型布局和瑞虎3大致相同,但視覺上的質感較好,並且在配色和材質上選擇,讓瑞虎3X的車內氣氛更加年輕。對於這個價位車型,大面積的硬塑材質並不意外,先不說觸感,至少視覺上不錯的,特別是中控面板下半部分的碳纖維紋理,讓其看上去不那麼單調,也稍微增加了一些檔次。

儀錶盤並不是傳統的圓形設計,而是類似瑞虎7的另類設計,算是特點之一,中間是行車電腦显示屏。

配置:一如既往的豐富

瑞虎3X的手動擋和自動擋車型都分別有三個車型選擇。手動擋和自動擋的低配車型配置較低,沒有裝配ESp,剎車輔助,牽引力控制,

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

擁有專業的維修技術團隊,同時聘請資深iphone手機維修專家,現場說明手機問題,快速修理,沒修好不收錢

舒適性上的配置也比較尷尬。而手動擋和自動擋的中高配車型配置則“好看”不少,除了常說的ESp,剎車輔助之外,還配備有上坡輔助,真皮方向盤中控彩色大屏等等,無論是安全性還是舒適性的配置都多了不少,中配車型相比低配車型只是相差4000的價格。不過有一點奇怪的是,低配配置有配胎壓監測,反而中配沒有,需要選裝。

而高配車型同樣是比中配車型差了4000的差價,多了胎壓監測,電動天窗,后視鏡加熱。總的來說,車型與車型之間差價和多出配置對比起來,還是比較厚道的,要說性價比的話,推薦中配車型。

空間:中等水平

瑞虎3X的車身尺寸為:4200*1760*1570mm,軸距:2555mm,相比瑞虎3,瑞虎3X要短一些,高度上因為造型問題也稍微低一些,相比其他競爭對手,例如,紳寶X25,瑞豐S3等,並沒什麼優勢。由此可知,瑞虎3X的車內空間並不會有太多驚喜了,前後排對於180cm以上的來說會稍微感到壓抑,180cm以下稍微好一些,但並不算寬裕,整體車內空間表現一般。

動力匹配

瑞虎3X搭載了1.5L自然吸氣發動機,最大功率輸出為106馬力,最大扭矩135牛米,匹配5擋手動以及4擋自動。由於瑞虎3搭載是1.6L的發動機,瑞虎3X的1.5L發動機算是“新貨”。至於詳細的動力輸出如何,有待後續評測。

總結:正所謂“兄弟多好打架”,瑞虎3面對越來越多年輕化的小型SUV,實在有些乏力,而瑞虎3X的推出,可以和瑞虎3分化一下小型市場,主打年輕市場,填補了奇瑞這個級別的年輕市場空缺。瑞虎3X整體設計很動感,很時尚,也很符合現代年輕人的審美,並且在這樣的價位能買到這些配置,也是比較值的。至於擔心動力的話,個人覺得,作為一輛代步車,日常使用而言,還是夠用的。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

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

網動是一群專業、熱情、向前行的工作團隊,我們擁有靈活的組織與溝通的能力,能傾聽客戶聲音,激發創意的火花,呈現完美的作品

銷量榜長期佔據第一,人氣哈弗H6應該選哪個配置?_貨運

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

網動結合了許多網際網路業界的菁英共同研發簡單易操作的架站工具,及時性的更新,為客戶創造出更多的網路商機。

58-12。38萬,價格相當實在,僅僅比起兩驅版本高8000元。但考慮哈弗H6城市SUV的定位,適時四驅的驅動形式,以及動力水平考慮,這套四驅在城市使用程度並不高,只會增加油耗,並不建議大家選擇四驅車型。(上圖分別為哈弗H6紅標版、藍標版)哈弗H6最大的亮點就是其空間表現了,4649*1852*1710mm的車身尺寸看着相當魁梧,而且有着2680mm的軸距,在這個價位中表現是非常出色的,滿足國人對車身尺寸以及空間的追求。

前言

要說中國的SUV神車是哪一輛的話,哈弗H6肯定可以牢牢霸佔這個地位,一直霸佔着中國SUV銷量第一的位置,在不斷有着競爭對手向起衝鋒的時候也可以守住地位,而且還依然領先第二名2萬輛。而這款神車有着超多的配置以及動力總成可以選擇,究竟這款中國神車應該選擇哪一個配置性價比才是最高呢?

(上圖分別為哈弗H6紅標版、藍標版)

哈弗H6有着兩種品牌標識,一個是沉穩的紅標版H6,另一個是標榜運動年輕的藍標版H6,藍標版H6要比紅標版官方指導價要高2000元,並且增加后視鏡電動摺疊功能,是否願意為藍標的外觀以及這電動后視鏡摺疊功能埋單就看自己的個人喜好了,在其他部分兩者幾乎是一模一樣。

哈弗H6有着兩種發動機總成選擇,分別是1.5T渦輪增壓汽油發動機以及2.0T柴油發動機,因為多數消費者均選擇汽油版本,本次對比僅僅針對1.5T發動機進行對比。而該發動機最大功率110千瓦,最大扭矩210牛米,表現只能說是一般,僅僅能滿足家庭需求。在日常駕駛中能夠感受明顯的渦輪遲滯現象,低扭表現一般,動力要到2500轉以上能有好轉,

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

搬家價格與搬家費用透明合理,不亂收費。本公司提供下列三種搬家計費方案,由資深專業組長到府估價,替客戶量身規劃選擇最經濟節省的計費方式

所以要想有着暢快的駕駛感受就不應該吝嗇腳下的油門了。

作為一款SUV,哈弗H6除了一般的兩驅車型以外,還有着四驅車型選擇,價位為11.58-12.38萬,價格相當實在,僅僅比起兩驅版本高8000元。但考慮哈弗H6城市SUV的定位,適時四驅的驅動形式,以及動力水平考慮,這套四驅在城市使用程度並不高,只會增加油耗,並不建議大家選擇四驅車型。

(上圖分別為哈弗H6紅標版、藍標版)

哈弗H6最大的亮點就是其空間表現了,4649*1852*1710mm的車身尺寸看着相當魁梧,而且有着2680mm的軸距,在這個價位中表現是非常出色的,滿足國人對車身尺寸以及空間的追求。

(上圖分別為哈弗H6紅標版、藍標版)

內飾方面,哈弗H6選擇的是一種比較中庸的風格,紅標使用的是仿木材料作為裝飾,而藍標則是選擇了金屬色澤的裝飾條,更為年輕好看。雖然在視覺上並不能給人驚艷的感覺,但總歸耐看,而且在用料做工上都是該價位的優秀水平,所以不少用戶選購哈弗H6是因為這個內飾表現。

(上圖分別為哈弗H6紅標版、藍標版)

配置方面也是H6的亮點之一,2017款全系標配ESp車身穩定系統、4安全氣囊、胎壓監測系統、上坡輔助、電動天窗、多功能方向盤、定速巡航、倒車影像、倒車雷達、仿皮座椅、駕駛位座椅電動調節、前排座椅加熱、車載導航、自動空調以及發動機啟停功能,考慮到2017款10.78萬的起售價,哈弗H6即使是最低配也有非常高的實用性。

對於選擇手動擋的用戶,筆者最為推薦的比最低配高6000元的手動兩驅尊貴型,在配置上多了前後排頭部安全氣囊、前駐車雷達、自動大燈、后視鏡電動摺疊以及感應雨刷,在舒適性以及安全性都有着較大的提升,而且價格差距不大。

而對於選擇自動擋的用戶,性價比最高的則是自動尊享型,也僅僅是以6000元的價格獲得上述的配置提升。而藍標雖然比起紅標版多了氙氣大燈,但考慮到它2000元的差價性價比還是比較一般的,畢竟一個氙氣大燈成本並不高於1000元。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

※回頭車貨運收費標準

宇安交通關係企業,自成立迄今,即秉持著「以誠待人」、「以實處事」的企業信念