【OpenGauss源码学习 —— (RowToVec)算子】

VecToRow 算子

  • 概述
  • ExecInitRowToVec 函数
  • ExecRowToVec 函数
    • VectorizeOneTuple 函数
  • ExecEndRowToVec 函数
  • 总结

声明:本文的部分内容参考了他人的文章。在编写过程中,我们尊重他人的知识产权和学术成果,力求遵循合理使用原则,并在适用的情况下注明引用来源。
本文主要参考了 OpenGauss1.1.0 的开源代码和《OpenGauss数据库源码解析》和《PostgresSQL数据库内核分析》一书

概述

  OpenGaussPortalRun 函数中会实际执行相关的 DML 查询,对数据进行计算和处理。在执行过程中,所有执行算子分为两大类行存储算子向量化算子。这两类算子分别对应行存储执行引擎向量化执行引擎行存储执行引擎的上层入口是 ExecutePlan 函数,向量化执行引擎的上层人口是 ExecuteVectorizedPlan 函数。其中向量化引擎是针对列存储表的执行引擎。如果存在行存储表和列存储表的混合计算,那么行存储执行引擎和向量化执行引擎直接可以通过 VecToRowRowToVec 算子进行相互转换行存储算子执行入口函数的命名规则一般为 “Exec + 算子名” 的形式,向量化算子执行入口函数的命名规则一般为 “ExecVee十算子名” 的形式,通过这样的命名规则,可以快速地找到对应算子的函数入口。
  在【OpenGauss源码学习 —— (VecToRow)算子】一文中,我们学习了VecToRow 算子的执行流程,本文则来补充学习一下 RowToVec 算子的执行流程。
  RowToVec 算子的主要功能和作用是将行存储表中的数据按列存储格式重新组织和转换,以便能够有效地交给向量化执行引擎处理。它负责将行数据逐行提取并转化成列向量,使得数据库系统能够在混合计算环境中高效地处理包括行存储表和列存储表的数据,从而实现更快速、更优化的查询和数据处理操作。这个过程是在混合存储环境中实现无缝数据转换的关键部分,有助于提高数据库查询性能和整体系统效率。
  RowToVec 算子的执行流程通常包括以下步骤:首先,RowToVec 算子从行存储表中获取一批行数据,这些行数据以行的形式存储在内存中。然后,它会逐行地将这些数据转换为列存储格式,这意味着将每列的数据分别提取出来,组成列向量。这个转换过程包括数据的重新排列和重新组织,以适应列存储的数据结构。一旦所有行数据都被转换为列向量,RowToVec 算子就会将这些向量传递给向量化执行引擎,以便后续的处理。这种转换操作允许在混合计算将行存储表和列存储表的数据进行有效整合,以便进行更高效的向量化计算。这样的执行流程可以在混合存储环境中实现数据的无缝转换和处理,以提高查询性能和效率。下面我们来详细的看一看相关函数源码一遍更好地理解和学习吧。

ExecInitRowToVec 函数

  ExecInitRowToVec 函数的主要功能是为 RowToVec 算子创建执行状态,并初始化该状态以准备执行操作。它包括创建状态结构设置计划节点初始化元组表分配向量缓冲初始化子节点等步骤。这个过程确保了 RowToVec 算子能够正确地将行数据转换为列向量,并在混合计算中无缝运行,以提高数据库查询性能。
  ExecInitRowToVec 函数的执行流程可以简要描述如下:

  1. 创建 RowToVecState 结构:首先,函数创建了一个 RowToVecState 结构,该结构用于存储 RowToVec 算子的执行状态信息。
  2. 设置基本属性:函数设置 RowToVecState 中的一些基本属性,如关联的计划节点执行状态、以及标记为向量化执行
  3. 检查数据类型支持:函数检查 RowToVec 算子是否支持所需的数据类型,以确保正确的数据处理。
  4. 初始化元组表:函数初始化用于存储结果的元组表,并分配所需的内存。
  5. 初始化子节点:函数初始化 RowToVec 算子的子节点,确保子节点能够正常执行。
  6. 创建表达式上下文:函数为 RowToVecState 创建表达式上下文,以便在执行期间进行表达式计算。
  7. 初始化元组类型:函数设置 RowToVecState元组类型,以匹配子节点的输出类型。
  8. 分配向量缓冲:函数为列向量数据分配内存,以便存储转换后的数据。
  9. 返回状态结构:最后,函数返回初始化完成的 RowToVecState 结构,以供后续的执行操作使用。

  ExecInitRowToVec 函数源码如下所示:(路径:src\gausskernel\runtime\vecexecutor\vecnode\vecrowtovector.cpp

// 初始化 RowToVecState 结构
// 这段代码的作用是初始化 RowToVecState 结构,为 RowToVec 算子的执行创建执行状态。
// 具体步骤包括创建状态结构、设置计划节点、初始化元组表、分配向量缓冲、初始化子节点等。
RowToVecState* ExecInitRowToVec(RowToVec* node, EState* estate, int eflags)
{RowToVecState* state = NULL;/** 创建状态结构*/state = makeNode(RowToVecState);state->ps.plan = (Plan*)node;state->ps.state = estate;state->ps.vectorized = true;// 检查 RowToVec 算子是否支持所需数据类型CheckTypeSupportRowToVec(node->plan.targetlist);/** 初始化元组表** 排序节点只从其排序关系中返回扫描元组。*/ExecInitResultTupleSlot(estate, &state->ps);/* 分配向量缓冲 */state->m_fNoMoreRows = false;/** 初始化子节点** 我们屏蔽了子节点对支持 REWIND、BACKWARD 或 MARK/RESTORE 的需求。*/outerPlanState(state) = ExecInitNode(outerPlan(node), estate, eflags);/** 杂项初始化** 为节点创建表达式上下文*/ExecAssignExprContext(estate, &state->ps);/** 初始化元组类型。不需要初始化投影信息,因为此节点不进行投影。*/ExecAssignResultTypeFromTL(&state->ps,ExecGetResultType(outerPlanState(state))->tdTableAmType);TupleDesc res_desc = state->ps.ps_ResultTupleSlot->tts_tupleDescriptor;state->m_pCurrentBatch = New(CurrentMemoryContext) VectorBatch(CurrentMemoryContext, res_desc);state->ps.ps_ProjInfo = NULL;return state;
}

ExecRowToVec 函数

  ExecRowToVec 函数的主要功能是将行数据转换为向量批次,用于向量化处理。它通过不断从外部计划节点获取行数据,然后将每个行数据向量化,直到外部计划的所有行都处理完毕。函数中的 VectorizeOneTuple 函数用于将单个行元组转换为向量,并使用适当的内存管理。一旦向量批次已满或外部计划的行耗尽,函数将返回当前的向量批次。
  ExecRowToVec 函数的的执行流程可以简要描述如下:

  1. 准备必要的变量和数据结构,包括初始化向量批次(VectorBatch)和执行上下文(ExprContext)。
  2. 重置执行上下文的内存,以确保在处理每个行数据时不会出现内存混淆。
  3. 获取外部计划节点的状态信息,准备从外部计划获取行数据。
  4. 如果已经没有更多的行数据需要处理(由 m_fNoMoreRows 标志控制),则直接跳到完成步骤。
  5. 通过循环迭代处理外部计划的每个行数据,每次获取一个外部计划的元组。
  6. 对每个获取的行数据执行向量化处理,将行数据转换为向量格式,并使用适当的内存进行管理。
  7. 如果向量批次已满,或者外部计划的所有行都已处理完毕,则结束循环。
  8. 在完成步骤中,将向量批次中的每列的行数设置为相同的值,以标记批次中的有效数据行数
  9. 最后,返回向量批次,其中包含转换后的向量化数据。

  ExecRowToVec 函数的源码如下:(路径:src\gausskernel\runtime\vecexecutor\vecnode\vecrowtovector.cpp

/** @Description: 向量化运算符--将行数据转换为向量批次。** @IN state: RowToVecState 结构,用于管理 RowToVec 算子的执行状态。* @return: 返回包含行表数据的向量批次,如果没有数据可转换则返回 NULL。*/
VectorBatch* ExecRowToVec(RowToVecState* state)
{int i;PlanState* outer_plan = NULL;TupleTableSlot* outer_slot = NULL;VectorBatch* batch = state->m_pCurrentBatch;/* 重置当前的 ecxt_per_tuple_memory 上下文 */ExprContext* econtext = state->ps.ps_ExprContext;ResetExprContext(econtext);/* 从节点获取状态信息 */outer_plan = outerPlanState(state);batch->Reset();/** 如果在返回 NULL 后再次调用 ExecProcNode(),它可能会重新启动,* 因此我们需要自行进行保护。*/if (state->m_fNoMoreRows) {goto done;}/** 处理每个外部计划元组,然后获取下一个元组,直到外部计划耗尽。*/for (;;) {outer_slot = ExecProcNode(outer_plan);if (TupIsNull(outer_slot)) {state->m_fNoMoreRows = true;break;}/** 向量化一个元组并切换到 exprcontext 的 ecxt_per_tuple_memory。*/if (VectorizeOneTuple(batch, outer_slot, econtext->ecxt_per_tuple_memory)) {/* 批次已满,现在返回当前批次 */break;}}done:for (i = 0; i < batch->m_cols; i++) {batch->m_arr[i].m_rows = batch->m_rows;}return batch;
}

VectorizeOneTuple 函数

  VectorizeOneTuple 函数的作用是将一个数据库查询结果中的单个元组(Tuple)转换为向量化数据结构(VectorBatch),以便进行向量化处理。它会逐一提取元组中的每列数据,并根据列的数据类型长度将其合适地存储在向量批次中的相应列中。此功能是在向量化执行引擎中关键的一步,通过高效地将行数据转换为列存储的向量形式,有助于加速数据库查询的处理速度和效率。此外,该函数还会检查向量批次是否已满,以便控制向量处理的大小和内存使用
  VectorizeOneTuple 函数的执行流程可以简要描述如下:

  1. 初始化必要的变量和标志,包括一个标志变量 may_more 用于表示是否可能还有更多的行需要处理,以及循环迭代中使用的计数器变量 i 和 j
  2. 切换当前内存上下文到传入的 transformContext 上下文,以确保内存管理的正确性。
  3. 确保输入的插槽 slot 不为空,并且具有有效的元组描述符
  4. 使用 tableam_tslot_getallattrs 函数从插槽中提取所有列的属性值
  5. 开始循环迭代插槽中的每个列数据处理每列的数据类型和长度
  6. 根据列的数据类型长度选择合适的处理方式,包括整数浮点数变长数据大整数TID行标识符)等。
  7. 如果列不为空(slot->tts_isnull[i] == false),则将列的值存储到向量批次的相应列中,并标记该列的数据为非 NULL
  8. 如果列为空(slot->tts_isnull[i] == true),则将向量批次中的相应列标记为 NULL
  9. 增加向量批次中的行数计数器 pBatch->m_rows
  10. 如果向量批次已满(行数达到 BatchMaxSize),则设置 may_more 标志为 true
  11. 切换回之前的内存上下文,以确保内存管理的正确性。
  12. 返回 may_more 标志,指示向量批次是否已满,以便在上层调用中做出相应的处理。

  VectorizeOneTuple 函数的源码如下:(路径:src\gausskernel\runtime\vecexecutor\vecnode\vecrowtovector.cpp

/** @Description: 将一个元组打包成向量批次。** @IN pBatch: 目标向量化数据。* @IN slot:   一个插槽(TupleTableSlot)中的源数据。* @IN transformContext: 切换到此上下文以避免内存泄漏。* @return: 如果 pBatch 已满,则返回 true,否则返回 false。*/
bool VectorizeOneTuple(_in_ VectorBatch* pBatch, _in_ TupleTableSlot* slot, _in_ MemoryContext transformContext)
{bool may_more = false;  // 标志是否可能还有更多行需要处理int i, j;/* 切换到当前的转换上下文 */MemoryContext old_context = MemoryContextSwitchTo(transformContext);/** 提取旧元组的所有值。*/Assert(slot != NULL && slot->tts_tupleDescriptor != NULL);tableam_tslot_getallattrs(slot);  // 从插槽中获取所有属性值j = pBatch->m_rows;for (i = 0; i < slot->tts_nvalid; i++) {  // 遍历元组的每一列int type_len;Form_pg_attribute attr = slot->tts_tupleDescriptor->attrs[i];  // 获取列的属性信息pBatch->m_arr[i].m_desc.typeId = attr->atttypid;  // 设置向量批次中列的数据类型if (slot->tts_isnull[i] == false) {  // 检查列是否为 NULLtype_len = attr->attlen;  // 获取列的数据长度switch (type_len) {  // 根据数据长度选择合适的处理方式case sizeof(char):case sizeof(int16):case sizeof(int32):case sizeof(Datum):pBatch->m_arr[i].m_vals[j] = slot->tts_values[i];  // 处理整数和浮点数break;case 12:case 16:case 64:case -2:pBatch->m_arr[i].AddVar(slot->tts_values[i], j);  // 处理变长数据break;case -1: {Datum v = PointerGetDatum(PG_DETOAST_DATUM(slot->tts_values[i]));  // 解压缩数据/* 如果是 numeric 列,尝试将 numeric 转换为 big integer */if (attr->atttypid == NUMERICOID) {v = try_convert_numeric_normal_to_fast(v);}pBatch->m_arr[i].AddVar(v, j);  // 处理大整数和压缩数据/* 由于可能创建了新的内存,因此我们必须及时检查和释放。 */if (DatumGetPointer(slot->tts_values[i]) != DatumGetPointer(v)) {pfree(DatumGetPointer(v));  // 释放临时内存}break;}case 6:if (attr->atttypid == TIDOID && attr->attbyval == false) {  // 处理 TID 数据pBatch->m_arr[i].m_vals[j] = 0;ItemPointer dest_tid = (ItemPointer)(pBatch->m_arr[i].m_vals + j);ItemPointer src_tid = (ItemPointer)DatumGetPointer(slot->tts_values[i]);*dest_tid = *src_tid;} else {pBatch->m_arr[i].AddVar(slot->tts_values[i], j);  // 处理其他复合类型数据}break;default:ereport(ERROR, (errcode(ERRCODE_INDETERMINATE_DATATYPE), errmsg("不支持的数据类型分支")));  // 不支持的数据类型分支,抛出错误}SET_NOTNULL(pBatch->m_arr[i].m_flag[j]);  // 标记列中的数据为非 NULL} else {SET_NULL(pBatch->m_arr[i].m_flag[j]);  // 标记列中的数据为 NULL}}pBatch->m_rows++;  // 增加向量批次中的行数if (pBatch->m_rows == BatchMaxSize) {  // 如果向量批次已满may_more = true;  // 设置 may_more 标志为 true}/* 切换回旧上下文 */(void)MemoryContextSwitchTo(old_context);  // 切换回之前的内存上下文return may_more;  // 返回 may_more 标志,指示向量批次是否已满
}

ExecEndRowToVec 函数

  ExecEndRowToVec 函数的主要作用是用于清理和释放 RowToVec 算子执行时所分配的资源和状态,包括向量批次数据执行上下文结果元组槽以及关闭外部计划节点。首先,它将向量批次数据的指针设置为 NULL,以防止内存泄漏。然后,通过 ExecFreeExprContext 函数取消与计划节点的输出上下文的链接,但不实际释放内存,这是因为内存释放可能由上层节点负责。接下来,它清空结果元组槽的数据,确保不会有残留数据。最后,它关闭外部计划节点释放与之相关的资源。这个函数用于 RowToVec 算子的执行结束时,进行资源清理和释放,以确保系统资源的有效利用。
  ExecEndRowToVec 函数的源码如下:(路径:src\gausskernel\runtime\vecexecutor\vecnode\vecrowtovector.cpp

void ExecEndRowToVec(RowToVecState* node)
{node->m_pCurrentBatch = NULL;  // 清空向量批次数据,防止内存泄漏/** 我们不实际释放任何 ExprContexts(参见 ExecFreeExprContext 中的注释),* 只需从计划节点中取消链接输出上下文即可。*/ExecFreeExprContext(&node->ps);  // 取消与计划节点的输出上下文的链接,不实际释放内存/** 清空元组表*/(void)ExecClearTuple(node->ps.ps_ResultTupleSlot);  // 清空结果元组槽的数据/** 关闭子计划节点*/ExecEndNode(outerPlanState(node));  // 关闭外部计划节点
}

总结

  RowToVec 算子是用于将行存储数据转换为向量化数据的操作符,它通过 ExecInitRowToVec 函数进行初始化将行数据逐行处理并转换为向量批次,这个过程由 ExecRowToVecVectorizeOneTuple 函数完成,最后,ExecEndRowToVec 函数用于清理和释放资源,协同工作,使得在处理混合行存储和列存储数据时,能够高效地进行向量化计算,提高数据库查询性能。
  这里用 AI 尝试性的生成了一张描述 RowToVec 算子的图,感觉很有意思。
在这里插入图片描述
  这张图解释了数据库系统中 RowToVec 算子的功能,展示了数据从行存储格式转换到列存储格式的过程。下面是对每个部分的详细解释:

顶部部分 - 行存储表:这部分代表数据库中常见的行存储表。在行存储中,数据按行组织,这里以水平线的形式表示。每条水平线象征一个数据行,包含顺序存储的各种字段。中间部分 - RowToVec算子转换:中间部分展示了RowToVec算子。它作为一个转换机制,将数据从行格式转换为列格式。这个过程对于在混合计算环境中操作的数据库非常关键,这些数据库使用行存储和列存储格式。算子将行数据逐行提取并转化为列向量,这一过程在图中以从水平线到垂直线的转换形式展示。底部部分 - 列存储格式:图的底部展示了转换后的数据,现在以列存储格式呈现。在列存储中,数据按列组织,这里以垂直线的形式表示。每条垂直线代表一列数据,显示了数据在转换后的新结构。

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

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

相关文章

github首次将文件合到远端分支,发现名字不是master,而是main

暂存区和本地仓库的信息都存储在.git目录中其中 其中&#xff0c;暂存区和本地仓库的信息都存储在.git目录中 在自己的github上实践 1、刚开始&#xff0c;git clone gitgithub.com:lingze8678/my_github.git到本地 2、在克隆后的代码中加入一个pdf文件 3、在git bash中操作…

CentOS增加虚拟内存 (Linux增加内存)

前言 因为囊中羞涩不敢言&#xff0c;所以内存只有2G&#xff0c;项目在运行的时候&#xff0c;占用的内存已经报表&#xff0c;所以有的时候就会出现宕机的情况发生&#xff0c;后面发现可以通过使用增加虚拟内存空间&#xff0c;来增加内存容量。 下面进入正题&#xff0c;讲…

Selenium+Python自动化测试之验证码处理

两种方式&#xff1a; 验证码识别技术 (很难达到100%) 添加Cookie &#xff08;*****五星推荐&#xff09; 方式一&#xff1a;验证码识别技术 逻辑方式&#xff1a; 1&#xff1a;打开验证码所在页面&#xff0c;截图。获取验证码元素坐标&#xff0c;剪切出验证码图片&…

【MATLAB】辛几何模态分解分解+FFT+HHT组合算法

有意向获取代码&#xff0c;请转文末观看代码获取方式~也可转原文链接获取~ 1 基本定义 辛几何模态分解&#xff08;CEEMDAN&#xff09;是一种处理非线性和非平稳信号的适应性信号分解方法。通过在信号中加入白噪声&#xff0c;并多次进行经验模态分解&#xff08;EMD&#…

深度学习TensorFlow2基础知识学习前半部分

目录 测试TensorFlow是否支持GPU&#xff1a; 自动求导&#xff1a; 数据预处理 之 统一数组维度 定义变量和常量 训练模型的时候设备变量的设置 生成随机数据 交叉熵损失CE和均方误差函数MSE 全连接Dense层 维度变换reshape 增加或减小维度 数组合并 广播机制&#…

clickhouse的向量化执行

背景 clickhouse快的很大一部分原因来源于数据的向量化执行&#xff0c;本文就来看一下向量化执行和正常标量执行的区别 SIMD的向量化执行 从上图可知&#xff0c;clickhouse通过SIMD指令可以做到一个cpu周期操作两个向量的运算操作&#xff0c;比起普通的cpu指令效率提高了N…

Understanding Computer Hardware

文章目录 I. Input Devices1. Keyboard&#xff08;1&#xff09;Layout&#xff08;2&#xff09;Key Types&#xff08;3&#xff09;Functionality&#xff08;4&#xff09;Connectivity&#xff08;5&#xff09;Ergonomics&#xff08;6&#xff09;Multimedia Keys&…

【计算机组成体系结构】主存储器的基本组成

一、半导体元器件存储二进制0/1的原理 一个存储器逻辑上分为MAR&#xff0c;MDR和存储体&#xff0c;这三块在时序逻辑电路的控制下相互配合工作。 而存储体有多个存储单元构成&#xff0c;每个存储单元又由每个存储元构成。一个存储元可以存放一位的二进制的0/1。 一个存储元…

OWASP安全练习靶场juice shop-更新中

Juice Shop是用Node.js&#xff0c;Express和Angular编写的。这是第一个 完全用 JavaScript 编写的应用程序&#xff0c;列在 OWASP VWA 目录中。 该应用程序包含大量不同的黑客挑战 用户应该利用底层的困难 漏洞。黑客攻击进度在记分板上跟踪。 找到这个记分牌实际上是&#…

想考研到电子类,未来从事芯片设计,目前该怎么准备?

最近看不少天坑学子想考研微电子专业&#xff0c;但却不知道该怎么准备&#xff1f;接下来就带大家一起来具体了解一下~ 首先是目标院校的选择&#xff1f; 目前所设的微电子专业学校里&#xff0c;比较厉害的有北京大学、清华大学、中国科学院大学、复旦大学、上海交通大学、…

ROS2教程08 ROS2的功能包、依赖管理、工作空间配置与编译

ROS2的功能包、依赖管理、工作空间配置与编译 版权信息 Copyright 2023 Herman YeAuromix. All rights reserved.This course and all of its associated content, including but not limited to text, images, videos, and any other materials, are protected by copyrigh…

品牌是如何通过软文推广产品的?媒介盒子为您揭秘

需求是概念的、抽象的&#xff0c;产品是具象的&#xff0c;多维的。软文推广就是通过发现消费者的需求来促使消费者主动购买产品&#xff0c;今天媒介盒子就来和大家聊聊&#xff1a;品牌是如何通过软文推广产品的。 一、 差异化内容打出独特点 差异化内容指通过和竞品的分析…

基于Intel Ai Analytics Toolkit 及边缘计算的溶氧预测水产养殖监测方案

基于AI的淡水养殖水质溯源、优化系统方案 前言一、关键需求及方案概述二、方案设计预测机制LSTM 模型基于intel AI 的时序水质分析模型与分类模型优化 三、实战分析1、方案简述2、数据分析预处理特征类型处理特征分布分析 3、特征构造4、特征选择过滤法重要性排序 5.构建LSTM模…

算法--最短路

这里写目录标题 xmind单源最短路简介所有边权都是正朴素的Dijkstra算法思想例子题解 堆优化版的Dijkstra算法 存在负数权Bellman-Ford算法思想例子题解 多源汇最短路简介 xmind 上述中&#xff0c;朴素Dijkstra算法适用于稠密图 其他用堆优化版 而SPFA算法一般都比Bellman-For…

设计模式:装饰者模式

目录 一、定义 二、场景 三、例子 四、优缺点 优点&#xff1a; 缺点&#xff1a; 一、定义 在不改变已有对象结构的情况下&#xff0c;动态添加新的功能到对象上&#xff0c;是继承的一种替代方案。属于结构型模式。 二、场景 1.扩展一个类的功能&#xff0c;添加附加职责…

七、ZooKeeper选举机制

目录 1、概念 2、全新集群选举 3、非全新集群选举 zookeeper默认的算法是FastLeaderElection,采用投票数大于半数则胜出

资料分析(花生)

基期A&#xff08;给出BR或BX&#xff09; 前期&#xff1a;代入、直除、假设分配隔年前期&#xff1a;求出间隔增长率&#xff0c;再变成第一类考法前期差值&#xff1a;假设分配法求得两个前期作差。 现期B 有增量求现期&#xff1a;求出 X&#xff0c;列不等式即可有增速求现…

【已解决】页内切换<router-view>使得url变化导致菜单高亮消失

在写项目时&#xff0c;我们常会用到侧边菜单栏&#xff0c;而具体页面中经常使用<router-view>切换子组件。 但是按照我们平时的写法&#xff0c;切换子组件后会导致url改变&#xff0c;从而使得菜单高亮消失&#xff0c;这是非常影响用户体验的。 所以&#xff0c;我…

class文件结构

文章目录 1. 常量池集合2. 访问标志3. 字段表集合4. 方法表集合5. 属性表集合 成员变量&#xff08;非静态&#xff09;的赋值过程&#xff1a;1. 默认初始化 2. 显示初始化/代码块中初始化 3. 构造器中初始化 4. 有了对象后对象。属性或者对象。方法的方式对成员变量进行赋值 …

无线网卡填坑记

没想到我安装无线网卡这么波澜起伏~ 起因 近来刚在电脑上玩完了 Dishonored 2&#xff0c;紧接着继续着我的刺客信条之旅。总是觉得键盘鼠标玩起来不爽&#xff0c;还是手柄玩这种游戏才舒服。突然&#xff0c;灵光一现&#xff0c;我想到正好有闲置的 Switch 掌机没怎么玩&am…