实现一个更快的终端彩色文本格式化工具库

news2025/4/25 12:55:14

终端中打印的五颜六色的彩色文本,你知道是怎么实现的吗,你都知道或用过哪些相关的工具库呢?来一起了解一下吧!

在前端项目开发中,说到控制台终端彩色文本格式化,你可能会想到 chalkpicocolorsansi-colors 甚至是 colors.js 等流行工具库。它们在 npmjs 上的周下载量都分别长期保持在千万到亿级别。彩色文本格式化是 CLI 类前端开发工具中必备的基础功能需求,然而其实现方式其实是非常简单的。

作为前端开发工具链中不可或缺的基础模块,此类工具库的实现原理与性能一直受到开发者社区的高度关注。如早期高度流行的 colors.js 多年来仍一直被广泛应用,但不少人对其扩展 String 原型链的实现方式颇有微词。后来居上的 chalk 库,也会因为其间接依赖稍多、复杂度稍高而时常受到吐槽,于是接着出现了以超轻量、无依赖、跑分高著称的 ansi-colorspicocolorskleur 等同类库。以至于 chalk 5 甚至将分离出去的两个功能相对独立的依赖库又进行了内置以实现零依赖、对主要功能实现逻辑重构以取得更高的跑分。

可以看到,作为基础类核心工具库,其在实现方式、执行性能等方面,如同安卓手机界堆硬件一般的内卷。基础工具卷实现与性能,受益的是所有开发者。换个角度来说,实现同样的能力CPU转的少了一些,在普遍应用海量累加的结果下,或许也能间接的节省不少电能消耗呢。

扯远了。铺垫了这么多,只是为了下面要介绍的内容和更高的跑分做个背景预热。

1 字符串拼接实现终端打印彩色文本样式

几年前在开发一个微型 CLI 工具时,希望打印彩色的字符串,但又觉得仅打印一两行信息,不值得多引入一个依赖库(甚至间接依赖近N个库)。

当时这方面流行的开源库主要有 colors.jschalk。在翻了一下 colors.js 的源码后,发现终端彩色字符的格式化实际上非常简单,简单的按字符串规则拼接即可。参考如下示例:

console.log(`\x1B[31m我有低保,交朋友吗?\x1B[39m`);
console.log(`\x1B[32m绿码,放心通过!\x1B[39m`);
console.log(`\x1B[32m\x1B[4m星星睡不着的时候,是不是也在数人类。\x1B[24m\x1B[39m`); 

字符串拼接的方式很简洁的解决了当时的需求,令我很满意。如果你在开发的 CLI 类工具,此类需求简单、且希望拥有较好的启动加载速度,也可以选择这么做。

如果你想了解终端中颜色控制指令相关的内容,可参考阅读:终端中的彩色文本控制指令的格式构成简介

2 clc: 设计一个极简的自用工具库

后来我又涉及一些其它 CLI 工具的开发,对控制台打印彩色文本的使用逐渐增多。

字符串拼接方式简单、零依赖,可以获得较好的启动加载性能,但当高频重度应用时则显得过于繁琐。于是参考 colors.js 的 styles 列表 写了一个极简单的工具方法,大致如下:

var isDisabled = false;
// 定义 ansi-colors 支持的颜色列表
var colorList = {// colorblack: [30, 39],red: [31, 39],green: [32, 39],yellow: [33, 39],blue: [34, 39],magenta: [35, 39],cyan: [36, 39],white: [37, 39],gray: [90, 39],// more...
};

function enable() { isDisabled = false; }
function disable() { isDisabled = true; }

// 定义一个 color 格式化方法
function color(str, colorType) {if (str === '' || str == null) return '';if (isDisabled) return String(str);var typecfg = colorList[colorType];return typecfg ? '\x1b[' + typecfg[0] + 'm' + str + '\x1b[' + typecfg[1] + 'm' : str;
}

// 使用 color 方法
console.log(color('ERROR', 'red'), `接口[${color('/xxx', 'cyan')}]调用异常!`); 

可以看到,其在实现与使用上都是超级简单的。在后续高频使用过程中,发现调用 color 方法的方式也稍显繁琐,于是又继续做了一些扩展:

function log(str, colorType) {console.log(color(str, colorType));
}

Object.keys(colorList).forEach(function (key) {color[key] = function (str) { return color(str, key); };log[key] = function () {var arr = [];for (var i = 0; i < arguments.length; i++) arr.push(String(arguments[i]));console.log(color(arr.join(' '), key));};
});

module.export = { log, color }; 

上面的代码中新增了 log 方法用于直接打印彩色文字,并将所有色彩类型扩展至 colorlog 函数的属性中。这样就可以实现简化的用法了。

由于在多个不同的项目中都会用到它,在来回复制了几次后,就决定将其发布到 npm 上。于是就有了 console-log-color 这个包。

从名称上就可以看出,我最开始的目的是想将高频调用的 console.log 给一起简化一下。

console-log-colors 工具包使用示例:

import { log, color } from 'console-log-colors';

const { green, magenta, underline, cyan, cyanBright } = color;

log.red('我有故人抱剑去,斩尽春风未曾归。');
log.green('别人笑我穿的厚,', underline('我笑他人冻的透'));
console.log(magenta('我觉得我好花心,你每天的样子我都好喜欢!'));
console.log(green('小时候哭着哭着就笑了,'), green(underline('长大后笑着笑着就哭了。')));
console.log(cyan('那天她夸我很会笑,'), bold(cyanBright('那十秒灵魂大概已卖掉。'))); 

它十分简洁,用法也够简单,没有 color.js 扩展 String 原型链的包袱,也不像 chalk 那样过于复杂。clc 基本可以满足我近几年来所有的 CLI 工具相关的开发需求。

后续不断的关注到 colorettepicocolorskleur 等库的出现,每一个新出来时,都会声称其比 chalk 快了 XYZ 倍。但 clc 已经够我用了,除了声称的性能优势,它们都并没有什么新的东西。

3 clc 重构:极致的性能优化、支持嵌套调用、链式调用

直到最近在某 CLI 框架及工具开发中遇到了嵌套调用样式异常的问题,在问题分析的过程中注意到了 kleur 文档中的跑分对比,一时心血来潮,参考其逻辑对 console-log-colors 也做了一下跑分,但发现结果比较一般,这让我起了兴趣,毕竟 color(str, colorType) 的实现已经够简单了。于是从简洁和高性能实现的角度做了进一步的详细对比分析与改进,便有了下面的内容。

3.1 性能优化:为什么 picocolors 足够快?

后来居上的 picocolorskleur 都以实现简洁性能高博得了大量关注。如 vue-cli 中采用的是 chalk(当然也由于 webpack 生态工具链对chalk高度的依赖,这是最好的选择),而 vite 生态工具链中则普遍采用的是 picocolors

为什么 picocolors 足够快?经查阅分析其源码,大致认为有如下几点原因:

  • 放弃支持链式调用:它确实太慢了
  • 够用就好:仅支持主要安全色,全部源码仅五十多行
  • 面向未来的 ES 语法,不考虑向前兼容
  • 极致的预处理(createColors方法):初始化时生成所有颜色的格式化方法,尽可能的避免不必要的计算
// picocolors 的 createColors 方法示例:
createColors = (enabled = isColorSupported) => {{red: enabled ? formatter("\x1b[31m", "\x1b[39m") : String// more...
}); 

那么,在保持接口逻辑不变的前提下,可以怎样对 clc 做性能上的改进与扩展呢?参考 pococolors / kleur / colorette 等的逻辑实现,我总结了如下几个要点:

  • 尽可能的减少动态逻辑判断、函数生成等
  • 变量缓存,避免对对象属性的高频读取
  • 预处理生成终态最简函数

用一句话来说就是:

如果一个状态的值在特定条件下是保持不变的,那么就不必在每次函数调用时去判断它

基于这几点对 color(str, colorType) 方法作了逻辑优化,最终实现参考如下:

function getFn(colorType) {var cfg = colorList[colorType];// 禁用模式下,直接返回极简的函数if (!cfg || !isSupported) return function (str) { return String(str) };var open = cfg[0], close = cfg[1];// 利用闭包缓存局部变量,返回一个极简的方法return function m(str) {if (str === '' || str == null) return '';return open + str + close;}
}

// init
Object.keys(colorList).forEach(function (key) { clc[key] = color[key] = getFn(key)); 

可以看到,如此处理后,当调用一个如 color.red 这样的方法时,实际上只执行了两行语句,其性能可想而知。这一点后面会有具体的数据对比。

3.2 支持嵌套调用

样式嵌套指的是对于一段复杂文本作色彩样式处理的同时,还对其子串作多次其他色彩样式处理。示例:

function fixture(color) {return color.red(`a red ${color.green("green")} red ${color.red("red")} red`);
} 

fixtrue 方法展示了一段文本中,red 样式中包含了 greenred 子文本样式,这里的子串 greenred 处就发生了嵌套。

由于 greenred 的结束字符串相同,外层的 red 实际上在遇到 green 的结尾字符时就也会结束。这就导致了后续本该显示为红色的文本全部都显示为了错误的默认色。

解决方案也比较简单:

  • 在执行字符串与颜色指令字符(open和close)拼接之前做预处理,查找同类型的闭合字符(close),在其后插入起始字符(open)
  • 取出插入 open 之后的字符串,作递归查找替换处理
  • 返回处理后的字符串再作拼接

支持嵌套样式的逻辑实现示例:

function red(str) {const open = '\x1B[31m'; // red openconst close = '\x1B[39m'; // red close// 递归方式查找相同的 close 字符,如果找到则在其后添加 open 字符const index = str.indexOf(redClose, open.length);if (index > -1) str = replaceClose(str, open, close, index);return open + str + end;
}

// 递归查找与替换示例
function replaceClose(str, open, close, idx) {// close 之后的位置插入 openconst start = str.substring(0, idx) + open;// 取出 close 之后的字符串let rest = str.substring(idx + close.length);// 余下的字符串继续作递归查找与替换const nextIdx = rest.indexOf(close);if (nextIdx > -1) rest = replaceClose(rest, open, close, nextIdx);// 最后拼接返回return start + rest;
} 

以前面 fixture 方法中的样式嵌套示例为例,经过兼容替换处理后,最终的输出字符会多加上两个 close 结束字符:

// 兼容处理前 - 存在嵌套调用样式与预期不一致问题
// \x1B[31ma red \x1B[32mgreen\x1B[39m red \x1B[31mred\x1B[39m red\x1B[39m
// 兼容处理后 - 效果与预期一致
// \x1B[31ma red \x1B[32mgreen\x1B[39m\x1B[31m red \x1B[31mred\x1B[39m\x1B[31m red\x1B[39m 

从下图中的执行结果对比可以看出其差别:

3.3 支持链式调用:ProxyObject.definePropertyReflect.defineProperty

链式调用的方式是这样的:

import { red } from 'console-log-colors';

const styled = red.bgWhiteBright.bold.underline('red bold underline');
console.log(styled); 

根据其用法可以很容易的联想到 Object getter 特性。例如采用 Object.definePropertycolor 方法实现链式调用:

function extend(fn, useGetter) {Object.keys(colorList).forEach(function (key) {if (useGetter) {Object.defineProperty(fn, key, {get() { return extend(function m(s) { return fn(color[key](s)) }, true) }});} else if (!fn[key]) {fn[key] = extend(function m(s) { return fn(color[key](s)) }, true);}});return fn;
}

// init
Object.keys(colorList).forEach(function (key) { color[key] = extend(getFn(key), false) }); 

从上面的示例中可以看到,通过定义 get 方法的方式,即简单的实现了无限层级链式调用支持。

但是也可以看到,由于每一次的 get 读取都是动态生成的,而且是对 colorList 遍历生成下一层级的所有方法,故其性能不会太高。实际上,链式层级越深则性能越差,其跑分结果也是惨不忍睹的。下面是以上逻辑实现的一组跑分结果对比:

# All Colors
Proxy: x 455,528 ops/sec ±0.87% (90 runs sampled)
Object.defineProperty: x 1,548,299 ops/sec ±0.74% (88 runs sampled)
Reflect.defineProperty:x 1,719,196 ops/sec ±0.78% (88 runs sampled)

# Chained colors
Proxy: x 45,787 ops/sec ±0.76% (84 runs sampled)
Object.defineProperty: x 492 ops/sec ±2.20% (78 runs sampled)
Reflect.defineProperty:x 517 ops/sec ±2.00% (78 runs sampled) 

如果每次生成的结果都能够缓存起来,那就可以在简化初始化复杂度的同时支持动态扩展。对于这种场合,上 Proxy 再合适不过了。示例:

function extend(fn) {return new Proxy(fn, {get(target, key) {if (!target[key] && (key in colorList)) {// 在对象属性上缓存起来target[key] = extend(function p(s) { return fn(color[key](s)) });}return target[key];}});
}

// init
Object.keys(colorList).forEach(function (key) { color[key] = extend(getFn(key)) }); 

基于 Proxy 的特性用少量的代码即实现了符合预期的逻辑:仅在发生属性的 get 调用时,才动态生成需被调用的方法,并在链式上一层级函数对象的属性上缓存起来。

如果将 Object getter 的动态生成结果缓存起来,那么也可以实现相同的效果,并可以支持到 ES5 的运行环境。将前面的代码稍微改进一下:

var fncache = {};
function extend(fn, keys) {var prefix = keys.join('');Object.keys(colorList).forEach(function (key) {var cachekey = prefix + key;Object.defineProperty(fn, key, {get() {if (!fncache[cachekey]) {fncache[cachekey] = extend(function m(s) { return fn(color[key](s)) }, keys.concat(key));}return fncache[cachekey]; }});});return fn;
}

// init
Object.keys(colorList).forEach(function (key) { color[key] = extend(getFn(key), [key]) }); 

基于 Proxy 和基于 Object getter 的方案在逻辑上基本一致,但在对比测试中发现,对 Proxy 对象的属性访问方式,性能上比 Object getter 要差了近两倍。

另外,在 ES6 中新增的 Reflect.definePropertyObject.defineProperty 语法与功能基本一致,经过多次的反复测试对比,看上去Reflect.defineProperty 的“跑分”更高一些。

get() 性能:Proxy < Object.defineProperty < Reflect.defineProperty

基于上述的测试结果考虑,最终选择结合 ReflectObject 实现链式调用的具体逻辑。于是最终的实现逻辑大致如下:

// 完整实现可参考: https://github.com/lzwme/console-log-colors/blob/master/src/index.js#L66
var TObject = typeof Reflect === 'undefined' ? Object : Reflect;
var fncache = {};

function extend(fn, keys, useGetter) {// 链式调用二级以上使用 Proxy 的方案。但实测跑分结果相当,最终决定不使用 Proxy// if (useGetter && typeof Proxy !== 'undefined') {// return new Proxy(fn, {// get(target, key) {// if (!target[key] && (key in colorList)) {// target[key] = extend(function p(s) { return fn(color[key](s)) }, true);// }// return target[key];// }// });// }var prefix = keys.join('');Object.keys(colorList).forEach(function (key) {var cachekey = prefix + key;TObject.defineProperty(fn, key, {get() {if (!fncache[cachekey]) {fncache[cachekey] = extend(function m(s) { return fn(color[key](s)) }, keys.concat(key));}return fncache[cachekey]; }});});return fn;
}

// init
Object.keys(colorList).forEach(function (key) { clc[key] = color[key] = extend(getFn(key), [key], false) }); 

3.4 支持浏览器控制台

前端开发的同学应该都知道,Chrome 浏览器控制台 console.log 支持 CSS 语法的彩色文本打印。如:

console.log('%cHello guys!', 'color:#3f9'); 

从 Chrome 69 开始,其 Console 控制台也开始支持 ansi color styles。故仅需对少许逻辑作简单得浏览器环境兼容,即可直接在浏览器中使用。

试试打开 Chrome Console 控制台并执行如下代码:

 // <script src=​"https:​/​/​cdn.jsdelivr.net/​npm/​console-log-colors@latest/​src/​index.js">​</script>​
var s = document.createElement('script');
s.src = "https://cdn.jsdelivr.net/npm/console-log-colors@latest/src/index.js";
s.onload = () => {console.log(clc);console.log(`${clc.cyan('[INFO]')} ${clc.yellow('Hello guys!')}`);
}
document.body.appendChild(s); 

经过实际测试,简单的文本着色是符合预期的,但在嵌套使用时会有一些差异,主要表现在结尾符号指令方面。 另外,其对 ANSI colors 24位 RGB 格式的语法支持的更好。例如你可以直接在 Chrome Console 面板中执行如下示例以体验:

console.log('\x1b[38;2;25;154;48m 透过你的眼 \x1b[38;2;255;154;48m离别也是诗 \x1b[0m');
console.log('\x1b[48;2;48;154;255m \x1b[38;2;255;255;255m 偶尔还是需要出去走一走,才知道躺在床上多么舒服 \x1b[0m'); 

3.5 strip: ANSI colors 字符移除

当需要对 congsole.log 打印的日志信息同时落地至文件时,则需要在写入文件前将ansi color styles渲染相关的字符全部移除掉,否则打开日志文件,其内容会像火星文一样。

基于ANSI escapes code 的构成规则,可以写一个正则表达式实现文本替换过滤。示例:

// 对 3/4 位颜色表示法格式的字符指令全局替换移除
function strip(str) { return str.replace(/\x1b\[\d+m/gm, '') } 

4 跑个分吧:极致优化后的性能对比

实话说,这个跑分结果出来是非常出乎意料的,因为它太高了,导致我反复做了许多次重试与对比才确认。

下面是在一台普通 Macbook Air 上的一次执行结果参考:

# All Colors
+ console-log-colors x 1,773,905 ops/sec ±0.65% (91 runs sampled)ansi-colorsx 153,603 ops/sec ±0.92% (93 runs sampled)cli-colorx 35,879 ops/sec ±0.71% (94 runs sampled)picocolors x 692,485 ops/sec ±0.92% (91 runs sampled)colorettex 215,972 ops/sec ±0.92% (89 runs sampled)chalkx 361,315 ops/sec ±3.65% (90 runs sampled))kleurx 485,399 ops/sec ±0.80% (93 runs sampled)

# Chained colors
+ console-log-colors x 60,202 ops/sec ±0.79% (94 runs sampled)ansi-colorsx 14,001 ops/sec ±7.89% (83 runs sampled))cli-colorx 14,339 ops/sec ±0.89% (90 runs sampled)chalkx 179,431 ops/sec ±0.97% (90 runs sampled)kleurx 62,768 ops/sec ±0.88% (91 runs sampled)

# Nested colors
+ console-log-colors x 101,060 ops/sec ±0.93% (89 runs sampled)ansi-colorsx 34,329 ops/sec ±11.88% (73 runs sampled)cli-colorx 12,013 ops/sec ±1.58% (89 runs sampled)picocolors x 100,250 ops/sec ±0.71% (93 runs sampled)colorettex 93,476 ops/sec ±0.94% (89 runs sampled))chalkx 71,938 ops/sec ±1.02% (90 runs sampled)kleurx 87,196 ops/sec ±0.80% (91 runs sampled) 

若有兴趣,可以拉取项目代码(console-log-colors)后执行命令 npm run benchmark 亲自试一试。

最后

为大家准备了一个前端资料包。包含54本,2.57G的前端相关电子书,《前端面试宝典(附答案和解析)》,难点、重点知识视频教程(全套)。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

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

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

相关文章

ElasticSearch入门安装与SpringBoot集成实战

介绍 Elasticsearch 是一个实时分布式搜索和分析引擎&#xff0c;一般用于全文搜索、结构化搜索&#xff0c;分析或者三者混用。 它的底层是基于Apache Lucene&#xff08;TM&#xff09;的开源搜索引擎&#xff0c;但是lucene只是一个库&#xff0c;需要java开发然后集成到应…

LeetCode刷题复盘笔记—一文搞懂贪心算法之452. 用最少数量的箭引爆气球(贪心算法系列第十一篇)

今日主要总结一下可以使用贪心算法解决的一道题目&#xff0c;452. 用最少数量的箭引爆气球 题目&#xff1a;452. 用最少数量的箭引爆气球 Leetcode题目地址 题目描述&#xff1a; 有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points &#…

备战蓝桥杯【二维前缀和】

&#x1f339;作者:云小逸 &#x1f4dd;个人主页:云小逸的主页 &#x1f4dd;Github:云小逸的Github &#x1f91f;motto:要敢于一个人默默的面对自己&#xff0c;强大自己才是核心。不要等到什么都没有了&#xff0c;才下定决心去做。种一颗树&#xff0c;最好的时间是十年前…

【自学MYSQL】MySQL Windows安装

MySQL Windows安装 MySQL Windows下载 首先&#xff0c;我们打开 MySQL 的官网&#xff0c;网址如下&#xff1a; https://dev.mysql.com/downloads/mysql/在官网的主页&#xff0c;我们首先根据我们的操作系统&#xff0c;选择对应的系统&#xff0c;这里我们选择 Windows&…

神奇的nextTick一定能获取到最新的dom么?

前言 众所周知&#xff0c;vue的dom更新操作时异步的&#xff0c;为了获取更新后的dom官方提供了相应的apinextTick,文档中对该api的描述为&#xff1a;将回调延迟到下次 DOM 更新循环之后执行 , 所谓的下一次&#xff0c;到底是哪一次呢&#xff1f;dom更新是异步任务&#xf…

【数据结构与算法】链表1:移除链表 设计链表链表反转(双指针法、递归法)

文章目录今日任务1.链表理论基础&#xff08;1&#xff09;什么是链表&#xff1f;&#xff08;2&#xff09;链表的类型&#xff08;3&#xff09;链表的存储方式&#xff08;4&#xff09;链表的定义&#xff08;5&#xff09;链表的操作&#xff08;6&#xff09;性能分析2.…

【设计模式】综述

设计模式概述 文章目录一、设计模式1. 基本简介2. 面向对象设计原则二、基本类别1. 创建型模式会更新每种模式的详细博客超链接&#xff0c;敬请期待呀参考博客&#x1f60a;点此到文末惊喜↩︎ 一、设计模式 1. 基本简介 定义&#xff1a;设计模式是一个针对重复发生的问题的…

使用HTTP隧道代理,请求超过频率要怎么办?

在网上&#xff0c;经常会看到有人说使用隧道代理经常遇到429错误&#xff08;请求超过频率&#xff09;&#xff0c;我们要如何解决这一问题呢&#xff1f;通常情况&#xff0c;优质的HTTP代理厂商隧道代理服务器采用的是高性能主机构建的动态IP代理服务器&#xff0c;是可以支…

IO流

标题IO流的体系结构FileReader和FileWriterFileReader读入数据的基本操作FileReader中使用read(char [] cbuf)读入数据FileWriter写出数据字节流使用FileInputStream和FileOutputStream读写文本文件使用FileInputStream和FileOutputStream读写非文本文件缓冲流缓冲流&#xff0…

智慧校园平台源码:实现互联互通的校园管理一体化

智慧校园管理平台主要以校园安全、智慧校园数据管理云平台为核心&#xff0c;实现数据统一管理&#xff0c;以智慧电子班牌为学生智慧之窗&#xff0c;以移动管理平台、家校沟通为辅。实现教师—家长一学校—学生循环的无纸化管理模式及教学服务&#xff0c;实现多领域的信息互…

【JavaSE】Lambda、Stream(659~686)

659.每天一考 1.写出获取Class实例的三种常见方式 Class clazz1 String.class; Class clazz2 person.getClass(); //sout(person); //xxx.yyy.zzz.Person... Class clazz3 Class.forName(String classPath);//体现反射的动态性2.谈谈你对Class类的理解 Class实例对应着加载…

小小bat-day1-自动文件上传

前言&#xff1a;日常服务器备份文件或者生产设备等数据文件都统一保存至文件服务器&#xff0c;进行日志分析或者将生产文件CSV、图片等转存至数仓进行数据分析&#xff0c;尤其生产的部分数据是保存在个人电脑的PC端&#xff0c;数据杂&#xff0c;获取困难&#xff0c;手动整…

day45【代码随想录】动态规划之完全平方数、单词拆分、打家劫舍、打家劫舍 II

文章目录前言一、完全平方数&#xff08;力扣279&#xff09;二、单词拆分&#xff08;力扣139&#xff09;三、打家劫舍&#xff08;力扣198&#xff09;四、打家劫舍 II前言 1、完全平方数 2、单词拆分 3、打家劫舍 4、打家劫舍 II 一、完全平方数&#xff08;力扣279&#…

2023软考报名(上半年)报名什么时候开始?-弘博创新

2023软考报名&#xff08;上半年&#xff09;报名预计在3月底-4月初开始&#xff0c;现在可以先进入备考了&#xff0c;参加学习可以到弘博创新&#xff0c;专业考前辅导多年&#xff0c;专业靠谱&#xff01; 系统集成项目管理工程师是全国计算机技术与软件专业技术资格&#…

Windows安装VMware虚拟机+配置Ubuntu的详细步骤以及解决配置过程中报错的问题(完整版)

目录 引言: 过程&#xff1a; 安装VMware虚拟机&#xff1a; 在VMware虚拟机中配置Ubuntu&#xff1a; 在VMware虚拟机中安装Ubuntu&#xff1a; VMware中启动虚拟机时报错问题的解决&#xff1a; 正式开始安装Ubuntu&#xff1a; 参考资料&#xff1a; 引言: 在学习计…

线程池源码解析项目中如何配置线程池

目录 基础回顾 线程池执行任务流程 简单使用 构造函数 execute方法 execute中出现的ctl属性 execute中出现的addWorker方法 addWorker中出现的addWorkerFailed方法 addWorker中出现的Worker类 Worker类中run方法出现的runWorker方法 runWorker中出现的getTask runWo…

Websocket详细介绍

需求背景 在某个资产平台&#xff0c;在不了解需求的情况下&#xff0c;我突然接到了一个任务&#xff0c;让我做某个页面窗口的即时通讯&#xff0c;想到了用websocket技术&#xff0c;我从来没用过&#xff0c;被迫接受了这个任务&#xff0c;我带着浓烈的兴趣&#xff0c;就…

MinIO

概述MinIO 是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口&#xff0c;非常适合于存储大容量非结构化的数据&#xff0c;例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等&#xff0c;而一个对象文件可以是任意大小&#xff0c;从…

Redis学习【7】之发布_订阅命令和事务

文章目录一 发布/订阅命令1.1 消息系统1.2 subscribe1.3 psubscribe1.4 publish1.5 unsubscribe1.6 punsubscribe1.7 pubsub1.7.1 pubsub channels1.7.2 pubsub numsub1.7.3 pubsub numpat二 Redis 事务2.1 Redis 事务特性Redis 事务实现2.1.1 三个命令2.1.2 基本使用2.2. Redi…

家用洗地机哪款质量好?洗地机排行榜

伴随着人们消费水平和生活品质的提升&#xff0c;人们对家庭中的需求也随之提高&#xff0c;洗地机凭借着吸拖洗为一体的功能深受大众喜爱&#xff0c;但是市面上洗地机产品越来越多&#xff0c;清洁效果也参差不齐&#xff0c;到底哪款洗地机质量好呢&#xff0c;跟随笔者脚步…