-1

我正在尝试为 javascript 中的STEP 文件编写一个解析器,它将主要用于浏览器,但也用于 Node,现在我使用 Node 进行调试。

它进展顺利,并且正在解析一段时间。但是,当我处理具有数百万行(大约 200Mb 或更多)的非常大的文件时,它会阻塞并最终崩溃并抱怨 JavaScript 堆内存不足!

这些文件看起来像这样:

...
#10=ORGANIZATION('O0001','LKSoft','company');
#11=PRODUCT_DEFINITION_CONTEXT('part definition',#12,'manufacturing');
#12=APPLICATION_CONTEXT('mechanical design');
#13=APPLICATION_PROTOCOL_DEFINITION('','automotive_design',2003,#12);
#14=PRODUCT_DEFINITION('0',$,#15,#11);
#15=PRODUCT_DEFINITION_FORMATION('1',$,#16);
#16=PRODUCT('A0001','Test Part 1','',(#18));
#17=PRODUCT_RELATED_PRODUCT_CATEGORY('part',$,(#16));
#18=PRODUCT_CONTEXT('',#12,'');
...
#3197182=APPLIED_ORGANIZATION_ASSIGNMENT(#10,#20,(#16));
#3197183=ORGANIZATION_ROLE('id owner');

这些文件有点不规则,所以我正在编写一个非常生硬的解析器,逐字母解析:

const fs = require('fs');

class bigObject {
  constructor(data) {

    this.parse(data);
  }

  propertyLexer(row) {

    let refNrRE = /[-0-9]/;
    let floatNumberRE = /[.\-0-9E]/;
    let charsRE = /[_a-zA-Z.]/;
    let stringRE = /'((?:''|[^'])*)'/;

    let lexedRow = [];
    let current = 0;
    let rowLen = row.length;

    while (current < rowLen) {
      let char = row[current];

      // I.E. #32123
      if (char === '#') {
        let property = '';

        while (refNrRE.test(row[current + 1]) && current < rowLen) {
          current++;
          property += row[current];
        }

        lexedRow.push(parseInt(property));

        current++;
      }

      // Empty property
      else if (char === '$') {
        lexedRow.push('');

        current++;
      }

      // Skip to next property
      else if (char === ',') {
        current++;
      }

      // I.E. 'Comments, blabla (more comments)'
      else if (char === "'") {
        let property = stringRE.exec(row.substr(current));

        lexedRow.push(property[1]);

        current += property[1].length + 2;
      }

      // I.E. .AREAUNIT.
      else if (charsRE.test(char)) {
        let property = '';

        while (charsRE.test(row[current]) && current < rowLen) {
          property += row[current];

          current++;
        }

        lexedRow.push(property);
      }

      // I.E. -1000.00
      else if (floatNumberRE.test(char)) {
        let property = '';

        while (floatNumberRE.test(row[current]) && current < rowLen) {
          property += row[current];

          current++;
        }

        lexedRow.push(property);
      }

      // Skip rest for now
      else {
        current++;
      }
    }

    return lexedRow;
  }

  parse(data) {
    if (typeof data !== "string") {
      try {
        data = data.toString();
      }
      catch (e) {
        throw `Indata not string or not able to convert to string: ${e}`;
      }
    }

    let stepRowRE = /#\d+\s*=\s*[a-zA-Z0-9]+\s*\([^)]*(?:\)(?!;)[^)]*)*\);/g;

    // Split single row into three capture groups
    let singleRowWithGroupingRE = /^#(\d+)\s*=\s*([a-zA-Z0-9]+)\s*\(([^)]*(?:\)(?!;)[^)]*)*)\);/;

    let stepRows = data.match(stepRowRE);
    let rowIndex = stepRows.length - 1;
    let rowsFromFile = {};
    let count = 0;

    for (let i = 0; i <= rowIndex; i++) {
      let matching = singleRowWithGroupingRE.exec(stepRows[i]);

      rowsFromFile[matching[1]] = {c: matching[2], p: this.propertyLexer(matching[3].replace(/(\r\n|\n|\r)/gm, ''))};

      if (i % 200000 === 0) {
        console.log(i + '::' + JSON.stringify(rowsFromFile[matching[1]]));
      }

      count++;

    }
  }
}

//// Start here ////

fs.readFile('./ifc-files/A-40-V-00252.ifc', (err, data) => {
  let newObject = new bigObject(data);
});

我收到此错误:

<--- Last few GCs --->

[11348:000002D4A6E72260]    81407 ms: Mark-sweep 1403.2 (1458.8) ->
1403.2 (1458.8) MB, 2428.1 / 0.0 ms  allocation failure GC in old space requested [11348:000002D4A6E72260]    83836 ms: Mark-sweep
1403.2 (1458.8) -> 1403.2 (1428.8) MB, 2429.0 / 0.0 ms  last resort gc [11348:000002D4A6E72260]    86282 ms: Mark-sweep 1403.2 (1428.8) ->
1403.1 (1428.8) MB, 2446.3 / 0.0 ms  last resort gc


<--- JS stacktrace --->

==== JS stack trace =========================================

Security context: 00000384656C0D51 <JS Object>
    1: parse [C:\Users\user\Projects\parser\index.js:~95] [pc=000000525FB71B18](this=000001EE5F96DE19 <a bigObject with map 0000036221B1B7A9>,data=0000034357F04201 <Very long string[190322237]>)
    2: new bigObject [C:\Users\user\Projects\parser\index.js:8] [pc=000000525FB48737](this=000001EE5F96DE19 <a bigObject with map 0000036221B1B7...

FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory

几天来我一直试图找出原因,但我看不到任何看起来像内存泄漏或无限循环的东西。

我的机器有 16Gb 内存,应该可以轻松处理 200mb 的文件,而且可以处理很多次!

有没有人可以帮助我解决我的问题?谢谢!

编辑:如果我使用 Firefox 甚至 Edge(!),以及当我使用--max_old_space_size=4096flag 增加 Chrome/Node(V8)的可用内存时,一切都很好。但是普通用户不太可能这样做......所以我仍然需要提高内存效率。但我不知道如何。

EDIT2:这不是 JSON.stringify 或我阅读了导致问题的整个文件的事实。如果我尝试读取比当前更大的文件,这将是一个问题。但现在更多的是因为我在内存中存储了太多东西。

4

1 回答 1

1

在您遇到任何复杂的事情之前,您的应用程序就会崩溃:第 95 行的崩溃发生在您调用 data.toString() 时。

显然 Node.js 不喜欢 200MB 的字符串。这并不特别令人惊讶。任何 String 实现都需要 200MB。

由于您的输入文件由换行符分隔的记录组成,我认为 mscdex 的建议是正确的方法:使用readline,逐行读取文件,并解析每一行。

代码示例似乎可以满足您的要求。

逐行方法的另一个好处是它不会阻塞事件循环。与其在没有机会交错其他事件的情况下完成一项艰巨的任务,您可以轻松地构建应用程序以在每个行事件之间产生。readline可能会自动为您执行此操作,但可能不会。

一些相关的 SO 问题:this onethat one

于 2017-02-15T16:34:14.647 回答