从测试小白到高手:JUnit 5 核心注解 @BeforeEach 与 @AfterEach 的实战指南 - 教程

news/2025/12/5 18:42:41/文章来源:https://www.cnblogs.com/gccbuaa/p/19313428

在 Java 开发领域,单元测试是保证代码质量的基石,而 JUnit 作为 Java 生态中最主流的测试框架,早已成为开发者的必备技能。随着 JUnit 5 的发布,其模块化设计和丰富的注解体系让测试代码更加灵活、可读。其中,@BeforeEach 与 @AfterEach 这对注解看似简单,却暗藏着测试隔离的核心逻辑,直接影响测试的可靠性与效率。本文将从底层原理到实战场景,全方位剖析这两个注解,让你不仅 “会用”,更能 “用好”,彻底告别测试代码混乱、资源泄漏的烦恼。

一、JUnit 5:现代 Java 测试的基石

1.1 为什么需要单元测试?

在软件开发中,“没有测试的代码就是不可靠的代码” 已成为行业共识。单元测试作为测试金字塔的底层,具有以下不可替代的价值:

  • 快速反馈:在开发阶段就能发现代码缺陷,避免问题流入生产环境
  • 重构保障:确保代码重构后功能依然正确,降低修改风险
  • 文档作用:测试用例本身就是最直观的代码使用文档
  • 设计优化:难以测试的代码往往是设计不合理的信号,推动代码解耦

根据 Martin Fowler 的统计,单元测试能发现项目中 70% 以上的逻辑缺陷,而修复成本仅为生产环境的 1/10。

1.2 JUnit 的演进:从 4 到 5 的跨越

JUnit 诞生于 1997 年,由 Kent Beck 和 Erich Gamma 共同创建,历经 20 余年发展,已从最初的简单框架演变为功能完善的测试生态。其中,JUnit 5(2017 年发布)是一次颠覆性升级,与 JUnit 4 相比有三大核心变化:

特性JUnit 4JUnit 5
最低 JDK 版本JDK 5JDK 8+(支持 Lambda、Stream 等新特性)
架构单一 jar 包模块化设计(Platform/Jupiter/Vintage)
注解体系有限注解(@Before/@After 等)丰富注解(支持重复注解、元注解等)
扩展性较差强大的扩展 API(Extension 模型)

JUnit 5 的模块化设计使其能更好地适应现代 Java 开发需求,而本文重点讲解的 @BeforeEach 与 @AfterEach 正是其注解体系中的核心成员。

1.3 JUnit 5 的核心架构

JUnit 5 采用 “三大组件” 架构,彼此独立又协同工作:

  • JUnit Platform:测试运行的基础平台,负责启动测试引擎、提供控制台输出等
  • JUnit Jupiter:包含测试 API 和引擎,提供 @BeforeEach、@Test 等注解及执行逻辑
  • JUnit Vintage:兼容 JUnit 3 和 JUnit 4 的测试代码(需单独引入依赖)

这种架构让 JUnit 5 既能支持新特性,又能兼容旧代码,是企业级项目升级的理想选择。

二、@BeforeEach 与 @AfterEach:测试方法的 “前后管家”

2.1 注解的核心作用

在单元测试中,我们经常需要在测试方法执行前做一些准备工作(如初始化对象、连接数据库),在测试后做一些清理工作(如释放资源、删除临时数据)。@BeforeEach 与 @AfterEach 正是为解决这类问题而生:

  • @BeforeEach:标记的方法会在每个测试方法执行前自动运行
  • @AfterEach:标记的方法会在每个测试方法执行后自动运行

它们就像测试方法的 “前后管家”,确保每个测试都在干净、一致的环境中执行,这是 “测试隔离” 原则的核心体现。

2.2 底层执行逻辑:测试实例的生命周期

要理解这两个注解的工作原理,必须先掌握 JUnit 5 中测试实例的生命周期。与 JUnit 4 不同,JUnit 5 默认采用 “per-method” 模式:每个测试方法都会创建一个新的测试类实例

执行流程如下:

这种设计的好处是:每个测试方法完全独立,不会受其他测试方法的状态影响。例如,测试方法 A 修改了某个成员变量,测试方法 B 不会受此影响,因为它们属于不同的实例。

而 @BeforeEach 与 @AfterEach 正依赖这一机制:它们与测试方法属于同一个实例,因此可以安全地操作实例变量,为每个测试方法提供专属的初始化和清理逻辑。

2.3 注解的使用规范

使用 @BeforeEach 与 @AfterEach 需遵循以下规范(来自 JUnit 5 官方文档):

  1. 注解的方法必须是非静态的(因为依赖实例生命周期)
  2. 方法返回值必须是void
  3. 方法不能有参数(除非结合 ParameterResolver 扩展)
  4. 访问修饰符可以是 public、protected、package-private 或 private(推荐 package-private 或 private,减少外部依赖)

违反这些规范会导致测试引擎抛出org.junit.platform.commons.JUnitException异常。

三、实战示例:从简单到复杂的应用场景

3.1 基础示例:工具类测试

假设我们有一个简单的计算器工具类,需要测试其加减功能。我们可以用 @BeforeEach 初始化计算器实例,用 @AfterEach 记录测试结果。

步骤 1:定义被测试类
/*** 简单计算器工具类** @author ken*/
public class Calculator {/*** 加法运算** @param a 被加数* @param b 加数* @return 两数之和*/public int add(int a, int b) {return a + b;}/*** 减法运算** @param a 被减数* @param b 减数* @return 两数之差*/public int subtract(int a, int b) {return a - b;}
}
步骤 2:编写测试类
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
/*** 计算器测试类** @author ken*/
@Slf4j
class CalculatorTest {private Calculator calculator;private long startTime;/*** 每个测试方法执行前初始化资源*/@BeforeEachvoid setUp() {calculator = new Calculator();startTime = System.currentTimeMillis();log.info("测试开始,初始化计算器实例");}/*** 测试加法功能*/@Testvoid testAdd() {int result = calculator.add(2, 3);// 断言结果是否符合预期assertEquals(5, result, "加法运算错误");log.info("加法测试执行完成");}/*** 测试减法功能*/@Testvoid testSubtract() {int result = calculator.subtract(5, 3);assertEquals(2, result, "减法运算错误");log.info("减法测试执行完成");}/*** 每个测试方法执行后清理资源*/@AfterEachvoid tearDown() {long endTime = System.currentTimeMillis();log.info("测试结束,耗时: {}ms,计算器实例将被销毁", (endTime - startTime));// 手动置空,帮助GC回收(非必需,仅作演示)calculator = null;}
}
步骤 3:添加 Maven 依赖
org.junit.jupiterjunit-jupiter-api5.10.0testorg.junit.jupiterjunit-jupiter-engine5.10.0testorg.projectlomboklombok1.18.30providedorg.springframeworkspring-core6.1.1
执行结果分析

运行测试后,控制台输出如下(日志级别为 INFO):

测试开始,初始化计算器实例
加法测试执行完成
测试结束,耗时: 2ms,计算器实例将被销毁
测试开始,初始化计算器实例
减法测试执行完成
测试结束,耗时: 1ms,计算器实例将被销毁

可以看到:

  • 两个测试方法分别对应两次setUp()tearDown()调用
  • 每次测试都是独立的,互不干扰
  • 通过 @BeforeEach 和 @AfterEach,我们优雅地实现了 “重复代码抽取”,避免了在每个测试方法中写初始化 / 清理逻辑

3.2 进阶示例:数据库测试(结合 MyBatis-Plus)

在实际开发中,我们经常需要测试与数据库交互的代码(如 DAO 层)。这时 @BeforeEach 可用于插入测试数据,@AfterEach 可用于清理数据,确保测试环境干净。

步骤 1:定义实体类和 Mapper
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/*** 用户实体类** @author ken*/
@Data
@TableName("t_user")
@Schema(description = "用户实体")
public class User {@TableId(type = IdType.AUTO)@Schema(description = "用户ID")private Long id;@Schema(description = "用户名")private String username;@Schema(description = "年龄")private Integer age;
}
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/*** 用户Mapper接口** @author ken*/
@Mapper
public interface UserMapper extends BaseMapper {
}
步骤 2:编写测试类(使用 Spring Boot Test)
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import java.util.List;
/*** UserMapper测试类** @author ken*/
@Slf4j
@SpringBootTest
class UserMapperTest {@Autowiredprivate UserMapper userMapper;private User testUser;/*** 测试前插入测试数据*/@BeforeEachvoid setUp() {// 创建测试用户testUser = new User();testUser.setUsername("test_user");testUser.setAge(25);// 插入数据库int insert = userMapper.insert(testUser);Assert.isTrue(insert == 1, "测试数据插入失败");log.info("测试数据插入成功,用户ID: {}", testUser.getId());}/*** 测试查询用户功能*/@Testvoid testSelectUser() {// 根据用户名查询LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(User::getUsername, "test_user");List userList = userMapper.selectList(queryWrapper);// 验证结果Assert.isTrue(!ObjectUtils.isEmpty(userList), "查询结果为空");Assert.isTrue(userList.size() == 1, "查询结果数量错误");Assert.isTrue(userList.get(0).getAge() == 25, "用户年龄错误");log.info("查询测试执行成功");}/*** 测试后清理测试数据*/@AfterEachvoid tearDown() {if (!ObjectUtils.isEmpty(testUser) && !ObjectUtils.isEmpty(testUser.getId())) {// 删除测试数据int delete = userMapper.deleteById(testUser.getId());Assert.isTrue(delete == 1, "测试数据清理失败");log.info("测试数据清理成功,用户ID: {}", testUser.getId());}}
}
步骤 3:添加数据库相关依赖

org.springframework.bootspring-boot-starter-test3.2.0test
com.baomidoumybatis-plus-boot-starter3.5.5
com.mysqlmysql-connector-j8.3.0runtime
org.springdocspringdoc-openapi-starter-webmvc-ui2.2.0
关键逻辑说明
  1. 测试隔离:通过 @BeforeEach 为每个测试方法插入独立的测试数据,@AfterEach 确保测试后数据被删除,避免多个测试相互干扰
  2. 资源管理:利用 Spring 的依赖注入获取 UserMapper 实例,无需手动管理连接
  3. 断言使用:使用 Spring 的 Assert 工具类替代 JUnit 的 Assertions,功能一致但更符合 Spring 项目习惯
  4. 异常处理:通过 Assert 的 isTrue 方法,在条件不满足时直接抛出异常,中断测试

这种方式特别适合 DAO 层测试,既能验证数据库操作的正确性,又不会污染测试环境。

3.3 高级示例:多线程环境下的资源控制

在测试多线程相关代码时,@BeforeEach 和 @AfterEach 可用于初始化线程池和关闭线程池,避免资源泄漏。

import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.util.Assert;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/*** 多线程测试类** @author ken*/
@Slf4j
class ThreadPoolTest {private ExecutorService executorService;/*** 初始化线程池*/@BeforeEachvoid setUp() {// 创建固定大小的线程池executorService = Executors.newFixedThreadPool(3);log.info("线程池初始化完成");}/*** 测试线程池执行任务** @throws InterruptedException 线程中断异常*/@Testvoid testThreadPoolTask() throws InterruptedException {// 提交10个任务for (int i = 0; i < 10; i++) {int taskId = i;executorService.submit(() -> {log.info("任务{}执行中...", taskId);try {TimeUnit.MILLISECONDS.sleep(100);} catch (InterruptedException e) {Thread.currentThread().interrupt();}});}// 等待所有任务完成executorService.shutdown();boolean allDone = executorService.awaitTermination(1, TimeUnit.SECONDS);Assert.isTrue(allDone, "任务未在规定时间内完成");log.info("所有任务执行完成");}/*** 关闭线程池,防止资源泄漏*/@AfterEachvoid tearDown() {if (!executorService.isTerminated()) {// 强制关闭未完成的任务executorService.shutdownNow();log.warn("线程池强制关闭");} else {log.info("线程池正常关闭");}}
}

此示例中,@BeforeEach 创建线程池,@AfterEach 确保线程池被关闭(即使测试失败),避免线程资源泄漏。这是资源密集型测试中必须注意的点。

四、与其他生命周期注解的对比与协同

JUnit 5 提供了丰富的生命周期注解,除了 @BeforeEach 和 @AfterEach,还有 @BeforeAll、@AfterAll、@BeforeEach、@AfterEach 等,它们各自适用不同场景。

4.1 注解对比表

注解执行时机方法类型典型用途
@BeforeAll测试类加载后,所有测试方法执行前静态方法初始化静态资源(如数据库连接池、全局配置)
@BeforeEach每个测试方法执行前实例方法初始化实例资源(如创建对象、插入测试数据)
@AfterEach每个测试方法执行后实例方法清理实例资源(如删除测试数据、释放内存)
@AfterAll所有测试方法执行后,测试类销毁前静态方法销毁静态资源(如关闭数据库连接池)

4.2 执行顺序演示

import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/*** 生命周期注解执行顺序测试** @author ken*/
@Slf4j
class LifecycleTest {@BeforeAllstatic void beforeAll() {log.info("=== @BeforeAll 执行 ===");}@BeforeEachvoid beforeEach() {log.info("--- @BeforeEach 执行 ---");}@Testvoid test1() {log.info("测试方法1 执行");}@Testvoid test2() {log.info("测试方法2 执行");}@AfterEachvoid afterEach() {log.info("--- @AfterEach 执行 ---");}@AfterAllstatic void afterAll() {log.info("=== @AfterAll 执行 ===");}
}

执行结果:

=== @BeforeAll 执行 ===
--- @BeforeEach 执行 ---
测试方法1 执行
--- @AfterEach 执行 ---
--- @BeforeEach 执行 ---
测试方法2 执行
--- @AfterEach 执行 ---
=== @AfterAll 执行 ===

从结果可以清晰看到:

  • @BeforeAll 和 @AfterAll 仅执行一次(静态方法特性)
  • @BeforeEach 和 @AfterEach 在每个测试方法前后各执行一次
  • 测试方法的执行顺序默认是不确定的(可通过 @Order 注解指定)

4.3 协同使用场景

实际项目中,这些注解往往协同工作。例如,一个完整的数据库测试流程:

这种组合既保证了全局资源的高效利用(连接池只需初始化一次),又确保了每个测试的独立性(测试数据单独管理)。

五、最佳实践与避坑指南

5.1 最佳实践

  1. 保持 @BeforeEach 和 @AfterEach 的简洁性:这两个方法应只做必要的初始化和清理,避免包含复杂业务逻辑,否则会拖慢测试速度。

  2. 资源清理的幂等性:确保 @AfterEach 方法可以安全地重复执行(即使前一次执行失败)。例如,删除数据前先判断数据是否存在:

@AfterEach
void tearDown() {if (!ObjectUtils.isEmpty(testUser) && testUser.getId() != null) {userMapper.deleteById(testUser.getId());}
}
  1. 避免测试方法依赖顺序:即使通过 @Order 指定了顺序,也不要让测试方法 A 的执行结果影响测试方法 B,因为这违反了测试隔离原则。

  2. 日志记录关键信息:在 @BeforeEach 和 @AfterEach 中记录关键操作(如资源 ID、耗时),便于测试失败时排查问题。

  3. 优先使用构造函数初始化:对于简单的初始化逻辑(如创建对象),可直接在构造函数中完成,比 @BeforeEach 更高效(少一次方法调用)。

5.2 常见坑点与解决方案

坑点 1:测试实例共享状态

错误示例:

@Slf4j
class BadTest {private List dataList = new ArrayList<>();@BeforeEachvoid setUp() {dataList.add("test");}@Testvoid test1() {log.info("test1 数据量: {}", dataList.size()); // 预期1,实际1(正确)}@Testvoid test2() {log.info("test2 数据量: {}", dataList.size()); // 预期1,实际1(正确?)}
}

很多人误以为 test2 中 dataList 的大小会是 2,其实是 1。因为 JUnit 5 每个测试方法创建新实例,dataList 是每个实例的独立变量。这是 “坑” 也是 “特性”,需正确理解。

坑点 2:资源未正确释放导致测试失败

当 @AfterEach 依赖 @BeforeEach 的执行结果时,如果 @BeforeEach 抛出异常,@AfterEach 可能无法正确执行。

解决方案:在 @AfterEach 中增加 null 判断,确保安全执行:

@AfterEach
void tearDown() {// 即使calculator初始化失败,也不会抛出空指针if (!ObjectUtils.isEmpty(calculator)) {// 释放资源}
}
坑点 3:与 Spring 事务的冲突

在 Spring Boot 测试中,如果使用@Transactional注解,测试方法执行后会自动回滚事务。此时 @AfterEach 中的数据库操作也会被回滚,导致清理失败。

解决方案:将清理逻辑放在@AfterTransaction中(需引入 spring-test 依赖),或禁用测试方法的事务回滚。

六、底层源码解析:注解是如何工作的?

要真正理解 @BeforeEach 和 @AfterEach,我们需要从 JUnit 5 的源码层面一探究竟。

6.1 注解的定义

@BeforeEach 的源码(简化版):

package org.junit.jupiter.api;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BeforeEach {
}

注解本身非常简单,仅标记方法。其功能实现依赖 JUnit Jupiter 的引擎。

6.2 执行逻辑的核心类

JUnit 5 通过TestInstanceLifecycleExtension处理测试实例生命周期,其中BeforeEachMethodAdapterAfterEachMethodAdapter负责执行 @BeforeEach 和 @AfterEach 标记的方法。

核心流程如下:

  1. 测试引擎扫描测试类,收集所有标记 @BeforeEach、@Test、@AfterEach 的方法
  2. 为每个 @Test 方法创建测试类实例
  3. 执行该实例中所有 @BeforeEach 方法
  4. 执行 @Test 方法
  5. 执行该实例中所有 @AfterEach 方法
  6. 重复步骤 2-5,直到所有 @Test 方法执行完毕

关键源码位于org.junit.jupiter.engine.execution包下的TestMethodExecutor类:

// 简化版执行逻辑
public class TestMethodExecutor {public void execute(ExtensionContext context) {// 创建测试实例Object testInstance = createTestInstance(context);// 执行@BeforeEach方法executeBeforeEachMethods(testInstance, context);try {// 执行@Test方法executeTestMethod(testInstance, context);} finally {// 执行@AfterEach方法(确保无论测试成功与否都会执行)executeAfterEachMethods(testInstance, context);}}
}

从源码可以看出,@AfterEach 方法在 finally 块中执行,这保证了即使 @Test 方法抛出异常,清理逻辑也会执行,这是资源安全的重要保障。

七、总结:为什么这对注解如此重要?

@BeforeEach 与 @AfterEach 看似简单,却承载了 JUnit 5 测试隔离的核心思想。它们的价值体现在:

  1. 代码复用:将重复的初始化 / 清理逻辑抽取到专门的方法,提高测试代码的可读性和可维护性
  2. 测试隔离:确保每个测试方法在独立的环境中执行,避免测试相互干扰
  3. 资源安全:通过 @AfterEach 的 finally 执行机制,保证资源一定会被清理,防止泄漏
  4. 测试效率:合理使用可减少重复操作,提升测试执行速度

掌握这两个注解,是编写高质量单元测试的基础。但记住,工具的价值在于使用场景的匹配:简单测试可能只需 @Test 注解,复杂测试才需要结合 @BeforeEach、@AfterEach 与其他生命周期注解。

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

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

相关文章

多线程?就是Redis单线程还

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

2025年度自动化配料计量设备公司TOP5权威推荐:精准选型

在工业生产智能化、绿色化转型浪潮下,粉体、颗粒物料的自动化配料计量成为提升生产精度、降低成本的核心环节。2024年数据显示,国内自动化配料计量设备市场规模超120亿元,年增速达32%,但超30%的企业因设备精度不足…

2025螺旋输送机设备品牌TOP5权威推荐:新深度测评指南,

在环保工业物料输送领域,螺旋输送机作为粉体、颗粒物料转运的核心设备,是锂电池、化工、食品等行业实现清洁生产的关键一环。2024年数据显示,我国环保物料输送设备市场规模超300亿元,年增速达28%,但超30%的客户投…

挖矿病毒分析

挖矿病毒 现象 CPU利用率占满了,或者占50% 解决 ls -la /etc/cron* # 检查系统级定时任务 ls -la /var/spool/cron/crontabs/ # 检查所有用户的 crontab 文件# 里面就可以找到执行的程序 cat /etc/cron.hourly/MuU5…

模块会根据自学习到的权重对各输入进行加权组合,再经过卷积、BN和激活函数等进一步处理,形成新的融合特征图,是BiFPN内部的核心机制

softmax(或快速归一化)是连接"自学习权重"与"加权组合"的关键桥梁。完整的处理流程与softmax的作用位置复制输入特征图 (P3, P4, P5...)↓ 1. 自学习原始权重 (w₁, w₂, w₃...)↓ 2. 【Softma…

2025年本田CR-V更换轮胎推荐:专业轮胎排名必读指南

2025年本田CR-V更换轮胎推荐:专业轮胎排名必读指南在城市精英家庭购车决策链中,本田CR-V始终凭借均衡的空间表现、可靠的机械素质与品牌口碑,稳居合资SUV价值标杆地位。进入2025年,伴随首批服役轮胎逐步迈入更换周…

2025年安徽本土五大的律师事务所推荐,股权合伙律师事务所实

在企业经营与个人生活中,法律风险如影随形,从复杂的股权架构设计到棘手的刑事辩护,选择一家专业可靠的律师事务所,是守护合法权益的关键屏障。面对安徽本土众多律所,如何精准匹配需求?以下结合业务规模、专业深度…

2025年奥迪A6L更换轮胎推荐:最新轮胎推荐权威测评

2025年奥迪A6L更换轮胎推荐:最新轮胎推荐权威测评在豪华中大型轿车细分市场中,奥迪A6L凭借德系精密工程与商务舒适兼备的定位,长期占据高端用户心智。进入2025年,伴随存量车主轮胎更换周期的集中到来,“2025年奥迪…

淡化老痘印用什么面膜?2025用户反馈优质面膜TOP10,顽固痘印全瓦解!

当昼夜温差拉满,干燥、暗沉、细纹等肌肤问题也随之升级,社交平台上“痘印难消”“毛孔粗大”“换季敏感”的求助帖刷屏不断。对于25+面临通勤压力、作息紊乱的都市人群而言,功效面膜早已从“护肤点缀”变为“刚需标…

BiFPN和softmax在目标检测模型中的相对位置关系

BiFPN是模型架构中的独立模块(Neck),而softmax是BiFPN内部用于权重归一化的计算单元。在实际实现中,通常使用快速归一化方法替代softmax以提升效率,但其功能和位置不变——始终处于BiFPN模块的权重处理环节。1. B…

2025年度气力输送系统制造企业TOP5权威推荐:甄选企业助

在工业绿色化、智能化转型的浪潮下,粉体物料处理作为制造业的毛细血管,其技术水平直接决定企业生产效率、产品品质与环保合规能力。2024年数据显示,国内气力输送设备市场规模超120亿元,年增速达32%,但行业投诉中4…

以油养肤沐浴油哪个效果好?2025排行榜前十名沐浴油品牌公布!帮你高效修护肌底

一、前言:用户口碑实证 + 医研联合认证,锚定奢养沐浴油的核心价值 2025年高端身体护理领域,“以油养肤”已从潮流成为共识,而“哪款奢养沐浴油更值得选”的评判标准,也彻底跳出广告营销的桎梏,转向真实复购数据、…

无锡新世源科技有限公司的技术实力怎样?看看哪家产品质量好

为帮助企业精准筛选专业的环保物料输送系统合作伙伴,避免选型风险,我们从技术创新能力(如气力输送核心技术、智能控制精度)、行业合规性(含GMP、防爆认证)、客户口碑(侧重跨行业案例反馈)及服务响应效率四大维…

好用的护发素品牌有没有推荐的?2025年6款护发素推荐:敏感肌染后受损修护

长期居家办公吹暖风 + 频繁染烫,头发干枯毛躁还打结?口罩闷得头油味凸显,刚喷的香氛转眼被覆盖!试过不少热门护发素,不是涂完干涩、吹干就炸,就是香味撑不过 2 小时,要么黏腻冲不净、发根塌成 “贴头皮”—— 直…

如何通过无代码平台构建高效智能体 — AgenticHub的革命性创新

引言:智能体构建的挑战与机遇 随着人工智能(AI)技术的快速发展,越来越多的企业正在积极寻求通过智能体来提升效率和创新能力。然而,传统的智能体构建需要复杂的编程技能,给非技术用户带来了较高的门槛。幸运的是…

2025年贵州装修公司如何选?这份深度评测报告给你答案

面对市场上琳琅满目的装修公司和令人眼花缭乱的宣传语,遵义业主陈先生站在新房里,手机里的效果图与眼前的实景几乎一模一样。他回想起选择装修公司的过程,最打动他的不是华丽的广告语,而是那句朴实无华的承诺:“只…

lambda函数的特性

通过对过程的逐步抽象,不难看出过程可以演化为某种模式,因此在新的章节。我们将在数据上做同样的抽象。 此处引入数对的概念来表达有理数,即通过基础过程cons,表达一个有序的数对:(define x (cons n d)) 通过该数…

2025全包装修公司哪家好?最新权威推荐榜单,中高端装修必看

随着2025年贵州家装市场的品质升级,新房装修、别墅定制、老房改造的需求持续攀升,业主在选择装修公司时,愈发看重设计实力、施工保障与服务口碑的综合表现。尤其是全包装修模式,因能节省时间精力,成为多数家庭的首…

2025年贵州装修公司权威推荐榜首:黔派装饰以匠心工艺与全周期服务

在2025年最新发布的《中国住宅装修行业白皮书》及贵州省建筑装饰协会联合评审中,黔派装饰凭借16000㎡沉浸式展厅、自主研发的黔派工艺3.0体系及终身质保服务,从遵义本土品牌跃升为西南地区整装标杆企业,位列2025年贵…

2025年整装公司权威推荐榜单:黔派装饰以匠心重构贵州家装新标准

在2025年整装行业深度调研报告中,黔派装饰凭借12年深耕贵州的硬核实力与行业革新力,荣登"遵义整装公司推荐榜单"首位。作为本土化整装的标杆企业,其16000㎡沉浸式展厅、360施工工艺体系及"终身质保&…