使用文件系統(tǒng)實(shí)現(xiàn)隊(duì)列數(shù)據(jù)結(jié)構(gòu)

隊(duì)列(Queue)是一種很常用的數(shù)據(jù)結(jié)構(gòu)。之前我都是在運(yùn)行內(nèi)存中使用這種數(shù)據(jù)結(jié)構(gòu)司顿。但是如果需要一個(gè)可以持久化的隊(duì)列緩存芒粹,僅僅是使用內(nèi)存中的隊(duì)列數(shù)據(jù)結(jié)構(gòu)就不行了,因?yàn)楹?jiǎn)單對(duì)隊(duì)列對(duì)象進(jìn)行序列化和反序列化處理免猾,又顯的不夠高效是辕,所以就考慮到使用文件系統(tǒng)(File System)來實(shí)現(xiàn)一個(gè)隊(duì)列數(shù)據(jù)結(jié)構(gòu)。

1. 基本需求

  1. 要滿足隊(duì)列最基本的特征:“先進(jìn)先出”(FIFO)以及一些標(biāo)準(zhǔn)的操作
  2. 隊(duì)列中的元素可以是任意數(shù)據(jù)類型
  3. 可以隨時(shí)對(duì)隊(duì)列中數(shù)據(jù)進(jìn)行讀取和寫入

2. 實(shí)現(xiàn)思路

我查閱的一些資料后猎提,發(fā)現(xiàn)了一個(gè) Java 版的實(shí)現(xiàn) 基本可以滿足需求获三。因?yàn)槲乙?iOS 中使用這個(gè)隊(duì)列,所以要用 Objective-C 改寫一下锨苏。在改寫完成下面把使用文件系統(tǒng)實(shí)現(xiàn)隊(duì)列的基本思路和要點(diǎn)記錄下來疙教,這樣以后用其他語言也都能實(shí)現(xiàn)了。

先介紹一下文件結(jié)構(gòu):

隊(duì)列文件結(jié)構(gòu):頭文件 + 數(shù)據(jù)段(包含很多個(gè)元素)伞租。

元素文件結(jié)構(gòu):元素頭文件 + 元素?cái)?shù)據(jù)段贞谓。

2.1 隊(duì)列文件的頭文件

這個(gè)頭文件是用來描述整個(gè)隊(duì)列,我們需要定義以下這些值:

  • 文件長(zhǎng)度葵诈,用于在新插入一個(gè)元素時(shí)裸弦,判斷是否需要增加這個(gè)文件的長(zhǎng)度
  • 元素個(gè)數(shù),方便的實(shí)現(xiàn)查詢隊(duì)列中元素個(gè)數(shù)
  • 第一個(gè)元素?cái)?shù)據(jù)段開始位置作喘,用于實(shí)現(xiàn) peek 操作
  • 最后一個(gè)元素?cái)?shù)據(jù)段的開始理疙,用于實(shí)現(xiàn) Add 操作

這幾個(gè)數(shù)據(jù)都是int 類型,這樣頭文件就是固定長(zhǎng)度泞坦。在讀取文件時(shí)窖贤,在固定位置就能讀到相應(yīng)的值。

代碼:

-(void)readFileHeader
{
    [self.fh seekToFileOffset:0];
    
    NSData *data = [self.fh readDataOfLength:HEADER_LENGTH];
    
    self.headBuffer = [NSMutableData dataWithData:data];
    
    self.fileLength = [self readInt:self.headBuffer Offset:0];
    
    self.elementCount = [self readInt:self.headBuffer Offset:4];
    
    int firstPos = [self readInt:self.headBuffer Offset:8];
    self.first = [self readElement:firstPos];
    
    int lastPos  = [self readInt:self.headBuffer Offset:12];
    self.last = [self readElement:lastPos];
}

2.2 元素頭文件

描述元素本身的頭文件包括的數(shù)據(jù)有:

  • 元素自身開始的位置
  • 元素?cái)?shù)據(jù)長(zhǎng)度

就像這樣:

typedef struct
{
    int position; 
    int length;
}QueueElement;

由于頭文件長(zhǎng)度固定贰锁,通過這兩個(gè)文件就可以讀取到這個(gè)元素的數(shù)據(jù)赃梧,已及下一元素開始的位置。

2.3 實(shí)現(xiàn) Peek 操作

Peek 操作就是讀取隊(duì)列中的第一個(gè)元素豌熄。由于在隊(duì)列頭文件中保存了第一個(gè)元素?cái)?shù)據(jù)的段的開始位置授嘀。再加上元素頭文件的信息,我們就可以順利讀出第一個(gè)元素房轿。

第一個(gè)元素的數(shù)據(jù)開始的位置 = 第一個(gè)元素開始的位置 + 元素頭文件長(zhǎng)度粤攒。

再加上已知數(shù)據(jù)的長(zhǎng)度所森,這樣的就可以順利讀出數(shù)據(jù)了。如果數(shù)據(jù)只是增加夯接,也就是文件長(zhǎng)度一值在增長(zhǎng)焕济,這種讀取方式就夠了。但是盔几,實(shí)際情況是晴弃,有可能我們會(huì)移除隊(duì)列中的元素,為能夠重復(fù)利用空間逊拍,新加入的元素可能會(huì)利用到已刪除元素的空間上鞠,所以在讀取的時(shí)候也要考慮到這種情況,這就是環(huán)形讀刃旧ァ(ring read)芍阎。

2.2.1 環(huán)形讀取

第一種情況:當(dāng)元素?cái)?shù)據(jù)段開始位置加上元素?cái)?shù)據(jù)段長(zhǎng)度小于隊(duì)列文件長(zhǎng)度,只需要正常讀取缨恒。

第二種情況谴咸,當(dāng)元素?cái)?shù)據(jù)段開始位置加上元素?cái)?shù)據(jù)段長(zhǎng)度大于隊(duì)列文件長(zhǎng)度,這就說明骗露,數(shù)據(jù)段并沒有完全寫在文件物理位置的尾端岭佳,而是可能還有一部分寫在的物理位置的前端,所以在讀取時(shí)需要考慮到這兩部分的數(shù)據(jù)萧锉。

代碼:

-(NSData *)ringRead:(int)startPos DataLength:(int)dLength
{
    startPos = [self wrapPosition:startPos];
    
    if (startPos + dLength <= self.fileLength)
    {
        [self.fh seekToFileOffset:startPos];
        NSData *data = [self.fh readDataOfLength:dLength];
        
        return data;
    }
    else
    {
        int countBeforeEOF = self.fileLength - startPos;
        [self.fh seekToFileOffset:startPos];
        
        NSData *beforeDataRead = [self.fh readDataOfLength:countBeforeEOF];//讀取尾端數(shù)據(jù)
        
        NSMutableData *readData = [NSMutableData dataWithData:beforeDataRead];
        
        int countAfterEOF = dLength - countBeforeEOF;
        [self.fh seekToFileOffset:HEADER_LENGTH];
        
        NSData *afterDataRead = [self.fh readDataOfLength:countAfterEOF];//讀取前端數(shù)據(jù)
        
        [readData appendData:afterDataRead];
        
        return readData;
    }
}

2.4 實(shí)現(xiàn) Add 操作

向隊(duì)列中添加一個(gè)元素珊随,這個(gè)元素需要添加在當(dāng)前最后一個(gè)元素的后面,同樣考慮到重復(fù)利用空間的問題柿隙,我們需要考慮環(huán)形寫入(ring write)叶洞。

2.4.1 環(huán)形寫入

第一種情況:當(dāng)需要寫入的元素?cái)?shù)據(jù)段開始位置加上元素?cái)?shù)據(jù)段長(zhǎng)度小于隊(duì)列文件長(zhǎng)度,直接從最后一個(gè)元素的尾端開始寫入就行禀崖。

第二種情況:當(dāng)需要寫入的元素?cái)?shù)據(jù)段開始位置加上元素?cái)?shù)據(jù)段長(zhǎng)度小于隊(duì)列文件長(zhǎng)度京办,這時(shí)就需要考慮當(dāng)前文件空閑空間的大小,如果空閑空間足夠帆焕,利用空閑空間進(jìn)行寫入,如果空間不夠就把文件長(zhǎng)度加大到足夠大再進(jìn)行寫入不恭。

代碼:

-(void)ringWrite:(int)startPos DataToWrite:(NSData *)data
{
    startPos = [self wrapPosition:startPos];
    
    if (startPos + [data length] <= self.fileLength)
    {
        [self.fh seekToFileOffset:startPos];
        [self.fh writeData:data];
        [self.fh synchronizeFile];
    }
    else
    {
        int countBeforeEOF = self.fileLength - startPos;
        NSData *beforeDataToWrite
                = [data subdataWithRange:NSMakeRange(0, countBeforeEOF)];
        
        [self.fh seekToFileOffset:startPos];
        [self.fh writeData:beforeDataToWrite];
        
        int countAfterEOF   = (int)([data length] - countBeforeEOF);
        NSData *afterDataToWrite
                = [data subdataWithRange:NSMakeRange(countBeforeEOF, countAfterEOF)];
        
        [self.fh seekToFileOffset:HEADER_LENGTH];
        [self.fh writeData:afterDataToWrite];
        
        [self.fh synchronizeFile];
    }
}

在增加文件長(zhǎng)度的時(shí)候叶雹,為保障在寫入文件時(shí)數(shù)據(jù)正確,就要使最后一個(gè)元素后有足夠的連續(xù)空間换吧,這樣就需要把元素在物理上從前到后進(jìn)行連續(xù)排列折晦。

代碼:

-(void)expandFileLengthIfNecessary:(int)dataLength
{
    int lengthToWrite = ELEMENT_HEADER_LENGTH + dataLength;
    
    int remainLength = [self getRemainLengthInFile];
    
    if (remainLength >= lengthToWrite)
    {
        return;
    }
    else
    {
        NSLog(@"Expand!");
        int previousLength = self.fileLength;
        int newLength;
        do
        {
            remainLength += previousLength;
            newLength = previousLength << 1;
            previousLength = newLength;
        } while (remainLength < lengthToWrite);
      
        [self.fh truncateFileAtOffset:newLength]; //增加文件長(zhǎng)度
        
        [self.fh seekToFileOffset:0];
        
        // If the buffer is split, we need to make it contiguous. //保證空間連續(xù)性,對(duì)空間元素進(jìn)行物理上的排序
        if(self.last.position < self.first.position)
        {
            [self.fh seekToFileOffset:0];
            NSData *fileData = [self.fh readDataToEndOfFile];
            
            NSMutableData *newFileData = [NSMutableData dataWithData:fileData];
            
            int moveLength = (self.last.position + ELEMENT_HEADER_LENGTH + self.last.length) - HEADER_LENGTH;
            
            NSData *dataToMove = [newFileData subdataWithRange:NSMakeRange(HEADER_LENGTH, moveLength)];
            
            [newFileData replaceBytesInRange:NSMakeRange(self.fileLength, moveLength) withBytes:[dataToMove bytes]];
            
            [self.fh writeData:newFileData];
            
            int newLastElementPos = self.fileLength + self.last.position - HEADER_LENGTH;

            QueueElement newLastElement;
            newLastElement.position = newLastElementPos;
            newLastElement.length = self.last.length;

            self.last = newLastElement;
        }
        
        self.fileLength = newLength;
        
        [self writeFileHeader:self.fileLength ElementCount:self.elementCount
                     FirstPos:self.first.position LastPos:self.last.position];
    }
}

2.5 其他操作

其他操作無非就是對(duì)頭文件的更新和讀取沾瓦,在這里就不詳細(xì)描述了满着,具體代碼可以查看項(xiàng)目中的代碼谦炒。

3. 總結(jié)

總結(jié)下來使用文件系統(tǒng)實(shí)現(xiàn)這隊(duì)列的要點(diǎn)有這么幾個(gè):

  1. 隊(duì)列數(shù)據(jù)結(jié)構(gòu)的特征
  2. 頭文件的定義
  3. 環(huán)形讀取和寫入
  4. 保持空間的連續(xù)性

4. 項(xiàng)目地址

https://github.com/wuzhou/oc-file-queue

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市风喇,隨后出現(xiàn)的幾起案子宁改,更是在濱河造成了極大的恐慌,老刑警劉巖魂莫,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件还蹲,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡耙考,警方通過查閱死者的電腦和手機(jī)谜喊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來倦始,“玉大人斗遏,你說我怎么就攤上這事⌒兀” “怎么了诵次?”我有些...
    開封第一講書人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)炫狱。 經(jīng)常有香客問我藻懒,道長(zhǎng),這世上最難降的妖魔是什么视译? 我笑而不...
    開封第一講書人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任嬉荆,我火速辦了婚禮,結(jié)果婚禮上酷含,老公的妹妹穿的比我還像新娘鄙早。我一直安慰自己,他們只是感情好椅亚,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開白布限番。 她就那樣靜靜地躺著,像睡著了一般呀舔。 火紅的嫁衣襯著肌膚如雪弥虐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,737評(píng)論 1 305
  • 那天媚赖,我揣著相機(jī)與錄音霜瘪,去河邊找鬼。 笑死惧磺,一個(gè)胖子當(dāng)著我的面吹牛颖对,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播磨隘,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼缤底,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼顾患!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起个唧,我...
    開封第一講書人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤江解,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后坑鱼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體膘流,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年鲁沥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了呼股。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡画恰,死狀恐怖彭谁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情允扇,我是刑警寧澤缠局,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站考润,受9級(jí)特大地震影響狭园,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜糊治,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一唱矛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧井辜,春花似錦绎谦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至刷允,卻和暖如春冤留,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背树灶。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工搀菩, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人破托。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像歧蒋,于是被迫代替她去往敵國(guó)和親土砂。 傳聞我的和親對(duì)象是個(gè)殘疾皇子州既,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容