據報,中國生態環境部副部長趙英民今(27)日於新聞發布會上表示

摘錄自2019年11月27日北京新浪網報導

據報,中國生態環境部副部長趙英民今(27)日於新聞發布會上表示,中國政府一直高度重視支持清潔能源和可再生能源的開發利用,截止到2018年底,中國可再生能源發電裝機達到7.3億千瓦,佔全部電力裝機38.3%。其中,水電裝機3.5億千瓦,風電裝機1.8億千瓦,光伏發電裝機1.7億千瓦,生物質發電裝機1,781萬千瓦,同比增長2.5%、12.4%、34%、20.7%。從數字可以看出來,中國可再生能源的發展非常迅速。

至於2018年可再生能源發電量達到1.9萬億千瓦時,佔全部發電量比重的26.7%。其中,水電1.2萬億千瓦時,風電3,660億千瓦時,光伏發電1,775億千瓦時,生物質發電906億千瓦時。這是一些具體的數字。應該說,隨著可再生能源促進法的深入推進,中國可再生能源的清潔能源替代作用正在日益顯現。

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

【其他文章推薦】

※如何利用一般常見的「L型資料夾」達到廣告宣傳效果?

※哪裡買的到省力省空間,方便攜帶的購物推車?

※飲水機皆有含淨水功能嗎?

※無毒橡膠墊片哪裡買的到?

※錢要花在刀口上,選購隔熱紙前必須知道的九件事 !

中國北部傳出爆發鼠疫後,內蒙中部烏蘭察布市政府為了撲滅老鼠與跳蚤

摘錄自2019年11月26日中央通訊社報導

中國北部傳出爆發鼠疫後,內蒙中部烏蘭察布市政府為了撲滅老鼠與跳蚤,已於上週開始在方圓近200英畝土地噴灑毒藥。

鼠疫桿菌(Yersinia pestis)可由感染病菌的老鼠經跳蚤傳給人類,而通報鼠疫病例已促使整個地區展開撲滅有害生物行動。

本月稍早前,兩個來自相同地區的患者經診斷感染傳染力強的肺鼠疫,並送往北京接受治療。世界衛生組織(WHO)指出,已證實肺鼠疫會於24至72小時致死,這種疾病是「最致命類型」,腺鼠疫的危險性較低。

與此同時,北京與內蒙古衛生健康委員會指出,已針對曾與3名鼠疫患者「密切接觸」人員實施隔離與醫學觀察。北京市衛生健康委員會上週表示,兩名在北京的病患目前「病危」,不過第3個在烏蘭察布市接受治療的患者則病況「穩定」。

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

【其他文章推薦】

※何謂NBR手套,其特性及功能為何?

※選購空壓機需注意八大事項 !

※一條龍物流瞄準台中港倉儲,未來商機 !

※飲用桶裝水到底安不安全? 破解錯誤迷思!

新北市轉軸新北市探針選用參考標準?

美國德州錢伯斯郡阿納瓦克(Anahuac)發生罕見的動物襲擊案

摘錄自2019年11月26日自由時報報導

美國德州錢伯斯郡阿納瓦克(Anahuac)發生罕見的動物襲擊案,幫忙照料一對老夫妻的59歲看護羅林斯(Christine Rollins)於24日上班時間遲遲沒有現身,84歲的屋主等不下去外出查看時,驚見她陳屍在屋外,目前當局認為她遭到多隻野豬攻擊死亡。

據《CNN》報導,錢伯斯郡警長霍桑(Brian Hawthorne)於25日的新聞發布會指出,羅林斯於24日清晨6時至6時30分左右,遭遇不同野豬的襲擊,當時外面天色還很黑。

霍桑指出,這是他從警35年以來所見過最糟糕的事情之一,法醫里弗斯(Selly Rivers)確認現場有多頭豬隻犯案,因為羅林斯身上的咬傷傷口大小不一。

德州公園和野生動物局的資料顯示,成年野豬的體重在100磅至400磅之間(約45.3公斤至181公斤)。霍桑則透露,野豬的確在德州造成問題,但很少出現襲擊人類的案件。

※ 本文與 行政院農業委員會林務局 合作刊登

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

【其他文章推薦】

※幫你考照過關,堆高機裝卸操作教學影片大公開 !

※高效率洗滌塔活性碳設備,能去除多少有機溶劑?

※飲用桶裝水到底安不安全? 破解錯誤迷思!

新北市探針業者,哪家可以精準車製?

※十大封口機人氣排行榜-烘焙必備幫手!

據報,山西長治長子縣嵐水鄉的禾能電廠二號鍋爐近日正式點火運行,這表示該縣每年產生的約20萬噸秸稈將會被利用

摘錄自2019年11月27日北京新浪網報導

據報,山西長治長子縣嵐水鄉的禾能電廠二號鍋爐近日正式點火運行,這表示該縣每年產生的約20萬噸秸稈將會被利用,能夠發電2億度,節約煤炭12萬噸左右,減少二氧化碳排放近20萬噸。

山西禾能發電有限公司副總經理王朝暉介紹,該電廠利用大量農、林廢棄物作為發電燃料,發電燃燒後的草木灰,還可年產1萬噸磷、鉀復合肥,反哺於農田。

至於發電燃料來源,長子是農業大縣,全縣糧食播種面積達54.8萬畝,除玉米秸稈外,所有植物的根、莖、葉都可燃燒發電。

為了便利秸稈收購,禾能電廠提供15台秸稈打捆機,農民們利用機械將田地裡的秸稈粉碎、打捆、裝車後運輸到電廠即可。禾能電廠收購秸稈價格為一噸280元,一年需求約20萬噸秸稈,能給當地民眾增收5,000餘萬元。

此前,當地採取過秸稈還田等諸多措施,但還是有個別村民偷偷焚燒秸稈。禾能電廠建成後,變廢為寶,不但減少環境污染和火災隱患,還為當地人帶來經濟效益。

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

【其他文章推薦】

示波器探測執行效能最佳化的8大秘訣

※專業客製化禮物、贈品設計,辦公用品常見【L夾】搖身一變大受好評!!

冷熱飲水機桌上型飲水機辦公室飲水機直立式飲水機,選購技巧大公開!!

※哪一些是橡膠加工製品?又區分什麼用途

※如何知道自已的電腦cpu支不支持AVX指令集?

※票選推薦煮婦最愛手壓封口機,省荷包不犧牲品質

環保署25日表示,近期發現新竹縣偏鄉遭棄置氫氟酸桶,已針對北中南區的廢氫氟酸再利用機構進行清查

摘錄自2019年11月25日中央通訊社新竹縣報導

環保署25日表示,近期發現新竹縣偏鄉遭棄置氫氟酸桶,已針對北中南區的廢氫氟酸再利用機構進行清查;對於廢液桶的來源,督察總隊已經有所掌握,盡快追緝違規業者。

環保署環境督察總隊北區督察大隊長張乃仁表示,遭惡意棄置的每桶廢液約50公升大小,這次清查行動是先針對北、中、南收受廢氫氟酸量較大的廠家,未來會以普查的方式進行各廠家的清查。

相關資料已交給地方環保局,可依廢棄物清理法開罰,至少新台幣6萬元,並限期改正,未改正得連續處罰。

環保署提醒再利用機構,必須謹慎處理收受的廢氫氟酸,並做成完整的紀錄以備查核。三區環境督察大隊的追查行動會持續執行,希望導正事業廢棄物再利用等去化市場機制。

 

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

【其他文章推薦】

※各大品牌中古空壓機買賣情報站

※【找工作】徵求中部倉庫堆高機人員

※選用哪種桶裝水,外宿露營超方便?

連續封口機購物網-不怕你比價,就怕你買貴!

塑膠射出成型不良品原因及改善對策 !

台中市議員陳廷秀26日於市議會都發建設水利委員會業務質詢時指出

摘錄自2019年11月26日中時電子報報導

台中市議員陳廷秀26日於市議會都發建設水利委員會業務質詢時指出,台中市污水接管率目前接管戶大約是17萬戶左右,未來要增加到35萬戶,台中市的「污水下水道倍增計畫」要在短短6、7年內倍增,全力推動污水下水道建設。

陳廷秀指出,台灣下水道的污水下水道是將各類廢水匯集至污水處理廠,經去污、消毒等淨化程序後,回歸河川、海洋或成為可永續循環的再生水資源,為城市永續發展的基礎建設,及衡量都市現代化程度的重要指標。污水下水道建設不僅能改善都市環境衛生、提高人民生活品質,更可避免污染我們賴以為生的水源。希望透過提早指定新建物污水排放方向,從源頭管制,提升污水用戶接管效率。

範水利局長范世億指出,地區要有專用排放污水的排放管,確定污水排放去處,除了原台中縣用戶接管率偏低、水資中心用地取得不易、臺中市人口全國第二高需接戶數較其他縣市多,最主要還是後巷違建阻礙用戶接管施工空間;未來應優先針對防火間隔(巷)既存違建,倘妨礙用戶接管施工,依公共衛生及安全查報拆除,以加速提升用戶接管率。

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

【其他文章推薦】

噴霧洗滌塔實際應用案例分享

※想知道CNC 自動車床與CNC車床有何區別??

示波器鮮為人知的使用技巧?

※客製專屬滑鼠墊、可愛造型L夾L型資料夾、透明證件套、手提袋,專業印刷設計廠商!  

※使用真空封口機常見問題?

高雄蓮池潭舊城國小旁整排樹被砍除,引發議論,高雄愛樹人認為蓮池潭是高雄重要觀光風景區

摘錄自2019年11月28日自由時報報導

高雄蓮池潭舊城國小旁整排樹被砍除,引發議論,高雄愛樹人認為蓮池潭是高雄重要觀光風景區,就被這樣亂砍,痛批韓國瑜愛樹承諾再跳票。高市觀光局澄清,是因為發生褐根病,也就是植物的黑死病,此病無法治療且會擴散到整個園區,遂進行必要處理、確保其他樹木不被傳染。

高雄愛樹人根據民眾提供資料,質疑高雄蓮池潭舊城國小旁,整排樹被砍除,就算是退一百步想,樹如果有病,難道不能用土壤改良、殺菌劑等方法醫治?

觀光局澄清,這是55棵褐根病的植株,也就是植物的「黑死病」,無法治療而且會擴散到整個園區,必須要整個移走焚燒,連土壤都要翻土消毒,預計109年1月10日會完成全部移除和消毒工作。

觀光局強調,整治方式是找植物專家和専業協會會勘的結果,於3月6日就辦地方說明會說明處理方式,施作前於11月20日也有發新聞稿。

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

【其他文章推薦】

飲水機設備有哪些?

※如何選購橡膠製品橡膠按鍵規格有哪些?該如何應用在商品上?  

AVX DistributorAVX TPSAVX鉭質電容器 規格有哪些?各別作用在於?

nbr乳膠手套可適用在什麼環境?

※空壓機何時可換油? 空壓機保養的正確觀念與維護 !

真空封口機該不該買?使用心得分享

台南地區黑琵數量預計12月逾2500隻,南市生態保育學會將舉辦黑面琵鷺模型展

摘錄自2019年11月28日自由時報報導

台南地區黑琵數量預計12月逾2500隻,南市生態保育學會將舉辦黑面琵鷺模型展、黑琵親子輕旅行等活動,即日起受理報名。

南市生態保育學會榮譽理事長邱仁武指出,全球族群數量已經超過4000隻,目前在台度冬數量約佔60%,大多分佈在台南、嘉義沿海一帶的濕地、魚塭及廢棄鹽田內。

今年1月下旬「2019黑面琵鷺全球同步普查」全球黑面琵鷺族群總數達4463隻,是全球遷徙鳥類保育的一個重要里程碑。其中在台灣調查到2407隻,擁有超過半數的度冬族群,相較去年有明顯增加,但仍少於2017年的2601隻,顯示台灣的黑琵保育工作仍需持續關心。

邱仁武說,台灣是全球增加最多的國家,而且連續四年突破兩千隻等殊榮,顯示台灣的濕地環境並未惡化,能供應黑面琵鷺良好的度冬條件。

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

【其他文章推薦】

※想知道台中食品倉有哪些?  

堆高機基本駕駛指導教學(影片)

廢氣洗滌塔,叫得動, 找得到的專業廠商‎

※市面十大品牌封口機!該如何選購?

塑膠射出成型加工商品有哪些?

go中的數據結構-接口interface

1. 接口的基本使用

  golang中的interface本身也是一種類型,它代表的是一個方法的集合。任何類型只要實現了接口中聲明的所有方法,那麼該類就實現了該接口。與其他語言不同,golang並不需要顯式聲明類型實現了某個接口,而是由編譯器和runtime進行檢查。

聲明

 1 type 接口名 interface{
 2     方法1
 3     方法2
 4     ...
 5    方法n 
 6 }
 7 type 接口名 interface {
 8     已聲明接口名1
 9     ...
10     已聲明接口名n
11 }
12 type iface interface{
13     tab *itab
14     data unsafe.Pointer
15 }

  接口自身也是一種結構類型,只是編譯器對其做了很多限制:

  • 不能有字段
  • 不能定義自己的方法
  • 只能聲明方法,不能實現
  • 可嵌入其他接口類型
 1 package main
 2 
 3     import (
 4         "fmt"
 5     )
 6 
 7     // 定義一個接口
 8     type People interface {
 9         ReturnName() string
10     }
11 
12     // 定義一個結構體
13     type Student struct {
14         Name string
15     }
16 
17     // 定義結構體的一個方法。
18     // 突然發現這個方法同接口People的所有方法(就一個),此時可直接認為結構體Student實現了接口People
19     func (s Student) ReturnName() string {
20         return s.Name
21     }
22 
23     func main() {
24         cbs := Student{Name:"小明"}
25 
26         var a People
27         // 因為Students實現了接口所以直接賦值沒問題
28         // 如果沒實現會報錯:cannot use cbs (type Student) as type People in assignment:Student does not implement People (missing ReturnName method)
29         a = cbs       
30         name := a.ReturnName() 
31         fmt.Println(name) // 輸出"小明"
32     }

  如果一個接口不包含任何方法,那麼它就是一個空接口(empty interface),所有類型都符合empty interface的定義,因此任何類型都能轉換成empty interface。

  接口的值簡單來說,是由兩部分組成的,就是類型和數據,判斷兩個接口是相等,就是看他們的這兩部分是否相等;另外類型和數據都為nil才代表接口是nil。

1 var a interface{} 
2 var b interface{} = (*int)(nil)
3 fmt.Println(a == nil, b == nil) //true false

2. 接口嵌套

  像匿名字段那樣嵌入其他接口。目標類型方法集中必須擁有包含嵌入接口方法在內的全部方法才算實現了該接口。嵌入其他接口類型相當於將其聲明的方法集中導入。這就要求不能有同名方法,不能嵌入自身或循環嵌入。

 1 type stringer interfaceP{
 2      string() string
 3 }
 4 
 5 type tester interface {
 6     stringer
 7     test()
 8 }    
 9 
10 type data struct{}
11 
12 func (*data) test() {}
13 
14 func (data) string () string {
15     return ""
16 }
17 
18 func main() {
19     var d data 
20     var t tester = &d 
21     t.test()
22     println(t.string())
23 }

  超集接口變量可隱式轉換為子集,反過來不行。

3. 接口的實現

golang的接口檢測既有靜態部分,也有動態部分。

  • 靜態部分
    對於具體類型(concrete type,包括自定義類型) -> interface,編譯器生成對應的itab放到ELF的.rodata段,後續要獲取itab時,直接把指針指向存在.rodata的相關偏移地址即可。具體實現可以看golang的提交日誌CL 20901、CL 20902。
    對於interface->具體類型(concrete type,包括自定義類型),編譯器提取相關字段進行比較,並生成值

  • 動態部分
    在runtime中會有一個全局的hash表,記錄了相應type->interface類型轉換的itab,進行轉換時候,先到hash表中查,如果有就返回成功;如果沒有,就檢查這兩種類型能否轉換,能就插入到hash表中返回成功,不能就返回失敗。注意這裏的hash表不是go中的map,而是一個最原始的使用數組的hash表,使用開放地址法來解決衝突。主要是interface <-> interface(接口賦值給接口、接口轉換成另一接口)使用到動態生產itab

interface的結構如下:

接口類型的結構interfacetype

 1 type interfacetype struct {
 2     typ     _type   
 3     pkgpath name   //記錄定義接口的包名
 4     mhdr    []imethod  //一個imethod切片,記錄接口中定義的那些函數。
 5 }
 6 
 7 // imethod表示接口類型上的方法
 8 type imethod struct {
 9     name nameOff // name of method
10     typ  typeOff // .(*FuncType) underneath
11 }

  nameOff 和 typeOff 類型是 int32 ,這兩個值是鏈接器負責嵌入的,相對於可執行文件的元信息的偏移量。元信息會在運行期,加載到 runtime.moduledata 結構體中。

4. 接口值的結構iface和eface

 為了性能,golang專門分了兩種interface,eface和iface,eface就是空接口,iface就是有方法的接口。

 1 type iface struct { 
 2     tab  *itab
 3     data unsafe.Pointer
 4 }
 5 
 6 type eface struct {
 7     _type *_type
 8     data  unsafe.Pointer
 9 }
10 
11 type itab struct {
12     inter *interfacetype   //inter接口類型
13     _type *_type   //_type數據類型
14     hash  uint32  //_type.hash的副本。用於類型開關。 hash哈希的方法
15     _     [4]byte
16     fun   [1]uintptr  // 大小可變。 fun [0] == 0表示_type未實現inter。 fun函數地址佔位符
17 }

  iface結構體中的data是用來存儲實際數據的,runtime會申請一塊新的內存,把數據考到那,然後data指向這塊新的內存。

itab中的hash方法拷貝自_type.hash;fun是一個大小為1的uintptr數組,當fun[0]為0時,說明_type並沒有實現該接口,當有實現接口時,fun存放了第一個接口方法的地址,其他方法一次往下存放,這裏就簡單用空間換時間,其實方法都在_type字段中能找到,實際在這記錄下,每次調用的時候就不用動態查找了

4.1 全局的itab table

iface.go:

1 const itabInitSize = 512
2 
3 // 注意:如果更改這些字段,請在itabAdd的mallocgc調用中更改公式。
4 type itabTableType struct {
5     size    uintptr             // 條目數組的長度。始終為2的冪。
6     count   uintptr             // 當前已填寫的條目數。
7     entries [itabInitSize]*itab // really [size] large
8 }

  可以看出這個全局的itabTable是用數組在存儲的,size記錄數組的大小,總是2的次冪。count記錄數組中已使用了多少。entries是一個*itab數組,初始大小是512。

5. 接口類型轉換

  把一個具體的值,賦值給接口,會調用conv系列函數,例如空接口調用convT2E系列、非空接口調用convT2I系列,為了性能考慮,很多特例的convT2I64、convT2Estring諸如此類,避免了typedmemmove的調用。

 1 func convT2E(t *_type, elem unsafe.Pointer) (e eface) {
 2     if raceenabled {
 3         raceReadObjectPC(t, elem, getcallerpc(), funcPC(convT2E))
 4     }
 5     if msanenabled {
 6         msanread(elem, t.size)
 7     }
 8     x := mallocgc(t.size, t, true)
 9     // TODO: 我們分配一個清零的對象只是為了用實際數據覆蓋它。
10     //確定如何避免歸零。同樣在下面的convT2Eslice,convT2I,convT2Islice中。
11     typedmemmove(t, x, elem)
12     e._type = t
13     e.data = x
14     return
15 }
16 
17 func convT2I(tab *itab, elem unsafe.Pointer) (i iface) {
18     t := tab._type
19     if raceenabled {
20         raceReadObjectPC(t, elem, getcallerpc(), funcPC(convT2I))
21     }
22     if msanenabled {
23         msanread(elem, t.size)
24     }
25     x := mallocgc(t.size, t, true)
26     typedmemmove(t, x, elem)
27     i.tab = tab
28     i.data = x
29     return
30 }
31 
32 func convT2I16(tab *itab, val uint16) (i iface) {
33     t := tab._type
34     var x unsafe.Pointer
35     if val == 0 {
36         x = unsafe.Pointer(&zeroVal[0])
37     } else {
38         x = mallocgc(2, t, false)
39         *(*uint16)(x) = val
40     }
41     i.tab = tab
42     i.data = x
43     return
44 }
45 
46 func convI2I(inter *interfacetype, i iface) (r iface) {
47     tab := i.tab
48     if tab == nil {
49         return
50     }
51     if tab.inter == inter {
52         r.tab = tab
53         r.data = i.data
54         return
55     }
56     r.tab = getitab(inter, tab._type, false)
57     r.data = i.data
58     return
59 }

  可以看出:

  • 具體類型轉空接口,_type字段直接複製源的type;mallocgc一個新內存,把值複製過去,data再指向這塊內存。
  • 具體類型轉非空接口,入參tab是編譯器生成的填進去的,接口指向同一個入參tab指向的itab;mallocgc一個新內存,把值複製過去,data再指向這塊內存。
  • 對於接口轉接口,itab是調用getitab函數去獲取的,而不是編譯器傳入的。

對於那些特定類型的值,如果是零值,那麼不會mallocgc一塊新內存,data會指向zeroVal[0]

5.1 接口轉接口

 1 func assertI2I2(inter *interfacetype, i iface) (r iface, b bool) {
 2     tab := i.tab
 3     if tab == nil {
 4         return
 5     }
 6     if tab.inter != inter {
 7         tab = getitab(inter, tab._type, true)
 8         if tab == nil {
 9             return
10         }
11     }
12     r.tab = tab
13     r.data = i.data
14     b = true
15     return
16 }
17 
18 func assertE2I(inter *interfacetype, e eface) (r iface) {
19     t := e._type
20     if t == nil {
21         // 顯式轉換需要非nil接口值。
22         panic(&TypeAssertionError{nil, nil, &inter.typ, ""})
23     }
24     r.tab = getitab(inter, t, false)
25     r.data = e.data
26     return
27 }
28 
29 func assertE2I2(inter *interfacetype, e eface) (r iface, b bool) {
30     t := e._type
31     if t == nil {
32         return
33     }
34     tab := getitab(inter, t, true)
35     if tab == nil {
36         return
37     }
38     r.tab = tab
39     r.data = e.data
40     b = true
41     return
42 }

  我們看到有兩種用法:

  • 返回值是一個時,不能轉換就panic。
  • 返回值是兩個時,第二個返回值標記能否轉換成功

  此外,data複製的是指針,不會完整拷貝值。每次都malloc一塊內存,那麼性能會很差,因此,對於一些類型,golang的編譯器做了優化。

5.2 接口轉具體類型

  接口判斷是否轉換成具體類型,是編譯器生成好的代碼去做的。我們看個empty interface轉換成具體類型的例子:

 1 var EFace interface{}
 2 var j int
 3 
 4 func F4(i int) int{
 5     EFace = I
 6     j = EFace.(int)
 7     return j
 8 }
 9 
10 func main() {
11     F4(10)
12 }

反彙編:

  go build -gcflags ‘-N -l’ -o tmp build.go

  go tool objdump -s “main.F4” tmp

  可以看彙編代碼:

1 MOVQ main.EFace(SB), CX       //CX = EFace.typ
2 LEAQ type.*+60128(SB), DX    //DX = &type.int
3 CMPQ DX, CX.                         //if DX == AX

  可以看到empty interface轉具體類型,是編譯器生成好對比代碼,比較具體類型和空接口是不是同一個type,而不是調用某個函數在運行時動態對比。

5.3 非空接口類型轉換

 1 var tf Tester
 2 var t testStruct
 3 
 4 func F4() int{
 5     t := tf.(testStruct)
 6     return t.i
 7 }
 8 
 9 func main() {
10     F4()
11 }
12 //反彙編
13 MOVQ main.tf(SB), CX   // CX = tf.tab(.inter.typ)
14 LEAQ go.itab.main.testStruct,main.Tester(SB), DX // DX = <testStruct,Tester>對應的&itab(.inter.typ)
15 CMPQ DX, CX //

  可以看到,非空接口轉具體類型,也是編譯器生成的代碼,比較是不是同一個itab,而不是調用某個函數在運行時動態對比。

6. 獲取itab的流程

  golang interface的核心邏輯就在這,在get的時候,不僅僅會從itabTalbe中查找,還可能會創建插入,itabTable使用容量超過75%還會擴容。看下代碼:

 1 func getitab(inter *interfacetype, typ *_type, canfail bool) *itab {
 2     if len(inter.mhdr) == 0 {
 3         throw("internal error - misuse of itab")
 4     }
 5 
 6     // 簡單的情況
 7     if typ.tflag&tflagUncommon == 0 {
 8         if canfail {
 9             return nil
10         }
11         name := inter.typ.nameOff(inter.mhdr[0].name)
12         panic(&TypeAssertionError{nil, typ, &inter.typ, name.name()})
13     }
14 
15     var m *itab
16 
17     //首先,查看現有表以查看是否可以找到所需的itab。
18     //這是迄今為止最常見的情況,因此請不要使用鎖。
19     //使用atomic確保我們看到該線程完成的所有先前寫入更新itabTable字段(在itabAdd中使用atomic.Storep)。
20     t := (*itabTableType)(atomic.Loadp(unsafe.Pointer(&itabTable)))
21     if m = t.find(inter, typ); m != nil {
22         goto finish
23     }
24 
25     // 未找到。抓住鎖,然後重試。
26     lock(&itabLock)
27     if m = itabTable.find(inter, typ); m != nil {
28         unlock(&itabLock)
29         goto finish
30     }
31 
32     // 條目尚不存在。進行新輸入並添加。
33     m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*sys.PtrSize, 0, &memstats.other_sys))
34     m.inter = inter
35     m._type = typ
36     m.init()
37     itabAdd(m)
38     unlock(&itabLock)
39 finish:
40     if m.fun[0] != 0 {
41         return m
42     }
43     if canfail {
44         return nil
45     }
46     //僅當轉換時才會發生,使用ok形式已經完成一次,我們得到了一個緩存的否定結果。
47     //緩存的結果不會記錄,缺少接口函數,因此初始化再次獲取itab,以獲取缺少的函數名稱。
48     panic(&TypeAssertionError{concrete: typ, asserted: &inter.typ, missingMethod: m.init()})
49 }

  流程如下:

  • 先用t保存全局itabTable的地址,然後使用t.find去查找,這樣是為了防止查找過程中,itabTable被替換導致查找錯誤。
  • 如果沒找到,那麼就會上鎖,然後使用itabTable.find去查找,這樣是因為在第一步查找的同時,另外一個協程寫入,可能導致實際存在卻查找不到,這時上鎖避免itabTable被替換,然後直接在itaTable中查找。
  • 再沒找到,說明確實沒有,那麼就根據接口類型、數據類型,去生成一個新的itab,然後插入到itabTable中,這裏可能會導致hash表擴容,如果數據類型並沒有實現接口,那麼根據調用方式,該報錯報錯,該panic panic。

  這裏我們可以看到申請新的itab空間時,內存空間的大小是unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*sys.PtrSize,參照前面接受的結構,len(inter.mhdr)就是接口定義的方法數量,因為字段fun是一個大小為1的數組,所以len(inter.mhdr)-1,在fun字段下面其實隱藏了其他方法接口地址。

6.1 在itabTable中查找itab find

 1 func itabHashFunc(inter *interfacetype, typ *_type) uintptr {
 2     // 編譯器為我們提供了一些很好的哈希碼。
 3     return uintptr(inter.typ.hash ^ typ.hash)
 4 }
 5 
 6    // find在t中找到給定的接口/類型對。
 7    // 如果不存在給定的接口/類型對,則返回nil。
 8 func (t *itabTableType) find(inter *interfacetype, typ *_type) *itab {
 9     // 使用二次探測實現。
10      //探測順序為h(i)= h0 + i *(i + 1)/ 2 mod 2 ^ k。
11      //我們保證使用此探測序列擊中所有表條目。
12     mask := t.size - 1
13     h := itabHashFunc(inter, typ) & mask
14     for i := uintptr(1); ; i++ {
15         p := (**itab)(add(unsafe.Pointer(&t.entries), h*sys.PtrSize))
16         // 在這裏使用atomic read,所以如果我們看到m!= nil,我們也會看到m字段的初始化。
17         // m := *p
18         m := (*itab)(atomic.Loadp(unsafe.Pointer(p)))
19         if m == nil {
20             return nil
21         }
22         if m.inter == inter && m._type == typ {
23             return m
24         }
25         h += I
26         h &= mask
27     }
28 }

  從註釋可以看到,golang使用的開放地址探測法,用的是公式h(i) = h0 + i*(i+1)/2 mod 2^k,h0是根據接口類型和數據類型的hash字段算出來的。以前的版本是額外使用一個link字段去連到下一個slot,那樣會有額外的存儲,性能也會差寫,在1.11中我們看到有了改進。

6.2 檢查並生成itab init

 1 // init用所有代碼指針填充m.fun數組m.inter / m._type對。 如果該類型未實現該接口,將m.fun [0]設置為0,並返回缺少的接口函數的名稱。
 2 //可以在同一m上多次調用此函數,即使同時調用也可以。
 3 func (m *itab) init() string {
 4     inter := m.inter
 5     typ := m._type
 6     x := typ.uncommon()
 7 
 8     // inter和typ都有按名稱排序的方法,
 9      //並且接口名稱是唯一的,
10      //因此可以在鎖定步驟中對兩者進行迭代;
11      //循環是O(ni + nt)而不是O(ni * nt)。
12     ni := len(inter.mhdr)
13     nt := int(x.mcount)
14     xmhdr := (*[1 << 16]method)(add(unsafe.Pointer(x), uintptr(x.moff)))[:nt:nt]
15     j := 0
16 imethods:
17     for k := 0; k < ni; k++ {
18         i := &inter.mhdr[k]
19         itype := inter.typ.typeOff(i.ityp)
20         name := inter.typ.nameOff(i.name)
21         iname := name.name()
22         ipkg := name.pkgPath()
23         if ipkg == "" {
24             ipkg = inter.pkgpath.name()
25         }
26         for ; j < nt; j++ {
27             t := &xmhdr[j]
28             tname := typ.nameOff(t.name)
29             if typ.typeOff(t.mtyp) == itype && tname.name() == iname {
30                 pkgPath := tname.pkgPath()
31                 if pkgPath == "" {
32                     pkgPath = typ.nameOff(x.pkgpath).name()
33                 }
34                 if tname.isExported() || pkgPath == ipkg {
35                     if m != nil {
36                         ifn := typ.textOff(t.ifn)
37                         *(*unsafe.Pointer)(add(unsafe.Pointer(&m.fun[0]), uintptr(k)*sys.PtrSize)) = ifn
38                     }
39                     continue imethods
40                 }
41             }
42         }
43         // didn't find method
44         m.fun[0] = 0
45         return iname
46     }
47     m.hash = typ.hash
48     return ""
49 }

  這個方法會檢查interface和type的方法是否匹配,即type有沒有實現interface。假如interface有n中方法,type有m中方法,那麼匹配的時間複雜度是O(n x m),由於interface、type的方法都按字典序排,所以O(n+m)的時間複雜度可以匹配完。在檢測的過程中,匹配上了,依次往fun字段寫入type中對應方法的地址。如果有一個方法沒有匹配上,那麼就設置fun[0]為0,在外層調用會檢查fun[0]==0,即type並沒有實現interface

  這裏我們還可以看到golang中continue的特殊用法,要直接continue到外層的循環中,那麼就在那一層的循環上加個標籤,然後continue 標籤

6.3 把itab插入到itabTable中 itabAdd

 1 // itabAdd將給定的itab添加到itab哈希表中。
 2 //必須保持itabLock。
 3 func itabAdd(m *itab) {
 4     // 設置了mallocing時,錯誤可能導致調用此方法,通常是因為這是在恐慌時調用的。
 5     //可靠地崩潰,而不是僅在需要增長時崩潰哈希表。
 6     if getg().m.mallocing != 0 {
 7         throw("malloc deadlock")
 8     }
 9 
10     t := itabTable
11     if t.count >= 3*(t.size/4) { // 75% 負載係數
12         // 增長哈希表。
13         // t2 = new(itabTableType)+一些其他條目我們撒謊並告訴malloc我們想要無指針的內存,因為所有指向的值都不在堆中。
14         t2 := (*itabTableType)(mallocgc((2+2*t.size)*sys.PtrSize, nil, true))
15         t2.size = t.size * 2
16 
17         // 複製條目。
18         //注意:在複製時,其他線程可能會尋找itab和找不到它。沒關係,他們將嘗試獲取Itab鎖,因此請等到複製完成。
19         if t2.count != t.count {
20             throw("mismatched count during itab table copy")
21         }
22         // 發布新的哈希表。使用原子寫入:請參閱getitab中的註釋。
23         atomicstorep(unsafe.Pointer(&itabTable), unsafe.Pointer(t2))
24         // 採用新表作為我們自己的表。
25         t = itabTable
26         // 注意:舊錶可以在此處進行GC處理。
27     }
28     t.add(m)
29 }
30 // add將給定的itab添加到itab表t中。
31 //必須保持itabLock。
32 func (t *itabTableType) add(m *itab) {
33     //請參閱註釋中的有關探查序列的註釋。
34     //將新的itab插入探針序列的第一個空位。
35     mask := t.size - 1
36     h := itabHashFunc(m.inter, m._type) & mask
37     for i := uintptr(1); ; i++ {
38         p := (**itab)(add(unsafe.Pointer(&t.entries), h*sys.PtrSize))
39         m2 := *p
40         if m2 == m {
41             //給定的itab可以在多個模塊中使用並且由於全局符號解析的工作方式,
42             //指向itab的代碼可能已經插入了全局“哈希”。
43             return
44         }
45         if m2 == nil {
46             // 在這裏使用原子寫,所以如果讀者看到m,它也會看到正確初始化的m字段。
47             // NoWB正常,因為m不在堆內存中。
48             // *p = m
49             atomic.StorepNoWB(unsafe.Pointer(p), unsafe.Pointer(m))
50             t.count++
51             return
52         }
53         h += I
54         h &= mask
55     }
56 }

  可以看到,當hash表使用達到75%或以上時,就會進行擴容,容量是原來的2倍,申請完空間,就會把老表中的數據插入到新的hash表中。然後使itabTable指向新的表,最後把新的itab插入到新表中。

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

【其他文章推薦】

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

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

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

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

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

※試算大陸海運運費!

git回退之git reset

參考

https://git-scm.com/book/zh/v2/Git-%E5%B7%A5%E5%85%B7-%E9%87%8D%E7%BD%AE%E6%8F%AD%E5%AF%86

https://git-scm.com/book/en/v2/Git-Tools-Reset-Demystified

https://git-scm.com/docs/git-reset

https://www.liaoxuefeng.com/wiki/896043488029600/897013573512192

從歷史記錄中刪除 參考 https://www.cnblogs.com/studywithallofyou/p/11772684.html https://www.cnblogs.com/studywithallofyou/p/11772844.html

前言

在使用git的時候,我們一般提倡是不允許回滾。對於問題的追蹤和項目的發展歷程而言歷史記錄都是有用的。並且為了節省一點存儲空間而丟失寶貴的代碼信息是不值當的。但是我們開發中,肯定會遇到特殊情況需要回退。比如確實操作錯了一步歷史提交,導致倉庫混亂污染或是內容丟失,我們需要回退到乾淨的一次提交,重新操作。

在git等所有的版本管理軟件中,刪除操作,只是增加一次記錄,內容並不會被刪掉,我們可以大膽的操作,這也符合版本倉庫的邏輯。只有特殊情況才需要真正的刪除,做這種操作的時候需要特別注意,因為一旦失誤,無法挽回。

工作流程

要想理解git reset,那麼就要搞清楚git倉庫管理流程:

我們修改完內容后,這些記錄單單是保存在我們本地目錄下,也就是工作目錄。如果丟了,就是丟了,無法找回。與普通磁盤上的文件一樣,除非到回收站找回。

這時,如果運行了add,那麼內容就被記錄到本地的暫存倉庫,也就是index。這時如果刪除文件,在暫存區的內容還存在,並沒有丟失。

如下圖,我們創建了一個文件4,這時文件4在本地。我們add到暫存倉庫,然後刪掉文件4,我們發現又多了一條記錄刪除記錄,原來的4還存在,我們可以commit。原來的文件4並沒有丟失。

運行commit之後,修改的內容就被保存到了倉庫,修改了HEAD(HEAD就是指向當前倉庫在哪一個提交歷史,不特殊修改,都指向最新的一次提交)。這時運行git status發現目錄是乾淨的。git裏面需要提交倉庫,暫存倉庫和本地目錄內容都完全一致,才是乾淨的,任何一個不一致都會有不同的提示。

比如上面的圖,提交的倉庫中沒有文件4,暫存倉庫中增加了4,所以显示綠色的提示內容,add了文件但是還沒有commit。但是本次又刪除了文件,本地目錄中的內容與暫存倉庫也不一樣,所以提示紅色的內容,因為算是警告,沒有add的內容是會丟失的。

修改

add

commit

git reset的三個選擇

理解了上面的流程,就可以理解git reset了。git reset就是分別逆向操作,也就是把HEAD(提交的倉庫)回退到一個指定歷史,把HEAD、index(暫存倉庫)都回退到指定歷史,把HEAD、index和本地目錄的內容都回退到一個指定歷史。

git reset –soft就是把HEAD回退到指定歷史。運行后結果如下

也就相當於我們add了修改的文件,本地目錄和暫存倉庫都已經一致了,就等待commit。如果我們commit,可以再次填寫commit記錄,也就實現了git commit –amend的功能。

git reset –mixed就是把HEAD和index都回退到指定的歷史。這個也是運行git reset不加參數時的默認規則。運行結果如下

相當於我們僅僅修改了文件,還沒做任何處理。

git reset –hard這個是把HEAD、index和本地目錄的內容都回退到指定的歷史記錄。做這一步操作的時候一定要小心,最好把所有的內容都提交,並且push到遠程或是拷貝一份。因為這個操作會重置本地的內容到一個指定歷史。

我們先運行git log,看到有三個提交歷史

運行git reset –hard HEAD~

我們發現倉庫是乾淨的,並且原來的3文件沒了,運行git log參看

提交的歷史記錄也沒有了。

這次是真的回退到了指定歷史,所有的記錄都不見了,我們可以開心的(真的嗎?)在原來一個乾淨的分支上繼續寫代碼了。

git reset –hard的後悔葯

世上有沒有後悔葯我不知道,但是git作為一個先進的分佈式管理器,卻有無限可能。如果你一不小心,腦袋發熱,運行了git reset –hard,但是發現不是你想要的,原來的記錄也沒了。怎麼辦?大腦瞬間充血,一片空白。不要慌,首先冷靜下來,然後運行git reflog,這個命令是告訴你你的每一次對倉庫操作的歷史記錄,如下

看一下最上面的幾條,第一條告訴你當前在e86d948這個提交記錄,通過reset切換過來的。對照上面的git log,可以發現這個是第二個提交歷史,也就是我們git reset –hard HEAD~的時候回退的分支。第二條記錄告訴我們當前是fc9fbc6分支,通過reset回退到這個分支的,我們可以參考上面的git log記錄,這個就是我們想要回去的分支。好了,有了記錄的哈希值,我們只需再運行一次git reset –hard,如下

查看一下git log

回來了。

git不會刪除任何已經提交到版本庫的內容,除非你非要專門特殊這樣做,並且為了再給你一次機會,像git reset這樣,就算你明確說明不要了,它也不會立馬刪除,除非超過一定時間或是你主動運行git gc等操作,把無用的,沒有關聯的內容刪掉。不然,那條記錄還是在本地倉庫,只不過它沒有被載入歷史的長河中。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

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

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

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

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

※專營大陸快遞台灣服務

台灣快遞大陸的貨運公司有哪些呢?