从ECMA规范到ArkTS接口(二)--TypedArray.prototype.subarray接口 原创

深开鸿
发布于 2024-4-25 10:08
浏览
1收藏

作者:王清

上篇介绍了typedArray.slice方法,本文介绍一个返回结果和参数和slice非常类似的函数:TypedArray.prototype.subarray。

TypedArray.prototype.subarray 方法是 TypedArray 原型上的一个方法,它允许开发者在不复制原始数据的前提下,创建原始 TypedArray 对象的一个新视图。这个新视图反映了原始二进制数据缓冲区的一个特定部分。这种方式在处理大量数据时尤其有用,因为它避免了不必要的复制,从而节省内存并提高应用程序的性能。这点是和slice方法最大的区别。

ECMA对TypedArray.prototype.subarray接口的定义:

按照ECMA的规范,TypedArray.prototype.subarray(begin, end) 方法定义如下:

  • begin: 起始索引,表示新视图的起始点。
  • end: 结束索引,代表新视图的终点(但不包括该索引本身)。

如果 end 参数省略,subarray 将默认包含从 begin 开始到原数组结尾的所有元素。如果 beginend 是负值,则它们表示从数组末尾开始的倒数索引。方法返回一个新的 TypedArray 实例,它表示原始 TypedArray 的一个连续子集。

我们可以直接查看ECMA规范中对该函数的描述:

23.2.3.30 %TypedArray%.prototype.subarray ( start, end )
	This method returns a new TypedArray whose element type is the element type of this TypedArray and whose ArrayBuffer is the ArrayBuffer of this TypedArray, referencing the elements in the interval from start (inclusive) to end (exclusive). If either start or end is negative, it refers to an index from the end of the array, as opposed to from the beginning.

It performs the following steps when called:

1. Let O be the this value.
2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]).
3. Assert: O has a [[ViewedArrayBuffer]] internal slot.
4. Let buffer be O.[[ViewedArrayBuffer]].
5. Let srcRecord be MakeTypedArrayWithBufferWitnessRecord(O, SEQ-CST).
6. If IsTypedArrayOutOfBounds(srcRecord) is true, then
	a. Let srcLength be 0.
7. Else,
	a. Let srcLength be TypedArrayLength(srcRecord).
8. Let relativeStart be ? ToIntegerOrInfinity(start).
9. If relativeStart = -∞, let startIndex be 0.
10. Else if relativeStart < 0, let startIndex be max(srcLength + relativeStart, 0).
11. Else, let startIndex be min(relativeStart, srcLength).
12. Let elementSize be TypedArrayElementSize(O).
13. Let srcByteOffset be O.[[ByteOffset]].
14. Let beginByteOffset be srcByteOffset + (startIndex × elementSize).
15. If O.[[ArrayLength]] is AUTO and end is undefined, then
	a. Let argumentsList be « buffer, 𝔽(beginByteOffset) ».
16. Else,
	a. If end is undefined, let relativeEnd be srcLength; else let relativeEnd be ? ToIntegerOrInfinity(end).
	b. If relativeEnd = -∞, let endIndex be 0.
	c. Else if relativeEnd < 0, let endIndex be max(srcLength + relativeEnd, 0).
	d. Else, let endIndex be min(relativeEnd, srcLength).
	e. Let newLength be max(endIndex - startIndex, 0).
	f. Let argumentsList be « buffer, 𝔽(beginByteOffset), 𝔽(newLength) ».
17. Return ? TypedArraySpeciesCreate(O, argumentsList).
This method is not generic. The this value must be an object with a [[TypedArrayName]] internal slot.

TypedArray.prototype.subarray与TypedArray.prototype.slice对比:

TypedArray.prototype.subarrayTypedArray.prototype.slice 是两个不同的 TypedArray 方法,用于生成原数组某部分的新数组,但他们在处理方式和结果的内存分配上存在差异。

首先,subarray 方法返回的新 TypedArray 对象,并不创建原 ArrayBuffer 的副本,而是创建了一个新的 TypedArray 视图,这个视图引用的是原 ArrayBuffer 中的相同内存区域。这意味着 subarray 方法生成的数组与原数组共享相同的数据存储,因此对新数组的修改会影响到原数组中相应的部分。这个方法的执行效率较高,因为它避免了复制操作,只是创建了一个指向相同内存区域的新视图。

另一方面,slice 方法会创建一个新的 ArrayBuffer,并将选定的元素从原 TypedArray 对象复制到新数组中。这意味着 slice 方法生成的新数组拥有自己的数据存储,对这个新数组的任何修改都不会影响原数组。由于涉及到内存中数据的复制操作,slice 方法在执行时的效率略低于 subarray

下面是这两个方法的对比分析:

  1. 内存共享与否
    • subarray:返回一个新 TypedArray,共享同一个 ArrayBuffer
    • slice:返回一个新 TypedArray,有自己独立的 ArrayBuffer
  2. 性能
    • subarray:性能较高,因为没有进行元素的复制,仅仅是创建了一个新的视图。
    • slice:性能较低,因为需要复制元素到新的 ArrayBuffer
  3. 修改影响
    • subarray:由于共享 ArrayBuffer,对生成的 TypedArray 的修改会影响到原数组。
    • slice:由于使用了新的 ArrayBuffer,对生成的 TypedArray 的修改不会影响到原数组。
  4. 参数处理
    • subarrayslice 方法对于 startend 参数的处理是相同的。如果 startend 是负数,它们会被解释为从数组末尾开始的索引。如果 endundefined,则操作会处理直到原数组的末尾。
  5. 返回值类型
    • 无论是 subarray 还是 slice,返回的新数组类型都与原数组相同。
  6. 错误处理
    • slice 方法在复制过程中有更严格的错误检查。例如,如果源 TypedArray 越界,它会抛出 TypeError

总的来说,选择 subarray 还是 slice 取决于你是否需要一个独立的数组副本,以及是否关心性能和内存使用。如果你只是想要一个指向相同数据的快速视图,并且不介意对这个视图的修改会影响原数据,那么 subarray 是一个好选择。而如果你需要一个完全独立的复制,不会影响原数据的副本,那么应该选择 slice 方法。

ArkTS对subarray的接口描述:

我们继续以\static_core\plugins\ets\stdlib\escompat\TypedUArrays.ets的Uint8Array定义为例:

/**
     * Creates a Uint8Array with the same underlying Buffer
     *
     * @param begin start index, inclusive
     *
     * @param end last index, exclusive
     *
     * @returns new Uint8Array with the same underlying Buffer
     */
    public subarray(begin?: number, end?: number): Uint8Array {
        return this.subarray(asIntOrDefault(begin, 0 as int), asIntOrDefault(end, this.lengthInt))
    }

    /**
     * Creates a Uint8Array with the same underlying Buffer
     *
     * @param begin start index, inclusive
     *
     * @param end last index, exclusive
     *
     * @returns new Uint8Array with the same underlying Buffer
     */
    public subarray(begin: number, end: number): Uint8Array {
        return this.subarray(begin as int, end as int)
    }

    /**
     * Creates a Uint8Array with the same underlying Buffer
     *
     * @param begin start index, inclusive
     *
     * @param end last index, exclusive
     *
     * @returns new Uint8Array with the same underlying Buffer
     */
    public subarray(begin: number, end: int): Uint8Array {
        return this.subarray(begin as int, end as int)
    }

    /**
     * Creates a Uint8Array with the same underlying Buffer
     *
     * @param begin start index, inclusive
     *
     * @param end last index, exclusive
     *
     * @returns new Uint8Array with the same underlying Buffer
     */
    public subarray(begin: int, end: number): Uint8Array {
        return this.subarray(begin as int, end as int)
    }

    /**
     * Creates a Uint8Array with the same underlying Buffer
     *
     * @param begin start index, inclusive
     *
     * @param end last index, exclusive
     *
     * @returns new Uint8Array with the same underlying Buffer
     */
    public subarray(begin: int, end: int): Uint8Array {
        const len: int = this.length as int
        const relStart = normalizeIndex(begin, len)
        const relEnd = normalizeIndex(end, len)
        let count = relEnd - relStart
        if (count < 0) {
            count = 0
        }
        return new Uint8Array(this.buffer, relStart * Uint8Array.BYTES_PER_ELEMENT as int, count)
    }

    /**
     * Creates a Uint8Array with the same Buffer
     *
     * @param begin start index, inclusive
     *
     * @returns new Uint8Array with the same Buffer
     */
    public subarray(begin: number): Uint8Array {
        return this.subarray(begin as int, this.lengthInt)
    }

    /**
     * Creates a Uint8Array with the same Buffer
     *
     * @param begin start index, inclusive
     *
     * @returns new Uint8Array with the same Buffer
     */
    public subarray(begin: int): Uint8Array {
        return this.subarray(begin, this.lengthInt)
    }

ArkTS对subarray的接口定义如上,可以看到基本覆盖了ECMA的规范的方方面面。此外,我们还可以看到,subarray提供了对int类型参数的支持,而TS是仅仅只支持number类型的参数的。

测试用例:

要验证我们的 subarray 方法实现是否正确,我们可以编写单元测试用例。这里我们使用了ArkTS和Jest测试框架,来确保我们的实现与ECMA规范一致。

const success = 0;
const fail = 1;
function testSubarrayWithOutParam(): int {
  let source: number[] = [10, 20, 30, 40, 50, 60];
  let ss = new ArrayBuffer(source.length as int * 1);

  let origin: Uint8Array;

  try {
    origin = new Uint8Array(ss);
    origin.set(source);
  } catch(e) {
    console.log(e);
    return fail;
  }

  let target: Uint8Array;

  try {
    target = origin.subarray();
  } catch(e) {
    console.log(e);
    return fail;
  }

  if (target.length as int != origin.length as int) {
    console.log("Array length mismatch on slice");
    return fail;
  }

  //Check all the data copied;
  for (let i: int = 0; i< origin.length as int; i++) {
    let tv = target[i] as number;
    let ov = origin[i] as number;
    console.log(source[i] + "->" + tv + "->" + ov);
    if (tv != ov) {
      console.log("Array data mismatch");
      return fail;
    }
  }

  origin= new Uint8Array(0);
  if (origin.length as int != 0){
    return fail;
  }

  try {
    target = origin.subarray();
  } catch(e) {
    console.log(e);
    return fail;
  }

  if (target.length as int != 0){
    return fail;
  }
  return success;
}

function testSubarrayOneParam(): int {
  let source: number[] = [10, 20, 30, 40, 50, 60];
  let ss = new ArrayBuffer(source.length as int * 1);

  let origin: Uint8Array;

  try {
    origin = new Uint8Array(ss);
    origin.set(source);
  } catch(e) {
    console.log(e);
    return fail;
  }

  let subarrayStart: int = 1;
  let subarrayEnd: int = origin.length as int;

  let target: Uint8Array;

  try {
    target = origin.subarray(subarrayStart);
  } catch(e) {
    console.log(e);
    return fail;
  }

  if (target.length as int != origin.length as int - subarrayStart) {
    console.log("Array length mismatch on subarray One Params" + target.length);
    return fail;
  }

  //Check all the data copied;
  for (let i: int = subarrayStart; i< subarrayEnd; i++) {
    let tv = target[i - subarrayStart] as number;
    let ov = origin[i] as number;
    console.log(source[i] + "->" + tv + "->" + ov);
    if (tv != ov) {
      console.log("Array data mismatch");
      return fail;
    }
  }

  subarrayStart = 0;
  try {
    target = origin.subarray(undefined);
  } catch(e) {
    console.log(e);
    return fail;
  }

  if (target.length as int != origin.length as int) {
    console.log("Array length mismatch on subarray One Params" + target.length);
    return fail;
  }

  //Check all the data copied;
  for (let i: int = subarrayStart; i< subarrayEnd; i++) {
    let tv = target[i - subarrayStart] as number;
    let ov = origin[i] as number;
    console.log(source[i] + "->" + tv + "->" + ov);
    if (tv != ov) {
      console.log("Array data mismatch");
      return fail;
    }
  }

  //The subarray method returns a view of the original array, so modifications made to the subarray will affect the original array, and vice versa.
  target[subarrayStart] = 1;
  for (let i: int = subarrayStart; i< subarrayEnd; i++) {
    let tv = target[i - subarrayStart] as number;
    let ov = origin[i] as number;
    console.log(source[i] + "->" + tv + "->" + ov);
    if (tv != ov) {
      console.log("Array data mismatch");
      return fail;
    }
  }

  origin[subarrayStart] = 2;
  for (let i: int = subarrayStart; i< subarrayEnd; i++) {
    let tv = target[i - subarrayStart] as number;
    let ov = origin[i] as number;
    console.log(source[i] + "->" + tv + "->" + ov);
    if (tv != ov) {
      console.log("Array data mismatch");
      return fail;
    }
  }

  return success;
}

function testSubarrayTwoParams(): int {
  let source: number[] = [10, 20, 30, 40, 50, 60, 70, 80];
  let ss = new ArrayBuffer(source.length as int * 1);

  let origin: Uint8Array;

  try {
    origin = new Uint8Array(ss);
    origin.set(source);
  } catch(e) {
    console.log(e);
    return fail;
  }

  let subarrayStart: int = 2;
  let subarrayEnd: int = 4;

  let target: Uint8Array;

  try {
    target = origin.subarray(subarrayStart, subarrayEnd);
  } catch(e) {
    console.log(e);
    return fail;
  }

  if (target.length as int != subarrayEnd - subarrayStart) {
    console.log("Array length mismatch on subarray2");
    return fail;
  }

  //Check all the data copied;
  for (let i: int = subarrayStart; i< subarrayEnd; i++) {
    let tv = target[i - subarrayStart] as number;
    let ov = origin[i] as number;
    console.log(source[i] + "->" + tv + "->" + ov);
    if (tv != ov) {
      console.log("Array data mismatch");
      return fail;
    }
  }

  subarrayStart = 0;
  subarrayEnd = origin.length as int;
  try {
    target = origin.subarray(subarrayStart, subarrayEnd);
  } catch(e) {
    console.log(e);
    return fail;
  }

  if (target.length as int != subarrayEnd - subarrayStart) {
    console.log("Array length mismatch on subarray2");
    return fail;
  }

  //Check all the data copied;
  for (let i: int = subarrayStart; i< subarrayEnd; i++) {
    let tv = target[i - subarrayStart] as number;
    let ov = origin[i] as number;
    console.log(source[i] + "->" + tv + "->" + ov);
    if (tv != ov) {
      console.log("Array data mismatch");
      return fail;
    }
  }

  try {
    target = origin.subarray(new Number(subarrayStart), undefined);
  } catch(e) {
    console.log(e);
    return fail;
  }

  if (target.length as int != subarrayEnd - subarrayStart) {
    console.log("Array length mismatch on subarray2");
    return fail;
  }

  //Check all the data copied;
  for (let i: int = subarrayStart; i< subarrayEnd; i++) {
    let tv = target[i - subarrayStart] as number;
    let ov = origin[i] as number;
    console.log(source[i] + "->" + tv + "->" + ov);
    if (tv != ov) {
      console.log("Array data mismatch");
      return fail;
    }
  }

  try {
    target = origin.subarray(undefined, undefined);
  } catch(e) {
    console.log(e);
    return fail;
  }

  if (target.length as int != subarrayEnd - subarrayStart) {
    console.log("Array length mismatch on subarray2");
    return fail;
  }

  //Check all the data copied;
  for (let i: int = subarrayStart; i< subarrayEnd; i++) {
    let tv = target[i - subarrayStart] as number;
    let ov = origin[i] as number;
    console.log(source[i] + "->" + tv + "->" + ov);
    if (tv != ov) {
      console.log("Array data mismatch");
      return fail;
    }
  }

  try {
    target = origin.subarray(undefined, new Number(subarrayEnd));
  } catch(e) {
    console.log(e);
    return fail;
  }

  if (target.length as int != subarrayEnd - subarrayStart) {
    console.log("Array length mismatch on subarray2");
    return fail;
  }

  //Check all the data copied;
  for (let i: int = subarrayStart; i< subarrayEnd; i++) {
    let tv = target[i - subarrayStart] as number;
    let ov = origin[i] as number;
    console.log(source[i] + "->" + tv + "->" + ov);
    if (tv != ov) {
      console.log("Array data mismatch");
      return fail;
    }
  }

  try {
    target = origin.subarray(0, 0);
  } catch(e) {
    console.log(e);
    return fail;
  }

  if (target.length as int != 0) {
    console.log("Array length mismatch on subarray2");
    return fail;
  }

  return success;
}

function testSubarrayTwoParamsWithOtherNumber(): int {
  let source: number[] = [10, 20, 30, 40, 50, 60, 70, 80];
  let ss = new ArrayBuffer(source.length as int * 1);

  let origin: Uint8Array;

  try {
    origin = new Uint8Array(ss);
    origin.set(source);
  } catch(e) {
    console.log(e);
    return fail;
  }

  let subarrayStart: int = 4;
  let subarrayEnd: int = 2;

  let target: Uint8Array;

  try {
    target = origin.subarray(subarrayStart, subarrayEnd);
  } catch(e) {
    return fail;
  }

  if (target.length as int != 0) {
    console.log("Array length mismatch on subarray2");
    return fail;
  }

  subarrayStart = -1;
  subarrayEnd = origin.length as int;
  try {
    target = origin.subarray(subarrayStart, subarrayEnd);
  } catch(e) {
    return fail;
  }

  if (target.length as int != subarrayEnd - (origin.length + subarrayStart)) {
    console.log("Array length mismatch on subarray2");
    return fail;
  }

  //Check all the data copied;
  for (let i: int = (origin.length + subarrayStart) as int; i< subarrayEnd; i++) {
    let tv = target[i - (origin.length + subarrayStart)] as number;
    let ov = origin[i] as number;
    console.log(source[i] + "->" + tv + "->" + ov);
    if (tv != ov) {
      console.log("Array data mismatch");
      return fail;
    }
  }

  subarrayStart = 0;
  subarrayEnd = -origin.length as int;
  try {
    target = origin.subarray(subarrayStart, subarrayEnd);
  } catch(e) {
    console.log(e);
    return fail;
  }

  if (target.length as int != (origin.length + subarrayEnd) - subarrayStart) {
    console.log("Array length mismatch on subarray2");
    return fail;
  }

  return success;
}

function testSubarrayOneLengthTwoParams(): int {
  let source: number[] = [10];
  let ss = new ArrayBuffer(source.length as int * 1);

  let origin: Uint8Array;

  try {
    origin = new Uint8Array(ss);
    origin.set(source);
  } catch(e) {
    console.log(e);
    return fail;
  }
  
  let subarrayStart: int = 4;
  let subarrayEnd: int = 2;

  let target: Uint8Array;

  try {
    target = origin.subarray(subarrayStart, subarrayEnd);
  } catch(e) {
    return fail;
  }

  if (target.length as int != 0) {
    console.log("Array length mismatch on subarray2");
    return fail;
  }

  subarrayStart = 2;
  subarrayEnd = 4;
  try {
    target = origin.subarray(subarrayStart, subarrayEnd);
  } catch(e) {
    return fail;
  }

  if (target.length as int != 0) {
    console.log("Array length mismatch on subarray2");
    return fail;
  }

  return success;
}


这些测试用例检查了 subarray 方法在正常条件下和非正常条件下的行为,对比slice函数的测试用例,我们使用通过修改源数组影响目标数组数值的方式验证内存共享的功能。

参考文献:

  1. ECMA262-14th规范

  2. 方舟运行时公共组件

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
2
收藏 1
回复
举报
1条回复
按时间正序
/
按时间倒序
红叶亦知秋
红叶亦知秋

前排学习第二篇

回复
2024-4-25 10:11:10
回复
    相关推荐