php FileReader读取大文件特定行数

该PHP类FileReader用于处理CSV文件,支持设置文件名、获取文件行数、读取指定范围的数据。它利用SplFileObject进行读取,提供了错误检查和文件有效性验证功能。
<?php
/**
 * 使用方法:
 * include('./FileReader.php');
 * $file = __DIR__.'/20190204 103 EDA CA1.csv';
 * $reader = new FileReader($file);
 * $line_number = $reader->get_lines();
 * $data = $reader->get_data(10,10);
 * Class FileReader
 */

class FileReader
{
    private $file_name;
    private $spl_object = null;
    private $error;
    private $file_type;

    public function __construct($file_name = '')
    {
        if ($file_name && file_exists($file_name)) {
            $this->file_name = $file_name;
        }
        $fileExtension = pathinfo($file_name, PATHINFO_EXTENSION);
        if ($fileExtension == 'csv') {
            $this->file_type = 'csv';
        }
    }

    public function set_file_name($file_name)
    {
        if (!$file_name || !file_exists($file_name)) {
            $this->error = 'File invalid';
            return false;
        }
        $this->file_name = $file_name;
        $this->spl_object = null;
    }

    public function get_file_name()
    {
        return $this->file_name;
    }

    private function _file_valid($file = '')
    {
        $file = $file ? $file : $this->file_name;
        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->file_name, 'rb');
        }
        return true;
    }

    public function get_data($length = 0, $start = 0)
    {
        if (!$this->_open_file()) {
            return false;
        }
        if($this->file_type == 'csv'){
            $this->spl_object->setFlags(\SplFileObject::READ_CSV);
        }
        $length = $length > 0 ? $length : $this->get_lines();
        $start = ($start < 0) ? 0 : $start;
        $data = array();
        $this->spl_object->seek($start);
        for($i = 0; $i < $length; ++$i) {
            $dataline = $this->spl_object->current();
            if (empty($dataline)) {
                if($this->spl_object->eof()){
                    break;
                }
                continue;
            } else {
                $data[] = yield $dataline;
            }
            $this->spl_object->next();
        }
        return $data;
    }

    public function get_lines()
    {
        if (!$this->_open_file()) {
            return false;
        }
        $this->spl_object->seek(filesize($this->file_name));
        return $this->spl_object->key();
    }

    public function get_error()
    {
        return $this->error;
    }
}

<think> 我们正在处理的问题是:如何处理FileReader读取大文件时的内存问题? 根据引用[2]和引用[3]的内容,我们知道FileReader读取大文件时会将整个文件内容加载到内存中,对于非常大的文件,这可能导致内存溢出、浏览器崩溃或读取失败(返回null)。因此,我们需要避免一次性读取整个文件。 引用[2]提到可以采用分段读取的方法,引用[3]则建议使用URL.createObjectURL()来创建一个指向文件的URL,然后通过其他方式(例如分块读取)来处理文件。 所以,解决思路有两种: 1. 使用FileReader的分段读取(分片读取)功能,通过多次读取小片段来避免一次性加载大文件。 2. 使用Blob.slice方法将文件分割成多个Blob片段,然后分别读取。 此外,引用[3]还提到可以使用URL.createObjectURL()来获取一个内存URL,这个URL可以用于创建指向文件对象的引用,然后我们可以通过XMLHttpRequest、Fetch API或者配合流式API(如ReadableStream)来分块处理文件内容。 下面我们分别介绍两种主要方法: 方法一:分段读取(使用FileReader的readAsArrayBuffer或readAsText分段) 步骤: a. 获取文件对象,并使用slice方法将文件分割成多个Blob片段(分块)。 b. 逐个读取每个片段,处理完一个片段后再读取下一个。 方法二:使用流式处理(Streams API) 这种方法更现代,但兼容性需要考虑。我们可以使用File.stream()方法获取ReadableStream,然后逐块处理。 考虑到兼容性,我们先介绍方法一(分段读取)的示例代码,然后介绍方法二(流式处理)。 方法一:分段读取示例代码 ```html <input type="file" id="fileInput"> <script> document.getElementById('fileInput').addEventListener('change', function(e) { const file = e.target.files[0]; if (!file) return; // 设置每个分片的大小(例如1MB) const chunkSize = 1024 * 1024; let offset = 0; const readNextChunk = () => { const chunk = file.slice(offset, offset + chunkSize); const reader = new FileReader(); reader.onload = function(e) { // 处理当前分片的数据 console.log('读取到分片数据:', e.target.result); offset += chunkSize; // 如果还有剩余,继续读取下一个分片 if (offset < file.size) { readNextChunk(); } else { console.log('文件读取完成'); } }; reader.onerror = function(error) { console.error('读取分片出错:', error); }; // 以ArrayBuffer形式读取分片(也可以根据需要使用readAsText,但大文件通常用二进制处理) reader.readAsArrayBuffer(chunk); }; // 开始读取第一个分片 readNextChunk(); }); </script> ``` 方法二:使用流式处理(Streams API)示例代码 注意:Streams API较新,在较新的浏览器中支持。它允许逐块处理数据而不需要将整个文件加载到内存中。 ```html <input type="file" id="fileInput"> <script> document.getElementById('fileInput').addEventListener('change', async function(e) { const file = e.target.files[0]; if (!file) return; // 获取文件的ReadableStream const stream = file.stream(); const reader = stream.getReader(); while (true) { const { value, done } = await reader.read(); if (done) { console.log('文件读取完成'); break; } // value是一个Uint8Array,代表一块数据(chunk) console.log('读取到分片数据:', value); // 在这里处理每一块数据 } }); </script> ``` 方法三:使用URL.createObjectURL()结合Range请求(如果用于视频等媒体则可以直接用,但用于处理整个文件内容则不常用,这里不展开) 总结: 1. 对于大文件,避免使用FileReader.readAsText()一次性读取整个文件。 2. 使用分片读取FileReader + Blob.slice)或者流式处理(Streams API)来逐块处理文件。 3. 分片读取兼容性更好,而流式处理更现代高效。 注意事项: - 分片大小需要根据实际情况调整,太小会导致读取次数过多,太大则可能仍然占用较多内存。 - 处理过程中,如果文件非常大,可能需要考虑暂停和恢复机制,避免阻塞主线程(可以使用Web Worker)。 引用[3]提到的方法(URL.createObjectURL)通常用于直接引用文件(例如作为图片或视频的源),而不用于读取文件内容。因此,对于大文件的内容处理,我们还是推荐分片读取或流式处理。 根据引用[2]和引用[3]的内容,我们给出了这两种解决方案[^2][^3]。 §§相关问题§§ 1. 如何通过Web Worker避免大文件处理阻塞主线程? 2. 如何使用JavaScript处理二进制文件(如图片、视频)? 3. 流式处理API(Streams API)的浏览器兼容性如何? </think> ### 处理FileReader读取大文件时的内存问题方法 当使用`FileReader`读取大文件时,主要会遇到以下内存问题: 1. **内存溢出**:一次性加载整个文件导致内存占用过高(特别是超过几百MB的文件) 2. **浏览器崩溃**:Chrome等浏览器可能直接崩溃 3. **读取失败**:文件内容可能返回`null`或触发`onerror`事件 4. **界面卡顿**:主线程阻塞导致页面无响应 以下是三种有效的解决方案: --- ### 方法一:分片读取(推荐) 通过`Blob.slice()`将文件分割成多个片段分批次处理: ```javascript const chunkSize = 5 * 1024 * 1024; // 5MB/片段 let offset = 0; function readChunk(file) { const chunk = file.slice(offset, offset + chunkSize); const reader = new FileReader(); reader.onload = (e) => { // 处理当前片段数据 processChunk(e.target.result); // 继续读取下一片段 offset += chunkSize; if (offset < file.size) { readChunk(file); } }; reader.readAsArrayBuffer(chunk); // 二进制读取效率更高 } ``` **优点**: - 兼容性好(支持IE10+) - 内存占用恒定(由分片大小决定) - 可实时显示进度条[^2] --- ### 方法二:流式处理(现代API) 使用Streams API实现真正的流式处理: ```javascript async function streamFile(file) { const stream = file.stream(); const reader = stream.getReader(); while (true) { const { done, value } = await reader.read(); if (done) break; // 处理Uint8Array格式的分块数据 processChunk(value); } } ``` **优点**: - 内存效率最高 - 支持暂停/恢复操作 - 原生异步处理 **注意**:需检测浏览器支持`file.stream()`[^3] --- ### 方法三:对象URL(仅显示) 对于不需要读取内容的场景(如图片/视频预览): ```javascript function previewFile(file) { const url = URL.createObjectURL(file); document.getElementById('preview').src = url; // 直接用于媒体元素 // 使用后需手动释放内存 // URL.revokeObjectURL(url); } ``` **适用场景**: - 图片/视频预览 - 文件下载 - PDF展示[^3] --- ### 通用优化建议 1. **二进制优先**:优先使用`readAsArrayBuffer()`而非`readAsText()` 2. **内存监控**:添加内存检查逻辑 ```javascript if (file.size > 100 * 1024 * 1024) { // 超过100MB alert('请使用分片上传'); return; } ``` 3. **Web Workers**:将处理移出主线程 4. **进度反馈**:添加`onprogress`事件更新UI ```javascript reader.onprogress = (e) => { const percent = Math.round((e.loaded / e.total) * 100); console.log(`${percent}% 已加载`); }; ``` > 对于超过500MB的超大文件,推荐组合使用**分片读取+Web Workers+流式处理**,并在服务器端实现分片上传机制[^1][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值