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 如何读取一亿行的大文件的更多相关文章

  1. Pandas 读取超过 65536 行的 Excel 文件

    Excel 文件的格式曾经发生过一次变化,在 Excel 2007 以前,使用扩展名为 .xls 格式的文件,这种文件格式是一种特定的二进制格式,最多支持 65,536 行,256 列表格.从 Exc ...

  2. PHP读取大文件的几种方法介绍

    读取大文件一直是一个头痛的问题,我们像使用php开发读取小文件可以直接使用各种函数实现,但一到大文章就会发现常用的方法是无法正常使用或时间太长太卡了,下面我们就一起来看看关于php读取大文件问题解决办 ...

  3. 用Python读取大文件

    通常我们在读取文件的时候,会用到read(), readline(), readlines(). 通常可能会有这样的用法: def test1(): with open("/tmp/test ...

  4. php -- 读取大文件

    在PHP中,对于文件的读取时,最快捷的方式莫过于使用一些诸如file.file_get_contents之类的函数,简简单单的几行代码就能 很漂亮的完成我们所需要的功能.但当所操作的文件是一个比较大的 ...

  5. 【Python】实现对大文件的增量读取

    背景 前段时间在做一个算法测试,需要对源于日志的数据进行分析才能获取到结果:日志文件较大,所以想要获取数据的变化曲线,增量读取是最好的方式. 网上有很多人的技术博客都是写的用for循环readline ...

  6. 新手C#SQLServer在程序里实现语句的学习2018.08.12

    从C#中连接到SQL Server数据库,再通过C#编程实现SQL数据库的增删改查. ado.net提供了丰富的数据库操作,这些操作可以分为三个步骤: 第一,使用SqlConnection对象连接数据 ...

  7. 2018年12月8日广州.NET微软技术俱乐部活动总结

    吕毅写了一篇活动总结,写得很好!原文地址是:https://blog.walterlv.com/post/december-event-microsoft-technology-salon.html ...

  8. 2018.5.12 storm数据源kafka堆积

    问题现象: storm代码依赖4个源数据topic,2018.5.12上午8点左右开始收到告警短信,源头的4个topic数据严重堆积. 排查: 1.查看stormUI, storm拓扑结构如下: 看现 ...

  9. Artificial Intelligence Computing Conference(2018.09.12)

    时间:2018.09.12地点:北京国际饭店会议中心

随机推荐

  1. CV codes代码分类整理合集 《转》

    from:http://www.sigvc.org/bbs/thread-72-1-1.html 一.特征提取Feature Extraction:   SIFT [1] [Demo program] ...

  2. 安装wepack

    安装webpack之前要安装node.js 1.安装webpack运行 npm install webpack -g 和npm install webpack-cli -g npm install w ...

  3. 洛谷P2687 [USACO4.3]逢低吸纳Buy Low, Buy Lower

    P2687 [USACO4.3]逢低吸纳Buy Low, Buy Lower 题目描述 “逢低吸纳”是炒股的一条成功秘诀.如果你想成为一个成功的投资者,就要遵守这条秘诀: "逢低吸纳,越低越 ...

  4. poj3728The merchant树剖+线段树

    如果直接在一条直线上,那么就建线段树 考虑每一个区间维护最小值和最大值和答案,就符合了合并的条件,一个log轻松做 那么在树上只要套一个树剖就搞定了,多一个log也不是问题 注意考虑在树上的话每一条链 ...

  5. Hive进阶_Hive的表连接

    等值连接 select e.empno, d.deptno from emp e, dept d where e.deptno=d.deptno; 不等值连接 select e.empno, e.en ...

  6. (转)网站DDOS攻击防护实战老男孩经验心得分享

    网站DDOS攻击防护实战老男孩经验心得分享 原文:http://blog.51cto.com/oldboy/845349

  7. 专题《一》mysql优化 ---------主从复制,读写

    主从复制是mysql自带的功能,读写分离用第三方插件 主从复制作用:读写分离(mycat),数据备份,高可用,集群 原理:二进制sql执行文件-----insert,update,delete,cre ...

  8. linux basename命令的使用

    用途 返回一个字符串参数的基本文件名称. 语法 basename String [ Suffix ] 描述 basename 命令读取 String 参数,删除以 /(斜杠) 结尾的前缀以及任何指定的 ...

  9. Coroutine(协程)模式与线程

    概念 协程(Coroutine)这个概念最早是Melvin Conway在1963年提出的,是并发运算中的概念,指两个子过程通过相互协作完成某个任务,用它可以实现协作式多任务,协程(coroutine ...

  10. Unity使用 转载

    创建空的ASP.NET MVC3项目,添加对Unity2.0动态库的引用. 方法1:在MSDN上下载Untity2.0,安装后,默认安装在C:\Program Files\Microsoft Unit ...