当前位置: 首页 > news >正文

设计模式之享元模式

参考资料

  • 曾探《JavaScript设计模式与开发实践》;
  • 「设计模式 JavaScript 描述」享元模式
  • 设计模式之享元模式
  • Javascript 设计模式 - 享元模式

定义

享元模式的英文叫:Flyweight Design Pattern。享元设计模式是用于性能优化的模式,这种设计模式的核心在于可以共享技术并支持对大量细分过后的对象进行调整,如果系统中因为创建大量类似的对象而导致内存占用过高,享元设计模式在其中就会起到非常重要的作用,因为它可以使其减少重复创建相同类似的实例对象。在JavaScript中浏览器特别是移动端的浏览器部分所能够使用的内存并不是很多,所以在其中节省内存就变得至关重要。

享元模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。

享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。

就是分享之意,指一物被众人共享,而这也正是该模式的终旨所在,意为单元,蝇量级的个体,该模式的核心就是使用共享技术来有效的支持大量的细粒度对象。

使用场景:

  • 数据库连接池;
  • 线程池;
  • String常量池;

内部状态与外部状态

享元模式要求将对象的属性划分为内部状态外部状态(状态在这里通常指属性)。享元模式的目标是尽量减少共享对象的数量,那么如何划分内部状态和外部状态呢?

  • 内部状态存储于对象内部。
  • 内部状态可以被一些对象共享。
  • 内部状态独立于具体的场景,通常不会改变。
  • 外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享。

把所有内部状态相同的对象都指定为同一个共享的对象。而外部状态 可以从对象身上剥离出来,并储存在外部。

剥离了外部状态的对象成为共享对象,外部状态在必要时被传入共享对象来组装成一个完整 的对象。虽然组装外部状态成为一个完整对象的过程需要花费一定的时间,但却可以大大减少系 统中的对象数量,相比之下,这点时间或许是微不足道的。因此,享元模式是一种用时间换空间的优化模式。

使用享元模式的关键是如何区别内部状态外部状态

举例说明-下象棋

我们还是通过网络上开房间下象棋的经典案例来说明享元设计模式。一个象棋游戏平台肯定有很多的房间,每个房间都有一盘棋,每盘棋上有32颗棋子,就有32个对象,但是每个房间里的【将】的属性有很多都是相同的,比如名字、颜色等,而且它们在各个房间的棋盘中都是不会发生变化的,唯一不同的就是所在的位置不一样。所以相同的属性就可以抽出来作为共享单元。

看例子:

享元类,即棋子公共的属性

public class ShareChessAttr {private Integer id;private String name;private String color;public ShareChessAttr(Integer id, String name, String color) {this.id = id;this.name = name;this.color = color;}
}

定义一个工厂去获取对应棋子的享元

public class ShareChessAttrFactory {public static final HashMap<Integer, ShareChessAttr> shareChessAttrMap = new HashMap();static {shareChessAttrMap.put(1, new ShareChessAttr(1, "将", "红"));shareChessAttrMap.put(2, new ShareChessAttr(2, "帅", "黑"));shareChessAttrMap.put(3, new ShareChessAttr(3, "车", "红"));shareChessAttrMap.put(4, new ShareChessAttr(4, "车", "黑"));}public static ShareChessAttr getShareChessAttr(Integer i) {return shareChessAttrMap.get(i);}
}

定义棋子

public class Chess {private ShareChessAttr shareChessAttr;private Integer positionX;private Integer positionY;public Chess(ShareChessAttr shareChessAttr, Integer positionX, Integer positionY) {this.shareChessAttr = shareChessAttr;this.positionX = positionX;this.positionY = positionY;}
}

定义棋盘

public class ChessBoard {private HashMap<Integer, Chess> chessOnBoard = new HashMap<Integer, Chess>();public ChessBoard() {chessOnBoard.put(1, new Chess(ShareChessAttrFactory.getShareChessAttr(1), 5, 0));chessOnBoard.put(2, new Chess(ShareChessAttrFactory.getShareChessAttr(2), 5, 0));}
}

如此每个棋盘中的棋子的id,名字和颜色都指向了同一个shareChessAttr。实现享元。

举例说明- HashMap 对象池

代码关键点:用 HashMap 对象池存储这些对象。

// 享元模式,对象池缓存对象
class colorFactory {constructor(name) {this.colors = {};}create(name) {let color = this.colors[name];if (color) return color;this.colors[name] = new Color(name);return this.colors[name];}
};

举例说明- 文件上传

在云文件上传模块的开发中,我们可以借助享元模式提升了程序的性能。下面我们就讲述这个例子。

对象爆炸

在云文件上传模块的开发中,可能会出现对象爆炸的问题。云文件的文件上传功能虽然可以选择依照队列,一个一个地排队上传,但也支持同时选择 2000 个文件。每一个文件都对应着一个 JavaScript 上传对象的创建,可是往程序里同时 new 了 2000 个 upload 对象,结 果可想而知,Chrome 中还勉强能够支撑,IE 下直接进入假死状态。

云文件支持好几种上传方式,比如浏览器插件、Flash 和表单上传等,为了简化例子,我们先假设只有插件和 Flash 这两种。不论是插件上传,还是 Flash 上传,原理都是一样的,当用户选择了文件之后,插件和 Flash 都会通知调用 Window 下的一个全局 JavaScript 函数,它的名字是 startUpload,用户选择的文件列表被组合成一个数组 files 塞进该函数的参数列表里,代码如下:

let id = 0;window.startUpload = function (uploadType, files) { // uploadType 区分是控件还是 flash for (let i = 0; i < files.length; i++) {const uploadObj = new Upload(uploadType, files[i].fileName, files[i].fileSize);uploadObj.init(id++); // 给 upload 对象设置一个唯一的 id }
};

当用户选择完文件之后,startUpload 函数会遍历 files 数组来创建对应的 upload 对象。接下来定义 Upload 构造函数,它接受 3 个参数,分别是插件类型文件名文件大小。这些信息都已经被插件组装在 files 数组里返回,代码如下:

const Upload = function (uploadType, fileName, fileSize) {this.uploadType = uploadType;this.fileName = fileName;this.fileSize = fileSize;this.dom = null;
};Upload.prototype.init = function (id) {const that = this;this.id = id;this.dom = document.createElement('div');this.dom.innerHTML ='<span>文件名称:' + this.fileName + ', 文件大小: ' + this.fileSize + '</span>' +'<button class="delFile">删除</button>';this.dom.querySelector('.delFile').onclick = function () {that.delFile();}document.body.appendChild(this.dom);
};

同样为了简化示例,我们暂且去掉了 upload 对象的其他功能,只保留删除文件的功能,对应 的方法是 Upload.prototype.delFile。该方法中有一个逻辑:当被删除的文件小于 3000 KB 时,该文件将被直接删除。否则页面中会弹出一个提示框,提示用户是否确认要删除该文件,代码如下:

Upload.prototype.delFile = function () {if (this.fileSize < 3000) {return this.dom.parentNode.removeChild(this.dom);}if (window.confirm('确定要删除该文件吗? ' + this.fileName)) {return this.dom.parentNode.removeChild(this.dom);}
};

接下来分别创建 3 个插件上传对象和 3 个 Flash 上传对象:

startUpload('plugin', [{fileName: '1.txt',fileSize: 1000},{fileName: '2.html',fileSize: 3000},{fileName: '3.txt',fileSize: 5000}
]);startUpload('flash', [{fileName: '4.txt',fileSize: 1000},{fileName: '5.html',fileSize: 3000},{fileName: '6.txt',fileSize: 5000}
]);

当点击删除最后一个文件时,可以看到弹出了是否确认删除的提示,如下图所示。

享元模式重构文件上传

上一节的代码是第一版的文件上传,在这段代码里有多少个需要上传的文件,就一共创建了多少个 upload 对象,接下来我们用享元模式重构它。

首先,我们需要确认插件类型 uploadType 是内部状态,那为什么单单 uploadType 是内部状态呢?前面讲过,划分内部状态和外部状态的关键主要有以下几点。

  • 内部状态储存于对象内部。
  • 内部状态可以被一些对象共享。
  • 内部状态独立于具体的场景,通常不会改变。
  • 外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享。

在文件上传的例子里,upload 对象必须依赖 uploadType 属性才能工作,这是因为插件上传、Flash 上传、表单上传的实际工作原理有很大的区别,它们各自调用的接口也是完全不一样的,必须在对象创建之初就明确它是什么类型的插件,才可以在程序的运行过程中,让它们分别调用各自的 startpausecanceldel 等方法。

实际上在云文件的真实代码中,虽然插件和 Flash 上传对象最终创建自一个大的工厂类,但它们实际上根据 uploadType 值的不同,分别是来自于两个不同类的对象。(在目前的例子中,为了 简化代码,我们把插件和 Flash 的构造函数合并成了一个。)

一旦明确了 uploadType,无论我们使用什么方式上传,这个上传对象都是可以被任何文件共 用的。而 fileNamefileSize 是根据场景而变化的,每个文件的 fileNamefileSize 都不一样,fileNamefileSize 没有办法被共享,它们只能被划分为外部状态。

剥离外部状态

明确了 uploadType 作为内部状态之后,我们再把其他的外部状态从构造函数中抽离出来,Upload 构造函数中只保留 uploadType 参数:

const Upload = function (uploadType) {this.uploadType = uploadType;
};

Upload.prototype.init 函数也不再需要,因为 upload 对象初始化的工作被放在了 uploadManager.add 函数里面,接下来只需要定义 Upload.prototype.del 函数即可:

Upload.prototype.delFile = function (id) {uploadManager.setExternalState(id, this);  // (1) if (this.fileSize < 3000) {return this.dom.parentNode.removeChild(this.dom);}if (window.confirm('确定要删除该文件吗? ' + this.fileName)) {return this.dom.parentNode.removeChild(this.dom);}
};

在开始删除文件之前,需要读取文件的实际大小,而文件的实际大小被储存在外部管理器 uploadManager 中,所以在这里需要通过 uploadManager.setExternalState 方法给共享对象设置正确的 fileSize,上段代码中的(1)处表示把当前 id 对应的对象的外部状态都组装到共享对象中。

工厂进行对象实例化

接下来定义一个工厂来创建 upload 对象,如果某种内部状态对应的共享对象已经被创建过,那么直接返回这个对象,否则创建一个新的对象:

const UploadFactory = (function () {const createdFlyWeightObjs = {};return {create: function (uploadType) {if (createdFlyWeightObjs[uploadType]) {return createdFlyWeightObjs[uploadType];}return createdFlyWeightObjs[uploadType] = new Upload(uploadType);}}
})();

管理器封装外部状态

现在我们来完善前面提到的 uploadManager 对象,它负责向 UploadFactory 提交创建对象的请求,并用一个 uploadDatabase 对象保存所有 upload 对象的外部状态,以便在程序运行过程中给 upload 共享对象设置外部状态,代码如下:

const uploadManager = (function () {const uploadDatabase = {};return {add: function (id, uploadType, fileName, fileSize) {const flyWeightObj = UploadFactory.create(uploadType);const dom = document.createElement('div');dom.innerHTML ='<span>文件名称:' + fileName + ', 文件大小: ' + fileSize + '</span>' +'<button class="delFile">删除</button>';dom.querySelector('.delFile').onclick = function () {flyWeightObj.delFile(id);}document.body.appendChild(dom);uploadDatabase[id] = {fileName: fileName,fileSize: fileSize,dom: dom};return flyWeightObj;},setExternalState: function (id, flyWeightObj) {const uploadData = uploadDatabase[id];for (const i in uploadData) {flyWeightObj[i] = uploadData[i];}}}
})();

然后是开始触发上传动作的 startUpload 函数:

let id = 0;
window.startUpload = function (uploadType, files) {for (let i = 0; i < files.length; i++) {const uploadObj = uploadManager.add(++id, uploadType, files[i].fileName, files[i].fileSize);}
};

最后是测试时间,运行下面的代码后,可以发现运行结果跟用享元模式重构之前一致:

startUpload('plugin', [{fileName: '1.txt',fileSize: 1000},{fileName: '2.html',fileSize: 3000},{fileName: '3.txt',fileSize: 5000}
]);startUpload('flash', [{fileName: '4.txt',fileSize: 1000},{fileName: '5.html',fileSize: 3000},{fileName: '6.txt',fileSize: 5000}
]);

享元模式重构之前的代码里一共创建了 6个 upload 对象,而通过享元模式重构之后,对象的数量减少为 2,更幸运的是, 就算现在同时上传 2000个文件,需要创建的 upload 对象数量依然是 2。

享元模式的适用性

享元模式是一种很好的性能优化方案,但它也会带来一些复杂性的问题,从前面两组代码的比较可以看到,使用了享元模式之后,我们需要分别多维护一个 factory 对象和一个 manager 对 象,在大部分不必要使用享元模式的环境下,这些开销是可以避免的。

享元模式带来的好处很大程度上取决于如何使用以及何时使用,一般来说,以下情况发生时便可以使用享元模式。

  • 一个程序中使用了大量的相似对象。
  • 由于使用了大量对象,造成很大的内存开销。
  • 对象的大多数状态都可以变为外部状态。
  • 剥离出对象的外部状态之后,可以用相对较少的共享对象取代大量对象。

可以看到,文件上传的例子完全符合这四点。

再谈内部状态和外部状态

如果顺利的话,通过前面的例子我们已经了解了内部状态和外部状态的概念以及享元模式的工作原理。我们知道,实现享元模式的关键是把内部状态和外部状态分离开来。有多少种内部状态的组合,系统中便最多存在多少个共享对象,而外部状态储存在共享对象的外部,在必要时被传入共享对象来组装成一个完整的对象。现在来考虑两种极端的情况,即对象没有外部状态和没有内部状态的时候。

没有内部状态的享元

在文件上传的例子中,我们分别进行过插件调用和 Flash 调用,即 startUpload('plugin', [])startUpload('flash', []),导致程序中创建了内部状态不同的两个共享对象。也许你会奇怪,在文件上传程序里,一般都会提前通过特性检测来选择一种上传方式,如果浏览器支持插件就用插件上传,如果不支持插件,就用 Flash 上传。那么,什么情况下既需要插件上传又需要 Flash 上传呢?

实际上这个需求是存在的,很多网盘都提供了极速上传(控件)与普通上传(Flash)两种模式,如果极速上传不好使(可能是没有安装控件或者控件损坏),用户还可以随时切换到普通上传模式,所以这里确实是需要同时存在两个不同的 upload 共享对象。

但不是每个网站都必须做得如此复杂,很多小一些的网站就只支持单一的上传方式。假设我们是这个网站的开发者,不需要考虑极速上传与普通上传之间的切换,这意味着在之前的代码中作为内部状态的 uploadType 属性是可以删除掉的。 在继续使用享元模式的前提下,构造函数 Upload 就变成了无参数的形式:

const Upload = function(){}; 

其他属性如 fileNamefileSizedom 依然可以作为外部状态保存在共享对象外部。在 uploadType 作为内部状态的时候,它可能为控件,也可能为 Flash,所以当时最多可以组合出两个共享对象。而现在已经没有了内部状态,这意味着只需要唯一的一个共享对象。现在我们要改写创建享元对象的工厂,代码如下:

const UploadFactory = (function () {let uploadObj;return {create: function () {if (uploadObj) {return uploadObj;}return uploadObj = new Upload();}}
})();

管理器部分的代码不需要改动,还是负责剥离和组装外部状态。可以看到,当对象没有内部状态的时候,生产共享对象的工厂实际上变成了一个单例工厂。虽然这时候的共享对象没有内部状态的区分,但还是有剥离外部状态的过程,我们依然倾向于称之为享元模式。

没有外部状态的享元

网上许多资料中,经常把 Java 或者 C#的字符串看成享元,这种说法是否正确呢?我们看看下面这段 Java 代码,来分析一下:

// Java 代码
public class Test {public static void main(String args[]) {String a1 = new String("a").intern();String a2 = new String("a").intern();System.out.println(a1 == a2); // true }
}

在这段 Java 代码里,分别 new 了两个字符串对象 a1a2intern 是一种对象池技术, new String("a").intern()的含义如下。

  • 如果值为 a 的字符串对象已经存在于对象池中,则返回这个对象的引用。
  • 反之,将字符串 a 的对象添加进对象池,并返回这个对象的引用。

所以 a1 == a2 的结果是 true,但这并不是使用了享元模式的结果,享元模式的关键是区别内部状态和外部状态。享元模式的过程是剥离外部状态,并把外部状态保存在其他地方,在合适的时刻再把外部状态组装进共享对象。这里并没有剥离外部状态的过程,a1a2 指向的完全就是同一个对象,所以如果没有外部状态的分离,即使这里使用了共享的技术,但并不是一个纯粹的享元模式。

优缺点

优点:

  • 大大减少对象的创建,降低系统的内存,使效率提高。

缺点:

  • 提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。

总结

享元模式是为解决性能问题而生的模式,这跟大部分模式的诞生原因都不一样。在一个存在 大量相似对象的系统中,享元模式可以很好地解决大量对象带来的性能问题。

享元模式的思想与单例比较类似,但是单例模式强调的是全局唯一,而享元模式则强调的是内存共享。

相关文章:

设计模式之享元模式

参考资料 曾探《JavaScript设计模式与开发实践》&#xff1b;「设计模式 JavaScript 描述」享元模式设计模式之享元模式Javascript 设计模式 - 享元模式 定义 享元模式的英文叫&#xff1a;Flyweight Design Pattern。享元设计模式是用于性能优化的模式&#xff0c;这种设计…...

【GAMES101】05 Rasterization(Triangles)

光栅化过程&#xff1a;将一系列变换后的三角形转换为像素的过程。 三角形在图形学中得到很多的应用。 最基础的多边形&#xff08;边数最少&#xff09;。任何多边形都可以拆成三角形。性质&#xff1a;三角形内部一定是平面的。三角形内外部定义非常清楚。定义三个顶点后&a…...

13. Pod 从入门到深入理解(二)

本章讲解知识点 Pod 容器共享 VolumeConfigMapSecretDownward APIEmptyDir VolumeHostPath Volume1. Pod 容器共享 Volume 1.1. Volume 的背景及需要解决的问题 存储是必不可少的,对于服务运行产生的日志、数据,必须有一个地方进行保存,但是我们的容器每一次重启都是“恢复…...

ORBBEC(奥比中光)AstraPro相机在ROS2下的标定与D2C(标定与配准)

文章目录 1.rgb、depth相机标定矫正1.1.标定rgb相机1.2.标定depth相机1.3.rgb、depth相机一起标定&#xff08;效果重复了&#xff0c;但是推荐使用&#xff09;1.4.取得标定结果1.4.1.得到的标定结果的意义 1.5.IR、RGB相机分别应用标定结果1.5.1.openCV应用标定结果1.5.2.ros…...

常量与变量:编程中重要的两种数据类型

常量与变量 在编程中&#xff0c;我们常常需要存储一些数据。这些数据有些是恒定不变的&#xff0c;有些却是可以随时变化的。对于恒定不变的数据&#xff0c;我们称之为常量&#xff1b;对于可以变化的数据&#xff0c;我们则称之为变量。这两种数据类型在程序中非常重要&…...

( 数组和矩阵) 287. 寻找重复数 ——【Leetcode每日一题】

❓287. 寻找重复数 难度&#xff1a;中等 给定一个包含 n 1 个整数的数组 nums &#xff0c;其数字都在 [1, n] 范围内&#xff08;包括 1 和 n&#xff09;&#xff0c;可知至少存在一个重复的整数。 假设 nums 只有 一个重复的整数 &#xff0c;返回 这个重复的数 。 你…...

【学习笔记】「JOISC 2022 Day2」复制粘贴 3

看了正解。我觉得很厉害。虽然用减枝水过去了。 区间 d p dp dp。但是这个转移怎么看都不是 O ( 1 ) O(1) O(1)的。 border \text{border} border 那么 trick \text{trick} trick应该都能看出来。能进行剪切操作当且仅当 s [ l , p ] s [ q , r ] s_{[l,p]}s_{[q,r]} s[l,p]​…...

武忠祥老师每日一题||定积分基础训练(三)

常用的基本不等式&#xff1a; sin ⁡ x < x < t a n x , x ∈ ( 0 , π 2 ) \sin x<x<\ tan x,x\in(0,\frac{\pi}{2}) sinx<x< tanx,x∈(0,2π​) e x ≥ 1 x , x ∈ ( − ∞ , ∞ ) e^x\ge1x,x\in(-\infty,\infty) ex≥1x,x∈(−∞,∞) x 1 x ≤ ln …...

Docker安装常用软件-Apollo(有问题)

零&#xff1a;apollo概念介绍 官网网站&#xff1a;GitHub - apolloconfig/apollo: Apollo is a reliable configuration management system suitable for microservice configuration management scenarios. gitee网址&#xff1a;mirrors / ctripcorp / apollo GitCode …...

f(x)与|f(x)|,f ‘ (x),F(x)常见关系。

1.f(x)与|f(x)|关系。 1.连续关系。(f(x)在"[a,b]上连续" > |f(x)|在"[a,b]连续") ①如果f(x)在[a,b]上连续。则|f(x)|在[a,b]上连续. &#xff08;因为f(x)在x0的连续点>x0必为|f(x)|的连续点&#xff09; 注&#xff1a;”[a,b]连续“包括&#…...

今天面了一个来字节要求月薪23K,明显感觉他背了很多面试题...

最近有朋友去字节面试&#xff0c;面试前后进行了20天左右&#xff0c;包含4轮电话面试、1轮笔试、1轮主管视频面试、1轮hr视频面试。 据他所说&#xff0c;80%的人都会栽在第一轮面试&#xff0c;要不是他面试前做足准备&#xff0c;估计都坚持不完后面几轮面试。 其实&…...

如何使用二元三次回归分析建立预测模型?(分析、原理、代码示例)

二元三次回归是一种用于建立两个自变量与一个因变量之间关系的回归模型&#xff0c;常用于数据分析和预测。下面我会更详细地解释一下二元三次回归的原理、分析和示例代码。 1、原理 二元三次回归分析用多项式回归建立预测模型&#xff0c;其中包括两个自变量&#xff08;通常…...

面向万物智联的应用框架的思考和探索(上)

原文&#xff1a;面向万物智联的应用框架的思考和探索&#xff08;上&#xff09;&#xff0c;点击链接查看更多技术内容。 应用框架&#xff0c;是操作系统连接开发者生态&#xff0c;实现用户体验的关键基础设施。其中&#xff0c;开发效率和运行体验是永恒的诉求&#xff0c…...

《Python机器学习基础教程》第1章学习笔记

目录 第1章 引言 1.1 为何选择机器学习 1.1.1 机器学习能够解决的问题 第1章 引言 机器学习又称为预测分析或统计学习&#xff0c;是一个交叉学科&#xff0c;是从数据中提取知识。 1.1 为何选择机器学习 智能应用早期&#xff0c;使用专家设计的规则体系来设计。 缺点&…...

ClickHouse 内存管理是如何实现的

概述 本文介绍Clickhouse内存管理的实现原理。通过本文的分析&#xff0c;可以对Clickhouse的内存管理有一个概要的理解。 Clickouse内存管理组成 ClickHouse 使用内存管理系统来控制内存资源的分配和释放。内存管理系统的主要组成部分是&#xff1a; 内存池&#xff1a;Cl…...

docker容器技术

什么是docker Docker 使用 Google 公司推出的 Go 语言 进行开发实现&#xff0c;基于 Linux 内核的 cgroup&#xff0c;namespace&#xff0c;以及 OverlayFS 类的 Union FS 等技术&#xff0c;对进程进行封装隔离&#xff0c;属于 操作系统层面的虚拟化技术。由于隔离的进程独…...

设计模式七大设计原则

文章目录 1、什么是设计模式2、单一职责原则3、开闭原则4、接口隔离原则5、依赖倒置原则6、迪米特法则&#xff08;最少知道原则&#xff09;7、里式替换原则8、组合优于继承 设计模式主要是为了满足一个字 变&#xff0c;这个字&#xff0c;可能是需求变更、可能是场景变更&a…...

【Hello Network】TCP协议相关理解

作者&#xff1a;小萌新 专栏&#xff1a;网络 作者简介&#xff1a;大二学生 希望能和大家一起进步 本篇博客简介&#xff1a;补充下对于TCP协议的各种理解 TCP协议相关实验 TCP相关试验理解CLOSE_WAIT状态理解TIME_WAIT状态解决TIME_WAIT状态引起的bind失败的方法理解listen的…...

实施CRM目标有哪几步?如何制定CRM目标?

在当今竞争激烈的商业环境中&#xff0c;与客户建立持久的关系是企业重要的工作。CRM客户管理系统能有效帮助企业管理优化流程、管理客户&#xff0c;提高销售成功率&#xff0c;推动收入增长。那么您了解如何实施CRM吗&#xff1f;下面说说实施CRM目标是什么&#xff0c;如何设…...

船舶建造概论(船舶建造工艺任务与现代造船模式)

船舶建造概论 1 船舶建造概论1.1 船舶建造工艺主要任务1.2 船舶建造流程&#xff08;1&#xff09;钢材料预处理&#xff08;2&#xff09; 钢材料加工&#xff08;3&#xff09;分段制作&#xff08;4&#xff09;总段制作&#xff08;5&#xff09;船台合拢&#xff08;6&…...

项目内训(2023.5.6)

目录 Nacos是什么&#xff1f; 领域模型是什么&#xff1f; domain模块一般是干什么的&#xff1f; 在小乌龟中合并其他分支的作用是什么&#xff1f; nacos的配置文件 服务集群、服务提供、服务更加灵活庞大、消费服务、访问比较麻烦&#xff0c;A和B服务一起访问 系统结…...

【操作系统OS】学习笔记第二章 进程与线程(下)【哈工大李治军老师】

基于本人观看学习 哈工大李治军老师主讲的操作系统课程 所做的笔记&#xff0c;仅进行交流分享。 特此鸣谢李治军老师&#xff0c;操作系统的神作&#xff01; 如果本篇笔记帮助到了你&#xff0c;还请点赞 关注 支持一下 ♡>&#x16966;<)!! 主页专栏有更多&#xff0…...

Linux命令集(Linux文件管理命令--rmdir指令篇)

Linux命令集&#xff08;Linux文件管理命令--rmdir指令篇&#xff09; Linux文件管理命令集&#xff08;rmdir指令篇&#xff09;5. rmdir(remove directory)1. 删除空的目录 folder12. 强制删除目录 folder1&#xff08;包括非空目录&#xff09;3. 递归删除目录及其目录下所有…...

在技术圈超卷的当下,学历到底是敲门砖还是枷锁?

前言 最近&#xff0c;突然之间被“孔乙己文学”刷屏了&#xff0c;短时间内“孔乙己文学”迅速走红&#xff0c;孔乙己是中国文学中的一位经典人物&#xff0c;他的长衫被认为是他的象征之一&#xff0c;孔乙己的长衫折射出很多现象&#xff0c;既有社会的&#xff0c;也有教育…...

Linux cgroup

前言 Cgroup和namespace类似&#xff0c;也是将进程进程分组&#xff0c;但是目的与namespace不一样&#xff0c;namespace是为了隔离进程组之前的资源&#xff0c;而Cgroup是为了对一组进程进行统一的资源监控和限制。 Cgroup的组成 subsystem 一个subsystem就是一个内核模…...

PID整定二:基于Ziegler-Nichols的频域响应

PID整定二&#xff1a;基于Ziegler-Nichols的频域响应 1参考2连续Ziegler-Nichols方法的PID整定2.1整定方法2.2仿真示例 1参考 1.1根轨迹图的绘制及分析 1.2计算机控制技术01-3.4离散系统的根轨迹分析法 1.3PID控制算法学习笔记 2连续Ziegler-Nichols方法的PID整定 2.1整定…...

【tkinter 专栏】专栏前言

文章目录 前言本章内容导图1. tkinter 工具及特点2. 为什么使用 Python 进行 GUI 设计?2.1 Python 可以做什么2.2 使用 tkinter 可以干什么?3. 如何学习使用 tkinter 进行 GUI 设计?4. 开发环境搭建4.1 Python 的版本4.2 安装 Python4.2.1 下载 Python 安装包4.2.2 安装 Pyt…...

解决Linux中文字体模糊的4种方法

在Linux中&#xff0c;字体是非常重要的一部分&#xff0c;因为它们直接影响到用户的视觉体验。如果Linux字体模糊不清&#xff0c;那么用户将很难阅读文本&#xff0c;这将极大地降低用户的工作效率。本文将介绍Linux Mint中文字体模糊的问题&#xff0c;并提供一些解决方案。…...

【Android入门到项目实战-- 7.3】—— 如何调用手机摄像头和相册

目录 一、调用摄像头拍照 二、打开相册选择照片 学完本篇文章可以收获如何调用手机的摄像头和打开手机相册选择图片功能。 一、调用摄像头拍照 先新建一个CameraAlbumTest项目。 修改activity_main.xml,代码如下&#xff1a; 按钮打开摄像头&#xff0c;ImageView将拍到的…...

浅聊AIOT

引言 IoT是(Internet of Things)的简称&#xff0c;也就是人们常说的物联网&#xff1b;随着智能硬件的发展和推广&#xff0c;制造成本也随之下降&#xff0c;很多的厂家也慢慢地拥抱网络互联&#xff0c;逐步实现设备互联&#xff0c;也就进入了人们常说的万物互联时代。虽然…...

找人做效果图去什么网站/百度收录批量提交入口

1、问题描述 在新安装的PyCharm 2017.3(Community Edition)中&#xff0c;调试Python代码的时候&#xff0c;只启动Debugger&#xff0c;不启动Console&#xff0c;无法看打印出来的日志。 图1 debug代码结果页面只有Debugger 2、原因分析 根据以往调试经验&#xff0c;一定是…...

档案网站建设对比/百度指数指的是什么

*CTF2022 oh-my-lotto&revenge 文章目录*CTF2022 oh-my-lotto&revenge简单分析下源码lottoapp非预期解修改PATH变量WGETRC预期解参考链接简单分析下源码 题目给了附件&#xff0c;主要看app,lotto,docker-compose.yml 这里是开了两个容器&#xff0c;然后通过links进行…...

上海进博会?/廊坊自动seo

ASP.NET Core断点续传 在ASP.NET WebAPi写过完整的断点续传文章&#xff0c;目前我对ASP.NET Core仅止于整体上会用&#xff0c;对于原理还未去深入学习&#xff0c;由于有园友想看断点续传在ASP.NET Core中的具体实现&#xff0c;于是借助在家中休息时间看了下ASP.NET Core是否…...

ps做网站导航/投诉百度最有效的电话

http://blog.tangcs.com/2009/07/24/basketball-shooting/转载于:https://www.cnblogs.com/WarrenTang/articles/1530149.html...

备案网站/优化网站seo

2705: [SDOI2012]Longge的问题 Time Limit: 3 Sec Memory Limit: 128 MBSubmit: 1959 Solved: 1229[Submit][Status][Discuss]Description Longge的数学成绩非常好&#xff0c;并且他非常乐于挑战高难度的数学问题。现在问题来了&#xff1a;给定一个整数N&#xff0c;你需要…...

网站建设20推广/最新军事新闻 今日 最新消息

随机数的不重复选择就是从n个数中随机选取m(m<n)个数。在本文中&#xff0c;我们用Java来实现。因此我们先介绍Java的相关知识。 在Java中&#xff0c;Java.util.Set接口和Java.util.List接口一样&#xff0c;都是继承自Java.util.Collection接口。但是两者有不同的特点&am…...