【问题标题】:Converting a larger byte array to a string将更大的字节数组转换为字符串
【发布时间】:2020-07-21 18:07:27
【问题描述】:

N 设置为125K 时,以下工作

let N = 125000
let x = [...Array(N)].map(( xx,i) => i)
let y = String.fromCodePoint(...x)
console.log(y.length)

当 N 设置为 128K 时,相同的代码会中断:

未捕获的 RangeError:超出最大调用堆栈大小

这是一个常见的操作:实现转换的最佳方式是什么?

请注意,我确实看过这个相关的问答。 https://stackoverflow.com/a/3195961/1056563 我们不应该依赖node.js,而且fromCharCode.apply 的方法也失败了。最后,这个答案已经有将近十年的历史了。

那么处理这种转换的最新高效方法是什么?

【问题讨论】:

  • 有趣。避免使用...(在对String.fromCodePoint 的调用中?)可能会解决这个问题。也就是说,我怀疑它与传递太多参数有关:stackoverflow.com/a/22747272/2864740
  • 哦!那是个很好的观点!随意回答。这不是一个真正的完整答案,但它指出了正确的方向,我会赞成
  • 它仍然无法工作,因为 fromCodePoint 不采用数组 :( 只是试图真正隔离观察到的问题。更大的问题仍然存在:“如何 [有效地] 从一个非常大的代码点序列?”
  • 是的,它不会 - 但是你意识到参数传递也不起作用是正确的。我正在寻找替代品
  • 使用“call”而不是“...”会改变行为吗?这可能是一个实现细节,取决于此类代码如何在内部统一。

标签: javascript


【解决方案1】:

问题是因为实现number of parameters accepted限制。当通过扩展运算符向 String.fromCodePoint 函数提供太多参数(在本例中超过 ~128k)时,这会导致引发异常。

解决这个问题的一种方法是相对有效地,虽然代码稍微多一些,但是在多个调用中批处理操作。这是我提出的实现,它解决了我认为与扩展性能相关的问题surrogate pairs 的处理(这是不正确的:fromCodePoint 不关心代理,使其更可取于@ 987654328@ 在这种情况下)。

let N = 500 * 1000;
let A = [...Array(N)].map((x,i) => i); // start with "an array".

function codePointsToString(cps) {
  let rs = [];
  let batch = 32767; // Supported 'all' browsers
  for (let i = 0; i < cps.length; ){
    let e = i + batch;
    // Build batch section, defer to Array.join.
    rs.push(String.fromCodePoint.apply(null, cps.slice(i, e)));
    i = e;
  }
  return rs.join('');
}

var result = codePointsToString(A);
console.log(result.length);

另外,我想要一个奖杯。上面的代码应该在 O(n) 时间内运行并最小化分配的对象数量。不能保证这是“最佳”方法。批处理方法的一个好处,以及为什么包含apply(或传播调用)的成本,是对String.fromCodePoint 和中间字符串的调用显着减少。 YMMV - 尤其是跨环境。

这是online benchmark。所有测试都可以访问和使用由 500k 个元素组成的同一个生成的“A”数组。

【讨论】:

  • 不错的一个!应该让批处理吗?
  • 我认为基于OP也需要fromCodePoint
  • 这个的运行时间是多少?我重写了一个预先分配的数组,并且在 20-30 毫秒的范围内(相对于其他重复连接数组的解决方案的 19 )。我在循环中看到了apply,所以我真的很怀疑。 apply 真的很慢
【解决方案2】:

给定的答案表现不佳:我在其中一个上测量了 19 秒,而其他答案相似 (*)。有必要预分配输出数组。以下是 20 到 40 毫秒 秒。快三个数量级。

function wordArrayToByteArray(hash) {
    var result = [...Array(hash.sigBytes)].map(x => -1)
    let words = hash.words
        //map each word to an array of bytes
        .map(function (v) {
            // create an array of 4 bytes (less if sigBytes says we have run out)
            var bytes = [0, 0, 0, 0].slice(0, Math.min(4, hash.sigBytes))
                // grab that section of the 4 byte word
                .map(function (d, i) {
                    return (v >>> (8 * i)) % 256;
                })
                // flip that
                .reverse()
            ;
            // remove the bytes we've processed
            // from the bytes we need to process
            hash.sigBytes -= bytes.length;
            return bytes;
        })
    words.forEach((w,i) => {
        result.splice(i * 4, 4, ...w)
    })
    result = result.map(function (d) {
        return String.fromCharCode(d);
    }).join('')
    return result
}

(*) 除了@User2864740 可能的例外 - 我们正在等待他的号码。但是他的解决方案也在循环中使用了apply(),这导致人们相信它也会很慢。

【讨论】:

  • 数字只有在相对比较时才有用:)
  • 这就是我提供它们的原因。 19 秒是与其他解决方案之一的一对一替换。减速的关键部分是concat的重复应用和apply的使用。您的解决方案避免了第一部分,但没有避免第二部分。我很想听听你的电话号码。
  • FWIW,这使用fromCharCode。这可能是一个意外的变化,我也这样做了:( - 这是一个更新的基准,涵盖只是转换过程。jsben.ch/hutLb
  • 您的可能具有相似的性能。在任何情况下,它都获得了奖励。
【解决方案3】:

“老式”JavaScript:

var N=125000;
var y="";
for(var i=0; i<N; i++)
  y+=String.fromCharCode(i);
console.log(y.length);

使用 N=1000000

【讨论】:

  • 我希望它具有可怕的性能特征,也许没有具体的实现优化。从概念上讲,这会在每个循环中创建一个新字符串,并在 O(n^2) 范围内复制原始字符串!虽然可以在特定的实现中优化这种方法,但我一般很难建议这种方法。对于代理对也可能不正确。
  • “不是(只是)你,而是我。” - 我也对另一个答案持怀疑态度。
猜你喜欢
  • 2021-11-11
  • 1970-01-01
  • 2016-09-30
  • 2018-10-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多