如何最简单地复制一个 ArrayBuffer 对象?

44

我正在使用ArrayBuffer对象,并且希望将它们复制一份。在使用实际指针和memcpy进行复制相当容易的情况下,我无法找到任何直接的JavaScript方法来完成此操作。

目前,我是这样复制我的ArrayBuffers

function copy(buffer)
{
    var bytes = new Uint8Array(buffer);
    var output = new ArrayBuffer(buffer.byteLength);
    var outputBytes = new Uint8Array(output);
    for (var i = 0; i < bytes.length; i++)
        outputBytes[i] = bytes[i];
    return output;
}

有更好的写法吗?

12个回答

67

我更喜欢以下的方法

function copy(src)  {
    var dst = new ArrayBuffer(src.byteLength);
    new Uint8Array(dst).set(new Uint8Array(src));
    return dst;
}

2
看起来你的答案比所选的答案更快。 - MaiaVictor
@Viclib,请注意您可能需要为IE提供备用方案。 - Gleno
另外需要注意的是,不同类型的性能也可能有所不同;据我记得,Uint8 的复制速度比 Float32 快。我认为最快的是 Int32;但是你必须正确地填充数据。结果可能因人而异。 - Gleno
1
据我所知,所有的都是。 - Gleno
@Gleno - 为什么IE需要回退? - Dan Nissenbaum
6
@DanNissenbaum 因为IE什么也做不了! - Indiana Kernick

38

看起来直接传入源数据视图会进行复制:

var a = new Uint8Array([2,3,4,5]);
var b = new Uint8Array(a);
a[0] = 6;
console.log(a); // [6, 3, 4, 5]
console.log(b); // [2, 3, 4, 5]

在FF 33和Chrome 36中测试过。


1
这是一个很好的答案,而且似乎在不同浏览器上都兼容。谢谢! - Louis LC
1
确实,这比被接受的答案好多了。谢谢! - Nathan
8
需要注意的是:new Uint8Array(a.buffer)并不会复制缓冲区。这取决于您的需求,它可能很有用。当使用ArrayBuffer调用时,还可以接受可选的byteOffsetlength参数。这与NodeJS的Buffer.from(Buffer, byteOffset, length)在Node v6中非常相似。 - STRML
1
目前这是最快的方法,非常感谢!我制作了一个工作台,想看的人可以去 http://jsben.ch/rkCpx (慢速情况已禁用)。 - FlameStorm

32

ArrayBuffer 应该支持 slice (http://www.khronos.org/registry/typedarray/specs/latest/),所以你可以尝试使用:

buffer.slice(0);

这个方法在Chrome 18下可以使用,但在Firefox 10或11下无法正常工作。对于Firefox,您需要手动复制它。您可以在Firefox中对slice()进行猴子补丁,因为Chrome的slice()会比手动复制更快。代码如下:

if (!ArrayBuffer.prototype.slice)
    ArrayBuffer.prototype.slice = function (start, end) {
        var that = new Uint8Array(this);
        if (end == undefined) end = that.length;
        var result = new ArrayBuffer(end - start);
        var resultArray = new Uint8Array(result);
        for (var i = 0; i < resultArray.length; i++)
           resultArray[i] = that[i + start];
        return result;
    }

接下来您可以调用:

buffer.slice(0);

要在Chrome和Firefox中复制数组


在Chrome(评论时为29版本),ArrayBuffer没有名为.slice的方法,而是使用.subarray(start [, end])。不确定在FF中是否相同。 - Bradley Bossard
看起来规范在我的回答提出后发生了变化。我会努力更新它。subarray()已经取代了slice()成为新标准。 - chuckj
1
重新查看规范,ArrayBuffer 应该有 slice() 方法。而类型化数组(例如 Uint8Array)应该有 subarray() 方法。以上对于 ArrayBuffer 是正确的。 - chuckj
9
请注意,subarray() 返回一个已有缓冲区的新视图,并且不会实际复制缓冲区。 - CvW
2
请注意,slice() 方法的 begin 参数是可选的,因此您可以简单地使用 buffer.slice() - Bo Lu

2

ArrayBuffer包装在Buffer中。 这是共享内存,不会进行复制。 然后从包装的Buffer创建一个新的Buffer。 这将复制数据。 最后获取对新BufferArrayBuffer的引用。

这是我能找到的最简单的方法。 最有效的? 可能。

const wrappingBuffer = Buffer.from(arrayBuffer)
const copiedBuffer = Buffer.from(wrappingBuffer)
const copiedArrayBuffer = copiedBuffer.buffer

1
在Firefox和Chrome中都会导致“Buffer未定义”的结果... - av01d
Buffer只存在于Node.js中。 - undefined

2

嗯...如果你想切割Uint8Array(从逻辑上讲,应该是这样),这个方法可能有效。

 if (!Uint8Array.prototype.slice && 'subarray' in Uint8Array.prototype)
     Uint8Array.prototype.slice = Uint8Array.prototype.subarray;

2

比chuckj的答案更快,但稍微复杂一些。在大型类型化数组上应该使用少约8倍的复制操作。基本上我们尽可能多地复制8字节块,然后复制剩余的0-7字节。这在当前版本的IE中特别有用,因为它没有为ArrayBuffer实现切片方法。

if (!ArrayBuffer.prototype.slice)
    ArrayBuffer.prototype.slice = function (start, end) {
    if (end == undefined) end = that.length;
    var length = end - start;
    var lengthDouble = Math.floor(length / Float64Array.BYTES_PER_ELEMENT); 
    // ArrayBuffer that will be returned
    var result = new ArrayBuffer(length);

    var that = new Float64Array(this, start, lengthDouble)
    var resultArray = new Float64Array(result, 0, lengthDouble);

    for (var i = 0; i < resultArray.length; i++)
       resultArray[i] = that[i];

    // copying over the remaining bytes
    that = new Uint8Array(this, start + lengthDouble * Float64Array.BYTES_PER_ELEMENT)
    resultArray = new Uint8Array(result, lengthDouble * Float64Array.BYTES_PER_ELEMENT);

    for (var i = 0; i < resultArray.length; i++)
       resultArray[i] = that[i];

    return result;
}

在Android原生浏览器中会出现“ArrayBuffer长度减去byteOffset不是元素大小的倍数”的错误。 - duckegg

0

如果你在浏览器中,可以这样做:

const copy = structuredClone(buffer);

0
通过巧妙地使用扩展运算符,你可以强制进行复制,就像这样:
const newCopy = new Uint8Array(
  [... new Uint8Array(source)]
).buffer;

0

上述操作中,有些只进行“浅层”复制。当与工作者和可转移数组一起使用时,您需要进行深层复制。

function copyTypedArray(original, deep){
    var copy;
    var kon = original.constructor;
    if(deep){
        var len = original.length;
        copy = new kon(len);
        for (var k=len; --k;) {
            copy[k] = original[k];
        }
    } else {
        var sBuf = original.buffer;
        copy = new kon(sBuf);
        copy.set(original);
    }
    return copy;
}

提示(针对困惑的人):类型化数组包含一个ArrayBuffer,可以通过"buffer"属性获得。
var arr = new Float32Array(8);
arr.buffer <-- this is an ArrayBuffer

0
如果你和我一样,可能正在寻找如何将一个WebAssembly模块的内存复制到另一个模块。下面是方法:
function copy_plugin_memory_to_host_memory(dest, src, len) {
    const plugin_data = new Uint8Array(plugin_memory.buffer, src, len);
    const host_data = new Uint8Array(host_memory.buffer, dest, len);
    host_data.set(plugin_data);
}

在这里,host_memoryplugin_memory是传递给两个单独的WASM文件的WebAssembly内存对象。当传递原始/复杂数据结构时,您希望使用主机调用copy_plugin_memory_to_host_memory作为导入函数。
const host_memory = new WebAssembly.Memory({
    initial: 20,
    maximum: 200,
    shared: true,
});
const plugin_memory = new WebAssembly.Memory({
    initial: 20,
    maximum: 200,
    shared: false,
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接