PHP读取CSV大文件导入数据库的示例
文章详细介绍了csv文件在php中快速导入到mysql数据库中的例子,虽然从最简单的几百MB的到最后使用插件实现几个GB数据导入中间有一些嗑碰了,但结果还是好的.
对于数百万条数据量的CSV文件,文件大小可能达到数百M,如果简单读取的话很可能出现超时或者卡死的现象.
为了成功将CSV文件里的数据导入数据库,分批处理是非常必要的,下面这个函数是读取CSV文件中指定的某几行数据,代码如下:
-
-
-
-
-
-
-
- function csv_get_lines($csvfile, $lines, $offset = 0) {
- if(!$fp = fopen($csvfile, 'r')) {
-
- return false;
- }
- $i = $j = 0;
- while (false !== ($line = fgets($fp))) {
- if($i++ < $offset) {
- continue;
- }
- break;
- }
- $data = array();
- while(($j++ < $lines) && !feof($fp)) {
- $data[] = fgetcsv($fp);
- }
- fclose($fp);
- return $data;
- }
-
-
-
- $data = csv_get_lines('path/bigfile.csv', 10, 2000000);
-
- print_r($data);
函数主要采用行定位的思路,通过跳过起始行数来实现文件指针定位,至于数据如何入库本文不再详细讲述.
上述函数对500M以内的文件进行过测试,运行通畅,对于1GB的文件发现有点慢了,于是再接着找方法.
如何快速完整的操作大文件仍然还存在一些问题.
1、如何快速获取CSV大文件的总行数?
办法一:直接获取文件内容,使用换行符进行拆分得出总行数,这种办法对小文件可行,处理大文件时不可行;
办法二:使用fgets一行一行遍历,得出总行数,这种办法比办法一好一些,但大文件仍有超时的可能;
办法三:借助SplFileObject类,直接将指针定位到文件末尾,通过SplFileObject::key方法获取总行数,这种办法可行,且高效。
具体实现方法,代码如下:
- $csv_file = 'path/bigfile.csv';
- $spl_object = new SplFileObject($csv_file, 'rb');
- $spl_object->seek(filesize($csv_file));
- echo $spl_object->key();
2、如何快速获取CSV大文件的数据?
仍然使用PHP的SplFileObject类,通过seek方法实现快速定位,代码如下:
- $csv_file = 'path/bigfile.csv';
- $start = 100000;
- $num = 100;
- $data = array();
- $spl_object = new SplFileObject($csv_file, 'rb');
- $spl_object->seek($start);
- while ($num-- && !$spl_object->eof()) {
- $data[] = $spl_object->fgetcsv();
- $spl_object->next();
- }
- print_r($data);
综合上面两点,整理成一个csv文件读取的类,代码如下:
- class CsvReader {
- private $csv_file;
- private $spl_object = null;
- private $error;
-
- public function __construct($csv_file = '') {
- if($csv_file && file_exists($csv_file)) {
- $this->csv_file = $csv_file;
- }
- }
-
- public function set_csv_file($csv_file) {
- if(!$csv_file || !file_exists($csv_file)) {
- $this->error = 'File invalid';
- return false;
- }
- $this->csv_file = $csv_file;
- $this->spl_object = null;
- }
-
- public function get_csv_file() {
- return $this->csv_file;
- }
-
- private function _file_valid($file = '') {
- $file = $file ? $file : $this->csv_file;
- if(!$file || !file_exists($file)) {
- return false;
- }
- if(!is_readable($file)) {
- return false;
- }
- return true;
- }
-
- private function _open_file() {
- if(!$this->_file_valid()) {
- $this->error = 'File invalid';
- return false;
- }
- if($this->spl_object == null) {
- $this->spl_object = new SplFileObject($this->csv_file, 'rb');
- }
- return true;
- }
-
- public function get_data($length = 0, $start = 0) {
- if(!$this->_open_file()) {
- return false;
- }
- $length = $length ? $length : $this->get_lines();
- $start = $start - 1;
- $start = ($start < 0) ? 0 : $start;
- $data = array();
- $this->spl_object->seek($start);
- while ($length-- && !$this->spl_object->eof()) {
- $data[] = $this->spl_object->fgetcsv();
- $this->spl_object->next();
- }
- return $data;
- }
-
- public function get_lines() {
- if(!$this->_open_file()) {
- return false;
- }
- $this->spl_object->seek(filesize($this->csv_file));
- return $this->spl_object->key();
- }
-
- public function get_error() {
- return $this->error;
- }
- }
调用方法如下:
- include('CsvReader.class.php');
-
- $csv_file = 'path/bigfile.csv';
-
- $csvreader = new CsvReader($csv_file);
-
- $line_number = $csvreader->get_lines();
-
- $data = $csvreader->get_data(10);
-
- echo $line_number, chr(10);
-
- print_r($data);
其实,上述CsvReader类并不只针对CSV大文件,对于其他文本类型的大文件或超大文件同样可用,前提是将类中fgetcsv方法稍加改动为current即可.