結合筆者開發(fā)的模型庫管理系統(tǒng),本文以Visual C++ 6.0和access 2003環(huán)境下的數(shù)據(jù)庫為例,介紹利用ADO實現(xiàn)對超長數(shù)據(jù)庫字段的訪問,包括寫入和讀出。
設計數(shù)據(jù)庫
使用Access2003作為數(shù)據(jù)庫系統(tǒng),數(shù)據(jù)庫名為blob,唯一的一個數(shù)據(jù)表為blob,如圖1所示。
圖1
其中包括四個字段,分別是id(文本),name(文本),data(OLE對象)和suffix(后綴名),其字段類型必須是OLE對象。id作為主鍵,name是該blob文件的文件名,data字段用來保存二進制大對象,suffix是二進制文件的后綴名,可以是rm,avi,bmp,mp3等。
系統(tǒng)的實現(xiàn):
1、SafeArray:
在對BLOB進行操作時,要用到SAFEARRAY結構。SAFEARRAY是一種結構化的數(shù)據(jù)類型,包含了一個由其它數(shù)據(jù)類型的數(shù)據(jù)元素組成的數(shù)組。之所以稱之為安全的數(shù)組是因為它包含了每一維的邊界信息,并限制在邊界內(nèi)進行數(shù)組元素的訪問。其Win32定義SAFEARRAY如下:
typedef struct tagSAFEARRAY { unsigned short cDims; unsigned short fFeatures; unsigned long cbElements; unsigned long cLocks; void * pvData; SAFEARRAYBOUND rgsabound[ 1 ]; } SAFEARRAY; |
這個結構的成員(cDims,cLocks等)是通過API函數(shù)來設置和管理的。真正的數(shù)據(jù)存放在pvData成員中,而SAFEARRAYBOUND結構定義該數(shù)組結構的細節(jié)。以下就是該結構成員的簡要描述:
成員 | 描述 |
cDims | 數(shù)組的維數(shù) |
fFeatures | 用來描述數(shù)組如何分配和如何被釋放的標志 |
cbElements | 數(shù)組元素的大小 |
cLocks | 一個計數(shù)器,用來跟蹤該數(shù)組被鎖定的次數(shù) |
pvData | 指向數(shù)據(jù)緩沖的指針 |
rgsabound | 描述數(shù)組每維的數(shù)組結構,其大小是可變的 |
rgsabound是一個有趣的成員,它的結構不太直觀。它是數(shù)據(jù)范圍的數(shù)組。該數(shù)組的大小依safe array維數(shù)的不同而有所區(qū)別。rgsabound成員是一個SAFEARRAYBOUND結構的數(shù)組--每個元素代表SAFEARRAY的一個維。
typedef struct tagSAFEARRAYBOUND { unsigned long cElements; unsigned long lLbound; } SAFEARRAYBOUND; |
維數(shù)被定義在cDims成員中。例如,一個'C'類數(shù)組的維數(shù)可以是[3][4][5]-一個三維的數(shù)組。如果我們使用一個SAFEARRAY來表示這個結構,我們定義一個有三個元素的rgsabound數(shù)組--一個代表一維。cDims = 3; SAFEARRAYBOUND rgsabound[ 3 ]; rgsabound[0]元素定義第一維。在這個例子中ILBOUND元素為0,是數(shù)組的下界。cElements成員的值等于三。數(shù)組的第二維([4])可以被rgsabound結構的第二個元素定義。下界也可以是0,元素的個數(shù)是4,第三維也是這樣。
2、將二進制文件寫入到數(shù)據(jù)庫:
由于這個二進制文件可能很大,所以無法將所有內(nèi)容一次性讀入到內(nèi)存。我們需要多次讀入,每次可以使用函數(shù)CFile::Read()從文件中讀出一個數(shù)據(jù)包,然后調(diào)用Field對象的AppandChrnk()函數(shù)將該包讀入數(shù)據(jù)庫。AppandChrnk()函數(shù)包含在Field對象中,原型如下:HRESULT AppendChunk (const _variant_t & Data );從函數(shù)原型中可以看到關鍵的問題是我們需把二進制數(shù)據(jù)賦值給VARIANT類型的變量。實現(xiàn)的關鍵代碼如下:
while(1) { uIsRead=f.Read(bVal,ChunkSize); if(uIsRead==0) break; rgsabound[0].cElements =uIsRead; rgsabound[0].lLbound = 0; psa = SafeArrayCreate(VT_UI1,1,rgsabound); ///創(chuàng)建SAFEARRAY對象 for(long index=0;index<UISREAD;INDEX++) SafeArrayPutElement(psa,&index,&bVal[index]); varChunk.vt = VT_ARRAY|VT_UI1; varChunk.parray = psa; //加入BLOB類型的數(shù)據(jù) m_pRecordset->Fields->GetItem("data")->AppendChunk(varChunk); ::VariantClear(&varChunk); ::SafeArrayDestroyData( psa); if(uIsRead<<CHUNKSIZE) break; } |
我們所有的讀入數(shù)據(jù)工作都在一個while循環(huán)中實現(xiàn),每次讀入一個數(shù)據(jù)包,直到讀完這個數(shù)據(jù),即數(shù)據(jù)量為二進制數(shù)據(jù)的長度ChunkSize。其中*pBuf為指向緩沖區(qū)指針,即要讀入的數(shù)據(jù)包。ChunkSize為VARIANT變量,用于保存二進制數(shù)據(jù),psa是指向安全數(shù)組SAFEARRAY的指針。
3、從數(shù)據(jù)庫讀出二進制對象到文件:
同樣,由于這個二進制對象可能很大,無法將所有內(nèi)容一次性讀入內(nèi)存中對應于保存數(shù)據(jù)時我們所使用的AppendChunk函數(shù),讀取數(shù)據(jù)應該使用GetChunk函數(shù),GetChunk的原型如下:
_variant_t GetChunk (long Length );唯一的參數(shù)Length代表需要讀取的字節(jié)數(shù)。
實現(xiàn)的關鍵代碼如下:
long lBlobSize = m_pRecordset->Fields-> Item["data"] ->ActualSize; while(lBlobSize>0) { lIsRead= lBlobSize >=ChunkSize? ChunkSize: lBlobSize; //從字段data中獲取一個數(shù)據(jù)包 varChunk = m_pRecordset->Fields->Item["data"]->GetChunk(lIsRead); for(index=0;index<LISREAD;INDEX++) ::SafeArrayGetElement(varChunk.parray,&index,buf+index); //將數(shù)據(jù)包寫入文件 f.Write(buf,lIsRead); lBlobSize -=lIsRead; } |
其中f是一個CFile對象,代表了要存儲的文件。Long型變量lBlobSize記錄了二進制對象的大小。利用一個while循環(huán),每次從數(shù)據(jù)庫中讀取lIsRead字節(jié),直至將其全部讀出。
結束語
由于信息技術的飛速發(fā)展,特別是多媒體技術的廣泛運用,可以預見,以后數(shù)據(jù)庫中大對象的應用將日益普遍,有關大對象的存取勢必將是數(shù)據(jù)庫技術的一個發(fā)展方向。本文討論了二進制大對象BLOB在數(shù)據(jù)庫中的存儲與讀取的VC實現(xiàn),能夠滿足基本的系統(tǒng)要求,具備了一定的可移植性,為今后更廣泛的應用和更深入的研究提供的借鑒基礎。