從map到堆棧

下面這些問題是從map的一些思考到堆棧的一些內容,自問自答,爲了讓自己理解更深一些。

1、問:Map這個數據結構,有幾種實現方式?
     HashMap和TreeMap。
     HashMap是使用數組+鏈表的方式實現的。在衝突少的情況下,O(1)的時間就能夠定位到節點,效率槓槓的。
     TreeMap是使用紅黑樹來實現的,由於紅黑樹是平衡二叉樹查找樹,所以,獲取數據的get操作和添加數據的put都需要O(logN)的時間複雜度,效率自然不如HashMap了。但由於是樹的結構,天生具有排序的特徵,所以,對於有排序需求的場景,就十分適用了。

2、HashMap具體是怎麼實現的?
     首先定義一個長度爲N的對象數組,一個哈希函數,將該對象的key進行哈希,求出的哈希值數組的下標,然後看該位置是否已經有對象存在,若沒有,則將其添加到該位置,若已經有對象(衝突),則通過該對象的next尋找到該鏈表最末尾的對象,將該對象鏈接到最末端(其實也可以直接掛到鏈表的頭部,這樣更節省時間)。

3、java中的HashMap和LinkedHashMap有什麼區別呢?
     HashMap的實現如上所述,LinkedHashMap,剛看到這個名詞時,以爲是直接使用鏈表而非數組來實現map的,震驚了一下,但其實不然。他的主題實現方式和HashMap是一樣的,只不過,LinkedHashMap可以按照插入順序或者訪問順序進行排序(構造函數中可以配),這是由於LinkedHashMap維護的是一個雙向的鏈表而非單向的,一個after指針,用於記錄衝突時的鏈表的下一個元素;一個before 指針用於記錄插入或者訪問的順序。 默認是按插入順序排序,如果指定按訪問順序排序,那麼調用get方法後,會將這次訪問的元素移至鏈表尾部,不斷訪問可以形成按訪問順序排序的鏈表。

4、如果頻繁且大量的對HashMap進行put和remove的操作,會造成什麼影響?怎麼解決?
     內存碎片。由於每次put的時候都需要new一個對象,remove的時候把這個對象移除,這樣,在堆中不斷地添加刪除對象,會導致大量的內存碎片,這樣,就需要更頻繁的進行內存整理(java中是GC)。
     由於內存碎片是由於不斷地new和delete對象而產生的,那麼,找個辦法不要讓他重複new和delete就行了。我想到的是使用對象池來管理對象,當一個一個對象從map中移除時,並不真正delete掉,而是放入對象池,那麼,當往map裏put一個元素時,直接從對象池中獲取對象,並把值set進去。這種直接設置值的方式,比new一個對象快,同時,由於不需要在堆上不斷new和delete對象,可以大大減少內存碎片的產生。當然,這種方式實現起來更復雜,並且需要對對象池裏面存活的對象數進行有效的管理,否則,如果空閒對象太多,會佔用內存而浪費資源。

5、堆的數據結構是怎樣的?他是如何管理對象的,當new和delete的時候,堆內部發生了什麼?
     堆是一個鏈表結構(本來以爲是個二叉樹或者B樹的結構,但似乎只是用了簡單的鏈表進行管理),用於記錄空閒的內存地址。
     當程序new一個對象時,需要在堆上申請申請一塊內存空間,系統會遍歷該鏈表,尋找第一個大於申請空間的堆節點,將該節點從空閒鏈表中刪除(將該節點沒 用完的空間再加入空閒鏈表中),並將該節點空間分配給程序。
     當delete一個對象時,系統會將該對象在堆上的內存釋放,把他重新加入空閒鏈表,並與相鄰的鏈表(如果有的話)合併???
     以上都是在C語言中內存的分配,而在java中,只有new沒有delete的說法,都是由JVM自動管理的。


6、堆和棧,在哪個上分配更快,爲什麼?
      (針對C++)棧是機器系統提供的數據結構,計算機會在底層對棧提供支持:分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執行,這就決定了棧的效率比較高。堆則是C/C++函數庫提供的,它的機制是很複雜的,例如爲了分配一塊內存,庫函數會按照一定的算法(具體的算法可以參考數據結構/操作系統)在堆內存中搜索可用的足夠大小的空間,如果沒有足夠大小的空間(可能是由於內存碎片太多),就有可能調用系統功能去增加程序數據段的內存空間,這樣就有機會分到足夠大小的內存,然後進行返回。顯然,堆的效率比棧要低得多。

7、棧和堆的大小如何設置?
     棧和堆的初始化一般可以進行配置。比如,JVM中,可以通過-Xmx來設置堆的大小和-Xss來設置每個線程的棧大小。由於棧僅僅存儲指向堆中的某個對象或者保存方法的入口,所以他的每個元素是定長的且佔用空間很小,因此棧一般初始化爲1M甚至更小。
    在C++中,棧是操作系統提供的,而堆是C++函數庫提供的。所以,程序運行時,你可以配置堆的大小,但無法調整每個程序棧的大小。

8、爲什麼堆和棧要區分開?二者有什麼區別?
     棧存在於程序中的目的主要是爲了控制程序的主流程,這點在方法調用中特別明顯,當在一個方法中A調用另一個方法B時,會把執行完B方法後的下一個執行語句地址push入棧,用於程序返回時使用(整個爲A的棧幀),在執行方法B時,若有B的局部變量,也繼續push入棧進行記錄。當B調用完畢時,將B的棧幀pop出棧,然後繼續執行A方法。在這個過程裏面,由於棧先進後出的特性,能夠很方便的控制一個程序的線性的流程,並且在B方法返回後,將的棧幀pop出棧,使得B方法的局部變量,一切都發生的很自然。
     而堆存在的目的,由於棧的每個元素都是固定長度(定長使得內存分配管理都十分方便),並不適合存儲變長的對象,所以,使用了堆來應對這種程序中動態變化的需求。當程序需要一塊內存存放對象時,會向堆進行申請,然後將指針存入棧中進行記錄,所以堆中的每個元素都應該被棧中的某個節點所引用,否則就是失效的節點。另外,在堆上存儲對象,而其他地方則只引用他的指針,這大大方便了對象的傳遞。
     所以,總結下,堆棧的分離,就是將行爲與數據分離開。棧用於控制流程、處理邏輯,堆用於存儲數據、管理內存,讓擅長的人做擅長的事,協作的多美好。

9、除了堆棧,數據區還包括哪些區域?
     在C++中,除了堆、棧,還有常量區(所有的常量都放這裏),全局/靜態存儲區(顧名思義,就是存儲全局變量和靜態變量的),自由存儲區(由malloc分配的內存塊,和堆十分相似,但他們用free結束)。
     在JVM中,則主要由Java虛擬機棧、堆,還有方法區(用於存儲類信息、常量、靜態變量,方法區裏面還包括了運行時常量池),程序計數器(用於記錄當前線程指令的位置)、本地方法棧

10、從B方法返回A方法時,是如何將A方法中的變量讀取進來的呢?在棧中查找對象是怎麼做的?
     比如,程序如下,
     A( ){
     int a=1;
     int b=2;
     int c = add(a,b);
     a = c+b;
     }
     B(int a,int b){
          return a+b;
     }
     在A方法執行到 int c = add(a,b);  這一句時,需要調用B方法,會將參數a=1,b=2 ,變量c 分別push進棧,同時,將B方法之後要執行的語句( a = c+b; )地址push進棧,之後進入B方法的棧幀實體進行程序執行。B執行完後,會將B方法的棧幀pop出棧兵更新棧中c的值。然後,程序返回A,繼續執行下一條語句。




關於棧和堆中分配空間小結 http://blog.csdn.net/lisiyong/article/details/1552279
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章