鸿蒙中 ForEach 与 LazyForEach 的内存管理区别

768 阅读4分钟

引言

在鸿蒙开发中,遍历集合或数组是十分常见的操作。为了提高性能和优化内存管理,不同的工具被开发出来,其中较为常用的有ForEachLazyForEach。本文将探讨这两者在内存管理方面的差异,特别是针对大数据集时的处理方式。

ForEach 的特点

ForEach 是一种用于立即遍历集合中所有元素的工具。在迭代过程中,它可能会消耗更多的内存,因为它需要在内存中存储整个集合的引用。对小数据集来说,这种方法通常不会有问题;然而,当数据量增大时,内存消耗可能会显得捉襟见肘,影响程序的性能和稳定性。

LazyForEach 的特点

相比之下,LazyForEach 采用惰性迭代的方法。它只在每次迭代时才计算所需的元素,从而显著减少了内存的即时占用。这种方法尤其适用于处理大数据集,能够有效降低内存使用,提高程序的效率。

内存占用比较

通过两个图表,我们可以更直观地理解 ForEach 和 LazyForEach 在内存管理上的区别:

没有使用LazyForEach的内存占用情况

image.png

该图展示了未使用 LazyForEach 的情况下,一个应用程序在滑动页面时的内存占用情况。可以看到,随着页面的滑动,内存占用逐渐增加,可能会影响应用程序的性能和用户体验。

使用LazyForEach后的内存占用情况

image.png

该图展示了采用 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),如有不对的地方,请多包容,感谢指正。