鸿蒙Harmony角落里的知识:从ECMA规范到ArkTS接口(二)

2024-06-22 01:20

本文主要是介绍鸿蒙Harmony角落里的知识:从ECMA规范到ArkTS接口(二),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

上篇介绍了typedArray.slice方法,鸿蒙Harmony角落里的知识:从ECMA规范到ArkTS接口(一)本文介绍一个返回结果和参数和slice非常类似的函数:TypedArray.prototype.subarray。


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, thena. 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, thena. 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

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

  1. 内存共享与否
    • subarray:返回一个新 TypedArray,共享同一个 ArrayBuffer
    • slice:返回一个新 TypedArray,有自己独立的 ArrayBuffer
  2. 性能
    • subarray:性能较高,因为没有进行元素的复制,仅仅是创建了一个新的视图。
    • slice:性能较低,因为需要复制元素到新的 ArrayBuffer
  3. 修改影响
    • subarray:由于共享 ArrayBuffer,对生成的 TypedArray 的修改会影响到原数组。
    • slice:由于使用了新的 ArrayBuffer,对生成的 TypedArray 的修改不会影响到原数组。
  4. 参数处理
    • subarray 和 slice 方法对于 start 和 end 参数的处理是相同的。如果 start 或 end 是负数,它们会被解释为从数组末尾开始的索引。如果 end 是 undefined,则操作会处理直到原数组的末尾。
  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 intconst relStart = normalizeIndex(begin, len)const relEnd = normalizeIndex(end, len)let count = relEnd - relStartif (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函数的测试用例,我们使用通过修改源数组影响目标数组数值的方式验证内存共享的功能。

最后

有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?但是又不知道从哪里下手,而且学习时频繁踩坑,最终浪费大量时间。所以本人整理了一些比较合适的鸿蒙(HarmonyOS NEXT)学习路径和一些资料的整理供小伙伴学习

点击领取→纯血鸿蒙Next全套最新学习资料(安全链接,放心点击

希望这一份鸿蒙学习资料能够给大家带来帮助,有需要的小伙伴自行领取,限时开源,先到先得~无套路领取!!

一、鸿蒙(HarmonyOS NEXT)最新学习路线

有了路线图,怎么能没有学习资料呢,小编也准备了一份联合鸿蒙官方发布笔记整理收纳的一套系统性的鸿蒙(OpenHarmony )学习手册(共计1236页)与鸿蒙(OpenHarmony )开发入门教学视频,内容包含:(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、Harmony南向开发、鸿蒙项目实战等等)鸿蒙(HarmonyOS NEXT)…等技术知识点。

获取以上完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料

二、HarmonyOS Next 最新全套视频教程

三、《鸿蒙 (OpenHarmony)开发基础到实战手册》

OpenHarmony北向、南向开发环境搭建

《鸿蒙开发基础》

  • ArkTS语言
  • 安装DevEco Studio
  • 运用你的第一个ArkTS应用
  • ArkUI声明式UI开发
  • .……

《鸿蒙开发进阶》

  • Stage模型入门
  • 网络管理
  • 数据管理
  • 电话服务
  • 分布式应用开发
  • 通知与窗口管理
  • 多媒体技术
  • 安全技能
  • 任务管理
  • WebGL
  • 国际化开发
  • 应用测试
  • DFX面向未来设计
  • 鸿蒙系统移植和裁剪定制
  • ……

《鸿蒙进阶实战》

  • ArkTS实践
  • UIAbility应用
  • 网络案例
  • ……

四、大厂面试必问面试题

五、鸿蒙南向开发技术

六、鸿蒙APP开发必备

七、鸿蒙生态应用开发白皮书V2.0PDF


完整鸿蒙HarmonyOS学习资料,请点击→纯血版全套鸿蒙HarmonyOS学习资料

总结
总的来说,华为鸿蒙不再兼容安卓,对中年程序员来说是一个挑战,也是一个机会。只有积极应对变化,不断学习和提升自己,他们才能在这个变革的时代中立于不败之地。 

                        

这篇关于鸿蒙Harmony角落里的知识:从ECMA规范到ArkTS接口(二)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1082886

相关文章

[职场] 公务员的利弊分析 #知识分享#经验分享#其他

公务员的利弊分析     公务员作为一种稳定的职业选择,一直备受人们的关注。然而,就像任何其他职业一样,公务员职位也有其利与弊。本文将对公务员的利弊进行分析,帮助读者更好地了解这一职业的特点。 利: 1. 稳定的职业:公务员职位通常具有较高的稳定性,一旦进入公务员队伍,往往可以享受到稳定的工作环境和薪资待遇。这对于那些追求稳定的人来说,是一个很大的优势。 2. 薪资福利优厚:公务员的薪资和

vue3项目将所有访问后端springboot的接口统一管理带跨域

vue3项目将所有访问后端springboot的接口统一管理带跨域 一、前言1.安装Axios2.创建Axios实例3.创建API服务文件4.在组件中使用API服务 二、跨域三、总结 一、前言 在Vue 3项目中,统一管理所有访问后端Spring Boot接口的最佳实践是创建一个专门的API服务层。这可以让你的代码更加模块化、可维护和集中管理。你可以使用Axios库作为HTT

【鸿蒙】ERROR_GET_BUNDLE_INSTALLER_FAILED

错误信息 [ERROR_GET_BUNDLE_INSTALLER_FAILED] Troubleshooting guide $ hdc file send D:\Huawei\devEcoProjects\entry\build\default\outputs\default\entry-default-unsigned.hap /sdcard/e8a215ea7be1444197e6a58e

linux常用API接口

linux常用API接口 文章目录 linux常用API接口1.应用层内存映射mmap取消内存映射munmap终端打印可用方式1.puts 函数2.文件操作函数 fprintf3.字符输出函数 putchar4.fwrite 函数 2.内核层 1.应用层 内存映射mmap mmap 是一个用于内存映射的系统调用,它可以将一个文件或设备中的内容映射到进程的地址空间中,允许程

ArkTS开发系列之导航 (2.7动画)

上篇回顾: ArkTS开发系列之导航 (2.6 图形) 本篇内容:动画的学习使用 一、 知识储备 1. 布局更新动画 包含显式动画(animateTo)和属性动画(animation) 动画类型名称特点显式动画闭包内的变化都会触发动画执行, 可以做较复杂的动画属性动画属性变化时触发动画执行, 设置简单 说白了,显示动画就是靠闭包事件触发,属性动画是挂在组件身上的属性变化触发 显式动画

关于CPU的一点知识

首先说一下,CPU是干啥的: CPU所负责的就是解释和运行最终转换成机器语言的程序内容 我们需要知道的CPU结构:重点需要关注寄存器 运算器 简单说就是负责运算从内存读取到寄存器中的数据,可以看作一个数据加工厂,就是对寄存器中的数据做运算,这些运算包含基本的算术和逻辑运算。 算术逻辑单元(ALU) 这个是运算器中重要的一个组成,主要负责的就是对数据的处理,从而实现对数据的算术和

关于微信没有接入鸿蒙NEXT的思考

6月21日,纯血鸿蒙发布,国内的质疑声终于停止,不再被人喊叫换皮 Android 了.就连编程语言都是华为自研的。 可是发布会后微信却成了热点,因为余承东在感谢了一圈互联网企业,如:淘宝、支付宝、美团、京东、抖音、今日头条、钉钉、小红书、微博、B站、高德、WPS等等. 唯独没有感谢腾讯. 中国互联网巨头只有哪么几家,腾讯、阿里、字节、拼多多、美团、百度、京东、华为 他们这些派系又诞生了无数

上位机图像处理和嵌入式模块部署(mcu和swd接口)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】         最近学习mcu的时候,接触了不少调试器,这里面有daplink、st-link v2、j-link v9。虽然模块的形状可能不太一样,但是硬件的连线都差不多,都是mcu上的3.3v、clk、dio和gnd四根连线。出于好奇,今天花了点时间了解了一下debug port、sw

【架构设计】模块化-面向接口编程

模块化的优势 结构清晰:业务独立,代码实现分离便于协作:耦合度低,不会影响团队其他成员的开发进度便于维护:各模块管理自身代码、布局、资源,不影响主工程 模块化的特点 高内聚,低耦合 常见的模块化方法 所有模块都放在一个工程里实现每个模块都放在一个独立工程中实现 模块的划分 从高到低 业务层平台能力层基础层 模块间的通信 直接依赖事件或广播通信路由通信面向接口通信 具体实现

使用ig507金融数据库的股票API接口经验有感:Java与Python

一、Java技术: 1. Java调用ig507金融数据库(ig507.com)股票API接口 引言: 随着金融科技的不断发展,数据驱动的投资策略变得越来越重要。本文将介绍如何使用Java语言调用ig507金融数据库的股票API接口,以获取实时股票数据,并展示基本的编程步骤和注意事项。 步骤一:引入依赖库 在Java项目中,首先需要引入用于处理HTTP请求和网络通信的库,如Apache