2013-09-28 22 views
2

我需要转换文本文件的字符编码而不占用服务器的内存,而输入文件是用户配置的,其大小不受限制。PHP使用iconv转换大文件

使用exec()(我宁愿避免,尽管我已经在应用程序中使用它来执行其他文件操作)来包装unix的iconv命令会更高效,还是应该逐行读取文件并将其输出到另一个文件?

我想以这种方式工作:

$in = fopen("in.txt", "r"); 
$out = fopen("out.txt", "w+"); 
while(($line = fgets($in, 4096)) !== false) { 
    $converted = iconv($charset["in"], $charset["out"], $line); 
    fwrite($out, $converted); 
} 
rename("out.txt", "in.txt"); 

有没有更好的办法将文件快速和有效地转换?我认为这可能是CPU密集型的,但是我相信iconv本身是一项昂贵的任务,所以我不确定我是否可以使它实际上不会吃太多的服务器。

谢谢!

+0

为什么你不尝试两种方式,并比较速度和内存负载? –

+0

现在只是在玩它,虽然我的测试数据并不能很好地代表产品的性能,因为这些数据的大小和形式在请求之间会有所不同,所以我想知道是否有关于如何处理这些数据的一般想法。 – kachnitel

回答

2

好吧, 感谢投入,我做了“功课”的基础上,并得到的结果,与实际CSV数据的50MB采样工作:

首先,使用过的文件迭代PHP:

$in = fopen("a.txt", "r"); 
$out = fopen("p.txt", "w+"); 

$start = microtime(true); 

while(($line = fgets($in)) !== false) { 
    $converted = iconv("UTF-8", "EUC-JP//TRANSLIT", $line); 
    fwrite($out, $converted); 
} 

$elapsed = microtime(true) - $start; 
echo "<br>Iconv took $elapsed seconds\r\n"; 


语言Iconv了2.2817220687866秒

这并不是如此糟糕,我想,等我在#bash中尝试了完全相同的方法,因此它不必加载所有文件,而是遍历每行代码(这可能不会完全发生,因为我理解Lajos Veres的回答)。事实上,这种方法并不完全有效(CPU始终处于高负载状态)。另外,输出文件比其他2小,尽管在快速浏览后它看起来是相同的,所以我必须在bash脚本中犯了一个错误,但是,对性能不应该有这样的影响:

#!/bin/bash 
echo "" > b.txt 
time echo $(
    while read line 
    do 
     echo $line |iconv -f utf-8 -t EUC-JP//TRANSLIT >> b.txt 
    done < a.txt 
) 

真正9m40.535s 用户2m2.191s SYS 3m18.993s

再经典的做法,我没有料想到会养猪的内存,然而,检​​查CPU /内存使用,它似乎没有比任何其他方法更多的记忆,因此成为赢家:

#!/bin/bash 
time echo $(
    iconv -f utf-8 -t EUC-JP//TRANSLIT a.txt -o b2.txt 
) 

真正0m0.256s 用户0m0.195s SYS 0m0.060s

我会尽力得到一个更大的文件样本,以测试2种有效的方法,以确保内存使用并不显着,但是,结果似乎很明显,足以假设在bash中通过整个文件的单次传递效率最高(我没有在PHP中尝试过,因为我相信将整个文件加载到数组/ PHP中的字符串永远不是一个好主意)。

1

FYI: http://sourceware.org/bugzilla/show_bug.cgi?id=6050

但无论如何,操作系统需要读取整个文件迟早的事。这意味着当它读取缓存时,清除lru-like逻辑将释放内存。 lru意味着可能更旧的页面将被丢弃。

你不能100%确定你的系统将如何容忍这个。您必须将此过程与不同的硬件或虚拟化分开,但这些解决方案也会造成瓶颈。

谨慎测试可能是最具成本效益的方式。但是,实施可能会导致大部分头痛,而不是预期的工作量。

我的意思是在百个并行线程中处理大量的g文件与每天几个文件完全不同。

+0

很高兴知道,谢谢!因此,我将比较整个文件加载到Dalim发布的2个脚本,看看它如何在我们的开发环境中与一些示例数据进行比较(尽管它是一个接口,可以传输几乎任何文件,因此它可以是任何类型的任何文件,线路长度)。 – kachnitel

1

这是Iconv与PHP和Iconv与Unix Bash的基准测试。

对于PHP - >

<?php 
$text = file('a.txt'); 
$text = $text[0]; 
$start = microtime(true); 
for ($i = 0; $i < 1000; $i++) { 
$str = iconv("UTF-8", "EUC-JP", $text); 
} 
$elapsed = microtime(true) - $start; 
echo "<br>Iconv took $elapsed seconds\r\n"; 
?> 

取决于我的服务器的测试结果,

[email protected]:/var/www# php benc.php 
<br>Iconv took 0.0seconds 

对于Unix巴什 - >

#!/bin/bash 
begin_time=$(($(date +%N)/10000000)) 
for i in {0..1000} 
do 
     iconv -f utf-8 -t EUC-JP a.txt -o b.txt 
done 
end_time=$(($(date +%s%N)/1000000)) 
total_time=$((end_time-begin_time)) 
echo ${total_time} 

取决于我的服务器的测试结果,

[email protected]:/var/www#bash test.sh 
1380410308211 

结果很明显地表明,在CPU占用率中,您可以从使用PHP的iConv获得更多性能。需要指出的是,赢家使用内存不如CPU。

注意:如果你运行,你应该在与* .sh和* .php文件相同的字典中创建和a.txt文件。

+0

谢谢,玩了这个,虽然bash时间并没有给我正确的时间,但是两次改变为'$(($(date +%s%N)/ 1000000))'修正了这个问题。但是,bash也会写输出,这可能会使其处于劣势,我将在几台服务器上进行比较,以及如何编写输出。 – kachnitel

0

为什么不直接在系统中执行而不是按块读取文件块。鉴于iconv存在于您的系统

system(sprintf('iconv -f %s -t %s %s > %s', 
       $charset['in'], $charset['out'], "in.txt", "out.txt"));