sync.Map是1.9才推薦的并發(fā)安全的map,除了互斥量以外,還運(yùn)用了原子操作,所以在這之前,有必要了解下 Go語(yǔ)言——原子操作
專注于為中小企業(yè)提供成都做網(wǎng)站、成都網(wǎng)站建設(shè)、成都外貿(mào)網(wǎng)站建設(shè)服務(wù),電腦端+手機(jī)端+微信端的三站合一,更高效的管理,為中小企業(yè)樊城免費(fèi)做網(wǎng)站提供優(yōu)質(zhì)的服務(wù)。我們立足成都,凝聚了一批互聯(lián)網(wǎng)行業(yè)人才,有力地推動(dòng)了1000多家企業(yè)的穩(wěn)健成長(zhǎng),幫助中小企業(yè)通過(guò)網(wǎng)站建設(shè)實(shí)現(xiàn)規(guī)模擴(kuò)充和轉(zhuǎn)變。
go1.10\src\sync\map.go
entry分為三種情況:
從read中讀取key,如果key存在就tryStore。
注意這里開(kāi)始需要加鎖,因?yàn)樾枰僮鱠irty。
條目在read中,首先取消標(biāo)記,然后將條目保存到dirty里。(因?yàn)闃?biāo)記的數(shù)據(jù)不在dirty里)
最后原子保存value到條目里面,這里注意read和dirty都有條目。
總結(jié)一下Store:
這里可以看到dirty保存了數(shù)據(jù)的修改,除非可以直接原子更新read,繼續(xù)保持read clean。
有了之前的經(jīng)驗(yàn),可以猜測(cè)下load流程:
與猜測(cè)的 區(qū)別 :
由于數(shù)據(jù)保存兩份,所以刪除考慮:
先看第二種情況。加鎖直接刪除dirty數(shù)據(jù)。思考下貌似沒(méi)什么問(wèn)題,本身就是臟數(shù)據(jù)。
第一種和第三種情況唯一的區(qū)別就是條目是否被標(biāo)記。標(biāo)記代表刪除,所以直接返回。否則CAS操作置為nil。這里總感覺(jué)少點(diǎn)什么,因?yàn)闂l目其實(shí)還是存在的,雖然指針nil。
看了一圈貌似沒(méi)找到標(biāo)記的邏輯,因?yàn)閯h除只是將他變成nil。
之前以為這個(gè)邏輯就是簡(jiǎn)單的將為標(biāo)記的條目拷貝給dirty,現(xiàn)在看來(lái)大有文章。
p == nil,說(shuō)明條目已經(jīng)被delete了,CAS將他置為標(biāo)記刪除。然后這個(gè)條目就不會(huì)保存在dirty里面。
這里其實(shí)就跟miss邏輯串起來(lái)了,因?yàn)閙iss達(dá)到閾值之后,dirty會(huì)全量變成read,也就是說(shuō)標(biāo)記刪除在這一步最終刪除。這個(gè)還是很巧妙的。
真正的刪除邏輯:
很繞。。。。
操作字符串離不開(kāi)字符串的拼接,但是Go中string是只讀類型,大量字符串的拼接會(huì)造成性能問(wèn)題。
拼接字符串,無(wú)外乎四種方式,采用“+”,“fmt.Sprintf()”,"bytes.Buffer","strings.Builder"
上面我們創(chuàng)建10萬(wàn)字符串拼接的測(cè)試,可以發(fā)現(xiàn)"bytes.Buffer","strings.Builder"的性能最好,約是“+”的1000倍級(jí)別。
這是由于string是不可修改的,所以在使用“+”進(jìn)行拼接字符串,每次都會(huì)產(chǎn)生申請(qǐng)空間,拼接,復(fù)制等操作,數(shù)據(jù)量大的情況下非常消耗資源和性能。而采用Buffer等方式,都是預(yù)先計(jì)算拼接字符串?dāng)?shù)組的總長(zhǎng)度(如果可以知道長(zhǎng)度),申請(qǐng)空間,底層是slice數(shù)組,可以以append的形式向后進(jìn)行追加。最后在轉(zhuǎn)換為字符串。這申請(qǐng)了不斷申請(qǐng)空間的操作,也減少了空間的使用和拷貝的次數(shù),自然性能也高不少。
bytes.buffer是一個(gè)緩沖byte類型的緩沖器存放著都是byte
是一個(gè)變長(zhǎng)的 buffer,具有 Read 和Write 方法。 Buffer 的 零值 是一個(gè) 空的 buffer,但是可以使用,底層就是一個(gè) []byte, 字節(jié)切片。
向Buffer中寫(xiě)數(shù)據(jù),可以看出Buffer中有個(gè)Grow函數(shù)用于對(duì)切片進(jìn)行擴(kuò)容。
從Buffer中讀取數(shù)據(jù)
strings.Builder的方法和bytes.Buffer的方法的命名幾乎一致。
但實(shí)現(xiàn)并不一致,Builder的Write方法直接將字符拼接slice數(shù)組后。
其沒(méi)有提供read方法,但提供了strings.Reader方式
Reader 結(jié)構(gòu):
Buffer:
Builder:
可以看出Buffer和Builder底層都是采用[]byte數(shù)組進(jìn)行裝載數(shù)據(jù)。
先來(lái)說(shuō)說(shuō)Buffer:
創(chuàng)建好Buffer是一個(gè)empty的,off 用于指向讀寫(xiě)的尾部。
在寫(xiě)的時(shí)候,先判斷當(dāng)前寫(xiě)入字符串長(zhǎng)度是否大于Buffer的容量,如果大于就調(diào)用grow進(jìn)行擴(kuò)容,擴(kuò)容申請(qǐng)的長(zhǎng)度為當(dāng)前寫(xiě)入字符串的長(zhǎng)度。如果當(dāng)前寫(xiě)入字符串長(zhǎng)度小于最小字節(jié)長(zhǎng)度64,直接創(chuàng)建64長(zhǎng)度的[]byte數(shù)組。如果申請(qǐng)的長(zhǎng)度小于二分之一總?cè)萘繙p去當(dāng)前字符總長(zhǎng)度,說(shuō)明存在很大一部分被使用但已讀,可以將未讀的數(shù)據(jù)滑動(dòng)到數(shù)組頭。如果容量不足,擴(kuò)展2*c + n 。
其String()方法就是將字節(jié)數(shù)組強(qiáng)轉(zhuǎn)為string
Builder是如何實(shí)現(xiàn)的。
Builder采用append的方式向字節(jié)數(shù)組后添加字符串。
從上面可以看出,[]byte的內(nèi)存大小也是以倍數(shù)進(jìn)行申請(qǐng)的,初始大小為 0,第一次為大于當(dāng)前申請(qǐng)的最大 2 的指數(shù),不夠進(jìn)行翻倍.
可以看出如果舊容量小于1024進(jìn)行翻倍,否則擴(kuò)展四分之一。(2048 byte 后,申請(qǐng)策略的調(diào)整)。
其次String()方法與Buffer的string方法也有明顯區(qū)別。Buffer的string是一種強(qiáng)轉(zhuǎn),我們知道在強(qiáng)轉(zhuǎn)的時(shí)候是需要進(jìn)行申請(qǐng)空間,并拷貝的。而B(niǎo)uilder只是指針的轉(zhuǎn)換。
這里我們解析一下 *(*string)(unsafe.Pointer(b.buf)) 這個(gè)語(yǔ)句的意思。
先來(lái)了解下unsafe.Pointer 的用法。
也就是說(shuō),unsafe.Pointer 可以轉(zhuǎn)換為任意類型,那么意味著,通過(guò)unsafe.Pointer媒介,程序繞過(guò)類型系統(tǒng),進(jìn)行地址轉(zhuǎn)換而不是拷貝。
即*A = Pointer = *B
就像上面例子一樣,將字節(jié)數(shù)組轉(zhuǎn)為unsafe.Pointer類型,再轉(zhuǎn)為string類型,s和b中內(nèi)容一樣,修改b,s也變了,說(shuō)明b和s是同一個(gè)地址。但是對(duì)s重新賦值后,意味著s的地址指向了“WORLD”,它們所使用的內(nèi)存空間不同了,所以s改變后,b并不會(huì)改變。
所以他們的區(qū)別就在于 bytes.Buffer 是重新申請(qǐng)了一塊空間,存放生成的string變量, 而strings.Builder直接將底層的[]byte轉(zhuǎn)換成了string類型返回了回來(lái),去掉了申請(qǐng)空間的操作。
?? 當(dāng)讀取91.2 MB文件時(shí),read1耗時(shí)43ms,read2耗時(shí)99ms。
查看源碼:
讀取文件主要是通過(guò) Read(p []byte) (n int, err error) :
官方文檔中關(guān)于該接口方法的說(shuō)明:
結(jié)論:
??ReadFile(filename string)方法之所以速度快的原因就是先計(jì)算出file文件的size,在初始化對(duì)應(yīng)size大小的buff,傳入ReadRead(p []byte) 來(lái)讀取字節(jié)流
package?main
import?"fmt"
func?main()?{
var?a,?b,?c?int
fmt.Scanf("%d%d%d",?a,?b,?c)
fmt.Println(a?+?b?+?c)
}
希望采納!
Go 原生的 pkg 中有一些核心的 interface ,其中 io.Reader/Writer 是比較常用的接口。很多原生的結(jié)構(gòu)都圍繞這個(gè)系列的接口展開(kāi),在實(shí)際的開(kāi)發(fā)過(guò)程中,你會(huì)發(fā)現(xiàn)通過(guò)這個(gè)接口可以在多種不同的io類型之間進(jìn)行過(guò)渡和轉(zhuǎn)化。本文結(jié)合實(shí)際場(chǎng)景來(lái)總結(jié)一番。
圍繞 io.Reader/Writer ,有幾個(gè)常用的實(shí)現(xiàn):
這些實(shí)現(xiàn)對(duì)于初學(xué)者來(lái)說(shuō)其實(shí)比較難去記憶,在遇到實(shí)際問(wèn)題的時(shí)候更是一臉蒙圈,不知如何是好。下面用實(shí)際的場(chǎng)景來(lái)舉例
encoding/base64 包中:
這個(gè)用來(lái)做 base64 編碼,但是仔細(xì)觀察發(fā)現(xiàn),它需要一個(gè)io.Writer作為輸出目標(biāo),并用返回的 WriteCloser 的Write方法將結(jié)果寫(xiě)入目標(biāo),下面是Go官方文檔的例子
這個(gè)例子是將結(jié)果寫(xiě)入到 Stdout ,如果我們希望得到一個(gè)字符串呢?觀察上面的圖,不然發(fā)現(xiàn)可以用bytes.Buffer作為目標(biāo) io.Writer :
這種場(chǎng)景經(jīng)常用在基于字節(jié)的協(xié)議上,比如有一個(gè)具有固定長(zhǎng)度的結(jié)構(gòu):
通過(guò)一個(gè) []byte 來(lái)反序列化得到這個(gè) Protocol ,一種思路是遍歷這個(gè) []byte ,然后逐一賦值。其實(shí)在 encoding/binary 包中有個(gè)方便的方法:
這個(gè)方法從一個(gè) io.Reader 中讀取字節(jié),并已 order 指定的端模式,來(lái)給填充 data (data需要是fixed-sized的結(jié)構(gòu)或者類型)。要用到這個(gè)方法首先要有一個(gè) io.Reader ,從上面的圖中不難發(fā)現(xiàn),我們可以這么寫(xiě):
換句話說(shuō),我們將一個(gè) []byte 轉(zhuǎn)成了一個(gè) io.Reader 。
反過(guò)來(lái),我們需要將 Protocol 序列化得到 []byte ,使用 encoding/binary 包中有個(gè)對(duì)應(yīng)的 Write 方法:
通過(guò)將 []byte 轉(zhuǎn)成一個(gè) io.Writer 即可:
比如對(duì)于常見(jiàn)的基于文本行的 HTTP 協(xié)議的讀取,我們需要將一個(gè)流按照行來(lái)讀取。本質(zhì)上,我們需要一個(gè)基于緩沖的讀寫(xiě)機(jī)制(讀一些到緩沖,然后遍歷緩沖中我們關(guān)心的字節(jié)或字符)。在Go中有一個(gè) bufio 的包可以實(shí)現(xiàn)帶緩沖的讀寫(xiě):
這個(gè)ReadString方法從 io.Reader 中讀取字符串,直到 delim ,就返回 delim 和之前的字符串。如果將 delim 設(shè)置為 \n ,相當(dāng)于按行來(lái)讀取了:
等價(jià)于
實(shí)現(xiàn)一個(gè)最簡(jiǎn)單的類似spark的流式處理流程
包含map和filter
數(shù)據(jù)
map函數(shù)
fliter函數(shù)
所有數(shù)據(jù)+1 過(guò)濾出偶數(shù) 過(guò)濾出大于5的數(shù)