HarmonyOS:ForEach:循环渲染

news2025/5/11 10:04:58

一、前言

ForEach接口基于数组类型数据来进行循环渲染,需要与容器组件配合使用,且接口返回的组件应当是允许包含在ForEach父容器组件中的子组件。例如,ListItem组件要求ForEach的父容器组件必须为List组件。

API参数说明见:ForEach API参数说明。

说明
从API version 9开始,该接口支持在ArkTS卡片中使用。

二、键值生成规则

在ForEach循环渲染过程中,系统会为每个数组元素生成一个唯一且持久的键值,用于标识对应的组件。当这个键值变化时,ArkUI框架将视为该数组元素已被替换或修改,并会基于新的键值创建一个新的组件。

ForEach提供了一个名为keyGenerator的参数,这是一个函数,开发者可以通过它自定义键值的生成规则。如果开发者没有定义keyGenerator函数,则ArkUI框架会使用默认的键值生成函数,即(item: Object, index: number) => { return index + ‘__’ + JSON.stringify(item); }。

ArkUI框架对于ForEach的键值生成有一套特定的判断规则,这主要与itemGenerator函数的第二个参数index以及keyGenerator函数的第二个参数index有关,具体的键值生成规则判断逻辑如下图所示。

ForEach键值生成规则

在这里插入图片描述

说明
ArkUI框架会对重复的键值发出警告。在UI更新的场景下,如果出现重复的键值,框架可能无法正常工作,具体请参见渲染结果非预期。

三、组件创建规则

在确定键值生成规则后,ForEach的第二个参数itemGenerator函数会根据键值生成规则为数据源的每个数组项创建组件。组件的创建包括两种情况:ForEach首次渲染和ForEach非首次渲染

3.1 首次渲染

在ForEach首次渲染时,会根据前述键值生成规则为数据源的每个数组项生成唯一键值,并创建相应的组件。

@Component
struct ChildItem {
  @Prop item: string

  build() {
    Text(this.item)
      .fontSize(20)
      .fontWeight(FontWeight.Medium)
      .fontColor($r('app.color.c_black'))
      .backgroundColor("#f5f5f5")
      .borderRadius(8)
      .padding(16)
      .width('100%')
  }
}

@Entry
@Component
struct TestForEach1 {
  @State message: string = 'ForEach:循环渲染';
  @State simpleList: Array<string> = ['one', 'two', 'three'];

  build() {
    Column({ space: 10 }) {
      ForEach(this.simpleList, (item: string) => {
        ChildItem({ item: item })
      }, (item: string) => item)
    }
    .height('100%')
    .width('100%')
    .padding(16)
    .backgroundColor("#ffffff")
  }
}

ForEach数据源不存在相同值案例首次渲染运行效果图

在这里插入图片描述

在上述代码中,键值生成规则是keyGenerator函数的返回值item。在ForEach渲染循环时,为数据源数组项依次生成键值one、two和three,并创建对应的ChildItem组件渲染到界面上。

当不同数组项按照键值生成规则生成的键值相同时,框架的行为是未定义的。例如,在以下代码中,ForEach渲染相同的数据项two时,只创建了一个ChildItem组件,而没有创建多个具有相同键值的组件。

@Component
struct ChildItem {
  @Prop item: string

  build() {
    Text(this.item)
      .fontSize(20)
      .fontWeight(FontWeight.Medium)
      .fontColor($r('app.color.c_black'))
      .backgroundColor("#f5f5f5")
      .borderRadius(8)
      .padding(16)
      .width('100%')
  }
}

@Entry
@Component
struct TestForEach1 {
  @State message: string = 'ForEach:循环渲染';
  // @State simpleList: Array<string> = ['one', 'two', 'three'];
  @State simpleList: Array<string> = ['one', 'two', 'two', 'three'];

  build() {
    Column({ space: 10 }) {
      ForEach(this.simpleList, (item: string) => {
        ChildItem({ item: item })
      }, (item: string) => item)
    }
    .height('100%')
    .width('100%')
    .padding(16)
    .backgroundColor("#ffffff")
  }
}

ForEach数据源存在相同值案例首次渲染运行效果图

在这里插入图片描述

在该示例中,最终键值生成规则为item。当ForEach遍历数据源simpleList,遍历到索引为1的two时,按照最终键值生成规则生成键值为two的组件并进行标记。当遍历到索引为2的two时,按照最终键值生成规则当前项的键值也为two,此时不再创建新的组件。

3.2 非首次渲染

在ForEach组件进行非首次渲染时,它会检查新生成的键值是否在上次渲染中已经存在。如果键值不存在,则会创建一个新的组件;如果键值存在,则不会创建新的组件,而是直接渲染该键值所对应的组件。例如,在以下的代码示例中,通过点击事件修改了数组的第三项值为"new three",这将触发ForEach组件进行非首次渲染。

@Component
struct ChildItem {
  @Prop item: string

  build() {
    Text(this.item)
      .fontSize(20)
      .fontWeight(FontWeight.Medium)
      .fontColor($r('app.color.c_black'))
      .backgroundColor("#f5f5f5")
      .borderRadius(8)
      .padding(16)
      .width('100%')
  }
}

@Entry
@Component
struct TestForEach1 {
  @State message: string = 'ForEach:循环渲染';
  @State simpleList: Array<string> = ['one', 'two', 'three'];

  // @State simpleList: Array<string> = ['one', 'two', 'two', 'three'];

  build() {
    Column({ space: 10 }) {
     Button('点击修改第3个数组项的值')
        .fontSize(24)
        .fontColor(Color.White)
        .onClick(() => {
          this.simpleList[2] = 'new three';
          console.log("点击了 修改第3个数组项的值 事件")
        })
      ForEach(this.simpleList, (item: string) => {
        ChildItem({ item: item })
      }, (item: string) => item)
    }
    .height('100%')
    .width('100%')
    .padding(16)
    .backgroundColor("#ffffff")
  }
}

ForEach非首次渲染案例运行效果图

在这里插入图片描述

从本例可以看出@State 能够监听到简单数据类型数组数据源 simpleList 数组项的变化。

  1. 当 simpleList 数组项发生变化时,会触发 ForEach 进行重新渲染。
  2. ForEach 遍历新的数据源 [‘one’, ‘two’, ‘new three’],并生成对应的键值one、two和new three。
  3. 其中,键值one和two在上次渲染中已经存在,所以 ForEach 复用了对应的组件并进行了渲染。对于第三个数组项 “new three”,由于其通过键值生成规则 item 生成的键值new three在上次渲染中不存在,因此 ForEach 为该数组项创建了一个新的组件。

四、使用场景

ForEach组件在开发过程中的主要应用场景包括:数据源不变、数据源数组项发生变化(如插入、删除操作)、数据源数组项子属性变化。

4.1数据源不变

在数据源保持不变的场景中,数据源可以直接采用基本数据类型。例如,在页面加载状态时,可以使用骨架屏列表进行渲染展示。

@Entry
@Component
struct TestForEach2 {
  @State simpleList: Array<number> = [1, 2, 3, 4, 5];

  build() {
    Column() {
      ForEach(this.simpleList, (item: number) => {
        ArticleSkeletonView()
          .margin({ top: 20 })
      }, (item: number) => item.toString())
    }
    .padding(20)
    .width('100%')
    .height('100%')
  }
}

@Builder
function textArea(width: number | Resource | string = '100%', height: number | Resource | string = '100%') {
  Row()
    .width(width)
    .height(height)
    .backgroundColor('#FFF2F3F4')
}

@Component
struct ArticleSkeletonView {
  build() {
    Row() {
      Column() {
        textArea(80, 80)
      }
      .margin({ right: 20 })

      Column() {
        textArea('60%', 20)
        textArea('50%', 20)
      }
      .alignItems(HorizontalAlign.Start)
      .justifyContent(FlexAlign.SpaceAround)
      .height('100%')
    }
    .padding(20)
    .borderRadius(12)
    .backgroundColor('#FFECECEC')
    .height(120)
    .width('100%')
    .justifyContent(FlexAlign.SpaceBetween)
  }
}

骨架屏运行效果图

在这里插入图片描述

在本示例中,采用数据项item作为键值生成规则,由于数据源simpleList的数组项各不相同,因此能够保证键值的唯一性。

4.2 数据源数组项发生变化

在数据源数组项发生变化的场景下,例如进行数组插入、删除操作或者数组项索引位置发生交换时,数据源应为对象数组类型,并使用对象的唯一ID作为最终键值。例如,当在页面上通过手势上滑加载下一页数据时,会在数据源数组尾部新增新获取的数据项,从而使得数据源数组长度增大。

class Article33 {
  id: string;
  title: string;
  brief: string;

  constructor(id: string, title: string, brief: string) {
    this.id = id;
    this.title = title;
    this.brief = brief;
  }
}

@Entry
@Component
struct TestForEach3 {
  @State isListReachEnd: boolean = false;
  @State articleList: Array<Article33> = [
    new Article33('001', '第1篇文章', '文章简介内容'),
    new Article33('002', '第2篇文章', '文章简介内容'),
    new Article33('003', '第3篇文章', '文章简介内容'),
    new Article33('004', '第4篇文章', '文章简介内容'),
    new Article33('005', '第5篇文章', '文章简介内容'),
    new Article33('006', '第6篇文章', '文章简介内容')
  ]

  loadMoreArticles() {
    this.articleList.push(new Article33('007', '加载的新文章', '文章简介内容'));
  }

  build() {
    Column({ space: 5 }) {
      List() {
        ForEach(this.articleList, (item: Article33) => {
          ListItem() {
            ArticleCard({ article: item })
              .margin({ top: 20 })
          }
        }, (item: Article33) => item.id)
      }
      .onReachEnd(() => {
        this.isListReachEnd = true;
      })
      .parallelGesture(
        PanGesture({ direction: PanDirection.Up, distance: 80 })
          .onActionStart(() => {
            if (this.isListReachEnd) {
              this.loadMoreArticles();
              this.isListReachEnd = false;
            }
          })
      )
      .padding(20)
      .scrollBar(BarState.Off)
    }
    .width('100%')
    .height('100%')
    .backgroundColor(0xF1F3F5)
  }
}

@Component
struct ArticleCard {
  @Prop article: Article33;

  build() {
    Row() {
      // 此处'app.media.icon'仅作示例,请开发者自行替换,否则imageSource创建失败会导致后续无法正常执行。
      Image($r('app.media.icon'))
        .width(80)
        .height(80)
        .margin({ right: 20 })

      Column() {
        Text(this.article.title)
          .fontSize(20)
          .margin({ bottom: 8 })
        Text(this.article.brief)
          .fontSize(16)
          .fontColor(Color.Gray)
          .margin({ bottom: 8 })
      }
      .alignItems(HorizontalAlign.Start)
      .width('80%')
      .height('100%')
    }
    .padding(20)
    .borderRadius(12)
    .backgroundColor('#FFECECEC')
    .height(120)
    .width('100%')
    .justifyContent(FlexAlign.SpaceBetween)
  }
}

数据源数组项变化案例运行效果图

在这里插入图片描述

在本示例中,ArticleCard组件作为ArticleListView组件的子组件,通过@Prop装饰器接收一个Article对象,用于渲染文章卡片。

  1. 当列表滚动到底部时,如果手势滑动距离超过指定的80,将触发loadMoreArticle()函数。此函数会在articleList数据源的尾部添加一个新的数据项,从而增加数据源的长度。
  2. 数据源被@State装饰器修饰,ArkUI框架能够感知到数据源长度的变化,并触发ForEach进行重新渲染。
4.3 数据源数组项子属性变化

当数据源的数组项为对象数据类型,并且只修改某个数组项的属性值时,由于数据源为复杂数据类型,ArkUI框架无法监听到@State装饰器修饰的数据源数组项的属性变化,从而无法触发ForEach的重新渲染。为实现ForEach重新渲染,需要结合@Observed和@ObjectLink装饰器使用。例如,在文章列表卡片上点击“点赞”按钮,从而修改文章的点赞数量。

@Observed
class Article34 {
  id: string;
  title: string;
  brief: string;
  isLiked: boolean;
  likesCount: number;

  constructor(id: string, title: string, brief: string, isLiked: boolean, likesCount: number) {
    this.id = id;
    this.title = title;
    this.brief = brief;
    this.isLiked = isLiked;
    this.likesCount = likesCount;
  }
}

@Entry
@Component
struct TestForEach4 {
  @State isListReachEnd: boolean = false;
  @State articleList: Array<Article34> = [
    new Article34('001', '第0篇文章', '文章简介内容', false, 100),
    new Article34('002', '第1篇文章', '文章简介内容', false, 100),
    new Article34('003', '第2篇文章', '文章简介内容', false, 100),
    new Article34('004', '第4篇文章', '文章简介内容', false, 100),
    new Article34('005', '第5篇文章', '文章简介内容', false, 100),
    new Article34('006', '第6篇文章', '文章简介内容', false, 100),
  ]

  loadMoreArticles() {
    this.articleList.push(new Article34('007', '加载的新文章', '文章简介内容', true, 200));
  }

  build() {
    Column({ space: 5 }) {
      List() {
        ForEach(this.articleList, (item: Article34) => {
          ListItem() {
            ArticleCard2({ article: item })
              .margin({ top: 20 })
          }
        }, (item: Article34) => item.id)
      }
      .onReachEnd(() => {
        this.isListReachEnd = true;
      })
      .parallelGesture(
        PanGesture({ direction: PanDirection.Up, distance: 80 })
          .onActionStart(() => {
            if (this.isListReachEnd) {
              this.loadMoreArticles();
              this.isListReachEnd = false;
            }
          })
      )
      .padding(20)
      .scrollBar(BarState.Off)
    }
    .width('100%')
    .height('100%')
    .backgroundColor(0xF1F3F5)
  }
}

@Component
struct ArticleCard2 {
  // @Prop article: Article34;
  @ObjectLink article: Article34;

  handleLiked() {
    this.article.isLiked = !this.article.isLiked;
    this.article.likesCount = this.article.isLiked ? this.article.likesCount + 1 : this.article.likesCount - 1;
  }

  build() {
    Row() {
      // 此处'app.media.icon'仅作示例,请开发者自行替换,否则imageSource创建失败会导致后续无法正常执行。
      Image($r('app.media.icon'))
        .width(80)
        .height(80)
        .margin({ right: 20 })

      Column() {
        Text(this.article.title)
          .fontSize(20)
          .margin({ bottom: 8 })
        Text(this.article.brief)
          .fontSize(16)
          .fontColor(Color.Gray)
          .margin({ bottom: 8 })

        Row() {
          // 此处app.media.icon_like','app.media.icon_unlike'仅作示例,请开发者自行替换,否则imageSource创建失败会导致后续无法正常执行。
          Image(this.article.isLiked ? $r('app.media.icon_like') : $r('app.media.icon_unlike'))
            .width(24)
            .height(24)
            .margin({ right: 8 })
          Text(this.article.likesCount.toString())
            .fontSize(16)
        }
        .onClick(() => this.handleLiked())
        .justifyContent(FlexAlign.Center)
      }
      .alignItems(HorizontalAlign.Start)
      .width('80%')
      .height('100%')
    }
    .padding(20)
    .borderRadius(12)
    .backgroundColor('#FFECECEC')
    .height(120)
    .width('100%')
    .justifyContent(FlexAlign.SpaceBetween)
  }
}

数据源数组项子属性变化案例运行效果图

在这里插入图片描述

在本示例中,Article类被@Observed装饰器修饰。父组件ArticleListView传入Article对象实例给子组件ArticleCard,子组件使用@ObjectLink装饰器接收该实例。

  1. 当点击第1个文章卡片上的点赞图标时,会触发ArticleCard组件的handleLiked函数。该函数修改第1个卡片对应组件里article实例的isLiked和likesCount属性值。
  2. 由于子组件ArticleCard中的article使用了@ObjectLink装饰器,父子组件共享同一份article数据。因此,父组件中articleList
  3. 第1个数组项的isLiked和likedCounts数值也会同步修改。
  4. 当父组件监听到数据源数组项属性值变化时,会触发ForEach重新渲染。
  5. 在此处,ForEach键值生成规则为数组项的id属性值。当ForEach遍历新数据源时,数组项的id均没有变化,不会新建组件。
    渲染第1个数组项对应的ArticleCard组件时,读取到的isLiked和likesCount为修改后的新值。
4.4 拖拽排序

当ForEach在List组件下使用,并且设置了onMove事件,ForEach每次迭代都生成一个ListItem时,可以使能拖拽排序。拖拽排序离手后,如果数据位置发生变化,则会触发onMove事件,上报数据移动原始索引号和目标索引号。在onMove事件中,需要根据上报的起始索引号和目标索引号修改数据源。数据源修改前后,要保持每个数据的键值不变,只是顺序发生变化,才能保证落位动画正常执行。

@Entry
@Component
struct TestForEachSort {
  @State arr: Array<string> = [];

  build() {
    Row() {
      List() {
        ForEach(this.arr, (item: string) => {
          ListItem() {
            Text(item.toString())
              .fontSize(16)
              .textAlign(TextAlign.Center)
              .size({height: 100, width: "100%"})
          }.margin(10)
          .borderRadius(10)
          .backgroundColor("#FFFFFFFF")
        }, (item: string) => item)
          .onMove((from:number, to:number) => {
            let tmp = this.arr.splice(from, 1);
            this.arr.splice(to, 0, tmp[0])
          })
      }
      .width('100%')
      .height('100%')
      .backgroundColor("#FFDCDCDC")
    }
  }
  aboutToAppear(): void {
    for (let i = 0; i < 100; i++) {
      this.arr.push(i.toString())
    }
  }
}

ForEach拖拽排序效果图

在这里插入图片描述

4.5 使用建议
  • 为满足键值的唯一性,对于对象数据类型,建议使用对象数据中的唯一id作为键值。
  • 尽量避免在最终的键值生成规则中包含数据项索引index,以防止出现渲染结果非预期和渲染性能降低。如果业务确实需要使用index,例如列表需要通过index进行条件渲染,开发者需要接受ForEach在改变数据源后重新创建组件所带来的性能损耗。
  • 基本数据类型的数据项没有唯一ID属性。如果使用基本数据类型本身作为键值,必须确保数组项无重复。因此,对于数据源会发生变化的场景,建议将基本数据类型数组转化为具备唯一ID属性的对象数据类型数组,再使用ID属性作为键值生成规则。
  • 对于以上限制规则,index参数存在的意义为:index是开发者保证键值唯一性的最终手段;对数据项进行修改时,由于itemGenerator中的item参数是不可修改的,所以须用index索引值对数据源进行修改,进而触发UI重新渲染。
  • ForEach在下列容器组件 List、Grid、Swiper以及WaterFlow 内使用的时候,不要与LazyForEach 混用。 以List为例,同时包含ForEach、LazyForEach的情形是不推荐的。
4.6 不推荐案例

开发者在使用ForEach的过程中,若对于键值生成规则的理解不够充分,可能会出现错误的使用方式。错误使用一方面会导致功能层面问题,例如渲染结果非预期,另一方面会导致性能层面问题,例如渲染性能降低。

4.7 渲染结果非预期

在本示例中,通过设置ForEach的第三个参数KeyGenerator函数,自定义键值生成规则为数据源的索引index的字符串类型值。当点击父组件Parent中“在第1项后插入新项”文本组件后,界面会出现非预期的结果。

@Entry
@Component
struct TestForEach5 {
  @State simpleList: Array<string> = ['one', 'two', 'three'];

  build() {
    Column() {
      Button('在第1项后插入新项')
        .fontSize(20)
        .fontWeight(FontWeight.Medium)
        .type(ButtonType.Normal)
        .onClick(() => {
          this.simpleList.splice(1, 0, "new item");
        })
      ForEach(this.simpleList, (item: string) => {
        ChildItem5({ item: item });
      }, (item: string, index: number) => index.toString())
    }
    .height('100%')
    .width('100%')
  }
}

@Component
struct ChildItem5 {
  @Prop item: string;

  build() {
    Text(this.item)
      .fontSize(20)
      .fontWeight(FontWeight.Medium)
      .fontColor($r('app.color.c_black'))
  }
}

渲染结果非预期运行效果图

在这里插入图片描述

ForEach在首次渲染时,创建的键值依次为"0"、“1”、“2”。

插入新项后,数据源simpleList变为[‘one’, ‘new item’, ‘two’, ‘three’],框架监听到@State装饰的数据源长度变化触发ForEach重新渲染。

ForEach依次遍历新数据源,遍历数据项"one"时生成键值"0",存在相同键值,因此不创建新组件。继续遍历数据项"new item"时生成键值"1",存在相同键值,因此不创建新组件。继续遍历数据项"two"生成键值"2",存在相同键值,因此不创建新组件。最后遍历数据项"three"时生成键值"3",不存在相同键值,创建内容为"three"的新组件并渲染。

从以上可以看出,当最终键值生成规则包含index时,期望的界面渲染结果为[‘one’, ‘new item’, ‘two’, ‘three’],而实际的渲染结果为[‘one’, ‘two’, ‘three’, ‘three’],渲染结果不符合开发者预期。因此,开发者在使用ForEach时应尽量避免最终键值生成规则中包含index。

4.8 渲染性能降低

在本示例中,ForEach的第三个参数KeyGenerator函数处于缺省状态。根据上述键值生成规则,此例使用框架默认的键值生成规则,即最终键值为字符串index + ‘__’ + JSON.stringify(item)。当点击“在第1项后插入新项”文本组件后,ForEach将需要为第2个数组项以及其后的所有项重新创建组件。

@Entry
@Component
struct TestForEach6 {
  @State simpleList: Array<string> = ['one', 'two', 'three'];

  build() {
    Column() {
      Button('在第1项后插入新项')
        .fontSize(20)
        .fontWeight(FontWeight.Medium)
        .type(ButtonType.Capsule)
        .onClick(() => {
          this.simpleList.splice(1, 0, "new item");
          console.log(`[onClick]: simpleList is ${JSON.stringify(this.simpleList)}`);
        })
      ForEach(this.simpleList, (item: string) => {
        ChildItem6({ item: item });
      })
    }
    // .justifyContent(FlexAlign.Center)
    .height('100%')
    .width('100%')
  }
}

@Component
struct ChildItem6 {
  @Prop item: string;

  aboutToAppear(): void {
    console.log(`ChildItem6 [aboutToAppear]: item is ${this.item}`);
  }

  build() {
    Text(this.item)
      .fontSize(20)
      .fontWeight(FontWeight.Medium)
      .fontColor($r('app.color.c_black'))
  }
}

渲染性能降低案例运行效果图

在这里插入图片描述

插入新项后,ForEach为new item、 two、 three三个数组项创建了对应的组件ChildItem,并执行了组件的aboutToAppear()生命周期函数。这是因为:

  1. 在ForEach首次渲染时,创建的键值依次为0__one、1__two、2__three。
  2. 插入新项后,数据源simpleList变为[‘one’, ‘new item’, ‘two’, ‘three’],ArkUI框架监听到@State装饰的数据源长度变化触发ForEach重新渲染。
  3. ForEach依次遍历新数据源,遍历数据项one时生成键值0__one,键值已存在,因此不创建新组件。继续遍历数据项new item时生成键值1__new item,不存在相同键值,创建内容为new item的新组件并渲染。继续遍历数据项two生成键值2__two,不存在相同键值,创建内容为two的新组件并渲染。最后遍历数据项three时生成键值3__three,不存在相同键值,创建内容为three的新组件并渲染。

尽管此示例中界面渲染的结果符合预期,但每次插入一条新数组项时,ForEach都会为从该数组项起后面的所有数组项全部重新创建组件。当数据源数据量较大或组件结构复杂时,由于组件无法得到复用,将导致性能体验不佳。因此,除非必要,否则不推荐将第三个参数KeyGenerator函数处于缺省状态,以及在键值生成规则中包含数据项索引index。

正确渲染并保证效率的ForEach写法是:

ForEach(this.simpleList, (item: string) => {
  ChildItem({ item: item })
}, (item: string) => item)  // 需要保证key唯一

提供了第三个参数KeyGenerator,在这个例子中,对数据源的不同数据项生成不同的key,并且对同一个数据项每次生成相同的key

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2284325.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++ STL:深入探索常见容器

你好呀&#xff0c;欢迎来到 Dong雨 的技术小栈 &#x1f331; 在这里&#xff0c;我们一同探索代码的奥秘&#xff0c;感受技术的魅力 ✨。 &#x1f449; 我的小世界&#xff1a;Dong雨 &#x1f4cc; 分享我的学习旅程 &#x1f6e0;️ 提供贴心的实用工具 &#x1f4a1; 记…

Java面试题2025-设计模式

1.说一下开发中需要遵守的设计原则&#xff1f; 设计模式中主要有六大设计原则&#xff0c;简称为SOLID &#xff0c;是由于各个原则的首字母简称合并的来(两个L算一个,solid 稳定的)&#xff0c;六大设计原则分别如下&#xff1a; 1、单一职责原则 单一职责原则的定义描述非…

flink StreamGraph解析

Flink程序有三部分operation组成&#xff0c;分别是源source、转换transformation、目的地sink。这三部分构成DAG。 DAG首先生成的是StreamGraph。 用户代码在添加operation的时候会在env中缓存&#xff08;变量transformations&#xff09;&#xff0c;在env.execute()执行的…

基于SpringBoot的网上摄影工作室开发与实现 | 含论文、任务书、选题表

随着互联网技术的不断发展&#xff0c;摄影爱好者们越来越需要一个在线平台来展示和分享他们的作品。基于SpringBoot的网上摄影工作室应运而生&#xff0c;它不仅为用户提供了一个展示摄影作品的平台&#xff0c;还为管理员提供了便捷的管理工具。本文幽络源将详细介绍该系统的…

数字人+展厅应用方案:开启全新沉浸式游览体验

随着人们生活质量的不断提升&#xff0c;对于美好体验的追求日益增长。在展厅展馆领域&#xff0c;传统的展示方式已难以满足大众日益多样化的需求。而通过将数字人与展厅进行深度结合&#xff0c;可以打造数字化、智能化新型展厅&#xff0c;不仅能提升展示效果&#xff0c;还…

基于单片机的家用无线火灾报警系统的设计

1 总体设计 本设计家用无线火灾报警系统利用单片机控制技术、传感器检测技术、GSM通信技术展开设计&#xff0c;如图2.1所示为本次系统设计的主体框图&#xff0c;系统包括单片机主控模块、温度检测模块、烟雾检测模块、按键模块、GSM通信模块、液晶显示模块、蜂鸣器报警模块。…

多级缓存(亿级并发解决方案)

多级缓存&#xff08;亿级流量&#xff08;并发&#xff09;的缓存方案&#xff09; 传统缓存的问题 传统缓存是请求到达tomcat后&#xff0c;先查询redis&#xff0c;如果未命中则查询数据库&#xff0c;问题如下&#xff1a; &#xff08;1&#xff09;请求要经过tomcat处…

iic、spi以及uart

何为总线&#xff1f; 连接多个部件的信息传输线&#xff0c;是部件共享的传输介质 总线的作用&#xff1f; 实现数据传输&#xff0c;即模块之间的通信 总线如何分类&#xff1f; 根据总线连接的外设属于内部外设还是外部外设将总线可以分为片内总线和片外总线 可分为数…

Shell编程(for循环+并发问题+while循环+流程控制语句+函数传参+函数变量+函数返回值+反向破解MD5)

本篇文章继续给大家介绍Shell编程&#xff0c;包括for循环、并发问题&#xff0c;while循环&#xff0c;流程控制语句&#xff0c;函数传参、函数变量、函数返回值&#xff0c;反向破解MD5等内容。 1.for循环 for 变量 in [取值列表] 取值列表可以是数字 字符串 变量 序列…

深入 Rollup:从入门到精通(三)Rollup CLI命令行实战

准备阶段&#xff1a;初始化项目 初始化项目&#xff0c;这里使用的是pnpm&#xff0c;也可以使用yarn或者npm # npm npm init -y # yarn yarn init -y # pnpm pnpm init安装rollup # npm npm install rollup -D # yarn yarn add rollup -D # pnpm pnpm install rollup -D在…

CycleGAN模型解读(附源码+论文)

CycleGAN 论文链接&#xff1a;Unpaired Image-to-Image Translation using Cycle-Consistent Adversarial Networks 官方链接&#xff1a;pytorch-CycleGAN-and-pix2pix 老规矩&#xff0c;先看看效果 总体流程 先简单过一遍流程&#xff0c;细节在代码里说。CycleGAN有…

线程配置经验

工作时&#xff0c;时常会遇到&#xff0c;线程相关的问题与解法&#xff0c;本人会持续对开发过程中遇到的关于线程相关的问题及解决记录更新记录在此篇博客中。 目录 一、线程基本知识 1. 线程和进程 二、问题与解法 1. 避免乘法级别数量线程并行 1&#xff09;使用线程池…

全程Kali linux---CTFshow misc入门

图片篇(基础操作) 第一题&#xff1a; ctfshow{22f1fb91fc4169f1c9411ce632a0ed8d} 第二题 解压完成后看到PNG&#xff0c;可以知道这是一张图片&#xff0c;使用mv命令或者直接右键重命名&#xff0c;修改扩展名为“PNG”即可得到flag。 ctfshow{6f66202f21ad22a2a19520cdd…

深度学习笔记——循环神经网络之LSTM

大家好&#xff0c;这里是好评笔记&#xff0c;公主号&#xff1a;Goodnote&#xff0c;专栏文章私信限时Free。本文详细介绍面试过程中可能遇到的循环神经网络LSTM知识点。 文章目录 文本特征提取的方法1. 基础方法1.1 词袋模型&#xff08;Bag of Words, BOW&#xff09;工作…

[MILP] Logical Constraints 0-1 (Note2)

1. 如果选择了项目1&#xff0c;则项目2&#xff0c;3也要求被选中 表示为&#xff1a; 2. 如果确定了选项目1&#xff0c;则接下来必须选项目2或者项目3 表示为&#xff1a; or 3. 如果同时选择了项目2和项目3&#xff0c;则不可以选择项目1 表示为&#xff1a; 4. 如果…

DFFormer实战:使用DFFormer实现图像分类任务(二)

文章目录 训练部分导入项目使用的库设置随机因子设置全局参数图像预处理与增强读取数据设置Loss设置模型设置优化器和学习率调整策略设置混合精度&#xff0c;DP多卡&#xff0c;EMA定义训练和验证函数训练函数验证函数调用训练和验证方法 运行以及结果查看测试完整的代码 在上…

如何复现o1模型,打造医疗 o1?

如何复现o1模型&#xff0c;打造医疗 o1&#xff1f; o1 树搜索一、起点&#xff1a;预训练规模触顶与「推理阶段&#xff08;Test-Time&#xff09;扩展」的动机二、Test-Time 扩展的核心思路与常见手段1. Proposer & Verifier 统一视角方法1&#xff1a;纯 Inference Sca…

【JavaWeb06】Tomcat基础入门:架构理解与基本配置指南

文章目录 &#x1f30d;一. WEB 开发❄️1. 介绍 ❄️2. BS 与 CS 开发介绍 ❄️3. JavaWeb 服务软件 &#x1f30d;二. Tomcat❄️1. Tomcat 下载和安装 ❄️2. Tomcat 启动 ❄️3. Tomcat 启动故障排除 ❄️4. Tomcat 服务中部署 WEB 应用 ❄️5. 浏览器访问 Web 服务过程详…

【NOI】C++程序结构入门之循环结构三-计数求和

文章目录 前言一、计数求和1.导入2.计数器3.累加器 二、例题讲解问题&#xff1a;1741 - 求出1~n中满足条件的数的个数和总和&#xff1f;问题&#xff1a;1002. 编程求解123...n问题&#xff1a;1004. 编程求1 * 2 * 3*...*n问题&#xff1a;1014. 编程求11/21/3...1/n问题&am…

新项目上传gitlab

Git global setup git config --global user.name “FUFANGYU” git config --global user.email “fyfucnic.cn” Create a new repository git clone gitgit.dev.arp.cn:casDs/sawrd.git cd sawrd touch README.md git add README.md git commit -m “add README” git push…