詳解nodejs 文本操作模塊-fs模塊(三)
下面繼續(xù)nodejs的學習,在前兩篇中,已經(jīng)把文件操作的打開,關閉讀寫這兩個最基本的功能進行了簡單的說明,它們的強大之處,讓我覺得知道這幾種方法之后,基本上就可以隨意的操作文件了,但是open,read,write等方法,需要操作的參數(shù)確實是有點多的,所以,基于讓使用者更簡單的完成讀寫操作,開發(fā)者們,繼續(xù)給這些方法做了進一步的封裝,也就是本文接下來將要說的readFile,和writeFile方法,當然也有他們的同步執(zhí)行方法,只是篇幅有限,并且同步的方法和異步的方法,在內(nèi)部實現(xiàn)和參數(shù)使用中,差別不大,所以在以后的文章中,基本上不會再涉及到同步方法了。
使用方法
fs.readFile(fileName,[options],callback);
其中:
fileName是表示您要操作的文件的地址,這個地址可以使用絕對地址,也可以使用相對地址,關于它可以支持的所有規(guī)則,可以參考之前文章中的path操作,path模塊,就是專門為了地址這個功能存在的。
options是讀取文件時,所需要的參數(shù),options是一個對象,它只包含兩個參數(shù):options = { encoding: “utf-8”, flag: 'r' },其中,encoding表示讀取文件成功后,返回的數(shù)據(jù)的編碼格式,默認返回格式為buffer對象,flag的值表示是如何讀取文件的,支持的參數(shù),與使用fs.open時,相同,具體請參考:文本操作模塊-fs模塊(一),但是在我個人看來,這里的flag取值一般也就是r,r+著兩種方式了,畢竟readFile就是為了讀取文件內(nèi)容才定義的。
callback是回調(diào)函數(shù),當改文件讀取成功時,執(zhí)行該文件,并且callback方法支持兩個參數(shù):
callback(err,data){ //err為讀取失敗時的錯誤對象,保持錯誤信息 //data為讀取成功時,返回的讀取信息,該信息的返回格式,是由options對象中的encoding決定 }
在接下來給出測試用例之前,我們再來想想另外的一個問題,那就是使用fs.read方法讀取文件時,我需要知道明確的傳入要讀取信息的長度,而在這里,我要讀取一整個文件的內(nèi)容,那么這個文件包含的內(nèi)容的總長度要怎么計算呢?這是一個問題,當然我也是在看readFile的源碼時,看到了這個,所以才在這里提前說明一下的。
對的,fs模塊中,提供了一個方法,可以讓你獲取到文件的一些基本信息,這個方法就是fs.fstat方法,它也是一個異步執(zhí)行的方法,使用方法如下:
var fs = require("fs"); fs.open('fs.js','r',function(err,fd){ if(err){ console.log("open file error"); //如果打開文件失敗時,會執(zhí)行到這里 return false; } fs.fstat(fd,function(err,state){ if(err){ console.log("err when fstate!"); return false; } console.log(state); //state是一個對象,其中包含著當前打開文件的一些基本信息 }); });
保持上面的代碼,然后在控制臺執(zhí)行的結果如下:
{ dev: 16777220, mode: 33279, nlink: 1, uid: 501, gid: 20, rdev: 0, blksize: 4096, ino: 1456286, size: 86418, blocks: 176, atime: Sat Aug 15 2015 15:46:59 GMT+0800 (CST), mtime: Sat Aug 15 2015 15:01:55 GMT+0800 (CST), ctime: Sat Aug 15 2015 15:01:55 GMT+0800 (CST), birthtime: Mon Aug 03 2015 22:47:02 GMT+0800 (CST) }
當我們使用console.log在控制臺打印信息時,只能顯示一些本身的屬性,其實state還支持一些方法,比如:isDirectory,isFile,isBlockDevice等方法,這里因為我平時也不會太用到nodejs做東西,所以對其中的很多屬性,都用不到,使用不到,所以也就導致,我對于這個屬性或者方法的含義,不能很好的理解,所以這里就不多說了。
至于關于state的東西,可以在fs.js中,查看查找fs.State構造函數(shù),既可以找到所有包含的信息。
測試用例
var fs = require("fs"); fs.readFile('test.js',"utf-8",function(err,data){ if(err){ console.log("readFile file error"); return false; } console.log(data); });
上面給出的是最簡單的示例了,因為我讀取的文件中,保持的內(nèi)容只是一段中文文本,所以這里使用的是utf-8的編碼格式,如果這里不傳入編碼格式,那么返回的data值則是一個Buffer對象。
readFile源碼分析
雖然這里叫做源碼分析,實質上,只是來一起看下,readFile在源碼中是如何實現(xiàn)的。該部分只有源碼,請查看源碼中對應的注釋,了解源碼的整改結構。
fs.readFile = function(path, options, callback_) { var callback = maybeCallback(arguments[arguments.length - 1]); //msybeCallback用來判斷是否為一個function,這里時判斷傳入的第二個參數(shù)是否為function //如果不是,那么就定義一個,這里對于后面的邏輯影響不大 //給options設置一些默認值,readFile的第二個參數(shù),只能是三種情況 //1:function,這個時候,options使用默認值,第二個參數(shù)為回調(diào)函數(shù) //2:string類型,這個時候,第二個參數(shù)為encoding的屬性值 //3:object類型,這個時候,表示options為一個完整的對象,可能包含也可能不包含encoding和flag屬性 //如果不為上述的三種類型,那么直接拋出一個類型異常,停止執(zhí)行 if (util.isFunction(options) || !options) { options = { encoding: null, flag: 'r' }; } else if (util.isString(options)) { options = { encoding: options, flag: 'r' }; } else if (!util.isObject(options)) { throw new TypeError('Bad arguments'); } var encoding = options.encoding; assertEncoding(encoding); //判斷encoding是否為當前支持的編碼類型,如果不支持,則拋出一個異常,停止執(zhí)行。 //判斷encoding的方法在Buffer模塊中,請參考前面的文章,文章地址,請在源碼分析的結尾查看。 // first, stat the file, so we know the size. var size; var buffer; // single buffer with file data var buffers; // list for when size is unknown var pos = 0; var fd; //參數(shù)驗證成功,開始執(zhí)行讀取數(shù)據(jù)的,設置flag默認值 var flag = options.flag || 'r'; //首先根據(jù)路徑,打開文件,open的使用,請參考前面的文章 fs.open(path, flag, 438 /*=0666*/, function(er, fd_) { //如果失敗,那么獲取到失敗的error對象,并返回該對象 if (er) return callback(er); //記錄打開文件的文件描述符 fd = fd_; //使用fstat,查看當前打開文件的一些基本信息。 //fstat獲取到的信息,請向前翻看。 fs.fstat(fd, function(er, st) { if (er) { //如果在執(zhí)行fstat時失敗,則執(zhí)行關閉文件的操作, //關閉之后,把錯誤信息,傳入readFile的回調(diào)函數(shù) return fs.close(fd, function() { callback(er); }); } size = st.size; //根據(jù)文件的大小,執(zhí)行不同的操作 //如果為空文件,則重新定義一個空的buffers,執(zhí)行read文件的方法 if (size === 0) { // the kernel lies about many files. // Go ahead and try to read some bytes. buffers = []; return read(); } //如果文件內(nèi)容,大于內(nèi)存所能保存的最大量,則拋出一個范圍異常。 if (size > kMaxLength) { var err = new RangeError('File size is greater than possible Buffer: ' + '0x3FFFFFFF bytes'); return fs.close(fd, function() { callback(err); }); } //否則,定義一個與內(nèi)容相當大小的Buffer對象,開始執(zhí)行read方法 buffer = new Buffer(size); read(); }); }); function read() { //根據(jù)size的不同,執(zhí)行兩個方法 //當讀取成功時,執(zhí)行afterRead方法。 //其中,size,buffer,fd,pos,等都是父級作用域的變量 if (size === 0) { buffer = new Buffer(8192); fs.read(fd, buffer, 0, 8192, -1, afterRead); } else { fs.read(fd, buffer, pos, size - pos, -1, afterRead); } } function afterRead(er, bytesRead) { if (er) { //讀取文件失敗時,根據(jù)失敗時的錯誤對象,執(zhí)行readFile的回調(diào)函數(shù) return fs.close(fd, function(er2) { return callback(er); }); } //如果讀取的數(shù)據(jù)量為0,則表示已經(jīng)讀取結束,則執(zhí)行close方法,結束readFile方法,并返回數(shù)據(jù) if (bytesRead === 0) { return close(); } //如果有值,則更改pos的值,也就是更改在read方法中,讀取文件起始位置的值。 pos += bytesRead; //如果pos的值,已經(jīng)等于文件的長度size了,則表示當前文件已經(jīng)讀取結束了,則關閉文件 //否則,繼續(xù)調(diào)用read方法,繼續(xù)讀取。 //如果size===0的話,有可能是fstat沒有能正常的讀取到size的值,就執(zhí)行后面的 if (size !== 0) { if (pos === size) close(); else read(); } else { // unknown size, just read until we don't get bytes. //猜測,這里可能是在某些系統(tǒng)下,無法獲取到文件的字節(jié)數(shù),所以添加的這個判斷。 buffers.push(buffer.slice(0, bytesRead)); read(); } } function close() { fs.close(fd, function(er) { //當文件讀取結束時,拼接讀取到的數(shù)組, if (size === 0) { // collected the data into the buffers list. buffer = Buffer.concat(buffers, pos); } else if (pos < size) { buffer = buffer.slice(0, pos); } //根據(jù)是否有encoding,做一次編碼轉換 if (encoding) buffer = buffer.toString(encoding); //把最終的數(shù)據(jù),傳入readFile的回調(diào)函數(shù)中。 return callback(er, buffer); }); } };
OK,上面就是源碼中,readFile的實現(xiàn)邏輯,源碼中,有提到了判斷encoding是否為當前支持的編碼方式的地方。
在前面我也說了句,在使用readFile時,設置flag的值,其實是無用的(我本人的想法,也可能是我資歷尚淺,沒有碰到過這樣的需求),但是不妨礙有些人為了測試或者好玩,對于readFile的時候,設置flag=“w+”的情況,當然這個時候,直接就報錯來吧。
前面的是readFile的相關東西,下面繼續(xù)看下寫文件的呢,也就是writeFile的方法來。
使用方法
fs.writeFile(fileName,data,[options],callback);
其中:
- fileName是表示您要操作的文件的地址,關于地址,請查看前面readFile方法時,注釋的鏈接。
- data,為需要寫入的數(shù)據(jù),可以直接是字符串,也可以是buffer數(shù)據(jù)。
- options是讀取文件時,所需要的參數(shù),options是一個對象,它只包含三個參數(shù):options = { encoding: “utf-8”, flag: 'r' ,mode:438},這里的三個參數(shù),其中encoding和flag和前面readFile所指代的含義相同,而mode所指代的含義,表示當前文件的操作權限,這個和fs.wirte時是相同的,可以參考:文本操作模塊-fs模塊(二)。
- callback是回調(diào)函數(shù),當改文件讀取成功時,執(zhí)行該文件,并且callback方法支持兩個參數(shù):
最簡單的示例:
var fs = require("fs"); fs.writeFile('test.js',"新添加的數(shù)據(jù)",{flag:"a+"},function(err,data){ if(err){ console.log("readFile file error"); return false; } console.log(data); });
這里說的示例,都是最簡單的示例,參數(shù)什么的,好多都沒有設置,因為在我看來,只要我能把源碼中相關的信息看懂,那么關于這些API的使用,我就可以幾乎找到它所有的使用方法,所以,在這里給出示例的時候,我都是給的一個最簡單的示例,然后后面繼續(xù)開始看下源碼中的信息,在源碼中,就可以把writeFile的使用方法,都能懂一些了。
writeFile源碼
繼續(xù)看下writeFile中的源碼實現(xiàn)邏輯,讓我們可以對writeFile有更深層次的了解。
function writeAll(fd, buffer, offset, length, position, callback) { //判斷最后一個是否為回調(diào)函數(shù),如果不是,給一個默認的回調(diào)函數(shù) //默認的回調(diào)函數(shù),在使用者來說,是無法訪問到的 callback = maybeCallback(arguments[arguments.length - 1]); // write(fd, buffer, offset, length, position, callback) //文件標志符是fd,需要寫入的數(shù)據(jù)是buffer,真實寫入數(shù)據(jù),是從buffer的offset位置開始 //取長度為length的數(shù)據(jù),寫入到fd指向的文件中,position的位置處。 fs.write(fd, buffer, offset, length, position, function(writeErr, written) { if (writeErr) { //如果寫入失敗,則關閉文件,執(zhí)行callback,傳入失敗信息保存對象 fs.close(fd, function() { if (callback) callback(writeErr); }); } else { //written為保存的數(shù)據(jù)byte值,如果意見全部保存,則保存完成,關閉文件 if (written === length) { fs.close(fd, callback); } else { //如果沒有保存完畢,則更新offset,length,position的值,繼續(xù)調(diào)用該方法, //保存剩下的數(shù)據(jù),直到把所有的數(shù)據(jù)保存成功為止。 offset += written; length -= written; position += written; writeAll(fd, buffer, offset, length, position, callback); } } }); } fs.writeFile = function(path, data, options, callback) { //判斷最后一個是否為回調(diào)函數(shù),如果不是,給一個默認的回調(diào)函數(shù) var callback = maybeCallback(arguments[arguments.length - 1]); //給options設置一些默認值,writeFile的第三個參數(shù),只能是三種情況 //1:function,這個時候,options使用默認值,第三個參數(shù)為回調(diào)函數(shù) //2:string類型,這個時候,第三個參數(shù)為encoding的屬性值 //3:object類型,這個時候,表示options為一個完整的對象,可能包含也可能不包含encoding和flag屬性 //如果不為上述的三種類型,那么直接拋出一個類型異常,停止執(zhí)行 //這里的判斷和readFile基本完全一樣,只是默認值不一樣而已 if (util.isFunction(options) || !options) { options = { encoding: 'utf8', mode: 438 /*=0666*/, flag: 'w' }; } else if (util.isString(options)) { options = { encoding: options, mode: 438, flag: 'w' }; } else if (!util.isObject(options)) { throw new TypeError('Bad arguments'); } //判斷當前設置的encoding是否為當前支持的encoding,如果不支持,會拋出一個異常, //停止繼續(xù)向下執(zhí)行 assertEncoding(options.encoding); //默認信息設置好,則打開文件,執(zhí)行寫入操作 var flag = options.flag || 'w'; fs.open(path, flag, options.mode, function(openErr, fd) { if (openErr) { //打開失敗時,把失敗原因,傳入writeFile的回調(diào)函數(shù) if (callback) callback(openErr); }else { //打開成功時,要把需要寫入的data信息,改為buffer對象 var buffer = util.isBuffer(data) ? data : new Buffer('' + data,options.encoding || 'utf8'); //判斷是否打開的方式,是否是追加模式, //這里讓我很疑惑的一個問題是,為什么這里要用正則表達式? //這樣簡單的判斷,直接食用indexOf,不是會有更高的效率? // var position = flag.indexOf("a") == -1 ? 0 : null; var position = /a/.test(flag) ? null : 0; //準備好了所有的信息,則開始使用writeAll方法,寫入文件 writeAll(fd, buffer, 0, buffer.length, position, callback); } }); };
OK,writeFile的源碼就是這樣了,其實這里還有一個就是追加到文件的方法,命名為appendFile,這個就不單獨來寫了,看下源碼,應該就能懂了。
fs.appendFile = function(path, data, options, callback_) { var callback = maybeCallback(arguments[arguments.length - 1]); if (util.isFunction(options) || !options) { options = { encoding: 'utf8', mode: 438 /*=0666*/, flag: 'a' }; } else if (util.isString(options)) { options = { encoding: options, mode: 438, flag: 'a' }; } else if (!util.isObject(options)) { throw new TypeError('Bad arguments'); } if (!options.flag) options = util._extend({ flag: 'a' }, options); //調(diào)用的writeFile fs.writeFile(path, data, options, callback); };
appendFile的源碼,就更沒有什么新東西了,只是做了一個判斷,然后給flag標簽添加了一個a屬性值,之后就直接調(diào)用的weiteFile的方法了。
總結
關于nodejs的操作文件,是比較重要的一個概念,所以包含的信息,也是比較多的,本篇依然是在之前open,read,write等的基礎上,執(zhí)行的再一次的封裝,不屬于新的概念,只是為了能讓使用者更簡單的使用讀寫文件的功能而已。后面繼續(xù)在看一些其他的操作文件的API的功能及其實現(xiàn)。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
Node Mongoose用法詳解【Mongoose使用、Schema、對象、model文檔等】
這篇文章主要介紹了Node Mongoose用法,結合實例形式分析了Mongoose使用、Schema、對象、model文檔等基本原理、用法及操作注意事項,需要的朋友可以參考下2020-05-05Express實現(xiàn)定時發(fā)送郵件的示例代碼
在開發(fā)中我們有時候需要每隔?一段時間發(fā)送一次電子郵件,或者在某個特定的時間進行發(fā)送郵件,無需手動去操作,基于這樣的情況下我們需要用到了定時任務。本文就來用Express實現(xiàn)定時發(fā)送郵件吧2023-04-04npm?does?not?support?Node.js問題的解決辦法
這篇文章主要給大家介紹了關于npm?does?not?support?Node.js問題的解決辦法,文中通過代碼以及圖文介紹的非常詳細,對大家的學習或者工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-10-10Node.js?中的?module.exports?與?exports區(qū)別介紹
這篇文章主要介紹了Node.js中的module.exports與exports區(qū)別介紹,每個模塊中都有module對象,存放了當前模塊相關的信息,更多相關內(nèi)容需要的朋友可以參考一下2022-09-09使用Node.js實現(xiàn)RESTful API的示例
Node.js可以用很少代碼簡單地實現(xiàn)一個Web服務,并且它有一個非?;钴S的社區(qū),通過Node出色的包管理機制(NPM)可以非常容易獲得各種擴展支持。 對簡單的應用場景Node.js實現(xiàn)REST是一個非常合適的選擇。 本文介紹如何用Node.js實現(xiàn)REST服務。2017-08-08