spring 中的路径匹配

在 spring 中,不管是注解使用时配置的基础包扫描路径,还是在 spring MVC 中根据路径匹配到具体的 handler,都会使用到路径匹配,今天我们来看下 spring 中的路径匹配到底是如何实现的。

glob pattern 语法

spring 借鉴了一种称之为 glob pattern 的语法,这是一种常用的匹配文件路径的模式语法,通过简单的规则来匹配文件名、路径名等,实现文件的查找、筛选等操作。常用在 Unix/Linux 系统的 shell 编程中。

下面我们来详细看下语法:

在 glob pattern 常用的特殊字符只有三个,'?'、'*'、'{',含义如下:

  • ?  匹配一个字符
  • *  匹配零个或多个字符
  • {xxx, xxx} 匹配 {} 中任意一组字符 

spring 中的实现

在 spring 中定义了基于字符串的路径匹配的接口,org.springframework.util.PathMatcher,目前只有一个实现类,org.springframework.util.AntPathMatcher,所以接下来我们所描述的路径匹配实现都是基于 AntPathMatcher。 

在 AntPathMatcher 中,匹配规则如下:

  • ? 匹配单个任意字符
  • *  匹配零个或多个任意字符
  • ** 匹配单个或多个路径中的文件夹,即匹配当前包及其子包
  • {spring:[a-z]+} 匹配regexp [a-z]+作为名为"spring"的路径变量

在 AntPathMatcher 中定义了是否采用此种匹配规则的方法 AntPathMatcher#isPattern,其实就是判断路径中是否存在规则中出现的三个特殊字符。

@Override
public boolean isPattern(@Nullable String path) {if (path == null) {return false;}boolean uriVar = false;for (int i = 0; i < path.length(); i++) {char c = path.charAt(i);if (c == '*' || c == '?') {return true;}if (c == '{') {uriVar = true;continue;}if (c == '}' && uriVar) {return true;}}return false;
}

在 AntPathMatcher#doMatch 中定义了匹配的算法逻辑,而将真正的字符串匹配委托给了内部类AntPathStringMatcher。

下面来看下 AntPathStringMatcher 的创建

private boolean matchStrings(String pattern, String str,@Nullable Map<String, String> uriTemplateVariables) {return getStringMatcher(pattern).matchStrings(str, uriTemplateVariables);
}// 获取 pattern 对应的匹配器
protected AntPathStringMatcher getStringMatcher(String pattern) {AntPathStringMatcher matcher = null;Boolean cachePatterns = this.cachePatterns;if (cachePatterns == null || cachePatterns.booleanValue()) {matcher = this.stringMatcherCache.get(pattern);}if (matcher == null) {// AntPathMatcher 内部类matcher = new AntPathStringMatcher(pattern, this.caseSensitive);if (cachePatterns == null && this.stringMatcherCache.size() >= CACHE_TURNOFF_THRESHOLD) {// 超过阈值,关闭缓存,上限 65536deactivatePatternCache();return matcher;}if (cachePatterns == null || cachePatterns.booleanValue()) {// 加入缓存this.stringMatcherCache.put(pattern, matcher);}}return matcher;
}

可以看到,在字符串路径匹配时,先去获取字符串路径匹配器,此时将一个个 pattern 片段作为 key,去匹配器缓存获取一遍,获取不到,才进行创建。

在路径匹配时,传入的路径都是绝对路径,所以会存在很多相同的片段,此处应用缓存,也是为了提高匹配器的复用,减少对象的创建。

private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?\\}|[^/{}]|\\\\[{}])+?)\\}");// *  0个或多个
// ?  1个
// {}  url路径 /user/{user}
public AntPathStringMatcher(String pattern, boolean caseSensitive) {this.rawPattern = pattern;this.caseSensitive = caseSensitive;StringBuilder patternBuilder = new StringBuilder();// 采用 glob pattern 语法Matcher matcher = GLOB_PATTERN.matcher(pattern);int end = 0;// 将 glob pattern 语法转化为 java 正则语法while (matcher.find()) {patternBuilder.append(quote(pattern, end, matcher.start()));String match = matcher.group();if ("?".equals(match)) {patternBuilder.append('.');}else if ("*".equals(match)) {patternBuilder.append(".*");}// {}else if (match.startsWith("{") && match.endsWith("}")) {int colonIdx = match.indexOf(':');if (colonIdx == -1) {patternBuilder.append(DEFAULT_VARIABLE_PATTERN);this.variableNames.add(matcher.group(1));}else {// ':' 之前的作为 variableName, 之后的部分作为 variablePatternString variablePattern = match.substring(colonIdx + 1, match.length() - 1);patternBuilder.append('(');patternBuilder.append(variablePattern);patternBuilder.append(')');String variableName = match.substring(1, colonIdx);this.variableNames.add(variableName);}}end = matcher.end();}// 没有 glob pattern 语法,完整字符串匹配if (end == 0) {this.exactMatch = true;this.pattern = null;}else {this.exactMatch = false;patternBuilder.append(quote(pattern, end, pattern.length()));this.pattern = Pattern.compile(patternBuilder.toString(),Pattern.DOTALL | (this.caseSensitive ? 0 : Pattern.CASE_INSENSITIVE));}
}

由于 glob pattern 匹配语法与 java 正则语法有区别,此处将 glob pattern 语法做了转化,转化为 java 的正则语法,再进行了编译,赋值给 AntPathStringMatcher.pattern 属性。

此处的 quote 方法是采用了 java Pattern#quote 中的处理,对不需要匹配的字符串引用起来,不看做正则表达式,比如:".class" 引用完之后,变成 "\Q.class\E",这样字符串中的字符 '.' 就不会看作正则中的特殊字符。

循环时,采用 Matcher#find 方法,此方法会找出参数 pattern 中的下一个子序列与 GLOB_PATTERN,进行匹配,比如:"*.class",执行 Matcher#find 方法后,再执行 Matcher#group 会捕获到 "*",此时经过转化变成 ".*",最后拼接,重新编译去匹配的正则表达式就是:".*\Q.class\E"。

// org.springframework.util.AntPathMatcher$AntPathStringMatcher
public boolean matchStrings(String str, @Nullable Map<String, String> uriTemplateVariables) {// 精确匹配if (this.exactMatch) {return this.caseSensitive ? this.rawPattern.equals(str) : this.rawPattern.equalsIgnoreCase(str);}// 正则匹配else if (this.pattern != null) {Matcher matcher = this.pattern.matcher(str);if (matcher.matches()) {if (uriTemplateVariables != null) {if (this.variableNames.size() != matcher.groupCount()) {throw ...}for (int i = 1; i <= matcher.groupCount(); i++) {String name = this.variableNames.get(i - 1);if (name.startsWith("*")) {throw ...}String value = matcher.group(i);uriTemplateVariables.put(name, value);}}return true;}}return false;
}

执行完准备工作,待到字符串具体匹配时,就很简单了,是精确匹配,默认大小写敏感,此时调用 equals 方法判断两个字符串是否相等,非精确匹配,此时已经将 glob pattern 转化为 java 的正则匹配,直接调用 Matcher#matches 方法即可。

下面来看下 AntPathMatcher#doMatch 的匹配逻辑:

// AntPathMatcher
@Override
public boolean match(String pattern, String path) {return doMatch(pattern, path, true, null);
}
// 匹配
protected boolean doMatch(String pattern, @Nullable String path, boolean fullMatch,@Nullable Map<String, String> uriTemplateVariables) {// path 路径不存在或者和 pattern 的开头不一致,直接返回 falseif (path == null || path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) {return false;}// 分割 patternString[] pattDirs = tokenizePattern(pattern);// caseSensitive true 默认大小写敏感// isPotentialMatch 匹配到 pattDirs 中通配符就返回了if (fullMatch && this.caseSensitive && !isPotentialMatch(path, pattDirs)) {return false;}// 分割 pathString[] pathDirs = tokenizePath(path);int pattIdxStart = 0;int pattIdxEnd = pattDirs.length - 1;int pathIdxStart = 0;int pathIdxEnd = pathDirs.length - 1;// Match all elements up to the first **while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {String pattDir = pattDirs[pattIdxStart];// pattDir 是 "**" 时跳出循环if ("**".equals(pattDir)) {break;}// 字符串匹配,不匹配直接返回 falseif (!matchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables)) {return false;}// 对于匹配过的,都执行自增pattIdxStart++;pathIdxStart++;}// path 已经耗尽if (pathIdxStart > pathIdxEnd) {// Path is exhausted, only match if rest of pattern is * or **'s// pattern 也耗尽,判断 pattern 和 path 结尾是否都以 pathSeparator 结尾if (pattIdxStart > pattIdxEnd) {return (pattern.endsWith(this.pathSeparator) == path.endsWith(this.pathSeparator));}// pattern 未耗尽,fullMatch 为 false 直接返回// fullMatch 传参为 true,此处并不会返回if (!fullMatch) {return true;}// path 耗尽,pattern 也走到了结尾,判断 pattern 末端是否为 "*",并且此时 path 以 pathSeparator 结尾if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) {return true;}// path 耗尽,pattern 还存在,只能是 "**",不为 "**" 直接返回 false,不匹配for (int i = pattIdxStart; i <= pattIdxEnd; i++) {if (!pattDirs[i].equals("**")) {return false;}}return true;}// pattern 耗尽,直接返回 false,因为 path 耗尽的情况上面已经分析了,此处即 path 还存在else if (pattIdxStart > pattIdxEnd) {// String not exhausted, but pattern is. Failure.return false;}// fullMatch 为 false,最前面 pattern 遇到 "**" 跳出循环,path 和 pattern 都未耗尽,此时 pattern 为 "**",返回true// 执行时传参 fullMatch 为 true,所以此处判断直接跳过else if (!fullMatch && "**".equals(pattDirs[pattIdxStart])) {// Path start definitely matches due to "**" part in pattern.return true;}// path 和 pattern 都未耗尽,pattern 遇到了 "**",跳出了前面的 while 循环// up to last '**'while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {// 取最后一个 patternString pattDir = pattDirs[pattIdxEnd];// 为 "**" 跳出循环if (pattDir.equals("**")) {break;}// 末尾字符串匹配,不匹配直接返回 falseif (!matchStrings(pattDir, pathDirs[pathIdxEnd], uriTemplateVariables)) {return false;}// 对于匹配过的,都执行自减pattIdxEnd--;pathIdxEnd--;}// pattern 末尾和 path 一直能匹配上,但 path 的 idxEnd 已经小于 idxStart,说明 path 已耗尽//示例:pattern: "a/b/c/**/e/f/g",只有 e 和 f 都变为 **,才匹配//      path:    "a/b/c/g"	if (pathIdxStart > pathIdxEnd) {// String is exhausted// 剩下的 pattern 只有全为 "**" 才能满足匹配,否则返回 false,不匹配for (int i = pattIdxStart; i <= pattIdxEnd; i++) {if (!pattDirs[i].equals("**")) {return false;}}return true;}// pattern 短,path 长,只能是走到了 pattern 的最后一个 "**" 退出了循环,所以不存在 pattern 耗尽// 举例:// pattern: a/b/c/**/e/f/g/**/h/j     pattIdxStart 3  pattIdxEnd 7// path:    a/b/c/e/f/g/h/j           pathIdxStart 3  pathIdxEnd 5// pattIdxStart != pattIdxEnd 才有中间部分需要匹配while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) {int patIdxTmp = -1;// pattIdxStart 为 "**",所以 i 从 pattidxStart + 1 开始,找下一个 "**"// pattern: a/b/c/**/e/f/g/**/**/h/j    patIdxTmp 为 7for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) {if (pattDirs[i].equals("**")) {patIdxTmp = i;break;}}// "**/**" 这种情况,跳过一个,即 pattIdxStart 自增,接着执行下一次循环if (patIdxTmp == pattIdxStart + 1) {// '**/**' situation, so skip onepattIdxStart++;continue;}// Find the pattern between padIdxStart & padIdxTmp in str between// strIdxStart & strIdxEnd// 剩余 pattern 长度,即第一个和第二个,两个 "**" 之间 pattern 的个数int patLength = (patIdxTmp - pattIdxStart - 1);// 剩余 path 未匹配的个数int strLength = (pathIdxEnd - pathIdxStart + 1);int foundIdx = -1;// path 剩余长度 大于 pattern 长度,直接返回false// 只有 strLength >=  pattern 长度,才会进入循环// strLength - patLength 即最大循环多少次,因为 "**" 可以匹配 path 中的子路径,即 path 子路径比 pattern 多出的部分// 举例:// pattern: a/b/c/**/e/f/g/**/h/j         pattIdxStart 3   pattIdxEnd 7   patIdxTmp 7   patLength = 3// path:    a/b/c/m/n/e/f/g/h/j		      pathIdxStart 3   pathIdxEnd 7				    strLength = 5// pattern: a/b/c/**/e/f/g/**/**/h/j      pattIdxStart 3   pattIdxEnd 8   patIdxTmp 7   patLength = 3// path:    a/b/c/m/n/e/f/g/h/j		      pathIdxStart 3   pathIdxEnd 7				    strLength = 5// pattern: a/b/c/**/e/f/**/g/**/**/h/j   pattIdxStart 3   pattIdxEnd 9   patIdxTmp 6   patLength = 2   // path:	a/b/c/m/n/e/f/g/h/j           pathIdxStart 3   pathIdxEnd 7                 strLength = 5    foundIdx = 3 + 2 = 5// 匹配完更新 pattIdxStart 6   pathIdxStart = 5 + 2 = 7,接着再次进入循环,此时 patIdxTmp 8,计算出 patLength = 8-6-1=1,strLength = 7-7+1=1,即就剩一个未匹配了// 匹配完更新 foundIdx = 7+0=7,pattIdxStart = 8 pathIdxStart = 7 + 1 = 8,再次进入循环,发现 pathIdxStart > pathIdxEnd,path已耗尽,退出循环strLoop:for (int i = 0; i <= strLength - patLength; i++) {for (int j = 0; j < patLength; j++) {// pattIdxStart 此时为 "**", +1 即从下一个开始String subPat = pattDirs[pattIdxStart + j + 1];String subStr = pathDirs[pathIdxStart + i + j];// 不匹配,进行下一次循环// 因为 patternIdxStart 为 "**",可以匹配 path 中的子路径,所以即使字符串匹配返回 false,也不能认为不匹配,只需进行下一次循环即可if (!matchStrings(subPat, subStr, uriTemplateVariables)) {continue strLoop;}}// 执行到此处,表明字符串匹配都成功了,退出循环// foundIdx = pathIdxStart + 子路径多出部分长度,即从这里开始与 pattern 匹配上了foundIdx = pathIdxStart + i;break;}// 循环完都匹配不上,直接返回 falseif (foundIdx == -1) {return false;}// 更新 pattIdxStart 和 pathIdxStart// pattern 更新到最后一个 "**"pattIdxStart = patIdxTmp;// pathIdxStart 相当于等于 原pathIdxStart + 子路径部分长度 + pattern 部分长度pathIdxStart = foundIdx + patLength;}// 首尾部分都匹配完了,此时如果还剩下 pattern,只能为 "**",否则不匹配,返回 false// pattern: a/b/c/**/d/e/**/**/**/k/f/g// path:    a/b/c/m/n/d/e/k/f/g// 执行到此处还有一种可能,就是 pattIdxStart == pattIdxEnd,并且为 "**",此时返回 true// pattern: a/b/c/**// path:    a/b/c/e/f/g    此时也匹配for (int i = pattIdxStart; i <= pattIdxEnd; i++) {if (!pattDirs[i].equals("**")) {return false;}}return true;
}

代码比较长,归纳总结其实现逻辑如下:

  1. pattern 和 path 从前匹配,匹配上了,更新 pattIdxStart 和 pathIdxStart,直到 pattern 遇见 **,退出循环
  2. 此时判断 pattern 和 path 是否耗尽,做出相应处理
  3. 都未耗尽,pattern 是遇到了第一个 ** 跳出了循环,此时从后向前匹配,匹配上了,更新 pattIdxEnd 和 pathIdxEnd,直到遇见 **,退出循环,此时还存在一种退出循环的可能,就是 path 耗尽了,pathIdxStart > pathIdxEnd。为什么不存在 pattern 耗尽的情况呢?pattern 耗尽,即 pattern 较短,path 较长,此时从上面已经得到了一个 **,只能是 pattern 遇见了 ** 退出循环,否则肯定是匹配不上的。举个例子,pattern:a/b/c/**/e/f,path:a/b/c/m/n/e/f
  4. 此时,pattIdxStart != pattIdxEnd,表示在首尾两个 ** 之间存在中间部分,继续循环,此时从 pattIdxStart + 1 开始,查找下一个 **,记录其索引 patIdxTmp,因为在首尾两个 ** 之间还可能存在 **,对于 **/** 这种情况,使 pattIdxStart 自增,进行下一次循环,直到pattIdxStart 和 patIdxTmp 之间存在其它部分,此时计算出 pattIdxStart 和 patIdxTmp 之间剩余多少需要匹配,由于 pattIdxStart 和 patIdxTmp 都对应 **,所以 patIdxTmp - pattIdxStart 之后还需要再减 1,即剔除 pattIdxStart。而对于 path,剩余未匹配的部分则为 pathIdxEnd - pathIdxStart,再加 1,包含 pathIdxStart。strLength - patLength,即最大可以存在几个子路径片段,因为 pattIdxStart 对应的 ** 可以匹配子路径,匹配完,计算一个 foundIdx,即 pathIdxStart + i,此处 i 表示从 pathIdxStart 开始,第几个索引对应的 path 片段开始与 pattern 片段匹配,i 从 0 开始。从这里开始与 pattern 片段匹配,之后更新 pattIdxStart = patIdxTmp,pathIdxStart = foundIdx + patLength,接着进行下一次循环
  5. 首尾,中间部分都匹配完了,一旦执行到了此处,存在的 pattern 剩余部分只能是 **

至此,就完成了路径匹配的核心逻辑。

下面的一些代码,在 AntPathMatcher#doMatch 中虽然也调用到了,权当了解。

protected String[] tokenizePattern(String pattern) {String[] tokenized = null;Boolean cachePatterns = this.cachePatterns;if (cachePatterns == null || cachePatterns.booleanValue()) {// tokenizedPatternCache 缓存获取tokenized = this.tokenizedPatternCache.get(pattern);}if (tokenized == null) {tokenized = tokenizePath(pattern);if (cachePatterns == null && this.tokenizedPatternCache.size() >= CACHE_TURNOFF_THRESHOLD) {// 超过阈值,关闭缓存deactivatePatternCache();return tokenized;}if (cachePatterns == null || cachePatterns.booleanValue()) {this.tokenizedPatternCache.put(pattern, tokenized);}}return tokenized;
}protected String[] tokenizePath(String path) {// 使用 java.util.StringTokenizerreturn StringUtils.tokenizeToStringArray(path, this.pathSeparator, this.trimTokens, true);
}// StringUtils
public static String[] tokenizeToStringArray(@Nullable String str, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) {if (str == null) {return EMPTY_STRING_ARRAY;}StringTokenizer st = new StringTokenizer(str, delimiters);List<String> tokens = new ArrayList<>();while (st.hasMoreTokens()) {String token = st.nextToken();if (trimTokens) {token = token.trim();}// 是否忽略空 tokenif (!ignoreEmptyTokens || token.length() > 0) {tokens.add(token);}}return toStringArray(tokens);
}// 可以认为基本上都是潜在的匹配
private boolean isPotentialMatch(String path, String[] pattDirs) {if (!this.trimTokens) {// path 中当前字符索引,从 0 开始int pos = 0;for (String pattDir : pattDirs) {// 跳过路径分隔符int skipped = skipSeparator(path, pos, this.pathSeparator);pos += skipped;skipped = skipSegment(path, pos, pattDir);// skipped < pattDir.length() 表明 pattDir 中存在通配符,否则 skipped 长度应与 pattDir.length() 长度相等if (skipped < pattDir.length()) {return (skipped > 0 || (pattDir.length() > 0 && isWildcardChar(pattDir.charAt(0))));}pos += skipped;}}return true;
}// 跳过分隔符
private int skipSeparator(String path, int pos, String separator) {int skipped = 0;// 以分隔符开头,跳过分隔符while (path.startsWith(separator, pos + skipped)) {skipped += separator.length();}return skipped;
}// abc*
// 跳过片段
private int skipSegment(String path, int pos, String prefix) {int skipped = 0;for (int i = 0; i < prefix.length(); i++) {char c = prefix.charAt(i);// 通配符字符,三个 '*'、'?'、'{'if (isWildcardChar(c)) {return skipped;}// path 中当前字符索引int currPos = pos + skipped;if (currPos >= path.length()) {return 0;}// 逐个字符比较,字符一致,skipped 自增if (c == path.charAt(currPos)) {skipped++;}}return skipped;
}

自 spring 5.0 开始,在 spring web 应用中还提供了另一个路径匹配处理类,PathPattern,语法与AntPathMatcher 类似。该解决方案专为 web 使用而设计,可以有效地处理编码和路径参数,并有效地匹配。

PathPattern是web应用的推荐解决方案,也是Spring WebFlux的唯一选择。它从5.3版开始在Spring MVC中启用,从6.0版开始默认启用。而 AntPathMatcher 主要用于选择类路径、文件系统和其他位置上的资源。

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

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

相关文章

基于PID控制器的双容控制系统matlab仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 4.1PID控制器的基本原理 4.2双容水箱系统的数学模型 5.完整工程文件 1.课题概述 基于PID控制器的双容控制系统matlab仿真&#xff0c;仿真输出PID控制下的水位和流量两个指标。 2.系统仿真结果 &…

Jetpack Compose实现一个简单的微信UI

https://blog.csdn.net/News53231323/article/details/128509048 https://franzliszt1847.blog.csdn.net/article/details/129344822

Nginx七层(应用层)反向代理:SCGI代理scgi_pass篇

Nginx七层&#xff08;应用层&#xff09;反向代理 SCGI代理scgi_pass篇 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this art…

MATLAB——字符串处理

文章目录 MATLAB——字符串处理字符串处理函数字符串或字符串数组构造 MATLAB——字符串处理 字符串处理函数 MATLAB中的字符串处理函数如下&#xff1a; 函数名称说明eval(string)作为一个MATLAb命令求字符串的值blanks(n)返回一个具有n个空格的字符串deblank去掉字符串末尾…

Python进行数据分析:从基础到实践

数据分析是现代数据驱动决策的重要工具,Python以其强大的数据处理和分析库成为数据分析的首选编程语言。本文将介绍Python进行数据分析的基础知识、常用库以及一个完整的实战项目,帮助读者从基础入门到实际应用。 目录 数据分析概述Python中的数据分析库 NumPypandasMatplot…

ENSP防火墙

实验拓扑图 需求&#xff1a; ENSP的配置&#xff1a; 防火墙&#xff1a; 交换机&#xff1a; 华为防火墙的配置&#xff1a; 接口配置&#xff1a; 安全区域&#xff1a; 安全策略&#xff1a; 办公区访问DMZ&#xff1a; 生产区访问DMZ&#xff1a; 游客区只能访问门户网…

[人工智能]对未来建筑行业的影响

作者主页: 知孤云出岫 目录 引言1. 人工智能在建筑行业的应用场景1.1 设计阶段1.2 施工阶段1.3 运营和管理 2. 关键技术2.1 机器学习2.2 计算机视觉2.3 自然语言处理2.4 大数据分析 3. 实际案例分析3.1 案例1&#xff1a;利用GAN生成建筑设计方案3.2 案例2&#xff1a;利用计算…

操作User表的CRUD增删改查(二):修改和删除

文章目录 修改运行发现数据库数据没有添加进去 修改 int u session.update(s, new User(1,"xiaoxiao",18));运行发现数据库数据没有添加进去 运行发现数据库没有添加进去数据&#xff0c;原因是默认是手动添加的&#xff0c;需要修改。 有两种方法然后再重新运行&a…

如何ssh远程Windows电脑

参考&#xff1a;https://www.jianshu.com/p/1321b46b40ee 上述教程中&#xff0c;直接根据微软的教程进行openssh安装 遇到的问题 远程windows电脑需要具备什么条件&#xff1f; 需要Windows电脑上安装了openssh server 远程Windows电脑的话&#xff0c;用户怎么创建&…

图像分割评测指标,dice,voe,ASD,RVD(学习)

图像分割是计算机视觉领域中的一个核心任务&#xff0c;它涉及到识别并分离图像中的特定对象或区域。在研究和应用中&#xff0c;为了评估图像分割算法的性能&#xff0c;通常会使用一系列的评测指标。这些指标可以帮助我们量化分割结果与实际目标之间的差异&#xff0c;从而指…

[Linux][Shell][Shell函数]详细讲解

目录 1.创建函数2.执行函数3.给函数传入参数4.返回值5.函数中处理变量1.全局变量2.局部变量 1.创建函数 语法&#xff1a;关键字 或 C风格function name {commands }name() {commands }2.执行函数 执行Shell函数&#xff0c;直接写函数名即可&#xff0c;无需添加其他内容函…

构建Memcached帝国:分布式部署策略与实践指南

构建Memcached帝国&#xff1a;分布式部署策略与实践指南 Memcached作为一个高性能的分布式内存缓存系统&#xff0c;在面对大规模分布式部署时&#xff0c;需要考虑一系列的策略和最佳实践来确保系统的稳定性和效率。本文将深入探讨Memcached分布式部署的注意事项&#xff0c…

教育与社会的发展

生产力与教育的关系 政治经济制度与教育的关系 文化和人口与教育的关系

whereis命令是 Linux 和类 Unix 系统中的一个命令行工具,用于定位二进制程序、源代码和手册页(man pages)的位置

文章目录 1、whereis2、实例 1、whereis whereis 命令是 Linux 和类 Unix 系统中的一个命令行工具&#xff0c;用于定位二进制程序、源代码和手册页&#xff08;man pages&#xff09;的位置。当你想要快速找到某个程序或命令的安装位置时&#xff0c;whereis 命令会非常有用。…

跨数据中心复制:Memcached的高效数据同步策略

跨数据中心复制&#xff1a;Memcached的高效数据同步策略 在当今的云计算和大数据时代&#xff0c;跨数据中心的数据复制是确保数据高可用性和灾难恢复的关键技术。Memcached作为一个高性能的分布式内存缓存系统&#xff0c;虽然本身不提供跨数据中心复制的功能&#xff0c;但…

YOLOv10改进 | 添加注意力机制篇 | 添加LSKAttention大核注意力机制助力极限涨点

一、本文介绍 在这篇文章中&#xff0c;我们将讲解如何将LSKAttention大核注意力机制应用于YOLOv10&#xff0c;以实现显著的性能提升。首先&#xff0c;我们介绍LSKAttention机制的基本原理&#xff0c;它主要通过将深度卷积层的2D卷积核分解为水平和垂直1D卷积核&#xff0…

方便好用的C#.Net万能工具库Masuit.Tools

文章目录 简介开发环境安装使用特色功能示例代码1. 检验字符串是否是Email、手机号、URL、IP地址、身份证号等2.硬件监测(需要管理员权限&#xff0c;仅支持Windows&#xff0c;部分函数仅支持物理机模式)3.html的防XSS处理&#xff1a;4.整理Windows系统的内存&#xff1a;5.任…

Web应用安全扫描工具——Wfuzz

Web应用安全扫描工具——Wfuzz 简介 Wfuzz 是一个开源的 Web 应用程序模糊测试工具。它主要用于发现 Web 应用程序中的各种漏洞和隐藏资源。通过向目标发送大量不同的请求&#xff0c;识别出潜在的安全问题&#xff0c;如目录和文件暴露、参数注入、文件上传、认证绕过等。 …

如何搭建互联网医院系统源码?医疗陪诊APP开发实战详解

今天&#xff0c;小编将为大家讲解如何搭建一个完整的互联网医院系统源码&#xff0c;并介绍医疗陪诊APP的开发实战。 一、互联网医院系统的架构设计 搭建一个完整的互联网医院系统&#xff0c;需要从架构设计开始。一个典型的互联网医院系统通常包含以下几个核心模块&#xf…

PyTorch人脸检测

新书速览|PyTorch深度学习与企业级项目实战-CSDN博客 人脸检测解决的问题是确定一幅图上有没有人脸&#xff0c;而人脸识别解决的问题是这张脸是谁的。可以说人脸检测是人脸识别的前期工作。这里介绍Dlib库&#xff0c;它提供了Python接口&#xff0c;里面有人脸检测器&#x…