尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
REST API接口测试实战:MockMvc + JUnit 5从入门到踩坑
写后端测试这几年我越来越觉得REST API接口测试是“最容易上手、也最容易写废”的环节。MockMvc在Spring MVC项目里几乎是标配的测试工具JUnit 5解决了很多JUnit 4时代的痛点两者配合起来既能单测Controller逻辑又能验证路由、参数绑定、JSON序列化这些真实请求链路里的关键环节。如果你正在写接口测试但只停留在“跑通不报错”的阶段或者被MockMvc的API搞得一头雾水这篇内容应该能帮你把思路理清楚。这篇文章是JUnit实战系列的第十八章重点放在“下”半场REST API接口测试的MockMvc JUnit 5完整实战。我会从测试环境搭建、GET/POST/PUT/DELETE各种请求的写法到参数校验、全局异常处理、Mockito隔离外部依赖再到我实际踩过的坑逐层拆开讲。适合已经会写基础JUnit 5用例、但想把Controller层测试做得更系统和深入的开发者。1. 测试环境准备最小依赖还是完整起步依赖1.1 依赖导入与版本兼容先说依赖。Spring Boot项目里做MockMvc测试最省事的做法是引入spring-boot-starter-test这个starter会自动带进来JUnit 5、Mockito、AssertJ、JsonPath等一系列测试库不用一个个手动加版本号。从Spring Boot 2.2开始默认的JUnit平台就是JUnit 5所以如果你的项目还在用JUnit 4跑测试大概率是pom里显式排掉了junit-jupiter或者用的是老版本Spring Boot。dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency这里有一个非常重要的细节spring-boot-starter-test内部是帮你管理了依赖版本的但你如果要手动引入junit-jupiter相关依赖一定要保证版本号和Spring Boot对应的版本匹配否则会出现NoSuchMethodError这类奇怪的问题。我见过一个项目Spring Boot是2.7但JUnit手动写了5.7.0结果跑测试直接报junit.platform.launcher相关的类找不到。这种问题排查起来很费时间最简单的办法就是只用starter自动管理的版本别手动覆盖。再补充一点如果你的项目是Spring Boot 3.x那底层已经是Jakarta EE 9了包名从javax.servlet变成了jakarta.servletMockMvc的使用方式本身没变但如果你在IDE里看到导入javax.servlet.http.HttpServletRequest报错说明项目已经升级到Jakarta别把旧包名混进去。1.2 测试基类与MockMvc实例化方式MockMvc有两种常见的装配方式一种是把整个Spring上下文拉起来一种是只切Web层。我给你看两种的完整写法。第一种完整上下文测试适合做集成测试连数据库、消息队列一起验证SpringBootTest AutoConfigureMockMvc class BookApiIntegrationTest { Autowired private MockMvc mockMvc; Test void contextLoads() { // 如果能跑到这里说明上下文启动没有问题 } }第二种只切Web层Service用Mockito打桩适合做纯Controller单元测试速度极快WebMvcTest(BookController.class) class BookControllerSliceTest { Autowired private MockMvc mockMvc; MockBean private BookService bookService; Test void shouldReturnBookWhenServiceReturnsData() { // ... } }很多人刚开始会纠结选哪种。我的建议很简单如果你要测的接口不依赖外部系统或者你可以用Testcontainers把依赖都准备好那就用SpringBootTest它能验证到真实Bean装配、事务、安全配置这些如果你只想快速验证Controller层的逻辑分支比如参数解析、状态码、响应结构那用WebMvcTest省下的启动时间非常可观。我实测的项目里一个中等规模的后端服务几十个ControllerSpringBootTest启动上下文大概要8到12秒WebMvcTest只需要1到2秒。如果在CI上跑全量测试这个时间差距就很可观了。另外一个容易被忽略的点是如果你用了WebMvcTest默认不会扫描Component、Service、Repository这些Bean所以你必须在测试类里把需要的依赖全部通过MockBean或Import补齐。Controller里如果注入了RestTemplate、WebClient这类客户端也是需要手动mock的。1.3 测试基类的抽取设计我在多个项目里都建议建一个测试基类把公共的MockMvc实例、ObjectMapper实例、通用的工具方法放在里面每个模块的测试类去继承。这样做的好处是避免每个测试类都重复注入。SpringBootTest AutoConfigureMockMvc public abstract class BaseApiTest { Autowired protected MockMvc mockMvc; Autowired protected ObjectMapper objectMapper; protected String toJson(Object obj) throws JsonProcessingException { return objectMapper.writeValueAsString(obj); } }子类测试就变得很干净class BookApiTest extends BaseApiTest { Test void createBook_shouldReturnCreated() throws Exception { String json toJson(new CreateBookRequest(Spring实战, Marchi)); mockMvc.perform(post(/api/books) .contentType(MediaType.APPLICATION_JSON) .content(json)) .andExpect(status().isCreated()); } }基类里我特别推荐放一个objectMapper因为写请求体时会频繁用到每次手动new都会造成Jackson配置不一致比如LocalDateTime的序列化格式可能和项目里不同导致测试请求体和生产环境的格式对不上。最后定位半天才发现是ObjectMapper配置不一致这种坑我踩过很多次。2. 第一个MockMvc测试GET请求的三种常见玩法2.1 状态码、响应头、JSON体三层验证MockMvc和真实HTTP请求的差异在于它不经过真实的Socket端口而是在内部模拟请求分发到DispatcherServlet执行。所以你不能用它来验证网络层的问题但它足够验证从路由匹配到Controller返回值这一整条链路。最基础的GET请求测试长这样mockMvc.perform(get(/api/books/1)) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath($.title).value(深入理解REST架构)) .andExpect(jsonPath($.author).value(Marchi));我把验证拆成三层来讲第一层status().isOk()验证HTTP状态码。这里要注意isOk()对应200isCreated()对应201isNoContent()对应204。如果你的接口返回202 Accepted那就得用status().isAccepted()别用isOk()糊弄过去否则测试对状态码的约束就失效了。第二层content().contentType()验证响应头里的Content-Type。这个经常被忽略但很有用。比如一个接口应该返回application/json结果因为配置问题返回了text/plain客户端会解析失败。加上这一层断言能提前发现响应头配置错误。第三层jsonPath()验证JSON主体。这是MockMvc最强大的地方它通过JsonPath表达式从响应体里提取数据然后和你期望的值比对。2.2 jsonPath的常用表达式与动态字段处理JsonPath表达式刚开始接触会有点懵其实就是类似XPath之于XML。我常用的有这些// 取普通字段 jsonPath($.title).value(某本书) // 取嵌套字段 jsonPath($.author.name).value(Marchi) // 取数组长度 jsonPath($.tags.length()).value(3) // 取数组中的元素 jsonPath($.tags[0]).value(java) // 判断字段是否存在 jsonPath($.id).exists() // 判断字段类型 jsonPath($.price).isNumber() jsonPath($.title).isString()关于动态字段的验证比如创建接口返回的ID是数据库自增的你再怎么精确预测每次值都可能变。这时候就不要用value()去断言具体ID用exists()只验证它存在然后再单独查询一次确认数据真的落库了这样测试既有约束力又不会因为ID变化而频繁失败。还有一类场景是分页接口返回结构是{ content: [], totalPages: 2 }。你可以这样断言jsonPath($.content).isArray() jsonPath($.content.length()).value(10) jsonPath($.totalPages).value(2) jsonPath($.totalElements).value(20)2.3 请求参数与路径变量的传参方式GET接口通常有两种传参方式MockMvc对应也有两种写法。路径变量风格mockMvc.perform(get(/api/books/{id}, 1L)) .andExpect(status().isOk());这种方式要注意{id}是URI模板占位符后面的参数会自动进行字符串转换。如果传入的值带特殊字符比如/api/books/abc/def那路径就乱了。所以占位符的值最好预先做URL编码。查询参数风格mockMvc.perform(get(/api/books) .param(page, 0) .param(size, 10) .param(sort, title,asc)) .andExpect(status().isOk());.param()方法很贴心它会自动处理URL编码你不用关心?和的拼接。多个同名的参数也可以重复调用.param(tag, java).param(tag, spring)模拟出来的效果和使用方在浏览器地址栏输入?tagjavatagspring是一样的。这一点对测试多值筛选接口特别有用。还有一个细节参数里如果传的是数字MockMvc默认会转成String再绑定到Controller的RequestParam Integer page一般情况下没问题。但如果你加了Validated并且对参数做了Min、Max校验要刻意测一下越界值比如page-1应该返回400这样是对参数约束的一种回归保护。3. POST/PUT/DELETE带请求体和鉴权的接口怎么测3.1 ObjectMapper序列化与RequestBody验证POST接口和GET最大的区别在于请求体。我习惯把所有请求参数封装成独立的DTO测试时用ObjectMapper把DTO序列化成JSON字符串再放到请求体里。Test void createBook_shouldReturn201() throws Exception { CreateBookRequest req new CreateBookRequest(Spring实战, Marchi, 59.0); mockMvc.perform(post(/api/books) .contentType(MediaType.APPLICATION_JSON) .content(toJson(req))) .andExpect(status().isCreated()) .andExpect(jsonPath($.id).exists()) .andExpect(jsonPath($.title).value(Spring实战)); }这里有一个很关键的陷阱contentType必须设置为APPLICATION_JSON否则Spring MVC的RequestBody参数解析可能不会触发接口直接返回415 Unsupported Media Type。这个错误我见过太多人犯了一看到415就懵其实原因就是contentType没设置或者写成了MediaType.TEXT_PLAIN。另一个陷阱是ObjectMapper对日期和时间的序列化。如果项目里的LocalDateTime字段使用了JsonFormat(pattern yyyy-MM-dd HH:mm:ss)这种注解你在测试里new一个ObjectMapper是感知不到这些注解的序列化出来的格式可能完全不对。所以我才在前面强调测试基类里一定要注入Spring容器里的ObjectMapper它已经配置了项目里所有自定义的序列化器和注解。3.2 请求头与鉴权Token的注入很多接口有登录鉴权测试时需要带上token。这时候用.header()方法直接设置请求头简单粗暴mockMvc.perform(get(/api/books/mine) .header(HttpHeaders.AUTHORIZATION, Bearer test-token-12345)) .andExpect(status().isOk());在实际项目中我更推荐把这些公共请求头封装成工具方法避免每个测试都写一大串重复代码。比如在基类里加一个方法protected RequestBuilder authorizedGet(String url, String token) { return get(url).header(HttpHeaders.AUTHORIZATION, Bearer token); }但这里需要提醒一下如果你启用了Spring Security并且有自定义的OncePerRequestFilter解析token那MockMvc测试跑的是完整的安全链路token必须是真实可被解析的格式。有些项目会把token解析逻辑包装成返回用户信息的过滤器这种情况下光是mock一个token字符串是不够的如果SecurityContext没有认证接口会返回401。两种解决思路第一种是测试时放行安全配置通过WithMockUser来指定当前用户第二种是在测试里构造一个真正的token。WithMockUser用起来最方便Test WithMockUser(username marchi, roles ADMIN) void deleteBook_shouldReturn204() throws Exception { mockMvc.perform(delete(/api/books/1)) .andExpect(status().isNoContent()); }但要注意WithMockUser设置的是Spring Security上下文里的认证信息它并不会自动把用户信息注入到你的Controller参数里。如果你的Controller通过AuthenticationPrincipal UserDetails user这种方式获取当前用户那WithMockUser的默认值是可以匹配的。3.3 嵌套JSON与集合类型的响应断言REST API返回嵌套JSON很常见比如一个Book对象里有作者信息作者里又有出版社信息。用JsonPath逐层取就非常方便jsonPath($.author.publisher.name).value(某出版社)当然也可以取整个嵌套对象返回Map再通过equalTo进行比较。不过我更推荐JsonPath分字段断言因为一旦整个嵌套对象序列化顺序或多余字段有变化equalTo比对特别容易碎分字段断言更稳定。集合类型和响应包装也是非常常见的场景。很多公司的接口规范都会定义统一的响应包装类比如ResultT、ApiResponseT之类。测试时不要只验证$.code和$.message更要深入到$.data里的具体内容mockMvc.perform(get(/api/books?size2)) .andExpect(status().isOk()) .andExpect(jsonPath($.code).value(0)) .andExpect(jsonPath($.data.content[0].title).value(Spring实战)) .andExpect(jsonPath($.data.content[1].title).value(深入理解REST架构));这里我要强调一个容易误判的点Controller返回的如果是ResponseEntityResultListBookMockMvc的JsonPath一样可以逐层穿透。你不需要在测试里去反序列化整个响应体JsonPath天然就是为了做这类断言设计的。但如果你需要做更复杂的断言比如比较整个列表的内容ObjectMapper.readValue(...)抓取响应体转对象也是个办法。4. 参数校验与全局异常处理错误路径也要覆盖4.1 Valid触发校验失败时的响应断言接口测试只测正常路径是不够的异常和边界路径恰恰是容易出Bug的地方。Spring MVC里参数校验最常见的写法是Controller方法参数上加ValidDTO字段上加各种校验注解。PostMapping(/api/books) public ResponseEntityBook createBook(Valid RequestBody CreateBookRequest req) { // ... }测试时故意传一个非法请求体CreateBookRequest invalidReq new CreateBookRequest(, Marchi, -10.0); mockMvc.perform(post(/api/books) .contentType(MediaType.APPLICATION_JSON) .content(toJson(invalidReq))) .andExpect(status().isBadRequest()) .andExpect(jsonPath($.message).exists());这里要注意Valid校验失败后Spring默认返回400 Bad Request错误响应体结构是的DefaultHandlerExceptionResolver或ResponseEntityExceptionHandler决定的。如果你的项目里有全局异常处理器RestControllerAdvice那错误响应的格式可能完全不一样。要确认你使用的框架版本下校验失败时具体返回哪些字段不要想当然以为$.message一定存在——如果全局异常处理器把错误信息放在$.error或$.errors[0].message里你的断言就会失败。4.2 自定义异常和ControllerAdvice的测试衔接很多项目的错误处理会统一交到RestControllerAdvice比如抛出BusinessException时返回特定的错误码和HTTP状态码。测试这类逻辑时我不建议真的在测试里制造业务异常而是通过Mockito让Service层抛出异常再验证Controller的响应Test void updateBook_whenBookNotFound_shouldReturn404() throws Exception { when(bookService.updateBook(eq(100L), any(UpdateBookRequest.class))) .thenThrow(new BookNotFoundException(book not found)); mockMvc.perform(put(/api/books/100) .contentType(MediaType.APPLICATION_JSON) .content(toJson(updateReq))) .andExpect(status().isNotFound()) .andExpect(jsonPath($.code).value(BOOK_NOT_FOUND)); }这个测试验证的是从Controller抛出的异常到RestControllerAdvice兜底处理这整条链路是否正常工作。如果全局异常处理器里忘记了对BookNotFoundException的映射这个测试会直接暴露出来非常实用。4.3 404、405、500等状态码模拟MockMvc不仅可以走Controller逻辑还能模拟路由匹配不到的情况。当你请求一个不存在路径时mockMvc.perform(get(/api/nonexistent)) .andExpect(status().isNotFound());如果路径拼错了但对上了别名或者请求方法不对就可能返回405。比如接口只支持POST你用GET去请求mockMvc.perform(get(/api/books)) .andExpect(status().isMethodNotAllowed()); // 405这种测试能发现一个实际问题是否有人在设计接口时一个URL对应了多个不同的HTTP方法而框架配置没有跟上。还有一个有意思的场景是500状态码的模拟。如果你想让接口真的抛一个未捕获的RuntimeException最好克隆一个专门用来测试异常的Service实现或者用Mockito的doThrow。注意不要为了制造500而在生产代码里埋雷测试完就有意保留。我自己写错误路径测试的时候有一个原则对4xx的断言尽量具体对5xx的断言保持结构层面即可不要断言过于具体的错误文案因为异常文案很容易改动但状态码和错误码通常是稳定的接口契约。5. MockMvc Mockito把外部服务隔离掉的测试方案5.1 对Service层打桩controller测业务编排当Controller已经相对成熟重点关注的是接口层的逻辑时用Mockito打桩Service层配合WebMvcTest做Controller切片测试是我最常用的一套组合。假设BookController依赖BookServiceWebMvcTest(BookController.class) class BookControllerSliceTest { Autowired private MockMvc mockMvc; MockBean private BookService bookService; Test void getBook_shouldReturnBook() throws Exception { Book book new Book(1L, Spring实战, Marchi, 59.0); when(bookService.findById(1L)).thenReturn(book); mockMvc.perform(get(/api/books/1)) .andExpect(status().isOk()) .andExpect(jsonPath($.title).value(Spring实战)); } }这里有个必须要提醒的点MockBean是Spring Boot测试提供的注解它会向ApplicationContext里注册一个mock版本的Bean替代原有的真实Bean。所以在切片测试里你打桩的是Service接口Controller里注入的就是这个mock实例。如果你没有对某个依赖方法打桩Mockito默认返回空值null、空集合等Controller里如果对这些空值做了不必要的处理测试会马上暴露出来。5.2 verify验证调用次数防止无效刷新除了打桩返回值Mockito的verify经常用来验证交互行为。比如一个刷新接口如果某个参数为空刷新的方法就不应该被调用Test void refreshCache_whenParamMissing_shouldNotCallService() throws Exception { mockMvc.perform(post(/api/cache/refresh) .param(force, false)) .andExpect(status().isAccepted()); verify(cacheService, never()).forceRefresh(); }verify还能用来确认调用参数是否符合预期verify(bookService, times(1)).findById(1L);有时候我在测试结束前加一个verifyNoMoreInteractions来强制校验没有其他多余调用这可以避免Controller悄悄多调了一个Service方法而没人发现。不过这个用法要克制否则接口一增加新逻辑测试就碎维护成本会直线上升。5.3 避免过度mock的实践建议Mockito虽好用但过度mock会导致测试失去意义。我见过有人把REST API的测试做成了“全链路打桩”连自己内部Service方法里简单的数据处理都mock掉最后测试只证明了“Mockito会按你设置的方式返回”整个测试几乎没有验证价值。我的实践原则是三层第一如果测试目标是Controller层的参数绑定、序列化、路由、异常处理就mock掉Service层专注验证HTTP契约。第二如果测试目标是核心业务逻辑就别用MockMvc了直接写纯JUnit测试调用Service方法用内存里的Repository实现或者真实数据库Testcontainers确保业务代码真正被执行。第三如果测试目标是跨服务的流程编排那用SpringBootTest把模块内部依赖都装配成真的只mock外部RPC或者消息队列客户端。这三种测试分层的边界有时候会被混淆但只要你明确当前测试“要守护什么”mock的选择自然就清晰了。6. 排查实录我在MockMvc测试中踩过的坑6.1NoSuchMethodError与版本冲突的定位思路MockMvc JUnit 5的版本冲突问题非常经典。现象是运行时抛java.lang.NoSuchMethodError或者java.lang.NoClassDefFoundError。原因多半是Spring框架版本和Spring Boot版本不一致或者spring-test的版本和spring-webmvc对不上。我的排查思路是这样的先看spring-boot-starter-parent定义的版本再执行mvn dependency:tree检查实际落地的依赖版本。常见的一个坑是项目里自己引了老版本的spring-web导致MockHttpServletRequest或MockHttpServletResponse里缺失某些方法。6.2 jsonPath取值一直失败JsonPath报错最常见的原因是路径写错比如响应体里字段名是first_name你写成firstName或者字段是data而不是data.list。我的经验是调试时先别加太细的jsonPath断言先用简单的断言打印出响应体内容mockMvc.perform(get(/api/books)) .andDo(print());andDo(print())会把整个请求和响应体打印出来一眼就能看到实际返回的JSON结构再对着结构写JsonPath表达式就清晰了。另外有些接口返回的JSON里有$符号或者符号的字段名比如$refJsonPath会把$当作根路径处理直接取会报错。必须用jsonPath($[$ref])这种写法转义。6.3 MockMvc请求返回404、406、415状态码这三个状态码在MockMvc测试里是最常出现的我把它们的经典场景和排查方向列成一个速查表状态码典型场景排查方向404路径不存在或Controller方法未匹配检查URL是否拼错、检查RequestMapping的路径是否一致、检查测试类是否扫描到了Controller406Content-Type或Accept头不匹配检查.accept()设置的值以及ResponseBody序列化结果是否和Accept匹配415请求体格式和RequestBody不匹配检查.contentType()是否设置为APPLICATION_JSON以及JSON格式是否合法关于404还有一个坑就是WebMvcTest的场景。如果你用WebMvcTest(BookController.class)但Controller内部通过RequestMapping直接返回视图名而不是ResponseBody就可能出现404。Controller如果在类上加了Controller而没有加ResponseBody方法的返回值会被当作视图名解析测试接口路径时就会得到404或HttpMessageNotWritableException。解决办法是确保Controller返回的是ResponseEntity或者类上加了RestController。6.4 中文乱码和字符编码问题中文乱码在MockMvc测试里也经常遇到但很多时候不是测试代码的问题而是接口的Content-Type没有包含charsetUTF-8。如果接口返回的JSON是中文但响应头的Content-Type是application/json而没有charsetMockMvc在解析响应体时可能按ISO-8859-1来处理导致中文乱码。一个临时验证方法在断言前用andDo(print())看打印出来的响应体会不会是乱码。解决方法是让接口侧的MappingJackson2HttpMessageConverter正确配置UTF-8或者在测试断言时指定字符集.andExpect(content().encoding(UTF-8))在实际项目中我会在基类里为所有测试统一配置characterEncoding防止每个接口测试都重复处理中文编码问题。6.5 测试数据残留导致断言失败MockMvc测试如果连了真实数据库比如H2或者Testcontainers非常容易出现测试数据残留导致互相影响的问题。比如第一个测试插入了一个id1的书第二个测试查询时按id1能查到但后续测试断言数量时把之前的数据也算进去了导致总数对不上。我长期实践下来的方案是每个测试方法都开启事务并回滚或者至少在每个测试之前清理相关数据。Spring的Transactional用在测试方法上默认情况下测试结束会回滚这样测试之间数据不会互相污染。SpringBootTest AutoConfigureMockMvc Transactional class BookApiIntegrationTest { // ... }但要注意使用Transactional回滚的前提是测试过程中Controller和Service层的数据库操作都在同一个事务里。如果项目中用了REQUIRES_NEW传播行为或者异步线程里操作数据库回滚就失效了。这种情况下就得用数据清理策略。关于这个话题我在实际项目里还有一个常用的技巧在测试基类里统一维护一套构造测试数据的工具方法比如createBookInDb(...)然后在每个测试里用独立的ID范围或数据特征去隔离断言时也只关心自己创建的数据。最后分享一点测试重构的经验MockMvc测试如果不好好控制粒度项目一大会变得很脆弱。我见过最多的失败原因不是代码逻辑错了而是测试断言写得过于具体比如精确断言某个自增ID的具体值或者断言一个可能因为配置变化而改变的日期格式。所以我的一个原则是对核心契约状态码、业务字段、关键业务值做严格断言对易变信息自增ID、服务器时间、分页总数做存在性和类型断言。另外测试命名也值得投入一点精力。别用test1、test2这种名字我用的是方法名_场景_预期结果的格式比如createBook_titleTooLong_shouldReturn400。这样的命名在测试报告里一目了然别人接手你的代码时也能快速知道这个测试在守护什么行为。REST API的MockMvc JUnit 5测试说到底是一件需要不断打磨的事。环境配置、请求构造、响应断言每一层都有细节。等你把前面这些点都跑通了再回头看那些让新人抓狂的415、406、404基本就是一眼定位不会再被它们卡住。
RELATED

相关推荐

pandas去重实战:drop_duplicates与duplicated参数详解及避坑指南

pandas去重实战:drop_duplicates与duplicated参数详解及避坑指南

我这几年代人做数据清洗,最常听的一句话就是:"把重复的删掉就行。" 说这话的时候大家都很轻松,仿佛重复值是什么一眼就能看穿的东西。可真动手之后才发现, drop_duplicates() 一行代码确实能跑,但跑完之后…

📅 2026/10/20 23:39:38
钓鱼攻击防范指南:5类典型邮件诈骗案例与员工安全意识培训要点

钓鱼攻击防范指南:5类典型邮件诈骗案例与员工安全意识培训要点

做网络安全意识培训这几年,我开场一般会先问现场同事一个问题:如果周五下午四点,你的直属领导从企业邮箱发来一封邮件,让你马上安排一笔18.8万的付款,收款信息在附件里,还叮嘱你“合同后补、先付出去”&…

📅 2026/10/20 23:39:38
栈与队列:从原理到手写实现,彻底搞懂LIFO和FIFO

栈与队列:从原理到手写实现,彻底搞懂LIFO和FIFO

栈和队列大概是数据结构里最容易被低估的两个东西。很多人学的时候觉得“不就是先进后出和先进先出吗”,然后考试一过就扔了。但真正写过业务代码、调过性能瓶颈的人都知道,这两个结构几乎无处不在:浏览器的前进后退、编辑器的撤销重做、消息…

📅 2026/10/20 23:39:37
MORE NEWS

更多资讯

📰

语音情感识别实战:双向LSTM+MFCC全流程解析

简介:这是一套基于双向LSTM(BLSTM)的语音情感识别算法项目源码,面向从事语音识别、情感计算研究的工程师与初学者,旨在提供可直接运行的实战基线。传统方案依赖手工特征,而本项目采用BLSTM同时捕捉语音序列…

📰

NPOI v2.2.1 实战:C#/WPF 大数据量 Excel 导入导出

简介:NPOI v2.2.1是面向.NET开发者的开源Office文档处理库,支持通过C#、VB.NET等语言创建、读取和修改Word、Excel文件,无需安装Microsoft Office即可操作。它适合在WPF应用中集成数据导入导出、报表生成、邮件合并与文件转换等功能&#xff…

📰

智能工作流助手的Skill机制:从技能存放到热更新与维护的工程实践

WorkBuddy 的 Skill 机制,是我认为这套工具里最值得认真琢磨的一块。最近我们团队把它从备选工具正式扶正,从跑定时任务、收消息通知,到接提数、报表和流程审批的活,全都往上面压。用多了之后我最大的感触是:一个智能工…

📰

Python日期时间处理全指南:从time到zoneinfo,避开时区与夏令时陷阱

1. 先把"日期时间"这件事拆开:模块划分与核心认知讲真,Python 里日期时间看起来是最不需要上心的模块之一,import datetime就是一行的事。但这些年我见过太多项目栽在它手上,不是不会用,而是没搞明白这套东西…

📰

C语言指针完全指南:从内存地址到野指针与函数指针

1. 指针不是洪水猛兽:它只是一个装着地址的变量很多人学到指针就卡住了,"指针""指针变量""指向"这些词绕来绕去,越绕越晕。我身边不止一个朋友在学到这一章时直接放弃了C语言,说"指针太难了&a…

📰

C#调用OpenVINO+ByteTrack实现工业级多目标追踪

简介:本资源是一个面向C#开发者与计算机视觉初学者的实战型目标检测与追踪Demo,聚焦于在Windows平台下用C#集成OpenVINO推理引擎部署YOLOv8模型,并结合ByteTrack实现多目标实时追踪,适用于智能安防、工业巡检等需低延迟响应的边缘…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬