55

I'd like to merge multiple arraybuffers to create a Blob. however, as you know, TypedArray dosen't have "push" or useful methods...

E.g.:

var a = new Int8Array( [ 1, 2, 3 ] );
var b = new Int8Array( [ 4, 5, 6 ] );

As a result, I'd like to get [ 1, 2, 3, 4, 5, 6 ].

Charles
  • 50,943
  • 13
  • 104
  • 142
yomotsu
  • 1,412
  • 1
  • 12
  • 17

8 Answers8

102

Use the set method. But note, that you now need twice the memory!

var a = new Int8Array( [ 1, 2, 3 ] );
var b = new Int8Array( [ 4, 5, 6 ] );

var c = new Int8Array(a.length + b.length);
c.set(a);
c.set(b, a.length);

console.log(a);
console.log(b);
console.log(c);
Prinzhorn
  • 22,120
  • 7
  • 61
  • 65
  • thank you for your reply. i could understand. I need to create a new merged one aint I. – yomotsu Dec 28 '12 at 15:43
  • 7
    @yomotsu yes you need to create a new one. If you know C, a TypedArray is similar to using `malloc` (without the need to `free`). But there's nothing like `realloc`. – Prinzhorn Dec 28 '12 at 18:35
  • There is a package on NPM for this (https://www.npmjs.com/package/concat-typed-array). It took me a while, but I finally figured out that its output matches yours. https://codesandbox.io/s/4rok95vy24?autoresize=1&expanddevtools=1&hidenavigation=1 What tripped me up is that I needed to compare the outputs after using `.toString()`. – Ryan Aug 12 '18 at 20:49
  • 1
    @Ryan that's because otherwise you're comparing references, which are different (it's a new array, a new pointer to a memory location). To compare array contents you always need to compare them element-by-element. `[1] === [1]` is false. – Prinzhorn Aug 13 '18 at 07:13
12

for client-side ~ok solution:

const a = new Int8Array( [ 1, 2, 3 ] )
const b = new Int8Array( [ 4, 5, 6 ] )
const c = Int8Array.from([...a, ...b])
JerryCauser
  • 811
  • 5
  • 17
  • 2
    This will run into length limits. You shouldn't use the spread operator for unbounded data. – Timmmm Apr 16 '20 at 08:08
  • It's just the same as this but with longer arrays. [Read the documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#Spread_with_many_values). – Timmmm May 15 '20 at 07:34
  • 3
    @Timmmm It works only for arugments of function calls. `When using spread syntax for function calls,...` and `But beware: by using apply this way, you run the risk of exceeding the JavaScript engine's argument length limit` also it says `argument limit of 65536.` I've tested spread with two 65536-length Int8Arrays and it works. – JerryCauser May 17 '20 at 12:14
  • @Timmmm also tested with 2 16777216-length Int8arrays and spread still work with it – JerryCauser May 17 '20 at 12:39
  • 2
    The only downside of this less verbose solution is that it creates an intermediate _array_ instead of a typed array. For shorter arrays it won't be a problem, anyway. – MaxArt Dec 23 '20 at 09:55
  • @MaxArt as I said it is ~ok solution for client side. On backend I'd recommend current top solution. – JerryCauser Jan 11 '21 at 12:10
9

I always use this function:

function mergeTypedArrays(a, b) {
    // Checks for truthy values on both arrays
    if(!a && !b) throw 'Please specify valid arguments for parameters a and b.';  

    // Checks for truthy values or empty arrays on each argument
    // to avoid the unnecessary construction of a new array and
    // the type comparison
    if(!b || b.length === 0) return a;
    if(!a || a.length === 0) return b;

    // Make sure that both typed arrays are of the same type
    if(Object.prototype.toString.call(a) !== Object.prototype.toString.call(b))
        throw 'The types of the two arguments passed for parameters a and b do not match.';

    var c = new a.constructor(a.length + b.length);
    c.set(a);
    c.set(b, a.length);

    return c;
}

The original function without checking for null or types

function mergeTypedArraysUnsafe(a, b) {
    var c = new a.constructor(a.length + b.length);
    c.set(a);
    c.set(b, a.length);

    return c;
}
Dänu
  • 5,791
  • 9
  • 43
  • 56
  • 1
    No `null` checks, `==` instead of `===`, and this is basically a duplicate of Prinzhorn's answer, wrapped in a function. – Cerbrus Feb 26 '16 at 07:18
  • Again, just some function I like to use, thought maybe someone else could use it as well - fell free to correct any errors you may find. – Dänu Feb 26 '16 at 10:14
  • One doesn't usually correct errors in an answer's code. That's up to the person that answered the question, hence the comment. – Cerbrus Feb 26 '16 at 10:18
  • Well I never saw the need to do a strict comparison nor a null check in my use cases, but if it doesn't influence performance negatively, it would make the function more fleshed out. – Dänu Feb 26 '16 at 10:32
  • Yes! Throwing strings is always welcomed of course. I definitely want you as my colleague. – nuts-n-bits Aug 17 '19 at 22:32
3

if I have multiple typed arrays

            arrays = [ typed_array1, typed_array2,..... typed_array100]

I want concat all 1 to 100 sub array into single 'result' this function works for me.

  single_array = concat(arrays)


function concat(arrays) {
  // sum of individual array lengths
  let totalLength = arrays.reduce((acc, value) => acc + value.length, 0);

  if (!arrays.length) return null;

   let result = new Uint8Array(totalLength);

      // for each array - copy it over result
      // next array is copied right after the previous one
      let length = 0;
      for(let array of arrays) {
            result.set(array, length);
            length += array.length;
      }

      return result;
   }
hoogw
  • 4,982
  • 1
  • 37
  • 33
1

As a one-liner, which will take an arbitrary number of arrays (myArrays here) and of mixed types so long as the result type takes them all (Int8Array here):

let combined = Int8Array.from(Array.prototype.concat(...myArrays.map(a => Array.from(a))));
randomsock
  • 955
  • 6
  • 9
0

For people who love one-liners:

  const binaryData = [
    new Uint8Array([1, 2, 3]),
    new Int16Array([4, 5, 6]),
    new Int32Array([7, 8, 9])
  ];

  const mergedUint8Array = new Uint8Array(binaryData.map(typedArray => [...new Uint8Array(typedArray.buffer)]).flat());
Maz T
  • 1,184
  • 13
  • 18
0
function concat (views: ArrayBufferView[]) {
    let length = 0
    for (const v of views)
        length += v.byteLength
        
    let buf = new Uint8Array(length)
    let offset = 0
    for (const v of views) {
        const uint8view = new Uint8Array(v.buffer, v.byteOffset, v.byteLength)
        buf.set(uint8view, offset)
        offset += uint8view.byteLength
    }
    
    return buf
}
Hongfei Shen
  • 103
  • 1
  • 7
-1

I like @prinzhorn's answer but I wanted something a bit more flexible and compact:

var a = new Uint8Array( [ 1, 2, 3 ] );
var b = new Float32Array( [ 4.5, 5.5, 6.5 ] );

const merge = (tArrs, type = Uint8Array) => {
  const ret = new (type)(tArrs.reduce((acc, tArr) => acc + tArr.byteLength, 0))
  let off = 0
  tArrs.forEach((tArr, i) => {
    ret.set(tArr, off)
    off += tArr.byteLength
  })
  return ret
}

merge([a, b], Float32Array)
mattdlockyer
  • 6,984
  • 4
  • 40
  • 44