DDD之3實體和值對象_貨運

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

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

圖中是一個別墅的模型,代表實體,可以真實的看得到。那麼在DDD設計方法論中,實體和值對象是什麼呢?

背景

實體和值對象是領域模型中的領域對象,是組成領域模型的基礎單元,一起實現實體最基本的核心領域邏輯。

那麼問題來了:

1, 他兩在領域模型中的作用是什麼?

2,在系統中跟代碼模型和數據模型是怎麼對應的?

搞清楚這兩個問題很重要。回答問題是需要有知識基礎的,先來捋清楚這兩個概念的定義和內涵。然後在小結部分我們來回答這兩個問題。

實體

定義: DDD中的一類對象,擁有唯一標識符,經歷各種狀態變更后仍然可以保持一致,對這類對象而言,重要的是延續性和標識,(對象的延續性和標識可以超出軟件的生命周期)而非屬性。

形態:不同的設計過程中,形態不一致。

值對象

定義:通過對象的屬性值來識別的對象是值對象,它將多個相關屬性組合為一個概念整體。它是沒有標識符的對象;
**

特點:值對象描述了領域中的一件東西,這個東西是不可變的,它將不同的相關屬性組合成了一個概念整體,當度量和描述改變的時候,它可以用另外一個值對象替換,並進行相等性比較而不會帶來副作用;
**
**
簡單來說: 值對象本質就是一個集合;
**
意義:領域建模過程中,值對象可以保證屬性歸類的清晰和概念的完整性;
**

**
**

上圖中: 如果把省市區地址放在人員實體中,會顯得屬性很多很零碎。 推薦的做法是把省市區地址構成一個集合,即地址值對象;

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

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

例子:人員地址案例;

缺點:如果實體引用的值對象過多,會導致實體堆積一批缺乏概念完整性的屬性,值對象失去了業務含義,操作起來不方便;

實體PK值對象

DDD提倡從領域模型設計出發,而不是先設計數據模型;

小結

首先明確了實體和值對象的概念,以及在不同的設計階段的形態。然後通過一個例子展示了實體和值對象的概念和使用;

這是一個從業務模型向系統模型落地過程,考驗的是設計能力,我們應該結合自己的業務場景,選擇合適的方法進行微服務設計。

最後我來回答一下在背景部分拋出的兩個問題?

1, 實體和值對象在領域模型中的作用是什麼?

2,在系統中跟代碼模型和數據模型是怎麼對應的?

經過上面的分析,我的回答如下:

希望大家都理解好DDD的實體和值對象,設計出高度靈活的代碼;

原創不易,關注誠可貴,轉發價更高!轉載請註明出處,讓我們互通有無,共同進步,歡迎溝通交流。
我會持續分享Java軟件編程知識和程序員發展職業之路,歡迎關注,我整理了這些年編程學習的各種資源,關注公眾號‘李福春持續輸出’,發送’學習資料’分享給你!

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

※回頭車貨運收費標準

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

LeetCode 74,直擊BAT經典面試題_網頁設計公司

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

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

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

今天是LeetCode專題43篇文章,我們今天來看一下LeetCode當中的74題,搜索二維矩陣,search 2D Matrix。

這題的官方難度是Medium,通過率是36%,和之前的題目不同,這題的點贊比非常高,1604個贊,154個反對。可見這題的質量還是很高的,事實上也的確如此,這題非常有意思。

題意

這題的題意也很簡單,給定一個二維的數組matrix和一個整數target,這個數組當中的每一行和每一列都是遞增的,並且還滿足每一行的第一個元素大於上一行的最後一個元素。要求我們返回一個bool變量,代表這個target是否在數組當中。

也就是說這個是一個典型的判斷元素存在的問題,我們下面來看看兩個樣例:

Input:
matrix = [
  [1,   3,  5,  7],
  [10, 11, 16, 20],
  [23, 30, 34, 50]
]
target = 3
Output: true
Input:
matrix = [
  [1,   3,  5,  7],
  [10, 11, 16, 20],
  [23, 30, 34, 50]
]
target = 13
Output: false

題解

這題剛拿到手可能會有些蒙,我們當然很容易可以看出來這是一個二分的問題,但是我們之前做的二分都是在一個一維的數組上,現在的數據是二維的,我們怎麼二分呢?

我們仔細閱讀一下題意,再觀察一下樣例,很容易發現,如果一個二維數組滿足每一行和每一列都有序,並且保證每一行的第一個元素大於上一行的最後一個元素,那麼如果我們把這個二維數組reshape到一維,它依然是有序的。

比如說有這樣一個二維數組:

[[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]

它reshape成一維之後會變成這樣:

[1, 2, 3, 4, 5, 6, 7, 8, 9]

reshape是numpy當中的說法,也可以簡單理解成把每一行串在一起。所以這題最簡單的做法就是把矩陣降維,變成一位的數組之後再通過二分法來判斷元素是否存在。如果偷懶的話可以用numpy來reshape,如果不會numpy的話,可以看下我之前關於numpy的教程,也可以自己用循環來處理。

reshape之後就是簡單的二分了,完全沒有任何難度:

class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        import numpy as np
        arr = np.array(matrix)
        # 通過numpy可以直接reshape
        arr = arr.reshape((-1, ))
        l, r = 0, arr.shape[0]
        if r == 0:
            return False
        # 套用二分
        while l+1 < r:
            m = (l + r) >> 1
            if arr[m] <= target:
                l = m
            else:
                r = m
        return arr[l] == target

正經做法

引入numpy reshape只是給大家提供一個解決的思路,這顯然不是一個很好的做法。那正確的方法應該是怎樣的呢?

還是需要我們對問題進行深入分析,正向思考感覺好像沒什麼頭緒,我們可以反向思考。這也是解題常用的套路,假設我們已經知道了target這個数字存在矩陣當中,並且它的行號是i,列號是j。那麼根據題目當中的條件,我們能夠得出什麼結論呢?

我們分析一下元素的大小關係,可以得出行號小於i的所有元素都小於它,行號大於i的所有元素都大於它。同行的元素列號小於j的元素小於它,列號大於j的元素大於它。

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

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

也就是說,行號i就是一條隱形的分界線,將matrix分成了兩個部分,i上面的小於target,i下方的大於target。所以我們能不能通過二分找到這個i呢?

想到這裏就很簡單了,我們可以通過每行的最後一個元素來找到i。對於一個二維數組而言,每行的最後一個元素連起來就是一個一維的數組,就可以很簡單地進行二分了。

找到了行號i之後,我們再如法炮製,在i行當中進行二分來查找j的位置。找到了之後,再判斷matrix[i][j]是否等於target,如果相等,那麼說明元素在矩陣當中。

整個的思路應該很好理解,但是實現的時候有一個小小的問題,就是我們查找行的時候,找的是大於等於target的第一行的位置。也就是說我們查找的是右端點,那麼二分的時候維護的是一個左開右閉的區間。在邊界的處理上和平常使用的左閉右開的寫法相反,注意了這點,就可以很順利地實現算法了:

class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        n = len(matrix)
        if n == 0:
            return False
        
        m = len(matrix[0])
        if m == 0:
            return False
        
        # 初始化,左開右閉,所以設置成-1, n-1
        l, r = -1, n-1
        
        while l+1 < r:
            mid = (l + r) >> 1
            # 小於target的時候移動左邊界
            if matrix[mid][m-1] < target:
                l = mid
            else:
                r = mid
                
        row = r
        
        # 正常的左閉右開的二分
        l, r = 0, m
        
        while l+1 < r:
            mid = (l + r) >> 1
            if matrix[row][mid] <= target:
                l = mid
            else:
                r = mid
                
        return matrix[row][l] == target

我們用了兩次二分,查找到了結果,每一次二分都是一個O(logN)的算法,所以整體也是log級的算法。

優化

上面的算法沒有問題,但是我們進行了兩次二分,感覺有些麻煩,能不能減少一次,只使用一次二分呢?

如果想要只使用一次二分就找到答案,也就是說我們能找到某個方法來切分整個數組,並且切分出來的數組也存在大小關係。這個條件是使用二分的基礎,必須要滿足。

我們很容易在數組當中找到這樣的切分屬性,就是元素的位置。在矩陣元素的問題當中,我們經常用到的一種方法就是對矩陣當中的元素進行編號。比如說一個點處於i行j列,那麼它的編號就是i * m + j,這裏的m是每行的元素個數。這個編號其實就是將二維數組壓縮到一維之後元素的下標。

我們可以直接對這個編號進行二分,編號的取值範圍是確定的,是[0, mn)。我們有了編號之後,可以還原出它的行號和列號。而且根據題目中的信息,我們可以確定這個矩陣當中的元素按照編號也存在遞增順序。所以我們可以大膽地使用二分了:

class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        n = len(matrix)
        if n == 0:
            return False
        
        m = len(matrix[0])
        if m == 0:
            return False
        
        l, r = 0, m*n
        
        while l+1 < r:
            mid = (l + r) >> 1
            # 還原行號和列號
            x, y = mid // m, mid % m
            if matrix[x][y] <= target:
                l = mid
            else:
                r = mid
        return matrix[l // m][l % m] == target

這樣一來我們的代碼大大簡化,並且代碼運行的效率也提升了,要比使用兩次二分的方法更快。

總結

這道題到這裏就結束了,這題難度並不大,想出答案來還是不難的。但是如果在面試當中碰到,想要第一時間想到最優解法還是不太容易。這一方面需要我們積累經驗,看到題目大概有一個猜測應該使用什麼類型的算法,另一方面也需要我們對問題有足夠的理解和分析,從而讀到題目當中的隱藏信息

關於這題還有一個變種,就是去掉其中每行的第一個元素大於上一行最後一個元素的限制。那麼矩陣當中元素按照編號順序遞增的性質就不存在了,對於這樣的情況, 我們該怎麼樣運用二分呢?這個問題是LeetCode的240題,感興趣的話可以去試着做一下這題,看看究竟解法有多大的變化。

如果喜歡本文,可以的話,請點個關注,給我一點鼓勵,也方便獲取更多文章。

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

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

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

XStream學習手冊_租車

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

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

一、前言

1、XStream官網

    http://x-stream.github.io

2、XStream是什麼

    XStream是一個簡單的基於Java的類庫,用來將Java對象序列化成XML(JSON)或反序列化為對象(即:可以輕易的將Java對象和XML文檔相互轉換)

3、XSteam能幹什麼

    XStream在運行時使用Java反射機制對要進行序列化的對象樹的結構進行探索,並不需要對對象作出修改。XStream可以序列化內部字段,包括私private和final字段,並且支持非公開類以及內部類。

    在缺省情況下,XStream不需要配置映射關係,對象和字段將映射為同名XML元素。但是當對象和字段名與XML中的元素名不同時,XStream支持指定別名。XStream支持以方法調用的方式,或是Java 標註的方式指定別名。

    XStream在進行數據類型轉換時,使用系統缺省的類型轉換器。同時,也支持用戶自定義的類型轉換器。

4、XStream特點

  • 使用方便 – XStream的API提供了一個高層次外觀,以簡化常用的用例

  • 無需創建映射 – XStream的API提供了默認的映射大部分對象序列化

  • 性能  – XStream快速和低內存佔用,適合於大對象圖或系統

  • 乾淨的XML  – XStream創建一個乾淨和緊湊XML結果,這很容易閱讀

  • 不需要修改對象 – XStream可序列化的內部字段,如private和final字段,支持非公開類和內部類。默認構造函數不是強制性的要求

  • 完整對象圖支持 – XStream允許保持在對象模型中遇到的重複引用,並支持循環引用

  • 可自定義的轉換策略 – 定製策略可以允許特定類型的定製被表示為XML的註冊

  • 安全框架 – XStream提供了一個公平控制有關解組的類型,以防止操縱輸入安全問題

  • 錯誤消息 – 出現異常是由於格式不正確的XML時,XStream拋出一個統一的例外,提供了詳細的診斷,以解決這個問題

  • 另一種輸出格式 – XStream支持其它的輸出格式,如JSON

5、XStream常見的用途

    傳輸、持久化、配置、單元測試

二、XStream入門

1、添加XSteam依賴

<dependency> <groupId>com.thoughtworks.xstream</groupId> <artifactId>xstream</artifactId> <version>1.4.12</version> </dependency> <dependency> <groupId>org.codehaus.jettison</groupId> <artifactId>jettison</artifactId> <version>1.4.1</version> </dependency>

2、XStream基本使用

package io.github.xstream.test01; ​ import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver; import lombok.AllArgsConstructor; import lombok.ToString; ​ public class XStreamTest01 { public static void main(String[] args) { Student student = new Student("張三", 20); XStream xStream = new XStream();//需要XPP3庫 //XStream xStream = new XStream(new DomDriver());//不需要XPP3庫 //XStream xStream = new XStream(new StaxDriver());//不需要XPP3庫開始使用Java 6 //XML序列化 String xml = xStream.toXML(student); System.out.println(xml); //XML反序列化 student = (Student) xStream.fromXML(xml); System.out.println(student); ​ xStream = new XStream(new JettisonMappedXmlDriver()); xStream.setMode(XStream.NO_REFERENCES); //Json序列化 String json = xStream.toXML(student); System.out.println(json); //Json反序列 student = (Student) xStream.fromXML(json); System.out.println(student); } } ​ @AllArgsConstructor @ToString class Student { private String name; private int age; }

3、程序運行結果

<io.github.xstream.test01.Student>
  <name>張三</name>
  <age>20</age>
</io.github.xstream.test01.Student>
Security framework of XStream not initialized, XStream is probably vulnerable.
Student(name=張三, age=20)
{"io.github.xstream.test01.Student":{"name":"張三","age":20}}
Student(name=張三, age=20)
Security framework of XStream not initialized, XStream is probably vulnerable.

注意:文中使用到的Lombok註解,Lombok依賴自行添加;XStream序列化XML時需要引用的jar包:xstream-[version].jar、xpp3-[version].jar、xmlpull-[version].jar,當引入xstream依賴後會自動依賴xpp3、xmlpull依賴。XStream序列化JSON需要引用的jar包:jettison-[version].jar。

    使用XStream序列化時,對JavaBean沒有任何限制。JavaBean的字段可以是私有的,也可以沒有getter或setter方法,還可以沒有默認的構造函數。

    XStream序列化XML時可以允許用戶使用不同的XML解析器,用戶可以使用一個標準的JAXP DOM解析器或自Java 6集成STAX解析器。這樣用戶就不需要依賴xpp3-[version].jar。

三、XStream混疊

1、混疊是一種技術來定製生成XML或者使用XStream特定的格式化XML。假設,一個下面的XML格式是用於序列化/反序列化Student對象。

<student name="張三"> <phone> <brand>小米</brand> <description>小米手機的描述</description> </phone> <phone> <brand>蘋果</brand> <description>蘋果手機的描述</description> </phone> </student>

2、根椐上面的XML格式,我們創建實體類

@AllArgsConstructor @ToString class Student { private String studentName; private List<Phone> phones; } ​ @AllArgsConstructor @ToString class Phone { private String brand; private String description; }

3、執行代碼

package io.github.xstream.test02; ​ import com.thoughtworks.xstream.XStream; import lombok.AllArgsConstructor; import lombok.ToString; ​ import java.util.ArrayList; import java.util.List; ​ public class XStreamTest02 { public static void main(String[] args) { List<Phone> phones = new ArrayList<>(); phones.add(new Phone("小米手機", "小米手機的描述")); phones.add(new Phone("蘋果手機", "蘋果手機的描述")); Student student = new Student("張三", phones); ​ XStream xStream = new XStream();//需要XPP3庫 //XML序列化 String xml = xStream.toXML(student); System.out.println(xml); } } ​ @AllArgsConstructor @ToString class Student { private String studentName; private List<Phone> phones; } ​ @AllArgsConstructor @ToString class Phone { private String brand; private String description; }

4、驗證輸出

<io.github.xstream.test02.Student> <studentName>張三</studentName> <phones> <io.github.xstream.test02.Phone> <brand>小米手機</brand> <description>小米手機的描述</description> </io.github.xstream.test02.Phone> <io.github.xstream.test02.Phone> <brand>蘋果手機</brand> <description>蘋果手機的描述</description> </io.github.xstream.test02.Phone> </phones> </io.github.xstream.test02.Student>

    在上面的結果,我們已經看到了Student對象名稱是完全合格的。要替換它作為學生的標籤,按照四、XStream類混疊的步驟

    另外,在上述結果中可以看出,所需studentName要重命名來命名。要替換它,按照五、XStream字段混疊的步驟

    在上面的結果,我們可以看到手機標記被添加成為手機列表。替換它,按照六、XStream隱式集合混疊的步驟

    在上面的結果,我們可以看到這個名字來作為一個子節點,需要將它作為根節點的屬性。替換它,按照七、XStream屬性混疊的步驟

四、XStream類混疊

1、類混疊是用來創建一個類的XML完全限定名稱的別名。讓我們修改XStreamTest02例子,將下面的代碼添加到XStreamTest02例子裏面

xStream.alias("student", Person02.class); xStream.alias("phone", Phone.class);

2、執行代碼

package io.github.xstream.test02; ​ import com.thoughtworks.xstream.XStream; import lombok.AllArgsConstructor; import lombok.ToString; ​ import java.util.ArrayList; import java.util.List; ​ public class XStreamTest02 { public static void main(String[] args) { List<Phone> phones = new ArrayList<>(); phones.add(new Phone("小米手機", "小米手機的描述")); phones.add(new Phone("蘋果手機", "蘋果手機的描述")); Student student = new Student("張三", phones); ​ XStream xStream = new XStream();//需要XPP3庫 xStream.alias("student", Student.class); xStream.alias("phone", Phone.class); ​ //XML序列化 String xml = xStream.toXML(student); System.out.println(xml); } } ​ @AllArgsConstructor @ToString class Student { private String studentName; private List<Phone> phones; } ​ @AllArgsConstructor @ToString class Phone { private String brand; private String description; }

3、執行結果

<student> <studentName>張三</studentName> <phones> <phone> <brand>小米手機</brand> <description>小米手機的描述</description> </phone> <phone> <brand>蘋果手機</brand> <description>蘋果手機的描述</description> </phone> </phones> </student>

可以看到<io.github.xstream.test02.Student>和<io.github.xstream.test02.Phone>分別被修改為了<student>和<phone>

五、XStream字段混疊

1、字段混疊用於創建以XML字段的別名。讓我們再次修改原來的XStreamTest02例子,將下面的代碼添加到XStreamTest02例子裏面

xStream.aliasField("name", Student.class, "studentName");

2、執行代碼

package io.github.xstream.test02; ​ import com.thoughtworks.xstream.XStream; import lombok.AllArgsConstructor; import lombok.ToString; ​ import java.util.ArrayList; import java.util.List; ​ public class XStreamTest02 { public static void main(String[] args) { List<Phone> phones = new ArrayList<>(); phones.add(new Phone("小米手機", "小米手機的描述")); phones.add(new Phone("蘋果手機", "蘋果手機的描述")); Student student = new Student("張三", phones); ​ XStream xStream = new XStream();//需要XPP3庫 xStream.alias("student", Student.class); xStream.alias("phone", Phone.class); xStream.aliasField("name", Student.class, "studentName"); ​ //XML序列化 String xml = xStream.toXML(student); System.out.println(xml); } } ​ @AllArgsConstructor @ToString class Student { private String studentName; private List<Phone> phones; } ​ @AllArgsConstructor @ToString class Phone { private String brand; private String description; }

3、執行結果

<student> <name>張三</name> <phones> <phone> <brand>小米手機</brand> <description>小米手機的描述</description> </phone> <phone> <brand>蘋果手機</brand> <description>蘋果手機的描述</description> </phone> </phones> </student>

可以看到<studentName>被修改為了<name>

六、XStream隱式集合混疊

1、隱式集合混疊時使用的集合是表示在XML無需显示根。例如,在我們的例子中,我們需要一個接一個,但不是在根節點來显示每一個節點。讓我們再次修改原來的XStreamTest02例子,將下面的代碼添加到XStreamTest02例子裏面

xStream.addImplicitCollection(Student.class, "phones");

2、執行代碼

package io.github.xstream.test02; ​ import com.thoughtworks.xstream.XStream; import lombok.AllArgsConstructor; import lombok.ToString; ​ import java.util.ArrayList; import java.util.List; ​ public class XStreamTest02 { public static void main(String[] args) { List<Phone> phones = new ArrayList<>(); phones.add(new Phone("小米手機", "小米手機的描述")); phones.add(new Phone("蘋果手機", "蘋果手機的描述")); Student student = new Student("張三", phones); ​ XStream xStream = new XStream();//需要XPP3庫 xStream.alias("student", Student.class); xStream.alias("phone", Phone.class); xStream.aliasField("name", Student.class, "studentName"); xStream.addImplicitCollection(Student.class, "phones"); ​ //XML序列化 String xml = xStream.toXML(student); System.out.println(xml); } } ​ @AllArgsConstructor @ToString class Student { private String studentName; private List<Phone> phones; } ​ @AllArgsConstructor @ToString class Phone { private String brand; private String description; }

3、執行結果

<student> <name>張三</name> <phone> <brand>小米手機</brand> <description>小米手機的描述</description> </phone> <phone> <brand>蘋果手機</brand> <description>蘋果手機的描述</description> </phone> </student>

可以看到<phones>被隱藏了

七、XStream屬性混疊

1、屬性混疊用於創建一個成員變量作為XML屬性序列化。讓我們再次修改原來的XStreamTest02例子,將下面的代碼添加到XStreamTest02例子裏面

xStream.useAttributeFor(Student.class, "studentName");

2、執行代碼

※超省錢租車方案

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

package io.github.xstream.test02; ​ import com.thoughtworks.xstream.XStream; import lombok.AllArgsConstructor; import lombok.ToString; ​ import java.util.ArrayList; import java.util.List; ​ public class XStreamTest02 { public static void main(String[] args) { List<Phone> phones = new ArrayList<>(); phones.add(new Phone("小米手機", "小米手機的描述")); phones.add(new Phone("蘋果手機", "蘋果手機的描述")); Student student = new Student("張三", phones); ​ XStream xStream = new XStream();//需要XPP3庫 xStream.alias("student", Student.class); xStream.alias("phone", Phone.class); xStream.aliasField("name", Student.class, "studentName"); xStream.addImplicitCollection(Student.class, "phones"); xStream.useAttributeFor(Student.class, "studentName"); ​ //XML序列化 String xml = xStream.toXML(student); System.out.println(xml); } } ​ @AllArgsConstructor @ToString class Student { private String studentName; private List<Phone> phones; } ​ @AllArgsConstructor @ToString class Phone { private String brand; private String description; }

3、執行結果

<student name="張三"> <phone> <brand>小米手機</brand> <description>小米手機的描述</description> </phone> <phone> <brand>蘋果手機</brand> <description>蘋果手機的描述</description> </phone> </student>

可以看到<name>被作為了<student>的屬性

八、XStream包混疊

1、包混疊用於創建一個類XML的完全限定名稱的別名到一個新的限定名稱。讓我們再次修改原來的XStreamTest02例子,將下面代碼

xStream.alias("student", Student.class); xStream.alias("phone", Phone.class);

修改為

xStream.aliasPackage("xx.xx.xx.xx", "io.github.xstream.test02");

2、執行代碼

package io.github.xstream.test02; ​ import com.thoughtworks.xstream.XStream; import lombok.AllArgsConstructor; import lombok.ToString; ​ import java.util.ArrayList; import java.util.List; ​ public class XStreamTest02 { public static void main(String[] args) { List<Phone> phones = new ArrayList<>(); phones.add(new Phone("小米手機", "小米手機的描述")); phones.add(new Phone("蘋果手機", "蘋果手機的描述")); Student student = new Student("張三", phones); ​ XStream xStream = new XStream();//需要XPP3庫 // xStream.alias("student", Student.class); // xStream.alias("phone", Phone.class); xStream.aliasPackage("xx.xx.xx.xx", "io.github.xstream.test02"); xStream.aliasField("name", Student.class, "studentName"); xStream.addImplicitCollection(Student.class, "phones"); xStream.useAttributeFor(Student.class, "studentName"); ​ //XML序列化 String xml = xStream.toXML(student); System.out.println(xml); } } ​ @AllArgsConstructor @ToString class Student { private String studentName; private List<Phone> phones; } ​ @AllArgsConstructor @ToString class Phone { private String brand; private String description; }

3、執行結果

<xx.xx.xx.xx.Student name="張三"> <xx.xx.xx.xx.Phone> <brand>小米手機</brand> <description>小米手機的描述</description> </xx.xx.xx.xx.Phone> <xx.xx.xx.xx.Phone> <brand>蘋果手機</brand> <description>蘋果手機的描述</description> </xx.xx.xx.xx.Phone> </xx.xx.xx.xx.Student>

可以看到包名由io.github.xstream.test02替換為了xx.xx.xx.xx

九、XStream註解

1、前面的四、五、六、七、八步驟都是通過代碼操作的

//xStream.alias("student", Student.class);
//xStream.alias("phone", Phone.class);
xStream.aliasPackage("xx.xx.xx.xx", "io.github.xstream.test02");
xStream.aliasField("name", Student.class, "studentName");
xStream.addImplicitCollection(Student.class, "phones");
xStream.useAttributeFor(Student.class, "studentName");

2、XStream同時也支持註解,使用註解會變得簡單也會達到相同的效果

package io.github.xstream.test03; ​ import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.annotations.*; import com.thoughtworks.xstream.converters.basic.BooleanConverter; import lombok.AllArgsConstructor; import lombok.Data; import lombok.ToString; ​ import java.util.ArrayList; import java.util.List; ​ public class XStreamTest03 { public static void main(String[] args) { List<Phone> phones = new ArrayList<>(); phones.add(new Phone("小米手機", "小米手機的描述")); phones.add(new Phone("蘋果手機", "蘋果手機的描述")); Student student = new Student("張三", phones, 20, true); XStream xStream = new XStream();//需要XPP3庫 //xStream.processAnnotations(new Class[]{Student.class}); xStream.autodetectAnnotations(true); //XML序列化 String xml = xStream.toXML(student); System.out.println(xml); } } ​ @AllArgsConstructor @ToString //別名註解 @XStreamAlias("student") class Student { @XStreamAlias("name") //把字段節點設置成屬性 @XStreamAsAttribute private String studentName; //省略集合根節點 @XStreamImplicit private List<Phone> phones; //隱藏字段 @XStreamOmitField private int age; //設置轉換器 @XStreamConverter(value = BooleanConverter.class, booleans = {false}, strings = {"男", "女"}) private boolean sex; } ​ @AllArgsConstructor @ToString @XStreamAlias("phone") class Phone { private String brand; private String description; }

3、使用註解的話,需要XML序列化之前添加如下代碼

xStream.autodetectAnnotations(true);

或者

xStream.processAnnotations(new Class[]{Student.class});

4、執行結果

<student name="張三"> <phone> <brand>小米手機</brand> <description>小米手機的描述</description> </phone> <phone> <brand>蘋果手機</brand> <description>蘋果手機的描述</description> </phone> <sex>男</sex> </student>

使用註解我們也可以看到也能達到相同的效果

注意:當使用XStream對象處理一個被註解的類型時,XStream對象也會處理所有與其相關的類型的註解信息,即該類型的父類、父接口、所有子類的註解。

十、XStream自定義轉換器

1、XStream自帶的轉換器

    XStream內部有許多轉換器,用於JavaBean對象到XML或JSON之間的轉換。這些轉換器的詳細信息網址:http://x-stream.github.io/converters.html

2、使用自定義轉換器

package io.github.xstream.test04; ​ import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.annotations.XStreamAlias; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; import lombok.ToString; ​ public class XStreamTest04 { public static void main(String[] args) { Student student =new Student("張三",19); XStream xStream = new XStream(); //註冊轉換器 xStream.registerConverter(new StudentConverter()); //序列化 String xml = xStream.toXML(student); System.out.println(xml); //反序列化 student=(Student)xStream.fromXML(xml); System.out.println(student); } } ​ @Getter @Setter @ToString @AllArgsConstructor class Student { private String name; private int age; }

自定義轉換器

package io.github.xstream.test04; ​ import com.thoughtworks.xstream.converters.Converter; import com.thoughtworks.xstream.converters.MarshallingContext; import com.thoughtworks.xstream.converters.UnmarshallingContext; import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; ​ public class StudentConverter implements Converter { //定義轉換器能轉換的JavaBean類型 @Override public boolean canConvert(Class type) { return type.equals(Student.class); } ​ //把對象序列化成XML或JSON @Override public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) { Student student = (Student) value; writer.startNode("姓名"); writer.setValue(student.getName()); writer.endNode(); writer.startNode("年齡"); writer.setValue(student.getAge() + ""); writer.endNode(); writer.startNode("轉換器"); writer.setValue("自定義的轉換器"); writer.endNode(); } ​ //把XML或JSON反序列化成對象 @Override public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { Student student = new Student("", -1); reader.moveDown(); student.setName(reader.getValue()); reader.moveUp(); reader.moveDown(); student.setAge(Integer.parseInt(reader.getValue())); reader.moveUp(); return student; } }

3、執行結果

<student>
  <姓名>張三</姓名>
  <年齡>19</年齡>
  <轉換器>自定義的轉換器</轉換器>
</student>
Security framework of XStream not initialized, XStream is probably vulnerable.
Student(name=張三, age=19)

4、常用的轉換器接口與抽象類

SingleValueConverter:單值轉換接口
AbstractSingleValueConverter:單值轉換抽象類
Converter:常規轉換器接口

十一、XStream對象流

1、對象輸出流

package io.github.xstream.test05; ​ import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.annotations.XStreamAsAttribute; import lombok.AllArgsConstructor; import lombok.ToString; ​ import java.io.*; ​ public class XStreamTest05 { public static void main(String[] args) throws IOException, ClassNotFoundException { XStreamTest05 xStreamTest04 = new XStreamTest05(); String path = "F:\\test.txt"; XStream xStream = new XStream();//需要XPP3庫 xStream.processAnnotations(Student.class); xStream.autodetectAnnotations(true); xStreamTest04.writeObject(xStream, path); } ​ //對象輸出流方法 public void writeObject(XStream xStream, String path) throws IOException { Student zs = new Student("張三", 20); Student ls = new Student("李四", 21); Student ww = new Student("王五", 22); ObjectOutputStream objectOutputStream = xStream.createObjectOutputStream(new FileOutputStream(path)); objectOutputStream.writeObject(zs); objectOutputStream.writeObject(ls); objectOutputStream.writeObject(ww); objectOutputStream.writeObject("totalStudent"); objectOutputStream.writeInt(3); objectOutputStream.close(); } } ​ @AllArgsConstructor @ToString //別名註解 @XStreamAlias("student") class Student { @XStreamAlias("name") //把字段節點設置成屬性 @XStreamAsAttribute private String studentName; private int age; }

2、在指定路徑中打開test.txt文件,查看執行結果

<object-stream> <student name="張三"> <age>20</age> </student> <student name="李四"> <age>21</age> </student> <student name="王五"> <age>22</age> </student> <string>totalStudent</string> <int>3</int> </object-stream>

注意:XStream對象流是通過標準java.io.ObjectOutputStream和java.io.ObjectInputStream對象。因為XML文檔只能有一個根節點,必須包裝在一個序列化的所有元素額外的根節點。這個根節點默認為<object-stream>上面的例子所示。 

3、對象輸入流

package io.github.xstream.test05; ​ import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.annotations.XStreamAsAttribute; import lombok.AllArgsConstructor; import lombok.ToString; ​ import java.io.*; ​ public class XStreamTest05 { public static void main(String[] args) throws IOException, ClassNotFoundException { XStreamTest05 xStreamTest04 = new XStreamTest05(); String path = "F:\\test.txt"; XStream xStream = new XStream();//需要XPP3庫 xStream.processAnnotations(Student.class); xStream.autodetectAnnotations(true); xStreamTest04.readObject(xStream, path); } ​ //對象輸入流方法 public void readObject(XStream xStream, String path) throws IOException, ClassNotFoundException { ObjectInputStream objectInputStream = xStream.createObjectInputStream(new FileInputStream(path)); System.out.println((Student) objectInputStream.readObject()); System.out.println((Student) objectInputStream.readObject()); System.out.println((Student) objectInputStream.readObject()); System.out.println(objectInputStream.readObject()); System.out.println(objectInputStream.readInt()); } } ​ @AllArgsConstructor @ToString //別名註解 @XStreamAlias("student") class Student { @XStreamAlias("name") //把字段節點設置成屬性 @XStreamAsAttribute private String studentName; private int age; }

4、執行結果

Student(studentName=張三, age=20)
Student(studentName=李四, age=21)
Student(studentName=王五, age=22)
totalStudent
3

十二、XStream持久化API

1、保存Java對象

package io.github.xstream.test06; ​ import com.thoughtworks.xstream.persistence.FilePersistenceStrategy; import com.thoughtworks.xstream.persistence.PersistenceStrategy; import com.thoughtworks.xstream.persistence.XmlArrayList; import lombok.AllArgsConstructor; import lombok.ToString; ​ import java.io.File; import java.util.List; ​ public class XStreamTest06 { public static void main(String[] args) { XStreamTest06 xStreamTest06=new XStreamTest06(); xStreamTest06.saveObject(); } ​ //保存Java對象 public void saveObject(){ PersistenceStrategy strategy = new FilePersistenceStrategy(new File("F:\\")); List list = new XmlArrayList(strategy); list.add(new Student("張三",13)); list.add(new Student("李四",21)); list.add(new Student("王五",17)); } } ​ @ToString @AllArgsConstructor class Student { private String name; private int age; }

2、運行程序結果,在F磁盤的根路徑可以看到有三個文件:int@0.xml、int@1.xml、int@2.xml,每個對象都被序列化到XML文件里

3、讀取並刪除JavaBean對象

package io.github.xstream.test06; ​ import com.thoughtworks.xstream.persistence.FilePersistenceStrategy; import com.thoughtworks.xstream.persistence.PersistenceStrategy; import com.thoughtworks.xstream.persistence.XmlArrayList; import lombok.AllArgsConstructor; import lombok.ToString; ​ import java.io.File; import java.util.Iterator; import java.util.List; ​ public class XStreamTest06 { public static void main(String[] args) { XStreamTest06 xStreamTest06 = new XStreamTest06(); xStreamTest06.deleteObject(); } //讀取並刪除Java對象 public void deleteObject() { PersistenceStrategy strategy = new FilePersistenceStrategy(new File("F:\\")); List list = new XmlArrayList(strategy); for (Iterator it = list.iterator(); it.hasNext(); ) { System.out.println((Student) it.next()); //刪除對象序列化文件 it.remove(); } } } ​ @ToString @AllArgsConstructor class Student { private String name; private int age; }

4、運行程序結果,可以看到把F磁盤的根路徑int@0.xml、int@1.xml、int@2.xml文件刪除了

Security framework of XStream not initialized, XStream is probably vulnerable.
Student(name=張三, age=13)
Student(name=李四, age=21)
Student(name=王五, age=17)

十三、XStream操作JSON

1、XStream序列化JSON的重命名

package io.github.xstream.test07; ​ import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver; import io.github.xstream.test04.StudentConverter; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; import lombok.ToString; ​ public class XStreamTest07 { public static void main(String[] args) { XStreamTest07 xStreamTest07 = new XStreamTest07(); xStreamTest07.serializeJson(); } ​ public void serializeJson() { Student student = new Student("張三", 19); XStream xStream = new XStream(new JettisonMappedXmlDriver());//設置Json解析器 xStream.autodetectAnnotations(true); //JSON序列化 String xml = xStream.toXML(student); System.out.println(xml); //JSON反序列化 student = (Student) xStream.fromXML(xml); System.out.println(student); } } ​ @ToString @AllArgsConstructor @XStreamAlias("人") class Student { @XStreamAlias("姓名") private String name; @XStreamAlias("年齡") private int age; }

2、運行結果

{"人":{"姓名":"張三","年齡":19}}
Student(name=張三, age=19)
Security framework of XStream not initialized, XStream is probably vulnerable.

注意:XStream序列化JSON的重命名的方式與其序列化成XML的方式一樣!

3、去掉序列化JSON的根節點

package io.github.xstream.test07; ​ import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver; import com.thoughtworks.xstream.io.json.JsonHierarchicalStreamDriver; import com.thoughtworks.xstream.io.json.JsonWriter; import io.github.xstream.test04.StudentConverter; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; import lombok.ToString; ​ import java.io.Writer; ​ public class XStreamTest07 { public static void main(String[] args) { XStreamTest07 xStreamTest07 = new XStreamTest07(); xStreamTest07.removeRootNode(); } ​ public void removeRootNode() { Student student = new Student("張三", 19); XStream xStream = new XStream(new JsonHierarchicalStreamDriver() { public HierarchicalStreamWriter createWriter(Writer writer) { return new JsonWriter(writer, JsonWriter.DROP_ROOT_MODE); } }); //Json序列化 String xml = xStream.toXML(student); System.out.println(xml); } } ​ @ToString @AllArgsConstructor @XStreamAlias("人") class Student { @XStreamAlias("姓名") private String name; @XStreamAlias("年齡") private int age; }

4、運行結果

{
  "name": "張三", "age": 19 }

注意:去掉根節點后的JSON串是不能反序列化的,因為XStream不知道它的類型。

5、JSON的解析器區別

前面兩個例子使用了不同的JSON解析器,這裏說明他們的不同之處:

  1. JettisonMappedXmlDriver:是支持序列化和反序列化Json的。

  2. JsonHierarchicalStreamDriver:只支持序列化,不支持反序列化。

 

參考:

    http://x-stream.github.io

    https://www.yiibai.com/xstream

    https://www.cnblogs.com/LiZhiW/p/4313493.html

● 別在 Java 代碼里亂打日誌了,這才是正確的打日誌姿勢!

● 高可用Redis服務架構分析與搭建

● 8 種方案,幫你解決重複提交問題!請拿走

● IDEA 解決 Maven 依賴衝突的高能神器,這一篇夠不夠?

● 你連微服務的網關都說不清楚,還天天鼓搗着要把項目拆分微服務?

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

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

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

數據結構:用實例分析ArrayList與LinkedList的讀寫性能_包裝設計

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

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

目錄

  • 背景
  • ArrayList
  • LinkedList
  • 實例分析
    • 1、增加數據
    • 2、插入數據
    • 3、遍曆數據
      • 3.1、LinkedList遍歷改進
  • 總結

背景

ArrayList與LinkedList是Java編程中經常會用到的兩種基本數據結構,在書本上一般會說明以下兩個特點:

  • 對於需要快速隨機訪問元素,應該使用ArrayList
  • 對於需要快速插入,刪除元素,應該使用LinkedList

該文通過實際的例子分析這兩種數據的讀寫性能。

ArrayList

ArrayList是實現了基於動態數組的數據結構:

private static final int DEFAULT_CAPACITY = 10;
...
transient Object[] elementData;
...
public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

LinkedList

LinkedList是基於鏈表的數據結構。

private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }
...    
transient Node<E> first;
transient Node<E> last;
...
private void linkFirst(E e) {
        final Node<E> f = first;
        final Node<E> newNode = new Node<>(null, e, f);
        first = newNode;
        if (f == null)
            last = newNode;
        else
            f.prev = newNode;
        size++;
        modCount++;
    }

實例分析

  • 通過對兩個數據結構分別增加、插入、遍歷進行讀寫性能分析
1、增加數據
public class ArrayListAndLinkList {
    public final static int COUNT=100000;
    public static void main(String[] args) {

        // ArrayList插入
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
        Long start = System.currentTimeMillis();
        System.out.println("ArrayList插入開始時間:" + sdf.format(start));

        ArrayList<Integer> arrayList = new ArrayList<>();
        for (int i = 0; i < COUNT; i++) {
            arrayList.add(i);
        }

        Long end = System.currentTimeMillis();
        System.out.println("ArrayList插入結束時間:" + sdf.format(end));
        System.out.println("ArrayList插入" + (end - start) + "毫秒");


        // LinkedList插入
        start = System.currentTimeMillis();
        System.out.println("LinkedList插入開始時間:" + sdf.format(start));
        LinkedList<Integer> linkedList = new LinkedList<>();
        for (int i = 0; i < COUNT; i++) {
            linkedList.add(i);
        }
        end = System.currentTimeMillis();
        System.out.println("LinkedList插入結束時間:" + sdf.format(end));
        System.out.println("LinkedList插入結束時間" + (end - start) + "毫秒");
     }
}

輸出如下:
兩者寫入的性能相差不大!

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

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

2、插入數據

在原有增加的數據上,在index:100的位置上再插入10萬條數據。

public class ArrayListAndLinkList {
    public final static int COUNT=100000;
    public static void main(String[] args) {

        // ArrayList插入
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
        Long start = System.currentTimeMillis();
        System.out.println("ArrayList插入開始時間:" + sdf.format(start));

        ArrayList<Integer> arrayList = new ArrayList<>();
        for (int i = 0; i < COUNT; i++) {
            arrayList.add(i);
        }
        for (int i = 0; i < COUNT; i++) {
            arrayList.add(100,i);
        }

        Long end = System.currentTimeMillis();
        System.out.println("ArrayList插入結束時間:" + sdf.format(end));
        System.out.println("ArrayList插入" + (end - start) + "毫秒");


        // LinkedList插入
        start = System.currentTimeMillis();
        System.out.println("LinkedList插入開始時間:" + sdf.format(start));
        LinkedList<Integer> linkedList = new LinkedList<>();
        for (int i = 0; i < COUNT; i++) {
            linkedList.add(i);
        }
        for (int i = 0; i < COUNT; i++) {
            linkedList.add(100,i);
        }
        end = System.currentTimeMillis();
        System.out.println("LinkedList插入結束時間:" + sdf.format(end));
        System.out.println("LinkedList插入結束時間" + (end - start) + "毫秒");
     }
}

輸出如下:
ArrayList的性能明顯比LinkedList的性能差了很多。

看下原因:
ArrayList的插入源碼:

  public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

ArrayList的插入原理:在index位置上插入后,在index後續的數據上需要做逐一複製。

LinkedList的插入源碼:

public void add(int index, E element) {
        checkPositionIndex(index);

        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
 }
 ...
  void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

LinkedList的插入原理:在原來相互鏈接的兩個節點(Node)斷開,把新的結點插入到這兩個節點中間,根本不存在複製這個過程。

3、遍曆數據

在增加和插入的基礎上,利用get方法進行遍歷。

public class ArrayListAndLinkList {
    public final static int COUNT=100000;
    public static void main(String[] args) {

        // ArrayList插入
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
        Long start = System.currentTimeMillis();
        System.out.println("ArrayList插入開始時間:" + sdf.format(start));

        ArrayList<Integer> arrayList = new ArrayList<>();
        for (int i = 0; i < COUNT; i++) {
            arrayList.add(i);
        }
        for (int i = 0; i < COUNT; i++) {
            arrayList.add(100,i);
        }

        Long end = System.currentTimeMillis();
        System.out.println("ArrayList插入結束時間:" + sdf.format(end));
        System.out.println("ArrayList插入" + (end - start) + "毫秒");


        // LinkedList插入
        start = System.currentTimeMillis();
        System.out.println("LinkedList插入開始時間:" + sdf.format(start));
        LinkedList<Integer> linkedList = new LinkedList<>();
        for (int i = 0; i < COUNT; i++) {
            linkedList.add(i);
        }
        for (int i = 0; i < COUNT; i++) {
            linkedList.add(100,i);
        }
        end = System.currentTimeMillis();
        System.out.println("LinkedList插入結束時間:" + sdf.format(end));
        System.out.println("LinkedList插入結束時間" + (end - start) + "毫秒");

        // ArrayList遍歷
        start = System.currentTimeMillis();
        System.out.println("ArrayList遍歷開始時間:" + sdf.format(start));
        for (int i = 0; i < 2*COUNT; i++) {
            arrayList.get(i);
        }
        end = System.currentTimeMillis();
        System.out.println("ArrayList遍歷開始時間:" + sdf.format(end));
        System.out.println("ArrayList遍歷開始時間" + (end - start) + "毫秒");

        // LinkedList遍歷
        start = System.currentTimeMillis();
        System.out.println("LinkedList遍歷開始時間:" + sdf.format(start));
        for (int i = 0; i < 2*COUNT; i++) {
            linkedList.get(i);
        }
        end = System.currentTimeMillis();
        System.out.println("LinkedList遍歷開始時間:" + sdf.format(end));
        System.out.println("LinkedList遍歷開始時間" + (end - start) + "毫秒");

    }
}

輸出如下:

兩者的差異巨大:
我們看一下LInkedList的get方法:從頭遍歷或從尾部遍歷結點

public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }
 ...
 Node<E> node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }
3.1、LinkedList遍歷改進

我們採用迭代器對LinkedList的遍歷進行改進:

		...
		// LinkedList遍歷
        start = System.currentTimeMillis();
        System.out.println("LinkedList遍歷開始時間:" + sdf.format(start));
        Iterator<Integer> iterator = linkedList.iterator();
        while(iterator.hasNext()){
            iterator.next();
        }
        end = System.currentTimeMillis();
        System.out.println("LinkedList遍歷開始時間:" + sdf.format(end));
        System.out.println("LinkedList遍歷開始時間" + (end - start) + "毫秒");

再看下結果:
兩者的遍歷性能接近。

總結

  • List使用首選ArrayList。對於個別插入刪除非常多的可以使用LinkedList。
  • LinkedList,遍歷建議使用Iterator迭代器,尤其是數據量較大時LinkedList避免使用get遍歷。

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

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

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

從linux源碼看socket的阻塞和非阻塞_台中搬家

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

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

從linux源碼看socket的阻塞和非阻塞

筆者一直覺得如果能知道從應用到框架再到操作系統的每一處代碼,是一件Exciting的事情。
大部分高性能網絡框架採用的是非阻塞模式。筆者這次就從linux源碼的角度來闡述socket阻塞(block)和非阻塞(non_block)的區別。 本文源碼均來自採用Linux-2.6.24內核版本。

一個TCP非阻塞client端簡單的例子

如果我們要產生一個非阻塞的socket,在C語言中如下代碼所示:

// 創建socket
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
...
// 更改socket為nonblock
fcntl(sock_fd, F_SETFL, fdflags | O_NONBLOCK);
// connect
....
while(1)  {  
    int recvlen = recv(sock_fd, recvbuf, RECV_BUF_SIZE) ; 
    ......
} 
...

由於網絡協議非常複雜,內核裏面用到了大量的面向對象的技巧,所以我們從創建連接開始,一步一步追述到最後代碼的調用點。

socket的創建

很明顯,內核的第一步應該是通過AF_INET、SOCK_STREAM以及最後一個參數0定位到需要創建一個TCP的socket,如下圖綠線所示:

我們跟蹤源碼調用

socket(AF_INET, SOCK_STREAM, 0)
	|->sys_socket 進入系統調用
		|->sock_create
			|->__sock_create

進一步分析__sock_create的代碼判斷:

const struct net_proto_family *pf;
// RCU(Read-Copy Update)是linux的一種內核同步方法,在此不闡述
// family=INET
pf = rcu_dereference(net_families[family]);
err = pf->create(net, sock, protocol);

由於family是AF_INET協議,注意在操作系統裏面定義了PF_INET等於AF_INET,
內核通過函數指針實現了對pf(net_proto_family)的重載。如下圖所示:

則通過源碼可知,由於是AF_INET(PF_INET),所以net_families[PF_INET].create=inet_create(以後我們都用PF_INET表示),即
pf->create = inet_create;
進一步追溯調用:

inet_create(struct net *net, struct socket *sock, int protocol){
	Sock* sock;
	......
	// 此處是尋找對應協議處理器的過程
lookup_protocol:
	// 迭代尋找protocol==answer->protocol的情況
	list_for_each_rcu(p, &inetsw[sock->type]) answer = list_entry(p, struct inet_protosw, list);

		/* Check the non-wild match. */
		if (protocol == answer->protocol) {
			if (protocol != IPPROTO_IP)
				break;
		}
	......
	// 這邊answer指的是SOCK_STREAM
	sock->ops = answer->ops;
	answer_no_check = answer->no_check;
	// 這邊sk->prot就是answer_prot=>tcp_prot
	sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot);
	sock_init_data(sock, sk);
	......
}

上面的代碼就是在INET中尋找SOCK_STREAM的過程了
我們再看一下inetsw[SOCK_STREAM]的具體配置:

static struct inet_protosw inetsw_array[] =
{
	{
		.type =       SOCK_STREAM,
		.protocol =   IPPROTO_TCP,
		.prot =       &tcp_prot,
		.ops =        &inet_stream_ops,
		.capability = -1,
		.no_check =   0,
		.flags =      INET_PROTOSW_PERMANENT |
			      INET_PROTOSW_ICSK,
	},
	......
}

這邊也用了重載,AF_INET有TCP、UDP以及Raw三種:

從上述代碼,我們可以清楚的發現sock->ops=&inet_stream_ops;

const struct proto_ops inet_stream_ops = {
	.family		   = PF_INET,
	.owner		   = THIS_MODULE,
	......
	.sendmsg	   = tcp_sendmsg,
	.recvmsg	   = sock_common_recvmsg,
	......
}	

即sock->ops->recvmsg = sock_common_recvmsg;
同時sock->sk->sk_prot = tcp_prot;

我們再看下tcp_prot中的各個函數重載的定義:

struct proto tcp_prot = {
	.name			= "TCP",
	.close			= tcp_close,
	.connect		= tcp_v4_connect,
	.disconnect		= tcp_disconnect,
	.accept			= inet_csk_accept,
	......
	// 我們重點考察tcp的讀
	.recvmsg		= tcp_recvmsg,
	......
}

fcntl控制socket的阻塞\非阻塞狀態

我們用fcntl修改socket的阻塞\非阻塞狀態。
事實上:
fcntl的作用就是將O_NONBLOCK標誌位存儲在sock_fd對應的filp結構的f_lags里,如下圖所示。

fcntl(sock_fd, F_SETFL, fdflags | O_NONBLOCK);
	|->setfl

追蹤setfl代碼:

static int setfl(int fd, struct file * filp, unsigned long arg) {
	......
	filp->f_flags = (arg & SETFL_MASK) | (filp->f_flags & ~SETFL_MASK);
	......
}

上圖中,由sock_fd在task_struct(進程結構體)->files_struct->fd_array中找到對應的socket的file描述符,再修改file->flags

在調用socket.recv的時候

我們跟蹤源碼調用:

socket.recv
	|->sys_recv
		|->sys_recvfrom
			|->sock_recvmsg
				|->__sock_recvmsg
					|->sock->ops->recvmsg

由上文可知:
sock->ops->recvmsg = sock_common_recvmsg;

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

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

sock

值得注意的是,在sock_recmsg中,有對標識O_NONBLOCK的處理

	if (sock->file->f_flags & O_NONBLOCK)
		flags |= MSG_DONTWAIT;

上述代碼中sock關聯的file中獲取其f_flags,如果flags有O_NONBLOCK標識,那麼就設置msg_flags為MSG_DONTWAIT(不等待)。
fcntl與socket就是通過其共同操作File結構關聯起來的。

繼續跟蹤調用

sock_common_recvmsg

int sock_common_recvmsg(struct kiocb *iocb, struct socket *sock,
			struct msghdr *msg, size_t size, int flags) {
	......
	// 如果flags的MSG_DONTWAIT標識置位,則傳給recvmsg的第5個參數為正,否則為0
	err = sk->sk_prot->recvmsg(iocb, sk, msg, size, flags & MSG_DONTWAIT,
				   flags & ~MSG_DONTWAIT, &addr_len);
	.....				   
}

由上文可知:
sk->sk_prot->recvmsg 其中sk_prot=tcp_prot,即最終調用的是tcp_prot->tcp_recvmsg,
上面的代碼可以看出,如果fcntl(O_NONBLOCK)=>MSG_DONTWAIT置位=>(flags & MSG_DONTWAIT)>0, 再結合tcp_recvmsg的函數簽名,即如果設置了O_NONBLOCK的話,設置給tcp_recvmsg的nonblock參數>0,關係如下圖所示:

最終的調用邏輯tcp_recvmsg

首先我們看下tcp_recvmsg的函數簽名:

int tcp_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
		size_t len, int nonblock, int flags, int *addr_len)

顯然我們關注焦點在(int nonblock這個參數上):

int tcp_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
		size_t len, int nonblock, int flags, int *addr_len){
	......	
	// copied是指向用戶空間拷貝了多少字節,即讀了多少
	int copied;
	// target指的是期望多少字節
	int target;
	// 等效為timo = nonblock ? 0 : sk->sk_rcvtimeo;
	timeo = sock_rcvtimeo(sk, nonblock);
	......	
	// 如果設置了MSG_WAITALL標識target=需要讀的長度
	// 如果未設置,則為最低低水位值
	target = sock_rcvlowat(sk, flags & MSG_WAITALL, len);
	......

	do{
		// 表明讀到數據
		if (copied) {
			// 注意,這邊只要!timeo,即nonblock設置了就會跳出循環
			if (sk->sk_err ||
			    sk->sk_state == TCP_CLOSE ||
			    (sk->sk_shutdown & RCV_SHUTDOWN) ||
			    !timeo ||
			    signal_pending(current) ||
			    (flags & MSG_PEEK))
			break;
		}else{
			// 到這裏,表明沒有讀到任何數據
			// 且nonblock設置了導致timeo=0,則返回-EAGAIN,符合我們的預期
			if (!timeo) {
				copied = -EAGAIN;
				break;
		}
		// 這邊如果堵到了期望的數據,繼續,否則當前進程阻塞在sk_wait_data上
		if (copied >= target) {
			/* Do not sleep, just process backlog. */
			release_sock(sk);
			lock_sock(sk);
		} else
			sk_wait_data(sk, &timeo);
	} while (len > 0);		
	......
	return copied
}

上面的邏輯歸結起來就是:
(1)在設置了nonblock的時候,如果copied>0,則返回讀了多少字節,如果copied=0,則返回-EAGAIN,提示應用重複調用。
(2)如果沒有設置nonblock,如果讀取的數據>=期望,則返回讀取了多少字節。如果沒有則用sk_wait_data將當前進程等待。
如下流程圖所示:

阻塞函數sk_wait_data

sk_wait_data代碼-函數為:

	// 將進程狀態設置為可打斷INTERRUPTIBLE
	prepare_to_wait(sk->sk_sleep, &wait, TASK_INTERRUPTIBLE);
	set_bit(SOCK_ASYNC_WAITDATA, &sk->sk_socket->flags);
	// 通過調用schedule_timeout讓出CPU,然後進行睡眠
	rc = sk_wait_event(sk, timeo, !skb_queue_empty(&sk->sk_receive_queue));
	// 到這裏的時候,有網絡事件或超時事件喚醒了此進程,繼續運行
	clear_bit(SOCK_ASYNC_WAITDATA, &sk->sk_socket->flags);
	finish_wait(sk->sk_sleep, &wait);

該函數調用schedule_timeout進入睡眠,其進一步調用了schedule函數,首先從運行隊列刪除,其次加入到等待隊列,最後調用和體繫結構相關的switch_to宏來完成進程間的切換。
如下圖所示:

阻塞后什麼時候恢復運行呢

情況1:有對應的網絡數據到來

首先我們看下網絡分組到來的內核路徑,網卡發起中斷後調用netif_rx將事件掛入CPU的等待隊列,並喚起軟中斷(soft_irq),再通過linux的軟中斷機制調用net_rx_action,如下圖所示:

注:上圖來自PLKA(<<深入Linux內核架構>>)
緊接着跟蹤next_rx_action

next_rx_action
	|-process_backlog
		......
			|->packet_type->func 在這裏我們考慮ip_rcv
					|->ipprot->handler 在這裏ipprot重載為tcp_protocol
						(handler 即為tcp_v4_rcv)					

緊接着tcp_v4_rcv:

tcp_input.c
tcp_v4_rcv
	|-tcp_v4_do_rcv
		|-tcp_rcv_state_process
			|-tcp_data_queue
				|-sk->sk_data_ready=sock_def_readable
					|-wake_up_interruptible
						|-__wake_up
							|-__wake_up_common

在這裏__wake_up_common將停在當前wait_queue_head_t中的進程喚醒,即狀態改為task_running,等待CFS調度以進行下一步的動作,如下圖所示。

情況2:設定的超時時間到來

在前面調用sk_wait_event中調用了schedule_timeout

fastcall signed long __sched schedule_timeout(signed long timeout) {
	......
	// 設定超時的回掉函數為process_timeout
	setup_timer(&timer, process_timeout, (unsigned long)current);
	__mod_timer(&timer, expire);
	// 這邊讓出CPU
	schedule();
	del_singleshot_timer_sync(&timer);
	timeout = expire - jiffies;
 out:
 	// 返回經過了多長事件
	return timeout < 0 ? 0 : timeout;	
}

process_timeout函數即是將此進程重新喚醒

static void process_timeout(unsigned long __data)
{
	wake_up_process((struct task_struct *)__data);
}

總結

linux內核源代碼博大精深,閱讀其代碼很費周折。希望筆者這篇文章能幫助到閱讀linux網絡協議棧代碼的人。

公眾號

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

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

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

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

Volcano火山:容器與批量計算的碰撞_台中搬家公司

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

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

【摘要】 Volcano是基於Kubernetes構建的一個通用批量計算系統,它彌補了Kubernetes在“高性能應用”方面的不足,支持TensorFlow、Spark、MindSpore等多個領域框架,幫助用戶通過Kubernetes構建統一的容器平台。

Kubernetes 是當前非常流行的容器編排框架,在其發展早期重點以微服務類應用為主。隨着Kuberentes的用戶越來越多,更多的用戶希望在Kubernetes上運行BigData和AI框架,如Spark、TensorFlow等以構建統一的容器平台。但在Kubernetes運行這些高性能應用時,Kubernetes的默認調度器無法滿足高性能應用的需求,例如:公平調度、優先級、隊列等高級調度功能。由於Kubernetes的默認調度器是基於Pod進行調度,雖然在1.17中引入了調度框架,但仍無法滿足高性能應用對作業級調度的需求。

容器批量計算平台Volcano

針對雲原生場景下的高性能應用場景,華為雲容器團隊推出了Volcano項目。Volcano是基於Kubernetes構建的一個通用批量計算系統,它彌補了Kubernetes在“高性能應用”方面的不足,支持TensorFlow、Spark、MindSpore等多個領域框架,幫助用戶通過Kubernetes構建統一的容器平台。Volcano作為容器調度系統,不僅包括了作業調度,還包含了作業生命周期管理、多集群調度、命令行、數據管理、作業視圖及硬件加速等功能。

而在調度方面,Volcano 又對場景進行了細分、歸類,並提供了相關的方案及算法;同時也為這些功能提供了調度框架,方便用戶對調度器進行擴展。對於分佈式計算或是并行計算來說,根據場景和作業屬性的不同,也可以對其進行細分;在 《并行計算導論》 中將并行計算大致分為三類:

  • 簡單的并行

簡單的并行指多個子任務(tasks)之間沒有通信也不需要同步,可以完全的并行的執行。比較著名的例子應該就屬MapReduce了,它的兩個階段都屬於這種類型:mapper任務在執行時並不會彼此通信同步運行狀態;另一個常見的例子是蒙特·卡羅方法 ,各個子任務在計算隨機數時也無需彼此通信、同步。由於這種并行計算有比較廣泛的應用,例如 數據處理、VatR 等,針對不同的場景也產生了不同的調度框架,例如 Hadoop、DataSynapse 和 Symphony。同時,由於子任務之間無需信息和同步,當其中某幾個計算節點(workers)被驅逐后,雖然作業的執行時間可能會變長,但整個作業仍可以順利完成;而當計算節點增加時,作業的執行時間一般都會縮短。因此,這種作業也常常被稱作 Elastic Job。

  • 複雜的并行

複雜的并行作業指多個子任務 (tasks) 之間需要同步信息來執行複雜的并行算法,單個子任務無法完成部分計算。最近比較有名的例子應該算是 Tensorflow 的 “ps-work模式” 和 ring all-reduce 了,各個子任務之間需要大量的數據交換和信息同步,單獨的子任務無法獨立完成。正是由於作業的這種屬性,對作業調度平台也提出了相應的調度要求,比如 gang-scheduling、作業拓撲等。由於子任務之間需要彼此通信,因此作業在啟動后無法動態擴展子任務,在沒有checkpoint的情況下,任一子任務失敗或驅逐,整個作業都需要重啟,這種作業也常常被稱作 Batch Job,傳統的HPC場景多屬於這種類型的并行作業,針對這種場景的調度平台為 Slurm/PBS/SGE/HTCondor 等。

  • 流水線并行

流水線并行是指作業的多個子任務之間存在依賴關係,但不需要前置任務完全結束后再開始後續的任務;比如 Hadoop 里有相應的研究:在 Map 沒有完全結束的時候就部分開始 Reduce 階段,從而提高任務的并行度,提高整體的運行性能。符合這種場景的應用相對來說比較少,一般都做為性能優化;因此沒有針對這種場景的作業管理平台。需要區分一下工作流與流水線并行,工作流一般指作業之間的依賴關係,而流水線并行一般指作業內部多個任務之間的依賴。由於工作流中的作業差異比較大,很難提前開始後續步驟。

值得一提的是”二次調度”。由於簡單并行的作業一般會有大量的子任務,而且每個子任務所需要的資源相對一致,子任務之間也沒有通信和同步;使得資源的復用率相對比較高,因此二次調度在這種場景下能發揮比較大的作用;Hadoop的YARN,Symphony的EGO都屬於這種類型。但是在面對複雜并行的作業時,二次調度就顯得有也吃力;複雜并行作業一般並沒有太多的子任務,子任務之間還經常需要同時啟動,子任務之間的通信拓撲也可能不同 (e.g. ps/worker, mpi),而且作業與作業之間對資源的需求差異較大,因此導致了資源的復用率較低。

雖然針對兩種不同并行作業類型有不同的作業、資源管理平台,但是根本的目標都是為作業尋找最優的資源;因此,Volcano一直以支持以多種類型的作業為目標進行設計。目前,Volcano可以同時支持 Spark、TensorFlow和MPI等多種類型的作業。

常見調度場景

1.組調度 (Gang-scheduling)

運行批處理作業(如Tensorflow/MPI)時,必須協調作業的所有任務才能一起啟動;否則,將不會啟動任何任務。如果有足夠的資源并行運行作業的所有任務,則該作業將正確執行; 但是,在大多數情況下,尤其是在prem環境中,情況並非如此。在最壞的情況下,由於死鎖,所有作業都掛起。其中每個作業只成功啟動了部分任務,並等待其餘任務啟動。

2.作業級的公平調度 (Job-based Fair-share)

當運行多個彈性作業(如流媒體)時,需要公平地為每個作業分配資源,以滿足多個作業競爭附加資源時的SLA/QoS要求。在最壞的情況下,單個作業可能會啟動大量的pod資源利用率低, 從而阻止其他作業由於資源不足而運行。為了避免分配過小(例如,為每個作業啟動一個Pod),彈性作業可以利用協同調度來定義應該啟動的Pod的最小可用數量。 超過指定的最小可用量的任何pod都將公平地與其他作業共享集群資源。

3.隊列 (Queue)

隊列還廣泛用於共享彈性工作負載和批處理工作負載的資源。隊列的主要目的是:

  • 在不同的“租戶”或資源池之間共享資源
  • 為不同的“租戶”或資源池支持不同的調度策略或算法

這些功能可以通過層次隊列進一步擴展,在層次隊列中,項目被賦予額外的優先級,這將允許它們比隊列中的其他項目“跳轉”。在kube批處理中,隊列被實現為集群範圍的CRD。 這允許將在不同命名空間中創建的作業放置在共享隊列中。隊列資源根據其隊列配置(kube batch#590)按比例劃分。當前不支持分層隊列,但正在進行開發。

集群應該能夠在不減慢任何操作的情況下處理隊列中的大量作業。其他的HPC系統可以處理成百上千個作業的隊列,並隨着時間的推移緩慢地處理它們。如何與庫伯內特斯達成這樣的行為是一個懸而未決的問題。支持跨越多個集群的隊列可能也很有用,在這種情況下,這是一個關於數據應該放在哪裡以及etcd是否適合存儲隊列中的所有作業或pod的問題。

4.面向用戶的, 跨隊列的公平調度 (Namespace-based fair-share Cross Queue)

在隊列中,每個作業在調度循環期間有幾乎相等的調度機會,這意味着擁有更多作業的用戶有更大的機會安排他們的作業,這對其他用戶不公平。 例如,有一個隊列包含少量資源,有10個pod屬於UserA,1000個pod屬於UserB。在這種情況下,UserA的pod被綁定到節點的概率較小。

為了平衡同一隊列中用戶之間的資源使用,需要更細粒度的策略。考慮到Kubernetes中的多用戶模型,使用名稱空間來區分不同的用戶, 每個命名空間都將配置一個權重,作為控制其資源使用優先級的手段。

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

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

5.基於時間的公平調度 (Fairness over time)

對於批處理工作負載,通常不要求在某個時間點公平地分配資源,而是要求在長期內公平地分配資源。例如,如果有用戶提交大作業,則允許用戶(或特定隊列)在一定時間內使用整個集群的一半, 這是可以接受的,但在下一輪調度(可能是作業完成后數小時)中,應懲罰此用戶(或隊列)而不是其他用戶(或隊列)。在 HTCondor 中可以看到如何實現這種行為的好例子。

6.面向作業的優先級調度 (Job-based priority)

Pod優先級/搶佔在1.14版本中被中斷,它有助於確保高優先級的pod在低優先級的pod之前綁定。不過,在job/podgroup級別的優先級上仍有一些工作要做,例如高優先級job/podgroup應該嘗試以較低優先級搶佔整個job/podgroup,而不是從不同job/podgroup搶佔幾個pod。

7.搶佔 (Preemption & Reclaim)

通過公平分享來支持借貸模型,一些作業/隊列在空閑時會過度使用資源。但是,如果有任何進一步的資源請求,資源“所有者”將“收回”。 資源可以在隊列或作業之間共享:回收用於隊列之間的資源平衡,搶佔用於作業之間的資源平衡。

8.預留與回填 (Reservation & Backfill)

當一個請求大量資源的“巨大”作業提交給kubernetes時,當有許多小作業在管道中時,該作業可能會餓死,並最終根據當前的調度策略/算法被殺死。為了避免飢餓, 應該有條件地為作業保留資源,例如超時。當資源被保留時,它們可能會處於空閑和未使用狀態。為了提高資源利用率,調度程序將有條件地將“較小”作業回填到那些保留資源中。 保留和回填都是根據插件的反饋觸發的:volcano調度器提供了幾個回調接口,供開發人員或用戶決定哪些作業應該被填充或保留。

Volcano 調度框架

Volcano調度器通過作業級的調度和多種插件機制來支持多種作業;Volcano的插件機制有效的支撐了針對不同場景算法的落地,從早期的gang-scheduling/co-scheduling,到後來各個級別的公平調度。下圖展示了Volcano調度器的總體架構:

Cache 緩存了集群中Node和Pod信息,並根據PodGroup的信息重新構建 Job (PodGroup) 和 Task (Pod) 的關係。由於在分佈式系統中很難保證信息的同步,因此調度器經常以某一時間點的集群快照進行調度;並保證每個調度周期的決定是一致的。在每個調度周期中,Volcano 通過以下幾個步驟派發作業:

  • 在每個調度周期都會創建一個Session對象,用來存儲當前調度周期的所需的數據,例如,Cache 的一個快照。當前的調度器中僅創建了一個Session,並由一個調度線程執行;後續將會根據需要創建多個Session,併為每個Session分配一個線程進行調度;並由Cache來解決調度衝突。
  • 在每個調度周期中,會按順序執行 OpenSession, 配置的多個動作(action)和CloseSession。在 OpenSession中用戶可以註冊自定義的插件,例如gang、 drf,這些插件為action提供了相應算法;多個action根據配置順序執行,調用註冊的插件進行調度;最後,CloseSession負責清理中間數據。

(1) action是第一級插件,定義了調度周期內需要的各個動作;默認提供 enqueue、allocate、 preempt和backfill四個action。以allocate為例,它定義了調度中資源分配過程:根據 plugin 的 JobOrderFn 對作業進行排序,根據NodeOrderFn對節點進行排序,檢測節點上的資源是否滿足,滿足作業的分配要求(JobReady)后提交分配決定。由於action也是基於插件機制,因此用戶可以重新定義自己的分配動作,例如 基於圖的調度算法firmament。

(2) plugin是第二級插件,定義了action需要的各個算法;以drf插件為例,為了根據dominant resource進行作業排序,drf插件實現了 JobOrderFn函數。JobOrderFn函數根據 drf 計算每個作業的share值,share值較低代表當前作業分配的資源較少,因此會為其優先分配資源;drf插件還實現了EventHandler回調函數,當作業被分配或搶佔資源后,調度器會通知drf插件來更新share值。

  • Cache 不僅提供了集群的快照,同時還提供了調度器與kube-apiserver的交互接口,調度器與kube-apiserver之間的通信也都通過Cache來完成,例如 Bind。
  • 同時,為了支持上面這些場景,Volcano的調度器還增加了多個Pod狀態以提高調度的性能:
  • Pending: 當Pod被創建后就處於Pending狀態,等待調度器對其進行調度;調度的主要目的也是為這些Pending的Pod尋找最優的資源
  • Allocated: 當Pod被分配空閑資源,但是還沒有向kube-apiserver發送調度決策時,Pod處於Allocated狀態。 Allocated狀態僅存在於調度周期內部,用於記錄Pod和資源分配情況。當作業滿足啟動條件時 (e.g. 滿足minMember),會向kube-apiserver提交調度決策。如果本輪調度周期內無法提交調度決策,由狀態會回滾為Pending狀態。
  • Pipelined: 該狀態與Allocated狀態相似,區別在於處於該狀態的Pod分配到的資源為正在被釋放的資源 (Releasing)。該狀態主要用於等待被搶佔的資源釋放。該狀態是調度周期中的狀態,不會更新到kube-apiserver以減少通信,節省kube-apiserver的qps。
  • Binding: 當作業滿足啟動條件時,調度器會向kube-apiserver提交調度決策,在kube-apiserver返回最終狀態之前,Pod一直處於Binding狀態。該狀態也保存在調度器的Cache之中,因此跨調度周期有效。
  • Bound: 當作業的調度決策在kube-apiserver確認后,該Pod即為Bound狀態。
  • Releasing: Pod等待被刪除時即為Releasing狀態。
  • Running, Failed, Succeeded, Unknown: 與Pod的現有含義一致。

狀態之間根據不同的操作進行轉換,見下圖。

Pod的這些狀態為調度器提供了更多優化的可能。例如,當進行Pod驅逐時,驅逐在Binding和Bound狀態的Pod要比較驅逐Running狀態的Pod的代價要小 (思考:還有其它狀態的Pod可以驅逐嗎?);並且狀態都是記錄在Volcano調度內部,減少了與kube-apiserver的通信。但目前Volcano調度器僅使用了狀態的部分功能,比如現在的preemption/reclaim僅會驅逐Running狀態下的Pod;這主要是由於分佈式系統中很難做到完全的狀態同步,在驅逐Binding和Bound狀態的Pod會有很多的狀態競爭。

Volcano調度實現

Volcano調度器在支持上面這些主要場景時,分別使用了action和plugin兩級插件。總體來講,帶有動作屬性的功能,一般需要引入 action 插件;帶有選擇 (包括排序) 屬性的功能,一般使用 plugin 插件。因此,這些常見場景中,fair-sharing、queue、co-scheduling都通過plugin機制來實現:都帶有選擇屬性,比如“哪些作業應該被優先調度”;而preemption、reclaim、backfill、reserve 則通過 action 機制來實現:都帶有動作屬性,比如“作業A 搶佔 作業B”。這裏需要注意的是,action 與 plugin 一定是一同工作的;fair-sharing 這些 plugin 是藉助 allocate 發展作用,而 preemption 在創建新的 action 后,同樣需要 plugin 來選擇哪些作業應該被搶佔。這裏通過job-based fairness (DRF) 和 preempt 兩個功能的實現來介紹action 和 plugin 兩種插件機制的使用,其它功能類似:

  • Job-based Fairness (DRF): 目前的公平調度是基於DRF,並通過 plugin 插件來實現。在 OpenSession 中會先計算每個作業的 dominant resource和每個作業share的初始值;然後註冊 JobOrderFn回調函數,JobOrderFn 中接收兩個作業對象,並根據對像的 dominant resource 的 share值對作業進行排序;同時註冊EventHandler, 當Pod被分配或搶佔資源時,drf根據相應的作業及資源信息動態更新share值。

其它插件的實現方案也基本相似,在OpenSession中註冊相應的回調,例如 JobOrderFn, TaskOrderFn,調度器會根據回調函數的結果決定如何分配資源,並通過EventHandler來更新插件內的調度數。

  • Preemption: preempt是在allocate之後的一個action,它會為“高”優先級的Pending作業選取一個或多個“低”優先級的作業進行驅逐。由於搶佔的動作與分配的動作不一致,因此新創建了preempt action來處理相應的邏輯;同時,在選取高低優先級的作業時,preempt action還是依賴相應的plugin插件來實現。其它動作插件的實現方式也類似,即根據需要創建整體的流程;將帶有選擇屬性的問題轉換為算法插件。

6月4日晚20:00-21:00,Volcano/kube-batch 創始人在線授課,不僅傳授Volcano架構原理,還告訴你更多應用落地場景,戳鏈接免費觀看。

點擊關注,第一時間了解華為雲新鮮技術~

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

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

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

7.5-15萬不買日系車 上班族首選車型顏值高有格調!_網頁設計公司

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

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

內飾運動感沒有那麼強烈,整體比較中規中矩,不過,配色上沒有那麼沉悶。福克斯有1。0T 1。5T 1。6L三種動力車型選擇,三種車型配置相差並不算太大,都標配了ESp,剎車輔助,牽引力控制等的配置,每一款動力的車型低配和次低配舒適性的配置稍少了一些,但對於日常使用而言,影響並不大。

如今路上的車輛越來越多,塞車,找不到停車位,已經是習以為常的時。因為這樣,現在不少人會選擇“體型”較小的車型,這樣在路上行駛或者在塞車擠位的時候都比較輕鬆。對於一般的上班族而言,下面這些車型就適合。

上汽大眾-pOLO

指導價:7.59-14.69萬

pOLO的外觀大家也再熟悉不過了,大眾臉配上小“體型”,有一種別樣的規矩感。內飾造型同樣是大眾風,但鋼琴烤漆高亮度面板和鍍鉻飾條,稍微的提升了些檔次感,布局也以實用為主。

pOLO有1.4L,1.6L以及1.4T車型選擇,對於一般用車,1.4T車型的選擇性較低。1.4L車型除了頂配之外都沒有配備ESp,剎車輔助,牽引力控制,這方便比較不就到,舒適配置表現中下,畢竟是8萬的大眾車。1.6L車型低配和次低配車型安全配置較低,舒適性配置比1.4L車型多了幾個,例如,電動天窗,定速巡航等,頂配和次頂配車型有配備ESp,剎車輔助,牽引力控制,舒適性配置也比較齊全。另外,1.4L車型的自動風尚型可以選裝ESp,剎車輔助,牽引力控制。

長安福特-福克斯

指導價:9.98-16.58萬

馬丁臉有較強烈的視覺感,整體的設計運動型較強,對於喜歡有點激情的上班族來說,很適合。內飾運動感沒有那麼強烈,整體比較中規中矩,不過,配色上沒有那麼沉悶。

福克斯有1.0T 1.5T 1.6L三種動力車型選擇,

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

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

三種車型配置相差並不算太大,都標配了ESp,剎車輔助,牽引力控制等的配置,每一款動力的車型低配和次低配舒適性的配置稍少了一些,但對於日常使用而言,影響並不大。中配車型配置比較齊全,日常使用很足夠,例如,電動天窗,多功能方向盤,后駐車雷達等都有配備。而頂配車型還配備了併線輔助,車道偏離系統,但價格較貴,日常使用,選擇中低配車型就足夠了。

東風標緻-標緻308S

指導價:11.27-17.97萬

不一樣的風情,這是308S給人的第一眼感覺,濃濃的法系味道。內飾顯得比較簡約,但做工依然保持了法系的精緻,看起很有質感,物理按鍵被替代成了觸碰式的,這對操作的實用性有一定的減少。

308S有1.2T 1.6L 1.6T三種動力車型車選擇,1.2T和1.6L的頂配車型配置較為齊全一些,但有些奇怪的是,全系都有配備ESp,剎車輔助,上坡輔助,就是沒有配備牽引力控制,頂配有配備全景天窗但是全系都沒有電動天窗,這算是法系的獨特的風格?中低配車型雖然配置較少,但對於日常使用也還是足夠的,只要你對配置要求不高的話。1.6T車型整體配置算得上齊全,有配備牽引力控制,但還是沒有電動天窗,其他舒適性配置算得上豐富,不過價格就貴了不少,不太推薦。

總結:斯文派的pOLO,激情派的福克斯,時尚派的標誌308S,三種不同風格之選。在價格上,pOLO算上優惠,最低配裸車價可以去到6萬多,而福克斯和308S價格稍微貴一些,但配置上會好很多。總體而言,福克斯各方面比較均衡,價格不高也不低,配置也一樣,對於現在人選車的“理念”而言,比較適合。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

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

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

選擇SUV的N個理由,說中你了嗎?_如何寫文案

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

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

6%的人表示準備買SUV,這麼高的數據比例其實不驚人,確實在近幾年的汽車市場中,SUV持續火爆,自主品牌也厚積薄發,像哈弗H6,榮威RX5,吉利博越,寶駿560等等。那麼問題來了,廠家如此重本地研發SUV,加上越來越多消費者買SUV,究竟為的是什麼。

在某網站上曾看過一個數據調查,

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

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

其中有一個問題是“你會買一輛SUV么?”,在回答中有90.6%的人表示準備買SUV,這麼高的數據比例其實不驚人,確實在近幾年的汽車市場中,SUV持續火爆,自主品牌也厚積薄發,像哈弗H6,榮威RX5,吉利博越,寶駿560等等。那麼問題來了,廠家如此重本地研發SUV,加上越來越多消費者買SUV,究竟為的是什麼?

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

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

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

瑞虎3x 4G看點揭秘 打造中國最強智能語音行車系統_網頁設計公司

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

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

頂級互聯支撐 瑞虎3x讓年輕人越玩越high基於Cloudrive2。0系統的支持,瑞虎3x成為同級別中最具玩味的入門級小型SUV,也是同級唯一擁有4G互聯的車型,加上動感的造型、出色的操控,讓年輕人越玩越high。造型上,瑞虎3x採用了奇瑞最新的Life in motion(生•動)設計語言,一體式前格柵大燈組、16寸和17寸超大鋁合金輪轂、C字型光影尾燈,讓年輕人玩的更炫。

奇瑞進入戰略2.0后提出了產品年輕化的品牌核心理念,相繼推出的艾瑞澤5和瑞虎7均實現了產品年輕化。如今奇瑞戰略2.0后的第三款車型瑞虎3x已經進入了上市的“倒計時”,定位於“4G智趣SUV”的瑞虎3x憑藉什麼讓年輕人為之瘋狂?11月7日,奇瑞聯合科大訊飛在安徽合肥舉辦一場“4G智趣SUV 瑞虎3x Cloudrive2.0智雲互聯行車系統玩轉派對”活動,現場展示了其智能語音系統的奧秘。

不惜成本 打造中國最強智能語音行車系統

之所以稱之為“4G智趣SUV”,瑞虎3x離不開強大的互聯繫統的支持。為了實現產品的全面年輕化和智能化,奇瑞很早就與科大訊飛合作,強強聯合開發了Cloudrive系統,並不斷進化升級,不惜成本的為消費者打造更智能更便捷的互聯體驗。

據悉,瑞虎3x上的移動4G wifi信號極強,並可以為多達10部移動終端提供4G網絡熱點,在任何地點都能實現最快的瀏覽速度,為用戶提供了更好的上網體驗。此外,瑞虎3x還擁有全球最強語音系統,其採用科大訊飛雙麥語音降噪模塊,具備雲+端語音識別,極大增強了語音識別的成功率,並支持數十種方言,使車載語音不再是“小白”。

界面方面,瑞虎3x的Cloudrive2.0系統搭載了8英寸觸控大屏,分辨率達到1080p高清級別,UI界面也做到了極簡設計,並有聖誕版、春節版、搶紅包版等等多種個性桌面,讓每個車主都有自己的個性主題。

此外 通過專屬手機App,

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

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

用戶可隨時隨地獲取車輛信息,查詢違章等,該系統還支持免費在線升級,可以隨時享受最新的智能體驗。可以說瑞虎3x搭載的這套新系統無論是語音交互的融會貫通,還是娛樂導航極簡的交互和UI設計,都具有業內突出的表現,功能和性能都達到國內智能語音系統的最高水準。

頂級互聯支撐 瑞虎3x讓年輕人越玩越high

基於Cloudrive2.0系統的支持,瑞虎3x成為同級別中最具玩味的入門級小型SUV,也是同級唯一擁有4G互聯的車型,加上動感的造型、出色的操控,讓年輕人越玩越high。

造型上,瑞虎3x採用了奇瑞最新的Life in motion(生•動)設計語言,一體式前格柵大燈組、16寸和17寸超大鋁合金輪轂、C字型光影尾燈,讓年輕人玩的更炫。

動力方面,瑞虎3x採用了1.5+5MT和1.5+4AT全新動力組合,為年輕人提供了肆意玩樂的動力與操控、這台1.5L發動機最大功率高達106馬力,同時瑞虎3x還採用了蓮花調校基礎上的精準調校底盤,以及186mm的超強通過性,帶來了完美的操控樂趣。

此外,瑞虎3x在奇瑞技術2.0強大的依託下,還具備超越同級的品質保障。配置上,瑞虎3x不光有Cloudrive2.0智雲互聯行車系統,還有雙色皮織混搭座椅、真皮多功能方向盤、電動天窗,ESC电子穩定系統、HHC坡道輔助控制、倒車雷達+動態輔助倒車影像和TpMS胎壓監測等舒適和安全裝備,讓小車不再低廉,也讓所有喜愛瑞虎3x的年輕人玩的起。

奇瑞進入戰略2.0后一直強調品牌年輕化,並用實際行動證明其新產品的確深諳年輕消費者的用車需求,深深的抓住了目標消費群體,因此才有艾瑞澤5和瑞虎7的大賣。如今,為玩樂而生的瑞虎3x即將到來,它將是那些追求新鮮、個性,並以玩樂為主的年輕人的最佳之選,瑞虎3x將於11月15日在北京、成都、長沙三地聯動上市,讓我們一起期待這一時刻。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

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

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

上汽MG名爵全新SUV定名 名爵 ZS將於廣州車展亮相_網頁設計

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

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

其中,16T發動機採用小排量缸內中置直噴渦輪增壓等國際最先進技術,排量僅1。0L,最大功率卻高達125馬力,峰值扭矩170牛•米,動力性能優於1。6L自然吸氣發動機,實現同級最強的性能表現,又能兼顧超低油耗。1。5L發動機最大功率120馬力,峰值扭矩達到150牛•米。

11月9日,上汽乘用車宣布,MG名爵旗下全新緊湊型互聯網SUV定名“名爵ZS”,同時還發布了官圖。據了解,名爵ZS將於11月18日在廣州車展全球首發。

名爵ZS

名爵ZS名字本身就寓意了年輕一代。字母Z代表年輕人熱情、活潑的個性標籤,“Z一代”的年輕人,呼吸着互聯網的空氣,

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

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

既能秒懂國際大事,又能玩轉最新互聯應用。作為聚焦年輕人的互聯網SUV,名爵ZS詮釋了年輕一代專屬、真實的生活態度,是他們追求潮流、綻放個性的平台,將成為年輕人的SUV首選。

名爵ZS將搭載上汽“世界級”藍芯動力科技16T和1.5L發動機。其中,16T發動機採用小排量缸內中置直噴渦輪增壓等國際最先進技術,排量僅1.0L,最大功率卻高達125馬力,峰值扭矩170牛•米,動力性能優於1.6L自然吸氣發動機,實現同級最強的性能表現,又能兼顧超低油耗;1.5L發動機最大功率120馬力,峰值扭矩達到150牛•米。

MG名爵始終致力於為年輕人打造純粹的駕控樂趣。名爵ZS融合了國際范兒的全新設計語言、智能互聯繫統以及領先動力科技,是一輛滿足年輕人潮流、時尚、個性需求的互聯網汽車。榮威RX5爆款在前,名爵ZS緊隨其後,有望再次引爆SUV市場。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

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

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