大家好,我是小白,有點黑的那個白。
石首網(wǎng)站建設(shè)公司創(chuàng)新互聯(lián),石首網(wǎng)站設(shè)計制作,有大型網(wǎng)站制作公司豐富經(jīng)驗。已為石首1000多家提供企業(yè)網(wǎng)站建設(shè)服務(wù)。企業(yè)網(wǎng)站搭建\成都外貿(mào)網(wǎng)站建設(shè)公司要多少錢,請找那個售后服務(wù)好的石首做網(wǎng)站的公司定做!
最近遇到一個問題,因為業(yè)務(wù)需求,需要對接第三方平臺.
而三方平臺提供的一些HTTP(S)接口都有統(tǒng)一的密鑰生成規(guī)則要求.
為此我們封裝了一個獨(dú)立的包 xxx-go-sdk 以便維護(hù)和對接使用.
其中核心的部分是自定義HTTP Client,如下:
一些平臺會要求appKey/appSecret等信息,所以Client結(jié)構(gòu)體就變成了這樣,這時參數(shù)還比較少, 而且是必填的參數(shù),我們可以提供構(gòu)造函數(shù)來明確指定。
看起來很滿足,但是當(dāng)我們需要增加一個 Timeout 參數(shù)來控制超時呢?
或許你會說這還不簡單,像下面一樣再加一個參數(shù)唄
那再加些其他的參數(shù)呢?那構(gòu)造函數(shù)的參數(shù)是不是又長又串,而且每個參數(shù)不一定是必須的,有些參數(shù)我們又會考慮默認(rèn)值的問題。
為此,勤勞但尚未致富的 gophers 們使用了總結(jié)一種實踐模式
首先提取所有需要的參數(shù)到一個獨(dú)立的結(jié)構(gòu)體 Options,當(dāng)然你也可以用 Configs 啥的.
然后為每個參數(shù)提供設(shè)置函數(shù)
這樣我們就為每個參數(shù)設(shè)置了獨(dú)立的設(shè)置函數(shù)。返回值 func(*Options) 看著有點不友好,我們提取下定義為單個 Option 調(diào)整一下代碼
當(dāng)我們需要添加更多的參數(shù)時,只需要在 Options 添加新的參數(shù)并添加新參數(shù)的設(shè)置函數(shù)即可。
比如現(xiàn)在要添加新的參數(shù) Timeout
這樣后續(xù)不管新增多少參數(shù),只需要新增配置項并添加獨(dú)立的設(shè)置函數(shù)即可輕松擴(kuò)展,并且不會影響原有函數(shù)的參數(shù)順序和個數(shù)位置等。
至此,每個選項是區(qū)分開來了,那么怎么作用到我們的 Client 結(jié)構(gòu)體上呢?
首先,配置選項都被提取到了 Options 結(jié)構(gòu)體重,所以我們需要調(diào)整一下 Client 結(jié)構(gòu)體的參數(shù)
其次,每一個選項函數(shù)返回 Option,那么任意多個就是 ...Option,我們調(diào)整一下構(gòu)造函數(shù) NewClient 的參數(shù)形式,改為可變參數(shù),不再局限于固定順序的幾個參數(shù)。
然后循環(huán)遍歷每個選項函數(shù),來生成Client結(jié)構(gòu)體的完整配置選項。
那么怎么調(diào)用呢?對于調(diào)用方而已,直接在調(diào)用構(gòu)造函數(shù)NewClient()的參數(shù)內(nèi)添加自己需要的設(shè)置函數(shù)(WithXXX)即可
當(dāng)需要設(shè)置超時參數(shù),直接添加 WithTimeout即可,比如設(shè)置3秒的超時
配置選項的位置可以任意設(shè)置,不需要受常規(guī)的固定參數(shù)順序約束。
可以看到,這種實踐模式主要作用于配置選項,利用函數(shù)支持的特性來實現(xiàn)的,為此得名 Functional Options Pattern,優(yōu)美的中國話叫做「函數(shù)選項模式」。
最后, 我們總結(jié)回顧一下在Go語言中函數(shù)選項模式的優(yōu)缺點
本文是對 Gopher 2017 中一個非常好的 Talk?: [Understanding Channel](GopherCon 2017: Kavya Joshi - Understanding Channels) 的學(xué)習(xí)筆記,希望能夠通過對 channel 的關(guān)鍵特性的理解,進(jìn)一步掌握其用法細(xì)節(jié)以及 Golang 語言設(shè)計哲學(xué)的管窺蠡測。
channel 是可以讓一個 goroutine 發(fā)送特定值到另一個 gouroutine 的通信機(jī)制。
原生的 channel 是沒有緩存的(unbuffered channel),可以用于 goroutine 之間實現(xiàn)同步。
關(guān)閉后不能再寫入,可以讀取直到 channel 中再沒有數(shù)據(jù),并返回元素類型的零值。
gopl/ch3/netcat3
首先從 channel 是怎么被創(chuàng)建的開始:
在 heap 上分配一個 hchan 類型的對象,并將其初始化,然后返回一個指向這個 hchan 對象的指針。
理解了 channel 的數(shù)據(jù)結(jié)構(gòu)實現(xiàn),現(xiàn)在轉(zhuǎn)到 channel 的兩個最基本方法: sends 和 receivces ,看一下以上的特性是如何體現(xiàn)在 sends 和 receives 中的:
假設(shè)發(fā)送方先啟動,執(zhí)行 ch - task0 :
如此為 channel 帶來了 goroutine-safe 的特性。
在這樣的模型里, sender goroutine - channel - receiver goroutine 之間, hchan 是唯一的共享內(nèi)存,而這個唯一的共享內(nèi)存又通過 mutex 來確保 goroutine-safe ,所有在隊列中的內(nèi)容都只是副本。
這便是著名的 golang 并發(fā)原則的體現(xiàn):
發(fā)送方 goroutine 會阻塞,暫停,并在收到 receive 后才恢復(fù)。
goroutine 是一種 用戶態(tài)線程 , 由 Go runtime 創(chuàng)建并管理,而不是操作系統(tǒng),比起操作系統(tǒng)線程來說,goroutine更加輕量。
Go runtime scheduler 負(fù)責(zé)將 goroutine 調(diào)度到操作系統(tǒng)線程上。
runtime scheduler 怎么將 goroutine 調(diào)度到操作系統(tǒng)線程上?
當(dāng)阻塞發(fā)生時,一次 goroutine 上下文切換的全過程:
然而,被阻塞的 goroutine 怎么恢復(fù)過來?
阻塞發(fā)生時,調(diào)用 runtime sheduler 執(zhí)行 gopark 之前,G1 會創(chuàng)建一個 sudog ,并將它存放在 hchan 的 sendq 中。 sudog 中便記錄了即將被阻塞的 goroutine G1 ,以及它要發(fā)送的數(shù)據(jù)元素 task4 等等。
接收方 將通過這個 sudog 來恢復(fù) G1
接收方 G2 接收數(shù)據(jù), 并發(fā)出一個 receivce ,將 G1 置為 runnable :
同樣的, 接收方 G2 會被阻塞,G2 會創(chuàng)建 sudoq ,存放在 recvq ,基本過程和發(fā)送方阻塞一樣。
不同的是,發(fā)送方 G1如何恢復(fù)接收方 G2,這是一個非常神奇的實現(xiàn)。
理論上可以將 task 入隊,然后恢復(fù) G2, 但恢復(fù) G2后,G2會做什么呢?
G2會將隊列中的 task 復(fù)制出來,放到自己的 memory 中,基于這個思路,G1在這個時候,直接將 task 寫到 G2的 stack memory 中!
這是違反常規(guī)的操作,理論上 goroutine 之間的 stack 是相互獨(dú)立的,只有在運(yùn)行時可以執(zhí)行這樣的操作。
這么做純粹是出于性能優(yōu)化的考慮,原來的步驟是:
優(yōu)化后,相當(dāng)于減少了 G2 獲取鎖并且執(zhí)行 memcopy 的性能消耗。
channel 設(shè)計背后的思想可以理解為 simplicity 和 performance 之間權(quán)衡抉擇,具體如下:
queue with a lock prefered to lock-free implementation:
比起完全 lock-free 的實現(xiàn),使用鎖的隊列實現(xiàn)更簡單,容易實現(xiàn)
1.1 Go 安裝
Go的三種安裝方式
Go有多種安裝方式,你可以選擇自己喜歡的。這里我們介紹三種最常見的安裝方式:
Go源碼安裝:這是一種標(biāo)準(zhǔn)的軟件安裝方式。對于經(jīng)常使用Unix類系統(tǒng)的用戶,尤其對于開發(fā)者來說,從源碼安裝可以自己定制。
Go標(biāo)準(zhǔn)包安裝:Go提供了方便的安裝包,支持Windows、Linux、Mac等系統(tǒng)。這種方式適合快速安裝,可根據(jù)自己的系統(tǒng)位數(shù)下載好相應(yīng)的安裝包,一路next就可以輕松安裝了。**推薦這種方式**
第三方工具安裝:目前有很多方便的第三方軟件包工具,例如Ubuntu的apt-get、Mac的homebrew等。這種安裝方式適合那些熟悉相應(yīng)系統(tǒng)的用戶。
最后,如果你想在同一個系統(tǒng)中安裝多個版本的Go,你可以參考第三方工具GVM,這是目前在這方面做得最好的工具,除非你知道怎么處理。
Go源碼安裝
在Go的源代碼中,有些部分是用Plan 9 C和ATT匯編寫的,因此假如你要想從源碼安裝,就必須安裝C的編譯工具。
在Mac系統(tǒng)中,只要你安裝了Xcode,就已經(jīng)包含了相應(yīng)的編譯工具。
在類Unix系統(tǒng)中,需要安裝gcc等工具。例如Ubuntu系統(tǒng)可通過在終端中執(zhí)行sudo apt-get install gcc
libc6-dev來安裝編譯工具。
在Windows系統(tǒng)中,你需要安裝MinGW,然后通過MinGW安裝gcc,并設(shè)置相應(yīng)的環(huán)境變量。
你可以直接去官網(wǎng)下載源碼,找相應(yīng)的goVERSION.src.tar.gz的文件下載,下載之后解壓縮到$HOME目錄,執(zhí)行如下代碼:
cd go/src
./all.bash
運(yùn)行all.bash后出現(xiàn)"ALL TESTS PASSED"字樣時才算安裝成功。
上面是Unix風(fēng)格的命令,Windows下的安裝方式類似,只不過是運(yùn)行all.bat,調(diào)用的編譯器是MinGW的gcc。
如果是Mac或者Unix用戶需要設(shè)置幾個環(huán)境變量,如果想重啟之后也能生效的話把下面的命令寫到.bashrc或者.zshrc里面,
export GOPATH=$HOME/gopath
export PATH=$PATH:$HOME/go/bin:$GOPATH/bin
如果你是寫入文件的,記得執(zhí)行bash .bashrc或者bash
.zshrc使得設(shè)置立馬生效。
如果是window系統(tǒng),就需要設(shè)置環(huán)境變量,在path里面增加相應(yīng)的go所在的目錄,設(shè)置gopath變量。
當(dāng)你設(shè)置完畢之后在命令行里面輸入go,看到如下圖片即說明你已經(jīng)安裝成功
圖1.1 源碼安裝之后執(zhí)行Go命令的圖
如果出現(xiàn)Go的Usage信息,那么說明Go已經(jīng)安裝成功了;如果出現(xiàn)該命令不存在,那么可以檢查一下自己的PATH環(huán)境變中是否包含了Go的安裝目錄。
關(guān)于上面的GOPATH將在下面小節(jié)詳細(xì)講解
Go標(biāo)準(zhǔn)包安裝
Go提供了每個平臺打好包的一鍵安裝,這些包默認(rèn)會安裝到如下目錄:/usr/local/go
(Windows系統(tǒng):c:\Go),當(dāng)然你可以改變他們的安裝位置,但是改變之后你必須在你的環(huán)境變量中設(shè)置如下信息:
export GOROOT=$HOME/go
export GOPATH=$HOME/gopath
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin
上面這些命令對于Mac和Unix用戶來說最好是寫入.bashrc或者.zshrc文件,對于windows用戶來說當(dāng)然是寫入環(huán)境變量。
如何判斷自己的操作系統(tǒng)是32位還是64位?
我們接下來的Go安裝需要判斷操作系統(tǒng)的位數(shù),所以這小節(jié)我們先確定自己的系統(tǒng)類型。
Windows系統(tǒng)用戶請按Win+R運(yùn)行cmd,輸入systeminfo后回車,稍等片刻,會出現(xiàn)一些系統(tǒng)信息。在“系統(tǒng)類型”一行中,若顯示“x64-based
PC”,即為64位系統(tǒng);若顯示“X86-based PC”,則為32位系統(tǒng)。
Mac系統(tǒng)用戶建議直接使用64位的,因為Go所支持的Mac OS X版本已經(jīng)不支持純32位處理器了。
Linux系統(tǒng)用戶可通過在Terminal中執(zhí)行命令arch(即uname
-m)來查看系統(tǒng)信息:
64位系統(tǒng)顯示
x86_64
32位系統(tǒng)顯示
i386
Mac 安裝
訪問下載地址,32位系統(tǒng)下載go1.4.2.darwin-386-osx10.8.pkg,64位系統(tǒng)下載go1.4.2.darwin-amd64-osx10.8.pkg,雙擊下載文件,一路默認(rèn)安裝點擊下一步,這個時候go已經(jīng)安裝到你的系統(tǒng)中,默認(rèn)已經(jīng)在PATH中增加了相應(yīng)的~/go/bin,這個時候打開終端,輸入go
看到類似上面源碼安裝成功的圖片說明已經(jīng)安裝成功
如果出現(xiàn)go的Usage信息,那么說明go已經(jīng)安裝成功了;如果出現(xiàn)該命令不存在,那么可以檢查一下自己的PATH環(huán)境變中是否包含了go的安裝目錄。
Linux 安裝
訪問下載地址,32位系統(tǒng)下載go1.4.2.linux-386.tar.gz,64位系統(tǒng)下載go1.4.2.linux-amd64.tar.gz,
假定你想要安裝Go的目錄為 $GO_INSTALL_DIR,后面替換為相應(yīng)的目錄路徑。
解壓縮tar.gz包到安裝目錄下:tar zxvf go1.4.2.linux-amd64.tar.gz -C
$GO_INSTALL_DIR。
設(shè)置PATH,export PATH=$PATH:$GO_INSTALL_DIR/go/bin
然后執(zhí)行g(shù)o
圖1.2 Linux系統(tǒng)下安裝成功之后執(zhí)行g(shù)o顯示的信息
如果出現(xiàn)go的Usage信息,那么說明go已經(jīng)安裝成功了;如果出現(xiàn)該命令不存在,那么可以檢查一下自己的PATH環(huán)境變中是否包含了go的安裝目錄。
Windows 安裝
訪問Google Code 下載頁,32
位請選擇名稱中包含 windows-386 的 msi 安裝包,64 位請選擇名稱中包含 windows-amd64 的。下載好后運(yùn)行,不要修改默認(rèn)安裝目錄
C:\Go\,若安裝到其他位置會導(dǎo)致不能執(zhí)行自己所編寫的 Go 代碼。安裝完成后默認(rèn)會在環(huán)境變量 Path 后添加 Go 安裝目錄下的 bin 目錄
C:\Go\bin\,并添加環(huán)境變量 GOROOT,值為 Go 安裝根目錄 C:\Go\ 。
驗證是否安裝成功
在運(yùn)行中輸入 cmd 打開命令行工具,在提示符下輸入 go,檢查是否能看到 Usage 信息。輸入
cd %GOROOT%,看是否能進(jìn)入 Go 安裝目錄。若都成功,說明安裝成功。
不能的話請檢查上述環(huán)境變量 Path 和 GOROOT 的值。若不存在請卸載后重新安裝,存在請重啟計算機(jī)后重試以上步驟。
第三方工具安裝
GVM
gvm是第三方開發(fā)的Go多版本管理工具,類似ruby里面的rvm工具。使用起來相當(dāng)?shù)姆奖?,安裝gvm使用如下命令:
bash (curl -s -S -L )
安裝完成后我們就可以安裝go了:
gvm install go1.4.2
gvm use go1.4.2
也可以使用下面的命令,省去每次調(diào)用gvm use的麻煩: gvm use go1.4.2 --default
執(zhí)行完上面的命令之后GOPATH、GOROOT等環(huán)境變量會自動設(shè)置好,這樣就可以直接使用了。
apt-get
Ubuntu是目前使用最多的Linux桌面系統(tǒng),使用apt-get命令來管理軟件包,我們可以通過下面的命令來安裝Go,為了以后方便,應(yīng)該把
git mercurial 也安裝上:
sudo apt-get install python-software-properties
sudo add-apt-repository ppa:gophers/go
sudo apt-get update
sudo apt-get install golang-stable git-core mercurial
homebrew
homebrew是Mac系統(tǒng)下面目前使用最多的管理軟件的工具,目前已支持Go,可以通過命令直接安裝Go,為了以后方便,應(yīng)該把
git mercurial 也安裝上:
brew update brew upgrade
brew install go
brew install git
brew install mercurial
Go 語言之所以叫 Go,是想表達(dá)這門語言的運(yùn)行速度、開發(fā)速度、學(xué)習(xí)速度(develop)都像 gopher 一樣快。
gopher 是一種生活在加拿大的小動物,Go 語言的吉祥物就是這個小動物, 它的中文名叫作囊地鼠,他們最大的特點就是挖洞速度特別快。
Go 語言吉祥物是才華橫溢的插畫家 Renee French 設(shè)計的,她也是 golang 設(shè)計者之一 Rob Pike 的妻子。
URL中不能顯示地包含空格這已經(jīng)是一個共識,而空格以何種形式存在,在不同的標(biāo)準(zhǔn)中又不完全一致,以致于不同的語言也有了不同的實現(xiàn)。
rfc2396 中明確表示空格應(yīng)該被編碼為 %20 。
而W3C的標(biāo)準(zhǔn)中卻又說空格可以被替換為 + 或者 %20 。
老許當(dāng)場懵逼,空格被替換為 + ,那 + 本身只能被編碼。既然如此,為什么不直接對空格進(jìn)行編碼呢。當(dāng)然這只是老許心中的疑惑,以前的背景我們已經(jīng)無法追溯,已成的事實我們也無法改變。但,空格到底是被替換為 + 還是 20% , + 是否需要被編碼都是現(xiàn)在的我們需要直面的問題。
作為Gopher最先關(guān)注的自然是Go語言本身的實現(xiàn),因此我們首先了解一下Go中常用的三種URL編碼方式的異同。
使用 url.QueryEscape 編碼時,空格被編碼為 + ,而 + 本身被編碼為 %2B 。
使用 url.PathEscape 編碼時,空格被編碼為 20% , 而 + 則未被編碼。
使用 (Values).Encode 方法編碼時,空格被編碼為 + ,而 + 本身被編碼為 %2B ,進(jìn)一步查看 (Values).Encode 方法的源碼知其內(nèi)部仍舊調(diào)用 url.QueryEscape 函數(shù)。而 (Values).Encode 方法和 url.QueryEscape 的區(qū)別在于前者僅編碼query中的key和value,后者會對 = 、 均進(jìn)行編碼。
對我們開發(fā)者而言,這三種編碼方式到底應(yīng)該使用哪一種,請繼續(xù)閱讀后文相信你可以在后面的文章中找到答案。
既然空格和 + 在Go中的URL編碼方式有不同的實現(xiàn),那在其他語言中是否也存在這樣的情況呢,下面以PHP和JS為例。
urlencode
rawurlencode
PHP的 urlencode 和Go的 url.QueryEscape 函數(shù)效果一致,而 rawurlencode 則將空格和 + 均進(jìn)行編碼。
encodeURI
encodeURIComponent
JS的 encodeURI 和Go的 url.PathEscape 函數(shù)效果一致,而 encodeURIComponent 則將空格和 + 均進(jìn)行編碼。
在前文中已經(jīng)總結(jié)了 Go 、 PHP 和 JS 對 +Gopher指北 的編碼操作,下面總結(jié)一下其對應(yīng)的解碼操作是否可行的二維表。
上表中的 YY 和 Y 同含義,老許僅以 YY 表示在Go中推薦使用 url.PathEscape 進(jìn)行編碼,同時在PHP和JS中分別推薦使用 rawurldecode 和 decodeURIComponent 進(jìn)行解碼。
在實際的開發(fā)過程中,Gopher一定會存在需要解碼的場景,此時就需要和URL編碼方進(jìn)行溝通以得到合適的方式解碼。
那有沒有通用的不需要URL編解碼的方式呢?毫無疑問是有的!以 base32 編碼為例,其編碼字符集為 A-Z和數(shù)字2-7 ,此時對值進(jìn)行base32編碼后就無需url編碼了。
最后,衷心希望本文能夠?qū)Ω魑蛔x者有一定的幫助。
參考
go及gomobile的環(huán)境配置這里就不介紹了,直接說aar的生成和使用。
1. 設(shè)置環(huán)境變量GOPATH
GOPATH的值可以有多個,用半角分號間隔,但不能以其結(jié)束,設(shè)置完成后需要重新做 gomobile init 。
2. 在GOPATH里創(chuàng)建src文件夾,用于存放go的包和源文件
3. 在src中創(chuàng)建hello文件夾(go文件的包名)
4. 在hello中創(chuàng)建hello.go文件,并輸入內(nèi)容
5. 編譯
執(zhí)行命令: gomobile bind -target=android hello
會生成一個hello.aar文件
6. 導(dǎo)入到android工程
將hello.aar文件放入工程的libs中,并配置build.gradle
在根結(jié)點加入:
在dependencies結(jié)點下加入依賴:
7. 在Java中測試
運(yùn)行后,結(jié)果會輸出 Hello, Android and Gopher