C語言插入、刪除、更改檔案內容
我們平時所見的檔案,例如 txt、doc、mp4 等,檔案內容是按照從頭到尾的順序依次儲存在磁碟上的,就像排起一條長長的隊伍,稱為順序檔案。
除了順序檔案,還有索引檔案、雜湊檔案等,一般用於特殊領域,例如資料庫、高效檔案系統等。
順序檔案的儲存結構決定了它能夠高效讀取內容,但不能夠隨意插入、刪除和修改內容。例如在檔案開頭插入100個位元組的資料,那麼原來檔案的所有內容都要向後移動100個位元組,這不僅是非常低效的操作,而且還可能覆蓋其他檔案。因此C語言沒有提供插入、刪除、修改檔案內容的函式,要想實現這些功能,只能自己編寫函式。
以插入資料為例,假設原來檔案的大小為 1000 位元組,現在要求在500位元組處插入使用者輸入的字串,那麼可以這樣來實現:
- 1) 建立一個臨時檔案,將後面500位元組的內容複製到臨時檔案;
- 2) 將原來檔案的內部指標調整到500位元組處,寫入字串;
- 3) 再將臨時檔案中的內容寫入到原來的檔案(假設字串的長度為100,那麼此時檔案內部指標在600位元組處)。
刪除資料時,也是類似的思路。假設原來檔案大小為1000位元組,名稱為 demo.mp4,現在要求在500位元組處往後刪除100位元組的資料,那麼可以這樣來實現:
- 1) 建立一個臨時檔案,先將前500位元組的資料複製到臨時檔案,再將600位元組之後的所有內容複製到臨時檔案;
- 2) 刪除原來的檔案,並建立一個新檔案,命名為 demo.mp4;
- 3) 將臨時檔案中的所有資料複製到 demo.mp4。
修改資料時,如果新資料和舊資料長度相同,那麼設定好內部指標,直接寫入即可;如果新資料比舊資料長,相當於增加新內容,思路和插入資料類似;如果新資料比舊資料短,相當於減少內容,思路和刪除資料類似。實際開發中,我們往往會保持新舊資料長度一致,以減少程式設計的工作量,所以我們不再討論新舊資料長度不同的情況。
總起來說,本節重點討論資料的插入和刪除。
檔案複製函式
在資料的插入刪除過程中,需要多次複製檔案內容,我們有必要將該功能實現為一個函式,如下所示:
- /**
- * 檔案複製函式
- * @param fSource 要複製的原檔案
- * @param offsetSource 原檔案的位置偏移(相對檔案開頭),也就是從哪裡開始複製
- * @param len 要複製的內容長度,小於0表示複製offsetSource後邊的所有內容
- * @param fTarget 目標檔案,也就是將檔案複製到哪裡
- * @param offsetTarget 目標檔案的位置偏移,也就是複製到目標檔案的什麼位置
- * @return 成功複製的位元組數
- **/
- long fcopy(FILE *fSource, long offsetSource, long len, FILE *fTarget, long offsetTarget){
- int bufferLen = 1024*4; // 緩衝區長度
- char *buffer = (char*)malloc(bufferLen); // 開闢快取
- int readCount; // 每次呼叫fread()讀取的位元組數
- long nBytes = 0; //總共複製了多少個位元組
- int n = 0; //需要呼叫多少次fread()函式
- int i; //迴圈控制變數
- fseek(fSource, offsetSource, SEEK_SET);
- fseek(fTarget, offsetTarget, SEEK_SET);
- if(len<0){ //複製所有內容
- while( (readCount=fread(buffer, 1, bufferLen, fSource)) > 0 ){
- nBytes += readCount;
- fwrite(buffer, readCount, 1, fTarget);
- }
- }else{ //複製len個位元組的內容
- n = (int)ceil((double)((double)len/bufferLen));
- for(i=1; i<=n; i++){
- if(len-nBytes < bufferLen){ bufferLen = len-nBytes; }
- readCount = fread(buffer, 1, bufferLen, fSource);
- fwrite(buffer, readCount, 1, fTarget);
- nBytes += readCount;
- }
- }
- fflush(fTarget);
- free(buffer);
- return nBytes;
- }
該函式可以將原檔案任意位置的任意長度的內容複製到目標檔案的任意位置,非常靈活。如果希望實現《C語言實現檔案複製功能(包括文字檔案和二進位制檔案)》一節中的功能,那麼可以像這面這樣呼叫:
fcopy(fSource, 0, -1, fTarget, 0);檔案內容插入函式
請先看程式碼:
- /**
- * 向檔案中插入內容
- * @param fp 要插入內容的檔案
- * @param buffer 緩衝區,也就是要插入的內容
- * @param offset 偏移量(相對檔案開頭),也就是從哪裡開始插入
- * @param len 要插入的內容長度
- * @return 成功插入的位元組數
- **/
- int finsert(FILE *fp, long offset, void *buffer, int len){
- long fileSize = fsize(fp);
- FILE *fpTemp; //臨時檔案
- if(offset>fileSize || offset<0 || len<0){ //插入錯誤
- return -1;
- }
- if(offset == fileSize){ //在檔案末尾插入
- fseek(fp, offset, SEEK_SET);
- if(!fwrite(buffer, len, 1, fp)){
- return -1;
- }
- }
- if(offset < fileSize){ //從開頭或者中間位置插入
- fpTemp = tmpfile();
- fcopy(fp, 0, offset, fpTemp, 0);
- fwrite(buffer, len, 1, fpTemp);
- fcopy(fp, offset, -1, fpTemp, offset+len);
- freopen(FILENAME, "wb+", fp );
- fcopy(fpTemp, 0, -1, fp, 0);
- fclose(fpTemp);
- }
- return 0;
- }
程式碼說明:
1) fsize() 是在《C語言獲取檔案大小(長度)》自定義的函式,用來獲取檔案大小(以位元組計)。
2) 第17行判斷資料的插入位置,如果是在檔案末尾,就非常簡單了,直接用 fwrite() 寫入即可。
3) 如果從檔案開頭或中間插入,就得建立臨時檔案。
tmpfile() 函式用來建立一個臨時的二進位制檔案,可以讀取和寫入資料,相當於 fopen() 函式以"wb+"方式開啟檔案。該臨時檔案不會和當前已存在的任何檔案重名,並且會在呼叫 fclose() 後或程式結束後自動刪除。
檔案內容刪除函式
請看下面的程式碼:
- int fdelete(FILE *fp, long offset, int len){
- long fileSize = getFileSize(fp);
- FILE *fpTemp;
- if(offset>fileSize || offset<0 || len<0){ //錯誤
- return -1;
- }
- fpTemp = tmpfile();
- fcopy(fp, 0, offset, fpTemp, 0); //將前offset位元組的資料複製到臨時檔案
- fcopy(fp, offset+len, -1, fpTemp, offset); //將offset+len之後的所有內容都複製到臨時檔案
- freopen(FILENAME, "wb+", fp ); //重新開啟檔案
- fcopy(fpTemp, 0, -1, fp, 0);
- fclose(fpTemp);
- return 0;
- }
檔案第5~7行用來判斷傳入的引數是否合法。freopen() 以"w+"方式開啟檔案時,如果有同名的檔案存在,那麼先將檔案內容刪除,作為一個新檔案對待。