《leetcode-runner》【图解】【源码】如何手搓一个debug调试器——表达式计算
前文:
《leetcode-runner》如何手搓一个debug调试器——引言
《leetcode-runner》如何手搓一个debug调试器——架构
《leetcode-runner》如何手搓一个debug调试器——指令系统
《leetcode-runner》【图解】如何手搓一个debug调试器——调试程序【JDI开发】【万字详解】
文章目录
- 1. 什么是表达式计算
- 2. 解决思路
- 2.1 变量转换为常量
- 2.2 变量识别的难点
- 2.2.1 如何获取变量a的值
- 2.2.2 不同类型的处理逻辑
- 2.2.3 复杂表达式
- 3. 计算架构
- 3.1 面向对象解决问题
- 3.2 UML图
- 3.3 递归处理
- 3.4 递归处理的case
- 4. 链式调用
- 5. 链式Token
仓库地址:leetcode-runner
本文主要聚焦于如何编表达式计算功能
1. 什么是表达式计算
我们直接通过案例说明
画红框对应的功能逻辑就是表达式计算功能
在leetcode-runner项目中,也支持相关的计算逻辑
2. 解决思路
2.1 变量转换为常量
我们先看最简单的表达式——1 + 2
这好解决,直接将表达式输入给计算引擎,将引擎返回结果
在Java中,Jexl就是个不错的计算引擎,他还支持字符串的计算
比如 “123” + 4,计算返回"1234"
在leetcode-runner中,封装如下计算引擎,进行辅助计算
现在,我们上点难度
a + 2
这时候,有的同学要问了,尼玛,这个a是什么东西
a就是被debug代码中存在的变量捏
这时,我们初见麻烦的地方
用户输入的表达式
不一定是纯粹的常量表达式,也有可能含有变量
因此,在计算表达式的时候,需要识别变量,同时将他更换为常量
比如代码中定义 int a = 10;
在计算 a + 2时,我们就需要将变量a处理成常量,得到10 + 2
的表达式,在将其输入给计算引擎,进行计算
也就是说,对于表达式计算逻辑,我们需要识别所有变量,并将其处理为常量,然后将只含有常量的表达式输入计算引擎,执行计算逻辑
对于简单的变量取值,并不会有太大难度,但不同类型的变量,在JDI中获取他们实际的值是一件相当复杂且麻烦的事,下一节将会说明识别的难点
2.2 变量识别的难点
- 如何获取变量的Value
- 获取Value后,不同类型有着不同的处理逻辑
2.2.1 如何获取变量a的值
对于变量a,想要获取他的值相对简单,笔者将给出leetcode-runner简化后的代码
// 从Context中获取ThreadReference
var thread = context.getThread();
// 获取栈帧
StackFrame stack = thread.frame(0);
// 获取可见变量的引用
List<LocalVariable> localVariables = frame.visibleVariables();
// 获取局部变量的值
Map<LocalVariable, Value> values = frame.getValues(localVariables);
如果对Context
不了解的读者,可以参考前一篇介绍JDI的文章《leetcode-runner》【图解】如何手搓一个debug调试器——调试程序【JDI开发】【万字详解】
当获取values后,只要通过变脸所有数据,判断是否存在一个名叫a
的变量,即可获取a的值
但需要注意的是,JDI中所有数据都是封装为Value
因此我们还需要对他进一步处理
// 因为a是int类型, 所以他的Value是PrimitiveValue
Value a = ...
PrimitiveValue pv = (PrimitiveValue) a;
// 强转为IntegerValue
IntegerValue intValue = (IntegerValue) pv;
int intA = intValue.value();
tip: IntergerValue是int类型的镜像,不是Integer类型的镜像。Integer的镜像是ObjectReference!
嘿嘿,麻烦吧,巨tm麻烦,这还是我提前知道a是int类型。如果我不知道a是啥类型,只能枚举所有类型,逐层判断
基本类型处理起来就已经这么操蛋,再来个对象类型就更头疼了。对象越复杂,处理越麻烦…
因此,在leetcode-runner中,单独封装了一个类,专门负责处理Value,可以从Value中获取真正的数据,并以String的形式返回
这个类就是JavaValueInspector
,该类为了简化分析逻辑,采用不少的递归算法,并对一些特殊数据结构做出特殊处理,以此返回更让人容易识别的数据。比如链表,二叉树等等,这些数据结构都会以可视化的方式返回,并不是如实反应他的实际数据
JavaValueInspector
逻辑比较复杂,笔者就不做过多的解释,感兴趣的读者可以clone我的项目,或者直接查看下方源码。JavaValueInspector
的入口方法是inspectValue(Value value),看源码去吧,桀桀
package com.xhf.leetcode.plugin.debug.execute.java.p;import com.sun.jdi.*;
import com.xhf.leetcode.plugin.debug.analysis.converter.convert.TreeNode;
import com.xhf.leetcode.plugin.utils.MapUtils;import java.util.*;
import java.util.stream.Collectors;/*** 强转value, 并给出value的实际内容, md, 真恶心啊我靠* 底层这些破玩意儿真是一点办法没有, 全得写死, 而且为了简化代码,* 使用了不少递归处理, 最怕递归太深了, 出现StackOverflow, 爆栈什么的, 那种事情不要啊~*/
public class JavaValueInspector {// 单例private static final JavaValueInspector INSTANCE = new JavaValueInspector();private JavaValueInspector() {}public static JavaValueInspector getInstance() {return INSTANCE;}public String inspectValue(Value value) {return inspectValue(value, 0);}private String inspectValue(Value value, int depth) {if (value == null) {return null;}// 处理基本类型if (value instanceof PrimitiveValue) {return handlePrimitiveValue((PrimitiveValue) value);}// 处理引用类型else if (value instanceof ObjectReference) {return handleObjectReference((ObjectReference) value, depth);}// 其他未知类型else {return null;}}private String handlePrimitiveValue(PrimitiveValue value) {String res;if (value instanceof BooleanValue) {res = String.valueOf(((BooleanValue) value).value());} else if (value instanceof ByteValue) {res = String.valueOf(((ByteValue) value).value());} else if (value instanceof CharValue) {res = "\"" + ((CharValue) value).value() + "\"";} else if (value instanceof DoubleValue) {res = String.valueOf(((DoubleValue) value).value());} else if (value instanceof FloatValue) {res = String.valueOf(((FloatValue) value).value());} else if (value instanceof IntegerValue) {res = String.valueOf(((IntegerValue) value).value());} else if (value instanceof LongValue) {res = String.valueOf(((LongValue) value).value());} else if (value instanceof ShortValue) {res = String.valueOf(((ShortValue) value).value());} else {res = null;}return res;}private String handleObjectReference(ObjectReference objRef, int depth) {String res;if (objRef instanceof StringReference) {res = "\"" + ((StringReference) objRef).value() + "\"";} else if (objRef instanceof ArrayReference) {if (((ArrayReference) objRef).length() == 0) {return "[]";}Value value = ((ArrayReference) objRef).getValue(0);if (value instanceof StringReference) {res = "[" + ((ArrayReference) objRef).getValues().stream().map(String::valueOf).collect(Collectors.joining(", ")) + "]";} else if (value instanceof PrimitiveValue) {res = "[" + ((ArrayReference) objRef).getValues().stream().map(e -> handlePrimitiveValue((PrimitiveValue) e)).collect(Collectors.joining(", ")) + "]";} else if (isWrapperType(value)) {res = "[" + ((ArrayReference) objRef).getValues().stream().map(e -> handleWrapper((ObjectReference) e)).collect(Collectors.joining(", ")) + "]";} else {res = "[" + ((ArrayReference) objRef).getValues().stream().map(e -> handleObjectReference((ObjectReference) e, depth + 1)).collect(Collectors.joining(", ")) + "]";}} else if (objRef instanceof ThreadReference) {res = ((ThreadReference) objRef).name();} else if (objRef instanceof ClassObjectReference) {res = String.valueOf(((ClassObjectReference) objRef).reflectedType());} else if (objRef instanceof ClassLoaderReference) {res = "ClassLoader...";} else if (isWrapperType(objRef)) {res = handleWrapper(objRef);} else if (isArrayList(objRef)) {res = handleArrayList(objRef, depth);} else if (isArraysArrayList(objRef)) {res = handleArraysArrayList(objRef, depth);} else if (isLinkedList(objRef)) {res = handleLinkedList(objRef, depth);} else if (isSingletonList(objRef)) {res = handleSingletonList(objRef, depth);} else if (isArrayDeque(objRef)) {res = handleArrayDeque(objRef, depth);} else if (objRef instanceof ThreadGroupReference) {res = "objRef 是 ThreadGroupReference, 不支持处理!";} else if (isTreeNode(objRef)) {res = handleTreeNode(objRef, depth);} else if (isListNode(objRef)) {res = handleListNode(objRef, depth);} else if (isPriorityQueue(objRef)) {res = handlePriorityQueue(objRef, depth);} else {res = handleComplexObject(objRef, depth);}return res;}private String handlePriorityQueue(ObjectReference objRef, int depth) {if (objRef == null) {return "null";}ReferenceType referenceType = objRef.referenceType();Value value = objRef.getValue(referenceType.fieldByName("queue"));if (value == null) {return null;}return this.inspectValue(value, depth);}private boolean isPriorityQueue(ObjectReference objRef) {if (objRef == null) {return false;}return objRef.type().name().equals("java.util.PriorityQueue");}private String handleListNode(ObjectReference objRef, int depth) {if (! isMyListNode(objRef)) {return handleComplexObject(objRef, depth);}ReferenceType referenceType = objRef.referenceType();Value val = objRef.getValue(referenceType.fieldByName("val"));Value next = objRef.getValue(referenceType.fieldByName("next"));StringBuilder sb = new StringBuilder("[");while (next != null) {val = objRef.getValue(referenceType.fieldByName("val"));next = objRef.getValue(referenceType.fieldByName("next"));objRef = (ObjectReference) next;sb.append(getIntNodeVal(val)).append(",");}if (sb.charAt(sb.length() - 1) == ',') {sb.deleteCharAt(sb.length() - 1);}sb.append("]");return sb.toString();}private boolean isListNode(Value value) {// 判断是否是 java.util.ArrayDequereturn value instanceof ObjectReference && value.type().name().contains("ListNode");}/*** 判断是否是我提供的ListNode* @param objRef* @return*/private boolean isMyListNode(ObjectReference objRef) {// 检查并获取变量ReferenceType referenceType = objRef.referenceType();Value val = objRef.getValue(referenceType.fieldByName("val"));Value next = objRef.getValue(referenceType.fieldByName("next"));if (val == null || next == null) {return false;}if (! (val instanceof IntegerValue) ) {return false;}// 判断类型return next.type().name().equals(referenceType.name());}/*** 处理复杂对象的打印* @param objRef* @param depth* @return*/private String handleComplexObject(ObjectReference objRef, int depth) {StringBuilder sb = new StringBuilder();if (objRef == null || objRef.referenceType() == null) {return "null";}List<String> results = new ArrayList<>();for (Field field : objRef.referenceType().allFields()) {if (!field.isStatic()) {Value value = objRef.getValue(field);// 过滤某些变量if (isSkipWord(field.name())) {continue;}results.add(field.name() + " : " + this.inspectValue(value, depth + 1)); // 深度+1}}if (!results.isEmpty()) {sb.append("\n").append(getTabsByDepth(depth)); // 外层缩进sb.append("{\n");for (String s : results) {sb.append(getTabsByDepth(depth + 1)).append(s); // 字段缩进sb.append("\n");}sb.append(getTabsByDepth(depth)).append("}"); // 结束的 右大括号 也要缩进}else {sb.append(getTabsByDepth(depth));sb.append("{}");}return sb.toString();}private String handleTreeNode(ObjectReference objRef, int depth) {// 检查是否和我项目提供的TreeNode一致, 否则不进行打印
// if (! isMyTreeNode(objRef)) {
// // 采用默认的复杂对象打印器
// return handleComplexObject(objRef, depth);
// }// 创建头节点TreeNode cp = dfs(objRef);return new TreeNodePrinter(cp).visitAndReturn().toString();}/*** 返回头节点* @return*/private TreeNode dfs(ObjectReference objRef) {if (objRef == null) {return null;}ReferenceType referenceType = objRef.referenceType();Value val = objRef.getValue(referenceType.fieldByName("val"));Value left = objRef.getValue(referenceType.fieldByName("left"));Value right = objRef.getValue(referenceType.fieldByName("right"));TreeNode head = new TreeNode(getIntNodeVal(val));head.left = dfs((ObjectReference) left);head.right = dfs((ObjectReference) right);return head;}private int getIntNodeVal(Value val) {return ((IntegerValue)val).intValue();}/*** 判断是否是我提供的TreeNode** @param objRef* @return*/@Deprecated // 因为debug内核采用的是我提供的TreeNode. 所以不用判断private boolean isMyTreeNode(ObjectReference objRef) {// 检查并获取变量ReferenceType referenceType = objRef.referenceType();Value val = objRef.getValue(referenceType.fieldByName("val"));Value left = objRef.getValue(referenceType.fieldByName("left"));Value right = objRef.getValue(referenceType.fieldByName("right"));if (val == null || left == null || right == null) {return false;}if (! (val instanceof IntegerValue) ) {return false;}// 判断类型if (!left.type().name().equals(referenceType.name())) {return false;}return right.type().name().equals(referenceType.name());}private boolean isTreeNode(Value value) {// 判断是否是 java.util.ArrayDequereturn value instanceof ObjectReference && value.type().name().contains("TreeNode");}// 在打印复杂对象时, 有些字段不需要打印private final Set<String> skipWords = Set.of("hash", "next", "entrySet", "modCount", "threshold", "loadFactor", "keySet", "values", "comparator", "serialVersionUID", "DEFAULT_INITIAL_CAPACITY");private boolean isSkipWord(String s) {return skipWords.contains(s);}public String getTabsByDepth(int depth) {return " ".repeat(Math.max(0, depth));}private boolean isArrayDeque(Value value) {// 判断是否是 java.util.ArrayDequereturn value instanceof ObjectReference && "java.util.ArrayDeque".equals(value.type().name());}private String handleArrayDeque(ObjectReference objRef, int depth) {ReferenceType referenceType = objRef.referenceType();Value elements = objRef.getValue(referenceType.fieldByName("elements"));Value head = objRef.getValue(referenceType.fieldByName("head"));Value tail = objRef.getValue(referenceType.fieldByName("tail"));if (elements != null && head != null && tail != null) {StringBuilder sb = new StringBuilder("[");// 迭代循环ArrayReference arrayReference = (ArrayReference) elements;int length = arrayReference.length();for (int i = ((IntegerValue) head).value(); i != ((IntegerValue) tail).value(); i = (i + 1) % length) {sb.append(this.inspectValue(arrayReference.getValue(i), depth + 1)).append(",");}// 如果最后一个是, 删除if (sb.charAt(sb.length() - 1) == ',') {sb.deleteCharAt(sb.length() - 1);}return sb.append(']').toString();}return null;}/*** 获取 Collections.singletonList 的值*/private String handleSingletonList(ObjectReference objRef, int depth) {Value value = objRef.getValue(objRef.referenceType().fieldByName("element"));if (value != null) {return "[" + this.inspectValue(value, depth + 1) + "]";}return null;}/*** Collections.singletonList, 就很操蛋*/private boolean isSingletonList(Value value) {return value instanceof ObjectReference && "java.util.Collections$SingletonList".equals(value.type().name());}private boolean isLinkedList(Value value) {return value instanceof ObjectReference && "java.util.LinkedList".equals(value.type().name());}private boolean isLinkedListNode(Value value) {return value instanceof ObjectReference && "java.util.LinkedList$Node".equals(value.type().name());}public String handleLinkedList(ObjectReference objRef, int depth) {Field first = objRef.referenceType().fieldByName("first");if (first != null) {Value value = objRef.getValue(first);StringBuilder sb = new StringBuilder("[");// 循环迭代while (isLinkedListNode(value)) {ObjectReference node = (ObjectReference) value;// 获取node.itemField item = node.referenceType().fieldByName("item");if (item != null) {Value itemValue = node.getValue(item);sb.append(inspectValue(itemValue, depth + 1)).append(",");}// 获取node.nextField next = node.referenceType().fieldByName("next");// 更新node(value)if (next != null) {value = node.getValue(next);}}// 如果最后一位是逗号 移除最后一个逗号if (sb.length() > 0 && sb.charAt(sb.length() - 1) == ',') {sb.deleteCharAt(sb.length() - 1);}return sb.append("]").toString();}return "";}/*** 判断是否是Arrays.ArrayList*/private boolean isArraysArrayList(Value value) {return value instanceof ObjectReference && "java.util.Arrays$ArrayList".equals(value.type().name());}/*** 获取Arrays.ArrayList* @return 以String的形式表示Arrays.ArrayList*/private String handleArraysArrayList(ObjectReference objRef, int depth) {Field a = objRef.referenceType().fieldByName("a");if (a != null) {Value value = objRef.getValue(a);if (value instanceof ArrayReference) {StringBuilder sb = new StringBuilder("[");ArrayReference arrayReference = (ArrayReference) value;for (int i = 0; i < arrayReference.getValues().size(); i++) {sb.append(this.inspectValue(arrayReference.getValue(i), depth + 1));if (i != arrayReference.getValues().size() - 1) {sb.append(", ");}}return sb.append("]").toString();}}return "";}/*** 获取ArrayList* @return 以String的形式表示ArrayList*/private String handleArrayList(ObjectReference objRef, int depth) {Field elementData = objRef.referenceType().fieldByName("elementData");Field size = objRef.referenceType().fieldByName("size");if (elementData != null && size != null) {Value elementDataValue = objRef.getValue(elementData);Value sizeValue = objRef.getValue(size);if (elementDataValue instanceof ArrayReference && sizeValue instanceof IntegerValue) {ArrayReference arrayReference = (ArrayReference) elementDataValue;int sizeInt = ((IntegerValue) sizeValue).value();StringBuilder sb = new StringBuilder("[");// 递归判断ArrayList的每个元素for (int i = 0; i < sizeInt; i++) {Value value = arrayReference.getValue(i);sb.append(this.inspectValue(value, depth + 1));if (i != sizeInt - 1) {sb.append(", ");}}return sb.append("]").toString();}}return "";}private boolean isArrayList(Value value) {return value instanceof ObjectReference && "java.util.ArrayList".equals(value.type().name());}// 包装类集合public final Map<String, String> WRAPPER_TO_PRIMITIVES = MapUtils.getMapFromList(Arrays.asList("java.lang.Integer", "java.lang.Double", "java.lang.Character", "java.lang.Long","java.lang.Float", "java.lang.Boolean", "java.lang.Byte", "java.lang.Short"),Arrays.asList("int", "double", "char", "long","float", "boolean", "byte", "short"));public final Map<String, String> PRIMITIVES_TO_WRAPPER = MapUtils.getMapFromList(Arrays.asList("int", "double", "char", "long","float", "boolean", "byte", "short"),Arrays.asList("java.lang.Integer", "java.lang.Double", "java.lang.Character", "java.lang.Long","java.lang.Float", "java.lang.Boolean", "java.lang.Byte", "java.lang.Short"));/*** 通过包装类型返回对应的基本类型* @param wrapperTypeName 包装类型* @return 对应的基本类型*/public String getPrimitiveTypeByWrapperTypeName(String wrapperTypeName) {return WRAPPER_TO_PRIMITIVES.get(wrapperTypeName);}/*** 通过primitiveType获取对应的包装类型* @param primitiveTypeName 基本类型* @return 对应的包装类型*/public String getWrapperTypeByPrimitiveTypeName(String primitiveTypeName) {return PRIMITIVES_TO_WRAPPER.get(primitiveTypeName);}/*** 判断是不是primitiveType* @param value* @return*/public boolean isPrimitiveType(Value value) {return value instanceof PrimitiveValue;}public boolean isPrimitiveType(String typeName) {return PRIMITIVES_TO_WRAPPER.containsKey(typeName);}public boolean isWrapperType(String typeName) {return WRAPPER_TO_PRIMITIVES.containsKey(typeName);}// 判断对象是否为包装类型public boolean isWrapperType(Value value) {if (value == null) {return false;}if (value instanceof ObjectReference) {// 获取对象的 ReferenceTypeObjectReference objRef = (ObjectReference) value;ReferenceType refType = objRef.referenceType();String className = refType.name();// 判断该类是否是包装类型return WRAPPER_TO_PRIMITIVES.containsKey(className);}return false; // 如果是基本类型值,则认为它不是包装类型}private String handleWrapper(ObjectReference objRef) {if (objRef == null) {return "null";}// 获取包装类型类的 'value' 字段Field valueField = objRef.referenceType().fieldByName("value");if (valueField != null) {// 获取字段值Value value = objRef.getValue(valueField);// 将值转换为字符串并返回return value.toString();}return "";}/*** 获取包装类型类的 'value' 字段. 该字段是PrimitiveValue* @param objRef* @return*/public Value getWrapperValue(ObjectReference objRef) {if (objRef == null) {return null;}// 获取包装类型类的 'value' 字段Field valueField = objRef.referenceType().fieldByName("value");if (valueField != null) {// 获取字段值return objRef.getValue(valueField);}return null;}public List<String> getWrapperTypeList() {return new ArrayList<>(WRAPPER_TO_PRIMITIVES.keySet());}
}
2.2.2 不同类型的处理逻辑
在leetcode-runner中,将Value处理逻辑分为一下几大类
- 纯变量(如a,b,c这种单纯变量)
- 数组(如arr[0])
- 表达式计算(如1+2,a+b这种)
- 实例方法调用(如demo.invoke())
- 纯方法调用( dfs(),takeLocalValue(a) )
- 纯常量(如1,3.14,“nihao”, ‘&’)
- 运算符(如+,-,*,/,<<,&等)
以上五种类型的计算处理逻辑存在较大差异,因此单独令出来这五种类型
2.2.3 复杂表达式
a + 1
很简单,只需要识别出a,获取a对应的Value,处理成int型常量,再将整体写入计算引擎即可
但如果表达式长这样呢?arr[b.invoke(1 + 2, c, arr2[0])] + dfs(1,2,c.invoke())
呢?阁下又当如何应对呢?
我尼玛…
3. 计算架构
3.1 面向对象解决问题
面对复杂表达式识别计算,我们只能动用根源思想——面向对象来解决问题
所谓的面向对象,就是将数据结构和算法封装在同一个类中
如果我们将不同类型的表达式封装在对象中,这个对象又具备计算的能力。那么我们可以将表达式计算拆解为不同类型的类的组合,调用统一接口,得到计算值
像arr[b.invoke(1 + 2, c, arr2[0])] + dfs(1,2,c.invoke())
这个表达式,从宏观上来看,就是一个计算表达式
在计算表达式当中,存在一个数组表达式arr[...]
,和纯函数调用表达式dfs(...)
在数组的维度信息中,存在一个方法调用表达式——a.invoke(...)
。方法调用的内部,又可以拆分为计算表达式1+2
,纯变量表达式c
,数组表达式arr2[0]
在纯函数调用的括号内,存在两个纯常量表达式——1,2
,存在一个方法调用表达式c.invoke()
通过这么一套人肉拆解,如此复杂的表达式最终会被拆解为由基本表达式组合而成的复杂的表达式
有关具体的计算逻辑都被封装在对应类型当中,通过封装的方式减少单个类的处理复杂度,实现最终的计算
3.2 UML图
在leetcode-runner中,表达式统一由Token表示
Token封装了表达式的同时,也封装了表达式的计算逻辑
TokenFactory
,负责识别用户输入的表达式,并根据不同的类型生成不同的表达式,在工厂内部,通过Rule
判断输入类型
Rule
,封装了表达式匹配规则。不同类型的规则对应着不同的Token
整体的工作流程如下
TokenFactory
遍历所有Rule
,执行匹配逻辑。如果匹配,则创建规则对应的Token
这里提供简化后的代码
Rule[] rules = new Rule[]{new EvalRule(),new OperatorRule(),new PureCallRule(),new PureCallRuleChain(), // 链式匹配, 后文会有介绍new VariableRule(),new VariableRuleChain(), // 因为链式匹配功能强大, 部分的Token功能被覆盖new ArrayRule(),new ArrayRuleChain(),new ConstantRule()
};/*** 解析字符串得到对应的Token** @param s 表达式* @return token*/
public Token parseToToken(String s, Context ctx) {List<Rule> matches = new ArrayList<>(5);// 根据解析规则解析得到不同的Tokenfor (Rule rule : rules) {boolean match = rule.match(s);if (match) {matches.add(rule);}}// 判断if (matches.isEmpty()) {throw new ComputeError("无法识别的内容! " + s);}if (matches.size() > 1) {throw new ComputeError(s + " 被多条规则匹配! 请检查规则编写是否正确! + " + matches.stream().map(Rule::getName).collect(Collectors.joining(",")));}Rule targetRule = matches.get(0);Class<? extends Token> tokenClass = targetRule.tokenClass();String className = tokenClass.getName();return tokenClass.getDeclaredConstructor(String.class, Context.class).newInstance(s, ctx);
}
3.3 递归处理
假设用户输入的是arr[1 + 2][c.invoke()]
,在识别的过程中,它会被ArrayToken
处理,但他在处理内部逻辑时,任然需要处理其他的内容,比如表达式计算1+2
,再比如方法调用c.invoke()
。如果这些处理逻辑都封装在ArayToken
内部,那么这个类将会非常臃肿,因此在处理括号内部的内容时,需要将计算逻辑交给其他Token
也就是说,ArrayToken
在处理时,需要使用TokenFactory
提供的创建Token的功能,把处理逻辑外包除去
当然,不仅仅是ArrayToken
需要调用TokenFactory
外包,InvokeToken
也需要外包功能
这种外包的思想,以笔者粗俗的间接,体现的就是递归思想
原本需要计算arr[1+2][c.invoke()]
,现在只需要让别的Token计算1+2
和c.invoke()
这就将原本复杂的计算逻辑拆解为更小的计算逻辑
因为这种递归的思想普遍存在于Token
的计算过程中,因此在AbstractToken
内部,封装了TokenFactory
,方便子类直接使用,让每个Token都具备递归迭代处理表达式的能力
3.4 递归处理的case
以ArrayToken的计算逻辑为例
ArrayToken会优先解析出数组变量名,随后处理数组的每一个维度信息
对于维度内容,都会通过TokenFactory
识别计算,交由别的Token递归处理
/*** Array类型的Token, 规则详见{@link TokenFactory.ArrayRule}*/
public static class ArrayToken extends AbstractToken {public ArrayToken(String token, Context ctx) {super(token, ctx);}/*** 通过token查询array变量* @return 返回array变量的引用*/private ArrayReference getArrayValue() {return getArrayValue(this.token);}public ArrayReference getArrayValue(String arrayId) {// 识别变量, 并获取array变量int idx = arrayId.indexOf("[");String vName = arrayId.substring(0, idx);Value v = takeValueByVName(vName);if (! (v instanceof ArrayReference) ) {throw new ComputeError("变量 " + vName + " 不是数组类型");}return (ArrayReference) v;}// 该方法用于测试, 请勿随意调用public List<String> getDimsStr() {return getDimsStr(this.token);}/*** 从token中解析维度信息* @param token token 形如: arr[1][2][3], arr[1], arr[a.invoke()][1+2], [1][2] 属于完全的数组类型, 不会包含除数组外的任何内容. 此外, 为了配合隐式调用, 支持只有数组括号内容的解析* @return 维度信息: 每一个维度的表达式: 如{1,2,3}; {1}, {a.invoke(), 1+2}, {1,2}*/public List<String> getDimsStr(String token) {// 计算array的维度, 并计算索引int idx = token.indexOf("[");Pattern compile = Pattern.compile("(\\[[^\\]]*\\])");String dimPart = token.substring(idx);Matcher matcher = compile.matcher(dimPart);List<String> dims = new ArrayList<>();while (matcher.find()) {// 匹配出每一个[]String range = matcher.group().replace("[", "").replace("]", "");dims.add(range);}return dims;}/*** 通过this.token获取维度信息* @return 维度信息: 每一个维度的整形数据*/public List<Integer> getDims() {return getDims(this.token);}/*** 从arrayId中解析维度* @param arrayId 形如: arr[1][2][3], arr[1], arr[a.invoke()][1+2] 属于完全的数组类型, 不会包含除数组外的任何内容* @return 维度信息: 每一个维度的int索引数据*/public List<Integer> getDims(String arrayId) {List<String> dimsStr = getDimsStr(arrayId);if (dimsStr.isEmpty()) {throw new ComputeError("数组没有维度!");}List<Integer> dims = new ArrayList<>();for (String s : dimsStr) {// 获取索引数值Token t = tf.parseToToken(s, ctx);Value vv = t.getValue();int value = getDimValue(vv);dims.add(value);}return dims;}/*** 将Value转换为索引数值* @param vv Value* @return int*/private int getDimValue(Value vv) {String typeName = vv.type().name();int value;switch (typeName) {case "int":value = ((IntegerValue) vv).value();break;case "java.lang.Integer":value = ((IntegerValue) WrapperObjReferenceToPrimitive(vv)).value();break;case "long":// 忽略精度损失. 谁吃饱了撑着拿个long型的作为索引, feigebuge能支持这种离谱的计算都算是仁慈了value = (int) ((LongValue) vv).value();break;case "java.lang.Long":value = (int) ((LongValue) WrapperObjReferenceToPrimitive(vv)).value();break;case "short":value = ((ShortValue) vv).value();break;case "java.lang.Short":value = ((ShortValue) WrapperObjReferenceToPrimitive(vv)).value();break;default:throw new ComputeError("数组索引必须为整型!!");}return value;}/*** 迭代arrayReference* @param array arrayReference* @param dims 维度* @return Value*/public Value itrArray(ArrayReference array, List<Integer> dims) {return itrArray(array, dims, getArrayIdentify(this.token));}public Value itrArray(ArrayReference array, List<Integer> dims, String arrayId) {Value value = null;// 迭代arrayfor (int i = 0; i < dims.size(); i++) {int dim = dims.get(i);if (dim >= array.length()) {throw new ComputeError("数组 " + arrayId + " 访问越界越界!! 数组第" + (i + 1) + "维度的元素个数=" + array.length() + ", 索引=" + dim);}if (dim <= -1) {throw new ComputeError("数组索引必须为正数!! index = " + value);}value = array.getValue(dim);// 如果没有迭代到最后一位, 则判断是否是数组类型if (i != dims.size() - 1) {if (!(value instanceof ArrayReference)) {throw new ComputeError("变量" + arrayId + "第 " + i + " 维度不是数组类型, 是" + value.type().name() + "类型!!");}// 跟新arrayarray = (ArrayReference) value;}}return value;}@Overridepublic Value getValue() {// 获取array变量ArrayReference array = getArrayValue();// 获取索引List<Integer> dims = getDims();// 迭代所有的维度, 获取最终的valuereturn itrArray(array, dims);}protected Value getValue(String token) {// 获取array变量ArrayReference array = getArrayValue(token);// 获取索引List<Integer> dims = getDims(token);// 迭代所有的维度, 获取最终的valuereturn itrArray(array, dims, token);}/*** 从token解析数组的标识* arr[1].invoke -> arr[1]* @param token token* @return 数组标识*/private String getArrayIdentify(String token) {TokenFactory.MyMatcher matcher = arrayPattern.matcher(token);if (! matcher.find()) {throw new ComputeError("无法匹配数组, ArrayChainRule规则错误! " + token);}int end = matcher.end();return token.substring(0, end);}
}
在getValue()方法中,ArrayToken先调用getArrayValue()
方法,获取数组变量的引用
随后通过调用getDims()
方法,获得所有维度信息
最后,通过itrArray方法,迭代array变量,获取最终的值
以arr[0][0]为例,先获取arr对应的Value
然后解析出所有维度信息,封装为List。本例中将会得到{0, 0}
最后通过维度信息,迭代arr,获得arr[0][0]的数据
public Value getValue() {// 获取array变量ArrayReference array = getArrayValue();// 获取索引List<Integer> dims = getDims();// 迭代所有的维度, 获取最终的valuereturn itrArray(array, dims);}
在方法getDims()
中,会调用TokenFactory
,迭代计算维度信息
如下方代码所示,在循环体内,调用tf.parseToToken(s, ctx)
获取对应Token,将计算逻辑交由对应的Token处理
/*** 从arrayId中解析维度* @param arrayId 形如: arr[1][2][3], arr[1], arr[a.invoke()][1+2] 属于完全的数组类型, 不会包含除数组外的任何内容* @return 维度信息: 每一个维度的int索引数据*/
public List<Integer> getDims(String arrayId) {List<String> dimsStr = getDimsStr(arrayId);if (dimsStr.isEmpty()) {throw new ComputeError("数组没有维度!");}List<Integer> dims = new ArrayList<>();for (String s : dimsStr) {// 获取索引数值Token t = tf.parseToToken(s, ctx);Value vv = t.getValue();int value = getDimValue(vv);dims.add(value);}return dims;
}
4. 链式调用
在上一章节给出的计算架构已经能较为合理的处理绝大部分的内容。通过递归的处理,将不同的表达式交由不同的Token进行计算处理,简化每个类的处理逻辑
但如果用户给出的是链式表达式
比如:
- a.b.c
- a.invoke().c
- arr[0].invoke()
- arr[0].c
- dfs().arr[0].c
该抓虾还是抓虾。因为链式的计算逻辑和非链式的计算逻辑是不同的,除非在原有的ArrayToken
,VariableToken
等含有变量的Token中添加额外逻辑,否则只能是寄!
但如果在原有逻辑中添加新东西,就违背了开闭原则,而且会让单一类的功能变得臃肿
基于此,在leetcode-runner中,额外引入TokenChain,RuleChain。专门处理这种链式的计算
TokenChain
RuleChain
AbstractRuleChain
对于TokenChain,额外提供了getValue(PValue pV)
方法,通过调用该方法,TokenChain就会知道,自己封装的Token并不是处于链式调用的开头,而是中间的某一段链式
其中,PValue封装前一段链式计算的结果
对于链式调用,只可能存在于数组类型,纯函数调用,实例方法调用,变量类型
因为链式存在的前提是拥有变量,因此新增ArrayTokenChain
,PureCallTokenChain
,InvokeTokenChain
,VariableTokenChain
不过在后续代码编写的过程中,笔者发现VariableTokenChain
的识别规则可以覆盖InvokeTokenChain
的规则
所以InvokeTokenChain
就被淘汰了
5. 链式Token
在leetcode-runner中,链式token的定义为
- token必须只能包含链式调用,不能是非链式
- 比如a.c就是链式,a不是链式。因此a不会被链式Token识别
- 不同类型的链式Token,识别的时候要求,token的开头必须是对应的基本类型
- 比如ArrayTokenChain,对应的基本类型是Array。所以他的合法Token必须形如:arr[0].invoke(),arr[0][1 + 2].c
在链式Token的计算过程中,需要迭代计算。而迭代就意味着离不开TokenFactory
识别token,创建全新的TokenChain进行计算
为了更好的适配链式计算,TokenFactory
新增parseToTokenChain(String s, Context ctx):TokenChain
/*** 解析TokenChain* @param s s* @param ctx ctx* @return TokenChain*/
public TokenChain parseToTokenChain(String s, Context ctx) {List<Rule> matches = new ArrayList<>(3);// 根据解析规则解析得到不同的Tokenfor (Rule rule : rules) {if (rule.match(s)) {matches.add(rule);}}// 判断if (matches.isEmpty()) {throw new ComputeError("无法识别的内容! " + s);}if (matches.size() > 1) {throw new ComputeError(s + " 被多条规则匹配! 请检查规则编写是否正确! + " + matches.stream().map(Rule::getName).collect(Collectors.joining(",")));}// 如果包含EvalRule, 报错if (matches.stream().anyMatch(e -> e instanceof EvalRule)) {throw new ComputeError(s + " 被EvalRule匹配! 无法得到对应的链式! parseToTokenChain方法使用错误! + " + matches.stream().map(Rule::getName).collect(Collectors.joining(",")));}Rule targetRule;// 规则转换if (! (matches.get(0) instanceof RuleChain) ) {String key = matches.get(0).getName();if (!ruleMap.containsKey(key)) {throw new ComputeError(s + " 被" + key + "匹配! 无法得到对应的链式! parseToTokenChain方法使用错误! + " + matches.stream().map(Rule::getName).collect(Collectors.joining(",")));}targetRule = ruleMap.get(key);}else {targetRule = matches.get(0);}Class<? extends Token> tokenClass = targetRule.tokenClass();String className = tokenClass.getName();
return (TokenChain) tokenClass.getDeclaredConstructor(String.class, Context.class).newInstance(s, ctx);
}
需要注意的是,如果匹配的是最后一段链式,是无法被RuleChain,也就是链式规则匹配的。因为它的token在结构上只有一个片段。但从整体上来看,它是链式的一部分,需要按照TokenChain的处理方式计算
所以parseToTokenChain
需要做出额外的调整。如果规则识别得到的结果是ArrayRule
,那么就需要转化为对应的链式规则ArrayRuleChain
相关文章:
《leetcode-runner》【图解】【源码】如何手搓一个debug调试器——表达式计算
前文: 《leetcode-runner》如何手搓一个debug调试器——引言 《leetcode-runner》如何手搓一个debug调试器——架构 《leetcode-runner》如何手搓一个debug调试器——指令系统 《leetcode-runner》【图解】如何手搓一个debug调试器——调试程序【JDI开发】【万字详解…...
Flink概述
一、Flink是什么 二、Flink特点 三、Flink vs SparkStreaming 表 Flink 和 Streaming对比 Flink Streaming 计算模型 流计算 微批处理 时间语义 事件时间、处理时间 处理时间 窗口 多、灵活 少、不灵活(窗口必须是批次的整数倍) 状态 有 …...
【Linux】信号
目录 一、信号的概念二、信号的产生2.1 通过键盘进行信号的产生2.2 通过系统调用进行信号的产生2.2.1 kill函数2.2.2 raise函数2.2.3 abort函数 2.3 通过异常的方式进行信号的产生2.4 通过软件条件的方式进行信号的产生2.4.1 关闭管道读端2.4.2 alarm函数 2.5 Core Dump&#x…...
【漏洞分析】DDOS攻防分析
0x00 UDP攻击实例 2013年12月30日,网游界发生了一起“追杀”事件。事件的主角是PhantmL0rd(这名字一看就是个玩家)和黑客组织DERP Trolling。 PhantomL0rd,人称“鬼王”,本名James Varga,某专业游戏小组的…...
【js进阶】设计模式之单例模式的几种声明方式
单例模式,简言之就是一个类无论实例化多少次,最终都是同一个对象 原生js的几个辅助方式的实现 手写forEch,map,filter Array.prototype.MyForEach function (callback) {for (let i 0; i < this.length; i) {callback(this[i], i, this);} };con…...
【VUE】计算属性+动态样式方法封装
【VUE】父子组件联动实现动态样式控制 【VUE】页面跳转实现动态样式控制 在utils下创建文件夹styleController 编写通用的方法 /*** 样式控制* 本文件主要提供一些动态控制样式的方法*//*** 控制表格表头中的 某些列 是否显示星号** param showStarActions boolean 当值为True时…...
Mac玩Steam游戏秘籍!
Mac玩Steam游戏秘籍! 大家好!最近有不少朋友在用MacBook玩Steam游戏时遇到不支持mac的问题。别担心,我来教你如何用第三方工具Crossover来畅玩这些不支持的游戏,简单又实用! 第一步:下载Crossover 首先&…...
【后端面试总结】tls中.crt和.key的关系
tls中.crt和.key的关系 引言 在现代网络通信中,特别是基于SSL/TLS协议的加密通信中,.crt和.key文件扮演着至关重要的角色。这两个文件分别代表了数字证书和私钥,是确保通信双方身份认证和数据传输安全性的基石。本文旨在深入探讨TLS中.crt和…...
【Axure】配色库
配色库是一个专为设计师和创意工作者打造的在线资源平台,旨在提供丰富的色彩解决方案,帮助用户轻松找到或创造美观和谐的色彩搭配。其中,一个典型的配色库包含了以下几个核心元素: 渐变色:提供多样化的渐变色方案&…...
PL/SQL语言的语法糖
PL/SQL语言的语法糖 引言 PL/SQL(Procedural Language/Structured Query Language)是Oracle公司为其数据库管理系统(DBMS)设计的一种过程化语言。作为一种扩展SQL的语言,PL/SQL不仅支持数据的查询和操作,…...
Pytorch|YOLO
🍨 本文为🔗365天深度学习训练营中的学习记录博客🍖 原作者:K同学啊 一、 前期准备 1. 设置GPU 如果设备上支持GPU就使用GPU,否则使用CPU import torch import torch.nn as nn import torchvision.transforms as transforms im…...
doc、pdf转markdown
国外的一个网站可以: Convert A File Word, PDF, JPG Online 这个网站免费的,算是非常厚道了,但是大文件上传多了之后会扛不住 国内的一个网站也不错: TextIn-AI智能文档处理-图像处理技术-大模型加速器-在线免费体验 https://…...
ZooKeeper 常见问题与核心机制解析
Zookeeper集群本身不直接支持动态添加机器。在Zookeeper中,集群的配置是在启动时静态定义的,并且集群中的每个成员都需要知道其他所有成员。当你想要增加一个新的Zookeeper服务器到现有的集群中时,你需要更新所有现有服务器的配置文件&#x…...
期权懂|场内期权合约行权价格是如何设定制度的?
锦鲤三三每日分享期权知识,帮助期权新手及时有效地掌握即市趋势与新资讯! 场内期权合约行权价格是如何设定制度的? 场内期权合约的行权价格是期权合约中的一个关键要素,它决定了期权买方在期权到期日或之前买入(对于…...
处理 SQL Server 中的表锁问题
在 SQL Server 中,表锁是一个常见的问题,尤其是在并发访问和数据更新频繁的环境中。表锁会导致查询性能下降,甚至导致死锁和系统停滞。本文将详细介绍如何识别、分析和解决 SQL Server 中的表锁问题。 什么是表锁? 表锁是 SQL S…...
【Mysql进阶知识】Mysql 程序的介绍、选项在命令行配置文件的使用、选项在配置文件中的语法
目录 一、程序介绍 二、mysqld--mysql服务器介绍 三、mysql - MySQL 命令行客户端 3.1 客户端介绍 3.2 mysql 客户端选项 指定选项的方式 mysql 客户端命令常用选项 在命令行中使用选项 选项(配置)文件 使用方法 选项文件位置及加载顺序 选项文件语法 使用举例&am…...
代码随想录算法训练营总结
本人是一名普普通通的计算机专业的毕业生,在大学学数据结构和算法就感觉非常难,到毕业也没刷过几道题,所幸后来入职的公司也没有考察算法相关的内容。到现在已经工作两年多了,看到过许多聊面试聊算法的文章,也接触到一…...
二进制/源码编译安装mysql 8.0
二进制方式: 1.下载或上传安装包至设备: 2.创建组与用户: [rootopenEuler-1 ~]# groupadd mysql [rootopenEuler-1 ~]# useradd -r -g mysql -s /bin/false mysql 3.解压安装包: tar xf mysql-8.0.36-linux-glibc2.12-x86_64.ta…...
AI 编程工具—Cursor进阶使用 阅读开源项目
AI 编程工具—Cursor进阶使用 阅读开源项目 首先我们打开一个最近很火的项目browser-use ,直接从github 上克隆即可 索引整个代码库 这里我们使用@Codebase 这个选项会索引这个代码库,然后我们再选上这个项目的README.md 文件开始提问 @Codebase @README.md 这个项目是用…...
掌握C语言内存布局:数据存储的智慧之旅
大家好,这里是小编的博客频道 小编的博客:就爱学编程 很高兴在CSDN这个大家庭与大家相识,希望能在这里与大家共同进步,共同收获更好的自己!!! 目录 引言正文一、数据类型介绍1.内置类型2.自定义…...
一键化配置java环境
一键化配置java环境 下载javaPathConfig 打开,将java的jdk路径写进去 例如我的路径就是 C:\Program Files\Java\jdk-1.8点击确认设置即可...
【I/O编程】UNIX文件基础
IO编程的本质是通过 API 操作 文件。 什么是 IO I - Input 输入O - Output 输出 这里的输入和输出都是站在应用(运行中的程序)的角度。外部特指文件。 这里的文件是泛指,并不是只表示存在存盘中的常规文件。还有设备、套接字、管道、链接…...
leetcode 面试经典 150 题:汇总区间
链接汇总区间题序号228题型数组解法一次遍历法难度简单熟练度✅✅✅ 题目 给定一个 无重复元素 的 有序 整数数组 nums 。 返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表 。也就是说,nums 的每个元素都恰好被某个区间范围所覆盖,并且不存在属…...
联想Android面试题及参考答案
请介绍一下 Android 的架构,并谈谈对 Linux 的了解。 Android 架构主要分为四层,从下往上依次是 Linux 内核层、系统运行库层、应用框架层和应用层。 Linux 内核层是 Android 系统的基础。它提供了底层的硬件驱动程序,包括显示驱动、摄像头驱动、音频驱动等多种硬件设备的驱…...
redux 结合 @reduxjs/toolkit 的使用
1,使用步骤 使用React Toolkit 创建 counterStore(store目录下) --> 为React注入store(src下面的index) --> React组件使用store中的数据(组件) 2,例如下面有一个简单加减的…...
【鱼皮大佬API开放平台项目】Spring Cloud Gateway HTTPS 配置问题解决方案总结
问题背景 项目架构为前后端分离的微服务架构: 前端部署在 8000 端口API 网关部署在 9000 端口后端服务包括: api-backend (9001端口)api-interface (9002端口) 初始状态: 前端已配置 HTTPS(端口 8000)后端服务未配…...
PHP反序列化
一、PHP面向对象的基础知识 基本概念 1、面向过程VS面向对象 以做饭为例,面向过程是自己从原材料到成品全部自己做,面向对象相当于去饭店,点菜,等待结果(上菜)。 2、类的定义 类是定义了一件事物的抽象…...
6、原来可以这样理解C语言_函数(7/8)嵌套调⽤和链式访问
目录 七、嵌套调⽤和链式访问 七、(1)、嵌套调⽤ 七、(2)、链式访问 七、嵌套调⽤和链式访问 七、(1)、嵌套调⽤ 嵌套调⽤就是函数之间的互相调⽤,每个函数就⾏⼀个乐⾼零件,正是因…...
安装 Jenkins 后无法访问用户名或密码且忘记这些凭证怎么办?
Jenkins 是一款功能强大的自动化服务器,在持续集成与交付(CI/CD)领域应用广泛。不过,用户在使用过程中,尤其是首次接触该系统或系统重启后,常常会遇到登录方面的问题。要是 Jenkins 突然要求输入用户名和密…...
VIVADO FIFO (同步和异步) IP 核详细使用配置步骤
VIVADO FIFO (同步和异步) IP 核详细使用配置步骤 目录 前言 一、同步FIFO的使用 1、配置 2、仿真 二、异步FIFO的使用 1、配置 2、仿真 前言 在系统设计中,利用FIFO(first in first out)进行数据处理是再普遍不过的应用了,…...
49_Lua调试
Lua提供了debug库用于创建自定义调试器,尽管Lua本身没有内置的调试器1。这个库允许开发者在程序运行时检查和控制执行流程,这对于开发过程中的错误查找和修复非常有用。 1.Debug库概述 debug库提供的函数可以分为两类:自省函数(introspection functions)和钩子函数(hoo…...
SR-BE 笔记和实验
一、笔记: SR不需要mpls,但要配置 mpls lsr-id 不需要MPLS LDP分标签,但仍然需要依赖IGP来分标签 fish—TE问题 SPF:Shortest Path First 最短路径算法 CSPF:Constrained SPF 受约束的SPF算法 BGP-LS: SR…...
实力认证 | 海云安入选《信创安全产品及服务购买决策参考》
近日,国内知名安全调研机构GoUpSec发布了2024年中国网络安全行业《信创安全产品及服务购买决策参考》,报告从产品特点、产品优势、成功案例、安全策略等维度对各厂商信创安全产品及服务进行调研了解。 海云安凭借AI大模型技术在信创安全领域中的创新应用…...
pytorch张量分块投影示例代码
张量的投影操作 背景 张量投影 是深度学习中常见的操作,将输入张量通过线性变换映射到另一个空间。例如: Y=W⋅X+b 其中: X: 输入张量(形状可能为 (B,M,K),即批量维度、序列维度、特征维度)。W: 权重矩阵((K,N),将 K 维投影到 N 维)。b: 偏置向量(可选,(N,))。Y:…...
Elasticsearch二次开发:实现实时定时同步同义词、近义词与停用词
Elasticsearch二次开发:实现实时定时同步同义词、近义词与停用词 引言 Elasticsearch(ES)作为开源搜索引擎的典范,以其强大的全文搜索、结构化搜索以及分析能力,在各个领域得到了广泛应用。在复杂的搜索场景中&#…...
Linux 常用文件查看命令
目录 cat 命令:连接与查看 more/less 命令:分页查看 tail 命令:实时追踪 cat 命令:连接与查看 基本功能:用于连接文件并打印到标准输出设备上,常用于查看文件内容。当有多个文件作为参数时,会…...
智能家居篇 一、Win10 VM虚拟机安装 Home Assistant 手把手教学
智能家居篇 一、Win10 VM虚拟机安装 Home Assistant 手把手教学 文章目录 [智能家居篇]( )一、Win10 VM虚拟机安装 Home Assistant 手把手教学 前言一.下载Vm版本的HomeAsistant安装包 二.打开Vmware选择新建虚拟机1.选择自定义高级2.选择16.x及以上3.选择稍后安装4.根据官网的…...
端口镜像和端口安全
✍作者:柒烨带你飞 💪格言:生活的情况越艰难,我越感到自己更坚强;我这个人走得很慢,但我从不后退。 📜系列专栏:网络安全从菜鸟到飞鸟的逆袭 目录 一,端口镜像二…...
打造更安全的Linux系统:玩转PAM配置文件
在Linux系统中,用户认证是确保系统安全的关键步骤。PAM(可插拔认证模块)为我们提供了一个非常灵活的框架,帮助我们管理各种服务的认证过程。其中,/etc/pam.d目录是PAM配置的核心部分,这里存放了每个服务所需…...
猫咪智商相当于人的几岁?
猫咪,这个神秘又高冷的物种,总能让我们又爱又恨。它们时而撒娇卖萌,时而独立自主,让人琢磨不透。那么,问题来了,猫咪的智商到底相当于人的几岁呢?今天,就来给大家好好揭秘一下喵星人…...
软件设计大致步骤
由于近期在做软件架构设计,这里总结下大致的设计流程 软件设计流程 1 首先要先写系统架构图,将该功能在整个系统的位置以及和大致的内部模块划分 2 然后写内部的结构图,讲内部的各个子系统,模块,组件之间的关系和调用…...
Elasticsearch入门学习
Elasticsearch是什么 Elasticsearch 是一个基于 Apache Lucene 构建的分布式搜索和分析引擎、可扩展的数据存储和矢量数据库。 它针对生产规模工作负载的速度和相关性进行了优化。 使用 Elasticsearch 近乎实时地搜索、索引、存储和分析各种形状和大小的数据。 特点 分布式&a…...
Mac安装配置使用nginx的一系列问题
brew安装nginx https://juejin.cn/post/6986190222241464350 使用brew安装nginx,如下命令所示: brew install nginx 如下图所示: 2.查看nginx的配置信息,如下命令: brew info nginxFrom:xxx 这样的,是n…...
Elasticsearch Python 客户端是否与自由线程 Python 兼容?
作者:来自 Elastic Quentin_Pradet 在这篇文章中,我们将进行一些实验,看看 Python Elasticsearch 客户端是否与新的 Python 3.13 自由线程(free-threading)版本兼容,其中 GIL 已被删除。 介绍 但首先&…...
ROS2 的所有控制台命令
以下是 ROS2 的控制台命令: 编译 colcon是ros的构建工具 sudo apt install python3-colcon-common-extensions 如只编译 turn_robot colcon build --packages-select turn_robot 编译全部功能包 colcon build source source /home/sukai/turn_robot/install…...
深入理解 Entity、VO、QO、DTO 的区别及其在 MVC 架构中的应用
文章背景 在现代软件开发中,我们经常会接触到各种数据结构的概念,比如 Entity、VO(Value Object)、QO(Query Object)、DTO(Data Transfer Object)等。这些概念尽管看似相似ÿ…...
角色认知培训
课程记录 需求传达的时候先强调重点,理清需求的过程中,大家一起分析 一开始单线程,总结复盘,提升效率,变成多线程 心态 2、复盘能力,每次优化策略 优化 团结、执行、 3、 头马的理解? 小…...
记录一次微信小程序使用云能力开发的过程
对于开发微信小程序云开发不知从何起的同学们,可以当作一次参考。虽说官方有文档,有模板示例,但是这些都是片段或者完整的结果展示。对于初学或者开发经验较少的同学们,可能不知先从那里入手进行第一步的开发。下面解析下构建微信…...
vim将一行行尾倒数第三个字符替换成1
%s/\v(.)(.)(.)(.)$/1\2\3\4\v:very magic模式,可以省略转义符 (.):圆括号的分组功能,将括号匹配内容放到第一个寄存器里面,第二个括号匹配内容放到第二个寄存器里面。 $:匹配行尾字符 \2:第二个括号匹配内容 \3:第三个…...
Kafka权威指南(第2版)读书笔记
目录 Kafka生产者——向Kafka写入数据生产者概览创建Kafka生产者bootstrap.serverskey.serializervalue.serializer 发送消息到Kafka同步发送消息异步发送消息 生产者配置client.idacks消息传递时间max.block.msdelivery.timeout.msrequest.timeout.msretries 和retry.backoff.…...