springboot系列--web相关知识探索三

ops/2025/2/12 1:54:36/

一、前言

web相关知识探索二中研究了请求是如何映射到具体接口(方法)中的,本次文章主要研究请求中所带的参数是如何映射到接口参数中的,也即请求参数如何与接口参数绑定。主要有四种、分别是注解方式、Servlet API方式、复杂参数、以及自定义对象参数。本次主要研究注解方式以及Servlet API方式。

二、 注解方式

接口参数绑定主要涉及的一下注解:

@PathVariable、@RequestHeader、@ModelAttribute、@RequestParam、@CookieValue、@RequestBody、@MatrixVariable

其中@MatrixVariable矩阵变量注解实际开发中基本不用,跳过研究。主要研究一下springmvc是如何将请求中的参数绑定到下面一个个注解修饰的参数中的。

一、测试用例 

@RestController
public class MvcTestController {/**** @param id* @param name* @param pv 这个map可以接受url上的所有值,*           key是id;value:url路径上的值*           key是name;value:* @param userAgent* @param header* @param age* @param list* @param params* @param _ga* @param cookie* @return*/@GetMapping("/demo/{id}/test/{name}")public Map<String,Object> test(@PathVariable("id") Integer id,@PathVariable("name") String name,@PathVariable Map<String,String> pv,@RequestHeader("User-Agent") String userAgent,@RequestHeader Map<String,String> header,@RequestParam("age") Integer age,@RequestParam("list") List<String> list,@RequestParam Map<String,String> params,@CookieValue("_ga") String _ga,@CookieValue("_ga") Cookie cookie){Map<String,Object> map = new HashMap<>();map.put("id",id);map.put("name",name);map.put("pv",pv);map.put("userAgent",userAgent);map.put("headers",header);map.put("age",age);map.put("list",list);map.put("params",params);map.put("_ga",_ga);return map;}@GetMapping("/test/servlet")public Map testServlet(HttpServletRequest request){Map<String,Object> map = new HashMap<>();map.put("request",request);return map;}@PostMapping("/test/method")public Map testPostMethod(@RequestBody String content){Map<String,Object> map = new HashMap<>();map.put("content",content);return map;}
}

首先,请求进来统一是有前端控制器(中央处理器),也即是DispatcherServlet这个类进行处理。然后中央处理器就会去调用处理器映射器,也即是handlerMapping,handlerMapping通过请求方式以及请求路径找到匹配的handler(也就是我们常说的接口,也是具体的处理方法),之后DispatcherServlet调用处理器适配器,也就是handlerAdatper根据具体的handler规则去执行对应的handler。

二·、原理

get请求:http://localhost:8080/demo/1/test/zhangsan,进入到上一章研究到的,通过handlerMapping获取到具体的handler地方。

1、HandlerMapping中找到能处理请求的Handler(Controller.method())

2、然后为当前Handler 找一个适配器 HandlerAdapter,这个适配器一般就是RequestMappingHandlerAdapter

3、适配器执行目标方法并确定方法参数的每一个值

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {try {ModelAndView mv = null;Exception dispatchException = null;try {processedRequest = this.checkMultipart(request);multipartRequestParsed = processedRequest != request;// 这里就是上一张研究到获取合适的handler的地方mappedHandler = this.getHandler(processedRequest);if (mappedHandler == null) {this.noHandlerFound(processedRequest, response);return;}
// 这里就是获取到合适的处理器适配器的地方,源码在下方HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());String method = request.getMethod();boolean isGet = "GET".equals(method);if (isGet || "HEAD".equals(method)) {long lastModified = ha.getLastModified(request, mappedHandler.getHandler());if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {return;}}if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// 这里是利用处理器适配器执行具体的handler     mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}this.applyDefaultViewName(processedRequest, mv);mappedHandler.applyPostHandle(processedRequest, response, mv);} catch (Exception var20) {dispatchException = var20;} catch (Throwable var21) {dispatchException = new NestedServletException("Handler dispatch failed", var21);}this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);} catch (Exception var22) {this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);} catch (Throwable var23) {this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));}} finally {if (asyncManager.isConcurrentHandlingStarted()) {if (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}} else if (multipartRequestParsed) {this.cleanupMultipart(processedRequest);}}
}

一、找到合适的处理器适配器 

// 找到合适的HandlerAdapter 
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {// 遍历所有的处理器适配器,直到找到合适的 ,总共会有4种   if (this.handlerAdapters != null) {Iterator var2 = this.handlerAdapters.iterator();while(var2.hasNext()) {HandlerAdapter adapter = (HandlerAdapter)var2.next();// 这里的handler刚好就是HandlerMethod 对象,具体可以查看上一张获取handler。if (adapter.supports(handler)) {return adapter;}}}throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");}// 判断是否支持当前handler,public final boolean supports(Object handler) {return handler instanceof HandlerMethod && this.supportsInternal((HandlerMethod)handler);}

 

0--支持方法上标注@RequestMapping、@GetMpping等注解的处理器适配器

1--支持函数式编程的

 二、利用处理器适配器执行具体的handler

从mv = ha.handle(processedRequest, response, mappedHandler.getHandler());方法进入

 

 一、参数解析器--HandlerMethodArgumentResolver

1、参数解析器主要是确定将要执行的目标方法的每一个参数的值是什么

2、SpringMVC目标方法能写多少种参数类型。取决于参数解析器

例如:目标方法(controller中的方法,也即是具体接口)使用了@RequestParam注解,springmvc就会使用RequestParamMethodArgumentResolvers这个参数解析器进行解析,,其他注解同理,会用其他解析器解析。

// 这是参数解析器接口,具体实现有27种,
public interface HandlerMethodArgumentResolver {// 当前解析器是否支持解析这种参数boolean supportsParameter(MethodParameter var1);// 支持就调用 resolveArgument@NullableObject resolveArgument(MethodParameter var1, @Nullable ModelAndViewContainer var2, NativeWebRequest var3, @Nullable WebDataBinderFactory var4) throws Exception;
}
二、返回值处理器--HandlerMethodReturnValueHandler

返回值处理器主要就是处理返回值类型的,有多少种返回值处理器就能处理多少种返回值类型。这期主要研究参数解析器,返回值处理器另外再研究。

public interface HandlerMethodReturnValueHandler {boolean supportsReturnType(MethodParameter var1);void handleReturnValue(@Nullable Object var1, MethodParameter var2, ModelAndViewContainer var3, NativeWebRequest var4) throws Exception;
}
 三、将参数解析器、返回值处理器包装到执行handler对象中
@Nullableprotected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ServletWebRequest webRequest = new ServletWebRequest(request, response);ModelAndView var15;try {WebDataBinderFactory binderFactory = this.getDataBinderFactory(handlerMethod);ModelFactory modelFactory = this.getModelFactory(handlerMethod, binderFactory);ServletInvocableHandlerMethod invocableMethod = this.createInvocableHandlerMethod(handlerMethod);if (this.argumentResolvers != null) {// invocableMethod这个对象就是执行具体的handler的,把参数解析器包装进去
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);}
// invocableMethod这个对象就是执行具体的handler的,把返回值处理器包装进去if (this.returnValueHandlers != null) {invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);}invocableMethod.setDataBinderFactory(binderFactory);invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);ModelAndViewContainer mavContainer = new ModelAndViewContainer();mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));modelFactory.initModel(webRequest, mavContainer, invocableMethod);mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);asyncWebRequest.setTimeout(this.asyncRequestTimeout);WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);asyncManager.setTaskExecutor(this.taskExecutor);asyncManager.setAsyncWebRequest(asyncWebRequest);asyncManager.registerCallableInterceptors(this.callableInterceptors);asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);Object result;if (asyncManager.hasConcurrentResult()) {result = asyncManager.getConcurrentResult();mavContainer = (ModelAndViewContainer)asyncManager.getConcurrentResultContext()[0];asyncManager.clearConcurrentResult();LogFormatUtils.traceDebug(this.logger, (traceOn) -> {String formatted = LogFormatUtils.formatValue(result, !traceOn);return "Resume with async result [" + formatted + "]";});// 执行具体的handler,也就是controller中的接口方法invocableMethod = invocableMethod.wrapConcurrentResult(result);}invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);if (asyncManager.isConcurrentHandlingStarted()) {result = null;return (ModelAndView)result;}var15 = this.getModelAndView(mavContainer, modelFactory, webRequest);} finally {webRequest.requestCompleted();}return var15;}

四、真正执行目标方法

 真正执行目标方法是在这个类ServletInvocableHandlerMethod里面的invokeAndHandle方法里的       

Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);这个方法

真正执行目标方法有三个重要的步骤:

1、将目标方法中的参数与请求进来的参数进行绑定以及判断是否能否绑定等等。也即是对请求参数进行解析,这里就需要用到参数解析器了

2、通过反射调用目标方法进行执行

3、将目标方法执行后的数据绑定,也就是对返回值进行处理,这里就用到返回值处理器了

    public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {//这里是真正执行目标方法,执行这个方法,就会到controller里面的接口方法,下图可见Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);this.setResponseStatus(webRequest);if (returnValue == null) {if (this.isRequestNotModified(webRequest) || this.getResponseStatus() != null || mavContainer.isRequestHandled()) {this.disableContentCachingIfNecessary(webRequest);mavContainer.setRequestHandled(true);return;}} else if (StringUtils.hasText(this.getResponseStatusReason())) {mavContainer.setRequestHandled(true);return;}mavContainer.setRequestHandled(false);Assert.state(this.returnValueHandlers != null, "No return value handlers");try {this.returnValueHandlers.handleReturnValue(returnValue, this.getReturnValueType(returnValue), mavContainer, webRequest);} catch (Exception var6) {if (logger.isTraceEnabled()) {logger.trace(this.formatErrorForReturnValue(returnValue), var6);}throw var6;}}

// 执行目标方法源码       
@Nullablepublic Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {// 这里就是将请求参数与目标方法中的参数进行绑定Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);if (logger.isTraceEnabled()) {logger.trace("Arguments: " + Arrays.toString(args));}// 通过反射执行目标方法return this.doInvoke(args);}

一、将请求参数与目标方法上的参数进行绑定

1、判断是否支持解析当前参数,如果有一个不支持就会报错

// 这段代码主要就是确定  controllerr中具体接口方法上的参数的值。也就是把请求参数的值与目标方法参数进行绑定protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {// 这里就是获取controllerr中具体接口方法上的参数、以及对应的类型,参数的位置等等,也就是获取参数声明信息MethodParameter[] parameters = this.getMethodParameters();// 如果目标方法没有参数,也就不需要绑定,直接返回if (ObjectUtils.isEmpty(parameters)) {return EMPTY_ARGS;} else {// 创建一个数组,长度是目标方法参数的个数Object[] args = new Object[parameters.length];// 循环遍历目标方法参数for(int i = 0; i < parameters.length; ++i) {MethodParameter parameter = parameters[i];parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);args[i] = findProvidedArgument(parameter, providedArgs);if (args[i] == null) {// 判断当前解析器是否支持当前接口方法中的这个参数类型if (!this.resolvers.supportsParameter(parameter)) {throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));}try {// 这里将进行参数解析,解析绑定后,将进行下一个参数的解析与绑定args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);} catch (Exception var10) {if (logger.isDebugEnabled()) {String exMsg = var10.getMessage();if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {logger.debug(formatArgumentError(parameter, exMsg));}}throw var10;}}}return args;}}// 判断解析器是否支持当前参数类型的解析,解析器上面有讲过,有27中解析器public boolean supportsParameter(MethodParameter parameter) {return this.getArgumentResolver(parameter) != null;}@Nullableprivate HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {// this.argumentResolverCache刚进来的时候是空的HandlerMethodArgumentResolver result = (HandlerMethodArgumentResolver)this.argumentResolverCache.get(parameter);if (result == null) {// 第一次是空的,会进到这里,然后	挨个遍历27个解析器Iterator var3 = this.argumentResolvers.iterator();while(var3.hasNext()) {HandlerMethodArgumentResolver resolver = (HandlerMethodArgumentResolver)var3.next();// 这里判断是否支持,具体源码在下方if (resolver.supportsParameter(parameter)) {result = resolver;// 解析出来后放入本地缓存中,下次进来就不用再判断了this.argumentResolverCache.put(parameter, resolver);break;}}}return result;}// 判断解析器是否支持当前参数解析,这里只拿PathVariable注解解析器源码举例public boolean supportsParameter(MethodParameter parameter) {// 判断当前参数是否使用了PathVariable注解,没使用就不支持解析if (!parameter.hasParameterAnnotation(PathVariable.class)) {return false;// 到了这里就说明当前参数一定是PathVariable注解修饰的,然后判断是不是map,如果不是map那么就能够支持} else if (!Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {return true;} else {// 到这里就说明参数是map类型的,然后还需要做其他判断,也就是PathVariable注解中的value一定要有值,如果没有值,就说明这个注解的map想要全部接受url// 上的参数,这种就需要用另外一个解析器进行处理了,如果有值就说明url上的参数是map类型的,名字是PathVariable上的value属性的值。然后会将这个mapp参数值映射到// 这个参数上PathVariable pathVariable = (PathVariable)parameter.getParameterAnnotation(PathVariable.class);return pathVariable != null && StringUtils.hasText(pathVariable.value());}}

2、通过了参数解析判断后,将进行真正的参数解析。 

   // 这里主要是进行参数解析这个方法是在HandlerMethodArgumentResolverComposite这个类下@Nullablepublic Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {// 拿到所有参数解析器        HandlerMethodArgumentResolver resolver = this.getArgumentResolver(parameter);if (resolver == null) {throw new IllegalArgumentException("Unsupported parameter type [" + parameter.getParameterType().getName() + "]. supportsParameter should be called first.");} else {// 调用参数解析器,解析参数的方法return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);}}// 这里是拿到这个参数对应的解析器,例如是@PathVariable注解修饰的参数,那就会拿到@PathVariable的参数解析器@Nullableprivate HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {// 这里首先会从缓存中获取,由于之前在做判断是否支持这种类型的参数时,有做过缓存处理,所以这里可以直接获取到这个参数类型对应的参数解析器HandlerMethodArgumentResolver result = (HandlerMethodArgumentResolver)this.argumentResolverCache.get(parameter);if (result == null) {Iterator var3 = this.argumentResolvers.iterator();while(var3.hasNext()) {HandlerMethodArgumentResolver resolver = (HandlerMethodArgumentResolver)var3.next();if (resolver.supportsParameter(parameter)) {result = resolver;this.argumentResolverCache.put(parameter, resolver);break;}}}return result;}// 真正开始解析参数,这个方法是在AbstractNamedValueMethodArgumentResolver这个类里面,应该是一个公共方法,类似于模板方法的公共方法,处理公共逻辑@Nullablepublic final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {// 这里将注解信息封装成一个对象,例如注解里标识这个参数以什么样的名字绑定,是否是必须得,以及默认值。例如:@PathVariable(value = "id",required = false)这里面的属性NamedValueInfo namedValueInfo = this.getNamedValueInfo(parameter);MethodParameter nestedParameter = parameter.nestedIfOptional();// 这里就是从封装对象解析出接口方法要绑定的名字,例如这个注解@PathVariable("id"),解析出来就是id这个名字Object resolvedName = this.resolveEmbeddedValuesAndExpressions(namedValueInfo.name);if (resolvedName == null) {throw new IllegalArgumentException("Specified name must not resolve to null: [" + namedValueInfo.name + "]");} else {// 这里开始将url上对应的值与id这个名字进行绑定,也即是对id进行赋值,这里将参数解析绑定后,下面逻辑可以不用看了Object arg = this.resolveName(resolvedName.toString(), nestedParameter, webRequest);if (arg == null) {if (namedValueInfo.defaultValue != null) {arg = this.resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);} else if (namedValueInfo.required && !nestedParameter.isOptional()) {this.handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);}arg = this.handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());} else if ("".equals(arg) && namedValueInfo.defaultValue != null) {arg = this.resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);}if (binderFactory != null) {WebDataBinder binder = binderFactory.createBinder(webRequest, (Object)null, namedValueInfo.name);try {arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);} catch (ConversionNotSupportedException var11) {throw new MethodArgumentConversionNotSupportedException(arg, var11.getRequiredType(), namedValueInfo.name, parameter, var11.getCause());} catch (TypeMismatchException var12) {throw new MethodArgumentTypeMismatchException(arg, var12.getRequiredType(), namedValueInfo.name, parameter, var12.getCause());}if (arg == null && namedValueInfo.defaultValue == null && namedValueInfo.required && !nestedParameter.isOptional()) {this.handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);}}this.handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);return arg;}}// 获取参数注解信息private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {// 先从缓存中获取NamedValueInfo namedValueInfo = (NamedValueInfo)this.namedValueInfoCache.get(parameter);if (namedValueInfo == null) {// 这个方法是从注解中获取注解属性信息namedValueInfo = this.createNamedValueInfo(parameter);// 这里其实就是重新更新一下namedValueInfo值中的defaultValue属性。namedValueInfo = this.updateNamedValueInfo(parameter, namedValueInfo);this.namedValueInfoCache.put(parameter, namedValueInfo);}return namedValueInfo;}// 从注解中获取注解属性信息,拿PathVariable注解举例protected AbstractNamedValueMethodArgumentResolver.NamedValueInfo createNamedValueInfo(MethodParameter parameter) {// 这里获取的是一个注解PathVariable ann = (PathVariable)parameter.getParameterAnnotation(PathVariable.class);Assert.state(ann != null, "No PathVariable annotation");// 这里会把注解传入这个对象,里面会把属性保存到这个对象中return new PathVariableNamedValueInfo(ann);}// 把属性保存到这个对象中private static class PathVariableNamedValueInfo extends AbstractNamedValueMethodArgumentResolver.NamedValueInfo {public PathVariableNamedValueInfo(PathVariable annotation) {// 父类NamedValueInfosuper(annotation.name(), annotation.required(), "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n");}}protected static class NamedValueInfo {private final String name;private final boolean required;@Nullableprivate final String defaultValue;public NamedValueInfo(String name, boolean required, @Nullable String defaultValue) {this.name = name;this.required = required;this.defaultValue = defaultValue;}}// 这里是对id进行赋值的源码,name:就是接口方法中使用注解属性的值,也就是@PathVariable("id")里面的id@Nullableprotected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {/***HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE的值为org.springframework.web.servlet.HandlerMapping.uriTemplateVariables*request请求信息里面在org.springframework.web.servlet.HandlerMapping.uriTemplateVariables这个key下,保存了url上的请求参数*从请求中获取到数据后,保存在map当中,其中key为:注解属性的id,value为请求携带的值,就拿@PathVariable("id"),这个注解来说吧,key-id,value-请求请来的值。(@GetMapping("/demo/{id}/test/{name}")是从这里面取到的id、name为key)这里是由于请求一进来会有一个UrlPathHelper类里面的方法将url里面的路径变量全部解析出来*然后提前保存到请求域当中。所以uriTemplateVars是请求域中的值,但是并未绑定到接口参数上,*/Map<String, String> uriTemplateVars = (Map)request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, 0);// 这里就是从请求域中的值里面获取到id对应的value        return uriTemplateVars != null ? uriTemplateVars.get(name) : null;}

 

三、Servlet API方式 

当我们的接口参数是WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId这些类型时,springmvc会有专门的处理器进行处理。也就是ServletRequestMethodArgumentResolver 这个处理器。主要源码如下。

@Overridepublic boolean supportsParameter(MethodParameter parameter) {Class<?> paramType = parameter.getParameterType();return (WebRequest.class.isAssignableFrom(paramType) ||ServletRequest.class.isAssignableFrom(paramType) ||MultipartRequest.class.isAssignableFrom(paramType) ||HttpSession.class.isAssignableFrom(paramType) ||(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||Principal.class.isAssignableFrom(paramType) ||InputStream.class.isAssignableFrom(paramType) ||Reader.class.isAssignableFrom(paramType) ||HttpMethod.class == paramType ||Locale.class == paramType ||TimeZone.class == paramType ||ZoneId.class == paramType);}

一、判断是否支持HttpservletRequest类型参数

 

 二、解析出参数


http://www.ppmy.cn/ops/124098.html

相关文章

数据结构--List的介绍

目录 1. 什么是List Collection中有那些方法&#xff1f; add(E e)方法 addAll(Collection c)方法 clear()方法 contains(Object o)方法 containsAll(Collection c)方法 equals(Object o)方法 hashCode()方法 isEmpty()方法 iterator()方法 remove(Object o)方法 …

【最新华为OD机试E卷-支持在线评测】补种未成活胡杨(100分)多语言题解-(Python/C/JavaScript/Java/Cpp)

🍭 大家好这里是春秋招笔试突围 ,一枚热爱算法的程序员 💻 ACM金牌🏅️团队 | 大厂实习经历 | 多年算法竞赛经历 ✨ 本系列打算持续跟新华为OD-E/D卷的多语言AC题解 🧩 大部分包含 Python / C / Javascript / Java / Cpp 多语言代码 👏 感谢大家的订阅➕ 和 喜欢�…

Java之反射

目录 反射 定义 主要用途 反射相关的类 Class类中【获得类相关方法】 Class类中【获得类中属性相关的方法】 Class类中【获得类中注解相关的方法】 Class类中【获得类中构造器相关的方法】 Class类中【获得类中方法相关的方法】 获得Class对象 代码示例1 代码示例…

python 常用关键字

关键字作用示例and逻辑与操作符&#xff0c;当左右两边的表达式都为真时&#xff0c;结果为真。if a > 0 and b < 0: print("a is positive and b is negative")as用于给导入的模块指定别名。import os as operating_systemassert断言&#xff0c;用于调试&…

提升音视频应用流畅度的关键:网络流量分析与监控

目录 音视频应用对网络连接质量的要求 通过网络流量分析工具提升音视频传输的流畅性 主要优势 结语 在当前高度依赖网络的时代&#xff0c;无论是在线会议、远程教育&#xff0c;还是高清视频流媒体播放&#xff0c;网络连接的质量直接影响用户体验。特别是在音视频应用中&…

机器学习中的多模态学习:用C/C++实现高效模型

引言 多模态学习&#xff08;Multimodal Learning&#xff09;是一种机器学习技术&#xff0c;它旨在整合多种数据类型&#xff08;例如图像、文本、音频、传感器数据等&#xff09;来提升模型的预测精度和泛化能力。其应用领域包括情感分析、多模态推荐系统、智能驾驶、语音识…

009——二叉树

目录 二叉树的五种基本形态&#xff1a; 1.二叉树可以是空树 2.只有一个根节点的树 3.斜树&#xff1a;只有左子树或右子树的树 4.左右孩子都有的树 二叉树的性质&#xff1a; 1.假设根节点是第一层&#xff0c;在二叉树的第i层上最多有2^(n-1)个结点 2.深度为k的二叉树…

【docker笔记8-镜像推送】

docker笔记8-镜像推送 一、基本命令二、案例1.Java demo2.打包镜像 一、基本命令 &#xff08;1&#xff09;推送镜像到远程仓库 docker tag local-image:tagname new-repo:tagname docker push new-repo:tagname这里首先要登录到docker&#xff0c;然后需要输入登录用户名和…