宋城虎,馬 靜
(中國電子科技集團公司第二十七研究所,河南 鄭州450047)
HashTable(也叫哈希表),是根據關鍵碼值(key,value)而直接進行訪問的數據結構。它通過把關鍵碼值映射到表中一個位置來訪問記錄,以加快查找的速度。在.NET Framework中,System.Collections命名空間提供了HashTable容器的實現。HashTable中key、value鍵值對均為object類型,所以HashTable可以支持任何類型的key、value鍵值對。
在工程應用中,上位機應用程序通常需要與多個下位機或者外圍設備進行通信,從而實現對設備的控制以及設備狀態與數據的接收與處理。當上位機通信的對象增多時,上位機編程也會隨之變得越來越復雜。
本文以C#WinForm框架下的上位機編程為例討論一種HashTable的應用方法,該方法實現簡單,使用靈活方便,可大幅度簡化上位機程序開發中的一些復雜問題。
HashTable的結構為1個key對應1個value,這個結構在實際應用中經常顯得過于單一,如果兩個key對應1個value那么顯然就會靈活許多,我們就可以構建類似于“對象‘key1’的‘key2’屬性為‘value’”這樣的邏輯關系(如圖1、圖2所示)。
由于在.NET Framework中的HashTable中key、value鍵值對均為object類型,那么value本身也可以存儲另一個HashTable,因此我們可以構建兩層哈希表來實現雙鍵哈希結構,即在第一層HashTable1中通過key1存儲第二層HashTable2,在第二層HashTable2中通過key2存儲value值(如圖3所示)。
于是我們只需要定義哈希表的三個基本操作“存儲、讀取、刪除”即可實現雙鍵哈希表功能。其中刪除需要針對key1和key2定義兩個函數,因此總共需要定義4個函數,以下給出代碼:
void hash_save(Hashtable hash,object key1,object key2,object value)
{
if(hash!=null)
{
Hashtable hash1;
if(hash[key1]==null)
{
hash1=new Hashtable();
hash.Add(key1,hash1);
}
else hash1=(Hashtable)(hash[key1]);
if(hash1[key2]==null)hash1.Add(key2,value);
else{
hash1.Remove(key2);
hash1.Add(key2,value);
}}}
object hash_Load(Hashtable hash,object key1,object key2)
{
i(fhash[key1]!=null)return((Hashtable)(hash[key1]))[key2];
else return null;
}
void hash_remove_key1(Hashtable hash,object key1)
{
((Hashtable)(hash[key1])).Clea(r);
}
void hash_remove_key2(Hashtable hash,object key1,object key2)
{
((Hashtable)(hash[key1])).Remove(key2);
}
上位機應用程序通常需要與多個下位機或者外圍設備進行通信,這里以udp通信為例(其他通信原理相同)。WinForm框架中有Socket類用來實現網絡通信,在建立一個udp通信時,我們需要實例化一個Socket對象,定義目標IPEndPoint和本地IPEndPoint,建立監聽線程,在監聽線程的回調函數中編寫數據處理代碼。
當上位機要與多個對象同時建立通信時,通常將以上過程復制多次,并用不同的變量名進行區分。當通信對象特別多時代碼的編輯和維護就會變得非常困難,且不方便移植也不利于復用。
這里借助上文定義的雙鍵哈希表可極大程度地優化這一過程。實現思路如下:
(1)給每一條通信鏈路定義一個唯一標識Sign;
(2)以標識Sign為key1、以參數標識為key2對該鏈路的所有相關參數進行注冊,存儲在哈希表中,例如給名為“sign1”的連接注冊目標IP:
hash_sav(ehash1,"sign1","目標IP","192.168.1.10");
(3)定義一個主索引用來記錄所有已經注冊的Sign,主索引依然可以使用雙鍵哈希表實現,例如索引中增加一個新的名為“Sign1”的連接:
int max=(int)hash_load(hash1,"udp主索引","連接總數")+1
hash_save(hash1,"udp主索引","連接總數",max);
hash_save(hash1,"udp主索引",max,"Sign1");
(4)定義udpcreate函數,該函數針對一個特定Sign,建立一條udp通信鏈路,返回初始化完畢的Socket對象。所有初始化相關參數以Sign為key在哈希表中讀取,例如讀取連接“sign1”的目標IP:
string ip=hash_load(hash1,"sign1","目標IP").ToString();
(5)在程序的udp初始化環節遍歷第3步中定義的主索引,調用udpcreate函數初始化所有udp連接;
(6)單獨定義每條通信的接收數據的回調函數,回調函數可以以委托結合文本宏的方式注冊在哈希表中。通信鏈路的注冊可以通過文件讀取轉移到配置文件中。
主索引操作以及udpcreate函數等通用型代碼均可封裝到一個模塊中,方便移植和復用。這樣每次開發一個新的上位機程序只需要編寫數據處理的回調函數以及根據工程需求編輯配置文件即可。
使用雙鍵哈希表可以不以變量為載體,動態地存儲和讀取任意數據,并能將數據關聯在任意對象上,這意味著它幾乎可以應用到程序的任何地方。例如,我們可以在控件刷新時讀取控件上綁定的狀態數據來決定控件的外觀或者顯示文字,這樣我們就可以通過改變哈希值來控制控件的狀態刷新;我們還可以利用雙鍵哈希表將兩個控件關聯起來,實現類似于父節點和子節點這樣的結構關系等。
由于使用雙鍵哈希表會自動創建許多次級HashTable,當某個key1不再使用時,應當注意釋放key1對應的資源,也就是調用上文提到的hash_remove_key1函數,避免出現內存泄漏。
在某工程項目的上位機軟件開發中,與上位機通信的分機有7個,其中包含udp和串口通信。該軟件開發中大量使用了雙鍵哈希表,與以往的開發經驗做對比極大程度地提高了開發效率,調試過程中的bug也有顯著減少,并且能夠簡單快捷地移植到其他項目開發中(如圖4、圖5所示)。

圖4 某項目上位機軟件通信調試界面

圖5 某項目通信配置文件
文章利用.NET Framework中的HashTable的key和value可支持任意類型值的特點,設計了一種雙鍵哈希表,可實現將任意類型的兩個對象作為索引存儲一個任意類型的value值。文章以WinForm框架下上位機程序開發為背景,以udp通信編程為例,詳細闡述了該技術的應用方法與效果,對該技術在其他方面的應用進行了展望。該技術已在作者參與的多個工程項目中得到應用,并得到了非常好的應用效果。