国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專(zhuān)欄INFORMATION COLUMN

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

banana_pi / 616人閱讀

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

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

前言

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

這篇筆記主要解決這么幾個(gè)問(wèn)題:

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

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

順便解決哪天我忘了可以通過(guò)搜索引擎搜到我自己寫(xiě)的筆記來(lái)看看。(因?yàn)樾枰?PHP 寫(xiě)這兩個(gè)功能的情況真的很少,我記性不好,免得忘了又重走一遍彎路)

遍歷目錄文件

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

這時(shí)候正確的實(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();
    
    // 這個(gè)就是包括路徑在內(nèi)的完整文件名了
    // echo $filename;

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

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

讀取文本文件

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

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

逐行讀取整個(gè)文件:

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

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

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

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

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

seek($offset); 

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

        $arr[] = $line;

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

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

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

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

復(fù)制大文件

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


最后

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

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

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

前面生成器對(duì)象循環(huán)代碼塊里最后都有一個(gè) $glob->next(); 代碼,意思是指向下一項(xiàng),這個(gè)至關(guān)重要,因?yàn)槿绻麤](méi)有了它,下一次循環(huán)獲取到的還是這次的結(jié)果。

舉個(gè)例子:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/31361.html

相關(guān)文章

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

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

    Harriet666 評(píng)論0 收藏0
  • 老舊話題:PHP讀取超大文件

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

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

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

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

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

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

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

    Batkid 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<