【问题标题】:How to read any local file by chunks using JavaScript?如何使用 JavaScript 按块读取任何本地文件?
【发布时间】:2018-10-19 15:22:27
【问题描述】:

如何通过块(2kb或更多)在本地读取任何大文件(大于1 GB),然后将块转换为字符串,处理字符串然后获取下一个块,依此类推,直到结束文件?

我只能读取小文件并将其转换为字符串,从代码中可以看出我不知道如何分块读取文件。如果我尝试使用大于 10mb 的文件,浏览器会冻结。

<html>
  <head>
    <title>Read File</title>
  </head>

  <body>
    <input type="file" id="myFile">
    <hr>
    <textarea style="width:500px;height: 400px" id="output"></textarea>

    <script>
      var input = document.getElementById("myFile");
      var output = document.getElementById("output");
      input.addEventListener("change", function () {
        if (this.files && this.files[0]) {
          var myFile = this.files[0];
          var reader = new FileReader();
          reader.addEventListener('load', function (e) {
            output.textContent = e.target.result;
          });
          reader.readAsBinaryString(myFile);
        }
      });
    </script>

  </body>
</html>

以下是我在研究如何完成它时在 StackOverflow 上找到的链接和答案,但它并没有解决我的问题。

1: 这个问题是在询问如何使用 UniversalXPConnect,并且仅在 Firefox 中,这就是为什么我发现那里的答案无关紧要,因为我使用 Chrome 并且不知道UniversalXPConnect 是什么。 How to read a local file by chunks in JavaScript

2: 这个问题是在询问如何仅读取文本文件,但我希望能够读取任何文件,而不仅仅是文本,也可以按块读取,这使得那里的答案无关紧要,但我喜欢答案的代码有多短。 Reading local text file into a JavaScript array [duplicate]

3:这也是关于文本文件的,并没有展示如何按块读取文件How to read a local text file.

我懂一点Java,你可以很容易地做到这一点;

char[] myBuffer = new char[512];
int bytesRead = 0;
BufferedReader in = new BufferedReader(new FileReader("foo.mp4"));
while ((bytesRead = in.read(myBuffer,0,512)) != -1){
...
}

但我是 javascript 新手

【问题讨论】:

  • 您是否尝试在浏览器中执行此操作?还是您使用的是 Node.js?
  • 是的,我正在尝试使用浏览器来实现它。谢谢。
  • FileReader 应该是异步的,并且您实际上并不想在这些分块读取之间做任何事情,所以我认为它可能不是您想要的。换一种说法:为什么你会期望分块阅读它会比你已经看到的更好?出于测试目的,我还将查看浏览器是否在读取或输出时冻结。将“加载”回调更改为类似于console.log("Done Reading!"); 如果在看到之前没有冻结,则它是浏览器的输出。
  • 刚刚测试过。肯定会看到浏览器冻结,而且肯定是 文件被读取之后。所以你需要一种方法来循环遍历结果,而不是文件本身。可能readAsArrayBuffer 是一个不错的起点。

标签: javascript html performance file filereader


【解决方案1】:

您可以使用 fs.createReadStream() 来做到这一点,可能缓冲的数据量取决于传递给流构造函数的 highWaterMark 选项。 所以你会这样做:

var read = fs.createReadStream('/something/something', { highWaterMark: 64 });

这是一个例子:

var fs = require('fs')
var read = fs.createReadStream('readfile.txt',{highWaterMark:64}) 
var write = fs.createWriteStream('written.txt')
read.on('open', function () {
    read.pipe(write);
});

看看它是如何一次读取 64 字节的(非常慢),你可以在资源管理器上以一种有趣的方式查看它,但要确保你有一个大的文本文件来测试它,而不是像我一样的千兆字节,而是至少 17 兆字节确实“用任何虚拟文本填充它” 将文件视图设置为“详细信息”并在 Windows 资源管理器中不断刷新目标位置,每次刷新都会看到大小增加。

我假设你知道 pipe 方法,如果你不知道,没问题!这很简单,这是一个链接: https://nodejs.org/api/stream.html#stream_readable_pipe_destination_options

或快速解释: readable.pipe(writable) pipe() 函数在可读流可用时读取数据并将其写入目标可写流。

【讨论】:

  • 我不明白如何将它添加到我的代码中,这不是 Node.js 吗?我如何将您的建议落实到我的代码中?
  • 在这里,我通过快速测试编辑了答案,如果它不起作用或者您需要更多帮助,请告诉我。
  • 不幸的是,问题是关于不使用 node.js
  • 好吧对不起,我其实不知道你可以不用node.js来读取文件
【解决方案2】:

所以问题不在于FileReader,而在于:

output.textContent = e.target.result;

因为您试图一次将 10MB 以上的字符串转储到 textarea 中。我什至不确定是否有一种“正确”的方式来做你想做的事情,因为即使你确实有它在块中,它仍然必须在每个循环中连接 output.textContent 的先前值通过这些块,这样当它接近尾声时,它会以同样的方式开始减速(更糟糕的是,真的,因为它会在每个循环上做缓慢的内存占用业务)。所以我认为循环过程的一部分将不得不添加一个新元素(比如一个新的textarea 将当前块推送到(所以它不必做任何连接来保留已经输出的内容) . 我还没有完成那部分,但这是我目前所得到的:

  var input = document.getElementById("myFile");
  var output = document.getElementById("output");
  var chunk_length = 2048; //2KB as you mentioned
  var chunker = new RegExp('[^]{1,' + chunk_length + '}', 'g');
  var chunked_results;

  input.addEventListener("change", function () {
    if (this.files && this.files[0]) {
      var myFile = this.files[0];
      var reader = new FileReader();
      reader.addEventListener('load', function (e) {
        chunked_results = e.target.result.match(chunker);
        output.textContent = chunked_results[0];
      });
      reader.readAsBinaryString(myFile);
    }
  });

这只是输出 2KB 块数组中的第一个字符串。只要在 DOM 文档中添加一个新元素/节点以输出所有其他块,您就会想做您的事情。

使用RegExpmatch 进行实际分块是从我发现的clever gist 中提取的。

【讨论】:

  • 谢谢 Anthony,但我自己解决了这个问题,虽然和你自己的类似。我不明白“RegExp('.{1,' + chunk_length + '}', 'g');”不过,我会为任何有同样问题的人发布我的解决方案。虽然大文件看起来很慢,但也许你可以帮忙?
  • 太棒了。很高兴我能帮忙(或不能)。
  • 我刚刚用一个 68MB 的文件测试了我的解决方案,它严重阻塞了浏览器。那是var content = document.createTextNode(chunk); output.appendChild(content); 所以大概没有串联(或者至少在表面上没有串联)。我将再次尝试为每个块添加一个实际的新 textarea 并进行比较,但如果这也让浏览器挂起,我想任何解决方案都会涉及某种类型的工人。
  • 检查我的答案,虽然可以按我的意愿工作,但我将不得不处理大文件的性能,欢迎您的任何改进。
  • 我认为你基本上在做我做的同样的事情,你只是在将文件传递给FileReader之前对文件进行切片,而我在使用@完成后将文件拆分为一个数组987654333@。两者都没有大量开销,是浏览器的输出杀死了它。我尝试为每个块添加一个新的文本区域,但它仍然用 20MB 的文件破坏了所有内容。所以问题不是“我如何把它分解成更小的块?”它是“如何将 10MB 以上的数据输出到浏览器窗口?”
【解决方案3】:

我能够通过对文件进行切片来解决这个问题,方法是指定切片的开始位置和结束位置的属性,这将是块,然后我将它包含在一个 while 循环中,以便每个循环块的位置将根据到所需的块大小,直到文件结束。

但是在运行它之后,我最终得到了文本区域中块的最后一个值,因此为了显示所有二进制字符串,我在每次迭代时连接输出。

<html>
<head>
  <title>Read File</title>
</head>

<body>
  <input type="file" id="myFile">
  <hr>
  <textarea style="width:500px;height: 400px" id="output"></textarea>

  <script>
    var input = document.getElementById("myFile");
    var output = document.getElementById("output");
    var chunk_size = 2048;
    var offset = 0;
    input.addEventListener("change", function () {
      if (this.files && this.files[0]) {
        var myFile = this.files[0];
        var size = myFile.size; //getting the file size so that we can use it for loop statement
        var i=0;
        while( i<size){
        var blob = myFile.slice(offset, offset + chunk_size); //slice the file by specifying the index(chunk size)
        var reader = new FileReader();
        reader.addEventListener('load', function (e) {
          output.textContent += e.target.result; //concatenate the output on each iteration.
        });
        reader.readAsBinaryString(blob);
        offset += chunk_size; // Increment the index position(chunk) 
        i += chunk_size; // Keeping track of when to exit, by incrementing till we reach file size(end of file).
        }
      }
    });
  </script>

</body>
</html>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-10-02
    • 1970-01-01
    • 2014-06-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-05
    相关资源
    最近更新 更多