如何强制JavaScript深度复制字符串?

Aff*_*Owl 50 javascript garbage-collection memory-management google-chrome

我有一些javascript代码,如下所示:

var myClass = {
  ids: {}
  myFunc: function(huge_string) {
     var id = huge_string.substr(0,2);
     ids[id] = true;
  }
}
Run Code Online (Sandbox Code Playgroud)

稍后,使用一些大字符串(100 MB +)调用该函数.我只想保存我在每个字符串中找到的短ID.但是,Google Chrome的子字符串函数(在我的代码中实际为正则表达式)只返回一个"切片字符串"对象,该对象引用原始对象.因此,在一系列调用之后myFunc,我的chrome选项卡内存不足,因为临时huge_string对象无法进行垃圾回收.

如何制作字符串的副本,id以便huge_string不维护对该字符串的引用,并且huge_string可以对其进行垃圾回收?

在此输入图像描述

Aff*_*Owl 46

JavaScript的ECMAScript实现因浏览器而异,但对于Chrome,许多字符串操作(substr,slice,regex等)只保留对原始字符串的引用,而不是复制字符串.这是Chrome中的一个已知问题(Bug#2869).要强制使用该字符串的副本,以下代码可以正常工作:

var string_copy = (' ' + original_string).slice(1);
Run Code Online (Sandbox Code Playgroud)

此代码的工作原理是在字符串的前面附加一个空格.此串联导致Chrome实现中的字符串副本.然后可以引用空格后面的子串.

解决方案的这个问题已经在这里重新创建:http://jsfiddle.net/ouvv4kbs/1/

警告:需要很长时间才能加载,打开Chrome调试控制台以查看进度打印输出.

// We would expect this program to use ~1 MB of memory, however taking
// a Heap Snapshot will show that this program uses ~100 MB of memory.
// If the processed data size is increased to ~1 GB, the Chrome tab
// will crash due to running out of memory.

function randomString(length) {
  var alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  var result = '';
  for (var i = 0; i < length; i++) {
    result +=
        alphabet[Math.round(Math.random() * (alphabet.length - 1))];
  }
  return result;
};

var substrings = [];
var extractSubstring = function(huge_string) {
  var substring = huge_string.substr(0, 100 * 1000 /* 100 KB */);
  // Uncommenting this line will force a copy of the string and allow
  // the unused memory to be garbage collected
  // substring = (' ' + substring).slice(1);
  substrings.push(substring);
};

// Process 100 MB of data, but only keep 1 MB.
for (var i =  0; i < 10; i++) {
  console.log(10 * (i + 1) + 'MB processed');
  var huge_string = randomString(10 * 1000 * 1000 /* 10 MB */);
  extractSubstring(huge_string);
}

// Do something which will keep a reference to substrings around and
// prevent it from being garbage collected.
setInterval(function() {
  var i = Math.round(Math.random() * (substrings.length - 1));
  document.body.innerHTML = substrings[i].substr(0, 10);
}, 2000);
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述


Pir*_*jan 21

不确定如何测试,但使用字符串插值创建新的字符串变量是否有效?

newString = `${oldString}`
Run Code Online (Sandbox Code Playgroud)

  • 这绝对有效并且性能非常好。在 4K 长度的字符串上进行测试,平均性能约为 0.004 毫秒。有好几次,执行时间都在0.001ms左右。这是我在控制台中运行的测试: `!function () { const outputArr = []; const 字符 = 'ABC'; while(outputArr.length &lt; 4000) { outputArr.push( chars[Math.floor(Math.random() * chars.length)])} const 输出 = outputArr.join(''); console.time('插值'); const newVariable = \`${输出}\`; console.timeEnd('插值'); }(); ` (2认同)

小智 12

您可以使用:

 String.prototype.repeat(1) 
Run Code Online (Sandbox Code Playgroud)

它似乎运作良好。请参阅 上的MDN 文档repeat

  • `var a = "嗨"; var b = a.repeat(1);` 对我有用。我尝试更改“a”,而“b”保持不变。 (2认同)

Dan*_*eng 10

我将Object.assign()方法用于字符串,对象,数组等:

const newStr = Object.assign("", myStr);
const newObj = Object.assign({}, myObj);
const newArr = Object.assign([], myArr);
Run Code Online (Sandbox Code Playgroud)

请注意,Object.assign仅将键及其属性值复制到一个对象内(仅限一级)。要深度克隆嵌套对象,请参考以下示例:

let obj100 = { a:0, b:{ c:0 } };
let obj200 = JSON.parse(JSON.stringify(obj100));
obj100.a = 99; obj100.b.c = 99; // No effect on obj200
Run Code Online (Sandbox Code Playgroud)

  • `const newStr = Object.assign("", myStr); console.log(newStr);` 这将打印一个数组:```[String: ''] {'0': 'H','1': 'e',...}]```。不幸的是不适用于字符串复制。 (2认同)

Mar*_*ons 7

编辑:这些测试早在 2021 年 9 月就在 Google Chrome 中运行,而不是在 NodeJS 中运行。

在这里看到一些回复很有趣。 如果您不担心旧版浏览器支持 (IE6+),请跳到插值方法,因为它的性能非常好。

按值复制字符串的最向后兼容(回到 IE6)且仍然非常高效的方法之一是将其拆分为一个新数组,并立即将新数组作为字符串重新加入:

let str = 'abc';
let copiedStr = str.split('').join('');
console.log('copiedStr', copiedStr);
Run Code Online (Sandbox Code Playgroud)

幕后花絮

上面的代码的作用是调用 JavaScript 不使用任何字符作为分隔符来拆分字符串,这会将每个单独的字符拆分为新创建的数组中自己的元素。这意味着,在短时间内,copiedStr变量看起来像这样:

['a', 'b', 'c']
Run Code Online (Sandbox Code Playgroud)

然后,立即copiedStr重新连接该变量,在每个元素之间不使用任何字符作为分隔符,这意味着新创建的数组中的每个元素都被推回到一个全新的字符串中,从而有效地复制了该字符串。

执行结束时,copiedStr是它自己的变量,输出到控制台:

abc
Run Code Online (Sandbox Code Playgroud)

表现

平均而言,在我的机器上这大约需要 0.007 毫秒 - 0.01 毫秒,但您的情况可能会有所不同。在 4,000 个字符的字符串上进行测试,该方法复制字符串的最大时间为 0.2 毫秒,平均约为 0.14 毫秒,因此它仍然具有稳定的性能。

无论如何,谁关心旧版支持?/插值方法

但是,如果您不担心旧版浏览器支持,那么Pirijan 的interpolation答案之一中提供的方法是一种非常高性能且易于复制字符串的方法:

let str = 'abc';
let copiedStr = `${str}`;
Run Code Online (Sandbox Code Playgroud)

在相同的 4,000 个字符长度的字符串上测试性能interpolation,我发现平均为 0.004 毫秒,最大值为 0.1 毫秒,最小值为惊人的 0.001 毫秒(非常频繁)。