在通常的學習和開發中,因為我們很少會接觸到大量資料的讀取和寫入,所以當突然有了這種需求的時候,我們可
能仍然會按照一些比較快捷的方法,像file_get_contents,fread等方法來讀取檔案,不過這樣以來如果讀取的檔案太
大,就會產生問題,在實現大檔案讀取和寫入的時候尋找了網上的一些資料,不過有些例子給的不是很符合我的需
求,所以我就結合網上已有的例子,再寫一篇總結性的部落格吧。
那麼究竟會產生什麼問題呢,這就要說一些PHP的底層實現數,file_get_contents和fread來說一下,首先說一下
PHP中檔案讀取的函數,file_get_contents和fread,這兩個函數其實原理是相同的,都是讀取內容到系統的記憶體中,
不過如果只是想將一個檔案的內容讀入到一個字串中,請使用file_get_contents(),它的效能比 fread() 好得多。
在讀取不是很大的檔案還是沒有問題的,不過當讀取大檔案的時候(例如2GB的日誌),如果你機器的記憶體只有4G,
如果你將整個檔案全部讀取然後存入字串中,就可能會導致系統的記憶體爆掉造成卡死,因為還有一部分記憶體要用於
維持系統的運行和其他進程的運行,既然會這樣,我們就需要一些其他辦法來避免一次性讀取太多的內容,通過這種
辦法來實現大檔案的讀取。
PHP檔案讀取:
下面這個是網上讀取大檔案的一個例子,來說明上面記憶體會爆掉的現象。。
_________________________________無敵分割線_______________________________________
需求如下: 現有一個1G左右的記錄檔,大約有500多萬行, 用php返回最後幾行的內容。
實現方法:
1. 直接採用file函數來操作
註:由於 file函數是一次性將所有內容讀入記憶體,而php為了防止一些寫的比較糟糕的程式佔用太多的記憶體而導致系統記憶體不足,使伺服器出現宕機,所以預設情況下限制只能最大使用記憶體16M,這是通過php.ini裡的memory_limit = 16M來進行設定,這個值如果設定-1,則記憶體使用量量不受限制.
下面是一段用file來取出這具檔案最後一行的代碼.
整個代碼執行完成耗時 116.9613 (s).
我機器是2個G的記憶體,當按下F5運行時,系統直接變灰,差不多20分鐘後才恢複過來,可見將這麼大的檔案全部直接讀入記憶體,後果是多少嚴重,所以不在萬不得以,memory_limit這東西不能調得太高,否則只有打電話給機房,讓reset機器了.
_____________________________________無敵分割線_____________________________________
上面的例子雖然是讀取最後幾行的例子,不過由於對檔案的內容進行了遍曆,所以跟讀取整個檔案是一樣的,不過如
果是為了讀取最後幾行的內容,也可以直接用fseek來進行定位讀取部分內容。
下面我們來一起探討下如何對大檔案進行讀取和寫入。
大檔案讀取:
因為要求讀取部分,如果檔案不是特別大,可以通過file_get_contents或fread內建的分割參數來進行分塊來進行讀取(這個地方感覺需要加一個sleep函數,來減少IO的峰值大小,不過不知是否正確,希望大牛指點),還有一種方法,就是通過while迴圈,用fgets來進行逐行的讀取,因為fgetss是通過檔案指標讀取一行,效率是比較高的。
下面附上通過fgets實現的大檔案讀取並對檔案內容進行編碼轉換(UTF-8 -> GBK)的例子,代碼如下:
$file = fopen($old_file_path,"r"); $result = fopen($temporary_file_path,"a"); $re_sign = 0; while(!feof($file)) { $content = fgets($file); $encode = mb_detect_encoding($content, array('ASCII','UTF-8','GB2312','GBK','BIG5')); if ($encode == 'UTF-8') { $str = iconv($encode,"GBK//IGNORE", $content); $encode = mb_detect_encoding($content, array('ASCII','UTF-8','GB2312','GBK','BIG5')); fwrite($result, $str); $re_sign = 1; } else { fwrite($result, $content); } } fclose($file); fclose($result); if($re_sign == 1){ rename($old_file_path, $old_file_path . '.bak' ); rename($temporary_file_path, $old_file_path); } else { unlink($temporary_file_path); }
大檔案寫入:
大檔案的寫入相對大檔案讀取來說產生的代價不是很大,因為檔案寫入是寫入到硬碟中,如果一次性寫入檔案過多,
只會產生卡硬碟的現象,如果從效率上來講一次性直接寫入的耗時和效率最高,所以大檔案的寫入的話建議一次性讀取後直接寫入檔案中。