现一个智能的SQL编辑器

补给资料    管注公众号:码农补给站     

前言

目前我司的多个产品中都支持在线编辑 SQL 来生成对应的任务。为了优化用户体验,在使用 MonacoEditor 为编辑器的基础上,我们还支持了如下几个重要功能:

  • 多种 SQL 的语法高亮
  • 多种 SQL 的报错提示(错误位置飘红)
  • 多种 SQL 的自动补全(智能提示)

本文旨在讲解上述功能的实现思路,对于技术细节,由于篇幅原因不会阐述的太详细。

Monaco Languages

Monaco Editor 内置的 languages

Monaco Editor 内置了相当多的 languages,比如 javaScriptCSSShell 等。

Monaco Editor 依赖包的 ESM 入口文件为 ./esm/vs/editor/editor.main.ts

而在入口文件中,Monaco Editor 引入了所有内置的 Languages。

这里 languages 文件可以分为两类,一类是../language文件夹下的,支持自动补全和飘红提示等高级功能;另一类则是../basic-languages文件夹下的,只支持一些基本功能。

使用内置的 Language 功能

以使用 typescript language 为例:

 
import { editor } from 'monaco-editor';const container = document.getElementById('container');editor.create(container, {language: 'typescript'
})

此时我们会发现,编辑器已经有语法高亮的功能了,但是浏览器控制台会抛异常,另外也没有自动补全功能和飘红提示功能,

这其实是因为,Monaco Editor 无法加载到 language 对应的 worker,对应的解决办法看这里: Monaco integrate-esm。

这里我们使用 Using plain webpack的方式,首先将对应的 worker 文件设置为 webpack entry:

 
module.exports = {entry: {index: path.resolve( __dirname, './src/index.ts'),'editor.worker': 'monaco-editor/esm/vs/editor/editor.worker.js','ts.worker': 'monaco-editor/esm/vs/language/typescript/ts.worker.js'},
}

另外还需要设置 Monaco Editor 的全局环境变量,这主要是为了告诉 Monaco Editor 对应的 worker 文件的路径

 
import { editor } from 'monaco-editor';(window as any).MonacoEnvironment = {getWorkerUrl: function (_moduleId, label) {switch (label) {case 'flink': {return './flink.worker.js';}case 'typescript': {return './ts.worker.js'}default: {return './editor.worker.js';}}}
};const container = document.getElementById('container');editor.create(container, {language: 'typescript'
})

这样一个具有语法高亮自动补全飘红提示 功能的 typescript 编辑器就设置好了

小结分析

首先上文中提到了当我们直接从 Monaco Editor 的入口文件中导入时,会自动的引入所有内置的 Languages,但是实际上这其中绝大都是我们不需要的,而由于其导入方式,很显然我们不需要的 languages 也无法被 treeShaking。要解决这个问题我们可以选择从 monaco-editor/esm/vs/editor/editor.api 文件中导入Monaco Editor 核心 API,然后通过 monaco-editor-webpack-plugin 来按需导入所需要的功能。另外这个插件也可以自动处理Monaco Editor 内置的 worker 文件的打包问题,以及自动注入 MonacoEnvironment全局环境变量。

自定义 Language

注册Language

Monaco Editor 提供了 monaco.languages.register方法,用来自定义 language

 
/*** Register information about a new language.*/
export function register(language: ILanguageExtensionPoint): void;export interface ILanguageExtensionPoint {id: string;extensions?: string[];filenames?: string[];filenamePatterns?: string[];firstLine?: string;aliases?: string[];mimetypes?: string[];configuration?: Uri;
}

第一步,我们需要注册一个 language, 配置项中 id 对应的就是语言名称(其他配置项可以暂时不填),这里自定义的 language 名为 myLang

 
import { editor, languages } from 'monaco-editor';languages.register({id: "myLang"
});const container = document.getElementById('container');editor.create(container, {language: 'myLang'
})

此时可以发现,页面上的编辑器没有任何其他附加功能,就是普通的文本编辑器。

设置 Language

通过 monaco.languages.setLanguageConfiguration,可以对 language 进行配置

 
/*** Set the editing configuration for a language.*/
export function setLanguageConfiguration(languageId: string,configuration: LanguageConfiguration
): IDisposable;/*** The language configuration interface defines the contract between extensions and* various editor features, like automatic bracket insertion, automatic indentation etc.*/
export interface LanguageConfiguration {comments?: CommentRule;brackets?: CharacterPair[];wordPattern?: RegExp;indentationRules?: IndentationRule;onEnterRules?: OnEnterRule[];autoClosingPairs?: IAutoClosingPairConditional[];surroundingPairs?: IAutoClosingPair[];colorizedBracketPairs?: CharacterPair[];autoCloseBefore?: string;folding?: FoldingRules;
}

这些配置会影响 Monaco Editor 的一些默认行为,比如设置 autoClosingPairs中有一项为一对圆括号,那么当输入左圆括号后,会自动补全右圆括号。

 
import { languages } from "monaco-editor";
const conf: languages.LanguageConfiguration = {comments: {lineComment: "--",blockComment: ["/*", "*/"],},brackets: [["(", ")"],],autoClosingPairs: [{ open: "(", close: ")" },{ open: '"', close: '"' },{ open: "'", close: "'" },],surroundingPairs: [{ open: "(", close: ")" },{ open: '"', close: '"' },{ open: "'", close: "'" },],
};languages.setLanguageConfiguration('myLang', conf)

高亮功能

Monarch

Moanco Editor 内置了 Monarch,用于实现语法高亮功能,它本质上是一个有限状态机,我们可以通过JSON的形式来配置其状态流转逻辑,并通过monaco.languages.setMonarchTokensProvider API 应用该配置。关于Monarch 的具体用法可以看一下这篇文章 以及 Monarch Document。

配置中最重要的是 tokenizer属性,意思是分词器,分词器会自动对编辑器内部的文本进行分词处理,每个分词器都有一个 root state,在 root state 中可以有多条规则,规则内部可以引用其他 state。

下面是一个简单的配置示例

 
import { languages } from "monaco-editor";
export const language: languages.IMonarchLanguage = {ignoreCase: true,tokenizer: {root: [{ include: '@comments' }, // 引用下面的 comments 规则{ include: '@whitespace' }, // 引用下面的 whiteSpace 规则{ include: '@strings' },// 引用下面的 strings 规则],whitespace: [[/\s+/, 'white']],comments: [[/--+.*/, 'comment'],[//*/, { token: 'comment.quote', next: '@comment' }]],comment: [[/[^*/]+/, 'comment'],[/*//, { token: 'comment.quote', next: '@pop' }],[/./, 'comment']],strings: [[/'/, { token: 'string', next: '@string' }]],string: [[/[^']+/, 'string'],[/''/, 'string'],[/'/, { token: 'string', next: '@pop' }]],}
};languages.setMonarchTokensProvider("myLang", language);

上面的配置中 root 下面有三条规则分别匹配 注释(comments)字符串(strings) 以及空白字符(whiteSpace), 每条规则可以大体分为两部分:

  • 匹配方式,比如说正则
  • 对应的 token 类型(任意字符串)

比如上述配置中 tokenizer.comments 规则

 
comments: [[/--+.*/, 'comment'], // 左边是正则表达式用来匹配文本,右边是该规则对应的 token 名称[//*/, { token: 'comment.quote', next: '@comment' }] // 左边是正则表达式用来匹配文本,右边显示声明对应的 token 名称
],

配置了如上 Monarch 之后,在编辑器内部输入注释或者字符串,那么Monaco editor 就会根据输入的内容进行分词处理

可以看到目前字符串和注释已经被高亮了。这里有一个新的问题,不同类型的分词的颜色是怎么设置的

Monaco Theme

从上图中右侧的 Elements 面板中可以看到,不同类型的分词,对应的标签的 className 不同,它们是由 Monarch 配置中的 token 映射而来的。MonacoEditor 内置了一些 Theme,默认的 Theme 是 vs,而默认的 theme 中已经设置了上述 Monarch 中的 token 对应的颜色,所以我们应用上述配置后,对应的分词直接就有了高亮颜色。

我们可以通过 monaco.editor.defineTheme 来定义一种新的 theme,如下例所示:

 
editor.defineTheme('myTheme', {base: 'vs',inherit: true,rules: [{ token: 'comment', foreground: 'ff4400' },{ token: 'string', foreground: '0000ff' }],colors: {},
});// xxxxeditor.create(container, {language: "myLang",theme: "myTheme"
});

这里将注释设置为红色,字符串设置为蓝色,显示效果如下图所示

飘红提示

飘红提示的功能就是在代码错误的位置打上标记(一般是红色波浪线),可以通过 monaco.editor.setModelMarkers API 来实现。比如我们想为 第1行的第1个字符到第2行的第2个字符 之间打上错误标记:

 
const editorIns = editor.create(container, {language: "myLang",theme: "myTheme",value: `hello
world`
});const model = editorIns.getModel();editor.setModelMarkers(model, 'myLang', [{startLineNumber: 1,startColumn: 1,endLineNumber: 2,endColumn: 2,message: "语法错误",severity: MarkerSeverity.Error}
])

severity 是标记类型,message 是提示信息,效果如下所示。

到此为止,实现了飘红的功能,但是没有实现在语法错误处飘红的功能,这需要额外的语法解析器支持,会在下文中讲到。

自动补全功能

Monaco Editor 提供了 monaco.languages.registerCompletionItemProvider API 来实现自动补全功能

 
import { editor, languages, MarkerSeverity, Position, CancellationToken, Range  } from "monaco-editor";languages.registerCompletionItemProvider('myLang', {triggerCharacters: ['.', '*'],provideCompletionItems(model: editor.IReadOnlyModel,position: Position,context: languages.CompletionContext,token: CancellationToken){const wordInfo = model.getWordUntilPosition(position);const wordRange = new Range(position.lineNumber,wordInfo.startColumn,position.lineNumber,wordInfo.endColumn);return new Promise((resolve) => {resolve({suggestions: [{label: "SELECT",kind: languages.CompletionItemKind.Keyword,insertText: "SELECT",range: wordRange,detail: '关键字',},{label: "SET",kind: languages.CompletionItemKind.Keyword,insertText: "SET",range: wordRange,detail: '关键字',},{label: "SHOW",kind: languages.CompletionItemKind.Keyword,insertText: "SHOW",range: wordRange,detail: '关键字',},]})})}
})

registerCompletionItemProvider 接受两个参数,第一个参数是 languageId 也就是 language 名称,

第二个参数是一个 CompletionItemProviderCompletionItemProvidertriggerCharacters用来配置触发自动补全的字符有哪些,而 provideCompletionItems则是一个函数,它接收 Monaco Editor 提供的当前的上下文信息,返回自动补全项列表。如上例中返回了三个自动补全项,那么当我们在编辑器中输入 S时,就会出现配置的自动补全项候选菜单。

通过这个 API 我们可以实现一种语言的关键字自动补全,只需要在CompletionItemProvider中返回该语言所有的关键字对应的自动补全项即可。

但是registerCompletionItemProvider目前做不到根据语义进行自动补全。

比如用户写一段 flinkSQL,当用户输入完 CREATE 关键字并按下空格后,应该出现的自动补全项应该是只有TABLECATALOGDATABASEFUNCTIONVIEW

再比如当用户输入 SELECT * FROM 时,后面应该提示表名而不是其他无关的关键字。与上文中的飘红提示一样,这些语义信息需要单独的语法解析器来分析。

小结分析

到此为止,在自定义 language 这一节中,我们已经了解了,在 Monaco Editor 中如何实现自定义语言的 语法高亮错误处飘红提示自动补全

在数栈产品中,本节讲到的功能都通过引入 monaco-sql-languages 依赖来实现,这是我们数栈 UED 团队自研的开源项目,目前已经支持多种 SQL Languages。

由于目前为止没有实现自定义 language 的语义分析功能,导致目前实现的编辑器不够智能。 另外,对于第一节中提到的 web worker ,在第二节中也没有有提到,实际上 Monaco Editor 自带的 web worker,也都是为了实现 language 的语义分析功能,下一节将阐述这一部分内容。

SQL Parser

要实现语义分析功能,很显然我们需要一个语法解析器。除了基本的语法解析的基础功能以外,我们还需要

  • 语法错误收集,收集编辑器中文本的语法错误信息,用于错误飘红提示功能。
  • 推断文本中指定位置的候选项列表,对于编辑器来说,指定位置一般就是光标所在位置。候选项是指在光标所在的位置应该要写什么。比如 SQL 中 SELECT 关键字后面可以跟字段或者函数,那么我们所要实现的 sql parser 就应该提示出在 SELECT 关键字后面的候选项应该是字段或者函数。

实现基础的 SQL Parser

Antlr4 语法文件

我们使用 Antlr4 来实现一个基本的 SQL Parser。Antlr4 是一个强大的解析器生成器,它能根据用户自定义的语法文件来生成对应的解析器。Antlr4 的语法文件为 .g4文件,内部可以包含多条规则,规则可以分为词法规则和语法规则,词法规则用于生成词法分析器,语法规则用于生成语法解析器。

例,我们现在写一份语法规则,匹配最简单的 SELECT 语句(不包括子查询、别名等规则),比如

 
SELECT * FROM table1;  -- eg1SELECT table2.name, age FROM schema2.table2; -- eg2

那么在antlr4中这份语法文件应该这样写:

 
grammar SelectStatement;/** 语法规则 begin */
program: selectStatement? EOF;// 声明 语句的匹配规则
selectStatement: KW_SELECT columnGroup KW_FROM tablePath SEMICOLON?;// 声明 语句中字段部分的匹配规则,字段部分可能为 col1, col2 的形式
columnGroup: columnPath (COMMA columnPath)*;// 声明 字段名匹配规则,字段名有可能为 db.table.col 或者 * 的形式
columnPath: dot_id | OP_STAR; // 声明 表名匹配规则,表名有可能为 db.table 的形式
tablePath: dot_id; // 匹配 id.id 形式的标识符号
dot_id: IDENTIFIER_LITERAL (DOT IDENTIFIER_LITERAL)*; 
/** 语法规则 end */ /** 词法规则 begin */
KW_SELECT:          'SELECT'; // 匹配 SELECT 关键字
KW_FROM:            'FROM'; // 匹配 FROM 关键字
OP_STAR:            '*'; // 匹配 * 
DOT:                '.'; // 匹配 .
COMMA:              ','; // 匹配 ,
SEMICOLON:          ';'; // 匹配 ;
IDENTIFIER_LITERAL: [A-Z_a-z][A-Z_0-9a-z]*; // 匹配标识符WS:                 [ \t\n\r]+ -> skip ; // 忽略空格换行等空白字符
/** 词法规则 end */

语法规则的编写格式类似于 EBNF。

然后运行 antlr4 命令,根据所写的语法文件生成对应的解析器。可以直接使用官方文档中提供的方式 antlr4 typescript-target doc ,或者直接使用社区提供的 antlr4ts 包,这里以使用 antlr4ts 为例。

生成的文件结果如下所示:

使用 Antlr4 生成的 Parser

在使用Antlr4 的生成的 Parser 之前我们需要安装,Antlr4 的运行时包。你可以将 Antlr4 的运行时包通过语法文件生成的parser文件之间的关系,类比为 react 和 react-dom之间的关系。这里以使用 antlr4ts 为运行时

 
import { CommonTokenStream, CharStreams } from 'antlr4ts';
import { SelectStatementLexer } from '../lib/selectStatement/SelectStatementLexer';
import { SelectStatementParser } from '../lib/selectStatement/SelectStatementParser';class SelectParser {private createLexer(input: string) {const inputStream = CharStreams.fromString(input);const lexer = new SelectStatementLexer(inputStream);return lexer}private createParser (input: string) {const lexer = this.createLexer(input);const tokens = new CommonTokenStream(lexer);const parser = new SelectStatementParser(tokens);return parser}parse (sql: string) {const parser = this.createParser(sql)const parseTree = parser.selectStatement();return parseTree;}
}
// 试一下效果
const selectParser = new SelectParser();
const parseTree = selectParser.parse('SELECT * FROM table1');

获取文本中的错误信息

当解析一个含有错误的文本时,Antlr4 会输出错误信息,例如输入

 
selectParser.parse('SELECT id FRO');

控制台打印

可以看到错误信息中包含了文本中的错误所处的位置,我们可以通过使用 Antlr4 ParserErrorListener 来获取错误信息。

声明一个 ParserErrorListener

 
import { ParserErrorListener } from 'antlr4ts';export class SelectErrorListener implements ParserErrorListener {private _parserErrorSet: Set<any> = new Set();syntaxError(_rec, _ofSym, line, charPosInLine, msg) {let endCol = charPosInLine + 1;this._parserErrorSet.add({startLine: line,endLine: line,startCol: charPosInLine,endCol: endCol,message: msg,})}clear () {this._parserErrorSet.clear();}get parserErrors () {return Array.from(this._parserErrorSet) }
}

使用 ParserErrorListener 收集错误信息

 
import { CommonTokenStream, CharStreams } from 'antlr4ts';
import { SelectStatementLexer } from '../lib/selectStatement/SelectStatementLexer';
import { SelectStatementParser } from '../lib/selectStatement/SelectStatementParser';class SelectParser {private _errorListener = new SelectErrorListener();createLexer(input: string) {const inputStream = CharStreams.fromString(input);const lexer = new SelectStatementLexer(inputStream);this._errorListener.clear();lexer.removeErrorListeners(); // 移除 Antlr4 内置的 ErrorListenerlexer.addErrorListener(this._errorListener)return lexer}createParser (input: string) {const lexer = this.createLexer(input);const tokens = new CommonTokenStream(lexer);const parser = new SelectStatementParser(tokens);parser.removeErrorListeners(); // 移除 Antlr4 内置的 ErrorListenerparser.addErrorListener(this._errorListener);return parser}parse (sql: string) {const parser = this.createParser(sql)const parseTree = parser.selectStatement();console.log(this._errorListener.parserErrors);return {parseTree,errors: this._errorListener.parserErrors,};}
}
// 试一下效果
const selectParser = new SelectParser();
const { errors } = selectParser.parse('SELECT id FRO');
console.log(errors);

打印结果

这样我们就获取到了文本中的语法错误出现的位置,以及错误信息。

到此为止上文中遗留的第一个问题就已经差不多解决了,我们只需要在合适的时机将编辑器的内容进行解析,拿到错误信息并且通过 editor.setModelMarkers这个 API 让错误的位置飘红就大功告成了。

自动补全功能

对于自动补全功能,Antlr4 并没有直接提供,但是社区已经有了比较优秀的解决方案 - antlr-c3 。它的作用是根据Antlr4 Parser 的解析结果,分析指定位置填哪些词法/语法规则是合法的

antlr4-c3 的使用方式比较简单。

 
import { CodeCompletionCore } from "antlr4-c3";// 这里 parser 是 parser 实例
let core = new CodeCompletionCore(parser); 
// tokenIndex 是想要自动补全的位置,对应由编辑器的光标位置转换而来
// parserContext 则是解析完之后的返回的 ParserTree 或者 ParserTree 的子节点(传入子节点可以更高效)
let candidates = core.collectCandidates(tokenIndex, parserContext);

那么结合上文中写的 SelectParser,代码应该是这样

 
import { CodeCompletionCore } from "antlr4-c3";
import { SelectParser } from "./selectParser";/*** input 源文本* caretPosition 编辑器光标位置*/
function getSuggestions(input: string, caretPosition) {const selectParser = new SelectParser();const parserIns = selectParser.createParser(input)let core = new CodeCompletionCore(parserIns);const parserContext = parserIns.selectStatement();// 伪代码const tokenIndex = caretPosition2TokenIndex(caretPosition)let candidates = core.collectCandidates(tokenIndex, parserContext);
}

core.collectCandidates 的返回值的数据类型如下:

 
interface CandidatesCollection {tokens: Map<number, TokenList>;rules: Map<number, CandidateRule>;
}

tokens 对应的是词法规则提示,比如关键字等,rules 对应的是语法规则,比如上述语法文件中的 columnPathtablePath等。

需要注意的是,antlr4-c3 默认不收集语法规则,需要我们手动设置需要收集的语法规则

 
import { SelectStatementParser } from '../lib/selectStatement/SelectStatementParser';let core = new CodeCompletionCore(parserIns);core.preferredRules= new Set([SelectStatementParser.RULE_tablePath,SelectStatementParser.RULE_columnPath
])
// 设置需要收集 tablePath 和 columnPath

这样我们就收集到了在指定位置的可以填什么。接下来我们需要将结果进行转换成我们需要的数据结果

 
import { CodeCompletionCore } from "antlr4-c3";
import { SelectParser } from "./selectParser";
import { SelectStatementParser } from '../lib/selectStatement/SelectStatementParser';/*** input 源文本* caretPosition 编辑器光标位置*/
export function getSuggestions(input: string, caretPosition?: any) {const selectParser = new SelectParser();const parserIns = selectParser.createParser(input)let core = new CodeCompletionCore(parserIns);core.preferredRules= new Set([SelectStatementParser.RULE_tablePath,SelectStatementParser.RULE_columnPath])const parserContext = parserIns.selectStatement();const tokenIndex = caretPosition2TokenIndex(caretPosition);let candidates = core.collectCandidates(tokenIndex, parserContext);const rule = [];const keywords = []for (let candidate of candidates.rules) {const [ruleType] = candidate;let syntaxContextType;switch (ruleType) {case SelectStatementParser.RULE_tablePath: {syntaxContextType = 'table';break;}case SelectStatementParser.RULE_columnPath: {syntaxContextType = 'column';break;}default:break;}if (syntaxContextType) {rule.push(syntaxContextType)}}for (let candidate of candidates.tokens) {const symbolicName = parserIns.vocabulary.getSymbolicName(candidate[0]);const displayName = parserIns.vocabulary.getDisplayName(candidate[0]);if(symbolicName && symbolicName.startsWith('KW_')) {const keyword = displayName.startsWith("'") && displayName.endsWith("'")? displayName.slice(1, -1): displayNamekeywords.push(keyword);}}console.log('===== suggest keywords: ',keywords);console.log('===== suggest rules:', rule);
}

这样我们就拿到了要提示的关键字和语法规则。关键字可以直接用于生成自动补全项,语法规则可以用于提示表名、字段名等。

小结分析

在这一节中,我们已经了解了,如何使用 Antlr4 和 antlr4-c3 来实现更加智能的飘红提示以及自动补全功能。

这一部分功能,在 monaco-sql-languages 中通过引入数栈前端团队自研的开源项目 dt-sql-parser 实现。

前文中提到的 worker 文件也正是用于运行 sql parser,因为dt-sql-parser 的解析可能会比较耗时,为了避免阻塞用户交互,将 sql parser 放到 web worker 中运行显然是更明智的选择。


原文链接:https://juejin.cn/post/7297917491794984972
 

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

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

相关文章

垃圾回收系统小程序定制开发搭建攻略

在这个数字化快速发展的时代&#xff0c;垃圾回收系统的推广对于环境保护和可持续发展具有重要意义。为了更好地服务于垃圾回收行业&#xff0c;本文将分享如何使用第三方制作平台乔拓云网&#xff0c;定制开发搭建垃圾回收系统小程序。 首先&#xff0c;使用乔拓云网账号登录平…

服务器数据恢复—云服务器mysql数据库表被truncate的数据恢复案例

云服务器数据恢复环境&#xff1a; 阿里云ECS网站服务器&#xff0c;linux操作系统mysql数据库。 云服务器故障&#xff1a; 在执行数据库版本更新测试时&#xff0c;在生产库误执行了本来应该在测试库执行的sql脚本&#xff0c;导致生产库部分表被truncate&#xff0c;还有部…

六种最常见的软件供应链攻击

软件供应链攻击已成为当前网络安全领域的热点话题&#xff0c;其攻击方式的多样性和复杂性使得防御变得极为困难。以下我们整理了六种常见软件供应链攻击方法及其典型案例&#xff1a; 软件供应链攻击已成为当前网络安全领域的热点话题&#xff0c;其攻击方式的多样性和复杂性…

pytorch直线拟合

目录 1、数据分析 2、pytorch直线拟合 1、数据分析 直线拟合的前提条件通常包括以下几点&#xff1a; 存在线性关系&#xff1a;这是进行直线拟合的基础&#xff0c;数据点之间应该存在一种线性关系&#xff0c;即数据的分布可以用直线来近似描述。这种线性关系可以是数据点…

Oracle RAC是啥?

Oracle RAC&#xff0c;全称是Oracle Real Application Cluster&#xff0c;翻译过来为Oracle真正的应用集群&#xff0c;它是Oracle提供的一个并行集群系统&#xff0c;由 Oracle Clusterware&#xff08;集群就绪软件&#xff09; 和 Real Application Cluster&#xff08;RA…

C语言每日一题(25)链表的中间结点

力扣 876. 链表的中间结点 题目描述 给你单链表的头结点 head &#xff0c;请你找出并返回链表的中间结点。 如果有两个中间结点&#xff0c;则返回第二个中间结点。 思路分析 快慢指针法 用一慢一快指针遍历整个链表&#xff0c;每次遍历&#xff0c;快指针都会比慢指针多…

大文件传输小知识 | UDP和TCP哪个传输速度快?

在网络世界中&#xff0c;好像有两位“传输巨头”常常被提起&#xff1a;UDP和TCP。它们分别代表着用户数据报协议和传输控制协议。那么它们是什么&#xff1f;它们有什么区别&#xff1f;它们在传输大文件时的速度又如何&#xff1f;本文将深度解析这些问题&#xff0c;帮助企…

获奖名单出炉 ,鲲鹏应用创新大赛2023全国总决赛圆满落幕

11月2日&#xff0c;以“数智未来 因你而来”为主题的鲲鹏应用创新大赛2023全国总决赛在四川成都顺利举办。经过长达6个月的层层筛选与激烈角逐&#xff0c;最终从三大赛事、5大赛道中评选出了13个金奖、16个银奖、19个铜奖。 鲲鹏应用创新大赛是面向全球开发者的顶级赛事&…

软件开发中常见的设计原则

软件开发中常见的设计原则 1. 单一责任原则2. 开放封闭原则3. 里氏替换原则4. 接口分离原则5. 依赖倒置原则6. 迪米特法则7. 合成复用原则8. 共同封闭原则9. 稳定抽象原则10. 稳定依赖原则 简写全拼中文翻译SRPThe Single Responsibility Principle单一责任原则OCPThe Open Clo…

做什么数据表格啊,要做就做数据可视化

是一堆数字更易懂&#xff0c;还是图表更易懂&#xff1f;很明显是图表&#xff0c;特别是数据可视化图表。数据可视化是一种将大量数据转化为视觉形式的过程&#xff0c;通过图形、图表、图像等方式呈现数据&#xff0c;以便更直观地理解和分析。 数据可视化更加生动、形象地…

实战!工作中常用的设计模式

文章目录 前言一、策略模式1.1、 业务场景1.2 、策略模式定义1.3、 策略模式使用1.3.1、一个接口&#xff0c;两个方法1.3.2、不同策略的差异化实现1.3.3、使用策略模式 二、责任链模式2.1、业务场景2.2、责任链模式定义2.3、责任链模式使用2.3.1、一个接口或者抽象类2.3.2、每…

JavaScript爬虫程序爬取游戏平台数据

这次我用一个JavaScript爬虫程序&#xff0c;来爬取游戏平台采集数据和分析的内容。爬虫使用了爬虫IP信息&#xff0c;爬虫IP主机为duoip&#xff0c;爬虫IP端口为8000。以下是每行代码和步骤的解释&#xff1a; // 导入所需的库 const axios require(axios); const cheerio …