从ECMA规范到ArkTS接口(二)--TypedArray.prototype.subarray接口 原创
作者:王清
上篇介绍了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
开始到原数组结尾的所有元素。如果 begin
或 end
是负值,则它们表示从数组末尾开始的倒数索引。方法返回一个新的 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.subarray
和 TypedArray.prototype.slice
是两个不同的 TypedArray
方法,用于生成原数组某部分的新数组,但他们在处理方式和结果的内存分配上存在差异。
首先,subarray
方法返回的新 TypedArray
对象,并不创建原 ArrayBuffer
的副本,而是创建了一个新的 TypedArray
视图,这个视图引用的是原 ArrayBuffer
中的相同内存区域。这意味着 subarray
方法生成的数组与原数组共享相同的数据存储,因此对新数组的修改会影响到原数组中相应的部分。这个方法的执行效率较高,因为它避免了复制操作,只是创建了一个指向相同内存区域的新视图。
另一方面,slice
方法会创建一个新的 ArrayBuffer
,并将选定的元素从原 TypedArray
对象复制到新数组中。这意味着 slice
方法生成的新数组拥有自己的数据存储,对这个新数组的任何修改都不会影响原数组。由于涉及到内存中数据的复制操作,slice
方法在执行时的效率略低于 subarray
。
下面是这两个方法的对比分析:
- 内存共享与否:
subarray
:返回一个新TypedArray
,共享同一个ArrayBuffer
。slice
:返回一个新TypedArray
,有自己独立的ArrayBuffer
。
- 性能:
subarray
:性能较高,因为没有进行元素的复制,仅仅是创建了一个新的视图。slice
:性能较低,因为需要复制元素到新的ArrayBuffer
。
- 修改影响:
subarray
:由于共享ArrayBuffer
,对生成的TypedArray
的修改会影响到原数组。slice
:由于使用了新的ArrayBuffer
,对生成的TypedArray
的修改不会影响到原数组。
- 参数处理:
subarray
和slice
方法对于start
和end
参数的处理是相同的。如果start
或end
是负数,它们会被解释为从数组末尾开始的索引。如果end
是undefined
,则操作会处理直到原数组的末尾。
- 返回值类型:
- 无论是
subarray
还是slice
,返回的新数组类型都与原数组相同。
- 无论是
- 错误处理:
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函数的测试用例,我们使用通过修改源数组影响目标数组数值的方式验证内存共享的功能。
前排学习第二篇