Sha*_*aun 8 php csv performance file-io parsing
我正在使用php中的解析器,它旨在从文本文件中提取MySQL记录.一个特定的行可能以一个字符串开头,该字符串对应于需要插入记录(行)的表,然后是记录本身.记录由反斜杠分隔,字段(列)用逗号分隔.为简单起见,我们假设我们有一个表格,代表我们数据库中的人,其中的字段是名字,姓氏和职业.因此,文件的一行可能如下所示
[People] ="\ Han,Solo,Smuggler\Luke,Skywalker,Jedi ......"
省略号(...)可能是额外的人.一种简单的方法可能是用于fgets()从文件中提取一行,并用于preg_match()从该行中提取表名,记录和字段.
但是,我们假设我们有很多星球大战的角色需要跟踪.事实上,这一行很多,最终会有200,000多个字符/字节长.在这种情况下,采用上述方法提取数据库信息似乎效率低下.您必须首先将数十万个字符读入内存,然后回读这些相同的字符以查找正则表达式匹配.
有没有一种方法,类似于使用文件构造String next(String pattern)的Scanner类的Java 方法,允许您在扫描文件时在线匹配模式?
这个想法是你不必扫描相同的文本两次(从文件中读取它到字符串,然后匹配模式)或冗余地将文本存储在内存中(在文件行字符串和匹配中)模式).这甚至会使性能显着提高吗?很难确切知道PHP或Java在幕后做了什么.
Onfgetcsv()
此功能可以很容易地根据某些分隔符在文件中拆分行,并且我确定它在扫描文件时逐个字符地检查分隔符.然而,问题是我正在寻找基本上两个分隔符,并且fgetcsv()只接受一个分隔符.例如:
我可以使用','作为分隔符.如果我将文件格式更改为也使用反斜杠的逗号,我可以将整行读入字段数组.那么问题是,我需要重申所有字段以确定记录的开始和结束位置以及准备sql.类似地,如果我使用'\'作为分隔符(单个反斜杠,在此处进行转义),那么我需要重复所有记录以提取字段并准备sql.
我所试图做的是检查都在最大性能一举逗号和反斜杠(也许还有其他的东西,如[表名]).如果fgetcsv()允许我指定多个分隔符(或正则表达式)或允许我更改它认为是"行尾"(从\n或\n\r到只有\),那么它将完美地工作,但是这似乎不可能.
您可以编写一个逐字符累加循环,(a) 当遇到逗号时将字段字符串推送到数组中,(b) 当找到记录指示符时调用一个函数将累加的字段字符串保存到 mysql 数据库:
while($c = fgetc($fp)) {
if($c == ',') {
$fields[] = implode(null,$accumulator);
$accumulator = array();
} else if($c == '\\') {
save_fields_to_mysql($fields);
$fields = array();
$accumulator = array();
} else
$accumulator[] = $c;
}
Run Code Online (Sandbox Code Playgroud)
如果您确定您的字段从不包含字段或记录分隔符作为数据,这可能对您有用。
如果可能的话,您需要提出一个转义序列来表示字段和记录分隔符的文字值(也可能是您的转义序列)。假设情况如此,并假设 % 符号作为转义字符:
define('ESCAPED',1);
define('NORMAL',0);
$readState = NORMAL;
while($c = fgetc($fp)) {
if($readState == ESCAPED) {
$accumulator[] = $c;
$readState = NORMAL;
} else if($c == '%') {
$readState = ESCAPED;
} else if($c == ',') {
$fields[] = implode(null,$accumulator);
$accumulator = array();
} else if($c == '\\') {
save_fields_to_mysql($fields);
$fields = array();
$accumulator = array();
} else
$accumulator[] = $c;
}
Run Code Online (Sandbox Code Playgroud)
即,任何出现的 % 都会设置一个状态变量,该变量指示在下一次循环中,我们读取的任何字符都将被视为文字数据,它是字段而不是指示符的一部分。
这应该使您的内存使用量保持在最低限度。
[更新] I/O 效率怎么样?
一位评论者正确地指出,该图是 I/O 密集型的,并且由于 I/O 往往是时间成本最高的操作,因此完全有可能这不是一个可接受的解决方案。
另一方面,我们可以选择将整个文件缓冲到内存中,其中包括 Asker 提到但想要避免的原始内存密集型解决方案。快乐的媒介可能位于中间的某个地方:我们可以使用可以作为第二个参数传递的读取限制,fgets()在单个 I/O 吞吐中引入稍大(但不是大得离谱)的字符数,然后逐个字符地处理该缓冲区而不是 I/O 流,并在我们烧完缓冲区时重新填充它。
不过,这确实使读取过程的代码密集程度更高$c = fgetc($fp),因为您必须监视缓冲区中的位置、缓冲区的满度以及文件中的位置。如果需要,您可以在读取循环内使用一系列标志和索引变量来完成此操作,但使用如下所示的抽象可能会更方便:
class StrBufferedChrReader {
private $_filename;
private $_fp;
private $_bufferIdx;
private $_bufferMax = 2048;
private $_buffer;
function __construct($filename=null,$bufferMax=null) {
if($bufferMax) $this->_bufferMax = $bufferMax;
if($filename) $this->open($filename);
}
function _refillBuffer() {
if($this->_fp) {
$this->_buffer = fgets($this->_fp,$this->_bufferMax + 1);
$this->_bufferIdx = 0;
return $this->_buffer;
}
return false;
}
function open($filename=null) {
if($filename) $this->_filename = $filename;
if($this->_fp = fopen($this->_filename))
$this->_refillBuffer();
return $this->_fp;
}
function getc() {
if($this->_bufferIdx == $this->_bufferMax)
if(!$this->_refillBuffer())
return false;
return $this->_buffer[$this->_bufferIdx++];
}
function close() {
$this->_buffer = null;
$this->_bufferIdx = null;
return fclose($this->_fp);
}
}
Run Code Online (Sandbox Code Playgroud)
您可以在上面的任一循环中使用它,如下所示:
$r = new StrBufferedChrReader($filename,$bufferSize);
while($c = $r->getc()) {
...
Run Code Online (Sandbox Code Playgroud)
像这样的事情允许您通过更改 $bufferSize 来在内存密集型解决方案和 I/O 密集型解决方案之间的连续体上标出许多不同的点。$bufferSize 越大,内存使用量越大,I/O 操作越少。$bufferSize 更小,内存使用量更少,I/O 操作更多。
(注意:不要假设该类已准备好用于生产。它只是作为可能的抽象的说明,可能包含一对一或其他错误。可能会导致视力模糊、睡眠不足、心悸或其他方面效果。使用前请咨询医生并进行单元测试。)