当前位置: 首页 > news >正文

实战 - 利用 ThreadLocal 线程局部变量实现数据缓存

文章目录

    • 1. 利用 ThreadLocal 缓存 AssetBranchCache 数据
      • 1. 定义 AssetBranchCache 类
      • 2. 定义 BranchContext 类操作 AssetBranchCache 对象
      • 3. 配置拦截器实时更新和清除缓存数据
      • 4. 定义 SaasThreadContextDataHolderBranch 类持有 AssetBranchCache 对象
      • 5. 定义 SaasThreadContextHolder 接口
      • 6. 定义 SaasThreadContextHolderBranch 组件
      • 7. 定义 SaasThreadContextUtil 工具类
      • 8. 定义 BranchNameCache 组件获取 AssetBranchCache 数据
    • 2. 业务使用
    • 1. 请求入口 IncidentController
      • 1. 请求参数的封装
      • 2. 响应实体的封装
    • 2. 获取延迟加载数据 IEventDelayLoadService

业务逻辑:安全事件,安全告警,风险主机列表页面会需要一些延迟加载数据,因此当我们进入这个页面时就会请求该接口获取延迟加载数据。

1. 利用 ThreadLocal 缓存 AssetBranchCache 数据

ThreadLocal 是 Java 中的一个类,它提供了一种线程局部变量的机制。线程局部变量是指只能被同一个线程访问和修改的变量,不同线程之间互不干扰。ThreadLocal 可以用来解决多线程并发访问共享变量的问题。

ThreadLocal可以用来实现数据缓存,即在一个线程中缓存一些上下文相关的数据,以便在该线程的后续操作中使用。在使用ThreadLocal实现上下文缓存时,可以将需要缓存的数据存储在ThreadLocal对象中,然后在需要使用这些数据的地方,通过ThreadLocal对象获取数据。由于每个线程都有自己的ThreadLocal对象,因此不同线程之间的数据不会相互干扰,从而保证了线程安全性。

1. 定义 AssetBranchCache 类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class AssetBranchCache {private UnmodifiableMap<Integer, String> idToNameMap;
}

2. 定义 BranchContext 类操作 AssetBranchCache 对象

BranchContext 类定义了一个静态的 ThreadLocal 变量 BRANCH_CACHE_THREAD_LOCAL,用于存储当前线程的 AssetBranchCache 对象:

public class BranchContext {private static final ThreadLocal<@Nullable AssetBranchCache> BRANCH_CACHE_THREAD_LOCAL = new TransmittableThreadLocal<>();/*** 设置 assetBranchCache*/public static void load(@Nullable AssetBranchCache assetBranchCache) {BRANCH_CACHE_THREAD_LOCAL.set(assetBranchCache);}/*** 获取 assetBranchCache*/@Nullablepublic static AssetBranchCache save() {return BRANCH_CACHE_THREAD_LOCAL.get();}/*** 清除 assetBranchCache 信息*/public static void remove() {BRANCH_CACHE_THREAD_LOCAL.remove();}
}

3. 配置拦截器实时更新和清除缓存数据

在使用ThreadLocal时需要注意内存泄漏问题,因为ThreadLocal对象是存储在每个线程的ThreadLocalMap中的,如果不及时清除ThreadLocal对象,可能会导致内存泄漏。

@Data
@Configuration
@CustomLog
public class IncidentInterceptorConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors( @NotNull InterceptorRegistry registry) {registry.addInterceptor(new HandlerInterceptorAdapter() {// preHandle方法:在请求处理前会执行@Overridepublic boolean preHandle( @NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler) throws Exception {BranchContext.remove();return true;}// afterCompletion方法:在请求处理后会执行@Overridepublic void afterCompletion(@NotNull HttpServletRequest request,@NotNull HttpServletResponse response, @NotNull Object handler,@Nullable Exception ex) throws Exception {BranchContext.remove();}});}
}

该类实现WebMvcConfigurer接口,用于配置拦截器。在addInterceptors方法中,注册了一个HandlerInterceptorAdapter类型的拦截器,该拦截器在请求处理前和请求处理后都会执行。在preHandle方法中,调用了BranchContext类的remove方法,用于清除ThreadLocal 缓存的数据。在afterCompletion方法中也调用了BranchContext类的remove方法,确保在请求处理完成后也清除当前线程的ThreadLocal 缓存的数据。

使用拦截器可以保证ThreadLocal中缓存的数据是实时更新的,每次请求进来都会清除缓存中的数据,重新加载。

4. 定义 SaasThreadContextDataHolderBranch 类持有 AssetBranchCache 对象

为了在项目的各个地方获取ThreadLocal中缓存的资产组数据,可以定义一个线程上下文资产组数据持有者类 SaasThreadContextDataHolderBranch

public interface SaasThreadContextDataHolder {}
@Data
@AllArgsConstructor
public class SaasThreadContextDataHolderBranch implements SaasThreadContextDataHolder {@Nullableprivate final AssetBranchCache assetBranchCache;
}

5. 定义 SaasThreadContextHolder 接口

SaasThreadContextHolder 接口中定义了一些方法来管理 SaasThreadContextDataHolder 对象

public interface SaasThreadContextHolder<T extends SaasThreadContextDataHolder> {// 获取 SaasThreadContextDataHolder 类@NotNullClass<T> getSaasThreadContextDataHolderClass();// 尝试加载SaasThreadContextDataHolderdefault boolean tryLoad(@NotNull SaasThreadContextDataHolder holder) {if (!getSaasThreadContextDataHolderClass().isInstance(holder)) {return false;}this.load((T) holder);return true;}// 加载 SaasThreadContextDataHoldervoid load(@NotNull T holder);// 存储 SaasThreadContextDataHolder@NotNullT save();// 清理 SaasThreadContextDataHoldervoid remove();
}

6. 定义 SaasThreadContextHolderBranch 组件

// 应用程序中自动发现和注册该组件
@AutoService(SaasThreadContextHolder.class)
public class SaasThreadContextHolderBranch implements SaasThreadContextHolder<SaasThreadContextDataHolderBranch> {@NotNull@Overridepublic Class<SaasThreadContextDataHolderBranch> getSaasThreadContextDataHolderClass() {return SaasThreadContextDataHolderBranch.class;}// 加载 SaasThreadContextDataHolderBranch 设置 ThreadLocal 缓存数据@Overridepublic void load(@NotNull SaasThreadContextDataHolderBranch holder) {AssetBranchCache assetBranchCache = holder.getAssetBranchCache();if (assetBranchCache != null) {BranchContext.load(assetBranchCache);}}// 获取 ThreadLocal 缓存数据填充 SaasThreadContextDataHolderBranch @NotNull@Overridepublic SaasThreadContextDataHolderBranch save() {return new SaasThreadContextDataHolderBranch(BranchContext.save());}// 清除 ThreadLocal 缓存数据@Overridepublic void remove() {BranchContext.remove();}
}

7. 定义 SaasThreadContextUtil 工具类

public class SaasThreadContextUtil {// 获取Spring容器中所有实现了SaasThreadContextHolder接口的组件@NotNullstatic List<SaasThreadContextHolder<?>> getSaasThreadContextHolders() {return (List) IterableUtils.toList(ServiceLoader.load(SaasThreadContextHolder.class));}@NotNullpublic static List<SaasThreadContextDataHolder> save() {List<SaasThreadContextHolder<?>> saasThreadContextHolders = getSaasThreadContextHolders();List<SaasThreadContextDataHolder> saasThreadContextDataHolders = new ArrayList<>(saasThreadContextHolders.size());for (SaasThreadContextHolder<?> saasThreadContextHolder : saasThreadContextHolders) {saasThreadContextDataHolders.add(saasThreadContextHolder.save());}return saasThreadContextDataHolders;}public static void load(@NotNull List<SaasThreadContextDataHolder> saasThreadContextDataHolders) {for (SaasThreadContextHolder<?> saasThreadContextHolder : getSaasThreadContextHolders()) {for (SaasThreadContextDataHolder saasThreadContextDataHolder : saasThreadContextDataHolders) {if (saasThreadContextHolder.tryLoad(saasThreadContextDataHolder)) {break;}}}}public static void remove() {for (SaasThreadContextHolder<?> saasThreadContextHolder : getSaasThreadContextHolders()) {saasThreadContextHolder.remove();}}
}

8. 定义 BranchNameCache 组件获取 AssetBranchCache 数据

@Component
@CustomLog
public class BranchNameCache {@Getter@Setter(onMethod_ = @Autowired)private IBranchService branchService;@Setter(onMethod_ = @Autowired)private ApplicationContext applicationContext;// 查询缓存数据 AssetBranchCachepublic AssetBranchCache getAssetBranchCache() {// 从ThreadLocal对象中获取缓存数据,如果不为null,直接返回AssetBranchCache result = BranchContext.save();if (result != null) {return result;}// 查询缓存数据BranchNameCache bean = applicationContext.getBean(BranchNameCache.class);String cacheKey = String.format(RedisKey.ASSET_BRANCH, Objects.requireNonNull(TenantInfoContext.getTenantInfo()).getTenantId())+ CacheUtil.getCacheUserRegion();result = bean.getAssetBranchInfo(cacheKey);// 将缓存数据设置到ThreadLocal对象中BranchContext.load(result);return result;}// Spring Cache 注解@Cacheable(value = "assetBranchCache", key = "#cacheKey")public AssetBranchCache getAssetBranchInfo(String cacheKey) {List<Branch> branches = this.getBranchService().listAll();Map<Integer, String> idToNameMap = new HashMap<>(branches.size());branches.forEach(branch -> {idToNameMap.put(branch.getId().intValue(), branch.getName());});return new AssetBranchCache( (UnmodifiableMap<Integer, String>) UnmodifiableMap.unmodifiableMap(idToNameMap));}public Map<Integer, String> getBranchIdToNameMap() {return this.getAssetBranchCache().getIdToNameMap();}
}

① getAssetBranchCache方法:获取 AssetBranchCache数据,先从ThreadLocal对象中查询,如果查询结果不为null,直接返回,否则调用资产服务查询,最后将查询结果设置到ThreadLocal对象中。

② @Cacheable注解:Spring框架的注解,用于缓存方法的返回值。具体来说,@Cacheable注解表示该方法的返回值应该被缓存,value属性指定了缓存的名称,key属性指定了缓存的键值,即用于查找缓存的唯一标识符。在这个例子中,缓存的名称为"assetBranchCache",缓存的键值为cacheKeycacheKey是一个方法参数或者是一个表达式,用于生成缓存的键值。如果缓存中已经存在相同的键值,则直接返回缓存中的值,否则执行方法并将返回值存入缓存中。

2. 业务使用

1. 请求入口 IncidentController

@Api("安全事件信息")
@CustomLog
@Validated
@ResponseResult
@RestController
@RequestMapping("/api/v1/incidents")
public class IncidentController {@CheckValidateAble@PreAuthorize("hasAnyAuthority('superAdmin','incidentQuery')")@PostMapping("/delayloaddata")@ApiOperation("安全事件额外数据延迟加载")@OperateLog(handle = { LoggerEnum.operation }, target = "operate.incident.log", action = "operate.incident.delayloaddata.log")public IncidentDelayLoadData delayLoadData(@RequestBody @Validated IncidentDelayLoadDataQo qo) {Map<String, IncidentDelayLoadData.EachIncidentDelayLoadData> incidentMap = delayLoadService.richInfo(qo);return IncidentDelayLoadData.builder().incidentMap(incidentMap).build();}
}

1. 请求参数的封装

@Data
@Validated
@Builder
@ApiModel(description = "事件延迟加载数据")
@NoArgsConstructor
@AllArgsConstructor
public class IncidentDelayLoadDataQo {@ApiModelProperty(value = "数据类型", example = "INCIDENT")RiskTypeEnum riskType = RiskTypeEnum.INCIDENT;@Valid@Size(max = 1000, message = "延迟加载的数据最大1000条")@NotNullprivate List<SecurityEntry> data;
}

① 风险类型:安全事件,安全告警,风险资产

public enum RiskTypeEnum {/*** 安全事件*/INCIDENT,/*** 安全告警*/ALERT,MOCK,/*** 风险资产*/RISK_ASSET;
}

② 事件延迟加载数据:

@Data
@Builder
@ApiModel(description = "事件延迟加载数据")
@NoArgsConstructor
@AllArgsConstructor
public class SecurityEntry {// 安全事件id、安全告警id、风险主机id@NotBlank@Pattern(regexp = "[^\"'&<>()+%\\\\]+")private String uuId;@Nullableprivate Long assetId;@Nullableprivate Long lastTime;@Nullableprivate List<@NotBlank @Pattern(regexp = "[^\"'&<>()+%\\\\]+") String> alertIds;}

@Nullable 注解是一种用于 Java 代码中的注解,它用于标记一个方法的返回值、参数或字段可以为 null。

③ 请求参数示例:

{"data": [{"uuId": "alert-ad41da97-94b2-4091-9a6d-a6972a7ce919","assetId": 17582,"lastTime": 1690460941},{"uuId": "alert-a6a2d31f-34df-4176-bca5-1ee368e41006","assetId": 17581,"lastTime": 1690460896}],"riskType": "ALERT"
}

2. 响应实体的封装

@Data
@Builder
@ApiModel(description = "事件延迟加载数据")
@NoArgsConstructor
@AllArgsConstructor
public class IncidentDelayLoadData {@ApiModelProperty("事件延迟加载数据")private Map<String, EachIncidentDelayLoadData> incidentMap;@Data@Builder@ApiModel(description = "每条事件的延迟加载数据")@NoArgsConstructor@AllArgsConstructorpublic static class EachIncidentDelayLoadData {@ApiModelProperty("一键遏制禁用标记")private Boolean oneClickDisposeDisabled;private String oneClickDisposeStatus;private String newestUsername;private String checkOutUsername;private String responsible;private AlertSeverityNumber alertSeverityNumber;private Long remarkNumber;private Integer connectStatus;@ApiModelProperty("当前资产类别信息")private MagnitudeInfo magnitude;@ApiModelProperty("处置入口威胁根除判断")private TableCellDataVo entryDisposal;@ApiModelProperty("主机IP")private HostIpDelayVo hostIp;@ApiModelProperty("主机资产组")private HostBranchDelayVo hostBranchId;@ApiModelProperty("主机业务组")private HostGroupDelayVo hostGroupIds;@ApiModelProperty("目的IP")private SrcDstIpDelayVo dstIp;@ApiModelProperty("源IP")private SrcDstIpDelayVo srcIp;@ApiModelProperty("源IP")private AssetUserDelayVo assetUser;}
}

① 外部类 IncidentDelayLoadData:包含一个名为incidentMapMap对象,其中键为事件ID,值为EachIncidentDelayLoadData对象,表示每个事件的延迟加载数据。

@Data
@Builder
@ApiModel(description = "事件延迟加载数据")
@NoArgsConstructor
@AllArgsConstructor
public class IncidentDelayLoadData {@ApiModelProperty("事件延迟加载数据")private Map<String, EachIncidentDelayLoadData> incidentMap;// ....
}

② 静态内部类 EachIncidentDelayLoadData:包含了事件的各种属性,如一键遏制禁用标记、最新用户名、检出用户名、责任人、告警严重性等等。其中,MagnitudeInfoTableCellDataVoHostIpDelayVoHostBranchDelayVoHostGroupDelayVoSrcDstIpDelayVoAssetUserDelayVo都是其他自定义类的对象,用于表示不同的属性。

③ 静态内部类的使用:

Java中的静态内部类是指在一个类的内部定义的静态类。静态内部类与非静态内部类的区别在于,静态内部类不依赖于外部类的实例,可以直接通过外部类名访问,而非静态内部类必须依赖于外部类的实例才能访问。

静态内部类可以访问外部类的静态成员和方法,但不能访问外部类的非静态成员和方法。静态内部类也可以定义静态成员和方法,这些静态成员和方法与外部类的静态成员和方法类似,可以直接通过类名访问。

public class OuterClass {// 外部类的成员和方法public static class StaticInnerClass {// 静态内部类的成员和方法}
}

静态内部类的实例化方式如下:

OuterClass.StaticInnerClass inner = new OuterClass.StaticInnerClass();

④ 响应数据示例:

{"strCode": null,"message": "成功","data": {"incidentMap": {"lqlapp35test3-452799db-2557-424b-ba86-aa1f2d72eb3e": {"oneClickDisposeDisabled": true,"oneClickDisposeStatus": "WAIT_DEAL","newestUsername": null,"checkOutUsername": null,"responsible": null,"alertSeverityNumber": null,"remarkNumber": 0,"connectStatus": null,"magnitude": null,"entryDisposal": {"originalValue": true,"renderValue": null},"hostIp": {"originalValue": "6.6.6.7","renderValue": "6.6.6.7(美国)"},"hostBranchId": {"originalValue": 0,"renderValue": ""},"hostGroupIds": {"count": 0,"data": []},"dstIp": null,"srcIp": null,"assetUser": null},"16c28c0d-649a-dispose-entity111-5d419e8790129": {"oneClickDisposeDisabled": false,"oneClickDisposeStatus": "WAIT_DEAL","newestUsername": null,"checkOutUsername": null,"responsible": null,"alertSeverityNumber": null,"remarkNumber": 0,"connectStatus": null,"magnitude": null,"entryDisposal": {"originalValue": true,"renderValue": null},"hostIp": {"originalValue": "192.168.40.29","renderValue": "192.168.40.29(管理IP范围)"},"hostBranchId": {"originalValue": 1,"renderValue": ""},"hostGroupIds": {"count": 0,"data": []},"dstIp": null,"srcIp": null,"assetUser": null}}},"code": 0
}

2. 获取延迟加载数据 IEventDelayLoadService

利用线程池管理并发任务的执行。通过将任务提交到线程池中,让线程池自动分配线程来执行任务,从而实现并发执行。线程池还可以控制并发任务的数量,避免系统资源被过度占用,从而提高系统的稳定性和可靠性。

@CustomLog
@Service
public class EventDelayLoadServiceImpl implements IEventDelayLoadService, ApplicationListener<ContextStoppedEvent> {// 定义一个线程池private static final ThreadPoolExecutor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(5,20,4,TimeUnit.SECONDS,new LinkedBlockingQueue<>(),new ThreadFactoryBuilder().setNameFormat(IncidentResponseDispositionServiceImpl.class.getSimpleName() + "-pool-%d").setDaemon(true).build(),new ThreadPoolExecutor.DiscardOldestPolicy());// 监听应用程序上下文停止事件,用于关闭线程资源@Overridepublic void onApplicationEvent(@NotNull ContextStoppedEvent ignored) {try {THREAD_POOL_EXECUTOR.shutdown();} catch (Exception e) {log.error("停止线程池失败", e);}}@Overridepublic Map<String, IncidentDelayLoadData.EachIncidentDelayLoadData> richInfo(IncidentDelayLoadDataQo delayLoadDataQo) {List<SecurityEntry> securityEntryList = delayLoadDataQo.getData();if (CollectionUtils.isEmpty(securityEntryList)) {return Collections.emptyMap();}RiskTypeEnum riskType = delayLoadDataQo.getRiskType();ConcurrentHashMap<String, IncidentDelayLoadData.EachIncidentDelayLoadData> data = new ConcurrentHashMap<>(securityEntryList.size());securityEntryList.forEach(entry -> data.put(entry.getUuId(), new IncidentDelayLoadData.EachIncidentDelayLoadData()));List<Callable<Void>> callables;// 根据riskType的不同,调用不同的方法获取Callable列表callablesswitch (riskType) {case INCIDENT:callables = getIncidentCallableList(securityEntryList, data);break;       case ALERT:callables = getAlertCallableList(securityEntryList, data);break;       case RISK_ASSET:callables = getRiskAssetCallableList(securityEntryList, data);break;default:return Collections.emptyMap();}// 通过线程池执行callables中的任务,并将结果存储在ConcurrentHashMap类型的数据data中try {List<Future<Void>> futures = THREAD_POOL_EXECUTOR.invokeAll(callables, 1, TimeUnit.MINUTES);for (Future<Void> future : futures) {try {future.get();} catch (Exception e) {log.warn("incident delay data,future get warn", e);}}} catch (Exception e) {log.warn("incident delay data warn ", e);}return data;}
}

以获取安全告警页面延迟加载数据的线程执行任务为例:getAlertCallableList

private List<Callable<Void>> getAlertCallableList(List<SecurityEntry> securityEntryList, ConcurrentHashMap<String, IncidentDelayLoadData.EachIncidentDelayLoadData> data) {HashMap<@NotNull String, @Nullable Long> uuIdToAssetId = new HashMap<>(securityEntryList.size());for (SecurityEntry securityEntry : securityEntryList) {uuIdToAssetId.put(securityEntry.getUuId(), securityEntry.getAssetId());}try {List<Alert> alerts = alertDao.getAlerts(securityEntryList.stream().map(SecurityEntry::getUuId).collect(Collectors.toList()));if (CollectionUtils.isEmpty(alerts)) {log.warn("no alert data to deal");data.clear();return List.of();}// 尝试加载ThreadLocal对象中的缓存数据List<SaasThreadContextDataHolder> contextDataHolders = SaasThreadContextUtil.save();Callable<Void> assetCallable = getAssetCallable(securityEntryList, data, uuIdToAssetId, contextDataHolders);Callable<Void> assetUserCallable = getAssetUserCallable(securityEntryList, data, uuIdToAssetId, contextDataHolders);Callable<Void> assetBranchCallable = getAlertAssetBranchCallable(securityEntryList, alerts, data, contextDataHolders);Callable<Void> remarkNumberCallable = getRemarkNumberCallable(securityEntryList, ALERT, data, contextDataHolders);return Arrays.asList(assetCallable,assetUserCallable,assetBranchCallable,remarkNumberCallable);} catch (IOException | JsonSerializeException e) {throw new IncidentRuntimeException(I18nUtils.i18n(I18nConstant.AlertOperateConstant.EXCEPTION_TO_BE_TRANSFERRED), e);}
}

在该方法中定义了一个获取安全告警资产组信息线程体,继续看下 getAlertAssetBranchCallable 方法:

@CustomLog
@Service
public class EventDelayLoadServiceImpl implements IEventDelayLoadService, ApplicationListener<ContextStoppedEvent> {@Setter(onMethod_ = { @Autowired })private BranchNameCache branchNameCache; private Callable<Void> getAssetBranchCallable(List<SecurityEntry> securityEntryList, List<Incident> incidentList, ConcurrentHashMap<String, IncidentDelayLoadData.EachIncidentDelayLoadData> data,List<SaasThreadContextDataHolder> contextDataHolders) {return () -> {try {// 设置ThreadLocal对象中的缓存数据SaasThreadContextUtil.load(contextDataHolders);// BranchNameCache 获取 AssetBranchCache 数据Map<Integer, String> branchIdToNameMap = branchNameCache.getBranchIdToNameMap();for (SecurityEntry securityEntry : securityEntryList) {//设置响应数据eachIncidentDelayLoadData.setHostBranchId(new HostBranchDelayVo(branchId, HostIpUtils.getHostBranch(assetId, branchId, branchIdToFullNameMap)));}return null;} finally {// 清除ThreadLocal对象中的缓存数据SaasThreadContextUtil.remove();}};}
}

相关文章:

实战 - 利用 ThreadLocal 线程局部变量实现数据缓存

文章目录 1. 利用 ThreadLocal 缓存 AssetBranchCache 数据1. 定义 AssetBranchCache 类2. 定义 BranchContext 类操作 AssetBranchCache 对象3. 配置拦截器实时更新和清除缓存数据4. 定义 SaasThreadContextDataHolderBranch 类持有 AssetBranchCache 对象5. 定义 SaasThreadC…...

wxwidgets Ribbon使用简单实例

// RibbonSample.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <wx/wx.h> #include "wx/wxprec.h" #include "wx/app.h" #include "wx/frame.h" #include "wx/textctrl.h" #include "…...

2023年第四届“华数杯”数学建模思路 - 案例:最短时间生产计划安排

文章目录 0 赛题思路1 模型描述2 实例2.1 问题描述2.2 数学模型2.2.1 模型流程2.2.2 符号约定2.2.3 求解模型 2.3 相关代码2.4 模型求解结果 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; 最短时间生产计划模型 该模型出现在好几个竞赛赛题上&#x…...

LeetCode404. 左叶子之和

404. 左叶子之和 文章目录 [404. 左叶子之和](https://leetcode.cn/problems/sum-of-left-leaves/)一、题目二、题解方法一&#xff1a;递归方法二&#xff1a;迭代 一、题目 给定二叉树的根节点 root &#xff0c;返回所有左叶子之和。 示例 1&#xff1a; 输入: root [3,9…...

Nginx 高性能内存池 ----【学习笔记】

跟着这篇文章学习&#xff1a; c代码实现一个高性能内存池&#xff08;超详细版本&#xff09;_c 内存池库_linux大本营的博客-CSDN博客https://blog.csdn.net/qq_40989769/article/details/130874660以及这个视频学习&#xff1a; nginx的内存池_哔哩哔哩_bilibilihttps://w…...

iOS--frame和bounds

坐标系 首先&#xff0c;我们来看一下iOS特有的坐标系&#xff0c;在iOS坐标系中以左上角为坐标原点&#xff0c;往右为X正方向&#xff0c;往下是Y正方向如下图&#xff1a; bounds和frame都是属于CGRect类型的结构体&#xff0c;系统的定义如下&#xff0c;包含一个CGPoint…...

docker logs 使用说明

docker logs 可以查看某个容器内的日志情况。 前置参数说明 c_name容器名称 / 容器ID logs 获取容器的日志 , 命令如下&#xff1a; docker logs [options] c_name option参数&#xff1a; -n 查看最近多少条记录&#xff1a;docker logs -n 5 c_name--tail与-n 一样 &#…...

Ceph入门到精通-Ceph PG状态详细介绍(全)

本文主要介绍PG的各个状态&#xff0c;以及ceph故障过程中PG状态的转变。 Placement Group States&#xff08;PG状态&#xff09; creating Ceph is still creating the placement group. Ceph 仍在创建PG。activating The placement group is peered but not yet active.…...

【数据结构】二叉树、二叉搜索树、平衡二叉树、红黑树、B树、B+树

概述 二叉树&#xff08;Binary Tree&#xff09;&#xff1a;每个节点最多有两个子节点&#xff08;左子节点和右子节点&#xff09;&#xff0c;没有限制节点的顺序。特点是简单直观&#xff0c;易于实现&#xff0c;但查找效率较低。 二叉搜索树&#xff08;Binary Search…...

【JVM】(二)深入理解Java类加载机制与双亲委派模型

文章目录 前言一、类加载过程1.1 加载&#xff08;Loading&#xff09;1.2 验证&#xff08;Verification&#xff09;1.3 准备&#xff08;Preparation&#xff09;1.4 解析&#xff08;Resolution&#xff09;1.5 初始化&#xff08;Initialization&#xff09; 二、双亲委派…...

npm i 报错项目启动不了解决方法

1.场景 在另一台电脑低版本node环境跑的react项目&#xff0c;换到另一台电脑node18环境执行npm i时候报错 2.解决方法 脚本前加上set NODE_OPTIONS--openssl-legacy-provider...

【从零开始学习JAVA | 第三十七篇】初识多线程

目录 前言&#xff1a; ​编辑 引入&#xff1a; 多线程&#xff1a; 什么是多线程&#xff1a; 多线程的意义&#xff1a; 多线程的应用场景&#xff1a; 总结&#xff1a; 前言&#xff1a; 本章节我们将开始学习多线程&#xff0c;多线程是一个很重要的知识点&#xff…...

微信新功能,你都知道吗?

近日iOS 微信8.0.40正式版来了&#xff0c;一起来看看有哪些变化&#xff1f; 1、朋友圈置顶 几个月前微信开始内测「朋友圈置顶」功能&#xff0c;从网友们的反馈来看&#xff0c;iOS 微信 8.0.40 似乎扩大了内测范围&#xff0c;更多用户可以体验到该功能了。 大家可以去自己…...

Android 中 app freezer 原理详解(二):S 版本

基于版本&#xff1a;Android S 0. 前言 在之前的两篇博文《Android 中app内存回收优化(一)》和 《Android 中app内存回收优化(二)》中详细剖析了 Android 中 app 内存优化的流程。这个机制的管理通过 CachedAppOptimizer 类管理&#xff0c;为什么叫这个名字&#xff0c;而不…...

Vue3_04_ref 函数和 reactive 函数

ref 函数 声明变量时&#xff0c;赋值的值要写在 ref() 函数中修改变量时&#xff0c;变量名.value xxx在模板中使用时可以省略掉 .value&#xff0c;直接使用变量名即可 <template><h1>一个人的信息</h1><h2>姓名&#xff1a;{{name}}</h2><…...

05 Ubuntu下安装.deb安装包方式安装vscode,snap安装Jetbrains产品等常用软件

使用deb包安装类型 deb包指的其实就是debian系统&#xff0c;ubuntu系统是基于debian系统的发行版。 一般我们会到需要的软件官网下载deb安装包&#xff0c;然后你既可以采用使用“软件安装”打开的方法来进行安装&#xff0c;也可以使用命令行进行安装。我推荐后者&#xff…...

性能测试jmeter连接数据库jdbc(sql server举例)

一、下载第三方工具包驱动数据库 1. 因为JMeter本身没有提供链接数据库的功能&#xff0c;所以我们需要借助第三方的工具包来实现。 &#xff08;有这个jar包之后&#xff0c;jmeter可以发起jdbc请求&#xff0c;没有这个jar包&#xff0c;也有jdbc取样器&#xff0c;但不能发起…...

8.3 C高级 Shell脚本

写一个脚本&#xff0c;包含以下内容&#xff1a; 显示/etc/group文件中第五行的内容创建目录/home/ubuntu/copy切换工作路径到此目录赋值/etc/shadow到此目录&#xff0c;并重命名为test将当前目录中test的所属用户改为root将test中其他用户的权限改为没有任何权限 #!/bin/b…...

2023年华数杯A题

A 题 隔热材料的结构优化控制研究 新型隔热材料 A 具有优良的隔热特性&#xff0c;在航天、军工、石化、建筑、交通等 高科技领域中有着广泛的应用。 目前&#xff0c;由单根隔热材料 A 纤维编织成的织物&#xff0c;其热导率可以直接测出&#xff1b;但是 单根隔热材料 A 纤维…...

【零基础学Rust | 基础系列 | 函数,语句和表达式】函数的定义,使用和特性

文章标题 简介一&#xff0c;函数1&#xff0c;函数的定义2&#xff0c;函数的调用3&#xff0c;函数的参数4&#xff0c;函数的返回值 二&#xff0c;语句和表达式1&#xff0c;语句2&#xff0c;表达式 总结&#xff1a; 简介 在Rust编程中&#xff0c;函数&#xff0c;语句…...

uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖

在前面的练习中&#xff0c;每个页面需要使用ref&#xff0c;onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入&#xff0c;需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...

条件运算符

C中的三目运算符&#xff08;也称条件运算符&#xff0c;英文&#xff1a;ternary operator&#xff09;是一种简洁的条件选择语句&#xff0c;语法如下&#xff1a; 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true&#xff0c;则整个表达式的结果为“表达式1”…...

python如何将word的doc另存为docx

将 DOCX 文件另存为 DOCX 格式&#xff08;Python 实现&#xff09; 在 Python 中&#xff0c;你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是&#xff0c;.doc 是旧的 Word 格式&#xff0c;而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...

CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云

目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...

Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)

目录 一、&#x1f44b;&#x1f3fb;前言 二、&#x1f608;sinx波动的基本原理 三、&#x1f608;波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、&#x1f30a;波动优化…...

使用 SymPy 进行向量和矩阵的高级操作

在科学计算和工程领域&#xff0c;向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能&#xff0c;能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作&#xff0c;并通过具体…...

MFC 抛体运动模拟:常见问题解决与界面美化

在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

什么是VR全景技术

VR全景技术&#xff0c;全称为虚拟现实全景技术&#xff0c;是通过计算机图像模拟生成三维空间中的虚拟世界&#xff0c;使用户能够在该虚拟世界中进行全方位、无死角的观察和交互的技术。VR全景技术模拟人在真实空间中的视觉体验&#xff0c;结合图文、3D、音视频等多媒体元素…...

k8s从入门到放弃之HPA控制器

k8s从入门到放弃之HPA控制器 Kubernetes中的Horizontal Pod Autoscaler (HPA)控制器是一种用于自动扩展部署、副本集或复制控制器中Pod数量的机制。它可以根据观察到的CPU利用率&#xff08;或其他自定义指标&#xff09;来调整这些对象的规模&#xff0c;从而帮助应用程序在负…...