標籤: 台北網頁設計

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

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

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

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

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

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

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

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

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

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

      本文包括內容:

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

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

    熱身:SVM的整理

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

    熱身1,Hard Margin SVM

    熱身2,Soft Margin SVM

    熱身3,LS-SVM

    1,線性學習器

    1.1 感知機算法

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

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

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

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

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

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

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

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

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

    2,非線性學習器

    2.1 Mercer定理

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

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

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

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

    2.2 正定矩陣

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

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

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

      正定矩陣的性質:

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

    3,損失函數

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

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

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

      (1) 0-1 損失函數

      (2)平方損失函數

       (3)絕對損失函數

       (4)對數損失函數

       給定一個訓練數據集

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

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

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

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

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

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

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

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

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

    4,最小二乘法

    4.1  什麼是最小二乘法?

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    4.2 最小二乘法的解法

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

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

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

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

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

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

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

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

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

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

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

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

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

    5,SMO算法

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

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

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

       等價於求解:

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

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

    5.1 SMO算法的推導

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

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

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

       求導得到:

       代入 u 的公式中,可得:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

     

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

      這種情況如下圖所示:

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

       回顧下第二個約束條件:

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

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

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

       由於:

       化簡下:

       然後將:

       代入上式,可得:

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

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

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

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

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

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

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

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

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

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

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

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

      而 b 滿足下述條件:

       下面更新 b:

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

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

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

     5.2  SMO算法的步驟

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

       意思是:

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

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

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

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

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

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

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

    5.3  SMO算法的實現

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

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

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

    5.4  SMO算法的優缺點

      優點:

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

      缺點:

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

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

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

    【其他文章推薦】

    ※超省錢租車方案

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

    ※回頭車貨運收費標準

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

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

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

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

  • LeetCode 75,90%的人想不出最佳解的簡單題

    LeetCode 75,90%的人想不出最佳解的簡單題

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

    今天是LeetCode專題的44篇文章,我們一起來看下LeetCode的75題,顏色排序 Sort Colors。

    這題的官方難度是Medium,通過率是45%,點贊2955,反對209(國際版數據),從這份數據上我們大概能看得出來,這題的難度不大,並且點贊遠遠高於反對,說明題目的質量很不錯。事實上也的確如此,這題足夠簡單也足夠有趣,值得一做。

    題意

    給定一個n個元素的數組,數組當中的每一個元素表示一個顏色。一共有紅白藍三種顏色,分別用0,1和2來表示。要求將這些顏色按照大小進行排序,返回排序之後的結果。

    要求不能調用排序庫sort來解決問題。

    桶排序

    看完題目應該感受到了,如果沒有不能使用sort的限制,這題毫無難度。即使加上了限制難度也不大,我們既然不能調用sort,難道還不能自己寫個sort嗎?Python寫個快排也才幾行而已。

    自己寫sort當然是可以的,顯然這是下下策。因為元素只有3個值,互相之間的大小關係也就只有那麼幾種,排序完全沒有必要。比較容易想到,我們可以統計一下這三個數值出現的次數,幾個0幾個1幾個2,我們再把這些數拼在一起,還原之前的數據不就可以了嗎?

    這樣的確可行,但實際上這也是一種排序方案,叫做基數排序,也稱為桶排序,還有些地方稱為小學生排序(大概是小學生都能懂的意思吧)。基數排序的思想非常簡單,我們創建一個數組,用它的每一位來表示某個元素是否在原數組當中出現過。出現過則+1,沒出現過則一直是0。我們標記完原數組之後,再遍歷一遍標記的數組,由於下標天然有序,所以我們就可以得到排序之後的結果了。

    如果你還有些迷糊也沒有關係,我們把代碼寫出來就明白了,由於這題讓我們提供一個inplace的方法,所以我們在最後的時候需要對nums當中的元素重新賦值。

    class Solution:
        def sortColors(self, nums: List[int]) -> None:
            """  Do not return anything, modify nums in-place instead.  """
            bucket = [0 for _ in range(3)]
            for i in nums:
                bucket[i] += 1
    
            ret = []
            for i in range(3):
                ret += [i] * bucket[i]
    
            nums[:] = ret[:]
    

    和排序相比,我們只是遍歷了兩次數據,第一次是遍歷了原數組獲得了其中0,1和2的數量,第二次是將獲得的數據重新填充回原數組當中。相比於快排或者是其他一些排序算法的耗時,桶排序只遍歷了兩次數組,明顯要快得多。但遺憾的是這並不是最佳的方法,題目當中明確說了,還有隻需要遍歷一次原數組的方法。

    two pointers

    在我們介紹具體的算法之前,我們先來分析一下問題。既然顏色只有三種,那麼當我們排完序之後,整個數組會被分成三個部分,頭部是0,中間是1,尾部是2。

    我們可以用一個區間來收縮1的範圍,假設我們當前區間的首尾元素分別是l和r。當我們讀到0的時候,我們就將它和l交換,然後將l向後移動一位。當我們讀到2的時候,則將它和r進行交換,將r向左移動一位。也就是說我們保證l和r之間的元素只有1。

    我們之前曾經介紹過這種維護一個區間的做法,雖然都是維護了一個區間,但是操作上是有一些區別的。之前介紹的two pointers算法,也叫做尺取法,本質上是通過移動區間的右側邊界來容納新的元素,通過移動左邊界彈出數據的方式來維護區間內所有元素的合法性。而當前的做法中,一開始獲得的就是一個非法的區間,我們通過元素的遍歷以及區間的移動,最後讓它變得合法。兩者的思路上有一些細微的差別,但形式是一樣的,就是通過移動左右兩側的邊界來維護或者是達到合法。

    class Solution:
        def sortColors(self, nums: List[int]) -> None:
            """  Do not return anything, modify nums in-place instead.  """
            l, r = 0, len(nums)-1
            i = 0
            while i < len(nums):
                if i > r:
                    break
       # 如果遇到0,則和左邊交換
                if nums[i] == 0:
                    nums[l], nums[i] = nums[i], nums[l]
                    l += 1
                # 如果遇到2,則和右邊交換
                # 交換之後i需要-1,因為可能換來一個0
                elif nums[i] == 2:
                    nums[r], nums[i] = nums[i], nums[r]
                    r -= 1
                    continue
                i += 1
    

    這種方法我們雖然只遍歷了數組一次,但是由於交換的次數過多,整體運行的速度比上面的方法還要慢。所以遍歷兩次數組並不一定就比只遍歷一次要差,畢竟兩者都是的算法,相差的只是一個常數。遍歷的次數只是構成常數的部分之一。

    除了這個方法之外,我們還有其他維護區間的方法。

    維護區間

    接下來要說的方法非常巧妙,我個人覺得甚至要比上面的方法還有巧妙。

    我們來假想一下這麼一個場景,假設我們不是在原數組上操作數據,而是從其中讀出數據放到新的數組當中。我們先不去想應該怎麼擺放這個問題,我們就來假設我們原數組當中的數據已經放好了若干個,那麼這個時候的新數組會是什麼樣?顯然,應該是排好序的,前面若干個0,中間若干個1,最後若干個2。

    那麼問題來了,假設這個時候我們讀到一個0,那麼應該怎麼放呢?為了簡化敘述我們把它畫成圖:

    我們假設藍色部分是0,綠色部分是1,粉色部分是2。a是0最右側的下標,b是1部分最右側的下標,c是2部分最右側的下標。那麼這個時候,當我們需要放入一個0的時候,應該怎麼辦?

    我們結合圖很容易想明白,我們需要把0放在a+1的位置,那麼我們需要把後面1和2的部分都往右側移動一格,讓出一格位置出來放0。我們移動數組顯然帶來的開銷會過於大,實際上沒有必要移動整個部分,只需要移動頭尾元素即可。比如1的部分左側被0佔掉了一格,那麼為了保持長度不變,右側也需要延伸一格。同理,2的部分右側也需要延伸一格。那麼整個操作用代碼來表示就是:nums[a+1] = 0,nums[b+1] = 1, nums[c+1] = 2。

    假設我們讀入的數是1,那麼我們需要把b延長一個單位,但是這樣帶來的後果是2的部分被侵佔,所以需要將2也延長,補上被1侵佔的一個單位。如果讀到的是2,那麼直接延長2即可,因為2後面沒有其他顏色了。

    假設我們有一個空白的數組,我們可以這麼操作,但其實我們沒有必要專門創建一個數組,我們完全可以用原數組自己填充自己。因為我們從原數組上讀取的數和擺放的數是一樣的,我們直接把数字擺放在原數組的頭部,佔用之前讀取的數即可。

    光說可能還有些迷糊,看下代碼馬上就清楚了:

    class Solution:
        def sortColors(self, nums: List[int]) -> None:
            """  Do not return anything, modify nums in-place instead.  """
            # 記錄0,1和2的末尾位置
            zero, one, two = -1, -1, -1
            n = len(nums)
            for i in range(n):
                # 如果擺放0
                # 那麼1和2都往後平移一位,讓一個位置出來擺放0
                if nums[i] == 0:
                    nums[two+1] = 2
                    nums[one+1] = 1
                    nums[zero+1] = 0
                    zero += 1
                    one += 1
                    two += 1
                elif nums[i] == 1:
                    nums[two+1] = 2
                    nums[one+1] = 1
                    one += 1
                    two += 1
                else:
                    nums[two+1] = 2
                    two += 1
    

    總結

    到這裏,這道題的解法基本上都講完了。

    相信大家也都看出來了,從難度上來說這題真的不難,相信大家都能想出解法來,但是要想到最優解還是有些困難的。一方面需要我們對題目有非常深入的理解,一方面也需要大量的思考。這類題目沒有固定的解法,需要我們根據題目的要求以及實際情況自行設計解法,這也是最考驗思維能力以及算法設計能力的問題,比考察某個算法會不會的問題要有意思得多。

    希望大家都能從這題當中獲得樂趣,如果喜歡本文,可以的話,請點個關注,給我一點鼓勵,也方便獲取更多文章。

    本文使用 mdnice 排版

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

    【其他文章推薦】

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

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

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

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

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

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

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

  • 詳解 Seata Golang 客戶端 AT 模式及其使用

    詳解 Seata Golang 客戶端 AT 模式及其使用

    源碼seata-golang

    概述

      我們知道 Seata Java Client 的 AT 模式,通過代理數據源,實現了對業務代碼無侵入的分佈式事務協調機制,將與 Transaction Coordinator (TC) 交互的邏輯、Commit 的邏輯、Rollback 的邏輯,隱藏在切面和代理數據源相應的代碼中,使開發者無感知。那如果這個方法,要用 Golang 來實現一遍,應該如何操作呢?關於這個問題,我想了很久,最初的設想是,對 database/sql 的 mysql driver 進行增強,在對包 github.com/go-sql-driver/mysql 研究了一段時間后,還是沒有頭緒,不知如何下手,最後轉而增強 database/sql 包。由於 AT 模式必須保證本地事務的正確處理,在具體業務開發時,首先要通過 db.Begin() 獲得一個 Tx 對象,然後再 tx.Exec() 執行數據庫操作,最後 tx.Commit() 提交或 tx.Rollback() 回滾。這種處理方式算是一個 Golang 數據庫事務處理的基本操作。 所以對 database/sql 的增強,我們重點關注這幾個方法 db.Begin()tx.Exec()tx.Commit()tx.Rollback

    事務提交、回滾

      通過 Seata Java Client 的相關代碼,我們知道,在本地事務提交的時候,主要是將分支事務註冊到 TC 上,並將數據庫操作產生的 undoLog 一起寫入到 undoLog 表;本地事務回滾的時候,需要將分支事務(即本地事務)的執行狀態報告給 TC,使 TC 好知道是否通知參与全局事務的其他分支回滾。

    func (tx *Tx) Commit() error {
            //註冊分支事務
    	branchId,err := tx.register()
    	if err != nil {
    		return errors.WithStack(err)
    	}
    	tx.tx.Context.BranchId = branchId
    
    	if tx.tx.Context.HasUndoLog() {
                    //將 undoLog 寫入 undoLog 表
    		err = manager.GetUndoLogManager().FlushUndoLogs(tx.tx)
    		if err != nil {
    			err1 := tx.report(false)
    			if err1 != nil {
    				return errors.WithStack(err1)
    			}
    			return errors.WithStack(err)
    		}
    		err = tx.tx.Commit()
    		if err != nil {
    			err1 := tx.report(false)
    			if err1 != nil {
    				return errors.WithStack(err1)
    			}
    			return errors.WithStack(err)
    		}
    	} else {
    		return tx.tx.Commit()
    	}
    	if tx.reportSuccessEnable {
    		tx.report(true)
    	}
    	tx.tx.Context.Reset()
    	return nil
    }
    

      db.Begin() 會產生一個 Tx 對象,tx.Exec() 會產生 undoLog,tx.Commit() 將 undoLog 刷到數據庫中。那麼 undoLog 保存到哪裡呢?答案是 TxContext 中。

    type TxContext struct {
    	*context.RootContext
    	Xid string
    	BranchId int64
    	IsGlobalLockRequire bool
    
    	LockKeysBuffer *model.Set
    	SqlUndoItemsBuffer []*undo.SqlUndoLog
    }
    

      Commit() 方法中的 tx.tx.Context,第一個 tx 是封裝的 Tx 對象,第二個 tx 是 database/sql 的 Tx,tx.tx.Context 則是 TxContext。UndoLogManager 則是操作 undoLog 的核心對象,處理 undoLog 的插入、刪除,並查詢出 undoLog 用於回滾。

    func (tx *Tx) Rollback() error {
    	err := tx.tx.Rollback()
    	if tx.tx.Context.InGlobalTransaction() && tx.tx.Context.IsBranchRegistered() {
                    // 報告 TC 分支事務執行失敗
    		tx.report(false)
    	}
    	tx.tx.Context.Reset()
    	return err
    }
    

      通過上面的代碼呢,我們知道增強型 Tx 對象需要向 TC 註冊分支事務,並報告分支事務的執行狀態,相應代碼如下:

    func (tx *Tx) register() (int64,error) {
    	return dataSourceManager.BranchRegister(meta.BranchTypeAT,tx.tx.ResourceId,"",tx.tx.Context.Xid,
    		nil,tx.tx.Context.BuildLockKeys())
    }
    
    func (tx *Tx) report(commitDone bool) error {
    	retry := tx.reportRetryCount
    	for retry > 0 {
    		var err error
    		if commitDone {
    			err = dataSourceManager.BranchReport(meta.BranchTypeAT, tx.tx.Context.Xid, tx.tx.Context.BranchId,
    				meta.BranchStatusPhaseoneDone,nil)
    		} else {
    			err = dataSourceManager.BranchReport(meta.BranchTypeAT, tx.tx.Context.Xid, tx.tx.Context.BranchId,
    				meta.BranchStatusPhaseoneFailed,nil)
    		}
    		if err != nil {
    			logging.Logger.Errorf("Failed to report [%d/%s] commit done [%t] Retry Countdown: %d",
    				tx.tx.Context.BranchId,tx.tx.Context.Xid,commitDone,retry)
    			retry = retry -1
    			if retry == 0 {
    				return errors.WithMessagef(err,"Failed to report branch status %t",commitDone)
    			}
    		}
    	}
    	return nil
    }
    

      和 TC 進行通信的主要邏輯還是在 DataSourceManager 裏面。AT 模式涉及的兩個關鍵對象 DataSourceManager、UndoLogManager 就浮出水面。一個用於遠程 TC 交互,一個用於本地數據庫處理。

    事務執行

    func (tx *Tx) Exec(query string, args ...interface{}) (sql.Result, error) {
    	var parser = p.New()
            // 解析業務 sql
    	act,_ := parser.ParseOneStmt(query,"","")
    	deleteStmt,isDelete := act.(*ast.DeleteStmt)
    	if isDelete {
    		executor := &DeleteExecutor{
    			tx:            tx.tx,
    			sqlRecognizer: mysql.NewMysqlDeleteRecognizer(query,deleteStmt),
    			values:        args,
    		}
    		return executor.Execute()
    	}
    
    	insertStmt,isInsert := act.(*ast.InsertStmt)
    	if isInsert {
    		executor := &InsertExecutor{
    			tx:            tx.tx,
    			sqlRecognizer: mysql.NewMysqlInsertRecognizer(query,insertStmt),
    			values:        args,
    		}
    		return executor.Execute()
    	}
    
    	updateStmt,isUpdate := act.(*ast.UpdateStmt)
    	if isUpdate {
    		executor := &UpdateExecutor{
    			tx:            tx.tx,
    			sqlRecognizer: mysql.NewMysqlUpdateRecognizer(query,updateStmt),
    			values:        args,
    		}
    		return executor.Execute()
    	}
    
    	return tx.tx.Tx.Exec(query,args)
    }
    

      執行業務 sql,並生成 undoLog 的關鍵,在於識別業務 sql 執行了什麼操作:插入?刪除?修改?這裏使用 tidb 的 sql parser 去解析業務 sql,再使用相應的執行器去執行業務 sql,生成 undoLog 保存在 Tx_Context 中。

    事務開啟

      db.Begin() 返回增強型的 Tx 對象。

    func (db *DB) Begin(ctx *context.RootContext) (*Tx,error) {
    	tx,err := db.DB.Begin()
    	if err != nil {
    		return nil,err
    	}
    	proxyTx := &tx2.ProxyTx{
    		Tx:         tx,
    		DSN:        db.conf.DSN,
    		ResourceId: db.GetResourceId(),
    		Context:    tx2.NewTxContext(ctx),
    	}
    	return &Tx{
    		tx: proxyTx,
    		reportRetryCount: db.conf.ReportRetryCount,
    		reportSuccessEnable: db.conf.ReportSuccessEnable,
    	},nil
    }
    

    seata-golang at 模式的使用

    sample 代碼

    • 首先執行 scripts 腳本,初始化數據庫
      如果之前沒有初始化過 seata 數據庫,先執行 seata-golang/scripts/server/db/mysql.sql 腳本
    • 修改 dsn 數據庫配置,修改下列文件:
    seata-golang/tc/app/profiles/dev/config.yml
    seata-golang/samples/at/product_svc/conf/client.yml
    seata-golang/samples/at/product_svc/conf/client.yml
    
    • 將下列文件中的 configPath 修改為 client.yml 配置文件的路徑
    seata-golang/samples/at/product_svc/main.go
    seata-golang/samples/at/order_svc/main.go
    seata-golang/samples/at/aggregation_svc/main.go
    
    • 依次運行 tc、order_svc、product_svc、aggragation_svc,訪問下列地址開始測試:
    http://localhost:8003/createSoCommit
    http://localhost:8003/createSoRollback
    

    TC 啟動參考參与 Seata 社區到 go 與 Seata 的邂逅

    seata-golang 後續安排

      接下來不打算再增加新的 feature。Java 版 Seata 畢竟發展了一年多時間,並且有很多社區成員一起維護,Go 版本目前主要是我在開發,時間不到2個月,現有的代碼,僅是完成了框架,還需要大量優化,改bug,後續的工作重心在於使 seata-golang 穩定運行,生產可用,希望對分佈式事務感興趣且對 Go 感興趣的同學一起加入進來,一起做些事情。進入微信群,請加我微信:scottlewis,備註進群。

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

    【其他文章推薦】

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

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

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

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

    ※回頭車貨運收費標準

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

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

  • 解Bug之路-記一次JVM堆外內存泄露Bug的查找

    解Bug之路-記一次JVM堆外內存泄露Bug的查找

    解Bug之路-記一次JVM堆外內存泄露Bug的查找

    前言

    JVM的堆外內存泄露的定位一直是個比較棘手的問題。此次的Bug查找從堆內內存的泄露反推出堆外內存,同時對物理內存的使用做了定量的分析,從而實錘了Bug的源頭。筆者將此Bug分析的過程寫成博客,以饗讀者。
    由於物理內存定量分析部分用到了linux kernel虛擬內存管理的知識,讀者如果有興趣了解請看ulk3(《深入理解linux內核第三版》)

    內存泄露Bug現場

    一個線上穩定運行了三年的系統,從物理機遷移到docker環境后,運行了一段時間,突然被監控系統發出了某些實例不可用的報警。所幸有負載均衡,可以自動下掉節點,如下圖所示:

    登錄到對應機器上后,發現由於內存佔用太大,觸發OOM,然後被linux系統本身給kill了。

    應急措施

    緊急在出問題的實例上再次啟動應用,啟動后,內存佔用正常,一切Okay。

    奇怪現象

    當前設置的最大堆內存是1792M,如下所示:

    -Xmx1792m -Xms1792m -Xmn900m -XX:PermSi
    ze=256m -XX:MaxPermSize=256m -server -Xss512k 
    

    查看操作系統層面的監控,發現內存佔用情況如下圖所示:

    上圖藍色的線表示總的內存使用量,發現一直漲到了4G后,超出了系統限制。
    很明顯,有堆外內存泄露了。

    查找線索

    gc日誌

    一般出現內存泄露,筆者立馬想到的就是查看當時的gc日誌。
    本身應用所採用框架會定時打印出對應的gc日誌,遂查看,發現gc日誌一切正常。對應日誌如下:

    查看了當天的所有gc日誌,發現內存始終會回落到170M左右,並無明顯的增加。要知道JVM進程本身佔用的內存可是接近4G(加上其它進程,例如日誌進程就已經到4G了),進一步確認是堆外內存導致。

    排查代碼

    打開線上服務對應對應代碼,查了一圈,發現沒有任何地方顯式利用堆外內存,其沒有依賴任何額外的native方法。關於網絡IO的代碼也是託管給Tomcat,很明顯,作為一個全世界廣泛流行的Web服務器,Tomcat不大可能有堆外內存泄露。

    進一步查找

    由於在代碼層面沒有發現堆外內存的痕迹,那就繼續找些其它的信息,希望能發現蛛絲馬跡。

    Dump出JVM的Heap堆

    由於線上出問題的Server已經被kill,還好有其它幾台,登上去發現它們也 佔用了很大的堆外內存,只是還沒有到觸發OOM的臨界點而已。於是就趕緊用jmap dump了兩台機器中應用JVM的堆情況,這兩台留做現場保留不動,然後將其它機器迅速重啟,以防同時被OOM導致服務不可用。
    使用如下命令dump:

    jmap -dump:format=b,file=heap.bin [pid]
    

    使用MAT分析Heap文件

    挑了一個heap文件進行分析,堆的使用情況如下圖所示:

    一共用了200多M,和之前gc文件打印出來的170M相差不大,遠遠沒有到4G的程度。
    不得不說MAT是個非常好用的工具,它可以提示你可能內存泄露的點:

    這個cachedBnsClient類有12452個實例,佔用了整個堆的61.92%。
    查看了另一個heap文件,發現也是同樣的情況。這個地方肯定有內存泄露,但是也佔用了130多M,和4G相差甚遠。

    查看對應的代碼

    系統中大部分對於CachedBnsClient的調用,都是通過註解Autowired的,這部分實例數很少。
    唯一頻繁產生此類實例的代碼如下所示:

    @Override
        public void fun() {
                BnsClient bnsClient = new CachedBnsClient();
              // do something
        		return  ;
    	}
    

    此CachedBnsClient僅僅在方法體內使用,並沒有逃逸到外面,再看此類本身

    public class CachedBnsClient   {
        private ConcurrentHashMap<String, List<String>> authCache = new ConcurrentHashMap<String, List<String>>();
        private ConcurrentHashMap<String, List<URI>> validUriCache = new ConcurrentHashMap<String, List<URI>>();
        private ConcurrentHashMap<String, List<URI>> uriCache = new ConcurrentHashMap<String, List<URI>>();
    	......
    }
    

    沒有任何static變量,同時也沒有往任何全局變量註冊自身。換言之,在類的成員(Member)中,是不可能出現內存泄露的。
    當時只粗略的過了一過成員變量,回過頭來細想,還是漏了不少地方的。

    更多信息

    由於代碼排查下來,感覺這塊不應該出現內存泄露(但是事實確是如此的打臉)。這個類也沒有顯式用到堆外內存,而且只佔了130M,和4G比起來微不足道,還是先去追查主要矛盾再說。

    使用jstack dump線程信息

    現場信息越多,越能找出蛛絲馬跡。先用jstack把線程信息dump下來看下。
    這一看,立馬發現了不同,除了正常的IO線程以及框架本身的一些守護線程外,竟然還多出來了12563多個線程。

    "Thread-5" daemon prio=10 tid=0x00007fb79426e000 nid=0x7346 waiting on condition [0x00007fb7b5678000]
       java.lang.Thread.State: TIMED_WAITING (sleeping)
    	at java.lang.Thread.sleep(Native Method)
    	at com.xxxxx.CachedBnsClient$1.run(CachedBnsClient.java:62)
    

    而且這些正好是運行再CachedBnsClient的run方法上面!這些特定線程的數量正好是12452個,和cachedBnsClient數量一致!

    再次check對應代碼

    原來剛才看CachedBnsClient代碼的時候遺漏掉了一個關鍵的點!

        public CachedBnsClient(BnsClient client) {
            super();
            this.backendClient = client;
            new Thread() {
                @Override
                public void run() {
                    for (; ; ) {
                        refreshCache();
                        try {
                            Thread.sleep(60 * 1000);
                        } catch (InterruptedException e) {
                            logger.error("出錯", e);
                        }
                    }
                }
                ......
            }.start();
        }
    

    這段代碼是CachedBnsClient的構造函數,其在裏面創建了一個無限循環的線程,每隔60s啟動一次刷新一下裏面的緩存!

    找到關鍵點

    在看到12452個等待在CachedBnsClient.run的業務的一瞬間筆者就意識到,肯定是這邊的線程導致對外內存泄露了。下面就是根據線程大小計算其泄露內存量是不是確實能夠引起OOM了。

    發現內存計算對不上

    由於我們這邊設置的Xss是512K,即一個線程棧大小是512K,而由於線程共享其它MM單元(線程本地內存是是現在線程棧上的),所以實際線程堆外內存佔用數量也是512K。進行如下計算:

    12563 * 512K = 6331M = 6.3G
    

    整個環境一共4G,加上JVM堆內存1.8G(1792M),已經明顯的超過了4G。

    (6.3G + 1.8G)=8.1G > 4G
    

    如果按照此計算,應用應用早就被OOM了。

    怎麼回事呢?

    為了解決這個問題,筆者又思考了好久。如下所示:

    Java線程底層實現

    JVM的線程在linux上底層是調用NPTL(Native Posix Thread Library)來創建的,一個JVM線程就對應linux的lwp(輕量級進程,也是進程,只不過共享了mm_struct,用來實現線程),一個thread.start就相當於do_fork了一把。
    其中,我們在JVM啟動時候設置了-Xss=512K(即線程棧大小),這512K中然後有8K是必須使用的,這8K是由進程的內核棧和thread_info公用的,放在兩塊連續的物理頁框上。如下圖所示:

    眾所周知,一個進程(包括lwp)包括內核棧和用戶棧,內核棧+thread_info用了8K,那麼用戶態的棧可用內存就是:

    512K-8K=504K
    

    如下圖所示:

    Linux實際物理內存映射

    事實上linux對物理內存的使用非常的摳門,一開始只是分配了虛擬內存的線性區,並沒有分配實際的物理內存,只有推到最後使用的時候才分配具體的物理內存,即所謂的請求調頁。如下圖所示:

    查看smaps進程內存使用信息

    使用如下命令,查看

    cat /proc/[pid]/smaps > smaps.txt
    

    實際物理內存使用信息,如下所示:

    7fa69a6d1000-7fa69a74f000 rwxp 00000000 00:00 0 
    Size:                504 kB
    Rss:                  92 kB
    Pss:                  92 kB
    Shared_Clean:          0 kB
    Shared_Dirty:          0 kB
    Private_Clean:         0 kB
    Private_Dirty:        92 kB
    Referenced:           92 kB
    Anonymous:            92 kB
    AnonHugePages:         0 kB
    Swap:                  0 kB
    KernelPageSize:        4 kB
    MMUPageSize:           4 kB
    
    7fa69a7d3000-7fa69a851000 rwxp 00000000 00:00 0 
    Size:                504 kB
    Rss:                 152 kB
    Pss:                 152 kB
    Shared_Clean:          0 kB
    Shared_Dirty:          0 kB
    Private_Clean:         0 kB
    Private_Dirty:       152 kB
    Referenced:          152 kB
    Anonymous:           152 kB
    AnonHugePages:         0 kB
    Swap:                  0 kB
    KernelPageSize:        4 kB
    MMUPageSize:           4 kB
    

    搜索下504KB,正好是12563個,對了12563個線程,其中Rss表示實際物理內存(含共享庫)92KB,Pss表示實際物理內存(按比例共享庫)92KB(由於沒有共享庫,所以Rss==Pss),以第一個7fa69a6d1000-7fa69a74f000線性區來看,其映射了92KB的空間,第二個映射了152KB的空間。如下圖所示:

    挑出符合條件(即size是504K)的幾十組看了下,基本都在92K-152K之間,再加上內核棧8K

    (92+152)/2+8K=130K,由於是估算,取整為128K,即反映此應用平均線程棧大小。
    

    注意,實際內存有波動的原因是由於環境不同,從而走了不同的分支,導致棧上的增長不同。

    重新進行內存計算

    JVM一開始申請了

    -Xmx1792m -Xms1792m
    

    即1.8G的堆內內存,這裡是即時分配,一開始就用物理頁框填充。
    12563個線程,每個線程棧平均大小128K,即:

    128K * 12563=1570M=1.5G的對外內存
    

    取個整數128K,就能反映出平均水平。再拿這個128K * 12563 =1570M = 1.5G,加上JVM的1.8G,就已經達到了3.3G,再加上kernel和日誌傳輸進程等使用的內存數量,確實已經接近了4G,這樣內存就對應上了!(注:用於定量內存計算的環境是一台內存用量將近4G,但還沒OOM的機器)

    為什麼在物理機上沒有應用Down機

    筆者登錄了原來物理機,應用還在跑,發現其同樣有堆外內存泄露的現象,其物理內存使用已經達到了5個多G!幸好物理機內存很大,而且此應用發布還比較頻繁,所以沒有被OOM。
    Dump了物理機上應用的線程,

    一共有28737個線程,其中28626個線程等待在CachedBnsClient上。 
    

    同樣用smaps查看進程實際內存信息,其平均大小依舊為

    128K,因為是同一應用的原因
    

    繼續進行物理內存計算

    1.8+(28737 * 128k)/1024K =(3.6+1.8)=5.4G
    

    進一步驗證了我們的推理。

    這麼多線程應用為什麼沒有卡頓

    因為基本所有的線程都睡眠在

     Thread.sleep(60 * 1000);//一次睡眠60s
    

    上。所以僅僅佔用了內存,實際佔用的CPU時間很少。

    總結

    查找Bug的時候,現場信息越多越好,同時定位Bug必須要有實質性的證據。例如內存泄露就要用你推測出的模型進行定量分析。在定量和實際對不上的時候,深挖下去,你會發現不一樣的風景!

    公眾號

    關注筆者公眾號,獲取更多乾貨文章:

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

    【其他文章推薦】

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

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

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

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

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

    ※超省錢租車方案

    ※回頭車貨運收費標準

  • Kubernetes日誌的6個最佳實踐

    Kubernetes日誌的6個最佳實踐

    本文轉自Rancher Labs

    Kubernetes可以幫助管理部署在Pod中的上百個容器的生命周期。它是高度分佈式的並且各個部分是動態的。一個已經實現的Kubernetes環境通常涉及帶有集群和節點的幾個系統,這些系統託管着幾百個容器,而這些容器不斷地基於工作負載啟動、毀滅。

    當在Kubernetes中處理大量的容器化應用和工作負載時,主動進行監控和調試錯誤十分重要。在容器、節點或集群級別,這些錯誤都能在容器中看到。Kubernetes的日誌機制是一個十分重要的組件,可以用來管理和監控服務以及基礎設施。在Kubernetes中,日誌可以讓你跟蹤錯誤甚至可以調整託管應用程序的容器的性能。

    配置stdout(標準輸出)和stderr(標準錯誤)數據流

    圖片來源:kubernetes.io

    第一步是理解日誌是如何生成的。通過Kubernetes,日誌會被發送到兩個數據流——stdout和stderr。這些數據流將寫入JSON文件,並且此過程由Kubernetes內部處理。你可以配置將哪個日誌發送到哪個數據流中。而一個最佳實踐的建議是將所有應用程序日誌都發送到stdout並且所有錯誤日誌都發送到stderr。

    決定是否使用Sidecar模型

    Kubernetes建議使用sidecar容器來收集日誌。在這一方法中,每個應用程序容器將有一個鄰近的“streaming容器”,該容器將會將所有日誌流傳輸到stdout和stderr。Sidecar模型可以幫助避免在節點級別公開日誌,並且它可以讓你控制容器級別的日誌。

    然而,這一模型的問題是它能夠適用於小容量的日誌記錄,如果面對大規模的日誌記錄,可能會造成大量資源被佔用。因此,你需要為每個正在運行的應用程序容器單獨運行一個日誌容器。在Kubernetes文檔中,將sidecar模型形容為“幾乎沒有很大的開銷”。需要由你決定是否嘗試這一模型並在選擇它之前查看它所消耗的資源類型。

    替代方法是使用日誌代理,該代理在節點級別收集日誌。這樣可以減少開銷,並確保安全地處理日誌。Fluentd已成為大規模聚合Kubernetes日誌的最佳選擇。它充當Kubernetes與你要使用Kubernetes日誌的任意數量的端點之間的橋樑。你也可以選擇像Rancher這樣的Kubernetes管理平台,在應用商店已經集成了Fluentd,無需從頭開始安裝配置。

    確定Fluentd可以更好地匯總和路由日誌數據后,下一步就是確定如何存儲和分析日誌數據。

    選擇日誌分析工具:EFK或專用日誌記錄

    傳統上,對於以本地服務器為中心的系統,應用程序日誌存儲在系統中的日誌文件中。這些文件可以在定義的位置看到,也可以移動到中央服務器。但是對於Kubernetes,所有日誌都發送到磁盤上/var/log的JSON文件中。這種類型的日誌聚合併不安全,因為節點中的Pod可以是臨時的也可以是短暫的。刪除Pod時,日誌文件將丟失。如果你需要嘗試對部分日誌數據丟失進行故障排除時,這可能很難。

    Kubernetes官方推薦使用兩個選項:將所有日誌發送到Elasticsearch,或使用你選擇的第三方日誌記錄工具。同樣,這裏存在一個潛在的選擇。採用Elasticsearch路線意味着你需要購買一個完整的堆棧,即EFK堆棧,包括Elasticsearch、Fluentd和Kibana。每個工具都有其自己的作用。如上所述,Fluentd可以聚合和路由日誌。Elasticsearch是分析原始日誌數據並提供可讀輸出的強大平台。Kibana是一種開源數據可視化工具,可以從你的日誌數據創建漂亮的定製dashboard。這是一個完全開源的堆棧,是使用Kubernetes進行日誌記錄的強大解決方案。

    儘管如此,有些事情仍然需要牢記。Elasticsearch除了由名為Elastic的組織構建和維護,還有龐大的開源社區開發人員為其做貢獻。儘管經過大量的實踐檢驗,它可以快速、強大地處理大規模數據查詢,但在大規模操作時可能會出現一些問題。如果採用的是自我管理(Self-managed)的Elasticsearch,那麼需要有人了解如何構建大規模平台。

    替代方案是使用基於雲的日誌分析工具來存儲和分析Kubernetes日誌。諸如Sumo Logic和Splunk等工具都是很好的例子。其中一些工具利用Fluentd來將日誌路由到他們平台,而另一些可能有它們自己的自定義日誌代理,該代理位於Kubernetes中的節點級別。這些工具的設置十分簡單,並且使用這些工具可以花費最少的時間從零搭建一個可以查看日誌的dashboard。

    使用RBAC控制對日誌的訪問

    在Kubernetes中身份驗證機制使用的是基於角色訪問控制(RBAC)以驗證一個用戶的訪問和系統權限。根據用戶是否具有特權(authorization.k8s.io/decision )並向用戶授予原因(authorization.k8s.io/reason ),對在操作期間生成的審核日誌進行註釋。默認情況下,審核日誌未激活。建議激活它以跟蹤身份驗證問題,並可以使用kubectl進行設置。

    保持日誌格式一致

    Kubernetes日誌由Kubernetes架構中不同的部分生成。這些聚合的日誌應該格式一致,以便諸如Fluentd或FluentBit的日誌聚合工具更易於處理它們。例如,當配置stdout和stderr或使用Fluentd分配標籤和元數據時,需要牢記這一點。這種結構化日誌提供給Elasticsearch之後,可以減少日誌分析期間的延遲。

    在日誌收集守護進程上設置資源限制

    由於生成了大量日誌,因此很難在集群級別上管理日誌。DaemonSet在Kubernetes中的使用方式與Linux類似。它在後台運行以執行特定任務。Fluentd和filebeat是Kubernetes支持的用於日誌收集的兩個守護程序。我們必須為每個守護程序設置資源限制,以便根據可用的系統資源來優化日誌文件的收集。

    結 論

    Kubernetes包含多個層和組件,因此對其進行良好地監控和跟蹤能夠讓我們在面對故障時從容不迫。Kubernetes鼓勵使用無縫集成的外部“Kubernetes原生”工具進行日誌記錄,從而使管理員更輕鬆地獲取日誌。文章中提到的實踐對於擁有一個健壯的日誌記錄體繫結構很重要,該體繫結構在任何情況下都可以正常工作。它們以優化的方式消耗計算資源,並保持Kubernetes環境的安全性和高性能。

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

    【其他文章推薦】

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

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

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

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

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

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

    ※回頭車貨運收費標準

  • 自主SUV陣型中的又一爆款車型 有實力擠進前十

    自主SUV陣型中的又一爆款車型 有實力擠進前十

    空間人性化更貼心雖然軸距只有2630mm,但是柔軟適中的座椅,方正的車身造型使其乘坐空間表現還是很出色的,前後排都能獲得充裕的頭部和腿部空間,多達22處的人性化儲物空間十分便利,開口較大的後備箱內部很規整,可以輕鬆放入大型物品,後排座椅4/6比例摺疊后更為平坦,擴展性夠強,滿足日常需求。

    緊抓住時間的腳步,東風風神推出了旗下全新的緊湊型SUV-東風風神AX5,並於11月29日正式上市,其定位介於風神AX7和風神AX3之間,秉承着“流•動之美”的設計理念打造,擁有着超高的顏值,它的推出無疑將豐富東風風神旗下的產品線布局,一起來看一下這輛潛力股吧!

    外觀

    高顏值高原創

    第一眼看見東風風神AX5,會讓人有眼前一亮的感覺,外觀造型非常忠於原創,幾乎找不到任何借鑒的影子,秉承着“流•動之美”的設計理念,車子總體以簡潔的線條為主,用豐富的曲線勾勒出飽滿的車體。

    前臉造型富有層次感,配以絢麗的遠近光一體帶透鏡大燈,加上造型獨特的保險杠和大尺寸蜂窩狀進氣格柵,讓車頭看上去更加時尚大氣,立式造型獠牙狀日行燈有着較高的辨識度。

    波浪式的前後分段雙腰線絕對是獨樹一幟的設計,讓側面更加年輕動感,車頂行李架、腳踏板、190mm的離地間隙配以18英寸雙色旋風輪轂,兼顧了美觀和實用性。

    廠家為AX5車窗配備了晴雨擋,雖說成本不高,但是勝在實用性強,夠貼心,全系尾燈均採用LED光源,穿透力更好更厚道,後車窗下面小鴨尾的折角豐富了尾部的層次感,零星的鍍鉻裝飾、雙色保險桿的設計,簡單而不失大氣。

    內飾/配置

    科技享受生活

    內飾整體造型是比較家族化的設計,環抱式的座艙風格比較簡潔、幹練,並沒有太多花哨的地方,用料和做工都很夠到,比較上檔次,懸浮式液晶显示屏是一大亮點。

    搭載了WindLink車載智能互聯繫統也是一大利器,具備了車輛關懷、語音識別、手機App、智能導航等多項功能,功能性上可以媲美奔馳寶馬等豪華品牌系統,撥通控制中心后,說出你想去的地方,客服會自動將導航信息發送至車載導航。

    鏈接手機App后,可實現手機控制車窗的開啟與關閉,車門開鎖、空調控制(夏天就一進到車內就可以享受涼爽的空調了)、道路救援、尋車以及車輛信息显示,就如隨身攜帶了專業的車輛檢測電腦,隨時了解愛車哪些地方出了問題和什麼時候該保養了。

    空間

    人性化更貼心

    雖然軸距只有2630mm,但是柔軟適中的座椅,方正的車身造型使其乘坐空間表現還是很出色的,前後排都能獲得充裕的頭部和腿部空間,多達22處的人性化儲物空間十分便利,開口較大的後備箱內部很規整,可以輕鬆放入大型物品,後排座椅4/6比例摺疊后更為平坦,擴展性夠強,滿足日常需求。

    動力

    平順流暢更輕鬆

    全系搭載東風風神自主研發的1.4T全鋁合金小排量增壓發動機,自重更輕更節能,與之匹配的是一款5速手動變速器,或者是德國的格特拉克生產的6速濕式雙離合變速器,保證低油耗的同時有着高效的動力輸出,駕駛過程中動力輸出夠線性,換擋平順,電動助力方向盤操控精準又省力,調教紮實的底盤很舒適。

    競爭力分析

    突圍而出

    那東風風神AX5到底是不是一款誠意之作呢,我們來看一下全系標配的配置,打紅色框的配置如LED日間行車燈、多功能方向盤、手機智能互聯、定速巡航、智能啟停系統、ESp、上坡輔助等可是一般車型作為高中低配的差距配置,而AX5卻作為全系標配來打造的一款車,性價比很高,優勢就很明顯了,也很有誠意。

    我們再拿銷量比較好的傳祺GS4作對比,看一下風神AX5的競爭力如何。

    安全配置上傳祺GS4的頭部氣囊有所欠缺,倒車影像也沒有,只有個陡坡緩降配置。

    多媒體配置和座椅配置上風神AX5是完爆傳祺GS4,非常的豐富。

    最關鍵的高科技配置如發動機啟停、併線輔助、全景攝像頭風神AX5都有配備,簡直就是越級的配置,要知道20萬級別的SUV都未必有這些配置,競爭力很強。

    全文總結:作為老資格的自主品牌,風神AX5全系搭載1.4T渦輪增壓發動機,也是適應時代的要求,加上6速DCT雙離合變速箱,組成雙T鉑金動力組合,動力強,油耗低,高顏值個性鮮明的外觀有着很高的原創度,相比於競爭對手有着足夠優秀的配置來與之抗衡,WindLink智能互聯繫統非常實用,科技化十足,舒適的乘坐空間能滿足家庭日常所需,綜合實力夠強。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

    ※超省錢租車方案

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

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

    ※回頭車貨運收費標準

  • 8.38萬元起同級別最精緻的SUV車主怎麼說

    8.38萬元起同級別最精緻的SUV車主怎麼說

    但是就是動力不咋的,不符合主流的1。5T發動機的動力表現。這估計就是在你看得見摸得着的地方做的很好,但是內在就不好說了。不滿意的:點煙器和車載電腦的USB接口在扶手箱下面的一個小盒子里,使用極不方便,儲物空間太小,畢竟這個車子的尺寸比較小。

    其實剛開始寫這篇文章我是拒絕的,因為H2s才剛上市,車主根本不好找,但是很多讀者都想進一步了解一下H2s,想知道車主的使用情況。沒辦法,編者只有硬着頭皮,各種找朋友問同學,費了好大的勁才找到了一些車主,下面一起來看看這些車主對他們H2s的評價。

    長城汽車-哈弗H2s

    指導價:8.38-10.28萬

    哈弗H2s是在11月18日廣州車展上市的,憑藉更亮麗的外觀和更精緻的內飾,再加上新的動力系統贏得了很大的關注度。

    車主:雙截棍

    購買車型:2017款 藍標 1.5T 手動精英型

    裸車購買價:8.78萬

    最滿意的地方:外觀很好看,我買車最看重的是顏值,當時對H2s一見傾心。風琴式的中控台個人認為很不錯,有點天馬星空的感覺。不過就是不好打理。萬年不變的1.5T發動機提速較慢,不過畢竟是帶渦輪的發動機,速度上來使勁踩油門提速還是有一點的。我身高1.75米,體重150斤,膝蓋老是碰到車前面的塑料蓋,所以大長腿的慎買啊!

    不滿意的地方:新車異味好大,異響暫時沒發現,空間比較小,車子沒有胎壓監測。

    車主:馬小虎

    購買車型:2017款 紅標 1.5T 手動精英型

    裸車購買價:8.88萬

    最滿意的地方:當初在紅藍標之間猶豫,後來覺得年輕么。就要選個霸氣的外觀,大嘴的紅標也許更適合自己,雖然紅標比藍標貴了1000塊錢,但是卻多了胎壓監測裝置、后視鏡電動摺疊,這兩個配置還是值1000塊錢的。H2s的整體配置挺高的,內飾做工也比較好。但是就是動力不咋的,不符合主流的1.5T發動機的動力表現。這估計就是在你看得見摸得着的地方做的很好,但是內在就不好說了。

    不滿意的:點煙器和車載電腦的USB接口在扶手箱下面的一個小盒子里,使用極不方便,儲物空間太小,畢竟這個車子的尺寸比較小。車子跑了300公里,油耗9L,新車沒有太大的參考性。

    車主:狗子

    購買車型:2017款 紅標 1.5T 自動精英型

    裸車購買價:9.88萬

    最滿意的地方:價格很實惠,配置比較高,內飾很精緻,做工也很好。其中變速箱為格特拉克的7速濕式雙離合變速箱。十萬的車子用上了濕式雙離合,看來長城這次是真的下本了。以前備受吐槽的哈弗變速箱終於在H2s上得到了改善。這款變速箱反應速度很快,邏輯清晰,平順性很好,底盤調教很紮實,行駛質感很好,但是還是受到那個老舊的發動機的拖累,提速較差。如果換個1.5T發動機,體驗會好很多吧!

    不滿意的:1.5T發動機該換了,座椅靠前了安全帶就跑到了後面,懸架有點硬,兒童座椅的接口處理的很粗糙。油耗目前不到9L。

    總結:H2s用上了7速濕式雙離合變速箱,這一點值得表揚,同時外觀靚麗,配置較高,內飾精緻,行駛質感較好。但是1.5T發動機確實該換了。如果很在乎動力,那麼H2s會讓你失望的。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

    ※超省錢租車方案

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

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

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

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

  • 如果你也喜歡自主品牌,那麼這些事情你有必要了解!

    如果你也喜歡自主品牌,那麼這些事情你有必要了解!

    可尼瑪事與願違啊。剛過去俗稱“金九銀十”銷售旺季的10月份,我國的乘用車銷量共計234。41萬輛,而光轎車就賣出了117。05萬輛。哪怕現在SUV市場再怎麼火,當月的轎車銷量依然要比SUV多出27。45萬輛。所以事實就是,轎車依然是中國汽車市場上最好賣的車型。

    憑藉著SUV浪潮的興起,我國的自主品牌頓時間風生水起,長城、吉利、傳祺…迅速取代合資品牌,成為中國車市的第一驅動力。甚至連一些名不見經傳的品牌,譬如力帆、野馬等也都憑藉推出SUV過上了翻身農奴把歌唱的好日子。咋一看,革面全面勝利,形勢一片大好!然鵝,在一片繁榮的背後貌似隱藏着危機四伏的危險。

    這個危機四伏的危險是什麼?

    咱們的自主品牌相當缺乏能夠拿得出手的轎車產品呀!除了帝豪、帝豪GL、艾瑞澤5、逸動之外,大家還能叫得上幾款口碑、銷量都不錯的自主品牌轎車么?沒有了。如果要說不光口碑好、銷量好,而且還有歷史傳承的自主品牌轎車,那就只有累計銷量剛剛突破百萬輛的帝豪了。而合資品牌呢?朗逸、福睿斯、高爾夫、卡羅拉,個個的銷量、口碑都是彪悍級的。

    SUV市場火熱,就拚命地研發SUV、銷售SUV固然是人之常情。畢竟吃飽了,才有力氣幹活,把現錢都掙了是正常思維。但咱們自主品牌偏偏是在這樣的思維下走到了極端。最典型的,莫過於剛剛憑藉哈弗H6單月7萬+銷量刷新中國單一車型月均銷量紀錄的長城。哈弗H6在SUV風頭無兩,但旗下的轎車車型C30,近半年來的月均的銷量卻只有千餘輛的水平。略有些營養不良的感覺。

    這個危機到底有多危險?

    有的人說:大家都買SUV了,誰還會買轎車?!我也是這樣想的啊!可尼瑪事與願違啊!剛過去俗稱“金九銀十”銷售旺季的10月份,我國的乘用車銷量共計234.41萬輛,而光轎車就賣出了117.05萬輛。哪怕現在SUV市場再怎麼火,當月的轎車銷量依然要比SUV多出27.45萬輛。所以事實就是,轎車依然是中國汽車市場上最好賣的車型。相信大夥都已經明白到轎車市場的重要性,但絕大多數的自主品牌卻竟然一致地選擇無視這個市場。這不叫集中力量干大事,這叫把所有雞蛋都放一個籃子里。就差合資品牌給你絆一跤,讓你的籃子里的雞蛋都摔破。

    合資品牌在轎車領域有多強勢?

    要說合資品牌跟自主品牌一樣,把全副身家押寶在SUV車型上那還不至於讓人膽戰心驚,但問題就在於合資品牌在轎車領域的牛逼程度簡直是垄斷式經營(不是人家想垄斷,是自主品牌把這市場拱手相讓)。同樣是“金九銀十”的10月份,轎車銷量的前十位企業分別為:一汽大眾、上汽大眾、上汽通用、東風日產、吉利控股、北京現代、長安福特、東風悅達起亞、廣汽本田和神龍汽車。十個茅坑,竟然有九個被合資品牌佔了。更可怕的是,自主品牌壓根連剩下的肉都吃不上。因為這十家車企的轎車銷量,已經佔到了整個轎車市場的72.53%。估計自主品牌也就能夠吃上兩口青菜吧,清淡點。

    自主品牌你們要當個“德智體美勞全面健康發展”的好學生呀!

    說實話,看着自主品牌由民間小作坊生產、向合資企業討落後的技術,到擁有媲美國際一線車企的現代化生產車間(去看看吉利台州的基地就一目瞭然了)以及發動機、汽車互聯等核心技術方面的創新性領先,我作為一个中國人是相當的自豪,我相信大家也是。但也正因為這一份對自主品牌的熱切期待,才更使得我們去發現自主品牌在轎車市場方面的不足,才憂心好不容易才建立起來的中國汽車工業因為轎車市場的乏力而付諸一旦。所以,請自主品牌們發展的步伐再穩一些、再穩一些。

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

    【其他文章推薦】

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

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

    ※回頭車貨運收費標準

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

    ※超省錢租車方案

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

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

  • 國產SUV稱霸!最接地氣的銷量榜解析

    國產SUV稱霸!最接地氣的銷量榜解析

    期望非常大,如果又遇到黑它的人,請大家備好磚頭,你懂的。

    哈弗H6的銷量如此火爆,下個月還能賣7萬輛嗎?期望非常大,如果又遇到黑它的人,請大家備好磚頭,你懂的。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※超省錢租車方案

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

    ※回頭車貨運收費標準

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

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

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

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

  • Action的三種實現方式,struts.xml配置的詳細解釋及其簡單執行過程(二)

    Action的三種實現方式,struts.xml配置的詳細解釋及其簡單執行過程(二)

    勿以惡小而為之,勿以善小而不為————————–劉備

    勸諸君,多行善事積福報,莫作惡

    上一章簡單介紹了Struts2的’兩個蝴蝶飛,你好’ (一),如果沒有看過,請觀看上一章

    一 Action的三種實現方式

    上一章開發的HelloAction和HelloAction2,並沒有繼承任何類或者實現任何接口,但是必須有一個execute() 方法,方法返回值是String類型。

    這樣的代碼不容易理解,更並不能使人看得出這個類是干什麼的,甚至不能區分這個控制器類與普通的Java類有什麼區別,通常開發中不這樣做。

    我們開發者在開發Struts2框架的時候,希望自己寫的這個Action類能夠具有易理解性,且已經支持某些功能,如參數接收,文件上傳等。

    一.一 第一種實現方式(普通Java類,裏面只包含execute()方法)

    package com.yjl.web.action;
    import org.apache.log4j.Logger;
    /**
    * @author 兩個蝴蝶飛
    * @version 創建時間:2018年8月23日 上午9:41:32
    * @description 第一種實現方式,普通java類,
    * 有一個execute()方法,也可以多寫幾個方法,用action中的標籤method來控制,可以正常訪問。
    */
    public class Hello1Action {
    	private static Logger logger=Logger.getLogger(Hello1Action.class);
    	public String execute() {
    		logger.info("兩個蝴蝶飛,web層你好");
    		return "success";
    	}
    }
    

    不具有開發時要求的規範性,且不支持某些struts2自身提供的功能。

    方法名稱只有一個 execute()

    一.二 第二種實現方式(實現Action接口)

    package com.yjl.web.action;
    import com.opensymphony.xwork2.Action;
    /**
    * @author 兩個蝴蝶飛
    * @version 創建時間:2018年8月23日 上午10:54:03
    * @description 第二種實現方式,實現Action接口,重寫裏面的execute()方法
    * 有一個execute()方法和五個String類型的常量
    */
    public class Hello2Action implements Action{
    	@Override
    	public String execute() throws Exception {
    		return Action.SUCCESS;
    		//return Action.ERROR;
    		//return Action.LOGIN;
    		//return Action.NONE;
    		//return Action.INPUT;
    	}
    }
    

    注意,Action接口是xwork2包下的接口。

    實現了Action接口,使開發者能夠看出來這是一個Action,具有了一定程度上的開發規範,

    但是實現了Action接口,所以必須要重寫execute()方法。

    一般自己寫Action,構思好之後上來就直接add(), edit(), delete(). select() 這些業務方法,

    每次都要重寫execute()方法,不太方便。 而且這種方式不具有struts2中某些功能,如驗證框架和國際化。

    Action中接口中有五個常用的結果字符串(好多方法都返回success,error,login,input,none,故將其封裝了一下) .

    這些字符串雖然是大寫,然而真實的值是全部小寫.

    package com.opensymphony.xwork2;
    
    public abstract interface Action
    {
      public static final String SUCCESS = "success";
      public static final String NONE = "none";
      public static final String ERROR = "error";
      public static final String INPUT = "input";
      public static final String LOGIN = "login";
      
      public abstract String execute()
        throws Exception;
    }
    

    一.三 繼承ActionSupport類(官方推薦)

    	package com.yjl.web.action;
    	import com.opensymphony.xwork2.ActionSupport;
    	/**
    	* @author 兩個蝴蝶飛
    	* @version 創建時間:2018年8月23日 上午11:04:20
    	* @description 第三種方式,繼承ActionSupport類。
    	* ActionSupport類實現了Action接口,也有Action中的五個常量.
    	*/
    	public class Hello3Action extends ActionSupport{
    		public String list() {
    			return "list";
    		}
    	}
    
    

    繼承了ActionSupport類,不需要重新寫execute()方法,直接寫業務方法即可。

    ActionSupport類,已經實現了 Action接口。 其具備Action中的五個常量,並且該類還實現了其他接口,

    源代碼:

    	public class ActionSupport implements Action, Validateable, ValidationAware, TextProvider, LocaleProvider, Serializable{
        ...
    	public String execute() throws Exception
      	{
    		//默認返回的是 success 字符串 
      		 return "success";
     	}
     	...
    }
    

    如驗證框架(Validateable,ValidationAware),國際化(LocaleProvider)。

    以後開發中,使用 繼承 ActionSupport 類的形式。

    二 配置文件 struts.xml中節點的詳細解釋

    在src下有一個struts.xml的配置文件,它配置了開發者自己編寫實現的Action,是struts2框架的核心,不能改變文件名稱。(注意,是struts.xml,並不是struts2.xml,並沒有那個2)。

    在struts.xml中,最上面是一個約束, 是一個根節點。

    二.一 修改常量節點

    在struts-core.jar核心包下,有一個包org.apache.struts2包下,有一個default.properties屬性文件,裏面記錄了很多常用的常量,

    其中常見的有:

    struts.i18n.encoding=UTF-8 
    struts.multipart.maxSize=2097152
    struts.action.extension=action,,
    struts.enable.DynamicMethodInvocation = false
    struts.devMode = false
    struts.ui.theme=xhtml
    struts.ognl.allowStaticMethodAccess=false
    

    建議修改后的值為:

    ###國際化操作,編碼格式為UTF-8
    struts.i18n.encoding=UTF-8
    ###上傳文件時最大的上傳大小,默認為2M. 根據項目情況具體填寫值,建議後面加兩個00
    struts.multipart.maxSize=209715200
    ###struts的訪問後綴名, struts1框架默認的是 .do 
    struts.action.extension=action,,
    ###struts是否可以訪問靜態方法
    struts.enable.DynamicMethodInvocation =true
    ###struts是否是開發者模式
    struts.devMode =true
    ###struts中ui標籤的主題,建議為simple
    struts.ui.theme=simple
    ###ognl中是否可以訪問靜態方法,為true
    struts.ognl.allowStaticMethodAccess=true
    

    可以在struts.xml中進行相應的修改,如

     <!--修改國際化編碼 -->
    <constant name="struts.i18n.encoding" value="UTF-8"></constant>
    <!--修改是否為開發者模式 -->
    <constant name="struts.devMode" value="true"></constant>
    

    按照name,value值的形式進行填寫。

    也可以在src下新建一個struts.properties,然後將這些值放置進去,struts也會自動struts.propeties中的常量值的。

    也可以在web.xml中,在 中,以 局部參數的形式傳遞進去。

    建議使用第一種方式,在struts.xml中用 ,畢竟這個文件常常打開,出錯了也容易發現。

    二.二 分模塊開發

    在實際的項目中,有很多的模塊,如果所有的配置都放在一個struts.xml,那麼一旦這個struts.xml被其他人誤操作導致了錯誤,那麼其他人的項目將無法運行的,當配置內容過多時,struts.xml的內容太長,不便於維護,所以最好是分模塊開發,一個模塊用一個配置文件,然後再利用 進行導入, 類似 於jsp中的 靜態包含一樣。

    所以建議每一個模塊都寫一個模塊.xml,然後在struts.xml中引入即可。如有三個模塊 User模塊和Class,Course,那麼可以將User的配置放置在user.xml中,Class配置放置在class.xml中,course模塊放置在course.xml,在struts.xml中只需要

    	<include file="user.xml"></include>
    	<include file="class.xml"></include>
    	<include file="course.xml"></include>
    

    靜態包含即可。 注意,file的文件路徑引用是否輸入正確。

    正確的位置引用,點擊ctrl+模塊.xml時,可以跳轉到相應的.xml文件中。如果沒有跳轉和反應,那說明位置引用錯誤,需要重新檢查一下。

    二.三 包節點

    在struts.xml配置文件中,最重要的節點就是package節點。 package,分包。 可以將action進行分包處理。

    這樣每一個action或者每一組action用package進行隔開,便於維護,類似於java中package的概念。

    二.三.一 <package> 節點的使用

    <package name="hello" extends="struts-default" namespace="/">
            <!--具體的Action-->
    </package>
    

    package中name節點是package的名字,是獨一無二的,不能夠重複。 最好與模塊名相同或者起一個有意義的名稱。

    extends節點表示繼承,即package之間可以相互的繼承,來避免重複化功能的編寫。 默認為struts-default。

    struts-default中struts已經定義了很多功能,開發者自己寫的包只需要extends 這個包名struts-default,

    就擁有了struts已經定義好的功能。 如攔截器功能,文件上傳功能。

    用戶也可以自己繼承自己所寫的包 。如父包名為

    那麼子包只需要 , 這樣child包不但擁有struts-default的功能,也擁有parent包中的特殊功能,這也是Java的多重繼承的體現。 所以package的name 要符合標識符的規範,具有可讀性。

    namespace節點表示命名空間,以/開頭,默認是”/” 。是為了在訪問路徑和訪問請求url方面體現package的分包作用. package中的name是在配置文件中體現分包,namespace是在url中體現分包。 建議開發中,namespace的路徑名與name保持一致。 package中的namespace的值與子節點action中name的值,共同構成了完整的訪問請求路徑。

    二.三.二 <package></package> 子節點<action></action>節點的使用

    在Hello3Action中定義兩個方法,一個是list()查詢,一個是add()添加的方法。

    package com.yjl.web.action;
    import org.apache.log4j.Logger;
    import com.opensymphony.xwork2.ActionSupport;
    /**
    * @author 兩個蝴蝶飛
    * @version 創建時間:2018年8月23日 上午11:04:20
    * @description 測試action標籤中method的方法訪問
    */
    public class Hello3Action extends ActionSupport{
    	private static final long serialVersionUID = 8737138848863458260L;
    	Logger logger=Logger.getLogger(Hello3Action.class);
    	public String list() {
    		logger.info("執行list方法");
    		return "list";
    	}
    	public String add() {
    		logger.info("執行add方法");
    		return "add";
    	}
    }
    
    

    標籤,有三個基本的屬性,

    	<action name="list" class="com.yjl.web.action.Hello3Action"
            method="list">
    
    </action>
    

    其中name為action的名字,表示區別一個package包下的不同的action。 其中這個name的值,不應該隨便取,應該是要訪問的方法名。

    在瀏覽器客戶端請求的url為 /項目名/package的namespace名稱/action的name名稱.action;

    class為要訪問的那個Action的全限定名稱,是class,用.(點)進行分隔。

    其中,class 可以省略, 省略默認為 ActionSupport 類, 全限定名稱為: com.opensymphony.xwork2.ActionSupport
    method為要訪問的那個方法名稱,類 extends ActionSupport 后,有很多很多的方法,如list(), add(), delete()等,那麼怎麼知道具體要訪問哪個方法呢? 用method這個屬性. method=”要方法的方法名” ,是方法名。

    action還有一個節點是converter,表示所用的是哪一個類型轉換器。(後面會有相應的解釋)

    很清楚, action 中的 class指定了訪問的是哪一個action, method 指定了訪問的是哪一個具體的方法, 利用了反射技術實現。

    在本實例了有兩個方法,所以要進行寫兩個Action, 一個Action類中會有多個方法,難道要一個個配置多個Action嗎?

    Struts2提供了一些簡單的方式

    二.三.三 配置Action的三種形式

    二.三.三.一 通過配置method的屬性完成

    簡單舉例如下:

    	<action name="list" class="com.yjl.web.action.Hello3Action"
    		method="list">
    			
    	</action>
      <action name="add" class="com.yjl.web.action.Hello3Action"
    		method="add">
                
        </action>
    

    缺點: 有幾個方法,就要配置有幾個action,當方法過多時,不易維護。

    二.三.三.二 通過配置 通配符完成。

    簡單舉例如下:

    	<action name="Hello3_*" class="com.yjl.web.action.Hello3Action"
    		method="{1}">
    			
    		</action>
    

    name的值為: 類簡寫名(去掉Action后)_* method中的值取第一個{1},從1開始,不是從0開始。

    這樣訪問Hello3Action中的list方法,訪問路徑就是 Hello3_list

    訪問Hello3Action中的add方法,訪問路徑就是Hello3_add

    簡化了action的相關配置。

    也有的人配置的更狠, 會配置成_, 即:

    	<action name="*_*" class="com.yjl.web.action.{1}Action"
    		method="{2}">
    			
    		</action>
    

    User類中的list就是User_list, User類中的add就是User_add,

    Class類中的list就是Class_list,Class類中的add就是Class_add

    這樣雖說簡化了開發,但卻不利用 result 節點的維護 ,不建議這樣配置。

    好多類的好多方法返回值,都寫在這一個action 下面,會亂。

    二.三.三.三 動態方法訪問

    不是用 * 通配符,而是用! 號。 即:

    想訪問UserAction中list方法() 前端寫url為 userAction!list.action
    想訪問UserAction中add方法() 前端寫url為 userAction!add.action
    想訪問ClassAction中list方法() 前端寫url為 classAction!list.action
    想訪問ClassAction中add方法() 前端寫url為 classAction!add.action

    這樣訪問也特別的方便。

    這樣的話, action中只需要配置name和class即可。 method已經由外部指定了,不需要寫method的值了。

    需要先添加變量 struts.enable.DynamicMethodInvocation, 使其變成 true,開啟。

    	<constant name="struts.enable.DynamicMethodInvocation" value="true"></constant>
    

    如果是UserAction的話,配置應該是:

    <action name="userAction" class="com.yjl.web.action.UserAction" >
    			
    </action>
    

    ClassAction的話,配置應該是

    <action name="classAction" class="com.yjl.web.action.ClassAction" >
    			
    </action>
    

    二.三.四 action子節點result的配置

    result表示結果,是對方法的返回值進行相應的分析。有兩個屬性,name和type

    	<result name="success" type="dispatcher">/index.jsp</result>
    

    其中name的值要與方法的返回值保持一致。

    如 list方法返回值是return SUCCESS,那麼這個list方法的返回值對應的result的值就是 ,

    如果返回是”hello”, 那麼這個name的返回值就是

    如果在action中配置通配符, name=Hello3_*形式,method=”{1}”, 那麼為了簡化result的配置,可以將result配置成 name={1},

    相應的.jsp,可以變成 /{1}.jsp。

    但這樣必須保證Action中方法的名稱與返回值的名稱相同,並且與跳轉到的jsp的名稱也要相同, 這樣不太好。

    result中type五種常見的形式, dispatcher(轉發到jsp),redirect(重定向到jsp), chain(轉發到另外一個方法),redirectAction(重定向到另外一個方法),stream(上傳和下載流)

    其中dispathcer和redirect是跳轉到jsp,如果想要傳遞數據,用dispather,

    如果不想傳遞數據,用redirect (dispathcer是轉發,redirect是重定向)

    chain,redirectAction是跳轉到action的操作,一般用於這同一個類中兩個方法之間的跳轉,

    如add()添加成功之後,需要跳轉到list()方法進行显示結果,這時就可以配置成:

    	<result name="add" type="redirectAction">Hello3_list</result>
    

    地址url也會相應的改變,如果是chain的話,地址欄是不會改變的。 chain是轉發到action, redirectAction是重定向到action.

    也可以在不同包之間的action進行的跳轉 。

    如 add 方法 想到跳轉到 /class 命名空間下的 Hello2Action 的 list 方法。

    <result name="add" type="redirectAction">
    	<!-- 要跳轉到哪一個命名空間,即哪一個包 -->
    	<param name="namespace">/class</param>
    	<!-- 要跳轉到哪一個Action 不加後綴 -->
    	<param name="actionName">Hello2Action</param>
    	<!-- 跳轉到哪一個方法 -->
    	<param name="method">list</param>
    	<!-- 可能要傳遞的參數. 用ognl表達式,根據情況添加 -->
    	<param name="id">${id}</param>
    </result>
    

    通過 param 標籤來配置帶參還是不帶參。

    二.四 全局結果頁面與局部結果頁面。

    這個全局是相對於package來說的,是package中的全局,並不是所有的struts.xml中的全局,所以全局結果的節點位置應該放在package節點裏面,與action節點平行。 用 節點。

    常用的全局結果頁面有兩種:

    error錯誤頁面,頁面出錯了都显示這個頁面,

    login 登錄頁面, 如果沒有登錄,輸入任何url都會跳轉到login頁面(認證時用)

    noprivilege 沒有權限頁面,如果用戶沒有權限訪問了某一個頁面,會給出相應的提示(授權時用)

    <global-results>
    			<result name="error">/error/error.jsp</result>
    			<result name="login">/login.jsp</result>
                <result name="noprivilege">/noprivilege.jsp</result>
    </global-results>
    

    當全局結果頁面與局部結果頁面發生衝突時,以局部結果頁面為準。

    全局配置時:

    <global-results>
    			<result name="success">/successGlobal.jsp</result>
    </global-results>
    

    在該包下的某個action 的方法result 也返回了 success

    	<result name='success'>success.jsp</result>
    

    那麼,當邏輯視圖為 success時,最終將返回 success.jsp

    二.五 配置跳轉頁面

    在開發中,常常有這麼一種情況,

    請求login.jsp 時,為 /login, 那麼就跳轉到 login.jsp 頁面,

    語法為 register.jsp 時,為 /register, 那麼就跳轉到 register 頁面。

    這個時候,配置 為:

    	<action name="*">
    			<result>/WEB-INF/content/{1}.jsp</result>
    	</action>
    

    將頁面放置在 content 文件夾下面,避免用戶直接訪問 jsp頁面。

    注意,要將此 action 放置在最後, 當所有上面的action都不匹配時,才匹配這一個action.

    三 Struts2的執行流程

    當用戶在客戶端發送一個請求后,如常用的標準的http://localhost:8080/Struts_Hello/user/User_add.action時,

    會經過前端控制器(StrutsPrepareAndExecuteFilter) 過濾器,執行一連串的過濾器鏈,然後根據user 找到了對應的package的namespape,進入到具體的package包下。 利用通配符的方式進行訪問,User_add會進行匹配相應的action,根據class和method找到是哪一個類的哪一個方法,在實例化類Action之前,會先執行攔截器。通過反射實例化類,運行方法, 方法運行成功之後,有一個返回值,這個返回值會與剛才action下的 中的name進行相應的匹配,匹配到哪一個,就執行哪一個result。 如果是diapatcher或者redirect,就显示到相應的.jsp頁面(帶有數據), 如果是chain或者redirectAction,那麼就去執行那一個方法,之後進行返回具體的視圖。

    執行過程圖如下:

    謝謝您的觀看!!!

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

    【其他文章推薦】

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

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

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

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

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

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

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