普通集合和泛型集合的區別,哈希表和字典表的區別,隊列和堆棧的區別以及堆和棧的區別。

普通集合和泛型集合的區別:程序員

泛型集合與傳統集合相比 類型更安全. 泛型集合無需裝箱拆箱操做. 泛型的重要性. 泛型是將來五年的主流技術 ...

一般狀況下,建議您使用泛型集合,由於這樣能夠得到類型安全的直接優勢而不須要從基集合類型派生並實現類型特定的成員。此外,若是集合元素爲值類型,泛型集合類型的性能一般優於對應的非泛型集合類型(並優於從非泛型基集合類型派生的類型),由於使用泛型時沒必要對元素進行裝箱。

下面的泛型類型對應於現有的集合類型:

List 是對應於 ArrayList 的泛型類。

Dictionary 是對應於 Hashtable 的泛型類。

Collection 是對應於 CollectionBase 的泛型類。Collection 能夠用做基類,可是與 CollectionBase 不一樣的是它不是抽象的,於是更易於使用。

ReadOnlyCollection 是對應於 ReadOnlyCollectionBase 的泛型類。ReadOnlyCollection 不是抽象的,它具備一個構造函數 ,該構造函數使其更易於將現有的 List 公開爲只讀集合。

Queue、Stack 和 SortedList 泛型類分別對應於與其同名的非泛型類。算法

 

哈希表和字典表的區別:數組

 

Hashtable 和 Dictionary <K, V> 類型 緩存

1):單線程程序中推薦使用 Dictionary, 有泛型優點, 且讀取速度較快, 容量利用更充分.
2):多線程程序中推薦使用 Hashtable, 默認的 Hashtable 容許單線程寫入, 多線程讀取, 對 Hashtable 進一步調用 Synchronized()方法能夠得到徹底線程安全的類型. 而Dictionary 非線程安全, 必須人爲使用 lock 語句進行保護, 效率大減.
3):Dictionary 有按插入順序排列數據的特性 (注: 但當調用 Remove() 刪除過節點後順序被打亂), 所以在須要體現順序的情境中使用 Dictionary 能得到必定方便.安全

 

HashTable中的key/value均爲object類型,由包含集合元素的存儲桶組成。存儲桶是 HashTable中各元素的虛擬子組,與大多數集合中進行的搜索和檢索相比,存儲桶可令搜索和檢索更爲便捷。每一存儲桶都與一個哈希代碼關聯,該哈希代碼是使用哈希函數生成的並基於該元素的鍵。HashTable的優勢就在於其索引的方式,速度很是快。若是以任意類型鍵值訪問其中元素會快於其餘集合,特別是當數據量特別大的時候,效率差異尤爲大。數據結構

HashTable的應用場合有:作對象緩存,樹遞歸算法的替代,和各類需提高效率的場合。多線程

 

 

一,哈希表(Hashtable)併發

在.NET Framework中,Hashtable是System.Collections命名空間提供的一個容器,用於處理和表現相似key/value的鍵值對,其中key一般可用來快速查找,同時key是區分大小寫;value用於存儲對應於key的值。Hashtable中key/value鍵值對均爲object類型,因此Hashtable能夠支持任何類型的key/value鍵值對.app

1.1 哈希表的簡單操做函數

在哈希表中添加一個key/value鍵值對:HashtableObject.Add(key,value); 
在哈希表中去除某個key/value鍵值對:HashtableObject.Remove(key); 
從哈希表中移除全部元素: HashtableObject.Clear(); 
判斷哈希表是否包含特定鍵key: HashtableObject.Contains(key); 

 

哈希表,名-值對。相似於字典(比數組更強大)。哈希表是通過優化的,訪問下標的對象先散列過。若是以任意類型鍵值訪問其中元素會快於其餘集合。

 

 

GetHashCode()方法返回一個int型數據,使用這個鍵的值生成該int型數據。哈希表獲取這個值最後返回一個索引,表示帶有給定散列的數據項在字典中存儲的位置。

 //Hashtable sample
System.Collections.Hashtable ht = new System.Collections.Hashtable(); 
//--Be careful: Keys can't be duplicated, and can't be null----
ht.Add(1, "apple");
ht.Add(2, "banana");
ht.Add(3, "orange"); 
//Modify item value:
if(ht.ContainsKey(1))
ht[1] = "appleBad"; 
//The following code will return null oValue, no exception
object oValue = ht[5]; 

1.2  哈希表遍歷

 

遍歷Hashtable對象的兩種方法:

因爲Hashtable每一個元素都是一個鍵/值對,所以元素類型既不是鍵的類型,也不是值的類型,而是DictionaryEntry類型。  

 
//方法一:遍歷traversal 1:
foreach (DictionaryEntry de in ht)
{
Console.WriteLine(de.Key);
Console.WriteLine(de.Value);

//方法二:遍歷traversal 2:
System.Collections.IDictionaryEnumerator d = ht.GetEnumerator();
while (d.MoveNext())
{
Console.WriteLine("key:{0} value:{1}", d.Entry.Key, d.Entry.Value);

//Clear items
ht.Clear();

1.3 排序

 

HashTable是通過優化的,訪問下標的對象先散列過,因此內部是無序散列的,保證了高效率,也就是說,其輸出不是按照開始加入的順序,而Dictionary遍歷輸出的順序,就是加入的順序,這點與Hashtable不一樣。若是必定要排序HashTable輸出,只能本身實現:

 //排序 Hashtable sorting

System.Collections.ArrayList akeys = new System.Collections.ArrayList(ht.Keys); //from Hashtable
akeys.Sort(); //Sort by leading letter
foreach (string skey in akeys)
{
Console.Write(skey + ":");
Console.WriteLine(ht[skey]);
}

 

1.四、HashTable與線程安全:

爲了保證在多線程的狀況下的線程同步訪問安全,微軟提供了自動線程同步的HashTable:

若是 HashTable要容許併發讀但只能一個線程寫, 要這麼建立 HashTable實例:

 //Thread safe HashTable
System.Collections.Hashtable htSyn = System.Collections.Hashtable.Synchronized(new System.Collections.Hashtable());

這樣, 若是有多個線程併發的企圖寫HashTable裏面的 item, 則同一時刻只能有一個線程寫, 其他阻塞; 對讀的線程則不受影響。

 

另一種方法就是使用lock語句,但要lock的不是HashTable,而是其SyncRoot;雖然不推薦這種方法,但效果同樣的,由於源代碼就是這樣實現的:

 

private static System.Collections.Hashtable htCache = new System.Collections.Hashtable ();

public static void AccessCache ()

 

{

lock ( htCache.SyncRoot )

{

htCache.Add ( "key", "value" );

}

}

 

//Is equivalent to 等同於 (lock is equivalent to Monitor.Enter and Exit()

public static void AccessCache ()

{

System.Threading.Monitor.Enter ( htCache.SyncRoot );

try

{

htCache.Add ( "key", "value" );

}

finally

{

System.Threading.Monitor.Exit ( htCache.SyncRoot );

}

}

二 字典

Dictionary<Tkey,Tvalue>是Hastbale的泛型實現。 

Dictionary和HashTable內部實現差很少,但前者無需裝箱拆箱操做,效率略高一點。

 //Dictionary sample
System.Collections.Generic.Dictionary<int, string> fruits = new System.Collections.Generic.Dictionary<int, string>(); 
fruits.Add(1, "apple");
fruits.Add(2, "banana");
fruits.Add(3, "orange"); 
foreach (int i in fruits.Keys)
{
Console.WriteLine("key:{0} value:{1}", i, fruits); }

if (fruits.ContainsKey(1))
{
Console.WriteLine("contain this key.");
}

2.1 字典遍歷

//遍歷鍵
foreach (string key in myDictionary.Keys)
{
    //遍歷某鍵的值
    foreach (string val in myDictionary[key])
    {

    }
}

因爲 Dictionary 是鍵和值的集合,所以元素類型並不是鍵類型或值類型。相反,元素類型是鍵類型和值類型的 KeyValuePair。 

foreach (KeyValuePair<string, string> kvp in myDictionary)
{
    string key = kvp.Key;//key包含了字典裏的鍵
    for (int i = 0; i < kvp.Value.Count; i++)
    {
        Response.Write(kvp.Value[i]);
    }
}

 

 

隊列和堆棧的區,以及堆和棧的區別:

堆棧 在計算機領域,堆棧是一個不容忽視的概念,可是不少人甚至是計算機專業的人也沒有明確堆棧實際上是兩種數據結構。 要點: 堆:順序隨意 棧:先進後出 c/C++ 堆和棧的區別 對瞭解Java會有幫助的1、預備知識—程序的內存分配 一個由c/C++編譯的程序佔用的內存分爲如下幾個部分 一、棧區(stack)— 由編譯器自動分配釋放 ,存放函數的參數值,局部變量的值等。其操做方式相似於數據結構中的棧。 二、堆區(heap) — 通常由程序員分配釋放, 若程序員不釋放,程序結束時可能由OS回收 。注意它與數據結構中的堆是兩回事,分配方式卻是相似於鏈表,呵呵。 三、全局區(靜態區)(static)—,全局變量和靜態變量的存儲是放在一塊的,初始化的全局變量和靜態變量在一塊區域, 未初始化的全局變量和未初始化的靜態變量在相鄰的另外一塊區域。 - 程序結束後有系統釋放 四、文字常量區 —常量字符串就是放在這裏的。 程序結束後由系統釋放 五、程序代碼區—存放函數體的二進制代碼。 2、例子程序 這是一個前輩寫的,很是詳細 //main.cpp int a = 0; 全局初始化區 char *p1; 全局未初始化區 main() { int b; 棧 char s[] = "abc "; 棧 char *p2; 棧 char *p3 = "123456 "; 123456\0在常量區,p3在棧上。 static int c =0; 全局(靜態)初始化區 p1 = (char *)malloc(10); p2 = (char *)malloc(20); 分配得來得10和20字節的區域就在堆區。 strcpy(p1, "123456 "); 123456\0放在常量區,編譯器可能會將它與p3所指向的 "123456 "優化成一個地方。 } 2、堆和棧的理論知識 2.1申請方式 stack: 由系統自動分配。 例如,聲明在函數中一個局部變量 int b; 系統自動在棧中爲b開闢空間 heap: 須要程序員本身申請,並指明大小,在c中malloc函數 如p1 = (char *)malloc(10); 在C++中用new運算符 如p2 = (char *)malloc(10); 可是注意p一、p2自己是在棧中的。 2.2 申請後系統的響應 棧:只要棧的剩餘空間大於所申請空間,系統將爲程序提供內存,不然將報異常提示棧溢出。 堆:首先應該知道操做系統有一個記錄空閒內存地址的鏈表,當系統收到程序的申請時, 會 遍歷該鏈表,尋找第一個空間大於所申請空間的堆結點,而後將該結點從空閒結點鏈表中刪除,並將該結點的空間分配給程序,另外,對於大多數系統,會在這塊內 存空間中的首地址處記錄本次分配的大小,這樣,代碼中的delete語句才能正確的釋放本內存空間。另外,因爲找到的堆結點的大小不必定正好等於申請的大 小,系統會自動的將多餘的那部分從新放入空閒鏈表中。 2.3申請大小的限制 棧:在Windows 下,棧是向低地址擴展的數據結構,是一塊連續的內存的區域。這句話的意思是棧頂的地址和棧的最大容量是系統預先規定好的,在 WINDOWS下,棧的大小是2M(也有的說是1M,總之是一個編譯時就肯定的常數),若是申請的空間超過棧的剩餘空間時,將提示overflow。因 此,能從棧得到的空間較小。 堆:堆是向高地址擴展的數據結構,是不連續的內存區域。這是因爲系統是用鏈表來存儲的空閒內存地址的,天然是不連續的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限於計算機系統中有效的虛擬內存。因而可知,堆得到的空間比較靈活,也比較大。 2.4申請效率的比較: 棧由系統自動分配,速度較快。但程序員是沒法控制的。 堆是由new分配的內存,通常速度比較慢,並且容易產生內存碎片,不過用起來最方便. 另外,在WINDOWS下,最好的方式是用VirtualAlloc分配內存,他不是在堆,也不是在棧是直接在進程的地址空間中保留一快內存,雖然用起來最不方便。可是速度快,也最靈活 2.5堆和棧中的存儲內容 棧: 在函數調用時,第一個進棧的是主函數中後的下一條指令(函數調用語句的下一條可執行語句)的地址,而後是函數的各個參數,在大多數的C編譯器中,參數是由右往左入棧的,而後是函數中的局部變量。注意靜態變量是不入棧的。 當本次函數調用結束後,局部變量先出棧,而後是參數,最後棧頂指針指向最開始存的地址,也就是主函數中的下一條指令,程序由該點繼續運行。 堆:通常是在堆的頭部用一個字節存放堆的大小。堆中的具體內容有程序員安排。 2.6存取效率的比較 char s1[] = "aaaaaaaaaaaaaaa "; char *s2 = "bbbbbbbbbbbbbbbbb "; aaaaaaaaaaa是在運行時刻賦值的; 而bbbbbbbbbbb是在編譯時就肯定的; 可是,在之後的存取中,在棧上的數組比指針所指向的字符串(例如堆)快。 好比: #include void main() { char a = 1; char c[] = "1234567890 "; char *p = "1234567890 "; a = c[1]; a = p[1]; return; } 對應的彙編代碼 10: a = c[1]; 00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh] 0040106A 88 4D FC mov byte ptr [ebp-4],cl 11: a = p[1]; 0040106D 8B 55 EC mov edx,dword ptr [ebp-14h] 00401070 8A 42 01 mov al,byte ptr [edx+1] 00401073 88 45 FC mov byte ptr [ebp-4],al 第一種在讀取時直接就把字符串中的元素讀到寄存器cl中,而第二種則要先把指針值讀到edx中,在根據edx讀取字符,顯然慢了。 ? .7小結: 堆和棧的區別能夠用以下的比喻來看出: 使用棧就象咱們去飯館裏吃飯,只管點菜(發出申請)、付錢、和吃(使用),吃飽了就走,沒必要理會切菜、洗菜等準備工做和洗碗、刷鍋等掃尾工做,他的好處是快捷,可是自由度小。 使用堆就象是本身動手作喜歡吃的菜餚,比較麻煩,可是比較符合本身的口味,並且自由度大。 堆和棧的區別主要分: 操做系統方面的堆和棧,如上面說的那些,很少說了。 還有就是數據結構方面的堆和棧,這些都是不一樣的概念。這裏的堆實際上指的就是(知足堆性質的)優先隊列的一種數據結構,第1個元素有最高的優先權;棧實際上就是知足先進後出的性質的數學或數據結構。 雖然堆棧,堆棧的說法是連起來叫,可是他們仍是有很大區別的,連着叫只是因爲歷史的緣由。五大內存分區 在C++中,內存分紅5個區,他們分別是堆、棧、自由存儲區、全局/靜態存儲區和常量存儲區。 棧,就是那些由編譯器在須要的時候分配,在不須要的時候自動清楚的變量的存儲區。裏面的變量一般是局部變量、函數參數等。 堆,就是那些由new分配的內存塊,他們的釋放編譯器不去管,由咱們的應用程序去控制,通常一個new就要對應一個delete。若是程序員沒有釋放掉,那麼在程序結束後,操做系統會自動回收。 自由存儲區,就是那些由malloc等分配的內存塊,他和堆是十分類似的,不過它是用free來結束本身的生命的。 全局/靜態存儲區,全局變量和靜態變量被分配到同一塊內存中,在之前的C語言中,全局變量又分爲初始化的和未初始化的,在C++裏面沒有這個區分了,他們共同佔用同一塊內存區。 常量存儲區,這是一塊比較特殊的存儲區,他們裏面存放的是常量,不容許修改(固然,你要經過非正當手段也能夠修改,並且方法不少) 明確區分堆與棧 在bbs上,堆與棧的區分問題,彷佛是一個永恆的話題,因而可知,初學者對此每每是混淆不清的,因此我決定拿他第一個開刀。 首先,咱們舉一個例子: void f() { int* p=new int[5]; } 這條短短的一句話就包含了堆與棧,看到new,咱們首先就應該想到,咱們分配了一塊堆內存,那麼指針p呢?他分配的是一塊棧內存,因此這句話的意思就是: 在棧內存中存放了一個指向一塊堆內存的指針p。在程序會先肯定在堆中分配內存的大小,而後調用operator new分配內存,而後返回這塊內存的首地址,放入棧中,他在VC6下的彙編代碼以下: 00401028 push 14h 0040102A call operator new (00401060) 0040102F add esp,4 00401032 mov dword ptr [ebp-8],eax 00401035 mov eax,dword ptr [ebp-8] 00401038 mov dword ptr [ebp-4],eax 這裏,咱們爲了簡單並無釋放內存,那麼該怎麼去釋放呢?是delete p麼?澳,錯了,應該是delete []p,這是爲了告訴編譯器:我刪除的是一個數組,VC6就會根據相應的Cookie信息去進行釋放內存的工做。 好了,咱們回到咱們的主題:堆和棧究竟有什麼區別? 主要的區別由如下幾點: 一、管理方式不一樣; 二、空間大小不一樣; 三、可否產生碎片不一樣; 四、生長方向不一樣; 五、分配方式不一樣; 六、分配效率不一樣; 管理方式:對於棧來說,是由編譯器自動管理,無需咱們手工控制;對於堆來講,釋放工做由程序員控制,容易產生memory leak。 空間大小:通常來說在32位系統下,堆內存能夠達到4G的空間,從這個角度來看堆內存幾乎是沒有什麼限制的。可是對於棧來說,通常都是有必定的空間大小 的,例如,在VC6下面,默認的棧空間大小是1M(好像是,記不清楚了)。固然,咱們能夠修改: 打開工程,依次操做菜單以下:Project-> Setting-> Link,在Category 中選中Output,而後在Reserve中設定堆棧的最大值和commit。 注意:reserve最小值爲4Byte;commit是保留在虛擬內存的頁文件裏面,它設置的較大會使棧開闢較大的值,可能增長內存的開銷和啓動時間。 碎片問題:對於堆來說,頻繁的new/delete勢必會形成內存空間的不連續,從而形成大量的碎片,使程序效率下降。對於棧來說,則不會存在這個問題, 由於棧是先進後出的隊列,他們是如此的一一對應,以致於永遠都不可能有一個內存塊從棧中間彈出,在他彈出以前,在他上面的後進的棧內容已經被彈出,詳細的 能夠參考數據結構,這裏咱們就再也不一一討論了。 生長方向:對於堆來說,生長方向是向上的,也就是向着內存地址增長的方向;對於棧來說,它的生長方向是向下的,是向着內存地址減少的方向增加。 分配方式:堆都是動態分配的,沒有靜態分配的堆。棧有2種分配方式:靜態分配和動態分配。靜態分配是編譯器完成的,好比局部變量的分配。動態分配由 alloca函數進行分配,可是棧的動態分配和堆是不一樣的,他的動態分配是由編譯器進行釋放,無需咱們手工實現。 分配效率:棧是機器系統提供的數據結構,計算機會在底層對棧提供支持:分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執行,這就決定了棧的效率比 較高。堆則是C/C++函數庫提供的,它的機制是很複雜的,例如爲了分配一塊內存,庫函數會按照必定的算法(具體的算法能夠參考數據結構/操做系統)在堆 內存中搜索可用的足夠大小的空間,若是沒有足夠大小的空間(多是因爲內存碎片太多),就有可能調用系統功能去增長程序數據段的內存空間,這樣就有機會分 到足夠大小的內存,而後進行返回。顯然,堆的效率比棧要低得多。 從這裏咱們能夠看到,堆和棧相比,因爲大量new/delete的使用,容易形成大量的內存碎片;因爲沒有專門的系統支持,效率很低;因爲可能引起用戶態 和核心態的切換,內存的申請,代價變得更加昂貴。因此棧在程序中是應用最普遍的,就算是函數的調用也利用棧去完成,函數調用過程當中的參數,返回地址, EBP和局部變量都採用棧的方式存放。因此,咱們推薦你們儘可能用棧,而不是用堆。 雖然棧有如此衆多的好處,可是因爲和堆相比不是那麼靈活,有時候分配大量的內存空間,仍是用堆好一些。 不管是堆仍是棧,都要防止越界現象的發生(除非你是故意使其越界),由於越界的結果要麼是程序崩潰,要麼是摧毀程序的堆、棧結構,產生以想不到的結果,就 算是在你的程序運行過程當中,沒有發生上面的問題,你仍是要當心,說不定何時就崩掉,那時候debug但是至關困難的:) 對了,還有一件事,若是有人把堆棧合起來講,那它的意思是棧,可不是堆,呵呵,清楚了?

相關文章
相關標籤/搜索