文章目录 全部的实现代码放在了文章末尾什么是适配器模式?准备工作包含头文件定义命名空间类的成员变量什么是仿函数?比较仿函数在priority_queue中的作用通过传入不同的仿函数可以做到大堆和小堆之间的切换通过传入不同的仿函数可以做到改变priority_queue里面的比较逻辑 构造函数默认构造迭代器区间构造 sizeemptytoppushpop 全部代码 全部的实现代码放在了文章末尾 【不了解堆(priority_queue)的可以看我这篇文章:模拟实现堆的接口函数】
priority_queue的模拟实现和stack,queue一样,采用了C++适配器模式
priority_queue的适配器一般是vector,也可以是deque
因为priority_queue是有特殊限制的线性表【只能取堆顶数据,并且需要高效的尾插尾删,还要支持高效的下标访问(因为priority_queue的push和pop一般是调用适配器的尾插,尾删。 实现向下(上)调整算法的时候需要高效的下标访问方法)】
所以只要是线性结构并且可以高效的实现尾插和尾删,支持高效的下标访问的线性表,就可以作为priority_queue的适配器
什么是适配器模式? 适配器模式是一种设计模式,它允许将不兼容接口的类一起工作。
适配器模式通常用于以下情况:
希望使用一个类,但其接口与其他代码不兼容。希望创建一个可重用的类,它能够将接口转换为其他接口。希望使用第三方库或遗留代码,但其接口与其他代码不兼容。 适配器模式通常包括以下三个主要部分:
目标接口(Target):这是期望使用的接口,客户端代码只能与目标接口交互。源接口(Adaptee):这是需要适配的类,其接口与目标接口不兼容。适配器(Adapter):这是一个类,它实现了目标接口,并将调用转换为对源接口的调用。适配器将源接口的调用转换为目标接口的调用,使得客户端代码可以与目标接口交互。 可以类比我们生活中的家庭电源接口和笔记本电脑充电口与电源适配器,它们之间也是一种适配器关系
笔记本电脑充电口是上面提到的目标接口
家庭电源接口是上面提到的源接口
电源适配器是上面提到的适配器
笔记本电脑的充电口是不能和家庭电源接口直接连接进行充电的,因为笔记本电脑用的是直流电,而家庭电源输出的是交流电,所以要把交流电转换为直流电才能给笔记本电脑供电,而电源适配器就能做到这一点
对应了上面提到的适配器模式解决的问题:
可以将不兼容接口的类一起工作
准备工作 创建两个文件,一个头文件mypriority_queue.hpp,一个源文件test.cpp
【因为模板的声明和定义不能分处于不同的文件中,所以把成员函数的声明和定义放在了同一个文件mypriority_queue.hpp中】
mypriority_queue.hpp:存放包含的头文件,命名空间的定义,成员函数和命名空间中的函数的定义
test.cpp:存放main函数,以及测试代码
包含头文件 iostream:用于输入输出vector:提供vector类型的适配对象deque: 提供deque类型的适配对象assert.h:提供assert报错函数 定义命名空间 在文件mypriority_queue.hpp中定义上一个命名空间mypriority_queue
把priority_queue类和它的成员函数放进命名空间封装起来,防止与包含的头文件中的函数/变量重名的冲突问题
类的成员变量 有两个一个是适配器对象,一个是提供比较的仿函数,默认con是vector类型,comp是std库里面的less
什么是仿函数? C++中的仿函数是指那些能够像函数一样调用的对象,它们的类至少需要有成员函数operator()()。
仿函数可以是任何类型的对象,只要它的类重载了运算符(),它就能够被调用,就可以作为函数使用。
在C++中,标准模板库(STL)中的一些容器算法使用仿函数来封装各种操作,使得这些操作可以应用于容器中的元素。
仿函数可以是用户自定义的,也可以是C++标准库中定义的。例如,STL中的std::less和std::greater就是两个标准的比较仿函数,它们用于排序算法中。用户也可以定义自己的仿函数
总结:
C++的仿函数是重载了operator()的类实例化的对象,它可以被像函数那样调用
举个例子:
下图是一个可以简单进行大于比较的仿函数和它的类
比较仿函数在priority_queue中的作用 通过传入不同的仿函数可以做到大堆和小堆之间的切换 【stl库里面是传入less类型的仿函数为大堆,传入greater为小堆。 即大于是小堆,小于是大堆】
为什么呢?
因为priority_queue需要用到比较的地方是向上(下)调整算法
如下图:
【向上(调整)这篇文章不做详细讲解,不了解的可以看我这篇文章:模拟实现堆的接口函数】
根据传给priorit_queue的第三个模板参数的不同,就可以得到不同类型的comp
这样调用comp实现的功能就不一样
std::less类型的comp的功能是判断左参数是否小于右参数
std::greater类型的comp的功能是判断左参数是否大于右参数
通过这一点就可以调整大小堆了
因为
如果comp是小于,那么comp(con[parent],con[child])就是父亲节点<孩子节点就交换,那么就会把大的节点一直往堆顶送,就可以实现大堆
comp是小于的时候同理
通过传入不同的仿函数可以做到改变priority_queue里面的比较逻辑 我们使用priority_queue的时候,一般不需要我们传入我们自己写的仿函数,使用库里面的std::less和std::greater就可以满足我们的大部分需求
但是总规有一些情况,库里面的比较仿函数的比较逻辑不符合我们的要求
此时就需要我们自己写一个比较仿函数,传给priority_queue
例
如果我们定义了一个坐标类pos
MessageBox 弹框 MessageBox 的作用是替代系统自带的 alert、confirm ,仅适合展示较为简单的内容。如果需要弹出较为复杂的内容,请使用定制的弹窗。基本仿照ElementUI的同名组件。
原生,无依赖项,自适应布局,双端通用,如有弹窗层级问题需要自行调整。
git仓库地址:https://gitee.com/amswait/message-box、
效果预览 引用方式 <script src="/message-box.min.js"></script> 注意:需要确保CSS文件在JS同级目录;
API messageBox:访问核心弹窗对象。
$close:关闭弹窗的方法。
$alert:显示带有消息和确认按钮的提示框。
$confirm:显示带有消息、确认和取消按钮的确认框。
options 配置项类型是否必填示例是否可传入HTML代码msgString是$alert('测试文案')$alert('<strong>这是 <i>HTML</i> 片段</strong>')titleString否$alert('测试', '测试标题')|同上optionsObject否options.confirmButtonTextString否$confirm("测试",null,{confirmButtonText:"确认文案"})同上options.cancelButtonTextString否$confirm("测试",null,{cancelButtonText:"取消文案"})同上options.callbackFunction否$confirm("测试",void 0,{callback:function(result, type){$alert('结果:'+ result+' 触发按钮类型:'+type)}}); options.callback出参说明 参数名含义result确认的结果:点击关闭按钮与取消按钮为false,点击确认按钮为truetype触发事件的按钮类型:close为关闭按钮,confirm为确认按钮,cancel为取消按钮
Hi~!这里是奋斗的小羊,很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~~
💥💥个人主页:奋斗的小羊
💥💥所属专栏:C++
🚀本系列文章为个人学习笔记,在这里撰写成文一为巩固知识,二为记录我的学习过程及理解。文笔、排版拙劣,望见谅。
目录 1、赋值运算符重载1.1运算符重载1.2赋值运算符重载 2、取地址运算符重载2.1const成员函数2.2取地址运算符重载 3、类型转化4、再探构造函数5、static成员6、友元7、内部类8、匿名对象9、对象拷贝时的编译器优化 1、赋值运算符重载 1.1运算符重载 当运算符被用于类类型的对象时,C++允许我们通过运算符重载的形式指定新的定义。C++规定类类型对象使用运算符时,必须转换成调用对应运算符重载,若没有对应的运算符重载,则会编译报错。
运算符重载是具有特殊名字的函数,它的名字是由operator和后面要定义的运算符共同构成,和其他函数一样,它也具有其返回类型和参数列表以及函数体重载运算符函数的参数个数和该运算符作用的运算对象数量一样多。一元运算符有一个参数,二元运算符有两个参数,二元运算符的左侧运算对象传给第一个参数,右侧运算对象传给第二个参数 #include <iostream> using namespace std; class Date { public: Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } void Print() { cout << _year << "-" << _month << "-" << _day << endl; } //类中成员变量都可以访问 //int Get() //{ // return _year; //} //private: int _year; int _month; int _day; }; bool operator<(Date d1, Date d2) { return d1.
一、MVC 什么是MVC? MVC全名是Model View Controller,是模型(Model)- 视图(view)- 控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。
最简单、最经典的就是JSP+Servlet+JavaBean
(视图和逻辑隔离开来,控制器(controller)是核心)
MVC响应过程 M(Model)模型 JavaBean
V(View)视图 html、JSP、Thymeleaf、volicity、freemaker
C(Control)控制器 Servlet、Controller、Action
1. 控制器收到来自用户的请求
2. 控制器调用业务层完成JavaBean数据封装
3. 完成业务后通过控制器跳转JSP页面的方式给用户反馈信息
4. JSP为用户作出响应。
什么是JavaBean? JavaBean:一种规范,表达实体和信息的规范,便于封装重用
1. 所有属性为private
2. 提供默认无参构造方法
3. 提供getter和setter
4. 实现serializable接口
二、三层架构 1. 表现层(Servlet、Controller):负责控制,拿到View传递过来的数据,封装之后交给Service处理,Service处理完了之后,Controller拿到结果,奖结果交给界面。
2. 逻辑层(Service):负责业务逻辑
3. 数据访问层(DAO):纯粹的JDBC的增删改查操作
注:上一层可以调用下一层所有代码,例如StudentServlet不止可以调用IStudentService,还可以调用ITeacherService
三、分页 做分页前先定好每一页显示多少条数据
sql语句: SELECT * FROM tablename LIMIT [offset],rows; limit中offset代表偏移到哪个位置,rows代表往下数多少个
pageNo,pageSize:1,5、2,5、3,5
SELECT * FROM tableName LIMIT (pageNo-1)*pageSize,pageSize; pageNolimit10,525,5310,5 分页常见的问题:
1.封装一个分页需要哪些信息
数据表list,总页数totalPage,本页数pageNo和每页显示多少数据pageSize
2.分页需要发送那两条sql语句
SELECT * FROM tableName LIMIT [offset],pageSize; SELECT COUNT(*) FROM tableName;
docker部署rabbitMQ 如果用目录挂载会启动失败,要用数据卷挂载。
docker pull rabbitmq:3.8-management #挂载数据卷 -v mq-plugins:/plugins \ #设置主机名 --hostname mq \ docker run \ -e RABBITMQ_DEFAULT_USER=rabbitmq \ -e RABBITMQ_DEFAULT_PASS=1234 \ -v mq-plugins:/plugins \ --name mq \ -p 15672:15672 \ -p 5672:5672 \ -d \ rabbitmq:3.8-management # 查看挂载的数据卷所在信息(目录) docker inspect mq-plugins
🌸个人主页:https://blog.csdn.net/2301_80050796?spm=1000.2115.3001.5343
🏵️热门专栏:
🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm=1001.2014.3001.5482
🍕 Collection与数据结构 (92平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm=1001.2014.3001.5482
🧀线程与网络(96平均质量分) https://blog.csdn.net/2301_80050796/category_12643370.html?spm=1001.2014.3001.5482
🍭MySql数据库(93平均质量分)https://blog.csdn.net/2301_80050796/category_12629890.html?spm=1001.2014.3001.5482
🍬算法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12676091.html?spm=1001.2014.3001.5482
🍃 Spring(97平均质量分)https://blog.csdn.net/2301_80050796/category_12724152.html?spm=1001.2014.3001.5482
感谢点赞与关注~~~
目录 1. AOP概述2. Spring AOP快速入门2.1 引入AOP依赖2.2 编写AOP程序 3. Spring AOP详解3.1 核心概念3.1.1 切点(PointCut)3.1.2 连接点(Join Point)3.1.3 通知(Advice)3.1.4 切面(Aspect) 3.2 通知类型3.3 @Pointcut3.4 切面优先级@Order3.5 切点表达式3.5.1 execution表达式3.5.2 @annotation(翻译:注解) 4. Spring AOP原理4.1 代理模式4.1.1 静态代理4.1.2 动态代理 1. AOP概述 什么是AOP?
所谓AOP,就是面相切面的编程.什么是面向切面的编程呢,切面就是指定某一类特定的问题,所以,面向切面的编程就是正对于同一类问题进行编程.
简单来说,就是针对某一类事情的集中处理.什么是Spring AOP?
AOP是一种思想,实现AOP的方法有很多,有Spring AOP,有AspectJ,有CGLIB等.Spring AOP是其中的一种实现方式. 某种程度上,他和我们前面提到的统一功能处理的效果差不多,但是,统一功能处理并不等同于SpringAOP,拦截器的作用维度是URL,即一次请求响应,但是AOP的作用维度更加细致,可以对包括包,类,方法,参数等进行统一功能的处理,可以实现更加复杂的业务逻辑.
举例:
现在有一些业务执行效率比较低,我们需要对接口进行优化,第一步需要定位出耗时较长的业务方法,在针对业务额来进行优化.我们就可以在每一个方法的结束和开始的地方加上时间戳,之后作差展示出来.但是在一个项目中,我们有好多接口,此时在接口中一个个低添加时间戳又不是很现实,此时我们就需要用到AOP.
接下来,我们就来看看AOP如何使用.
2. Spring AOP快速入门 需求: 统计图书管理系统各个接口和方法的执行时间.
2.1 引入AOP依赖 在pom文件中添加依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> 2.2 编写AOP程序 记录Controller中每个方法的执行时间.
Hi~!这里是奋斗的小羊,很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~~
💥💥个人主页:奋斗的小羊
💥💥所属专栏:C++
🚀本系列文章为个人学习笔记,在这里撰写成文一为巩固知识,二为记录我的学习过程及理解。文笔、排版拙劣,望见谅。
目录 1、类的定义1.1类定义格式1.2访问限定符1.3类域 2、实例化2.1什么是实例化2.2对象大小2.3 this指针 3、C++和C语言实现Stack对比4、类的默认成员函数4.1构造函数4.2析构函数4.3拷贝构造函数 1、类的定义 C语言结构体中只能定义变量,C++中结构体内不仅可以定义变量还可以定义函数。
1.1类定义格式 class ClassName { //类体:成员函数和成员变量 }; class为定义类的关键字,ClassName为类的名字,{ }中为类的主体类中的变量称为类的属性或成员变量,类中的函数称为类的方法或成员函数C++中struct也可以定义类,C++兼容C中struct的用法,同时也升级struct成了类,一般情况下还是常用class定义类定义在类里面的成员函数默认为inline class Date { public: void Init(int year, int month, int day) { _year = year; _month = month; _day = day; } private: //为了区分成员变量,一般习惯 //给成员变量加一个标识 int _year; int _month; int _day; }; //不再需要typedef,ListNode就可以代表类型 struct ListNode { int val; ListNode* next; }; 类的两种定义方式:声明和定义全部放在类体中 / 类声明放在.h文件中,成员函数定义在.cpp文件中,成员函数名前需要加类名 1.2访问限定符 C++一种实现封装的方式,用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将接口(函数)提供给外部的用户使用 public修饰的成员在类外可以直接被访问,protected和private修饰的成员在类外不能直接被访问访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现为止class定义成员没有被访问限定符修饰时默认private,struct默认为public 1.3类域 类定义了一个新的作用域,类的所有成员都在类的作用域中,在类外定义成员时,需要使用::作用域操作符指明成员属于哪个类域类域影响的是编译的查找规则,编译器默认只会在局部和全局查找,只有指定类域才会到类域中区查找 2、实例化 2.
MVCC(Multi-Version Concurrency Control,多版本并发控制)是一种在数据库系统中用于实现并发控制的技术,它允许多个事务并发地读取和修改数据,而不会发生数据冲突或不一致的问题。MVCC 的核心思想是保存数据的多个版本,这样不同的事务可以读取或修改不同的数据版本,从而避免了传统的锁机制所带来的性能瓶颈。
MVCC 的底层实现原理 1. 版本号和时间戳 版本号:
每个数据项都有一个版本号,标识其创建的时间。版本号可以是单调递增的数字,也可以是时间戳。 读版本号:
读取操作会获取一个读版本号,用于确定可以读取哪些版本的数据。 写版本号:
写操作会生成一个新的版本号,用于标识新版本的数据。 2. 数据版本管理 行级版本:
每一行数据都有多个版本,每个版本有自己的版本号。当一个事务开始时,它会读取数据的当前版本,并在此基础上进行修改。修改后的数据会被保存为一个新的版本。 可见性规则:
数据的可见性由版本号决定。事务只能看到在其开始之前已经提交的数据版本。 3. 读写分离 快照读 (Snapshot Read):
读取操作获取一个快照版本的数据,这个快照反映了事务开始时的数据状态。快照读不会阻塞写操作。 当前读 (Current Read):
读取操作获取最新的数据版本。当前读可能会阻塞写操作,因为需要确保读取的是最新提交的数据。 4. 垃圾回收 无效版本清理: 旧的数据版本会被标记为无效,并在适当的时候进行清理。清理过程通常发生在后台,以避免影响性能。 MySQL InnoDB 存储引擎中的 MVCC 实现 MySQL 的 InnoDB 存储引擎使用 MVCC 来支持事务的并发执行。InnoDB 的 MVCC 实现基于以下几个关键概念:
1. 行记录格式 记录元数据: InnoDB 的每一行记录都包含了额外的元数据,用于记录版本信息。包括系统版本号(SYS_ROW_ID)、最低可见版本号(MIN_TRX_ID)和最高可见版本号(MAX_TRX_ID)。 2. 可见性判断 可见性规则: InnoDB 使用事务 ID 来判断数据版本的可见性。如果一个事务 ID 在另一个事务的开始 ID 之前,那么该数据版本对该事务是可见的。可见性规则如下: 事务可以看到自己创建的所有版本。事务可以看到所有在它开始之前已经提交的版本。事务看不到在它开始之后创建的版本。事务看不到尚未提交的版本。 3. 事务隔离级别 REPEATABLE READ:
前言: 在C/C++程序中,会需要把数字与字符串做出互相转换的操作,用于实现程序想要的效果。下面将介绍多种方法实现数字与字符串互相转换。
字符串转为数字 一、利用ASCII 我们知道每个字符都有一个ASCII码,利用这一点可以将字符-'0'转为数字。在字母大小写转换时也可以利用这个性质。
#include<iostream> #include<cstring> using namespace std; char ch[]={'1','2','3','4','5'}; int main(){ for(int i=0;i<strlen(ch);i++){ cout<<ch[i]-'0'<<" "; } return 0; } 输出int类型的1,2,3,4,5
二、stoi() stoi函数是C++标准库中的一个函数,用于将字符串转换为整数,针对于string类型的。stoi函数是C++11引入的,因此只有在C++11及以上的版本中才能使用该函数。如果你的编译器版本较老,不支持C++11,那么就无法使用stoi函数。学校机房的dev-C++一般是C98标准的,需要自己手动改成C11。
int num=stoi(const string& str, size_t* idx = 0, int base = 10); 其中,str是需要转换的字符串;idx是一个指向size_t类型的指针,用于保存转换结束的位置;base是进制,默认为10进制。一般只给传第一个参数即可,也可以利用此函数实现进制转换。
类似的还有stol()、stof()、stod(),分别将字符串类型转换成long long、float、double类型。
#include<iostream> #include<cstring> using namespace std; string s="12345"; int main(){ int a=stoi(s); long long b=stol(s); double c=stod(s); float d=stof(s); cout<<"int:"<<a<<endl; cout<<"long long:"<<b<<endl; cout<<"double:"<<c<<endl; cout<<"float:"<<d<<endl; return 0; } 三、atoi() atoi()函数是C语言中的一个函数,主要用于将字符串转换为整数。针对于字符数组,类似的还有atol()、atof()、atod()。
int num=atoi(const char *str); 其中,str是一个以null结尾的字符串,表示要转换的整数。
在数字化时代,信息的保密性与安全性日益成为我们不可忽视的重要环节。尤其对于包含敏感信息或个人隐私的PDF文档而言,保护其免受未授权访问的侵扰显得尤为重要。通过为PDF文档设置密码保护,我们能够筑起一道坚实的防线,确保只有拥有正确密码的用户才能访问或修改文档内容。本文将深入解析如何为PDF文档实施密码保护策略,从准备工作到具体操作步骤,再到关键的注意事项,为您的文档安全保驾护航。
pdf加密码的设置方法一:使用“星优PDF工具箱”软件
软件下载地址:星优PDF工具箱-星优PDF工具箱最新版免费下载-星优办公软件
步骤一:首先,请确保你的电脑上已经下载安装“星优PDF工具箱”应用程序。打开工具后,点击左侧的“PDF文件处理”功能区域,接着在右侧点击“PDF加密”功能。
步骤二:功能确定后,在新界面点击“添加文件”按钮,这将打开一个文件选择器窗口。在这里,您可以浏览并选择您希望加密的PDF文件。可一次性上传多个文件,实现批量操作。
步骤三:文件上传成功后,在底部进行设置。在密码输入框内,我们设置想要的打开密码即可。还可以设置“保存目录”选项,通过点击并浏览,选择一个您希望保存加密后PDF文件的指定位置。
步骤四:设置完成,点击“开始转换”按钮,软件便会立即启动pdf加密码的设置。没一会,即可看到加密操作顺利完成。
步骤五:加密完成后,软件会自动打开输出文件夹。我们尝试打开已加密的PDF文件。此时,您会发现文件已受到密码保护,需要输入正确的密码才能正常访问其内容。这一验证步骤确保了您的PDF文件已经成功加密,达到了预期的安全保护效果。
pdf加密码的设置方法二:使用“优速PDF工厂”软件
下载并安装优速PDF工厂软件是保护PDF文件安全的第一步。该软件提供了多种功能,其中包括对PDF文件进行加密,以确保文件内容在传输和存储过程中不受未经授权的访问。
首先,确保你已经从官方网站或可靠来源下载并成功安装了优速PDF工厂软件。启动软件后,你将看到主界面,通常可以在首页或菜单中找到“PDF文件操作”选项。点击这个选项,进入到PDF文件处理的功能区域。
接下来,找到并点击“PDF加密”功能。这个选项通常位于界面的导航栏或主功能区内。进入加密功能后,你需要点击页面上的“添加文件”按钮,将你想要加密的PDF文件导入到软件中。优速PDF工厂支持同时处理多个PDF文件,因此你可以一次性选择多个文件进行加密。
在文件添加完毕后,在界面底部可以找到密码设置栏。在这里,输入你希望设置的加密密码。请确保密码的安全性和复杂性,以确保文件的最佳安全保护效果。
完成密码设置后,转到右侧或底部,找到并点击“开始转换”按钮。软件将根据你的设置和要求,开始对选定的PDF文件进行加密处理。这个过程通常很快速,取决于你选择的文件数量和大小。
加密完成后,你可以在指定的输出目录或默认位置找到加密后的PDF文件。打开文件时,系统将要求输入之前设置的密码,确保只有知道密码的人才能访问文件内容。
pdf加密码的设置方法三:使用“OLERT”软件
访问OLERT在线PDF转换工具的网站是一种便捷的方式,它提供了多种PDF文件处理选项,包括加密功能,帮助用户确保文件的安全性和隐私保护。
首先,打开你的浏览器并访问OLERT在线PDF转换工具的官方网站。通常,这类工具的网站设计简洁直观,用户可以快速找到所需的功能。
一旦进入网站,寻找并选择“PDF加密”功能选项。这一选项可能会显示在首页或导航栏的显眼位置。点击进入“PDF加密”功能区域后,你将看到一个上传文件的区域。
接下来,点击“上传文件”按钮,选择你想要加密的PDF文件。OLERT工具通常支持一次性上传多个文件,便于用户批量处理需要加密的文件。
在文件上传完成后,网页可能会显示一个加密密码设置的文本框。在这里,输入你希望设置的加密密码,并确认密码以确保准确性和安全性。
完成密码设置后,找到并点击“开始处理”按钮或类似的指示按钮,开始对你选择的PDF文件进行加密操作。加密过程通常会很快完成,具体时间取决于文件的大小和数量。
加密完成后,网页会生成一个下载链接或按钮,允许你下载已加密的PDF文件。点击下载链接,保存文件到你的计算机或设备中。
pdf加密码的设置方法四:使用“PDF-XChange Editor”软件
打开PDF-XChange Editor软件:
如果尚未安装,你可以从官方网站下载并安装PDF-XChange Editor软件。
打开需要加密的PDF文件:
在PDF-XChange Editor中,点击菜单栏的“文件”选项,然后选择“打开”以浏览并打开需要进行加密的PDF文件。
设置密码:
依次选择菜单栏的“文件” > “安全性” > “设置密码”选项。
设置打开密码:
在弹出的安全设置对话框中,勾选“设置打开密码”选项。
输入你希望设置的打开密码。确保密码的安全性和复杂性,以保护文件内容不被未经授权的访问。
设置其他权限密码(可选):
如果需要,你可以根据具体需求在同一个对话框中设置其他权限,如修改、打印等。这些选项取决于PDF-XChange Editor的版本和你的许可类型。
保存设置:
完成密码设置后,点击“确定”或“应用”按钮,以保存你的安全设置。
pdf加密码的设置方法五:使用“福昕PDF阅读器”软件
首先,确保你已经安装了福昕PDF阅读器这款功能强大的软件。它以其稳定的性能和丰富的功能而广受用户好评,其中就包括PDF文件的加密功能,能够帮助你有效保护文件的敏感信息。
接下来,按照以下步骤,你可以轻松地为你的PDF文件设置密码保护:
启动福昕PDF阅读器:双击桌面上的福昕PDF阅读器图标,或者在开始菜单中搜索并启动它。软件启动后,你将看到一个简洁而直观的用户界面。
打开PDF文件:在软件界面中,通常有多种方式可以打开PDF文件。你可以点击菜单栏上的“文件”选项,然后选择“打开”,浏览并找到你想要加密的PDF文件。另外,你也可以直接将PDF文件拖拽到软件窗口中打开。
找到加密选项:成功打开PDF文件后,将注意力转向菜单栏。在这里,你需要找到并点击“文件”这一选项,以展开更多功能。在下拉菜单中,仔细寻找“安全性”或类似的选项,并点击进入。在安全性设置中,你将看到“设置密码”或“加密文档”等具体选项,点击它以开始加密过程。
设置密码:点击“设置密码”后,软件会弹出一个对话框,要求你输入并确认密码。这里有两种密码类型可供选择:
打开密码:这是一种基础保护,只有输入正确的密码才能打开并查看PDF文件的内容。这对于保护高度敏感的文档非常有效。
权限密码:除了限制文件访问外,你还可以设置权限密码来控制文档的打印、编辑、复制等权限。这样,即使文件被打开,未授权的用户也无法进行特定操作。
根据你的需求,选择适合的密码类型,并在相应的输入框中输入你想要的密码。请注意,为了增强安全性,建议使用复杂且难以猜测的密码组合,包括大小写字母、数字和特殊符号。
保存加密设置:在输入并确认密码后,务必仔细阅读对话框中的其他选项(如加密强度、兼容性等),并根据需要进行调整。最后,点击“确定”或“应用”按钮以保存你的加密设置。此时,软件将开始对PDF文件进行加密处理,并在完成后提示你加密成功。
验证加密效果:为了确认文件已成功加密,你可以尝试关闭并重新打开PDF文件,看是否需要输入密码才能访问其内容。如果一切正常,那么恭喜你,你已经成功为你的PDF文件设置了密码保护!
pdf加密码的设置方法六:使用Windows系统加密
首先,你需要定位到想要加密的PDF文件或文件夹。这通常意味着你需要在文件资源管理器中浏览你的文件系统和目录,直到找到目标文件或文件夹。
右击需要加密的PDF文件或文件夹:
一旦你找到了想要加密的PDF文件或文件夹,用鼠标右键点击它。这将弹出一个上下文菜单,其中包含了一系列针对该文件或文件夹的操作选项。
选择“属性”选项:
在右键弹出的菜单中,向下滚动或查找并点击“属性”选项。这将打开一个包含文件或文件夹详细信息的窗口,其中包括大小、位置、创建日期等基本信息,以及一系列高级设置选项。
点击“高级”按钮:
在“属性”窗口中,你会注意到窗口的底部或顶部可能有一个“高级”按钮。点击这个按钮将打开一个名为“高级属性”的新窗口,这里提供了对文件或文件夹的更深入控制选项。
勾选“加密内容以便保护数据”选项:
在“高级属性”窗口中,你需要仔细查找与加密相关的选项。请注意,这个选项的确切位置可能因Windows的不同版本而略有差异。它可能位于“压缩或加密属性”部分,也可能在“安全”或“详细信息”选项卡下。一旦找到“加密内容以便保护数据”选项,勾选它。这个选项的作用是为文件或文件夹的内容提供加密保护,确保只有拥有正确密钥的用户才能访问。
在上一篇文章中,我们简要介绍了Java虚拟机(JVM)的概念、作用及其与操作系统之间的关系。现在,让我们更进一步,深入探讨JVM的内部结构,了解它的主要组件以及它们是如何协同工作来执行Java程序的。本文将为您揭示JVM的架构之谜,帮助您更好地理解Java程序的运行机制。
JVM的主要组件 JVM由几个关键组件构成,每个组件都有其独特的职责。以下是JVM的主要组件:
1. 类加载器(Class Loaders) 类加载器负责将Java类文件加载到JVM中。JVM使用三种类型的类加载器:
Bootstrap ClassLoader:负责加载Java核心库,如rt.jar。Extension ClassLoader:负责加载Java扩展库,如jre/lib/ext目录下的类库。System ClassLoader:负责加载当前应用的类路径(Classpath)中的类库。
类加载器采用双亲委派模型,确保类只被加载一次,并且是按照特定的顺序加载。 2. 运行时数据区(Runtime Data Area) 运行时数据区是JVM在执行Java程序时存储数据的内存区域。它包括以下几个部分:
方法区(Method Area):存储已被加载的类信息、常量、静态变量等。堆(Heap):Java对象的存储区域,是垃圾回收的主要场所。栈(Stack):每个线程运行时都有一个栈,用于存储局部变量、方法调用的上下文等。本地方法栈(Native Method Stack):为使用到的本地方法(如C/C++编写的)提供栈空间。程序计数器(Program Counter Register):每个线程都有一个程序计数器,用于存储指向下一条指令的地址。 3. 执行引擎(Execution Engine) 执行引擎负责执行Java字节码。它可以是解释器(Interpreter),也可以是即时编译器(JIT Compiler),或者两者的结合。执行引擎的主要职责包括:
解释字节码:将字节码逐条解释执行。编译优化:将频繁执行的字节码编译成本地机器码,以提高执行效率。垃圾回收:执行垃圾回收操作,清理不再使用的对象。 4. 本地库接口(Native Interface) 本地库接口是JVM与底层操作系统进行交互的桥梁。它允许Java程序调用其他语言编写的本地库(如C/C++库)。
5. 垃圾回收器(Garbage Collector,GC) 垃圾回收器负责管理JVM的内存,它自动回收不再使用的对象所占用的内存。垃圾回收器有多种实现,每种都有其特定的回收策略和算法。
总结 JVM的架构设计非常精妙,它确保了Java程序的跨平台性和高效执行。通过了解JVM的各个组件,我们可以更好地理解Java程序的运行机制,这对于编写高效且稳定的Java应用程序至关重要。在接下来的文章中,我们将深入探讨类加载机制和类文件结构,揭示Java类是如何被加载并执行的。
了解C语言中的按位逻辑运算: 在C语言中,按位运算符是处理二进制数据的强大工具。按位运算符可以直接操控数据的每一位,这使得它们在处理低级别的数据操作、优化性能、嵌入式系统开发、加密算法等场景中变得尤为重要。在这篇博客中,我们将深入探讨几种常见的按位逻辑运算符,包括按位与 (AND)、按位或 (OR)、按位异或 (XOR)、按位取反 (NOT) 等,并展示如何在C语言中使用它们。 接下來我們就以宏定義的方式來説明:
以下是一些常见的按位逻辑运算符及其定义:
按位或 (OR): #define OR(a, b) ((a) | (b))
只要 a 或 b 的对应位有一个为 1,结果的对应位就为 1。否则为 0。
按位与 (AND): #define AND(a, b) ((a) & (b))
只有当 a 和 b 的对应位都为 1 时,结果的对应位才为 1。否则为 0。
按位异或 (XOR): #define XOR(a, b) ((a) ^ (b))
当 a 和 b 的对应位不同时,结果的对应位为 1;相同时,结果的对应位为 0。
按位或取反 (NOR): #define NOR(a, b) (~((a) | (b)))
对 OR 操作的结果取反。
按位与取反 (NAND): #define NAND(a, b) (~((a) & (b)))
引言
在当今的数据驱动世界,实时数据采集和处理已经成为企业做出及时决策的重要手段。本文将详细介绍如何通过前端JavaScript代码采集用户行为数据、利用API和Kafka进行数据传输、通过Flink实时处理数据的完整流程。无论你是想提升产品体验还是做用户行为分析,这篇文章都将为你提供全面的解决方案。
设计一个通用的ClickHouse表来存储用户事件时,需要考虑多种因素,包括事件类型、时间戳、用户信息、设备信息、地理位置、页面信息等。这个设计应具有扩展性和灵活性,以便支持未来可能添加的新事件类型或字段。
以下是一个通用的ClickHouse表设计示例:
表名:user_events CREATE TABLE user_events ( -- 基础信息 event_id UInt64, -- 事件唯一标识符 user_id String, -- 用户ID event_type String, -- 事件类型 (如 "click", "view", "purchase" 等) event_timestamp DateTime64(3), -- 事件发生时间,精确到毫秒 session_id String, -- 会话ID,用于追踪用户在一个会话中的所有活动 page_url String, -- 事件发生的页面URL referrer_url String, -- 事件发生前的来源页面URL -- 设备信息 device_type String, -- 设备类型 (如 "desktop", "mobile", "tablet") os String, -- 操作系统 (如 "Windows", "iOS", "Android") browser String, -- 浏览器类型 (如 "Chrome", "Safari") app_version String, -- 应用版本号(如果是移动应用) -- 地理位置信息 country String, -- 国家 region String, -- 省/州/地区 city String, -- 城市 ip_address String, -- 用户IP地址 -- 事件详细信息 product_id String DEFAULT '', -- 产品ID (如事件涉及到某个产品) category_id String DEFAULT '', -- 分类ID (如产品或内容的分类) campaign_id String DEFAULT '', -- 广告活动ID (如涉及到营销活动) custom_data String DEFAULT '', -- 自定义数据,存储JSON格式的额外信息 -- 索引和分区 PRIMARY KEY (event_id), -- 主键,用于唯一标识每个事件 INDEX idx_user_id (user_id) TYPE set(1024) GRANULARITY 3, -- 基于用户ID的索引,加速查询 INDEX idx_event_type (event_type) TYPE set(256) GRANULARITY 3, -- 基于事件类型的索引 INDEX idx_event_timestamp (event_timestamp) TYPE minmax GRANULARITY 1 -- 基于时间戳的索引 ) ENGINE = MergeTree() PARTITION BY toYYYYMM(event_timestamp) -- 按照事件发生的月份进行分区 ORDER BY (event_timestamp, user_id, event_type) -- 排序键,优化查询 TTL event_timestamp + INTERVAL 1 YEAR DELETE -- 数据存储期限为1年,自动删除过期数据 SETTINGS index_granularity = 8192; -- 索引粒度设置 设计说明 基础信息
还记得我们昨天讨论的《Querying Historical Cohesive Subgraphs over Temporal Bipartite Graphs》这篇论文吗?
https://blog.csdn.net/m0_62361730/article/details/141003301
这篇(还没看的快去看)
这篇论文主要研究如何在时间双向图上查询历史凝聚子图,而《Efficient Core Maintenance in Large Bipartite Graphs》则比较关注动态双向图中的双核维护(维护(𝛼, 𝛽)-核是为了在插入、删除时可以快速找到关键子结构)。两者的主要区别在于前者处理的是时间维度上的子图查询,而后者则是对动态更新的高效维护。然而,两者在处理动态数据方面有相似之处,均需要考虑图的频繁更新对算法效率的影响。
论文简介 《Efficient Core Maintenance in Large Bipartite Graphs》由Wensheng Luo、Qiaoyuan Yang、Yixiang Fang和Xu Zhou撰写。这篇论文主要探讨了如何在大型双向图中高效地维护(𝛼, 𝛽)-核 (bi-core)。(𝛼, 𝛽)-核作为双向图中的一种重要的凝聚子图模型,已在许多实际应用中得到了广泛应用,如产品推荐、欺诈者检测和社区搜索。然而,由于双向图通常是动态的,其顶点和边经常被插入和删除,从头计算(𝛼, 𝛽)-核的成本非常高。为了缓解这个问题,论文提出了一些高效的(𝛼, 𝛽)-核维护算法,通过引入双核数(bi-core numbers)的新概念,有效地减少计算冗余。
甚麽是(𝛼, 𝛽)-核? (𝛼, 𝛽)-核是图中用于识别重要子结构的一种概念,特别适用于异质信息网络(Heterogeneous Information Networks,HINs)或双向图(Bipartite Graphs)。它是对经典的k-core概念的扩展,旨在捕捉图中的稠密子图或核心部分。
定义 在(𝛼, 𝛽)-核中,α和β分别是图中两种类型节点的度数阈值。具体来说,对于一个双向图G = (U, V, E),其中U和V是两类节点集,E是连接U和V的边集,(𝛼, 𝛽)-核定义如下:
一个子图H = (U’, V’, E’)是图G的(𝛼, 𝛽)-核,当且仅当: 对于每个节点u ∈ U’,其在子图H中的度数(即连接到V’中的节点数)至少为α。对于每个节点v ∈ V’,其在子图H中的度数(即连接到U’中的节点数)至少为β。 直观理解 (𝛼, 𝛽)-核确保了在子图中,类型U的每个节点至少有α个邻居,且类型V的每个节点至少有β个邻居。这种结构在分析图的密集区域或核心部分时非常有用,因为它过滤掉了那些连接较少、可能不太重要的节点,突出了稠密的子结构。
例子 假设有一个双向图表示学术网络,其中U是作者集合,V是论文集合,边表示作者和论文之间的写作关系。如果我们定义一个(𝛼, 𝛽)-核,比如(3, 2)-核:
大家好,我是栗筝i,这篇文章是我的 “栗筝i 的 Java 技术栈” 专栏的第 028 篇文章,在 “栗筝i 的 Java 技术栈” 这个专栏中我会持续为大家更新 Java 技术相关全套技术栈内容。专栏的主要目标是已经有一定 Java 开发经验,并希望进一步完善自己对整个 Java 技术体系来充实自己的技术栈的同学。与此同时,本专栏的所有文章,也都会准备充足的代码示例和完善的知识点梳理,因此也十分适合零基础的小白和要准备工作面试的同学学习。当然,我也会在必要的时候进行相关技术深度的技术解读,相信即使是拥有多年 Java 开发经验的从业者和大佬们也会有所收获并找到乐趣。
–
在多线程编程中,如何有效地进行线程间通信和协调是一个关键问题。Java 并发包中的阻塞队列集合(BlockingQueue)为开发者提供了强大的工具,能够简化线程同步与数据共享的复杂性。阻塞队列不仅能够在生产者和消费者之间进行线程安全的数据传递,还通过自动的阻塞和唤醒机制,帮助我们轻松实现高效的生产者-消费者模型。本篇文章将详细介绍 Java 中几种常用的阻塞队列集合,分析它们的特点、应用场景及实现原理,帮助您更好地理解并掌握这些并发工具。
文章目录 1、Java 阻塞队列的介绍1.1、Java 阻塞队列概述1.2、Java 阻塞队列接口1.3、Java 阻塞队列与非阻塞队列 2、Java 阻塞队列的具体实现2.1、数据结构2.2、插入操作2.3、获取操作 3、Java 阻塞队列知识点拓展3.1、ArrayBlockingQueue 和 LinkedBlockingQueue 之间的区别3.2、关于 SynchronousQueue 的介绍 1、Java 阻塞队列的介绍 1.1、Java 阻塞队列概述 Java 中的阻塞队列(BlockingQueue)是一种在多线程环境下用于线程安全的数据结构,它不仅提供了典型的队列操作(如插入和移除),还可以在队列为空或满时自动阻塞操作线程,直到队列状态允许操作的继续。阻塞队列通过阻塞和等待机制有效地协调生产者和消费者线程之间的操作,确保数据一致性和线程安全。
以下是其主要功能和应用场景:
线程间通信:阻塞队列在生产者-消费者模型中扮演了关键角色,它允许生产者线程和消费者线程之间进行线程安全的数据传递。具体表现为:
生产者线程:生产者线程将数据放入队列中。如果队列已满,生产者线程将被阻塞,直到队列有空闲空间。
消费者线程:消费者线程从队列中取出数据。如果队列为空,消费者线程将被阻塞,直到有数据可供消费。
流量控制:通过阻塞机制,阻塞队列可以有效地控制生产者和消费者的工作节奏,避免过载和资源浪费。当队列达到容量上限时,阻塞队列会自动阻止进一步的插入操作,直到有空间可用,从而避免过载和资源浪费。
简化并发编程:阻塞队列封装了复杂的同步机制,简化了多线程环境下的数据共享和线程协调,使得开发者可以专注于业务逻辑,而不必担心线程安全问题。
1.2、Java 阻塞队列接口 BlockingQueue 是 Java 并发包(java.util.concurrent)中的一个接口,继承自 Queue 接口。它提供了额外的阻塞操作,例如在队列为空时等待元素变得可用,或在队列已满时等待空间变得可用。
BlockingQueue 阻塞队列在 Java 中的主要实现有三个:
ArrayBlockingQueue: 基于数组实现的有界阻塞队列,必须指定固定容量,支持可选的公平性策略。LinkedBlockingQueue: 基于链表实现的阻塞队列,默认无界或指定容量,有较高的插入和删除性能。SynchronousQueue: 一个没有内部容量的队列,每个插入操作必须等待一个对应的删除操作,反之亦然,适用于直接交换数据的场景。 1.
IO模块接线
在FANUC系统中IO模块的种类比较多,每种IO模块的使用场合也不相同,每种IO模块的接线脚位也有很大区别,对于电气设计人员来说,清楚知道常用IO模块的接线脚位,才能更好的规划地址、设计图纸,对于设备维修人员来说,只有清楚了解常用IO模块的接线脚位才能更快速有效的处理有关IO模块的故障,接下来简单介绍下常用IO模块对应的脚位图纸:
1.操作盘用IO单元
操作盘用IO单元常被用于连接操作面板,该IO单元具有两个50芯接线脚位,分别为CE56,CE57脚位,接线脚位如下图,其中红色圈出的脚位必须要连接0V,容易遗忘,需特别注意。
FANUC发那科模块 A03B-0823-C001 I/O BASIC
FANUC发那科模块 A03B-0819-C185 AOD16D3
FANUC发那科模块 A03B-0819-C060 ADA02B
FANUC发那科模块 A03B-0819-C011 AIF01A
FANUC按键板N860-3127-T010 A86L-0001-0137 N86D-3127-R010
作者:南墨
1.Source性能调优 1.1 Spooldir Source 使用Spooldir Source采集日志数据时,若每行日志数据<100bp,可以通过将多行合并传输来提升传输性能
建议合并时根据数据长度来确定多少行合并为一个单位进行传输,合并后的长度建议在1K以上,譬如数据长度为50bp,那么可以采用20行合并为一个单位传输,配置示例如下:
server.sources.static_log_source.deserializer.maxBatchLine = 20
server.sources.static_log_source.deserializer.maxLineLength = 2048
1.2 Avro Source Avro source支持SSL加密传输,但加密传输势必会影响传输性能,因此如果环境足够安全或传输的数据非敏感数据,建议采用非加密传输来提升传输性能,配置示例如下:
server.sources.avro_source.ssl = false
1.3 TailDir Source 如果TAILDIR监视的目录下有数千文件,按照正则表达式列出所有的文件会是一个比较耗费资源的过程,建议打开cachePatternMatching开关以提升性能,配置示例如下:
server.sources.taildir_source.cachePatternMatching = false
2. Channel性能调优 2.1 File Channel 使用File Channel会将缓存数据写入本地磁盘,由于需要频繁的读写dataDirs所在磁盘,若数据流量比较大,可能造成磁盘IO高,从而影响传输性能;如果IO响应时间经常超过10ms,那么建议将dataDirs设置在更多的磁盘上以降低磁盘IO,配置示例如下:
server.channels.file_channel.dataDirs = /data/data1/flume/datadir, /data/data2/flume/datadir, /data/data3/flume/datadir
2.2 Memory Channel Memory Channel使用内存作为缓存,相较于File Channel有更好的性能,但使用Memory Channel可靠性较低,一旦宕机或其他意外发生,Channel中缓存的数据将会丢失,因此Channel的容量(capacity)不宜设置过大,另一方面Channel容量大小直接影响到flume进程占用内存大小,容量越大,占用的内存越大,GC耗时越长, 性能也越低;建议Channel容量设置为transactionCapacity的十倍,最好不要超过100000;配置示例如下:
server.channels.memory_channel.capacity = 100000
3. Sink性能调优 3.1 Hdfs Sink Hdfs Sink支持文件滚动,滚动策略有按时间(hdfs.rollInterval)、文件大小(hdfs.rollSize)、Event个数(hdfs.rollCount)滚动,该配置对传输性能有影响,滚动约频繁对性能影响越大;超时时间(hdfs.callTimeout)也对传输数据有影响,尤其是HDFS压力较大的场景;
如何提升hdfs sink的性能?
在业务允许的范围内,尽可能减小文件滚动频率;若HDFS压力较大、flume日志中有比较频繁hdfs超时异常,参见5.5 性能瓶颈监控及调优中超时异常章节介绍调整hdfs.callTimeout设置;
配置示例如下:
server.channels.hdfs_sink.coalesceIncrements = true 3.2 Kafka Sink 向kafka中写数据,一般要写若干副本(至少为1),Kafka Sink的kafka.
随着工业自动化的不断发展,传统的人工巡检方式已经难以满足现代工业对安全、效率和精度的要求。旗晟机器人推出的B2双电机系列挂轨巡检机器人,以其独特的优势,为工业巡检领域带来了革命性的变化。
一、产品亮点
B2双电机系列挂轨巡检机器人以其小巧的体积、强大的功能、稳定的性能,成为工业巡检的理想选择。该机器人配备了高精度的定位与导航系统,能够确保全场景无死角的巡逻检查,有效降低了漏检和误检的可能性。同时,其实时监测环境参数和异常检测功能,能够及时发现潜在问题,大大降低了人工成本和安全风险。
二、技术规格
1、外观与尺寸:B2双电机系列挂轨巡检机器人外观小巧,整机尺寸为635mm×365mm×593mm,便于在狭窄通道如管廊、输煤栈桥等复杂环境中部署。其自转半径≥1000mm,运行速度可达0-1.2m/s,爬坡能力≥20°,能够轻松应对各种地形。
2、云台与传感器:机器人搭载了双光谱云台,支持水平0°-360°、垂直-90°+90°的旋转角度,配备的红外热像仪分辨率高达384×288(兼容640),测温范围-20°C+550°C,测温精度±2°C或±2%。同时,可见光相机具备31倍光学变倍,最大分辨率1920x1080,星光级画质,确保在各种光线条件下都能清晰成像。此外,机器人还配备了雨刷、补光灯等辅助设备,提升了在不同环境下的适应能力。
3、电池与续航:机器人采用锂电池供电,续航能力长达6KM,充电时长不超过2小时,支持分布式无线充电和手动充电两种方式,确保了巡检任务的连续性和高效性。
4、安全防护与通讯:B2双电机系列挂轨巡检机器人在安全防护方面表现出色,前后装有安全触边和超声波避障系统,停障距离0-2米可调。同时,机器人支持2.4G/5.8G局域网和4G/5G通讯,确保了数据的实时传输和远程监控。
三、应用场景与优势
B2双电机系列挂轨巡检机器人广泛应用于煤矿厂、输煤栈桥、化工厂、管廊等复杂环境。在这些高低温、大风、易燃易爆、有毒易腐蚀的化工厂中,机器人凭借其IP65的防护等级和强大的环境适应能力,有效减轻了工作人员的劳动强度,降低了因人员疏忽、漏检等带来的损失。同时,机器人能够实时传播巡检数据,提供数据支持分析,帮助用户及时发现并处理潜在问题,提升工作效率和质量。
四、项目价值
B2双电机系列挂轨巡检机器人的引入,不仅解决了传统人工巡检的诸多局限性,如无法发现微泄漏、声音异响检测易遗漏、温度差异检测不直观等问题,还通过高度智能的机器人技术和图像识别技术,实现了设备区域的全覆盖巡视和实时监测。这不仅降低了人工成本和安全风险,还提高了巡检的精度和效率,为企业的安全生产和运维管理提供了有力保障。
总之,B2双电机系列挂轨巡检机器人以其卓越的性能和广泛的应用前景,正逐步成为工业巡检领域的新宠儿。随着技术的不断进步和应用的不断拓展,相信这款机器人将在更多领域发挥重要作用,为企业的智能化转型和高质量发展贡献力量。
12.1 Spring MVC 框架处理JSON数据 JSON 格式数据在现阶段Web项目开发中扮演者非常重要的角色。在前端页面后后台交互的过程中,需要一种格式清晰、高效且两端都可以轻松使用的数据格式做交互的媒介,JSON正可以满足这一需求,下面学习使用Spring MVC 框架处理JSON数据。
12.1.1 JSON 数据的传递处理 步骤1:修改控制层
控制层返回的JSON类型数据其实就是一个特殊格式的字符串,为了方便、高效的处理JSON数据,需要引入一个处理JSON类型数据的jar包——fastjson-1.2.31.jar,此包是阿里巴巴提供的一个处理JSON数据的开源工具,以高效著称。
需要在SysUserController控制器中增加一个验证账号是否重复的接口/uesrExist。关键代码如示例1所示。
示例1
@ResponseBody @GetMapping("/userExist") public Object userExist(@RequestParam String account) { log.debug("验证用户名 account=" + account + "的用户是否存在"); HashMap<String, Object> resultMap = new HashMap<String, Object>(); if (StringUtils.isNullOrEmpty(account)) { resultMap.put("exist", 1); } else { SysUser sysUser = sysUserService.getAccountExist(account); if (null != sysUser) { resultMap.put("exist", 1); } else { resultMap.put("exist", 0); } } return JSON.toJSONString(resultMap); } 在上述代码中,首先以用户账号account为参数调用业务层方法,查询用户信息,根据返回的对象是否为null,判断该账号是否已存在,然后将结果封装到一个HashMap对象中,最后通过调用JSON.toJsonString(resultMap)方法,将其转换为JSON格式数据并返回。
步骤2:修改视图层
在完成控制层代码的修改之后,还需要对前端页面相关的.js文件进行相应的调整,通过jQuery发起异步请求,,然后将后台控制器返回的结果展示在浏览器上。修改sysUSer/add.js文件,
关键代码如示例2所示。
可重入 1)如果一开始这个锁是没有的,第一次来加锁,这段lua脚本会如何执行?
"if (redis.call(‘exists’, KEYS[1]) == 0) then " +
"redis.call(‘hset’, KEYS[1], ARGV[2], 1); " +
"redis.call(‘pexpire’, KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
一开始这个锁如果没有,第一次加锁,会进这个if then分支,hset设置一个hash的数据结构,pexpire设置这个key的生存时间,直接返回nil,也就是已给null,这个lua脚本后面的内容其实就不会执行了
如果Future拿到了那个lua脚本执行成功后的返回值之后,就会触发一个监听器
这是我们前面梳理过的逻辑
2)如果是在一个客户端的一个线程内,先对一个lock进行了加锁,然后后面又加了一次锁,形成了一个叫做可重入锁的概念,就同一个线程对一个lock可以反复的重复加锁多次,每次加锁和一次释放锁必须是配对的
第二次过来加锁的时候
"if (redis.call(‘hexists’, KEYS[1], ARGV[2]) == 1) then " +
"redis.call(‘hincrby’, KEYS[1], ARGV[2], 1); " +
"redis.call(‘pexpire’, KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
hexists 命令,如果是查询的这个数据存的话,就返回“1”
那么这个判断会看到锁已经存在,下面的逻辑就给这个值+1 ,不断地+1
那可以猜测,释放的时候也是要不断地-1,加了多少次,也要对应的释放多少次。
锁互斥 如果已经有一个客户端的线程对一个key加了锁,那么此时其他的线程或者是客户端如果也要对这个key加锁,是如何被阻塞住的呢?部署在其他机器上的服务实例,或者是部署在其他机器上的其他服务
还是这个lua脚本实现的,我们现在已经加了一个名为anyLock的锁,并且线程id假如说是2345-zxcv-1