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