2018.2.12 PHP 如何读取一亿行的大文件
PHP 如何读取一亿行的大文件
我们可能在很多场景下需要用 PHP 读取大文件,之后进行处理,如果你没有相关的经验可以看下,希望能给你带来一些启发。
模拟场景
我们有一个 1亿 行,大小大概为 3G 的日志文件,需要分析每一行获取一个 ID,然后拿这些 ID 逐行向数据库发起查询。
先想想 ...
遇到此类的问题稍微有点经验的程序员就需要考虑如下一些问题:
由于 PHP 可以利用的内存有限,即使可以修改我们也不要随便更改这个配置,就用默认的好了,由此可以确定这里肯定不能一次读完,需要考虑逐行分块读取
使用什么方法读比较合理?
思路
• 读写一个文件通常处理的流程是【打开】、【写入|读取】、【关闭】
• 我们知道 PHP 读取文件支持curl、file_get_contents、fopen,前两个对请求远程文件支持的比较好,可以一次性把结果读取到一个字符串里,里面封装了 http 请求以及读写文件完整流程的一些方法,使得我们读取文件非常方便,一个函数搞定,而另外一个fopen就把所有操作权都给你了,你需要怎么读怎么写自己组合
• 拿到 ID 后需要控制对数据库查询的次数,每行分析出 ID 就查数据库也是不合理的,如果考虑把所有 ID 拿到,整体一次请求数据库也是不现实的,PHP 变量可以控制的内存有限,而且数据库也不可能让你一次查询 3G 的 SQL 啊
我们不需要读取每行数据都执行打开、关闭文件,这种开销是巨大的,基于以上考虑,只有 fopen 可选了,fopen 可以分步骤进行操作,对于向数据库查询的操作,我们应该尽量控制单次请求 SQL 的 ID 数量在一个合理的范围,所以脑子里大概有这么个东西
<?php // 1、打开日志文件 // 2、循环读取每一行,保持打开状态 while { // 2.1、获取每一行数据,分析出我们需要的 ID // 2.2、拿着 ID 去数据库进行查询 # 由于是连接到数据库,我们也需要考虑操作数据库的连接、断开等开销成本 # 这里应该实现每查询一批 ID,批次的向数据库进行查询,以确保减少这部分的开销 } // 3、关闭日志文件
开始动手了
模拟机器配置
对于读写来说机器的配置及运行环境很重要,下面介绍下我的试验环境
CPU:4核 Intel(R) Core(TM) i7-4750HQ CPU @ 2.00GHz内存:2G硬盘:PCI-E SSD
PHP INFO
$ php -vPHP 7.1.8 (cli) (built: Aug 4 2017 18:59:36) ( NTS )Copyright (c) 1997-2017 The PHP GroupZend Engine v3.1.0, Copyright (c) 1998-2017 Zend Technologies$ php -i | grep memory_limitmemory_limit => 128M => 128M
测试写入
我们使用以下脚本 write.php 写入到 test.log 中
<?php$start = date("Y-m-d H:i:s");$line = 0; // 行数$count = 0; // 行计数器$str = ""; // 初始写入的字符串$file = fopen("./test.log", "w");while($line <= 100000000){ ++$line; ++$count; $str .= "The line number is " . $line . "\n"; // 为了减少 fwrite 的开销,我们每累计 200 万行写入一次 // 为啥是 200 万?试了 300 万就超出内存限制了,如果这个可以接受就这个了,不行再调整 if($count == 2000000){ fwrite($file, $str); // 重置行计数、字符串 $str = ""; $count = 0; }}echo "start:" . $start . ",end:" . date("Y-m-d H:i:s") . "\n";
执行,验证是不是写的没问题
// 执行写入脚本$ php write.phpstart:2017-10-30 00:24:40,end:2017-10-30 00:26:05// 查看日志大小,du -sh 是展示一个人类能看得懂的文件大小,灰常有用$ du -sh test.log2.7G test.log// 查看日志多少行$ wc -l test.log100000000 test.log// 查看日志前 5 行$ head -5 test.logThe line number is 1The line number is 2The line number is 3The line number is 4The line number is 5// 查看日志倒数 5 行$ tail -n 5 test.logThe line number is 99999996The line number is 99999997The line number is 99999998The line number is 99999999The line number is 100000000
可以看到写入大概执行了一分半左右,这个速度还可以接受吧,接受不了你自己调整那个 200 万
测试读取
我们的读取脚本 read.php 如下
<?php$start = date('Y-m-d H:i:s');$count = 0; // 计数器,累计到一定值重置$ids = ""; // ID 集合// 打开文件$file = fopen('./test.log', 'r');while(!feof($file)){ // 获取当前指针行的数据,stream_get_line 的作用和 fgets 类似,不过这个可以指定换行符 $current = stream_get_line($file, 1024, "\n"); if($current){ ++$count; // 拼接需要查询的 ID,假设你要获取的是最后一个空格之后的值 $ids .= "," . substr($current, strrpos($current, " ")+1); // 为啥这里是 1000 ?多了不更快么? // 是的,对于向数据库发起查询操作拼接 sql 来说 in 的 id 数量多少也很重要,我这边实验的是 1000 个 id 查询一次,并没有考虑太多 // 如果实际用到这种还需要结合 sql 的 max_allowed_packet 更多的考量这里的值怎么样是比较合理的 if($count == 1000){ $sql = "select id from table where id in(" . ltrim($ids, ",") . ")"; // 查数据库 $count = 0; $ids = ""; } }}// 关闭文件fclose($file);echo 'start:' . $start . ',end:' . date('Y-m-d H:i:s') . "\n";
执行
$ php read.phpstart:2017-10-30 00:30:18,end:2017-10-30 00:30:49
实验了多次基本读取时间维持在半分钟左右,这个速度我觉得还行 ...
总结
以我的配置来说,对于 3G 的文件写入时间大概在一分半钟左右,读取时间在半分钟左右,也就是说类似的需求在如今 SSD 这么便宜的情况下做一些大文件读取的任务,PHP 读取的过程不会受限太多,随着数据量的增大,while 里面的每一个函数、每一个方法的使用都显得尤为重要,举例来说,在做实验的过程中用到了 echo 函数,如果去掉就会减少很多倍的时间。
2018.2.12 PHP 如何读取一亿行的大文件的更多相关文章
- Pandas 读取超过 65536 行的 Excel 文件
Excel 文件的格式曾经发生过一次变化,在 Excel 2007 以前,使用扩展名为 .xls 格式的文件,这种文件格式是一种特定的二进制格式,最多支持 65,536 行,256 列表格.从 Exc ...
- PHP读取大文件的几种方法介绍
读取大文件一直是一个头痛的问题,我们像使用php开发读取小文件可以直接使用各种函数实现,但一到大文章就会发现常用的方法是无法正常使用或时间太长太卡了,下面我们就一起来看看关于php读取大文件问题解决办 ...
- 用Python读取大文件
通常我们在读取文件的时候,会用到read(), readline(), readlines(). 通常可能会有这样的用法: def test1(): with open("/tmp/test ...
- php -- 读取大文件
在PHP中,对于文件的读取时,最快捷的方式莫过于使用一些诸如file.file_get_contents之类的函数,简简单单的几行代码就能 很漂亮的完成我们所需要的功能.但当所操作的文件是一个比较大的 ...
- 【Python】实现对大文件的增量读取
背景 前段时间在做一个算法测试,需要对源于日志的数据进行分析才能获取到结果:日志文件较大,所以想要获取数据的变化曲线,增量读取是最好的方式. 网上有很多人的技术博客都是写的用for循环readline ...
- 新手C#SQLServer在程序里实现语句的学习2018.08.12
从C#中连接到SQL Server数据库,再通过C#编程实现SQL数据库的增删改查. ado.net提供了丰富的数据库操作,这些操作可以分为三个步骤: 第一,使用SqlConnection对象连接数据 ...
- 2018年12月8日广州.NET微软技术俱乐部活动总结
吕毅写了一篇活动总结,写得很好!原文地址是:https://blog.walterlv.com/post/december-event-microsoft-technology-salon.html ...
- 2018.5.12 storm数据源kafka堆积
问题现象: storm代码依赖4个源数据topic,2018.5.12上午8点左右开始收到告警短信,源头的4个topic数据严重堆积. 排查: 1.查看stormUI, storm拓扑结构如下: 看现 ...
- Artificial Intelligence Computing Conference(2018.09.12)
时间:2018.09.12地点:北京国际饭店会议中心
随机推荐
- 浏览器版本低于IE10跳转到指定网页
var userAgent = navigator.userAgent; var ie6 = (/msie\s*(\d+)\.\d+/g.exec(userAgent.toLowerCase()) | ...
- javascript中的scroll事件
window.addEventListener('scroll',function(){ if(document.compatMode == "CSS1Compat") { ale ...
- Automake使用说明
说明 从零开始编写automake工程非常复杂也没有必要,我们只要能看懂开源项目的automake即可,然后根据自己实际情况进行修改即可,下面给出两个比较好的参考项目,其中spice-gtk涵盖了使用 ...
- Git入门 时光穿梭鸡 版本回退 工作区 暂存区
分布式集中式 CVS及SVN都是集中式的版本控制系统 , 而Git是分布式版本控制系统 集中式版本控制系统,版本库是集中存放在中央服务器的, 而干活的时候,用的都是自己的电脑,所以要先从中央服务器取得 ...
- RVO算法
http://blog.sina.com.cn/s/blog_6ad33d350102xqal.html 简介 在介绍VO,RVO之前,需要先介绍路径规划. 对Agent进行路径规划,实际上要完成的任 ...
- AndroidStudio常用快捷键
Alt+Insert:查找override函数 Alt+Enter:自动引包
- linux mysql 简单记录
mysql 1.linux下启动mysql的命令:mysqladmin start/ect/init.d/mysql start (前面为mysql的安装路径) 2.linux下重启mysql的命令: ...
- [Xcode 实际操作]六、媒体与动画-(9)使用CATransaction Push制作入场动画
目录:[Swift]Xcode实际操作 本文将演示如何制作入场动画. 在项目导航区,打开视图控制器的代码文件[ViewController.swift] import UIKit class View ...
- 了解HTTP协议和TCP协议
HTTP(超文本传输协议),互联网上应用最为广泛的一种网络协议.所有的www文件都必须遵守这个标准.HTTP是一个客户端和服务端请求和应答的标准(TCP):客户通过浏览器发起一个到服务器上指定端口的H ...
- according to tld or attribute directive in tag file attribute *** does not accept any expressions
http://stackoverflow.com/questions/13428788/according-to-tld-or-attribute-directive-in-tag-file-attr ...