引言
在鸿蒙开发中,遍历集合或数组是十分常见的操作。为了提高性能和优化内存管理,不同的工具被开发出来,其中较为常用的有ForEach和LazyForEach。本文将探讨这两者在内存管理方面的差异,特别是针对大数据集时的处理方式。
ForEach 的特点
ForEach 是一种用于立即遍历集合中所有元素的工具。在迭代过程中,它可能会消耗更多的内存,因为它需要在内存中存储整个集合的引用。对小数据集来说,这种方法通常不会有问题;然而,当数据量增大时,内存消耗可能会显得捉襟见肘,影响程序的性能和稳定性。
LazyForEach 的特点
相比之下,LazyForEach 采用惰性迭代的方法。它只在每次迭代时才计算所需的元素,从而显著减少了内存的即时占用。这种方法尤其适用于处理大数据集,能够有效降低内存使用,提高程序的效率。
内存占用比较
通过两个图表,我们可以更直观地理解 ForEach 和 LazyForEach 在内存管理上的区别:
没有使用LazyForEach的内存占用情况
该图展示了未使用 LazyForEach 的情况下,一个应用程序在滑动页面时的内存占用情况。可以看到,随着页面的滑动,内存占用逐渐增加,可能会影响应用程序的性能和用户体验。
使用LazyForEach后的内存占用情况
该图展示了采用 LazyForEach 后的内存占用情况。通过使用惰性加载,内存占用得到了有效控制,应用程序的性能和响应速度得到了显著提升。
通过以上对比,我们可以清楚看出 LazyForEach 在处理大量数据时的内存管理优势。具体来说,LazyForEach 只会创建当前可视范围内的组件,从而大幅减少内存占用,这对大型数据集极为有利。
适用场景
ForEach:适用于需要立即访问集合中所有元素的场景。LazyForEach:适用于只需按需访问元素的场景,尤其是当集合很大或元素计算成本较高时。
应用示例
在OpenHarmony的平台上,第三方库 pulltorefresh 支持将 LazyForEach 用作数据源。这里附上相关链接和示例代码:
支持lazyForEarch的数据作为数据源
// IDataSource 需要提供给 LazyForEach 的数据源 必须要实现的接口
class BasicDataSource implements IDataSource{
private listeners: DataChangeListener[] = new Array<DataChangeListener>();
public totalCount(): number {
return 0;
}
public getData(index: number): Object {
return index;
}
// 为LazyForEach组件向其数据源处添加listener监听
registerDataChangeListener(listener: DataChangeListener): void {
if (this.listeners.indexOf(listener) < 0) {
console.info('add listener');
this.listeners.push(listener);
}
}
// 为对应的LazyForEach组件在数据源处去除listener监听
unregisterDataChangeListener(listener: DataChangeListener): void {
const pos = this.listeners.indexOf(listener);
if (pos >= 0) {
console.info('remove listener');
this.listeners.splice(pos, 1);
}
}
// 通知LazyForEach组件需要重载所有子组件
notifyDataReload(): void {
this.listeners.forEach(listener => {
listener.onDataReloaded();
})
}
// 通知LazyForEach组件需要在index对应索引处添加子组件
notifyDataAdd(index: number): void {
this.listeners.forEach(listener => {
listener.onDataAdd(index);
})
}
// 通知LazyForEach组件需要在index对应索引处添加子组件
notifyDataChange(index: number): void {
this.listeners.forEach(listener => {
listener.onDataChange(index);
})
}
// 通知LazyForEach组件需要在index对应索引处删除该子组件
notifyDataDelete(index: number): void {
this.listeners.forEach(listener => {
listener.onDataDelete(index);
})
}
notifyDataMove(from: number, to: number): void {
this.listeners.forEach(listener => {
listener.onDataMove(from, to);
})
}
}
// 将内部操纵数据的方法 类型,替换为我们希望的数据
class MyDataSource extends BasicDataSource {
// 数据本身
private dataArray: string[] = [];
// 总个数
public totalCount(): number {
return this.dataArray.length;
}
// 获取数据
public getData(index: number): Object {
return this.dataArray[index];
}
// 在指定的位置添加数据
public addData(index: number, data: string): void {
this.dataArray.splice(index, 0, data);
this.notifyDataAdd(index);
}
// 最佳数据
public pushData(data: string): void {
this.dataArray.push(data);
this.notifyDataAdd(this.dataArray.length - 1);
}
// 清空数据
public clear():void{
this.dataArray = [];
}
}
@Entry
@Component
struct MyComponent {
@State refreshText: string = '';
@State data: MyDataSource = new MyDataSource();
// 需绑定列表或宫格组件
private scroller: Scroller = new Scroller();
private timer:null|number=null;
aboutToAppear() {
for (let i = 1; i <= 20; i++) {
this.data.pushData(`Hello ${i}`);
}
}
build() {
Column() {
PullToRefresh({
// 必传项,列表组件所绑定的数据
data: $data,
// 必传项,需绑定传入主体布局内的列表或宫格组件
scroller: this.scroller,
// 必传项,自定义主体布局,内部有列表或宫格组件
customList:() => {
// 一个用@Builder修饰过的UI方法
this.getListView();
},
// 可选项,下拉刷新回调
onRefresh: () => {
return new Promise<string>((resolve, reject) => {
// 模拟网络请求操作,请求网络2秒后得到数据,通知组件,变更列表数据
this.timer = setTimeout(() => {
resolve('刷新成功');
console.log(' 刷新成功');
this.data.addData(0,'ADD HEAD 0');
}, 2000);
});
},
// 可选项,上拉加载更多回调
onLoadMore: () => {
return new Promise<string>((resolve, reject) => {
// 模拟网络请求操作,请求网络2秒后得到数据,通知组件,变更列表数据
this.timer=setTimeout(() => {
resolve('');
console.log('上拉加载完成');
this.data.pushData(`Hello ${this.data.totalCount()}`);
}, 2000);
});
},
customLoad: null,
customRefresh: null,
})
}
}
@Builder
private getListView() {
List({ space: 3 , scroller: this.scroller }) {
LazyForEach(this.data, (item: string, index?:number) => {
ListItem() {
Row() {
Text(item).fontSize(50)
//组件挂载显示触发
.onAppear(() => {
if(index){
console.log(" onAppear: index="+index + ' content= ' +this.data.getData(index) );
}
})
//组件卸载载显示触发
.onDisAppear(()=>{
if(index) {
console.log(" onDisAppear: index=" + index + ' content= ' + this.data.getData(index));
}
})
}.margin({ left: 10, right: 10 })
}
}, (item: string) => item);
}.cachedCount(5)
.backgroundColor('#eeeeee')
.divider({ strokeWidth: 1, color: 0x222222 })
.edgeEffect(EdgeEffect.None) // 必须设置列表为滑动到边缘无效果
}
aboutToDisappear() {
clearTimeout(this.timer);
this.data.clear();
}
}
以上实现基于HarmonyOS NEXT (API12),如有不对的地方,请多包容,感谢指正。