傳蘋果將與 BMW 聯手,以 BMW i3 為原型打造 Apple Car

  根據德國商業雜誌 Manager Magazin 報導,蘋果與 BMW 正在秘密商議將 BMW 的電動車 i3 提供給蘋果,做為 Apple Car 的原型車使用,這並不是蘋果第一次與 BMW 合作,同時這也讓我們對 Apple Car 的輪廓又更清楚了一些。   蘋果從 2014 年秋天開始和 BMW 研議 Apple Car 的發展計劃,在兩間公司一度中斷協議後,根據報導指出,蘋果執行長 Tim Cook 帶領著高級主管前往德國萊比錫參訪 BMW 工廠的行程,一方面參觀 BMW i3 的生產,一方面重啟合作對話。   這不是蘋果第一次與 BMW 合作,從車用系統 Car Play 到 Apple Watch 專屬 App,BMW 與蘋果有著密切往來,近來對汽車產業動作頻頻的蘋果,也已透過挖角高級主管,和接觸包括特斯拉、福特和通用汽車員工尋覓相關人材等方式開始佈局。   然而,目前關於 Apple Car 的細節仍十分有限,蘋果最後會自己做一輛車出來,還是就直接用改裝的方式,讓 i3 成為 Apple Car 也仍是未知數,不過可以確定的是,內部代號「Project Titan」的蘋果汽車計劃,如果確定可行的話,將有可能在 2020 年登場。     本文全文授權轉載自《科技新報》─〈〉

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

【其他文章推薦】

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

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

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

go中的數據結構-字典map

1. map的使用

  golang中的map是一種數據類型,將鍵與值綁定到一起,底層是用哈希表實現的,可以快速的通過鍵找到對應的值。

  類型表示:map[keyType][valueType] key一定要是可比較的類型(可以理解為支持==的操作),value可以是任意類型。

  初始化:map只能使用make來初始化,聲明的時候默認為一個為nil的map,此時進行取值,返回的是對應類型的零值(不存在也是返回零值)。添加元素無任何意義,還會導致運行時錯誤。向未初始化的map賦值引起 panic: assign to entry in nil map。

 1 package main
 2 
 3 import (  
 4     "fmt"
 5 )
 6 
 7 // bool 的零值是false
 8 var m map[int]bool 
 9 a, ok := m[1]
10 fmt.Println(a, ok) // false  false
11 
12 // int 的零值是0
13 var m map[int]int 
14 a, ok := m[1]
15 fmt.Println(a, ok) // 0  false
16 
17 
18 func main() {  
19     var agemap[string]int
20     if age== nil {
21         fmt.Println("map is nil.")
22         age= make(map[string]int)
23     }
24 }

  清空map:對於一個有一定數據的集合 exp,清空的辦法就是再次初始化: exp = make(map[string]int),如果後期不再使用該map,則可以直接:exp= nil 即可,但是如果還需要重複使用,則必須進行make初始化,否則無法為nil的map添加任何內容。

  屬性:與切片一樣,map 是引用類型。當一個 map 賦值給一個新的變量,它們都指向同一個內部數據結構。因此改變其中一個也會反映到另一個。作為形參或返回參數的時候,傳遞的是地址的拷貝,擴容時也不會改變這個地址。

 1 func main() {
 2     exp := map[string]int{
 3         "steve": 20,
 4         "jamie": 80,
 5     }
 6     fmt.Println("Ori exp", age)
 7     newexp:= exp
 8     newexp["steve"] = 18
 9     fmt.Println("exp changed", exp)
10 }
11 
12 //Ori age map[steve:20 jamie:80]
13 //age changed map[steve:18 jamie:80]

  遍歷map:map本身是無序的,在遍歷的時候並不會按照你傳入的順序,進行傳出。

 1 //正常遍歷:
 2 for k, v := range exp { 
 3     fmt.Println(k, v)
 4 }
 5 
 6 //有序遍歷
 7 import "sort"
 8 var keys []string
 9 // 把key單獨抽取出來,放在數組中
10 for k, _ := range exp {
11     keys = append(keys, k)
12 }
13 // 進行數組的排序
14 sort.Strings(keys)
15 // 遍曆數組就是有序的了
16 for _, k := range keys {
17     fmt.Println(k, m[k])
18 }

2. map的結構

   Go中的map在可以在 $GOROOT/src/runtime/map.go找到它的實現。哈希表的數據結構中一些關鍵的域如下所示:

 1 type hmap struct {
 2     count        int  //元素個數
 3     flags        uint8   
 4     B            uint8 //擴容常量
 5     noverflow    uint16 //溢出 bucket 個數
 6     hash0        uint32 //hash 種子
 7     buckets      unsafe.Pointer //bucket 數組指針
 8     oldbuckets   unsafe.Pointer //擴容時舊的buckets 數組指針
 9     nevacuate    uintptr  //擴容搬遷進度
10     extra        *mapextra //記錄溢出相關
11 }
12 
13 type bmap struct {
14     tophash        [bucketCnt]uint8  
15     // Followed by bucketCnt keys 
16     //and then bucketan Cnt values  
17     // Followed by overflow pointer.
18 } 

  說明:每個map的底層都是hmap結構體,它是由若干個描述hmap結構體的元素、數組指針、extra等組成,buckets數組指針指向由若干個bucket組成的數組,其每個bucket里存放的是key-value數據(通常是8個)和overflow字段(指向下一個bmap),每個key插入時會根據hash算法歸到同一個bucket中,當一個bucket中的元素超過8個的時候,hmap會使用extra中的overflow來擴展存儲key。

  圖中len 就是當前map的元素個數,也就是len()返回的值。也是結構體中hmap.count的值。bucket array是指數組指針,指向bucket數組。hash seed 哈希種子。overflow指向下一個bucket。

map的底層主要是由三個結構構成:

  1. hmap — map的最外層的數據結構,包括了map的各種基礎信息、如大小、bucket,一個大的結構體。
  2. mapextra — 記錄map的額外信息,hmap結構體里的extra指針指向的結構,例如overflow bucket
  3. bmap — 代表bucket,每一個bucket最多放8個kv,最後由一個overflow字段指向下一個bmap,注意key、value、overflow字段都不显示定義,而是通過maptype計算偏移獲取的。

  mapextra的結構如下

 1 // mapextra holds fields that are not present on all maps.
 2 type mapextra struct {
 3     // If both key and value do not contain pointers and are inline, then we mark bucket
 4     // type as containing no pointers. This avoids scanning such maps.
 5     // However, bmap.overflow is a pointer. In order to keep overflow buckets
 6     // alive, we store pointers to all overflow buckets in hmap.extra.overflow and hmap.extra.oldoverflow.
 7     // overflow and oldoverflow are only used if key and value do not contain pointers.
 8     // overflow contains overflow buckets for hmap.buckets.
 9     // oldoverflow contains overflow buckets for hmap.oldbuckets.
10     // The indirection allows to store a pointer to the slice in hiter.
11     overflow    *[]*bmap
12     oldoverflow *[]*bmap
13 
14     // nextOverflow holds a pointer to a free overflow bucket.
15     nextOverflow *bmap
16 }

  其中hmap.extra.nextOverflow指向的是預分配的overflow bucket,預分配的用完了那麼值就變成nil。

  bmap的詳細結構如下

  在map中出現哈希衝突時,首先
以bmap為最小粒度掛載,一個bmap累積8個kv之後,就會申請一個新的bmap(overflow bucket)掛在這個bmap的後面形成鏈表,優先用預分配的overflow bucket,如果預分配的用完了,那麼就malloc一個掛上去。這樣減少對象數量,減輕管理內存的負擔,利於gc。
注意golang的map不會shrink,內存只會越用越多,overflow bucket中的key全刪了也不會釋放。

  bmap中所有key存在一塊,所有value存在一塊,這樣做方便內存對齊。當key大於128字節時,bucket的key字段存儲的會是指針,指向key的實際內容;value也是一樣。

  hash值的高8位存儲在bucket中的tophash字段。每個桶最多放8個kv對,所以tophash類型是數組[8]uint8。把高八位存儲起來,這樣不用完整比較key就能過濾掉不符合的key,加快查詢速度。實際上當hash值的高八位小於常量minTopHash時,會加上minTopHash,區間[0, minTophash)的值用於特殊標記。查找key時,計算hash值,用hash值的高八位在tophash中查找,有tophash相等的,再去比較key值是否相同。

 1 type typeAlg struct {
 2     // function for hashing objects of this type
 3     // (ptr to object, seed) -> hash
 4     hash func(unsafe.Pointer, uintptr) uintptr
 5     // function for comparing objects of this type
 6     // (ptr to object A, ptr to object B) -> ==?
 7     equal func(unsafe.Pointer, unsafe.Pointer) bool
 8 
 9 // tophash calculates the tophash value for hash.
10 func tophash(hash uintptr) uint8 {
11     top := uint8(hash >> (sys.PtrSize*8 - 8))
12     if top < minTopHash {
13         top += minTopHash
14     }
15     return top
16 }

  golang為每個類型定義了類型描述器_type,並實現了hashable類型的_type.alg.hash和_type.alg.equal,以支持map的范型,定義了這類key用什麼hash函數、bucket的大小、怎麼比較之類的,通過這個變量來實現范型。

3. map的基本操作

3.1 map的創建

 1 //makemap為make(map [k] v,hint)實現Go map創建。
 2 //如果編譯器已確定映射或第一個存儲桶,可以在堆棧上創建,hmap或bucket可以為非nil。
 3 //如果h!= nil,則可以直接在h中創建map。
 4 //如果h.buckets!= nil,則指向的存儲桶可以用作第一個存儲桶。
 5 func makemap(t *maptype, hint int, h *hmap) *hmap {
 6     if hint < 0 || hint > int(maxSliceCap(t.bucket.size)) {
 7         hint = 0
 8     }
 9 
10     // 初始化Hmap
11     if h == nil {
12         h = new(hmap)
13     }
14     h.hash0 = fastrand()
15 
16     // 查找將保存請求的元素數的size參數
17     B := uint8(0)
18     for overLoadFactor(hint, B) {
19         B++
20     }
21     h.B = B
22 
23     // 分配初始哈希表
24     // if B == 0, 稍後會延遲分配buckets字段(在mapassign中)
25     //如果提示很大,則將內存清零可能需要一段時間。
26     if h.B != 0 {
27         var nextOverflow *bmap
28         h.buckets, nextOverflow = makeBucketArray(t, h.B, nil)
29         if nextOverflow != nil {
30             h.extra = new(mapextra)
31             h.extra.nextOverflow = nextOverflow
32         }
33     }
34 
35     return h
36 }

  hint是一個啟發值,啟發初建map時創建多少個bucket,如果hint是0那麼就先不分配bucket,lazy分配。大概流程就是初始化hmap結構體、設置一下hash seed、bucket數量、實際申請bucket、申請mapextra結構體之類的。   申請buckets的過程:

 1 // makeBucketArray初始化地圖存儲區的後備數組。
 2 // 1 << b是要分配的最小存儲桶數。
 3 // dirtyalloc之前應該為nil或bucket數組
 4 //由makeBucketArray使用相同的t和b參數分配。
 5 //如果dirtyalloc為零,則將分配一個新的支持數組,dirtyalloc將被清除並作為後備數組重用。
 6 func makeBucketArray(t *maptype, b uint8, dirtyalloc unsafe.Pointer) (buckets unsafe.Pointer, nextOverflow *bmap) {
 7     base := bucketShift(b)
 8     nbuckets := base
 9     // 對於小b,溢出桶不太可能出現。
10     // 避免計算的開銷。
11     if b >= 4 {
12         //加上估計的溢出桶數
13         //插入元素的中位數
14         //與此值b一起使用。
15         nbuckets += bucketShift(b - 4)
16         sz := t.bucket.size * nbuckets
17         up := roundupsize(sz)
18         if up != sz {
19             nbuckets = up / t.bucket.size
20         }
21     }
22     if dirtyalloc == nil {
23         buckets = newarray(t.bucket, int(nbuckets))
24     } else {
25        // dirtyalloc先前是由上面的newarray(t.bucket,int(nbuckets)),但不能為空。
26         buckets = dirtyalloc
27         size := t.bucket.size * nbuckets
28         if t.bucket.kind&kindNoPointers == 0 {
29             memclrHasPointers(buckets, size)
30         } else {
31             memclrNoHeapPointers(buckets, size)
32         }
33     }
34 
35     if base != nbuckets {
36         //我們預先分配了一些溢出桶。
37         //為了將跟蹤這些溢出桶的開銷降至最低,我們使用的約定是,如果預分配的溢出存儲桶發生了溢出指針為零,則通過碰撞指針還有更多可用空間。
38         //對於最後一個溢出存儲區,我們需要一個安全的非nil指針;只是用bucket。
39         nextOverflow = (*bmap)(add(buckets, base*uintptr(t.bucketsize)))
40         last := (*bmap)(add(buckets, (nbuckets-1)*uintptr(t.bucketsize)))
41         last.setoverflow(t, (*bmap)(buckets))
42     }
43     return buckets, nextOverflow
44 }

  默認創建2
b個bucket,如果
b大於等於4,那麼就預先額外創建一些overflow bucket。除了最後一個overflow bucket,其餘overflow bucket的overflow指針都是nil,最後一個overflow bucket的overflow指針指向bucket數組第一個元素,作為哨兵,說明到了到結尾了。

3.2 查詢操作

 1 // mapaccess1返回指向h [key]的指針。從不返回nil,而是 如果值類型為零,它將返回對零對象的引用,該鍵不在map中。
 2   //注意:返回的指針可能會使整個map保持活動狀態,因此請不要堅持很長時間。
 3   func mapaccess1(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
 4       if raceenabled && h != nil {  //raceenabled是否啟用數據競爭檢測。
 5         callerpc := getcallerpc()
 6         pc := funcPC(mapaccess1)
 7         racereadpc(unsafe.Pointer(h), callerpc, pc)
 8         raceReadObjectPC(t.key, key, callerpc, pc)
 9     }
10     if msanenabled && h != nil {
11         msanread(key, t.key.size)
12     }
13     if h == nil || h.count == 0 {
14         return unsafe.Pointer(&zeroVal[0])
15     }    
16     // 併發訪問檢查
17     if h.flags&hashWriting != 0 {
18         throw("concurrent map read and map write")
19     }
20     
21     // 計算key的hash值
22     alg := t.key.alg
23     hash := alg.hash(key, uintptr(h.hash0)) // alg.hash
24 
25     // hash值對m取餘數得到對應的bucket
26     m := uintptr(1)<<h.B - 1
27     b := (*bmap)(add(h.buckets, (hash&m)*uintptr(t.bucketsize)))
28 
29     // 如果老的bucket還沒有遷移,則在老的bucket裏面找
30     if c := h.oldbuckets; c != nil {
31         if !h.sameSizeGrow() {
32             m >>= 1
33         }
34         oldb := (*bmap)(add(c, (hash&m)*uintptr(t.bucketsize)))
35         if !evacuated(oldb) {
36             b = oldb
37         }
38     }
39     
40     // 計算tophash,取高8位
41     top := uint8(hash >> (sys.PtrSize*8 - 8))
42     
43     for {
44         for i := uintptr(0); i < bucketCnt; i++ {
45             // 檢查top值,如高8位不一樣就找下一個
46             if b.tophash[i] != top {
47                 continue
48             }
49             
50             // 取key的地址
51             k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))
52             
53             if alg.equal(key, k) { // alg.equal
54                 // 取value得地址
55                 v := add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.valuesize))
56             }
57         }
58        
59         // 如果當前bucket沒有找到,則找bucket鏈的下一個bucket
60         b = b.overflow(t)
61         if b == nil {
62             // 返回零值
63             return unsafe.Pointer(&zeroVal[0])
64         }
65     }
66 }
  1. 先定位出bucket,如果正在擴容,並且這個bucket還沒搬到新的hash表中,那麼就從老的hash表中查找。

  2. 在bucket中進行順序查找,使用高八位進行快速過濾,高八位相等,再比較key是否相等,找到就返回value。如果當前bucket找不到,就往下找overflow bucket,都沒有就返回零值。

  訪問的時候,並不進行擴容的數據搬遷。並且併發有寫操作時拋異常

  注意,t.bucketsize並不是bmap的size,而是bmap加上存儲key、value、overflow指針,所以查找bucket的時候時候用的不是bmap的szie。

3.3 更新/插入過程

 1 // 與mapaccess類似,但是如果map中不存在密鑰,則為該密鑰分配一個插槽
 2 func mapassign(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
 3     ...
 4     //設置hashWriting調用alg.hash,因為alg.hash可能出現緊急情況后,在這種情況下,我們實際上並沒有進行寫操作.
 5     h.flags |= hashWriting
 6 
 7     if h.buckets == nil {
 8         h.buckets = newobject(t.bucket) // newarray(t.bucket, 1)
 9     }
10 
11 again:
12     bucket := hash & bucketMask(h.B)
13     if h.growing() {
14         growWork(t, h, bucket)
15     }
16     b := (*bmap)(unsafe.Pointer(uintptr(h.buckets) + bucket*uintptr(t.bucketsize)))
17     top := tophash(hash)
18 
19     var inserti *uint8
20     var insertk unsafe.Pointer
21     var val unsafe.Pointer
22     for {
23         for i := uintptr(0); i < bucketCnt; i++ {
24             if b.tophash[i] != top {
25                 if b.tophash[i] == empty && inserti == nil {
26                     inserti = &b.tophash[i]
27                     insertk = add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))
28                     val = add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.valuesize))
29                 }
30                 continue
31             }
32             k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))
33             if t.indirectkey {
34                 k = *((*unsafe.Pointer)(k))
35             }
36             if !alg.equal(key, k) {
37                 continue
38             }
39             // 已經有一個 mapping for key. 更新它.
40             if t.needkeyupdate {
41                 typedmemmove(t.key, k, key)
42             }
43             val = add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.valuesize))
44             goto done
45         }
46         ovf := b.overflow(t)
47         if ovf == nil {
48             break
49         }
50         b = ovf
51     }
52     //// 如果已經達到了load factor的最大值,就繼續擴容。
53     //找不到鍵的映射。分配新單元格並添加條目。
54     //如果達到最大負載係數或溢出桶過多,並且我們還沒有處於成長的中間,就開始擴容。
55     if !h.growing() && (overLoadFactor(h.count+1, h.B) ||     
56         tooManyOverflowBuckets(h.noverflow, h.B)) {
57         hashGrow(t, h)
58         goto again // //擴大表格會使所有內容無效, so try again
59     }
60     if inserti == nil {
61         // 當前所有存儲桶已滿,請分配一個新的存儲桶
62         newb := h.newoverflow(t, b)
63         inserti = &newb.tophash[0]
64         insertk = add(unsafe.Pointer(newb), dataOffset)
65         val = add(insertk, bucketCnt*uintptr(t.keysize))
66     }
67 
68     // 在插入的位置,存儲鍵值
69     if t.indirectkey {
70         kmem := newobject(t.key)
71         *(*unsafe.Pointer)(insertk) = kmem
72         insertk = kmem
73     }
74     if t.indirectvalue {
75         vmem := newobject(t.elem)
76         *(*unsafe.Pointer)(val) = vmem
77     }
78     typedmemmove(t.key, insertk, key)
79     *inserti = top
80     h.count++
81 
82 done:
83     if h.flags&hashWriting == 0 {
84         throw("concurrent map writes")
85     }
86     h.flags &^= hashWriting
87     if t.indirectvalue {
88         val = *((*unsafe.Pointer)(val))
89     }
90     return val
91 }    
  • hash表如果正在擴容,並且這次要操作的bucket還沒搬到新hash表中,那麼先進行搬遷(擴容細節下面細說)。

  • 在buck中尋找key,同時記錄下第一個空位置,如果找不到,那麼就在空位置中插入數據;如果找到了,那麼就更新對應的value;

  • 找不到key就看下需不需要擴容,需要擴容並且沒有正在擴容,那麼就進行擴容,然後回到第一步。

  • 找不到key,不需要擴容,但是沒有空slot,那麼就分配一個overflow bucket掛在鏈表結尾,用新bucket的第一個slot放存放數據。

3.5 刪除的過程

 1 func mapdelete(t *maptype, h *hmap, key unsafe.Pointer) {
 2     ...
 3     // Set hashWriting after calling alg.hash, since alg.hash may panic,
 4     // in which case we have not actually done a write (delete).
 5     h.flags |= hashWriting
 6 
 7     bucket := hash & bucketMask(h.B)
 8     if h.growing() {
 9         growWork(t, h, bucket)
10     }
11     b := (*bmap)(add(h.buckets, bucket*uintptr(t.bucketsize)))
12     top := tophash(hash)
13 search:
14     for ; b != nil; b = b.overflow(t) {
15         for i := uintptr(0); i < bucketCnt; i++ {
16             if b.tophash[i] != top {
17                 continue
18             }
19             k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))
20             k2 := k
21             if t.indirectkey {
22                 k2 = *((*unsafe.Pointer)(k2))
23             }
24             if !alg.equal(key, k2) {
25                 continue
26             }
27             // 如果其中有指針,則僅清除鍵。
28             if t.indirectkey {
29                 *(*unsafe.Pointer)(k) = nil
30             } else if t.key.kind&kindNoPointers == 0 {
31                 memclrHasPointers(k, t.key.size)
32             }
33             v := add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.valuesize))
34             if t.indirectvalue {
35                 *(*unsafe.Pointer)(v) = nil
36             } else if t.elem.kind&kindNoPointers == 0 {
37                 memclrHasPointers(v, t.elem.size)
38             } else {
39                 memclrNoHeapPointers(v, t.elem.size)
40             }
41         // 若找到把對應的tophash裏面的打上空的標記
42             b.tophash[i] = empty
43             h.count--
44             break search
45         }
46     }
47 
48     if h.flags&hashWriting == 0 {
49         throw("concurrent map writes")
50     }
51     h.flags &^= hashWriting
52 }    
  1. 如果正在擴容,並且操作的bucket還沒搬遷完,那麼搬遷bucket。

  2. 找出對應的key,如果key、value是包含指針的那麼會清理指針指向的內存,否則不會回收內存。

3.6 map的擴容

  通過上面的過程我們知道了,插入、刪除過程都會觸發擴容,判斷擴容的函數如下:

 1 // overLoadFactor 判斷放置在1 << B個存儲桶中的計數項目是否超過loadFactor。
 2 func overLoadFactor(count int, B uint8) bool {
 3     return count > bucketCnt && uintptr(count) > loadFactorNum*(bucketShift(B)/loadFactorDen)  
 4     //return 元素個數>8 && count>bucket數量*6.5,其中loadFactorNum是常量13,loadFactorDen是常量2,所以是6.5,bucket數量不算overflow bucket.
 5 }
 6 
 7 // tooManyOverflowBuckets 判斷noverflow存儲桶對於1 << B存儲桶的map是否過多。
 8 // 請注意,大多數這些溢出桶必須稀疏使用。如果使用密集,則我們已經觸發了常規map擴容。
 9 func tooManyOverflowBuckets(noverflow uint16, B uint8) bool {
10     // 如果閾值太低,我們會做多餘的工作。如果閾值太高,則增大和縮小的映射可能會保留大量未使用的內存。
11     //“太多”意味着(大約)溢出桶與常規桶一樣多。有關更多詳細信息,請參見incrnoverflow。
12     if B > 15 {
13         B = 15
14     }
15     // 譯器在這裏看不到B <16;掩碼B生成較短的移位碼。
16     return noverflow >= uint16(1)<<(B&15)
17 }
18 
19 {
20     ....
21     // 如果我們達到最大負載率或溢流桶過多,並且我們還沒有處於成長的中間,就開始成長。
22     if !h.growing() && (overLoadFactor(h.count+1, h.B) || tooManyOverflowBuckets(h.noverflow, h.B)) {
23         hashGrow(t, h)
24         goto again // 擴大表格會使所有內容失效,so try again
25     }
26     //if (不是正在擴容 && (元素個數/bucket數超過某個值 || 太多overflow bucket)) {
27     進行擴容
28     //}
29     ....
30 }

  每次map進行更新或者新增的時候,會先通過以上函數判斷一下load factor。來決定是否擴容。如果需要擴容,那麼第一步需要做的,就是對hash表進行擴容:

 1 //僅對hash表進行擴容,這裏不進行搬遷
 2 func hashGrow(t *maptype, h *hmap) {
 3     // 如果達到負載係數,則增大尺寸。否則,溢出bucket過多,因此,保持相同數量的存儲桶並橫向“增長”。
 4     bigger := uint8(1)
 5     if !overLoadFactor(h.count+1, h.B) {
 6         bigger = 0
 7         h.flags |= sameSizeGrow
 8     }
 9     oldbuckets := h.buckets
10     newbuckets, nextOverflow := makeBucketArray(t, h.B+bigger, nil)
11 
12     flags := h.flags &^ (iterator | oldIterator)
13     if h.flags&iterator != 0 {
14         flags |= oldIterator
15     }
16     // 提交增長(atomic wrt gc)
17     h.B += bigger
18     h.flags = flags
19     h.oldbuckets = oldbuckets
20     h.buckets = newbuckets
21     h.nevacuate = 0
22     h.noverflow = 0
23 
24     if h.extra != nil && h.extra.overflow != nil {
25         // 將當前的溢出bucket提升到老一代。
26         if h.extra.oldoverflow != nil {
27             throw("oldoverflow is not nil")
28         }
29         h.extra.oldoverflow = h.extra.overflow
30         h.extra.overflow = nil
31     }
32     if nextOverflow != nil {
33         if h.extra == nil {
34             h.extra = new(mapextra)
35         }
36         h.extra.nextOverflow = nextOverflow
37     }
38 
39     //哈希表數據的實際複製是增量完成的,通過growWork()和evacuate()。
40 }

  如果之前為2^n ,那麼下一次擴容是2^(n+1),每次擴容都是之前的兩倍。擴容后需要重新計算每一項在hash中的位置,新表為老的兩倍,此時前文的oldbacket用上了,用來存同時存在的兩個新舊map,等數據遷移完畢就可以釋放oldbacket了。擴容的函數hashGrow其實僅僅是進行一些空間分配,字段的初始化,實際的搬遷操作是在growWork函數中:

1 func growWork(t *maptype, h *hmap, bucket uintptr) {
2     //確保我們遷移了了對應的oldbucket,到我們將要使用的存儲桶。
3     evacuate(t, h, bucket&h.oldbucketmask())
4 
5     // 疏散一箇舊桶以在生長上取得進展
6     if h.growing() {
7         evacuate(t, h, h.nevacuate)
8     }
9 }

  evacuate是進行具體搬遷某個bucket的函數,可以看出
growWork會搬遷兩個bucket,一個是入參bucket;另一個是h.nevacuate。這個nevacuate是一個順序累加的值。可以想想如果每次僅僅搬遷進行寫操作(賦值/刪除)的bucket,那麼有可能某些bucket就是一直沒有機會訪問到,那麼擴容就一直沒法完成,總是在擴容中的狀態,因此會額外進行一次順序遷移,理論上,有N個old bucket,最多N次寫操作,那麼必定會搬遷完。在advanceEvacuationMark中進行nevacuate的累加,遇到已經遷移的bucket會繼續累加,一次最多加1024。

  優點:均攤擴容時間,一定程度上縮短了擴容時間(和gc的引用計數法類似,都是均攤)overLoadFactor函數中有一個常量6.5(loadFactorNum/loadFactorDen)來進行影響擴容時機。這個值的來源是測試取中的結果。

4. map的併發安全性

  map的併發操作不是安全的。併發起兩個goroutine,分別對map進行數據的增加:

 1 func main() {
 2     test := map[int]int {1:1}
 3     go func() {
 4         i := 0
 5         for i < 10000 {
 6             test[1]=1
 7             i++
 8         }
 9     }()
10 
11     go func() {
12         i := 0
13         for i < 10000 {
14             test[1]=1
15             i++
16         }
17     }()
18 
19     time.Sleep(2*time.Second)
20     fmt.Println(test)
21 }
22 
23 //fatal error: concurrent map read and map write

  併發讀寫map結構的數據引起了錯誤。

  解決方案1:加鎖

 1 func main() {
 2     test := map[int]int {1:1}
 3     var s sync.RWMutex
 4     go func() {
 5         i := 0
 6         for i < 10000 {
 7             s.Lock()
 8             test[1]=1
 9             s.Unlock()
10             i++
11         }
12     }()
13 
14     go func() {
15         i := 0
16         for i < 10000 {
17             s.Lock()
18             test[1]=1
19             s.Unlock()
20             i++
21         }
22     }()
23 
24     time.Sleep(2*time.Second)
25     fmt.Println(test)
26 }

  特點:實現簡單粗暴,好理解。但是鎖的粒度為整個map,存在優化空間。適用場景:all。

  解決方案2:sync.Map

 1 func main() {
 2     test := sync.Map{}
 3     test.Store(1, 1)
 4     go func() {
 5         i := 0
 6         for i < 10000 {
 7             test.Store(1, 1)
 8             i++
 9         }
10     }()
11 
12     go func() {
13         i := 0
14         for i < 10000 {
15             test.Store(1, 1)
16             i++
17         }
18     }()
19 
20     time.Sleep(time.Second)
21     fmt.Println(test.Load(1))
22 }

  sync.Map的原理:sync.Map裡頭有兩個map一個是專門用於讀的read map,另一個是才是提供讀寫的dirty map;優先讀read map,若不存在則加鎖穿透讀dirty map,同時記錄一個未從read map讀到的計數,當計數到達一定值,就將read map用dirty map進行覆蓋。
特點:官方出品,通過空間換時間的方式,讀寫分離;不適用於大量寫的場景,會導致read map讀不到數據而進一步加鎖讀取,同時dirty map也會一直晉陞為read map,整體性能較差。適用場景:大量讀,少量寫。

  解決方案3:分段鎖

  這也是數據庫常用的方法,分段鎖每一個讀寫鎖保護一段區間。sync.Map其實也是相當於表級鎖,只不過多讀寫分了兩個map,本質還是一樣的。

  優化方向:將鎖的粒度盡可能降低來提高運行速度。思路:對一個大map進行hash,其內部是n個小map,根據key來來hash確定在具體的那個小map中,這樣加鎖的粒度就變成1/n了。

5. map的GC內存回收

  golang里的map是只增不減的一種數組結構,他只會在刪除的時候進行打標記說明該內存空間已經empty了,不會回收。

 1 var intMap map[int]int
 2 
 3 func main() {
 4     printMemStats("初始化")
 5 
 6     // 添加1w個map值
 7     intMap = make(map[int]int, 10000)
 8     for i := 0; i < 10000; i++ {
 9         intMap[i] = i
10     }
11 
12     // 手動進行gc操作
13     runtime.GC()
14     // 再次查看數據
15     printMemStats("增加map數據后")
16 
17     log.Println("刪除前數組長度:", len(intMap))
18     for i := 0; i < 10000; i++ {
19         delete(intMap, i)
20     }
21     log.Println("刪除后數組長度:", len(intMap))
22 
23     // 再次進行手動GC回收
24     runtime.GC()
25     printMemStats("刪除map數據后")
26 
27     // 設置為nil進行回收
28     intMap = nil
29     runtime.GC()
30     printMemStats("設置為nil后")
31 }
32 
33 func printMemStats(mag string) {
34     var m runtime.MemStats
35     runtime.ReadMemStats(&m)
36     log.Printf("%v:分配的內存 = %vKB, GC的次數 = %v\n", mag, m.Alloc/1024, m.NumGC)
37 }
38 
39 //初始化:分配的內存 = 65KB, GC的次數 = 0
40 //增加map數據后:分配的內存 = 381KB, GC的次數 = 1
41 //刪除前數組長度: 10000
42 //刪除后數組長度: 0
43 //刪除map數據后:分配的內存 = 381KB, GC的次數 = 2
44 //設置為nil后:分配的內存 = 68KB, GC的次數 = 3

  可以看到delete是不會真正的把map釋放的,所以要回收map還是需要設為nil

sync.Map的原理詳解: 

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

【其他文章推薦】

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

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

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

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

堆和棧的區別

堆與棧

關於堆和棧的問題,對於熟悉C++的同學來說可能理解的比較清楚,但是由於Java的一切對象都是在堆上,因此有時候可能反而會比較迷惑,比如:

為什麼要分堆和棧?

堆和棧的區別是什麼?

為什麼堆是線程共享的而棧不是?

很多懂一點Java的人甚至是懂一點編程的人都知道內存一般分為堆和棧,棧由系統進行關係,而堆由程序員自己管理,…balabala,基本任何一本語言基礎書都會提到這些特點,但是你有想過為什麼嗎?

為什麼要分堆和棧

之所以要區分堆和棧,是由於程序需要兩種不同特性的內存形似而確定的。在C++中,新建一個對象有兩種方式,靜態分配動態分配

靜態分配

一般來說,靜態分配用於初始化已知對象大小的時候,比如int a[10];如果我們能夠確定這個數組是10個,我們可以使用這種方式。這種方式所需要的內存在編譯期間即可確定,因此操作系統便可以預先確定所指定大小內存給變量,並且可以在變量生命周期結束后自動釋放。

動態分配

然而在某些場景下,可能需要根據某些情況來申請內存,比如int* a =new int[count];而變量count可能就來自於某個配置文件或者用戶手動輸入的值。這個時候操作系統就無法再預先確定內存大小,並且不執行到new int[count]這一行代碼的時候,是無法知道所要分配的內存大小,因此操作系統分出一塊內存,用來進行動態分配。並且規定,動態分配的內存需要由客戶端自行管理。

Java 中的堆

由於JVM規範中規定,JVM中的一切對象都存儲在堆上(內存逃逸除外)。因此在Java中並不存在對象的靜態分配,因此堆和棧的來源看似就非常理所當然。但是要明白,在操作系統中,堆和棧的出現的緣由。

堆和棧的區別

知道了為什麼要區分堆和棧,再來看看堆和棧的區別。

  • 堆是運行時確定內存大小,而棧在編譯時即可確定內存大小

    理由便是第一節中提到的,這是區分堆和棧的初衷

  • 堆內存由用戶管理(Java中由JVM管理),棧內存會被自動釋放

  • 棧實現方式採用數據結構中的棧實現,具有(LIFO)的順序特點,堆為一塊一塊的內存

  • 棧由於其實現方式,在分配速度上比堆快的多。分配一塊棧內存不過是簡單的移動一個指針

  • 在JVM中,棧不會被程序員直接使用,程序員操作的一般都是堆。

  • 棧為線程私有而堆為線程共享

雖然堆和棧有這麼多的區別,但是這些區別都是由於操作系統而決定的,在硬件上,他們本質都是RAM

為什麼堆是線程共享的而棧不是?

上面最後一點提到了棧為線程私有而堆為線程共享。這是為什麼呢???

其實很簡單,為了解決一個問題:線程間通信。

想要實現線程間通信,目前有兩種方法:

  • 消息傳遞
  • 共享內存

共享內存便是我們所說的將堆設置為線程間共享的,這樣我們能夠通過堆中的對象實現數據共享,這樣便使得其他線程能夠知道某個線程修改了某個數據。但是這樣帶來的問題可能就有線程安全問題等,但是這樣做的優勢便在於速度更快節約內存,Java,C#等使用這種方式

消息傳遞是每個線程都私有自己的數據空間,當需要線程通信的時候,便需要一個線程显示的給另外一個線程發送具體的消息,這樣做的能夠讓多線程更加安全,但是私有的線程空間和消息傳遞帶來的是需要給內個線程都拷貝相同的對象,變量等,並且可能會帶來效率問題。而Erlang和JoCaml便是使用消息傳遞式線程通信。

尊重勞動成功,轉載註明原創

參考文章:

如果覺得寫得不錯,歡迎關注微信公眾號:逸游Java ,每天不定時發布一些有關Java進階的文章,感謝關注

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

【其他文章推薦】

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

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

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

北海道天候異常沒暴風雪 罕見吹起沙塵暴

摘錄自2020年1月6日中央社東京報導

日本北海道東部的十勝地方部分地區6日突然吹起沙塵暴,造成兩條高速公路部分路段因視線不佳暫停通行。由於冬天並非當地沙塵暴季節,研判可能是因為這個冬天降雪不足所造成。

北海道往年這個時期都是大雪紛飛,十勝地方應該全部會被白雪覆蓋,但受到這個冬天降雪異常減少,讓許多未被白雪覆蓋的田地露出來,變成乾燥的沙子容易被風捲起的狀態。

上述暫停通行路段在下午2時過後就恢復車輛通行,但警方及東日本高速道路公司等還是呼籲,駕駛人行經這些路段仍要留意強風,小心駕駛。

日本放送協會(NHK)報導,十勝地方這個冬天降雪較往年來得少,強風捲起農地沙子造成視線不佳的「塵煙霧」現象,是首度在冬天出現,十勝地方帶廣市去年12月降雪量僅17公分,不到往年降雪量四成;今天積雪6公分,較往年的26公分同樣大幅減少。

 

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

【其他文章推薦】

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

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

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

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

澳洲野火災情跨年後持續擴大 下雨仍無緩解

整理:劉妙慈 (環境資訊中心實習編輯)

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

【其他文章推薦】

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

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

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

搶佔市場份額!賓士推全新純電動車

日前,賓士稱將推出一款全新純電動車,續航里程在400-500km之間,競爭對手瞄準了特斯拉Model S。   據網通社報導,賓士2014年跟戴姆勒集團共同斥資64億美元研發低排放技術的專案,計畫未來幾年研發賓士新能源車,包括smartfortwo、A級、B級以及SLS AMG在內的多款電動車推出。   2017年起戴姆勒汽車集團斥資與福特、日產共同建設10個氫燃料補給站的舉措顯示,除了電動車外,氫燃料車也是賓士未來幾年重要產品策略。   此外,賓士還將隨主流發展插電混動版車型,據媒體消息稱截至2017年賓士將有10款插電混動版車型推出,包括S級和C級插電混動版。   除了賓士外,奧迪和寶馬均有新電動車計畫,奧迪全新Q6 e-tron Quattro概念車將於9月法蘭克福亮相,寶馬將於2018年推出一款純電動SUV競爭對手瞄準了特斯拉Model X,另外,寶馬一款基於5系打造的i5純電動也將為寶馬電動車細分市場贏得更多的份額。

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

【其他文章推薦】

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

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

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

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

保時捷推電動車,搶新市場一杯羹

  據科技日報消息,保時捷發佈了全新電動概念車Mission E。該車配有保時捷全新的800V超級充電技術,可以在15分鐘內充電80%。   Mission E全新概念車採用純電動設計,前後兩臺電動機分別驅動前後輪,形成四輪驅動系統,兩臺電動機總功率達到447kw,百公里加速僅需3.5秒,單次充電行駛里程約500公里。Mission E採用大量的空氣動力學套件,按照車身重心設計在底部安裝全新的鋰離子電池組等措施,加上130mm的最小離地間隙,都為該車的操控性起著重要作用。

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

【其他文章推薦】

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

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

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

Unity中用Mesh畫一個圓環

Probuider

前幾天在做一個小項目的時候,用到了Unity自帶的一個包其中的Arch生成1/4圓。

挺好玩的,可以在直接Unity中根據需要用Mesh定製生成圖形,而不用建模軟件。

但是存在一個小問題,就是在使用的時候他的中心點是在生成圖形的左下角。

旋轉的時候不符合我的需求,我想要的是生成的時候旋轉中心在圓心的位置,所以準備自己定製一個。

目標

關於生成圖形的原理可以參考這篇文章,講得雖然不算很詳細,但足夠了解基本概念了。

目標是生成下面圖中的一個1/4空心圓柱體

我們切換到Wireframe模式下,可以看出它是有一個一個的頂點,並通過一條條的直線連接起來。那麼我們如何確定這些頂點和線的位置呢?

小目標-生成一個面

其實很簡單的,我們一步一步慢慢來。一次生成一整個會有點麻煩,我們可以一面一面來。只要生成了第一個面,其他的面也是類似的方法生成就好。

在前面我們提到了我們要的是生成一個圓柱體,圓柱體一個的重要性質就是可以由一個圓形疊加產生,也就是只要我們生成一個圓形,就完成了大部分的工作。

我們知道3D建模就是由一個一個的三角形組合成的,所以我們要用三角形來模擬來一個空心的圓。

在Probuilder中生成這樣一個空心圓柱體用的是Arch,它有幾個參數,分別是
\(\color{#1E90FF}{Radius}\) 半徑,圓心到最外圈的距離
\(\color{#1E90FF}{Thickness}\) 厚度,圓心到最外圈的距離-圓心到最內圈的距離
\(\color{#1E90FF}{Depth}\) 深度
\(\color{#1E90FF}{NumberOfSides}\) 由多少個面組成,面越多越平滑,性能也越差
\(\color{#1E90FF}{DrawArchDegrees}\) 總共繪製的角度

\(\color{#1E90FF}{NumberOfSides}\)中的面是指由兩個三角形一頭一尾拼成的梯形,多個頭大腳小的梯形拼在一起便成了我們需要的圓形。

原理已經知道了,那下一步只要確定三角形頂點的位置就OK了。至於如何確定三角形頂點的位置,我們可以再看下這張圖。

是不是瞬間清晰明了,紅線的交匯處就是圓心的位置,数字則是每個頂點的編號。

我們假設圓心在原點,数字0-1所在的線為180度線。\(\color{#1E90FF}{Increment}\) = \(\color{#1E90FF}{DrawArchDegrees}\)/\(\color{#1E90FF}{NumberOfSides}\)就是線與線之間的角度。每條線的角度可以由\(\color{#1E90FF}{180-Increment*i}\)得到。i為第幾條線。

線上的點可以由\(\color{#1E90FF}{y = r* sinθ, y = r* cosθ}\)得到。

        //頂點坐標
        vertexList.Clear();
        float incrementAngle = DrawArchDegrees / NumberOfSides;
        //小於等於是因為n+1條線才能組成n個面
        for (int i = 0; i <= NumberOfSides; i++)
        {
            float angle = 180 - i * incrementAngle;
            float innerX = (Radius - Thickness) * Mathf.Cos(angle * Mathf.Deg2Rad);
            float innerY = (Radius - Thickness) * Mathf.Sin(angle * Mathf.Deg2Rad);
            vertexList.Add(new Vector3(innerX, innerY, 0));
            float outsideX = Radius * Mathf.Cos(angle * Mathf.Deg2Rad);
            float outsideY = Radius * Mathf.Sin(angle * Mathf.Deg2Rad);
            vertexList.Add(new Vector3(outsideX, outsideY, 0));
        }

在上面的代碼中我們已經計算出了頂點的位置,下一步我們要做的是按順序插入三角形頂點的位置。從這篇文章中我們可以知道,只有是三角形是正面的情況下才會被渲染。

而正反面可以通過法線的朝向進行判斷,向外的面就是正面,相反的就是背面。

在Unity中,法線的朝向可以由左手法則得到。拿出左手,伸直,拇指與其他四個指頭垂直,然後四指彎曲,指尖朝向循環的方向,拇指就指向法線的方向。

也就是說在上圖中,我們想渲染三角形,順序應該是類似這樣的012,321, 234, 543。

        //三角形索引
        triangleList.Clear();
        int direction = 1;
        for (int i = 0; i < NumberOfSides * 2; i++)
        {
            int[] triangleIndexs = getTriangleIndexs(i, direction);
            direction *= -1;
            for (int j = 0; j < triangleIndexs.Length; j++)
            {
                triangleList.Add(triangleIndexs[j]);
            }
        }

\(\color{#F08080}{getTriangleIndexs}\)代碼如下

    int[] getTriangleIndexs(int index, int direction)
    {
        int[] triangleIndexs = new int[3] { 0,1,2};
        for (int i = 0; i < triangleIndexs.Length; i++)
        {
            triangleIndexs[i] += index;
        }
        if (direction == -1)
        {
            int temp = triangleIndexs[0];
            triangleIndexs[0] = triangleIndexs[2];
            triangleIndexs[2] = temp;
        }
        return triangleIndexs;
    }

至於uv坐標就更簡單了,把內圈頂點uv坐標中的Y固定為0,外圈頂點uv坐標中的Y固定為1,而x坐標由\(\color{#1E90FF}{1/NumberOfSides}\)得到:

    //UV索引
    uvList.Clear();
    for (int i = 0; i <= NumberOfSides; i++)
    {
        float angle = 180 - i * incrementAngle;
        float littleX = (1.0f / NumberOfSides) * i;
        uvList.Add(new Vector2(littleX, 0));
        float bigX = (1.0f / NumberOfSides) * i;
        uvList.Add(new Vector2(bigX, 1));
    }

完整代碼如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//[RequireComponent(typeof(MeshFilter))]
//[RequireComponent(typeof(MeshRenderer))]
//[ExecuteInEditMode]
public class DrawArch : MonoBehaviour
{
    public float Radius = 20.0f;                //外圈的半徑
    public float Thickness = 10.0f;             //厚度,外圈半徑減去內圈半徑
    public float Depth = 1.0f;                  //厚度
    public float NumberOfSides = 30.0f;         //由多少個面組成
    public float DrawArchDegrees = 90.0f;       //要繪畫多長
    public Material archMaterial = null;
    
    private List<Vector3> vertexList = new List<Vector3>();
    private List<int> triangleList = new List<int>();
    private List<Vector2> uvList = new List<Vector2>();

    // Start is called before the first frame update
    void Start()
    {
        GenerateVertex();
    }

    void GenerateVertex()
    {
        //頂點坐標
        vertexList.Clear();
        float incrementAngle = DrawArchDegrees / NumberOfSides;
        //小於等於是因為n+1條線才能組成n個面
        for (int i = 0; i <= NumberOfSides; i++)
        {
            float angle = 180 - i * incrementAngle;
            float innerX = (Radius - Thickness) * Mathf.Cos(angle * Mathf.Deg2Rad);
            float innerY = (Radius - Thickness) * Mathf.Sin(angle * Mathf.Deg2Rad);
            vertexList.Add(new Vector3(innerX, innerY, 0));
            float outsideX = Radius * Mathf.Cos(angle * Mathf.Deg2Rad);
            float outsideY = Radius * Mathf.Sin(angle * Mathf.Deg2Rad);
            vertexList.Add(new Vector3(outsideX, outsideY, 0));
        }

        //三角形索引
        triangleList.Clear();
        int direction = 1;
        for (int i = 0; i < NumberOfSides * 2; i++)
        {
            int[] triangleIndexs = getTriangleIndexs(i, direction);
            direction *= -1;
            for (int j = 0; j < triangleIndexs.Length; j++)
            {
                triangleList.Add(triangleIndexs[j]);
            }
        }

        //UV索引
        uvList.Clear();
        for (int i = 0; i <= NumberOfSides; i++)
        {
            float angle = 180 - i * incrementAngle;
            float littleX = (1.0f / NumberOfSides) * i;
            uvList.Add(new Vector2(littleX, 0));
            float bigX = (1.0f / NumberOfSides) * i;
            uvList.Add(new Vector2(bigX, 1));
        }
        Mesh mesh = new Mesh()
        {
            vertices = vertexList.ToArray(),
            uv = uvList.ToArray(),
            triangles = triangleList.ToArray(),
        };

        mesh.RecalculateNormals();
        gameObject.AddComponent<MeshFilter>().mesh = mesh;
        gameObject.AddComponent<MeshRenderer>().material = archMaterial;
    }

    int[] getTriangleIndexs(int index, int direction)
    {
        int[] triangleIndexs = new int[3] { 0,1,2};
        for (int i = 0; i < triangleIndexs.Length; i++)
        {
            triangleIndexs[i] += index;
        }
        if (direction == -1)
        {
            int temp = triangleIndexs[0];
            triangleIndexs[0] = triangleIndexs[2];
            triangleIndexs[2] = temp;
        }
        return triangleIndexs;
    }
}

未完待續。。。

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

【其他文章推薦】

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

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

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

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

每天3分鐘操作系統修鍊秘籍(13):兩個緩衝空間Kernel Buffer和IO Buffer

兩個緩衝空間:kernel buffer和io buffer

先看一張圖,稍後將圍繞這張圖展開描述。圖中的fd table、open file table以及兩個inode table都可以不用理解,只需要知道它們體現出來的文件描述符和磁盤文件之間的對應關係:文件描述符fd(例如圖中的fd=3)是對應磁盤上文件的。

在Linux下,我們經常會在IO操作時不可避免的涉及到文件描述符,因為Linux下的所有IO操作都是通過文件描述符來完成的。但是,文件描述符是一個非常底層的概念,通過它操作的數據,都是二進制數據,所以通過文件描述符完成IO的模式通常也稱為裸IO(Raw IO)。而且,直接通過底層的文件描述符進行編程會比較麻煩,因為是二進制數據,它缺少很多功能,比如無法指定編碼,無法指定換行符(換行符有多種:\n、\n\r、\r)等等。注意fd是用戶空間的,它僅僅是一個數值而已,並不是想象中感覺比較底層就在內核空間。

所以,現代高級語言(比如C、Python、Java、Golang)都提供了比文件描述符更高一層次的標準IO庫,比如C的標準IO庫是stdio,Python的標準IO庫是IO模塊,等等。使用這些標準IO庫中的函數進行IO操作時,都會使用比文件描述符更高一層次的對象,例如C中稱為IO流(io stream),其它面向對象的語言中一般稱為IO對象,為了方便說明,這裏統稱為IO對象。上圖中的F就是文件對象。

標準IO庫可以看作是文件描述符的更高層次的封裝,提供了比文件描述符操作IO更多的功能。例如,可以在IO對象上指定編碼、指定換行符,此外還在用戶空間提供了一個標準IO庫的緩衝空間,通常可稱為stdio bufferIO buffer,而這些功能在文件描述符上都是沒有的。另外,標準IO庫既然是高層封裝,當然也會提供用戶不使用這些功能(比如不使用IO Buffer),而是直接使用文件描述符,那麼這時候的文件對象就相當於是文件描述符了,這時候的IO操作模式也就是裸IO模式。

所有從硬件讀取或寫入到硬件的數據,默認都會經過操作系統維護的這個Kernel Buffer。正如上圖中描述的是讀數據過程。

例如,cat進程想要讀取a.log文件,cat進程是用戶空間進程,它自身沒有權限打開文件以及讀文件數據,它只能通過系統調用的方式陷入內核,請求操作系統幫助讀取數據,操作系統讀取數據後會將數據放入到page cache(剛才已說明,對於普通文件維護的Kernel buffer稱為page cache或buffer cache)。然後還要將內核空間page cache中的數據拷貝到用戶空間的IO Buffer緩衝空間(因為cat程序的源代碼中使用了標準IO庫stdio),然後cat進程從自己的IO Buffer中讀取數據。這就是整個讀數據的過程。

需要注意的是,雖然這兩段緩衝空間都在內存中,但仍然有拷貝操作,因為內核的內存空間和用戶進程的虛擬內存空間是隔離的,用戶空間進程沒有權限訪問到內核空間的內存,但是內核具有最高權限,允許訪問任何內存地址。換句話說,在將Kernel Buffer的數據拷貝到IO Buffer空間的過程中,需要陷入到內核,OS需要掌控CPU。

此外,Linux也提供了所謂的直接IO模式,只需使用一個稱為O_DIRECT的標記即可,這時會繞過Kernel Buffer,直接將硬件數據拷貝到用戶空間。雖然看上去直接IO少了一個層次的參與感覺性能會更優秀,但實際上並非如此,操作系統為內核緩衝空間做了非常多的優化,使得並不會因此而降低性能。最典型且常見的一個優化是預讀功能,它表示在讀數據時,會比所請求要讀取的數據量多讀一點放入到Kernel Buffer,這樣在下次讀取接下來的一段數據時可以直接從Kernel Buffer中取數據,而無需再和硬件IO交互。所以,使用直接IO模式的場景是非常少的,一般只會在自帶了完整緩衝模型的大型軟件(比如數據庫系統)上可能會使用直接IO模式。

上面所描述的都是讀操作,關於寫操作,這裏不再多花篇幅去描述,整體過程和讀是類似的,都會經過IO Buffer和Kernel Buffer,只是其中一些細節有所不同,如果感興趣,可以閱讀《Linux/Unix系統編程手冊》的第13章。

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

【其他文章推薦】

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

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

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

詳解Spring Security的formLogin登錄認證模式

一、formLogin的應用場景

在本專欄之前的文章中,已經給大家介紹過Spring Security的HttpBasic模式,該模式比較簡單,只是進行了通過攜帶Http的Header進行簡單的登錄驗證,而且沒有定製的登錄頁面,所以使用場景比較窄。
對於一個完整的應用系統,與登錄驗證相關的頁面都是高度定製化的,非常美觀而且提供多種登錄方式。這就需要Spring Security支持我們自己定製登錄頁面,也就是本文給大家介紹的formLogin模式登錄認證模式。

準備工作

  • 新建一個Spring Boot 的web應用,引入Spring Security Starter。
  • 準備一個login.html登錄頁面,頁面內容非常簡單,一個from表單、用戶名和密碼輸入框,一個提交按鈕
  • 準備一個首頁index.html,在登錄成功之後需要進入index.html首頁
  • 首頁可以看到syslog(日誌管理)、sysuer(用戶管理)、biz1(業務一)、biz2(業務二)四個頁面超文本鏈接選項。通過controller控制層跳轉頁面,並在對應頁面寫一些標誌性文字即可,不需寫具體業務。

需求

  • 我們希望biz1(業務一)、biz2(業務二)普通的操作用戶user就可以訪問
  • 我們希望管理員可以訪問包括syslog(日誌管理)和sysuser(用戶管理)在內的所有資源

以上就是本文介紹formLogin模式需要進行的準備工作及需求,下面我們就來實現其中的核心的登錄驗證邏輯,準備工作非常簡單請自行實現。(新建spring boot應用,登錄頁面、首頁、四個業務頁面都寫成非常簡單的html即可,不用寫實際業務和樣式。)

二、說明

formLogin模式的三要素:

  • 登錄驗證邏輯
  • 資源訪問控制規則,如:資源權限、角色權限
  • 用戶信息

一般來說,使用權限認證框架的的業務系統登錄驗證邏輯是固定的,而資源訪問控制規則和用戶信息是從數據庫或其他存儲介質靈活加載的。但本文所有的用戶、資源、權限信息都是代碼配置寫死的,旨在為大家介紹formLogin認證模式,如何從數據庫加載權限認證相關信息我還會結合RBAC權限模型再寫文章的。

三、實現formLogin模式基礎配置

首先,我們要繼承WebSecurityConfigurerAdapter ,重寫configure(HttpSecurity http) 方法,該方法用來配置登錄驗證邏輯。請注意看下文代碼中的註釋信息。

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable() //禁用跨站csrf攻擊防禦,後面的章節會專門講解
            .formLogin()
                .loginPage("/login.html")//用戶未登錄時,訪問任何資源都轉跳到該路徑,即登錄頁面
                .loginProcessingUrl("/login")//登錄表單form中action的地址,也就是處理認證請求的路徑
                .usernameParameter("uname")///登錄表單form中用戶名輸入框input的name名,不修改的話默認是username
                .passwordParameter("pword")//form中密碼輸入框input的name名,不修改的話默認是password
                .defaultSuccessUrl("/index")//登錄認證成功后默認轉跳的路徑
                .and()
            .authorizeRequests()
                .antMatchers("/login.html","/login").permitAll()//不需要通過登錄驗證就可以被訪問的資源路徑
                .antMatchers("/biz1").hasAnyAuthority("biz1")  //前面是資源的訪問路徑、後面是資源的名稱或者叫資源ID
                .antMatchers("/biz2").hasAnyAuthority("biz2")
                .antMatchers("/syslog").hasAnyAuthority("syslog")
                .antMatchers("/sysuser").hasAnyAuthority("sysuser")
                .anyRequest().authenticated();
    }
    
}

上面的代碼分為兩部分:

  • 第一部分是formLogin配置段,用於配置登錄驗證邏輯相關的信息。如:登錄頁面、登錄成功頁面、登錄請求處理路徑等。
  • 第二部分是authorizeRequests配置端,用於配置資源的訪問權限。如:開發登錄頁面的permitAll開放訪問,“/biz1”(業務一頁面資源)需要有資源ID為”biz1″的用戶才可以訪問。

這時候,我們通過瀏覽器訪問,隨便測試一個沒有訪問權限的資源,都會跳轉到login.html頁面。

四、實現資源訪問限制的需求

在上文中,我們配置了登錄驗證及資源訪問的權限規則,我們還沒有具體的用戶,下面我們就來配置具體的用戶。重寫WebSecurityConfigurerAdapter的 configure(AuthenticationManagerBuilder auth)方法

public void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication()
        .withUser("user").password(passwordEncoder().encode("123456")).authorities("biz1","biz2")
        .and()
        .passwordEncoder(passwordEncoder());//配置BCrypt加密
}

@Bean
public PasswordEncoder passwordEncoder(){
    return new BCryptPasswordEncoder();
}
  • inMemoryAuthentication指的是在內存裏面存儲用戶的身份認證和授權信息。
  • withUser("user")用戶名是user
  • password(passwordEncoder().encode("123456"))密碼是加密之後的123456
  • authorities("biz1","biz2")指的是user用戶擁有資源ID為biz1(業務一)和biz2(業務二)資源的權限

這樣,我們就實現了文首提出的普通用戶只能訪問biz1(業務一)和biz2(業務二)資源的需求。那麼管理員用戶可以訪問所有的資源的配置方式,你會不會呢?同樣的配方、同樣的方式、自己可以嘗試一下哦!

五、靜態資源訪問

在我們的實際開發中,登錄頁面login.html和控制層Controller登錄驗證’/login’都必須無條件的開放。除此之外,一些靜態資源如css、js文件通常也都不需要驗證權限,我們需要將它們的訪問權限也開放出來。下面就是實現的方法:重寫WebSecurityConfigurerAdapter類的configure(WebSecurity web) 方法


   @Override
    public void configure(WebSecurity web) {
        //將項目中靜態資源路徑開放出來
        web.ignoring().antMatchers("/config/**", "/css/**", "/fonts/**", "/img/**", "/js/**");
    }

期待您的關注

  • 博主最近新寫了一本書:
  • 本文轉載註明出處(必須帶連接,不能只轉文字):。

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

【其他文章推薦】

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

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

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

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