43

I'm working with ArrayBuffer objects, and I would like to duplicate them. While this is rather easy with actual pointers and memcpy, I couldn't find any straightforward way to do it in Javascript.

Right now, this is how I copy my 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;
}

Is there a prettier way?

zneak
  • 134,922
  • 42
  • 253
  • 328

10 Answers10

66

I prefer the following method

function copy(src)  {
    var dst = new ArrayBuffer(src.byteLength);
    new Uint8Array(dst).set(new Uint8Array(src));
    return dst;
}
Gleno
  • 16,621
  • 12
  • 64
  • 85
37

It appears that simply passing in the source dataview performs a copy:

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]

Tested in FF 33 and Chrome 36.

Andrew
  • 3,332
  • 4
  • 31
  • 37
  • 1
    This is a great answer, and it seems cross-browser compatible. Thank you ! – Louis LC Oct 01 '14 at 15:42
  • 1
    Indeed, this is much better than the accepted answer. Thanks! – Nathan Jun 19 '15 at 17:31
  • 8
    Something to note: `new Uint8Array(a.buffer)` does *not* copy the buffer. This can be useful depending on your needs. It also accepts optional `byteOffset` and `length` params when called with an ArrayBuffer. This is very similar to NodeJS's `Buffer.from(Buffer, byteOffset, length)` in Node v6. – STRML Jul 05 '16 at 01:30
  • 1
    For now it is the most fast way, thanx a lot! Who want to see it I made a bench http://jsben.ch/rkCpx (slow cases are disabled) – FlameStorm Jul 31 '17 at 23:29
32

ArrayBuffer is supposed to support slice (http://www.khronos.org/registry/typedarray/specs/latest/) so you can try,

buffer.slice(0);

which works in Chrome 18 but not Firefox 10 or 11. As for Firefox, you need to copy it manually. You can monkey patch the slice() in Firefox because the Chrome slice() will outperform a manual copy. This would look something like,

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;
    }

Then you can call,

buffer.slice(0);

to copy the array in both Chrome and Firefox.

chuckj
  • 27,773
  • 7
  • 53
  • 49
  • In Chrome (29 at the time of this comment), ArrayBuffer does not have a method named .slice, but .subarray(start [, end]) instead. Not sure how it is in FF. – Bradley Bossard Oct 05 '13 at 00:47
  • Looks like the spec changed since my answer was posed. I will work on updating it. `subarray()` is in the new standard in place of `slice()`. – chuckj Oct 07 '13 at 23:32
  • 1
    Looking again at the spec, `ArrayBuffer` should have `slice()`. The typed arrays (such as `Uint8Array`) should have `subarray()`. The above is correct for `ArrayBuffer`. – chuckj Oct 07 '13 at 23:45
  • 9
    Note that `subarray()` returns a new view on an existing buffer and does not actually copy the buffer. – CvW Dec 15 '13 at 11:05
  • 2
    Note the begin parameter of `slice()` is optional, so you can just simply `buffer.slice()`. – Bo Lu Mar 04 '19 at 01:22
2

Hmmm... if it's the Uint8Array you want to slice (which logically, it should be), this may work.

 if (!Uint8Array.prototype.slice && 'subarray' in Uint8Array.prototype)
     Uint8Array.prototype.slice = Uint8Array.prototype.subarray;
Orwellophile
  • 13,235
  • 3
  • 69
  • 45
2

Faster and slightly more complicated version of chuckj's answer. Should use ~8x less copy operations on large Typed Arrays. Basically we copy as much 8-byte chunks as possible and then copy the remaining 0-7 bytes. This is especially useful in current version of IE, since it doesn't have slice method implemented for 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;
}
xmichaelx
  • 569
  • 1
  • 6
  • 17
  • This causes `ArrayBuffer length minus the byteOffset is not a multiple of the element size.` in Android stock browser. – duckegg Jul 16 '14 at 16:21
2

Wrap a Buffer around the ArrayBuffer. This is shared memory and no copy is made. Then create a new Buffer from the wrapping Buffer. This will copy the data. Finally get a reference to the new Buffer's ArrayBuffer.

This is the most straighforward way I can find. The most efficient? Perhaps.

const wrappingBuffer = Buffer.from(arrayBuffer)
const copiedBuffer = Buffer.from(wrappingBuffer)
const copiedArrayBuffer = copiedBuffer.buffer
Steven Spungin
  • 27,002
  • 5
  • 88
  • 78
0

In some cases (like webaudio Audiobuffers) you only have a reference to the 2 arrays.
So if you have array1 as a float32Array and array2 as a float32Array,
you must do an element by element copy.

To do so you can use different methods.

            var ib=z.inputBuffer.getChannelData(0);
            var ob=z.outputBuffer.getChannelData(0);

this

            ib.forEach((chd,i)=>ob[i]=chd);

or this nicer and probably faster

            ob.set(ib);

That's because Array.set populates an existing array with multiple data (even from another array)

Zibri
  • 9,096
  • 3
  • 52
  • 44
0

Some of the operations above only do "shallow" copies. When working with workers and transferable arrays, you need to do a deep copy.

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;
}

HINT (for the confused): Typed Arrays contain an ArrayBuffer, which can be obtained via the "buffer" property.

var arr = new Float32Array(8);
arr.buffer <-- this is an ArrayBuffer
bob
  • 7,539
  • 2
  • 46
  • 42
0

If you're in the browser you can do:

const copy = structuredClone(buffer);
Sam Denty
  • 3,693
  • 3
  • 30
  • 43
0

With some crafty usage of the spread operator, you can force a copy like so:

const newCopy = new Uint8Array(
  [... new Uint8Array(source)]
).buffer;
Brad
  • 159,648
  • 54
  • 349
  • 530