[UWP]使用CompositionAPI的翻轉動畫

1. 運行效果

在 這篇文章里我介紹了一個包含長陰影的番茄鍾,這個番茄鍾在狀態切換時用到了翻轉動畫,效果如上所示,還用到了彈簧動畫,可以看到翻轉後有點回彈。本來打算自己這個動畫效果寫的,但火火已經寫好了這個FlipSide控件,Github地址在,這篇文章就介紹下這個控件的部分原理。

2. TransformMatrix

Visual的 屬性是一個 的struct,它是應用於元素的轉換矩陣,可以進行動畫處理。它的默認值如下:

這時候動畫效果如下:

要使Visual可以正確旋轉需要按以下方式處理:

private void UpdateTransformMatrix(FrameworkElement element)
{
    var host = ElementCompositionPreview.GetElementVisual(element);
    var size = element.RenderSize.ToVector2();
    if (size.X == 0 || size.Y == 0) return;
    var n = -1f / size.X;

    Matrix4x4 perspective = new Matrix4x4(
        1.0f, 0.0f, 0.0f, 0.0f,
        0.0f, 1.0f, 0.0f, 0.0f,
        0.0f, 0.0f, 1.0f, n,
        0.0f, 0.0f, 0.0f, 1.0f);

    host.TransformMatrix =
        Matrix4x4.CreateTranslation(-size.X / 2, -size.Y / 2, 0f) *
        perspective *
        Matrix4x4.CreateTranslation(size.X / 2, size.Y / 2, 0f);
}

講真我也不明白為什麼要這麼寫,只知道是從微軟的 里抄的。每當SizeChanged事件發生時都需要調用這個函數重新設置TransformMatrix。

3. RotationAngleInDegrees

Visual包含兩個相似的屬性, 和 ,它們的定義如下:

//
// 摘要:
//     視覺對象的旋轉角度(以度為單位)。 可動畫處理。
//
// 返回結果:
//     The rotation angle of the visual in degrees.
public float RotationAngleInDegrees { get; set; }
//
// 摘要:
//     視覺對象的旋轉角度(以弧度為單位)。 可動畫處理。
//
// 返回結果:
//     The rotation angle in radians of the visual.
public float RotationAngle { get; set; }

這兩個屬性都用於控制Visua圍繞着RotationAxis和CenterPoint旋轉。在FlipSide這個控件里RotationAngleInDegrees比較適用:

float f1 = 0f, f2 = 0f;
if (IsFlipped)
{
    f1 = 180f;
    f2 = 360f;
    VisualStateManager.GoToState(this, "Slide2", false);
}
else
{
    f1 = 0f;
    f2 = 180f;
    VisualStateManager.GoToState(this, "Slide1", false);
}
if (springAnimation1 != null && springAnimation2 != null)
{
    springAnimation1.FinalValue = f1;
    springAnimation2.FinalValue = f2;
    s1Visual.StartAnimation("RotationAngleInDegrees", springAnimation1);
    s2Visual.StartAnimation("RotationAngleInDegrees", springAnimation2);
}

這段代碼用到了SpringAnimatin,所以有彈一下的效果。

4. RotationAxis

用於指定Visual旋轉的軸。FlipSide可以通過設置RotationAxis改變翻轉的角度,例如火火的Demo里使用根據鼠標改變RotationAxis:

private void OnFlipSidePointerReleased(object sender, PointerRoutedEventArgs e)
{
    var position = e.GetCurrentPoint(_FlipSide).Position;
    var v2 = (position.ToVector2() - _FlipSide.RenderSize.ToVector2() / 2);
    _FlipSide.Axis = new Vector2(-v2.Y, v2.X);
}

5. ExpressionAnimation

<controls:FlipSide x:Name="FlipSide" IsFlipped="True">
    <controls:FlipSide.Side1>
        <Grid Background="#FFE87A69" x:Name="InworkElement" CornerRadius="1">
            
        </Grid>
    </controls:FlipSide.Side1>
    <controls:FlipSide.Side2>
        <Grid Background="#FF5271c2" x:Name="BreakElement" CornerRadius="1">
            
        </Grid>
    </controls:FlipSide.Side2>
</controls:FlipSide>

上面XAML為FlipSide的調用代碼,它將Side1和Side2(這個命名超讓高達迷興奮)作為內容显示在UI上,當IsFlipped為False時显示Side1的內容,當IsFlipped為True時代表翻轉過去,此時显示Side2的內容。在翻轉動畫的過程中,何時隱藏Side1並显示Side2是個麻煩事。幸好UWP有強大的表達式動畫(ExpressionAnimation),FlipSide只用了下面幾句代碼處理這個問題:

s1Visual = ElementCompositionPreview.GetElementVisual(Side1Content);
s2Visual = ElementCompositionPreview.GetElementVisual(Side2Content);

var opacity1Animation = compositor.CreateExpressionAnimation("this.Target.RotationAngleInDegrees > 90 ? 0f : 1f");
var opacity2Animation = compositor.CreateExpressionAnimation("(this.Target.RotationAngleInDegrees - 180) > 90 ? 1f : 0f");

s1Visual.StartAnimation("Opacity", opacity1Animation);
s2Visual.StartAnimation("Opacity", opacity2Animation);

這段代碼的意思是當Side1的RotationAngleInDegrees大於90度時隱藏,否則显示;Side2則相反。其中,表達式中的this.Target表示使用這個表達式動畫的Vsual。

表達式動畫的話題很大,這篇文章就割愛了,可以參考下面給出的鏈接了解更多內容:

6. 結語

感謝火火提供了這個控件,讓我可以省下了不少功夫。其實我對TransformMatrix真的不理解,所以這部分只是用,沒辦法詳細介紹。而且我以前對UI里使用3D不感興趣,所以這方面真的沒法寫更多內容。期待火火為這方面補充一些博客。

7. 參考

8. 源碼

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

※幫你省時又省力,新北清潔一流服務好口碑

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

算法核心——空間複雜度和時間複雜度超詳細解析

算法核心——空間複雜度和時間複雜度超詳細解析

一、什麼是算法

算法

  • 一個有限指令集
  • 接受一些輸入(有些情況下不需要收入)
  • 產生輸出
  • 一定在有限步驟之後終止
  • 每一條指令必須:
  1. 有充分明確的目標,不可以有歧義
  2. 計算機能處理的範圍之內
  3. 描述應不依賴於任何一種計算機語言以及具體的實現手段

其實說白了,算法就是一個計算過程解決問題的方法。我們現在已經知道數據結構表示數據是怎麼存儲的,而“程序=數據結構+算法”,數據結構是靜態的,算法是動態的,它們加起來就是程序

對算法來說有輸入,有輸出,相當於函數參數返回值。我們寫算法的時候習慣把算法封裝到一個函數中。

二、什麼是好的算法

好,從上面我們知道了什麼是算法,下面我再說什麼是好的算法
在解決同一個問題的時候,我們通常會有很多種不一樣的算法,區別就在於,有的算法比較笨,有的算法比較聰明,那我們怎麼去衡量它們誰好誰壞呢?我們通常有下面兩個指標:

  • 空間複雜度:根據算法寫成的程序在執行時佔用存儲單元的長度。
  • 時間複雜度:根據算法寫成的程序在執行時耗費時間的長度。

先舉個例子說,如果讓你打印十個整數,你那個程序可能瞬間就給出結果了,如果讓你打印十萬個整數呢?這你就得多等一會了。所以這個程序運行的時間,就跟你要處理的數據是十個還是十萬個是相關的,這個十萬就是我們要處理的數據的規模。我們把它叫做n,是一個變量的話,那我們這個程序所用的時間空間都跟這個n是有直接關係的。解決一個問題有很多中不同的方法,你在設計這個方法的時候,一定要把這兩個要素考慮清楚。一不小心,如果空間複雜度太大的話,你那個程序就可能直接爆掉了,非正常中斷,我一會會在後面講,時間複雜度如果太大的話,你就可能等很長時間都等不出結果。

時間複雜度

先來看上面圖片中的幾組代碼,我是用Python表示的,你在看的時候考慮兩個問題:

 

  1. 四組代碼中,哪組的運行時間最短?
  2. 用什麼方式來體現算法運行的快慢?

剛才說n可以看作數據的規模,規模不一樣,運行時間肯定也不一樣,而且所用時間也不好確定,不同的n會得到不同的時間,所以我們用時間複雜度來表示算法運行的快慢。
先來看下面圖片中的幾個生活中的事件,估計時間:

這裏你會發現我們會用“”表示一個大概,後面還有相應的時間單位,那時間複雜度也參照類似的方法:
時間複雜度:用來評估算法運行效率的一個式子

看上面圖片所示,先說print(‘Hello World’),它的時間複雜度表示為O(1),O嚴格來說,它表示數學上一個式子的上界,我們可以簡單的理解為就是一個估計,大約,相當於上面說的“”。1可以理解為是個運行單位(類似於秒這樣的單位),為什麼是O(1),因為print(‘Hello World’)只執行了一次,同理分析第二個:

 

for i in range(n):
    print('Hello World')

它的時間複雜度表示為O(n),因為這組代碼執行了n次。n還是個單位,同理,分析第三個:

for i in range(n):
    for j in range(n):
        print('Hello World')

它的時間複雜度表示為O(),因為是有兩層循環,所以是,還是個單位。第四個你自己就可以分析了,我就不多此一舉了。但千萬不要以為就是這麼簡單,咱再看下面代碼圖片:

看到這個圖片,你是不是感覺很良好,和你猜的差不多是吧,哈哈,不要高興的太早,告訴你們,錯了,它們的時間複雜度不是這樣的。
為什麼?我說了,“1”是單位,但“3”不是單位,3是3乘1,就比如說在生活中,問你一壺水燒多長時間,沒有人回答說是三個幾分鐘或者幾個三分鐘。再說第二個,是單位,n也是個單位,但是比n大,所以我們在估計時用大單位,就好比生活中問你大概睡了多久,你一般說是幾個小時,而不是說幾個小時零幾分鐘,你強調的是一個大概的時間,明白了吧。
所以正確的時間複雜度是這樣的:

第一個為什麼是O(1),首先print(‘Hello World’)打印一次和打印三次實際的影響不大吧,就是不管執行幾次,只要它的規模不上升到n這麼大的時候,換句話說,1是個單位,所以不管怎樣,因為這是表示近似,不是表示精確的,所以是O(1).好,再看下面這個圖片:

當你的循環減半的時候,時間複雜度就會變為O(logn)。所以你可以這樣記,當算法過程出現循環折半的時候,複雜度式子中會出現logn。

 

時間複雜度小結

  • 時間複雜度是用來估計算法運行時間的一個式子(單位)
  • 一般來說,時間複雜度高的算法比時間複雜度低的算法慢
常見的時間複雜度(按效率排序)

複雜問題的時間複雜度

如何簡單快速地判斷算法複雜度

空間複雜度

在空間複雜度中需要注意的一點就是理解“空間換時間”,在研究一個算法的時候,時間比空間重要。

 

此篇完

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

【其他文章推薦】

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

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

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

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

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

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

系統架構設計師-軟件水平考試(高級)-論文-可靠性設計

系統架構設計師-軟件水平考試(高級)-論文-可靠性

前言

首先說一下為什麼這兩個月又沒消息了,因為這兩個月忙啊。

首先是接收上半年系統分析師的證書,並完成總結。其次是九月份PMP考試(4A通過,尚需努力),然後是十一月的軟考高項的考試。工作的事情就不談了,還好沒什麼私人事情需要處理。所以這兩個月沒什麼空寫博客,不過接下來應該會有一些時間來寫博客。

關於系統架構師這個分支,原本都打算完結了的。然後突然發現大家對系統架構師的論文比較感興趣,並且自從我上次透露了我有一個架構師/分析師的群后,陸陸續續不斷有人私信我加群。所以,就回過頭,再發一篇系統架構師的論文。並打算找時間,將自己系統分析師,PMP,項目管理師的知識整理出來。畢竟在過去的一年的時間,我連續通過系統架構師,系統分析師,PMP,並完成,參加了高項(雖然目前還不知道通過沒),我認為我的學習方法,知識體系等還是有一定作用的,希望對大家有所幫助。嘻嘻。

哦。差點忘了。由於我的架構師/分析師群是邀請制的,所以給你們群號,也是無法添加的。所以,如果有參加架構師/分析師的朋友,請私聊我。謝謝。

一,理論

(強調一下,圖片絕對清晰。如果看不清,請從新的頁面打開,或者下載下來)

論文

摘要:
本人於2015年11月參与浙江省某在線教育平台“外教一對一在線教育”項目,該項目為客戶提供了一對一歐美外教視頻教學,社交圈,公眾直播等功能提供全方位的軟件支撐,在該項目組中我擔任系統架構師崗位,主要負責整體架構設計與中間件選型。本文以該教育平台為例,主要討論了該系統有關可靠性方面的設計與應用,以及遇到的問題與解決方案。一方面通過負載均衡進行容錯技術中冗餘設計的實現,另一方面通過層次架構風格來明確系統結構體系,從而降低系統設計複雜度,提高系統可靠性。整個系統開發工作歷時18個月。目前,該系統已經穩定運行近一年半的時間。實踐證明,通過容錯設計,降低複雜度設計等,系統有效提高了可靠性,從而為公司業務提供持續穩定的服務支撐。

正文:
隨着國家對教育的越發重視,英語教育的市場份額逐步上升,其中用戶口語提升的需求越來越大。為此,一些公司開始提供與外國人聊天的平台。我所在公司決定從國際通訊領域進軍口語教育領域。為了這項戰略轉變,公司於2015年11月設計某在線教育平台系統(一下簡稱為“系統”)。該系統幫助人們與歐美外教進行面對面的口語交流和教學。其中隨意聊提供了一種類似QQ視頻通話,而正式課程還提供了H5互動課件與課後點評等,以提高教學質量。與此同時,還有公眾直播用於拉新,AI測試用於評定學院能力,降低成本。我參与了該項目的開發工作,擔任系統架構設計師職務,負責設計系統架構。本項目組全體成員共9人,我主要負責項目計劃制定,需求分析,整體架構設計與技術選型,以及部分底層設計。該項目的架構工作與次年2月完成,選擇了層次架構風格。整個項目耗時18個月,於2017年5月完成。
目前主流的可靠性設計技術有容錯設計,檢錯設計,降低複雜度設計等技術。容錯設計技術分為恢復塊設計,N版本程序設計和冗餘設計。其中恢復塊設計是選擇一組軟件操作作為容錯設計單元,將普通的程序塊編程恢復塊。N版本程序設計的核心是通過設計出多個模塊或不同版本,對於相同初始條件和相同輸入的操作結果,實現多數表決,防止其中某一軟件模塊/版本的故障提供錯誤的服務,以實現軟件容錯。冗餘設計是在一套完整的軟件系統之外,設計一種不同路徑,不同算法或不同實現方法的模塊或系統作為備份,在出現故障時可以使用冗餘的部分進行替換,從而維持軟件系統的正常運行。缺點是費用和資源的消耗會有所增加。檢錯技術是在軟件出現故障后能及時發現並報警。其缺點是不能自動解決故障。降低複雜度設計是因為軟件複雜性與軟件可靠性有着密切關係,是產生軟件缺陷的重要根源。在設計時考慮降低軟件的複雜性,是提高軟件可靠性的有效方法。

在了解系統需求后,我們決定聽從公司技術顧問的建議,容錯設計主要應用在冗餘設計方面,通過負載均衡,雙機容錯等機制完成冗餘設計。檢錯設計則是通過對Java異常處理機制的設計與封裝處理完成。至於降低複雜度方面,採用層次架構風格,使得系統的結構明確,立體,從而提高系統可靠性。接下來,我將從系統的冗餘設計,複雜度降低設計介紹可靠性在系統中的設計與應用,以及應用過程中遇到的問題與解決方案。

1.冗餘設計:

首先說冗餘設計,冗餘包含邏輯冗餘,數據冗餘,應用冗餘等。這裏以應用冗餘為例。為了提高系統的性能,可靠性,可拓展性等,我們採用了負載均衡技術。常見的負載均衡技術有F5硬件,LVS軟件,Nginx服務器配置等。出於便捷與成本的考慮,我們採用了Nginx服務器配置負載均衡技術。通過對Nginx服務器中upstream模塊的配置,就可以實現在多台服務器的反向代理家在負載均衡。採用負載均衡后,應用服務器集群存在Session問題無法統一的問題。解決方法有Session Sticky,Session Replication,Session數據集中存儲,Cookie Based四個方案。Session Sticky是通過確保同一個會話的請求都在同一個Web服務器上處理實現。Session Replication是增加Web服務器間會話數據的同步來保證不同Web服務器間的Session數據的一致。Cookie Based就是通過Cookie傳遞Session數據完成。經過考慮,我們採用了Session數據集中存儲。Session數據集中存儲通過令每台服務器從專門的session服務器獲取session數據來解決問題。優點是可靠性,可移植性與可拓展性的大幅提高。缺點是一方面讀寫Session數據引入了網絡操作,對數據讀取存在時延和不穩定性,但對於使用內網通信的系統並沒有太大影響。另一方面,如果Session服務器或集群出現問題,將會影響整個應用。我們通過雙機容錯機制解決該問題。除此之外,還有心跳線,看門狗等技術。限於篇幅,不再贅述。

2.降低複雜度設計:

再者就是降低複雜度設計,由於系統的複雜性和綜合性,我們決定採用層次架構風格,將系統架構分為接入層,應用層,服務層,數據層四個層次。這裏以應用層與服務層為例。應用層分為視圖層與業務邏輯層,視圖層負責App與網站的表現效果,業務邏輯層負責業務層的邏輯處理。為了解決系統日益複雜,應用日益臃腫問題,我們將系統按照應用橫向劃分,將系統劃分為課件管理系統,課程管理系統等十餘個子系統。如課件管理系統負責學員上課所用課件,有課件編輯,課件預覽,課件交互等多個功能模塊。功能模塊需調用服務層的服務支撐,如課件交互模塊需要調用stomp通信服務,實現學生與老師間課件的交互功能。另外,課件交互模塊通過對賬戶服務的調用,確立課件雙方的身份,從而明確雙方在課件交互過程中對課件交互部分的交互權限。該劃分使得系統體系變得清晰明了,極大降低系統複雜度,提高系統可靠性。應用層採用基於J2ee的MVC框架-Structs框架,主要通過Servlet和JSP技術實現。另外還有動靜分離,動態資源靜態化等,這裏不再贅述。

服務層提供通用服務。系統在應用層中按照應用橫向劃分,有效降低系統複雜度。但系統代碼仍然存在冗餘,比如用戶信息的調用在諸多應用子系統中都有相關模塊。另外應用的大小依舊十分巨大,複雜,而過小的應用劃分會增加數據庫連接數負擔,故我們提出服務化解決方案。服務化方案就是提取出各個應用的通用服務,如賬戶服務,Session服務等。出於技術成熟度與技術支持等考慮,我們最終採用了阿里的dubbo服務框架,建立服務層。開發過程中,產生了服務框架部署問題與實現服務框架的jar包和應用自身依賴的jar包衝突的問題。前者,我們通過Tomcat作為Web容器,而服務框架作為容器的一部分來解決。後者,我們通過Java的ClassLoader將服務框架自身用的類與應用的類進行隔離。除此之外,我們通過服務線程池隔離,分佈請求合併,服務調用端的流程控制來降低系統複雜度,提高系統可靠性。詳情限於篇幅,不再贅述。

最終項目成功上線,正常運行了近一年半,收到各方好評。尤其是H5課件的良好互動性,使得大量業界同行爭相模仿,改用H5製作課件。還有我們的服務化方案架構被作為許多傳統互聯網企業系統重構的經典方案。在系統的架構設計中,我們引入了層次架構的設計思想,有效地降低了維護成本,提高了系統的開放性,可擴展性,可重用性以及可移植性。當然還是存在一些問題的。如H5課件採用http協議,易被非法劫持,嵌入廣告,可以將協議修改為https來解決。還有我們採用的負載均衡算法是加權輪轉算法,過於簡單,常常出現資源分配不合理的現象,可以將算法改為加權最小連接數算法來解決。這些都是我在今後的系統設計和開發中需要注意與改進的地方,也是日後我應該努力的方向。

三,總結

這篇論文的項目,依舊是之前那片論文的項目-在線教育系統。但是其中很多技術,其實在原有項目中是沒有涉及的。

另外這篇論文與之前論文存在一個結構上的不同之處,那就是這次的核心論點只有兩個分論點。不過,第二個論點-降低複雜度設計,是通過兩個方面進行闡述的。這也算是論文中核心論點的一種回答方式。往往論文的核心論點,推薦使用三個分論點進行論述,而部分論文的核心論點就只能拆分為兩個分論點(或者,三個論點的拆分維度,自己不熟悉)。這時候就需要靈活的轉變自己的思想,將核心論點的兩個分論點氛圍主次論點回答,實際體現就是主論點兩個段落,次論點一個段落。

既然說到這裏,也說一下,如果核心論點可以拆分出多個分論點。如架構風格的層次架構完全可以拆分為接入層,應用層,服務層(基礎服務層,通用服務層,業務服務層),數據接入層,數據源等。那麼這種情況,我們完全可以從中挑選三點自己熟悉的部分,進行闡述。如果擔心這樣寫,文章顯得比較僵硬,就在相關位置寫上“此處,我們以XXX,XXX,XXX為重點,進行論述”這樣的話語即可。

附錄

早期未修改的論文:

摘要:
本人於2015年11月參与浙江省某在線教育平台“外教一對一在線教育”項目,該項目為客戶提供了一對一歐美外教視頻教學,社交圈,公眾直播等功能提供全方位的軟件支撐,在該項目組中我擔任系統架構師崗位,主要負責整體架構設計與中間件選型。本文以該教育平台為例,主要討論了該系統有關可靠性方面的設計與應用。一方面通過負載均衡與應用服務器集群實現容錯技術中冗餘設計的實現,另一方面通過建立了接入層,應用層,服務層,數據層四層層次的架構來降低明確系統結構,從而系統設計複雜度,提高系統可靠性。整個系統開發工作歷時18個月。目前,該系統已經穩定運行近一年半的時間。實踐證明,通過容錯設計,降低複雜度設計等,系統有效提高了可靠性,從而為公司業務提供持續穩定的服務支撐。

正文:
隨着國家對教育的越發重視,英語教育的市場份額逐步上升,其中用戶口語提升的需求越來越大。為此,一些公司開始提供與外國人聊天的平台。我所在公司決定從國際通訊領域進軍口語教育領域。為了這項戰略轉變,公司於2015年11月設計某在線教育平台系統(一下簡稱為“系統”)。該系統幫助人們與歐美外教進行面對面的口語交流和教學。其中隨意聊提供了一種類似QQ視頻通話,而正式課程還提供了H5互動課件與課後點評等,以提高教學質量。與此同時,還有公眾直播用於拉新,AI測試用於評定學院能力,降低成本。我參与了該項目的開發工作,擔任系統架構設計師職務,負責設計系統架構。本項目組全體成員共9人,我主要負責項目計劃制定,需求分析,整體架構設計與技術選型,以及部分底層設計。該項目的架構工作與次年2月完成,選擇了層次架構風格。整個項目耗時18個月,於2017年5月完成。

目前主流的可靠性設計技術有容錯設計,檢錯設計,降低複雜度設計等技術。容錯設計技術分為恢復塊設計,N版本程序設計和冗餘設計。其中恢復塊設計是選擇一組軟件操作作為容錯設計單元,將普通的程序塊編程恢復塊。N版本程序設計的核心是通過設計出多個模塊或不同版本,對於相同初始條件和相同輸入的操作結果,實現多數表決,防止其中某一軟件模塊/版本的故障提供錯誤的服務,以實現軟件容錯。冗餘設計是在一套完整的軟件系統之外,設計一種不同路徑,不同算法或不同實現方法的模塊或系統作為備份,在出現故障時可以使用冗餘的部分進行替換,從而維持軟件系統的正常運行。缺點是費用和資源的消耗會有所增加。檢錯技術是在軟件出現故障后能及時發現並報警。其缺點是不能自動解決故障。降低複雜度設計是因為軟件複雜性與軟件可靠性有着密切關係,是產生軟件缺陷的重要根源。在設計時考慮降低軟件的複雜性,是提高軟件可靠性的有效方法。

在了解系統需求后,我們決定聽從公司技術顧問的建議,在容錯設計,檢錯設計,降低複雜度設計三個主流方向分別作出相應處理和應用。容錯設計主要應用在冗餘設計方面,通過負載均衡,雙機容錯等機制完成冗餘設計。檢錯設計則是通過對Java異常處理機制的設計與封裝處理完成。至於降低複雜度,我們應用層次清晰的四層層次架構。通過將系統劃分為接入層,應用層,服務層,數據層,使得系統的結構明確,立體,從而降低系統複雜度。限於篇幅,接下來,我將從系統的冗餘設計,複雜度降低設計兩個方面介紹可靠性在系統中的設計與應用,以及應用過程中遇到的問題。

首先說冗餘設計,冗餘包含邏輯冗餘,數據冗餘,應用冗餘等。這裏以應用冗餘為例。一方面為了提高應用服務器性能,另一方面為了提高系統的可靠性,可拓展性等,我們採用了負載均衡技術。常見的負載均衡技術有F5硬件,LVS軟件,Nginx服務器配置等。出於便捷與成本的考慮,我們採用了Nginx服務器配置負載均衡技術。通過對Nginx服務器中upstream模塊的配置,就可以實現在多台服務器的反向代理家在負載均衡。為了提高負載均衡服務器可靠性,我們採用雙機熱備機制。但採用負載均衡后,應用服務器集群出現了Session問題無法統一的問題。解決方法有Session Sticky,Session Replication,Session數據集中存儲,Cookie Based四個方案。Session Sticky是通過確保同一個會話的請求都在同一個Web服務器上處理實現。Session Replication是增加Web服務器間會話數據的同步來保證不同Web服務器間的Session數據的一致。但一方面同步Session數據會造成網絡帶寬的開銷。另一方面,每台Web服務器都要保存所有Session數據,消耗大量內存。經過考慮,我們採用了第三種方案-Session數據集中存儲。Session數據集中存儲通過令每台服務器從專門的session服務器獲取session數據來解決問題。優點是可靠性,可移植性與可拓展性的大幅提高。缺點是一方面讀寫Session數據引入了網絡操作,對數據讀取存在時延和不穩定性,但對於使用內網通信的系統並沒有太大影響。另一方面,如果Session服務器或集群出現問題,將會影響整個應用。我們通過雙機容錯機制解決該問題。Cookie Based就是通過Cookie傳遞Session數據完成。實現簡單,但是存在如Cookie長度限制等問題。除此之外,還有心跳線,看門狗等諸多技術。限於篇幅,不再贅述。

再者就是降低複雜度設計,我們從架構風格選擇,技術選型等角度實現。由於系統的複雜性和綜合性,我們決定採用層次架構風格,將系統架構分為接入層,應用層,服務層,數據層四個層次。接入層負責多平台的接入,以及API網關,負載均衡等方面。API網關的使用使得對外資源與服務獲得統一,保持系統結構的明確,從而提高了系統可靠性。應用層分為視圖層與業務邏輯層,視圖層負責App與網站的表現效果,業務邏輯層負責業務層的邏輯處理。為了解決系統日益複雜,應用日益臃腫問題,我們將系統按照應用橫向劃分,將系統劃分為課件管理系統,課程管理系統等十餘個子系統。這樣的劃分使得系統體系變得清晰明了,極大降低系統複雜度,提高系統可靠性。應用層採用基於J2ee的MVC框架-Structs框架。服務層提供通用服務。系統在應用層中按照應用橫向劃分,有效降低系統複雜度。但系統代碼仍然存在冗餘,比如用戶信息的調用在諸多應用子系統中都有相關模塊。另外應用的大小依舊十分巨大,複雜,而過小的應用劃分會增加數據庫連接數負擔,故我們提出服務化解決方案。服務化方案就是提取出各個應用的通用服務,如賬戶服務,Session服務等。出於技術成熟度與技術支持等考慮,我們最終採用了阿里的dubbo服務框架,建立服務層。數據層涉及緩存,文件系統,數據庫,數據通知服務,搜索系統等模塊。由於用戶對數據訪問具有集中性,故我們基於Spring Cache與Redis實現緩存機制。數據訪問方面,Java已經有很多成熟技術,大致分為專用API方式,JDBC方式,給予ORM或類ORM接口方式三種。最終我們採用了成熟的ORM框架-Mybatis框架,再將框架包裝一層。這樣一方面提高系統開發效率,另一方面提高系統可移植性與可靠性。除此之外,還採用了solr作為數據層搜索引擎,數據訪問層物理部署採用Proxy方式。限於篇幅,不再贅述。

最終項目成功上線,正常運行了近一年半,收到各方好評。尤其是H5課件的良好互動性,使得大量業界同行爭相模仿,改用H5製作課件。還有我們的服務化方案架構被作為許多傳統互聯網企業系統重構的經典方案。在系統的架構設計中,我們引入了層次架構的設計思想,有效地降低了維護成本,提高了系統的開放性,可擴展性,可重用性以及可移植性。當然還是存在一些問題的。如H5課件採用http協議,易被非法劫持,嵌入廣告,可以將協議修改為https來解決。還有我們採用的負載均衡算法是加權輪轉算法,過於簡單,常常出現資源分配不合理的現象,可以將算法改為加權最小連接數算法來解決。這些都是我在今後的系統設計和開發中需要注意與改進的地方,也是日後我應該努力的方向。

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

新北清潔公司,居家、辦公、裝潢細清專業服務

撥亂反正-重構是門藝術活

前言

引用自: 《重構 改善既有代碼的設計》

重構是在不改變軟件可觀察行為的前提下改善其內部結構。當你面對一個最需要重構的遺留系統時,其規模之大、歷史之久、代碼質量之差,常會使得添加單元測試或者理解其邏輯都成為不可能的任務。此時你唯一能依靠的就是那些已經被證明是行為保持的重構手法: 用絕對安全的手法從焦油坑中整理出可測試的接口,給它添加測試,以此作為繼續重構的立足點。

因為我們部門內容平台的文章系統之前遺留了很多問題,急需解決這些具有”壞味道”的代碼。最後因為其他人手頭裡都有其他工作,最後這些任務就交給了我。以下是急需解決的問題。

  1. 內容平台新增/更新/取消/刪除文章,同步各集團下文章行為狀態,消息鏈路過長的問題。
  2. article分享錶停止規模新增,之前未做插入前的記錄判斷,通過新增的操作來進行記錄留存。
  3. 文章表拆除大字段到分表,如content、content_draft等字段。

鏈路過長概述

內容平台新增/更新/取消/刪除文章,同步各集團下文章行為狀態,消息鏈路過長的問題。

  • 問題導火索: 運營後台文章發布,發送消息到marketing-base

  • 慢鏈路,鏈路過長

    • mysql數據同步,單條執行n次

    • es索引數據同步,dubbo接口調用n次

圖1 鏈路圖

鏈路過長剖解及解決思路

具體問題,具體對待

//開啟同步開關的集團
        List<Integer> groupList = autoSyncStatusService.getAutoSyncGroupByManageType(MANAGE_TYPE_GROUP_ARTICLE); 

    for (Integer groupId : syncSubjectList) {
                SiteGroupInfoDTO siteGroupInfo = siteSPI.getGroupInfoById(groupId);
                Set<String> groupBrandSet = carOnSaleManage.getGroupBrandSet(siteGroupInfo);
                List<String> matchedBrandCodes = extractBrandCodesFromArticleLabel(article.getLabelInfos());
                if (CollectionUtils.isEmpty(matchedBrandCodes) || CollectionUtils.containsAny(groupBrandSet, matchedBrandCodes)) {
                    ArticleGroupMaterialBO groupMaterialBO =
                            ArticleBeanConverter.convertMaterial2GroupMaterial(article, groupId, groupList);
                    // 設置對應的集團主題id
                    ArticleGroupSubjectBO groupSubjectBO =
                            articleGroupSubjectService.getGroupSubjectBySoucheId(groupId, article.getSubjectId());
                    if (Objects.nonNull(groupSubjectBO.getId())) {
                        groupMaterialBO.setSubjectId(groupSubjectBO.getId());
                        groupMaterialBO.setMaterialId(myArticleId);
                        articleGroupMaterialService.addArticleGroupMaterial(groupMaterialBO);
                    }
            }
        } else {
                //查詢同步的文章數據是否存在
                List<ArticleGroupMaterialBO> list = articleGroupMaterialService.getListByMaterialId(myArticleId);
                for (ArticleGroupMaterialBO a : list) {
                    if (groupList.contains(a.getGroupId())) {
                        articleGroupMaterialService.changeRecommendStatus(a.getId(), a.getGroupId(), recommend, article.getLastOperatorName(), article.getLastOperatorName());
                    }
                }
        }
  • 第4行中我們可以看到這裡有一個for循環️,假設開啟同步開關的集體有1000家,則第18行中mysql插入操作就需要執行1000次。

  • 第24行這裏同樣有一個for循環體️,則26行內部的es數據同步則需要調用1000次。它的實現如下:

    @Override
        public boolean changeRecommendStatus(int id, int groupId, int recommended, String lastOperatorUserId, String lastOperatorName) {
            final boolean success = articleGroupMaterialDAO.changeRecommendStatus(
                    id, groupId, recommended, lastOperatorUserId, lastOperatorName) > 0;
            if (success) {
                //更新索引,更改推薦狀態
                articleSearchManage.updateArticleIndex(ArticleIndexUtil.getUpdateRecommendIndex(recommended, id, lastOperatorName));
            }
            return success;
        }

    解決思路

    Mybatis批量插入

    對於第一個循環️體中,我們需要將數據批量添加到數據庫,mybatis提供了將list集合循環添加到數據庫的方法。

    1. mapper層中創建 insertForeach(List < Fund > list) 方法,返回值是批量添加的數據條數
    public interface FundMapper {
      int insertForeach(List<Fund> list);
    }
    1. mybatis的xml文件中的insert語句如下
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
    <mapper namespace="com.center.manager.mapper.FundMapper">
    
      <insert id="insertForeach" parameterType="java.util.List" useGeneratedKeys="false">
                  insert into fund
                  ( id,fund_name,fund_code,date_x,data_y,create_by,create_date,update_by,update_date,remarks,del_flag)
                  values
                  <foreach collection="list" item="item" index="index" separator=",">
                      (
                          #{item.id},
                          #{item.fundName},
                          #{item.fundCode},
                          #{item.dateX},
                          #{item.dataY},
                          #{item.createBy},
                          #{item.createDate},
                          #{item.updateBy},
                          #{item.updateDate},
                          #{item.remarks},
                          #{item.delFlag}
                      )
                   </foreach>     
        </insert>    
    </mapper>
    ES批量更新

    com.souche.elastic.search.api.IndexService

    方法:BulkUpdateResponse bulkUpdate(String index, Map<String, Object> event, String query, String origin)
    
    參數:
    
        index:要操作的索引
    
        event:更新的數據,可以只包含需要更新的字段,相當於mysql的update語句中的set語句中的字段
    
        query:query中的條件相當於mysql中的where,具體語法與下面的搜索接口中【querys:string 複雜的複合查詢 不同字段的OR 查詢】相同
    
        origin:操作源,一般寫調用方自己的應用名,用於區分不同調用方
    
    返回值:
    
        BulkUpdateResponse:
    
          {
    
            requestId:本次操作的唯一標示
    
            status:狀態,目前返回默認都是true
    
            updated:成功更新的條數
    
            failed:更新失敗的條數
    
            message:第一條更新失敗的原因
    
          }
    
    調用示例:
    1Map<String, Object> data = new HashMap<>();
    2        data.put("id", 20);
    3        data.put("title", "xue yin");
    4        data.put("content", "kuang dao");
    5        BulkUpdateResponse response = indexService.bulkUpdate("test_index", data, "address=bj AND contry=cn", "shenfl");

    這條更新將test_index索引中所有 address是bj並且contry是cn 的數據的 title更新成‘xue yin’ content更新成‘kuang dao’,注意:address和contry兩個字段在索引中需要加索引

Article表插入邏輯優化,停止規模新增概述

Article邏輯優化剖解及解決思路

具體問題及解決思路

當前article數據表數據量:

select count(*) as 總數 from article;

結果如下:

總數
369737
  @Override
    public String addSharedArticle(ArticleBO articleBO) {
        ArticleDO articleDO = new ArticleDO();
        BeanUtils.copyProperties(articleBO, articleDO);
        String shortUUID = UUIDUtil.getShortUUID();
        articleDO.setUid(shortUUID);
        if (articleDAO.addSharedArticle(articleDO) > 0) {
            return shortUUID;
        }
        return StringUtil.EMPTY_STRING;
    }

從上面這個業務邏輯實現類中,我們可以看到事實上我們想得到的是插入表數據的uid。但是之前的邏輯中,我們並沒有判斷該條數據是否已經存在,我們需要在上面代碼中判斷數據是否存在,已存在,查詢最後一天數據的uid返回給上層。不存在的話,執行插入操作。

文章表拆除大字段到分表

article_material表結構設計

article_material | CREATE TABLE `article_material` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `my_article_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '內容平台我的文章id',
  `status` tinyint(3) unsigned NOT NULL COMMENT '1-待發布、2-發布、3-取消發布',
  `subject_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '主題id',
  `platform_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '平台id',
  `source` varchar(32) NOT NULL DEFAULT '' COMMENT '版塊',
  `crawler_article_id` varchar(32) NOT NULL DEFAULT '0' COMMENT '爬蟲的文章id',
  `title` varchar(64) NOT NULL DEFAULT '' COMMENT '標題',
  `cover_img` varchar(128) NOT NULL COMMENT '封面圖',
  `summary` varchar(255) NOT NULL DEFAULT '' COMMENT '摘要',
  `labels` varchar(512) NOT NULL DEFAULT '' COMMENT '標籤',
  `label_infos` varchar(1024) NOT NULL DEFAULT '' COMMENT '標籤詳細信息',
  `content` text NOT NULL COMMENT '內容,用戶看到的',
  `content_imgs` text NOT NULL COMMENT '內容中圖片',
  `content_videos` varchar(255) NOT NULL DEFAULT '' COMMENT '內容中視頻',
  `content_draft` text NOT NULL COMMENT '草稿內容,編輯后保存到這裏,發布后內容會複製到content,此字段清空',
  `content_imgs_draft` text NOT NULL COMMENT '草稿內容的圖片,同上',
  `content_videos_draft` varchar(255) NOT NULL DEFAULT '' COMMENT '草稿內容的視頻',
  `recommended` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '0-不推薦、1-推薦',
  `author_user_id` varchar(64) NOT NULL DEFAULT '' COMMENT '作者userId',
  `author_name` varchar(16) NOT NULL COMMENT '作者名稱',
  `last_operator_user_id` varchar(64) NOT NULL DEFAULT '' COMMENT '最後操作人userId',
  `last_operator_name` varchar(16) NOT NULL COMMENT '最後操作人名字',
  `publish_date` datetime DEFAULT NULL COMMENT '發布時間',
  `publisher_user_id` varchar(64) NOT NULL DEFAULT '' COMMENT '發布者userId',
  `publisher_name` varchar(16) NOT NULL DEFAULT '' COMMENT '發布者名字',
  `pv` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '流量pv',
  `uv` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '流量uv',
  `share_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '分享次數',
  `share_people_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '分享人數',
  `date_create` datetime NOT NULL,
  `date_update` datetime NOT NULL,
  `date_delete` datetime DEFAULT NULL,
  `deleted` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '0 表示未刪除,刪除后是毫秒級時間戳',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uniq_id` (`my_article_id`),
  KEY `idx_title_label_status` (`subject_id`,`platform_id`,`title`,`label_infos`(255),`source`)
) ENGINE=InnoDB AUTO_INCREMENT=861 DEFAULT CHARSET=utf8 COMMENT='文章素材庫,給集團提供文章素材'

上表中content, content_imgs,content_videos都是text類型等大字段,對於這種類型,我們需要把這種類型的表拆分成2張表 article_metedata和article_content 兩張表。

表拆分圖示

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

【其他文章推薦】

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

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

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

※幫你省時又省力,新北清潔一流服務好口碑

javascript中判斷數據類型

編寫javascript代碼的時候常常要判斷變量,字面量的類型,可以用typeof,instanceof,Array.isArray(),等方法,究竟哪一種最方便,最實用,最省心呢?本問探討這個問題。

1. typeof

1.1 語法

typeof返回一個字符串,表示未經計算的操作數的類型。

語法:typeof(operand) | typeof operand
參數:一個表示對象或原始值的表達式,其類型將被返回
描述:typeof可能返回的值如下:

類型 結果
Undefined “undefined”
Null “object”
Boolean “boolean”
Number “number”
Bigint “bigint”
String “string”
Symbol “symbol”
宿主對象(由JS環境提供) 取決於具體實現
Function對象 “function”
其他任何對象 “object”

從定義和描述上來看,這個語法可以判斷出很多的數據類型,但是仔細觀察,typeof null居然返回的是“object”,讓人摸不着頭腦,下面會具體介紹,先看看這個效果:

    // 數值
    console.log(typeof 37) // number
    console.log(typeof 3.14) // number
    console.log(typeof(42)) // number
    console.log(typeof Math.LN2) // number
    console.log(typeof Infinity) // number
    console.log(typeof NaN) // number 儘管它是Not-A-Number的縮寫,實際NaN是数字計算得到的結果,或者將其他類型變量轉化成数字失敗的結果
    console.log(Number(1)) //number Number(1)構造函數會把參數解析成字面量
    console.log(typeof 42n) //bigint
    // 字符串
    console.log(typeof '') //string
    console.log(typeof 'boo') // string
    console.log(typeof `template literal`) // string
    console.log(typeof '1') //string 內容為数字的字符串仍然是字符串
    console.log(typeof(typeof 1)) //string,typeof總是返回一個字符串
    console.log(typeof String(1)) //string String將任意值轉換成字符串
    // 布爾值
    console.log(typeof true) // boolean
    console.log(typeof false) // boolean
    console.log(typeof Boolean(1)) // boolean Boolean會基於參數是真值還是虛值進行轉換
    console.log(typeof !!(1)) // boolean 兩次調用!!操作想短語Boolean()
    // Undefined
    console.log(typeof undefined) // undefined
    console.log(typeof declaredButUndefinedVariabl) // 未賦值的變量返回undefined
    console.log(typeof undeclaredVariable ) // 未定義的變量返回undefined
    // 對象
    console.log(typeof {a: 1}) //object
    console.log(typeof new Date()) //object
    console.log(typeof /s/) // 正則表達式返回object
    // 下面的例子令人迷惑,非常危險,沒有用處,應避免使用,new操作符返回的實例都是對象
    console.log(typeof new Boolean(true)) // object
    console.log(typeof new Number(1)) // object
    console.log(typeof new String('abc')) // object
    // 函數
    console.log(typeof function () {}) // function
    console.log(typeof class C { }) // function
    console.log(typeof Math.sin) // function 

1.2 迷之null

javascript誕生以來,typeof null都是返回‘object’的,這個是因為javascript中的值由兩部分組成,一部分是表示類型的標籤,另一部分是表示實際的值。對象類型的值類型標籤是0,不巧的是null表示空指針,它的類型標籤也被設計成0,於是就有這個typeof null === ‘object’這個‘惡魔之子’。

曾經有ECMAScript提案讓typeof null返回‘null’,但是該提案被拒絕了。

1.3 使用new操作符

除Function之外所有構造函數的類型都是‘object’,如下:

    var str = new String('String');
    var num = new Number(100)
    console.log(typeof str) // object
    console.log(typeof num) // object
    var func = new Function()
    console.log(typeof func) // function 

1.4 語法中的括號

typeof運算的優先級要高於“+”操作,但是低於圓括號

    var iData = 99
    console.log(typeof iData + ' Wisen') // number Wisen
    console.log(typeof (iData + 'Wisen')) // string 

1.5 判斷正則表達式的兼容性問題

typeof /s/ === 'function'; // Chrome 1-12 , 不符合 ECMAScript 5.1
typeof /s/ === 'object'; // Firefox 5+ , 符合 ECMAScript 5.1 

1.6 錯誤

ECMAScript 2015之前,typeof總能保證對任何所給的操作數都返回一個字符串,即使是沒有聲明,沒有賦值的標示符,typeof也能返回undefined,也就是說使用typeof永遠不會報錯。

但是ES6中加入了塊級作用域以及let,const命令之後,在變量聲明之前使用由let,const聲明的變量都會拋出一個ReferenceError錯誤,塊級作用域變量在塊的頭部到聲明變量之間是“暫時性死區”,在這期間訪問變量會拋出錯誤。如下:

    console.log(typeof undeclaredVariable) // 'undefined'
    console.log(typeof newLetVariable) // ReferenceError
    console.log(typeof newConstVariable) // ReferenceError
    console.log(typeof newClass) // ReferenceError

    let newLetVariable
    const newConstVariable = 'hello'
    class newClass{} 

1.7 例外

當前所有瀏覽器都暴露一個類型為undefined的非標準宿主對象document.all。typeof document.all === ‘undefined’。景觀規範允許為非標準的外來對象自定義類型標籤,單要求這些類型標籤與已有的不同,document.all的類型標籤為undefined的例子在web領域被歸類為對原ECMA javascript標準的“故意侵犯”,可能就是瀏覽器的惡作劇。

總結:typeof返回變量或者值的類型標籤,雖然對大部分類型都能返回正確結果,但是對null,構造函數實例,正則表達式這三種不太理想。 

2. instanceof

2.1 語法

instanceof運算符用於檢測實例對象(參數)的原型鏈上是否出現構造函數的prototype。

語法:object instanceof constructor
參數:object 某個實例對象
          constructor 某個構造函數
描述:instanceof運算符用來檢測constructor.property是否存在於參數object的原型鏈上。

// 定義構造函數
    function C() {
    }
    function D() {
    }
    var o = new C()
    console.log(o instanceof C) //true,因為Object.getPrototypeOf(0) === C.prototype
    console.log(o instanceof D) //false,D.prototype不在o的原型鏈上
    console.log(o instanceof Object) //true 同上

    C.prototype = {}
    var o2 = new C()
    console.log(o2 instanceof C) // true
    console.log(o instanceof C) // false C.prototype指向了一個空對象,這個空對象不在o的原型鏈上
    D.prototype = new C() // 繼承
    var o3 = new D()
    console.log(o3 instanceof D) // true
    console.log(o3 instanceof C) // true C.prototype現在在o3的原型鏈上

需要注意的是,如果表達式obj instanceof Foo返回true,則並不意味着該表達式會永遠返回true,應為Foo.prototype屬性的值可能被修改,修改之後的值可能不在obj的原型鏈上,這時表達式的值就是false了。另外一種情況,改變obj的原型鏈的情況,雖然在當前ES規範中,只能讀取對象的原型而不能修改它,但是藉助非標準的__proto__偽屬性,是可以修改的,比如執行obj.__proto__ = {}后,obj instanceof Foo就返回false了。此外ES6中Object.setPrototypeOf(),Reflect.setPrototypeOf()都可以修改對象的原型。

instanceof和多全局對象(多個iframe或多個window之間的交互)

瀏覽器中,javascript腳本可能需要在多個窗口之間交互。多個窗口意味着多個全局環境,不同全局環境擁有不同的全局對象,從而擁有不同的內置構造函數。這可能會引發一些問題。例如表達式[] instanceof window.frames[0].Array會返回false,因為Array.prototype !== window.frames[0].Array.prototype。

起初,這樣可能沒有意義,但是當在腳本中處理多個frame或多個window以及通過函數將對象從一個窗口傳遞到另一個窗口時,這就是一個非常有意義的話題。實際上,可以通過Array.isArray(myObj)或者Object.prototype.toString.call(myObj) = “[object Array]”來安全的檢測傳過來的對象是否是一個數組。

2.2 示例

String對象和Date對象都屬於Object類型(它們都由Object派生出來)。

但是,使用對象文字符號創建的對象在這裡是一個例外,雖然原型未定義,但是instanceof of Object返回true。

    var simpleStr = "This is a simple string";
    var myString  = new String();
    var newStr    = new String("String created with constructor");
    var myDate    = new Date();
    var myObj     = {};
    var myNonObj  = Object.create(null);

    console.log(simpleStr instanceof String); // 返回 false,雖然String.prototype在simpleStr的原型鏈上,但是後者是字面量,不是對象
    console.log(myString  instanceof String); // 返回 true
    console.log(newStr    instanceof String); // 返回 true
    console.log(myString  instanceof Object); // 返回 true

    console.log(myObj instanceof Object);    // 返回 true, 儘管原型沒有定義
    console.log(({})  instanceof Object);    // 返回 true, 同上
    console.log(myNonObj instanceof Object); // 返回 false, 一種創建非 Object 實例的對象的方法

    console.log(myString instanceof Date); //返回 false

    console.log( myDate instanceof Date);     // 返回 true
    console.log(myDate instanceof Object);   // 返回 true
    console.log(myDate instanceof String);   // 返回 false 

注意:instanceof運算符的左邊必須是一個對象,像”string” instanceof String,true instanceof Boolean這樣的字面量都會返回false。

下面代碼創建了一個類型Car,以及該類型的對象實例mycar,instanceof運算符表明了這個myca對象既屬於Car類型,又屬於Object類型。

function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}
var mycar = new Car("Honda", "Accord", 1998);
var a = mycar instanceof Car;    // 返回 true
var b = mycar instanceof Object; // 返回 true 

不是…的實例

要檢測對象不是某個構造函數的實例時,可以使用!運算符,例如if(!(mycar instanceof Car))

instanceof雖然能夠判斷出對象的類型,但是必須要求這個參數是一個對象,簡單類型的變量,字面量就不行了,很顯然,這在實際編碼中也是不夠實用。

總結:obj instanceof constructor雖然能判斷出對象的原型鏈上是否有構造函數的原型,但是只能判斷出對象類型變量,字面量是判斷不出的。

3. Object.prototype.toString()

3.1. 語法

toString()方法返回一個表示該對象的字符串。

語法:obj.toString()
返回值:一個表示該對象的字符串
描述:每個對象都有一個toString()方法,該對象被表示為一個文本字符串時,或一個對象以預期的字符串方式引用時自動調用。默認情況下,toString()方法被每個Object對象繼承,如果此方法在自定義對象中未被覆蓋,toString()返回“[object type]”,其中type是對象的類型,看下面代碼:

    var o = new Object();
    console.log(o.toString()); // returns [object Object] 

注意:如ECMAScript 5和隨後的Errata中所定義,從javascript1.8.5開始,toString()調用null返回[object, Null],undefined返回[object Undefined]

3.2. 示例

覆蓋默認的toString()方法

可以自定義一個方法,來覆蓋默認的toString()方法,該toString()方法不能傳入參數,並且必須返回一個字符串,自定義的toString()方法可以是任何我們需要的值,但如果帶有相關的信息,將變得非常有用。

下面代碼中定義Dog對象類型,並在構造函數原型上覆蓋toString()方法,返回一個有實際意義的字符串,描述當前dog的姓名,顏色,性別,飼養員等信息。

function Dog(name,breed,color,sex) {
        this.name = name;
        this.breed = breed;
        this.color = color;
        this.sex = sex;
    }
    Dog.prototype.toString = function dogToString() {
        return "Dog " + this.name + " is a " + this.sex + " " + this.color + " " + this.breed
    }

    var theDog = new Dog("Gabby", "Lab", "chocolate", "female");
    console.log(theDog.toString()) //Dog Gabby is a female chocolate Lab 

4. 使用toString()檢測數據類型

目前來看toString()方法能夠基本滿足javascript數據類型的檢測需求,可以通過toString()來檢測每個對象的類型。為了每個對象都能通過Object.prototype.toString()來檢測,需要以Function.prototype.call()或者Function.prototype.apply()的形式來檢測,傳入要檢測的對象或變量作為第一個參數,返回一個字符串”[object type]”。

    // null undefined
    console.log(Object.prototype.toString.call(null)) //[object Null] 很給力
    console.log(Object.prototype.toString.call(undefined)) //[object Undefined] 很給力

    // Number
    console.log(Object.prototype.toString.call(Infinity)) //[object Number]
    console.log(Object.prototype.toString.call(Number.MAX_SAFE_INTEGER)) //[object Number]
    console.log(Object.prototype.toString.call(NaN)) //[object Number],NaN一般是数字運算得到的結果,返回Number還算可以接受
    console.log(Object.prototype.toString.call(1)) //[object Number]
    var n = 100
    console.log(Object.prototype.toString.call(n)) //[object Number]
    console.log(Object.prototype.toString.call(0)) // [object Number]
    console.log(Object.prototype.toString.call(Number(1))) //[object Number] 很給力
    console.log(Object.prototype.toString.call(new Number(1))) //[object Number] 很給力
    console.log(Object.prototype.toString.call('1')) //[object String]
    console.log(Object.prototype.toString.call(new String('2'))) // [object String]

    // Boolean
    console.log(Object.prototype.toString.call(true)) // [object Boolean]
    console.log(Object.prototype.toString.call(new Boolean(1))) //[object Boolean]

    // Array
    console.log(Object.prototype.toString.call(new Array(1))) // [object Array]
    console.log(Object.prototype.toString.call([])) // [object Array]

    // Object
    console.log(Object.prototype.toString.call(new Object())) // [object Object]
    function foo() {}
    let a = new foo()
    console.log(Object.prototype.toString.call(a)) // [object Object]

    // Function
    console.log(Object.prototype.toString.call(Math.floor)) //[object Function]
    console.log(Object.prototype.toString.call(foo)) //[object Function]

    // Symbol
    console.log(Object.prototype.toString.call(Symbol('222'))) //[object Symbol]

    // RegExp
    console.log(Object.prototype.toString.call(/sss/)) //[object RegExp] 

上面的結果,除了NaN返回Number稍微有點差池之外其他的都返回了意料之中的結果,都能滿足實際開發的需求,於是我們可以寫一個通用的函數來檢測變量,字面量的類型。如下:

    let Type = (function () {
        let type = {};
        let typeArr = ['String', 'Object', 'Number', 'Array', 'Undefined', 'Function', 'Null', 'Symbol', 'Boolean', 'RegExp', 'BigInt'];
        for (let i = 0; i < typeArr.length; i++) {
            (function (name) {
                type['is' + name] = function (obj) {
                    return Object.prototype.toString.call(obj) === '[object ' + name + ']'
                }
            })(typeArr[i])
        }
        return type
    })()
    let s = true
    console.log(Type.isBoolean(s)) // true
    console.log(Type.isRegExp(/22/)) // true 

除了能檢測ECMAScript規定的八種數據類型(七種原始類型,BooleanNullUndefinedNumberBigIntStringSymbol,一種複合類型Object)之外,還能檢測出正則表達式RegExpFunction這兩種類型,基本上能滿足開發中的判斷數據類型需求。

5. 判斷相等

既然說道這裏,不妨說一說另一個開發中常見的問題,判斷一個變量是否等於一個值。ES5中比較兩個值是否相等,可以使用相等運算符(==),嚴格相等運算符(===),但它們都有缺點,== 會將‘4’轉換成4,後者NaN不等於自身,以及+0 !=== -0。ES6中提出”Same-value equality“(同值相等)算法,用來解決這個問題。Object.is就是部署這個算法的新方法,它用來比較兩個值是否嚴格相等,與嚴格比較運算(===)行為基本一致。

    console.log(5 == '5') // true
    console.log(NaN == NaN) // false
    console.log(+0 == -0) // true
    console.log({} == {}) // false

    console.log(5 === '5') // false
    console.log(NaN === NaN) // false
    console.log(+0 === -0) // true
    console.log({} === {}) // false

Object.js()不同之處有兩處,一是+0不等於-0,而是NaN等於自身,如下:

    let a = {}
    let b = {}
    let c = b
    console.log(a === b) // false
    console.log(b === c) // true
    console.log(Object.is(b, c)) // true 

注意兩個空對象不能判斷相等,除非是將一個對象賦值給另外一個變量,對象類型的變量是一個指針,比較的也是這個指針,而不是對象內部屬性,對象原型等。

 

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

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

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

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

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

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

安利一個繪製指引線的JS庫leader-line

前言

之前看到一篇推薦這個搜索引擎的新聞,對於這個搜索引擎是否好用咱們不予置評,但是我在這個搜索引擎上面發現了一個好玩的前端功能。

如上圖,將鼠標浮動到學習來源上時,會展示一堆指引線。

本博客的右側文章目錄也集成了這個功能,諸位可以玩一玩。

當時覺得這個功能很好玩,而且前端領域其實這種指引線還是有很多用處的,比如新手指引,功能指引,腦圖之類的功能。

鑒於以後很可能需要用到,當時就調試了一下這個網站,發現使用了這個庫。

然後百度了一下,發現網上也沒什麼人介紹這個庫,所以這裏寫個安利文吧。

LeaderLine

這個庫在Github上的介紹很簡單:

Draw a leader line in your web page.

意思就是在網頁上畫指引線。

使用起來也非常方便:

<script src="leader-line.min.js"></script>
<script>
  new LeaderLine(
    document.getElementById('start'),
    document.getElementById('end')
  );
</script>

new一個LeaderLine對象即可,只需要輸入兩個dom元素節點而已。

當然也可以輸入更多的參數來繪製各種各樣的指引線:

具體的使用方法可以去查看lead-line的,這裏就不贅述了。

而且這個庫本身就提供了hover繪製指引線的功能,並且能偏移起始點和結束點的位置,同時當起始點和結束點變動時,也可以實時調整指引線。

這兩個功能可以將鼠標hover到右側的文章目錄上,然後滾動鼠標輪來查看效果。

原理

這個庫的實現原理其實很簡單,根據提供的兩個dom元素,找到這兩個dom元素的位置,然後通過svg在body下繪製一條指引線。

這個庫雖然只是個js,但是在引入後會將一些樣式寫到一個id為leader-line-defs的svg元素內。

這些指引線使用了一個叫leader-line的樣式class,如果繪製指引線時出現遮擋情況,可以通過調整這個樣式class的z-index或者position來處理。

可以預想一下,這些指引線都是position:absolute的,因為position:fixed的元素在滾動時肯定會存在問題。

原理都講了,所以諸位請在頁面有fixed元素或者absolute元素時,仔細查看指引線是否會與這些元素產生遮擋。

示例代碼

這裏就以我博客右側目錄集成的指引線功能作為示例代碼:

// 生成目錄上的指引線
function createCatalogLeaderLine($h2Arr) { // $h2Arr是一個dom元素集合,注意不是數組哦
  // lines的目的是為了保留leader-line變量,方便重繪
  var lines = {};
  var options = {
    color: '#5bf', // 指引線顏色
    endPlug: "disc", // 指引線結束點的樣式
    size: 2, // 線條尺寸
    startSocket: "left", //在指引線開始的地方從元素左側開始
    endSocket: "right", //在指引線開始的地方從元素右側結束
    hide:true // 繪製時隱藏,默認為false,在初始化時可能會出現閃爍的線條
  };
  [].slice.call($h2Arr).forEach(function (item) {
    var anchor = LeaderLine.mouseHoverAnchor(document.getElementById('catalog' + item.id), 'draw', {
      // 指引線動效
      animOptions: {
        duration: 500
      },
      // 清除默認的hover樣式
      hoverStyle:{
        backgroundColor: null
      },
      // 起始點樣式,這裏為了清除默認樣式
      style: {
        paddingTop: null,
        paddingRight: null,
        paddingBottom: null,
        paddingLeft: null,
        cursor: null,
        backgroundColor: null,
        backgroundImage: null,
        backgroundSize: null,
        backgroundPosition: null,
        backgroundRepeat: null
      },
      // 當起始點被hover時調用的事件
      onSwitch: function (event) {
        var line = lines[item.id]
        // 浮動上去就重繪
        if (event.type == "mouseenter") {
          line.position();
        }
      }
    });
    lines[item.id] = new LeaderLine(
      anchor,
      document.getElementById(item.id),
      options
    );
  })
  // 滾動時重繪指引線
  $(window).scroll(function () {
    for (var key in lines) {
      lines[key].position()
    }
  })
}

其中LeaderLine.mouseHoverAnchor為leader-line提供的api,顧名思義即可。

代碼就不講了,關鍵點都有註釋。

總結

沒什麼好總結的,這裏發一個小吐槽。

其實我博客集成這個功能時,最開始是直接把這個庫的js複製粘貼到了博客園的自定義js代碼中,沒想到博客園這方面做了大小限制。

所以我就把Magi這個搜索引擎的引用地址拿來用了,萬一哪天這個搜索引擎不能用了或者js地址變了那麼我目錄的指引功能可能就掛了。

N年之後你看到這篇文章,也許功能失效了,到時候別忘了給我發個短消息提醒我一下。

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

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

※帶您來看台北網站建置台北網頁設計,各種案例分享

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

手把手帶你實戰下Spring的七種事務傳播行為

目錄

本文介紹Spring的七種事務傳播行為並通過代碼演示下。

一、什麼是事務傳播行為?

事務傳播行為(propagation behavior)指的就是當一個事務方法被另一個事務方法調用時,這個事務方法應該如何運行。

例如:methodA方法調用methodB方法時,methodB是繼續在調用者methodA的事務中運行呢,還是為自己開啟一個新事務運行,這就是由methodB的事務傳播行為決定的。

二、事務的7種傳播行為

Spring在TransactionDefinition接口中規定了7種類型的事務傳播行為。事務傳播行為是Spring框架獨有的事務增強特性。這是Spring為我們提供的強大的工具箱,使用事務傳播行為可以為我們的開發工作提供許多便利。

7種事務傳播行為如下:

1.PROPAGATION_REQUIRED

如果當前沒有事務,就創建一個新事務,如果當前存在事務,就加入該事務,這是最常見的選擇,也是Spring默認的事務傳播行為。

2.PROPAGATION_SUPPORTS

支持當前事務,如果當前存在事務,就加入該事務,如果當前不存在事務,就以非事務執行。

3.PROPAGATION_MANDATORY

支持當前事務,如果當前存在事務,就加入該事務,如果當前不存在事務,就拋出異常。

4.PROPAGATION_REQUIRES_NEW

創建新事務,無論當前存不存在事務,都創建新事務。

5.PROPAGATION_NOT_SUPPORTED

以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。

6.PROPAGATION_NEVER

以非事務方式執行,如果當前存在事務,則拋出異常。

7.PROPAGATION_NESTED

如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,則按REQUIRED屬性執行。

其實這7中我也沒看懂,不過不急,咱們接下來直接看效果。

三、7種傳播行為實戰

演示前先建兩個表,用戶表和用戶角色表,一開始兩個表裡沒有數據。

需要注意下,為了數據更直觀,每次執行代碼時 先清空下user和user_role表的數據。

user表:

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `password` varchar(255) DEFAULT NULL,
  `sex` int(11) DEFAULT NULL,
  `des` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

user_role表:

CREATE TABLE `user_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) DEFAULT NULL,
  `role_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

1.PROPAGATION_REQUIRED測試

如果當前沒有事務,就創建一個新事務,如果當前存在事務,就加入該事務,這是最常見的選擇,也是Spring默認的事務傳播行為。

場景一:

此場景外圍方法沒有開啟事務。

1.驗證方法

兩個實現類UserServiceImpl和UserRoleServiceImpl制定事物傳播行為propagation=Propagation.REQUIRED,然後在測試方法中同時調用兩個方法並在調用結束后拋出異常。

2.主要代碼

外層調用方法代碼:

/**
     * 測試 PROPAGATION_REQUIRED
     *
     * @Author: java_suisui
     */
    @Test
    void test_PROPAGATION_REQUIRED() {
        // 增加用戶表
        User user = new User();
        user.setName("Java碎碎念");
        user.setPassword("123456");
        userService.add(user);
        // 增加用戶角色表
        UserRole userRole = new UserRole();
        userRole.setUserId(user.getId());
        userRole.setRoleId(200);
        userRoleService.add(userRole);
        //拋異常
        throw new RuntimeException();
    }

UserServiceImpl代碼:

/**
     * 增加用戶
     */
    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public int add(User user) {
        return userMapper.add(user);
    }

UserRoleServiceImpl代碼:

    /**
     * 增加用戶角色
     */
    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public int add(UserRole userRole) {
        return userRoleMapper.add(userRole);
    }

3.代碼執行后數據庫截圖

兩張表數據都新增成功,截圖如下:

4.結果分析

外圍方法未開啟事務,插入用戶表和用戶角色表的方法在自己的事務中獨立運行,外圍方法異常不影響內部插入,所以兩條記錄都新增成功。

場景二:

此場景外圍方法開啟事務。

1.主要代碼

測試方法代碼如下:

/**
     * 測試 PROPAGATION_REQUIRED
     *
     * @Author: java_suisui
     */
    @Transactional
    @Test
    void test_PROPAGATION_REQUIRED() {
        // 增加用戶表
        User user = new User();
        user.setName("Java碎碎念");
        user.setPassword("123456");
        userService.add(user);
        // 增加用戶角色表
        UserRole userRole = new UserRole();
        userRole.setUserId(user.getId());
        userRole.setRoleId(200);
        userRoleService.add(userRole);
        //拋異常
        throw new RuntimeException();
    }

2.代碼執行后數據庫截圖

兩張表數據都為空,截圖如下:

3.結果分析

外圍方法開啟事務,內部方法加入外圍方法事務,外圍方法回滾,內部方法也要回滾,所以兩個記錄都插入失敗。

結論:以上結果證明在外圍方法開啟事務的情況下Propagation.REQUIRED修飾的內部方法會加入到外圍方法的事務中,所以Propagation.REQUIRED修飾的內部方法和外圍方法均屬於同一事務,只要一個方法回滾,整個事務均回滾。

2.PROPAGATION_SUPPORTS測試

支持當前事務,如果當前存在事務,就加入該事務,如果當前不存在事務,就以非事務執行。

場景一:

此場景外圍方法沒有開啟事務。

1.驗證方法

兩個實現類UserServiceImpl和UserRoleServiceImpl制定事物傳播行為propagation=Propagation.SUPPORTS,然後在測試方法中同時調用兩個方法並在調用結束后拋出異常。

2.主要代碼

外層調用方法代碼:

    /**
     * 測試 PROPAGATION_SUPPORTS
     *
     * @Author: java_suisui
     */
    @Test
    void test_PROPAGATION_SUPPORTS() {
        // 增加用戶表
        User user = new User();
        user.setName("Java碎碎念");
        user.setPassword("123456");
        userService.add(user);
        // 增加用戶角色表
        UserRole userRole = new UserRole();
        userRole.setUserId(user.getId());
        userRole.setRoleId(200);
        userRoleService.add(userRole);
        //拋異常
        throw new RuntimeException();
    }

UserServiceImpl代碼:

/**
     * 增加用戶
     */
    @Transactional(propagation = Propagation.SUPPORTS)
    @Override
    public int add(User user) {
        return userMapper.add(user);
    }

UserRoleServiceImpl代碼:

    /**
     * 增加用戶角色
     */
    @Transactional(propagation = Propagation.SUPPORTS)
    @Override
    public int add(UserRole userRole) {
        return userRoleMapper.add(userRole);
    }

3.代碼執行后數據庫截圖

兩張表數據都新增成功,截圖如下:

4.結果分析

外圍方法未開啟事務,插入用戶表和用戶角色表的方法以非事務的方式獨立運行,外圍方法異常不影響內部插入,所以兩條記錄都新增成功。

場景二:

此場景外圍方法開啟事務。

1.主要代碼

test_PROPAGATION_SUPPORTS方法添加註解@Transactional即可。

2.代碼執行后數據庫截圖

兩張表數據都為空,截圖如下:

3.結果分析

外圍方法開啟事務,內部方法加入外圍方法事務,外圍方法回滾,內部方法也要回滾,所以兩個記錄都插入失敗。

結論:以上結果證明在外圍方法開啟事務的情況下Propagation.SUPPORTS修飾的內部方法會加入到外圍方法的事務中,所以Propagation.SUPPORTS修飾的內部方法和外圍方法均屬於同一事務,只要一個方法回滾,整個事務均回滾。

3.PROPAGATION_MANDATORY測試

支持當前事務,如果當前存在事務,就加入該事務,如果當前不存在事務,就拋出異常。

通過上面的測試,“支持當前事務,如果當前存在事務,就加入該事務”,這句話已經驗證了,外層添加@Transactional註解后兩條記錄都新增失敗,所以這個傳播行為只測試下外層沒有開始事務的場景。

場景一:

此場景外圍方法沒有開啟事務。

1.驗證方法

兩個實現類UserServiceImpl和UserRoleServiceImpl制定事物傳播行為propagation = Propagation.MANDATORY,主要代碼如下。

2.主要代碼

外層調用方法代碼:

    /**
     * 測試 PROPAGATION_MANDATORY
     *
     * @Author: java_suisui
     */
    @Test
    void test_PROPAGATION_MANDATORY() {
        // 增加用戶表
        User user = new User();
        user.setName("Java碎碎念");
        user.setPassword("123456");
        userService.add(user);
        // 增加用戶角色表
        UserRole userRole = new UserRole();
        userRole.setUserId(user.getId());
        userRole.setRoleId(200);
        userRoleService.add(userRole);
        //拋異常
        throw new RuntimeException();
    }

UserServiceImpl代碼:

/**
     * 增加用戶
     */
    @Transactional(propagation = Propagation.MANDATORY)
    @Override
    public int add(User user) {
        return userMapper.add(user);
    }

UserRoleServiceImpl代碼:

    /**
     * 增加用戶角色
     */
    @Transactional(propagation = Propagation.MANDATORY)
    @Override
    public int add(UserRole userRole) {
        return userRoleMapper.add(userRole);
    }

3.代碼執行后數據庫截圖

兩張表數據都為空,截圖如下:

4.結果分析

運行日誌如下,可以發現在調用userService.add()時候已經報錯了,所以兩個表都沒有新增數據,驗證了“如果當前不存在事務,就拋出異常”。

at com.example.springboot.mybatisannotation.service.impl.UserServiceImpl$$EnhancerBySpringCGLIB$$50090f18.add(<generated>)
    at com.example.springboot.mybatisannotation.SpringBootMybatisAnnotationApplicationTests.test_PROPAGATION_MANDATORY(SpringBootMybatisAnnotationApplicationTests.java:78)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)

4.PROPAGATION_REQUIRES_NEW測試

創建新事務,無論當前存不存在事務,都創建新事務。

這種情況每次都創建事務,所以我們驗證一種情況即可。

場景一:

此場景外圍方法開啟事務。

1.驗證方法

兩個實現類UserServiceImpl和UserRoleServiceImpl制定事物傳播行為propagation = Propagation.REQUIRES_NEW,主要代碼如下。

2.主要代碼

外層調用方法代碼:

    /**
     * 測試 REQUIRES_NEW
     *
     * @Author: java_suisui
     */
    @Test
    @Transactional
    void test_REQUIRES_NEW() {
        // 增加用戶表
        User user = new User();
        user.setName("Java碎碎念");
        user.setPassword("123456");
        userService.add(user);
        // 增加用戶角色表
        UserRole userRole = new UserRole();
        userRole.setUserId(user.getId());
        userRole.setRoleId(200);
        userRoleService.add(userRole);
        //拋異常
        throw new RuntimeException();
    }

UserServiceImpl代碼:

/**
     * 增加用戶
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public int add(User user) {
        return userMapper.add(user);
    }

UserRoleServiceImpl代碼:

    /**
     * 增加用戶角色
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public int add(UserRole userRole) {
        return userRoleMapper.add(userRole);
    }

3.代碼執行后數據庫截圖

兩張表數據都新增成功,截圖如下:

4.結果分析

無論當前存不存在事務,都創建新事務,所以兩個數據新增成功。

5.PROPAGATION_NOT_SUPPORTED測試

以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。

場景一:

此場景外圍方法不開啟事務。

1.驗證方法

兩個實現類UserServiceImpl和UserRoleServiceImpl制定事物傳播行為propagation = Propagation.NOT_SUPPORTED,主要代碼如下。

2.主要代碼

外層調用方法代碼:

    /**
     * 測試 PROPAGATION_NOT_SUPPORTED
     *
     * @Author: java_suisui
     */
    @Test
    void test_PROPAGATION_NOT_SUPPORTED() {
        // 增加用戶表
        User user = new User();
        user.setName("Java碎碎念");
        user.setPassword("123456");
        userService.add(user);
        // 增加用戶角色表
        UserRole userRole = new UserRole();
        userRole.setUserId(user.getId());
        userRole.setRoleId(200);
        userRoleService.add(userRole);
        //拋異常
        throw new RuntimeException();
    }

UserServiceImpl代碼:

/**
     * 增加用戶
     */
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    @Override
    public int add(User user) {
        return userMapper.add(user);
    }

UserRoleServiceImpl代碼:

    /**
     * 增加用戶角色
     */
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    @Override
    public int add(UserRole userRole) {
        return userRoleMapper.add(userRole);
    }

3.代碼執行后數據庫截圖

兩張表數據都新增成功,截圖如下:

4.結果分析

以非事務方式執行,所以兩個數據新增成功。

場景二:

此場景外圍方法開啟事務。

1.主要代碼

test_PROPAGATION_NOT_SUPPORTED方法添加註解@Transactional即可。

2.代碼執行后數據庫截圖

兩張表數據都新增成功,截圖如下:

3.結果分析

如果當前存在事務,就把當前事務掛起,相當於以非事務方式執行,所以兩個數據新增成功。

6.PROPAGATION_NEVER測試

以非事務方式執行,如果當前存在事務,則拋出異常。

上面已經有類似情況,外層沒有事務會以非事務的方式運行,兩個表新增成功;有事務則拋出異常,兩個表都都沒有新增數據。

7.PROPAGATION_NESTED測試

如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,則按REQUIRED屬性執行。

上面已經有類似情況,外層沒有事務會以REQUIRED屬性的方式運行,兩個表新增成功;有事務但是用的是一個事務,方法最後拋出了異常導致回滾,兩個表都都沒有新增數據。

到此Spring的7種事務傳播行為已經全部介紹完成了,有問題歡迎留言溝通哦!

完整源碼地址: https://github.com/suisui2019/springboot-study

推薦閱讀

限時領取免費Java相關資料,涵蓋了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo/Kafka、Hadoop、Hbase、Flink等高併發分佈式、大數據、機器學習等技術。
關注下方公眾號即可免費領取:

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

【其他文章推薦】

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

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

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

南投搬家前需注意的眉眉角角,別等搬了再說!

新北清潔公司,居家、辦公、裝潢細清專業服務

聚類——密度聚類DBSCAN

Clustering 聚類

密度聚類——DBSCAN

  前面我們已經介紹了兩種聚類算法:k-means和譜聚類。今天,我們來介紹一種基於密度的聚類算法——DBSCAN,它是最經典的密度聚類算法,是很多算法的基礎,擁有很多聚類算法不具有的優勢。今天,小編就帶你理解密度聚類算法DBSCAN的實質。

 

DBSCAN

 

基礎概念

    作為最經典的密度聚類算法,DBSCAN使用一組關於“鄰域”概念的參數來描述樣本分佈的緊密程度,將具有足夠密度的區域劃分成簇,且能在有噪聲的條件下發現任意形狀的簇。在學習具體算法前,我們先定義幾個相關的概念:

  • 鄰域:對於任意給定樣本x和距離ε,x的ε鄰域是指到x距離不超過ε的樣本的集合;

  • 核心對象:若樣本x的ε鄰域內至少包含minPts個樣本,則x是一個核心對象;

  • 密度直達:若樣本b在a的ε鄰域內,且a是核心對象,則稱樣本b由樣本x密度直達;

  • 密度可達:對於樣本a,b,如果存在樣例p1,p2,…,pn,其中,p1=a,pn=b,且序列中每一個樣本都與它的前一個樣本密度直達,則稱樣本a與b密度可達;

  • 密度相連:對於樣本a和b,若存在樣本k使得a與k密度可達,且k與b密度可達,則a與b密度相連。

 

光看文字是不是繞暈了?下面我們用一個圖來簡單表示上面的密度關係:

當minPts=3時,虛線圈表示ε鄰域,則從圖中我們可以觀察到:

  • x1是核心對象;

  • x2由x1密度直達;

  • x3由x1密度可達;

  • x3與x4密度相連。

為什麼要定義這些看上去差不多又容易把人繞暈的概念呢?其實ε鄰域使用(ε,minpts)這兩個關鍵的參數來描述鄰域樣本分佈的緊密程度,規定了在一定鄰域閾值內樣本的個數(這不就是密度嘛)。那有了這些概念,如何根據密度進行聚類呢?

DBSCAN聚類思想

  DBSCAN聚類的原理很簡單:由密度可達關係導出最大密度相連的樣本集合(聚類)。這樣的一個集合中有一個或多個核心對象,如果只有一個核心對象,則簇中其他非核心對象都在這個核心對象的ε鄰域內;如果是多個核心對象,那麼任意一個核心對象的ε鄰域內一定包含另一個核心對象(否則無法密度可達)。這些核心對象以及包含在它ε鄰域內的所有樣本構成一個類。

  那麼,如何找到這樣一個樣本集合呢?一開始任意選擇一個沒有被標記的核心對象,找到它的所有密度可達對象,即一個簇,這些核心對象以及它們ε鄰域內的點被標記為同一個類;然後再找一個未標記過的核心對象,重複上邊的步驟,直到所有核心對象都被標記為止。

  算法的思想很簡單,但是我們必須考慮一些細節問題才能產出一個好的聚類結果:

  • 首先對於一些不存在任何核心對象鄰域內的點,再DBSCAN中我們將其標記為離群點(異常);
  • 第二個是距離度量,如歐式距離,在我們要確定ε鄰域內的點時,必須要計算樣本點到所有點之間的距離,對於樣本數較少的場景,還可以應付,如果數據量特別大,一般採用KD樹或者球樹來快速搜索最近鄰,不熟悉這兩種方法的同學可以找相關文獻看看,這裏不再贅述;
  • 第三個問題是如果存在樣本到兩個核心對象的距離都小於ε,但這兩個核心對象不屬於同一個類,那麼該樣本屬於哪一個類呢?一般DBSCAN採用先來後到的方法,樣本將被標記成先聚成的類。

DBSCAN算法流程

DBSCAN算法小結

      之前我們學過了kmeans算法,用戶需要給出聚類的個數k,然而我們往往對k的大小無法確定。DBSCAN算法最大的優勢就是無需給定聚類個數k,且能夠發現任意形狀的聚類,且在聚類過程中能自動識別出離群點。那麼,我們在什麼時候使用DBSCAN算法來聚類呢?一般來說,如果數據集比較稠密且形狀非凸,用密度聚類的方法效果要好一些。

DBSCAN算法優點:

  1. 不需要事先指定聚類個數,且可以發現任意形狀的聚類;

  2. 對異常點不敏感,在聚類過程中能自動識別出異常點;

  3. 聚類結果不依賴於節點的遍歷順序;

DBSCAN缺點:

  1. 對於密度不均勻,聚類間分佈差異大的數據集,聚類質量變差;

  2. 樣本集較大時,算法收斂時間較長;

  3. 調參較複雜,要同時考慮兩個參數;

 

小結:

基於密度的聚類算法是廣為使用的算法,特別是對於任意形狀聚類以及存在異常點的場景。上面我們也提到了DBSCAN算法的缺點,但是其實很多研究者已經在DBSCAN的基礎上做出了改進,實現了多密度的聚類,針對海量數據的場景,提出了micro-cluster的結構來表徵距離近的一小部分點,減少存儲壓力和計算壓力…還有很多先進的密度聚類算法及其應用,相信看完這篇文章再去讀相關的論文會比較輕鬆。

 

掃碼關注

獲取有趣的算法知識

 

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

※幫你省時又省力,新北清潔一流服務好口碑

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

說服川普抗暖化 影后珍芳達曾想祭出美女戰術

摘錄自2019年12月18日中央社報導

珍芳達將在12月21日歡慶82歲生日,17日她在全國記者俱樂部(National Press Club)表示,她曾試圖在2016年美國總統川普當選後,安排包括女星潘蜜拉安德森在內的一群「美麗、性感、傑出」環保人士與川普會面,以說服他對付全球暖化問題。珍芳達(Jane Fonda)曾經跟川普女婿庫許納(Jared Kushner)和女兒伊凡卡(Ivanka Trump)討論她的想法,但並未得到回覆。最終這讓她搬到華盛頓住上幾個月,利用自己的名人力量動員群眾。

珍芳達曾分別以1971年「柳巷芳草」(Klute)與1978年「歸返家園」(Coming Home)兩部作品,摘下奧斯卡影后殊榮。

長年投身社會運動的珍芳達經常參與氣候變遷抗議活動,曾於今年10月在美國國會山莊外被警方逮捕。

 

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

燃煤電廠轉型燒生質能? 歐洲智庫警告:砍樹燒木屑 未必能減碳

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!