樹型結構是一類應用非常廣泛的資料結構。人類社會中宗族的族譜和現代企業的組織形式都是樹型結構。在電腦領域中,檔案系統中檔案的管理結構、儲存空間管理中的頁表、資料庫中的索引等也都是樹型結構。隨著Internet的飛速發展,樹型結構在瀏覽器/伺服器(Browser/Server,簡稱B/S)應用系統的應用也越來越廣泛。
目前,在互連網上廣泛存在、應用的樹型結構一般分為兩種:靜態和動態結構。靜態結構存在最多、實現簡單,但是靜態導致不能改變樹的結構和內容,無法反映樹的節點資訊的變化;而實現相對複雜的動態構造樹,雖然可以動態增加、刪除、更新節點資訊,但是大部分不能直接拖放節點來改變樹的結構以及節點間的次序,並且反覆重新整理整個頁面,給使用者維護帶來了許多不便。本文提出了一種基於Ajax(Asynchronous JavaScript and XML)通用的、動態載入節點的解決方案。實現上採用J2EE多層架構,樹節點的描述資訊採用資料庫儲存,以可延伸標記語言 (XML)(eXtensible Markup Language,簡稱XML)展現給JavaScript解析,支援無重新整理地增加、刪除、更新節點資訊,以及拖放節點來改變樹的結構和節點間的次序。文中第1部分簡要介紹了Ajax技術;第2部分詳細介紹了該方案的技術實現過程;第3部分分析了該方案的效率。
1、Ajax簡介
Ajax概念的最早提出者Jesse James Garrett認為:Ajax並不是一門新的語言或技術,它實際上是幾項技術按一定的方式組合在共同的協作中發揮各自的作用,它包括:
·使用擴充超媒體標記語言(eXtended Hypertext Markup Language,簡稱XHTML)和級聯樣式單(Cascading Style Sheet,簡稱CSS)標準化呈現;
·使用文件物件模型(Document Object Model,簡稱DOM)實現動態顯示和互動;
·使用可延伸標記語言 (XML)(eXtensible Markup Language,簡稱XML)和可擴充樣式表轉換(eXtensible Stylesheet Language Transformation,簡稱XSLT)進行資料交換與處理;
·使用XMLHTTP組件XMLHttpRequest對象進行非同步資料讀取;
·最後用JavaScript綁定和處理所有資料。
Ajax的工作原理如圖1所示,它相當於在使用者和伺服器之間加了一個中介層,使使用者操作與伺服器響應非同步化。並不是所有的使用者請求都提交給伺服器,像—些資料驗證和資料處理等都交給Ajax引擎處理,只有確定需要從伺服器讀取新資料時再由Ajax引擎代為向伺服器提交請求。這樣就把一些伺服器負擔的工作轉嫁到用戶端,利用用戶端閑置的處理能力來處理,減輕伺服器和頻寬的負擔,從而達到節約ISP的空間及頻寬租用成本的目的。
圖 1 未使用Ajax(a)和使用Ajax(b)的web應用比較 |
2、總體設計方案
傳統的伺服器程式採用Model 1開發模型,通常將商務邏輯、伺服器端處理過程和HTML代碼集中在一起表示,快速完成應用開發。Model 1 在小規模應用開發時優勢明顯,但是應用實現一般是基於過程的,一組伺服器頁面實現一個流程,如果流程改動將導致多個地方修改,非常不利於應用的擴充和更新。此外商務邏輯和表示邏輯混合在伺服器頁面中,耦合緊密,無法模組化,導致代碼無法複用。
Model 2則解決了這些問題,它是物件導向的MVC模式(Model-View-Controller,模型-視圖-控制器)在Web開發中的應用,Model表示應用的商務邏輯,View是應用的展示層頁面,Controller是提供應用的處理過程式控制制。通過這種MVC設計模式把應用邏輯,處理過程和顯示邏輯劃分成不同的組件、模組實現,組件間可以進行互動和重用。
本方案是採用J2EE的多層架構,設計時結合Struts架構將展示層、商務邏輯層和資料層劃分成不同的模組。展示層專註於樹的外觀顯示,商務邏輯層為伺服器端處理常式,處理樹的產生、變化,為減少耦合性,該程式全部模組化實現,不在表示頁面嵌入伺服器程式;模型層是資料的儲存和表示。下面分別介紹各層實現。
2.1 展示層實現
類似Windows資源管理員的檔案夾模式,節點的圖片樣式如表1所示。對於每個節點的DHTML 程式碼,需要包含節點的位置、前置圖片、樣式、針對該節點的其他動作等。同時為了節點顯示的連貫性,還需一些前置圖片。
表1 樹節點的前的圖片樣式表
對於樹的非葉子節點,圖片和節點資訊等,採用一個DIV ( division) 容器包含。DIV 等容器是DHTML 的基礎,使用它可以通過指令碼程式對其屬性進行操作,如設定其style 樣式的display 屬性來控制子節點的展開和隱藏。節點的位置、前置圖片、樣式、針對該節點的其他的操作等都放入容器中,例:
< DIV id =mParentID>
< IMG align = center border = 0 onclick =″nodeExpand (‘leafid’)″ name = m1Tree src =′Tplus.gif′>
< IMG align = center border = 0 name = m1Folder src =′folderClosed. gif′> 電腦學院 </DIV>
葉子節點無需容器直接輸出即可。
當點擊某節點前的“ + ”、“ - ”圖片時通過DIV 的style 樣式的display 屬性控制子節點的展開和隱藏。display:“none”(隱藏,不可見),display:“block”(顯示) 。相關JavaScript 代碼如下:
if (expandChild.style.display = =″none″){
// 當前為隱藏狀態,執行展開動作
this.Loading(parentObject);//判斷該分支的資料是否已經載入
expandChild.style.display =″block″;
if (para2 = =″last″)
parentObject.src =″Lminus. gif″; // 最後一個節點
else
parentObject.src = ″Tminus. gif″; // 顯示┠
expandFolder.src = ″folderOpen. gif″;
}else {
// 將當前節點的子節點全部隱藏
expandChild.style.display = ″none″;
if (para2 = = ″last″)
parentObject.src = ″Lplus. gif″;
else
parentObject.src = ″Tplus. gif″;
expandFolder.src = ″folderClosed. gif″;
}
2.2 樹型表結構設計
我們以資料庫為載體記錄節點的變化,樹型表結構至少要有以下欄位:節點的編號(CLASSID) ,對節點的描述(ClassName),父節點的編號(ParentId),這些是構建樹結構所必須的資訊。同時引入節點的類別代碼(ClassCode),節點的層級(ClassLevel),是否葉子節點 (Terminated)等輔助欄位,記錄節點次序,實體關聯圖如圖3所示。
樹遍曆的時間複雜度是O( n ),但是將樹資訊存放到資料庫後,就不能按傳統的方式遍曆樹,必須使用SQL 陳述式訪問資料庫表的內容,而一次性取的資料量越多,消耗的資源也越多,使用者等待的時間就越長。如果將無序的資料從資料庫中讀出,在伺服器端,必須將排序後的樹送到用戶端顯示。因此,最好從資料庫讀出已排好序的樹。
我們知道,字串排序是按照字典序形式。結合SQL 陳述式的特點和樹結構特點,資料庫表中,節點的類別代碼採用多級字串形式,如AAABBBCCC,從樹根節點開始,每向下一級字串就增加一級,並且子節點類別代碼以父節點類別代碼開始,再開始本級的類別代碼。同級的節點按照產生的順序編號,如節點類別代碼為AAA 的下一級孩子類別代碼為AAAAAA,AAAAAB 等,AAAAAB 的孩子節點為AAAAABAAA、AAAAABAAB等。每一級編號字元的寬度與實際的應用關聯,如AAA~ZZZ 一級則有263 個節點,如果不夠用再增加一個字元用於編碼。該巧妙的編號方式。使得在執行SQL 陳述式select * from tree_class order by classcode 後,一次獲得完整的先序樹。
2.3 商務邏輯層設計
2.3.1 動態載入技術
如果一次性擷取完整的先序樹,構造成xml提供給JavaScript解析,資料量越大,消耗的資源越多,用戶端響應延遲時間就越長,因此對於大資料量的樹,採用動態載入方式,即每次單擊“+”圖片時,判斷是否已載入子節點資料,如果未載入則通過Ajax的XMLHTTP組件XMLHTTPRequest對象非同步發送請求,串連伺服器執行SQL 陳述式“select * from tree_class where parent = 。order by classcode ”擷取節點資料。相關JavaScript 代碼如下:
/*判斷是否已經載入資料,未載入則訪問伺服器載入資料*/
dhtmlTree.prototype.Loading=function(pObject){
if(((pObject.XMLload==0)&&(this.XMLsource))&&(!this.XMLloading)){
pObject.XMLload=1;
this.loadXML(this.XMLsource+getUrlSymbol(this.XMLsource)+"id="+escape(pObject.id));
}
}
dtmlXMLObject.prototype.loadXML=function(url){//載入資料
try {
this.xmlDoc = new XMLHttpRequest();
/*通過GET方法非同步串連到 url 載入資料*/
this.xmlDoc.open("GET", url,true);//true:非同步;false:同步
this.xmlDoc.send(null);
} catch(e){
this.xmlDoc = new ActiveXObject("Microsoft.XMLHTTP");//使用IE
this.xmlDoc.open("GET", url,true);//true:非同步;false:同步
this.xmlDoc.send(null);
}
return this.xmlDoc.responseXML;
}
每次只取同一個父節點ParentId的子節點序列,按XML格式封裝成樹的文檔結構,例如:
<tree id="0">
<leaf child=”1" name="國防科技大學" id="1" im0="leaf.gif" im1="folderOpen.gif" im2=" folderClosed.gif"/>
</tree>
提供給JavaScript的dhtmlTreeObject.prototype.insertItem()解析並組織好html輸出節點;其中child:1表示有子節點,0表示沒有子節點;im0表示沒有子節點時的表徵圖;im1表示有子節點並且開啟節點時的表徵圖;im2表示有子節點並且關閉時的表徵圖;所以還可以在構造XML時自訂表徵圖。
2.3.2 樹型結構的構造
從資料庫中返回的是有序的先序樹,而XML是完整的樹型結構文檔,所以將樹型資料構造成預定義的XML格式,只需從根節點開始,遍曆一遍樹,即可將樹全部產生。相關JavaScript代碼如下:
/*動態載入樹的構造方法*/
dtmlXMLObject.prototype.constructTree=function(){
//採用動態載入時擷取的xml資料,解析樹型資料
var node=this.XMLLoader.getXMLTopNode("tree");
var parentId=node.getAttribute("id");
for(var i=0;i<node.childNodes.length;i++) { //逐個解析xml檔案的leaf節點
if((node.childNodes[i].nodeType==1)&&(node.childNodes[i].tagName == "leaf")){
var name=node.childNodes[i].getAttribute("text");
…………
var temp=dhtmlObject.a0Find(parentId);//擷取父節點對象
temp.XMLload=1;//已載入
//構造html輸出節點
dhtmlObject.insertItem(parentId,cId,name,im0,im1,im2,chd);
dhtmlObject.addDragger = this;//設定可拖放的對象
};
}
2.3.3 樹型結構的維護
在維護樹型結構表時,刪除節點較為簡單,SQL 陳述式為: "delete from tree_class where classcode like′"+ classcode +"%′",即可將其節點和孩子一併刪除;增加節點時,分為前插、後插、和插入子節點三種情況,前兩種情況需要更新遞迴更新類別代碼,後者只需找到父節點的孩子的最大類別代碼加1 後,作為增加節點的類別代碼;通過拖放來改變樹的結構時,只需將拖動節點的parentId更新為目標節點的Classid即可,對應的SQL語句為:"update tree_class set parentId = "+ classidTo+" where classid = "+ classidFrom。
3、效率分析
對於樹的儲存一般有兩種形式:二維表和鏈表,遍曆方式一般也有深度遍曆和廣度遍曆兩種方式,遍曆的時間複雜度都是O( n )。用二維表格儲存體時,在記憶體中用數組的下標能準確定位節點的父節點、兄弟節點所在的數組下標。資料庫中節點的定位也是準確的,但是將節點資訊從資料庫中讀到記憶體中時,如果無法通過記憶體數組下標定位節點資訊,那麼就必須遍曆一遍尋找一個節點,n 個節點中尋找一個節點的時間是O(n/2),n 個節點排序的時間複雜度將是O( n2/2),這也是一般實現的B/S 模式的樹結構效率低下的原因。本方案採用字典序編號方案,使得從資料庫中取得的樹是已經排序的,直接遍曆產生客戶頁面程式,時間複雜度為O( n )。
4、結 論
本文討論了基於Ajax的動態樹型結構的實現方案,支援無重新整理動態維護樹的節點資訊,支援拖放節點改變樹的節點結構以及次序;同時採用資料庫儲存節點資訊,保證了該方案有一定的通用性,此外結合XML描述樹的節點資訊,使得任何按本方案預定的xml文檔描述的資訊都可以通過樹來展現。本方案已經應用在我校的數字迎新系統以及老百姓大藥房資訊系統中。