成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

PHP超低內(nèi)存遍歷目錄文件和讀取超大文件

banana_pi / 671人閱讀

摘要:這篇筆記主要解決這么幾個問題如何使用超低內(nèi)存快速遍歷數(shù)以萬計的目錄文件如何使用超低內(nèi)存快速讀取幾百甚至是級文件順便解決哪天我忘了可以通過搜索引擎搜到我自己寫的筆記來看看。

這不是一篇教程,這是一篇筆記,所以我不會很系統(tǒng)地論述原理和實(shí)現(xiàn),只簡單說明和舉例。

前言

我寫這篇筆記的原因是現(xiàn)在網(wǎng)絡(luò)上關(guān)于 PHP 遍歷目錄文件和 PHP 讀取文本文件的教程和示例代碼都是極其低效的,低效就算了,有的甚至好意思說是高效,實(shí)在辣眼睛。

這篇筆記主要解決這么幾個問題:

PHP 如何使用超低內(nèi)存快速遍歷數(shù)以萬計的目錄文件?

PHP 如何使用超低內(nèi)存快速讀取幾百M(fèi)B甚至是GB級文件?

順便解決哪天我忘了可以通過搜索引擎搜到我自己寫的筆記來看看。(因為需要 PHP 寫這兩個功能的情況真的很少,我記性不好,免得忘了又重走一遍彎路)

遍歷目錄文件

網(wǎng)上關(guān)于這個方法的實(shí)現(xiàn)大多示例代碼是 glob 或者 opendir + readdir 組合,在目錄文件不多的情況下是沒問題的,但文件一多就有問題了(這里是指封裝成函數(shù)統(tǒng)一返回一個數(shù)組的時候),過大的數(shù)組會要求使用超大內(nèi)存,不僅導(dǎo)致速度慢,而且內(nèi)存不足的時候直接就崩潰了。

這時候正確的實(shí)現(xiàn)方法是使用 yield 關(guān)鍵字返回,下面是我最近使用的代碼:

valid()) {
                    yield $sub->current();
                    $sub->next();
                }
                if ($include_dirs)
                    yield $rfile;
            } else {
                yield $rfile;
            }
        }
        closedir($dh);
    }
}

// 使用
$glob = glob2foreach("/var/www");
while ($glob->valid()) {
    
    // 當(dāng)前文件
    $filename = $glob->current();
    
    // 這個就是包括路徑在內(nèi)的完整文件名了
    // echo $filename;

    // 指向下一個,不能少
    $glob->next();
}

yield 返回的是生成器對象(不了解的可以先去了解一下 PHP 生成器),并沒有立即生成數(shù)組,所以目錄下文件再多也不會出現(xiàn)巨無霸數(shù)組的情況,內(nèi)存消耗是低到可以忽略不計的幾十 kb 級別,時間消耗也幾乎只有循環(huán)消耗。

讀取文本文件

讀取文本文件的情況跟遍歷目錄文件其實(shí)類似,網(wǎng)上教程基本上都是使用 file_get_contents 讀到內(nèi)存里或者 fopen + feof + fgetc 組合即讀即用,處理小文件的時候沒問題,但是處理大文件就有內(nèi)存不足等問題了,用 file_get_contents 去讀幾百M(fèi)B的文件幾乎就是自殺。

這個問題的正確處理方法同樣和 yield 關(guān)鍵字有關(guān),通過 yield 逐行處理,或者 SplFileObject 從指定位置讀取。

逐行讀取整個文件:

valid()) {
    
    // 當(dāng)前行文本
    $line = $glob->current();
    
    // 逐行處理數(shù)據(jù)
    // $line

    // 指向下一個,不能少
    $glob->next();
}

通過 yield 逐行讀取文件,具體使用多少內(nèi)存取決于每一行的數(shù)據(jù)量有多大,如果是每行只有幾百字節(jié)的日志文件,即使這個文件超過100M,占用內(nèi)存也只是KB級別。

但很多時候我們并不需要一次性讀完整個文件,比如當(dāng)我們想分頁讀取一個1G大小的日志文件的時候,可能想第一頁讀取前面1000行,第二頁讀取第1000行到2000行,這時候就不能用上面的方法了,因為那方法雖然占用內(nèi)存低,但是數(shù)以萬計的循環(huán)是需要消耗時間的。

這時候,就改用 SplFileObject 處理,SplFileObject 可以從指定行數(shù)開始讀取。下面例子是寫入數(shù)組返回,可以根據(jù)自己業(yè)務(wù)決定要不要寫入數(shù)組,我懶得改了。

seek($offset); 

    $i = 0;
    
    while (! $fp->eof()) {
        
        // 必須放在開頭
        $i++;
        
        // 只讀 $count 這么多行
        if ($i > $count)
            break;
        
        $line = $fp->current();
        $line = trim($line);

        $arr[] = $line;

        // 指向下一個,不能少
        $fp->next();
    }
    
    return $arr;
}

以上所說的都是文件巨大但是每一行數(shù)據(jù)量都很小的情況,有時候情況不是這樣,有時候是一行數(shù)據(jù)也有上百M(fèi)B,那這該怎么處理呢?

如果是這種情況,那就要看具體業(yè)務(wù)了,SplFileObject 是可以通過 fseek 定位到字符位置(注意,跟 seek 定位到行數(shù)不一樣),然后通過 fread 讀取指定長度的字符。

也就是說通過 fseek 和 fread 是可以實(shí)現(xiàn)分段讀取一個超長字符串的,也就是可以實(shí)現(xiàn)超低內(nèi)存處理,但是具體要怎么做還是得看具體業(yè)務(wù)要求允許你怎么做。

復(fù)制大文件

順便說下 PHP 復(fù)制文件,復(fù)制小文件用 copy 函數(shù)是沒問題的,復(fù)制大文件的話還是用數(shù)據(jù)流好,例子如下:


最后

我這只說結(jié)論,沒有展示測試數(shù)據(jù),可能難以服眾,如果你持懷疑態(tài)度想求證,可以用 memory_get_peak_usage 和 microtime 去測一下代碼的占用內(nèi)存和運(yùn)行時間。

補(bǔ)充:踩坑和修改大文件

這篇筆記是我昨晚睡不著無聊突然想起來就隨手寫的,今天起來又看了一下,發(fā)現(xiàn)有一個巨坑沒提到,雖說不計劃寫成教程,但是這個巨坑必須提一下。

前面生成器對象循環(huán)代碼塊里最后都有一個 $glob->next(); 代碼,意思是指向下一項,這個至關(guān)重要,因為如果沒有了它,下一次循環(huán)獲取到的還是這次的結(jié)果。

舉個例子:

有個文本文件里面有三行文本,分別是 111111、222222、333333 ,當(dāng)我們用以下代碼讀取的時候,while 會循環(huán)三次,每次 $line 分別對應(yīng) 111111、222222、333333 。

valid()) {
    
    // 當(dāng)前行文本
    $line = $glob->current();
    
    // 逐行處理數(shù)據(jù)
    // $line

    // 指向下一個,不能少
    $glob->next();
}

但是,如果沒有 $glob->next(); 這一行,就會導(dǎo)致 $line 始終是讀到第一行 111111 ,會導(dǎo)致死循環(huán)或者讀取到的不是預(yù)期的數(shù)據(jù)。

看到這里你可能會覺得這是廢話,不,不是,理論上不容易出現(xiàn)這個錯誤,但是在實(shí)際的編程中我們可能會使用 continue 跳到下次循環(huán),如果你寫著寫著不記得了,在 $glob->next(); 前面使用 continue 跳到下次循環(huán),就會導(dǎo)致下次循環(huán)的 $line 依然是這次的值,導(dǎo)致異常甚至死循環(huán)。

要解決這個問題,除了保持編碼警惕性,也可以修改下 $glob->next(); 的位置。

valid()) {
    
    // 當(dāng)前項
    $line = $glob->current();
    
    // 指向下一個,不能少
    $glob->next();
    
    // 注意,這時已經(jīng)指向下一項
    // 再使用 $glob->current() 獲取到的就不是 $line 的值了,而是下一項的值了

    // 在這后面你就可以放心使用 continue 了
    // 但是別忘了讀取當(dāng)前項只能通過 $line 了
    
    // 逐行處理數(shù)據(jù)
}

這個坑我是踩過的,無意間使用 continue 導(dǎo)致讀取數(shù)據(jù)不對。其實(shí)出現(xiàn)這種錯誤導(dǎo)致死循環(huán)程序崩潰是好事,立即排查能排查出結(jié)果,最可怕的是只讀錯數(shù)據(jù),讓人一時半會兒察覺不到。

另外,補(bǔ)充一下修改大文件的要點(diǎn)。

要讀大文件往往會涉及到修改它,如果是從中摘取數(shù)據(jù)或者大幅度修改,我們可以使用 fopen + fwrite 組合配合生成器對象逐行處理數(shù)據(jù)之后逐行寫入,這樣效率也是高的,盡量避免存到變量里再集中寫入以免占用內(nèi)存爆炸。

valid()) {
    
    // 當(dāng)前行文本
    $line = $glob->current();

    // 逐行處理數(shù)據(jù)
    // 將處理過的寫入新文件
    fwrite($handle, $line . "
");
    
    // 指向下一個,不能少
    $glob->next();
}
fclose($handle);

如果是修改大文件里的小細(xì)節(jié),這個我還沒做過,不過據(jù)我了解好像是通過 Stream Functions 的 filter 實(shí)現(xiàn)效率比較高。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/31361.html

相關(guān)文章

  • 關(guān)于php的yield生成器

    摘要:今天分享一個特別好用的東西,里面的生成器才引入的功能,可以避免數(shù)組過大導(dǎo)致內(nèi)存溢出的問題理解生成器關(guān)鍵字不是返回值,他的專業(yè)術(shù)語叫產(chǎn)出值,只是生成一個值,并不會立即生成所有結(jié)果集,所以內(nèi)存始終是一條循環(huán)的值應(yīng)用場景遍歷文件目錄讀取超大文件日 今天分享一個特別好用的東西,php里面的生成器(PHP 5.5.0才引入的功能),可以避免數(shù)組過大導(dǎo)致內(nèi)存溢出的問題 理解:生成器yield關(guān)鍵字...

    Harriet666 評論0 收藏0
  • 老舊話題:PHP讀取超大文件

    摘要:然而,剛進(jìn)來就擰螺絲的人如果能夠?qū)ψx取一個的超大文件有所見解的話,造火箭也是遲早的事兒。其中,用于告知當(dāng)前文件讀取指針?biāo)谖恢?,可以手動設(shè)定文件讀取指針的位置。 作為一名常年深耕curd的PHPer,關(guān)注內(nèi)存那是不可能的,反正apache或者fpm都幫我們做了,況且運(yùn)行一次就銷毀,根本就不存在什么內(nèi)存問題。 然而偏偏就有些個不開眼的人把這些個東西當(dāng)面試題,比如總有刁民用php讀取一個1...

    goji 評論0 收藏0
  • PHP讀取超大的excel文件數(shù)據(jù)的方案

    摘要:場景和痛點(diǎn)說明今天因為一個老同學(xué)找我,說自己公司的物流業(yè)務(wù)都是現(xiàn)在用處理,按月因為數(shù)據(jù)量大,一個差不多有百萬數(shù)據(jù),文件有接近,打開和搜索就相當(dāng)?shù)穆?lián)想到場景要導(dǎo)入數(shù)據(jù),可能數(shù)據(jù)量很大,這里利用常用的一些方法比如會常有時間和內(nèi)存限制問題下面我 場景和痛點(diǎn) 說明 今天因為一個老同學(xué)找我,說自己公司的物流業(yè)務(wù)都是現(xiàn)在用excel處理,按月因為數(shù)據(jù)量大,一個excel差不多有百萬數(shù)據(jù),文件有接...

    dkzwm 評論0 收藏0
  • 如何高效地遍歷 MongoDB 超大集合?

    摘要:執(zhí)行測試代碼兩種不同遍歷方法的代碼分別位于和。參考如何使用對一個萬的的表進(jìn)行遍歷操作關(guān)于專注于微信小程序微信小游戲支付寶小程序和線上應(yīng)用實(shí)時監(jiān)控。自從年雙十一正式上線,累計處理了億錯誤事件,付費(fèi)客戶有金山軟件百姓網(wǎng)等眾多品牌企業(yè)。 GitHub 倉庫:Fundebug/loop-mongodb-big-collection showImg(https://segmentfault.c...

    王晗 評論0 收藏0
  • 如何高效地遍歷 MongoDB 超大集合?

    摘要:執(zhí)行測試代碼兩種不同遍歷方法的代碼分別位于和。參考如何使用對一個萬的的表進(jìn)行遍歷操作關(guān)于專注于微信小程序微信小游戲支付寶小程序和線上應(yīng)用實(shí)時監(jiān)控。自從年雙十一正式上線,累計處理了億錯誤事件,付費(fèi)客戶有金山軟件百姓網(wǎng)等眾多品牌企業(yè)。 GitHub 倉庫:Fundebug/loop-mongodb-big-collection showImg(https://segmentfault.c...

    Batkid 評論0 收藏0

發(fā)表評論

0條評論

閱讀需要支付1元查看
<