Medium高贊系列,如何正確的在Stack Overflow提問

在我們寫程序的時候,經常會遇到各色各樣的問題,在國內,小夥伴們經常去知乎、CSDN、博客園、思否、安卓巴士等地方提問並獲得答案。

這些地方彙集了很多優秀的、愛分享的國內資源。小編比較自豪的一件事情就是:當初學習dubbo期間,因為一個數據關閉錯誤一直找不到正確的解決方式,就順手把自己解決問題的步驟寫下,並附上參考資料中的方法,算是把那類問題做了一個增強版的總結,沒想過幾個月後,有位粉絲專門找上來感謝我,幫他解決了疑惑。

技術人,就是那麼容易得到滿足。得到別人的一句謝謝,開心的像個傻瓜。前行路上,愛分享、把資源提供給更多的人,是最開心和愉快的事情。

現在是移動互聯網的時代,倘若我們能鏈接到更多的人,倘若我們來連接的不僅僅有國內,還有國外,那豈不是更好?那麼如何在國外得到自己想要的答案?我們不妨去Stack Overflow這個平台去試試,優秀的問答平台,你們懂的。

但是提問也是一門藝術,所以趕緊來看看他們的總結,助你更好地在平台上提問。

原文地址:https://medium.com/better-programming/how-to-ask-a-question-that-gets-answered-on-stack-overflow-45f87f1a2fef

作者:Nabil Nalakath

時間:2019.11.12

當有人告訴我他們在開發中遇到的問題時,在大多數情況下,我的直接答覆是:“您在Stack Overflow上發現了什麼?”

但是,很多開發者會給出奇怪的答案,例如:“我不知道如何使用它,我因提出較差的問題而被禁止,人們總是不贊成我的帖子,或者給我有關如何提問的鏈接,”等。

Stack Overflow是互聯網上最有用,訪問最多的網站之一,但它也是互聯網上最殘酷的平台之一。

如果您犯了一個錯誤或提出了一個愚蠢的問題,人們不會理財你,這就是該平台自成立以來一直保持其標準的方式。因此,別指望有什麼收穫。

相反,我們需要習慣它並改變提問的方式。夠了,讓我們來看看您在提問時要注意哪些重要事項。

發布問題時要注意的事項

  1. 標題要具體(不要在標題中張貼整個問題或廣泛的問題)

  2. 使用正確的標籤(這對於快速獲得答案非常重要)

  3. 張貼代碼的相關部分,並在問題編輯器中使用代碼標籤將其格式化為代碼(如果代碼不是整齊的,大多數人都不會去回答)

  4. 如果您要解釋運行時出現的問題,請嘗試發布屏幕截圖

  5. 如果有日誌的話,發布正確的錯誤日誌(特別是在應用崩潰的情況下)

  6. 如果您的部分輸出沒有錯誤,並且想要對輸出進行特定的修改,而且您似乎無法弄清楚如何,將問題分為兩部分,在問題中清楚提及:

  • 你現在有什麼
  • 你需要達到的目標
  1. 如果與UI相關,請發布線框屏幕截圖,如果不可用,請嘗試在現有的UI屏幕截圖中使用諸如Paint之類的簡單工具標記所需的內容或您要進行的更改

  2. 如果您認為版本代碼可能與解決問題有關,請發布版本代碼(例如:果問題僅在舊版本的PHP或Android中發生,而在新版本中則沒有)

發布時要避免的錯誤

  • 切勿發布代碼中包含品牌名稱或公司名稱的部分

  • 裁剪屏幕截圖以僅显示相關內容

  • 如果代碼包含部分內容,例如鍵或密碼(例如PHP郵件程序代碼中的电子郵件密碼),請始終用****或特殊字符替換密碼字段

  • 不要發布自己創建的特殊算法或應用引擎代碼,除非您不介意其他人使用它或將其開源

壞問題和好問題

讓我們看一下146票贊成的這篇文章:

地址:https://stackoverflow.com/questions/3905734/how-to-send-100-000-emails-weekly?source=post_page-----45f87f1a2fef----------------------

如您在本示例中看到的,已發布的問題不是特定問題。如果您要這樣的教程類型答案,那麼Stack Overflow並不是一個好地方。

以該示例為例,在這種情況下,用戶要求每周使用PHP向100,000個用戶發送一封电子郵件。但問題並沒有显示用戶方面的任何努力。

到目前為止,還沒有提及用戶已經嘗試了什麼或他們面臨的任何特定錯誤。這是不能回答問題的完美範例。

另外,這裡有一些很好的示例問題供您參考。

地址:https://stackoverflow.com/questions/11227809/why-is-processing-a-sorted-array-faster-than-processing-an-unsorted-array?source=post_page-----45f87f1a2fef----------------------

地址:https://stackoverflow.com/questions/51096796/how-to-enable-horizontal-scrolling-for-chart-js-in-ionic?source=post_page-----45f87f1a2fef----------------------

地址:https://stackoverflow.com/questions/47923524/app-is-crashing-on-some-devices-android-studio-shows-out-of-memory-exception-er?source=post_page-----45f87f1a2fef----------------------

如您所見,即使有人不回答,如果您以適當的方式提出問題,您仍然會獲得贊成票

致謝

最後,如果您得到查詢的答案並且符合您的要求,請將其標記為可接受的答案以關閉問題。

這將幫助發布答案的人獲得聲譽,並鼓勵他們幫助更多人。

畢竟,平台的存在僅是因為這些樂於助人的無私奉獻者願意為您提供幫助,因此這是您為他們所做的最少的事情。

結尾

提問是一門藝術,小編也經常遇到很多提問看不懂、看不明白的情況。無論是在團隊里還是平時和大家交流的過程中,多多少少會遇到互相不理解的情況,所以,做技術的我們實在是太難了,哈哈。

當然,如果學會了一些必要的技巧,提問對我們來說還是just so so,畢竟共同語言這麼多,雖然問題形形色色,但是茫茫人海,總會有人遇到你遇到的問題,總存在能解決問題的方法。

這是一篇很好的提問的範例,不僅僅是在Stack Overflow上,包括我們自己國內的平台、自己項目組、都可以用類似的技巧來提問,能大大節省溝通成本,獲得更高效率。

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

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

【其他文章推薦】

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

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

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

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

Java描述設計模式(19):模板方法模式

本文源碼: ||

一、生活場景

通常一款互聯網應用的開發流程如下:業務需求,規劃產品,程序開發,測試交付。現在基於模板方法模式進行該過程描述。

public class C01_InScene {
    public static void main(String[] args) {
        DevelopApp developApp = new DevelopApp() ;
        developApp.templateMethod() ;
    }
}
/**
 * 軟件開發抽象類
 */
abstract class SoftDevelop {
    public void templateMethod(){
        //調用基本方法
        doBiz ();
        doProduct();
        doDevelop();
        doTest();
    }
    public abstract void doBiz () ;
    public abstract void doProduct () ;
    public abstract void doDevelop () ;
    public abstract void doTest () ;
}
/**
 * APP開發具體類
 */
class DevelopApp extends SoftDevelop {
    @Override
    public void doBiz() {
        System.out.println("整理App業務");
    }
    @Override
    public void doProduct() {
        System.out.println("輸出App產品");
    }
    @Override
    public void doDevelop() {
        System.out.println("進行App開發");
    }
    @Override
    public void doTest() {
        System.out.println("進行App測試");
    }
}

二、模板方法模式

1、基礎概念

模板方法模式是類的行為模式。準備一個抽象類,將部分邏輯以具體方法以及具體構造函數的形式實現,然後聲明一些抽象方法來迫使子類實現剩餘的邏輯。不同的子類可以用不同的方式實現這些抽象方法,從而對剩餘的邏輯有不同的實現。簡單說,模板方法模式定義流程中的核心的框架,而將實際的業務操作延遲到子類中,使得子類可以不改變流程的結構,但可以重定義業務程序。

2、模式圖解

3、核心角色

  • 抽象模板角色

類中實現了模板方法(template),定義流程結構,具體業務需求子類需要去實現。

  • 具體模板角色

實現父類所定義的一個或多個抽象方法,是整個流程的組成方法。抽象模板角色都可以有任意多個具體模板角色與之對應,具體模板角色都可以給出這些抽象方法的不同實現。

4、源碼實現

/**
 * 抽象模板角色
 */
abstract class AbstractTemplate {
    /**
     * 模板方法
     */
    public void templateMethod(){
        //調用基本方法
        abstractMethod();
        hookMethod();
        concreteMethod();
    }
    /**
     * 相關基本方法
     */
    protected abstract void abstractMethod();
    protected void hookMethod(){}
    private final void concreteMethod(){}
}
/**
 * 具體模板角色
 */
class ConcreteTemplate extends AbstractTemplate{
    /**
     * 基本方法的實現
     */
    @Override
    public void abstractMethod() {
    }
    /**
     * 重寫父類的方法
     */
    @Override
    public void hookMethod(){
    }
}

5、不同方法描述

  • 模板方法

定義在抽象類中的,把基本操作方法組合在一起形成一個總流程的方法,可以有任意多個模板方法。

  • 基本方法
  1. 抽象方法:抽象方法由抽象類聲明,由具體子類實現。
  2. 具體方法:具體方法由抽象類聲明並實現,而子類並不實現。
  3. 鈎子方法:鈎子方法由抽象類聲明並實現,而子類可以加以擴展。

三、JavaEE應用

HttpServlet擔任抽象模板角色,模板方法:由service()方法擔任。基本方法:由doPost()、doGet()等方法擔任。service()方法流程,省略了部分判斷邏輯。該方法調用七個do方法中的一個或幾個,完成對客戶端請求的響應。這些do方法需要由HttpServlet的具體子類提供,在JavaEE中使用時,通常會自己實現相關方法。在API的封裝是典型的模板方法模式。

protected void service(HttpServletRequest req, HttpServletResponse resp) 
    throws ServletException, IOException {
    if (method.equals("GET")) {
        this.doGet(req, resp);
    } else if (method.equals("HEAD")) {
        this.doHead(req, resp);
    } else if (method.equals("POST")) {
        this.doPost(req, resp);
    } else if (method.equals("PUT")) {
        this.doPut(req, resp);
    } else if (method.equals("DELETE")) {
        this.doDelete(req, resp);
    } else if (method.equals("OPTIONS")) {
        this.doOptions(req, resp);
    } else if (method.equals("TRACE")) {
        this.doTrace(req, resp);
    } else {
        String errMsg = lStrings.getString("http.method_not_implemented");
        Object[] errArgs = new Object[]{method};
        errMsg = MessageFormat.format(errMsg, errArgs);
        resp.sendError(501, errMsg);
    }
}

四、源代碼地址

GitHub·地址
https://github.com/cicadasmile/model-arithmetic-parent
GitEE·地址
https://gitee.com/cicadasmile/model-arithmetic-parent

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

【其他文章推薦】

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

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

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

湄公河開發不止 洞里薩湖水量大減 柬埔寨淡水漁業恐崩盤

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

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

【其他文章推薦】

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

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

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

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

Elon Musk:Model X 會讓特斯拉銷量倍增

美國豪華電動車製造商特斯拉 (Tesla) 執行長 Elon Musk 仍舊對公司業務信心滿滿,據彭博社報導,Musk 7 日表示,特斯拉預定本季問世的電動運動休旅車「Model X」有望讓公司的電動車銷售量倍增。   特斯拉希望讓 2015 年的電動車銷售量達到 55,000 台,該公司目前只販售 Model S 車款,而其上半年的交車量達到 21,552 台、全年度目標達成度約有 40%。   特斯拉曾於 7 月 2 日宣布,旗下高檔電動車 Model S 第二季交車超出預期,第二季交車量來到 11,507 輛,高於原先預估的 10,000 至 11,000 輛。

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

【其他文章推薦】

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

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

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

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

傳蘋果將與 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 是電子產業重要的元件?

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

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

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