首页
壁纸
留言板
友链
更多
统计归档
Search
1
TensorBoard:训练日志及网络结构可视化工具
12,588 阅读
2
主板开机跳线接线图【F_PANEL接线图】
7,035 阅读
3
Linux使用V2Ray 原生客户端
6,149 阅读
4
移动光猫获取超级密码&开启公网ipv6
4,683 阅读
5
NVIDIA 显卡限制功率
3,131 阅读
好物分享
实用教程
linux使用
wincmd
学习笔记
mysql
java学习
nginx
综合面试题
大数据
网络知识
linux
放码过来
python
javascript
java
opencv
蓝桥杯
leetcode
深度学习
开源模型
相关知识
数据集和工具
模型轻量化
语音识别
计算机视觉
杂七杂八
硬件科普
主机安全
嵌入式设备
其它
bug处理
登录
/
注册
Search
标签搜索
好物分享
学习笔记
linux
MySQL
nvidia
typero
内网穿透
webdav
vps
java
cudann
gcc
cuda
树莓派
CNN
图像去雾
ssh安全
nps
暗通道先验
阿里云
jupiter
累计撰写
354
篇文章
累计收到
71
条评论
首页
栏目
好物分享
实用教程
linux使用
wincmd
学习笔记
mysql
java学习
nginx
综合面试题
大数据
网络知识
linux
放码过来
python
javascript
java
opencv
蓝桥杯
leetcode
深度学习
开源模型
相关知识
数据集和工具
模型轻量化
语音识别
计算机视觉
杂七杂八
硬件科普
主机安全
嵌入式设备
其它
bug处理
页面
壁纸
留言板
友链
统计归档
搜索到
78
篇与
的结果
2023-08-24
springcloud学习笔记(Alibaba)
1.微服务概述1.1 什么是微服务?微服务(Microservice Architecture) 是近几年流行的一种架构思想,关于它的概念很难一言以蔽之。究竟什么是微服务呢?我们在此引用ThoughtWorks 公司的首席科学家 Martin Fowler 于2014年提出的一段话:原文:https://martinfowler.com/articles/microservices.html汉化:https://www.cnblogs.com/liuning8023/p/4493156.html就目前而言,对于微服务,业界并没有一个统一的,标准的定义。但通常而言,微服务架构是一种架构模式,或者说是一种架构风格,它体长将单一的应用程序划分成一组小的服务,每个服务运行在其独立的自己的进程内,服务之间互相协调,互相配置,为用户提供最终价值,服务之间采用轻量级的通信机制(HTTP)互相沟通,每个服务都围绕着具体的业务进行构建,并且能狗被独立的部署到生产环境中,另外,应尽量避免统一的,集中式的服务管理机制,对具体的一个服务而言,应该根据业务上下文,选择合适的语言,工具(Maven)对其进行构建,可以有一个非常轻量级的集中式管理来协调这些服务,可以使用不同的语言来编写服务,也可以使用不同的数据存储。再来从技术维度角度理解下:微服务化的核心就是将传统的一站式应用,根据业务拆分成一个一个的服务,彻底地去耦合,每一个微服务提供单个业务功能的服务,一个服务做一件事情,从技术角度看就是一种小而独立的处理过程,类似进程的概念,能够自行单独启动或销毁,拥有自己独立的数据库。1.2 微服务与微服务架构微服务强调的是服务的大小,它关注的是某一个点,是具体解决某一个问题/提供落地对应服务的一个服务应用,狭义的看,可以看作是IDEA中的一个个微服务工程,或者Moudel。IDEA 工具里面使用Maven开发的一个个独立的小Moudel,它具体是使用SpringBoot开发的一个小模块,专业的事情交给专业的模块来做,一个模块就做着一件事情。强调的是一个个的个体,每个个体完成一个具体的任务或者功能。微服务架构是一种架构模式,由Martin Fowler 于2014年提出。它通常将单一应用程序划分成一组小的服务,服务之间相互协调,互相配合,为用户提供最终价值。每个服务运行在其独立的进程中,服务与服务之间采用轻量级的通信机制(如HTTP)互相协作,每个服务都围绕着具体的业务进行构建,并且能够被独立的部署到生产环境中,另外,应尽量避免统一的,集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具(如Maven)对其进行构建。1.3 微服务优缺点优点单一职责原则;每个服务足够内聚,足够小,代码容易理解,这样能聚焦一个- 指定的业务功能或业务需求;开发简单,开发效率高,一个服务可能就是专一的只干一件事;微服务能够被小团队单独开发,这个团队只需2-5个开发人员组成;微服务是松耦合的,是有功能意义的服务,无论是在开发阶段或部署阶段都是独立的;微服务能使用不同的语言开发;易于和第三方集成,微服务允许容易且灵活的方式集成自动部署,通过持续集成工具,如jenkins,Hudson,bamboo;微服务易于被一个开发人员理解,修改和维护,这样小团队能够更关注自己的工作成果,无需通过合作才能体现价值;微服务允许利用和融合最新技术;微服务只是业务逻辑的代码,不会和HTML,CSS,或其他的界面混合;每个微服务都有自己的存储能力,可以有自己的数据库,也可以有统一的数据库;缺点开发人员要处理分布式系统的复杂性;多服务运维难度,随着服务的增加,运维的压力也在增大;系统部署依赖问题;服务间通信成本问题;数据一致性问题;系统集成测试问题;性能和监控问题;1.4 微服务技术栈有那些? DubboSpringCloud服务注册中心ZookeeperSpring Cloud Netfilx Eureka服务调用方式RPCREST API服务监控Dubbo-monitorSpring Boot Admin断路器不完善Spring Cloud Netfilx Hystrix服务网关无Spring Cloud Netfilx Zuul分布式配置无Spring Cloud Config服务跟踪无Spring Cloud Sleuth消息总栈无Spring Cloud Bus数据流无Spring Cloud Stream批量任务无Spring Cloud Task2.SpringCloud概述2.1 Spring Cloud 是什么?Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的开发便利性简化了分布式系统的开发,比如服务发现、服务网关、服务路由、链路追踪等。Spring Cloud 并不重复造轮子,而是将市面上开发得比较好的模块集成进去,进行封装,从而减少了各模块的开发成本。换句话说:Spring Cloud 提供了构建分布式系统所需的“全家桶”。Spring官网:https://spring.io/2.2 SpringCloud和SpringBoot的关系SpringBoot专注于快速、方便的开发单个个体微服务;SpringCloud是关注全局的微服务协调整理治理框架,它将SpringBoot开发的一个个单体微服务,整合并管理起来,为各个微服务之间提供:配置管理、服务发现、断路器、路由、为代理、事件总栈、全局锁、决策竞选、分布式会话等等集成服务;SpringBoot可以离开SpringCloud独立使用,开发项目,但SpringCloud离不开SpringBoot,属于依赖关系;SpringBoot专注于快速、方便的开发单个个体微服务,SpringCloud关注全局的服务治理框架;2.3 SpringCloud能干嘛?Distributed/versioned configuration 分布式/版本控制配置Service registration and discovery 服务注册与发现Routing 路由Service-to-service calls 服务到服务的调用Load balancing 负载均衡配置Circuit Breakers 断路器Distributed messaging 分布式消息管理2.4 自学参考书SpringCloud Netflix 中文文档:https://springcloud.cc/spring-cloud-netflix.htmlSpringCloud 中文API文档(官方文档翻译版):https://springcloud.cc/spring-cloud-dalston.htmlSpringCloud中国社区:http://springcloud.cn/SpringCloud中文网:https://springcloud.cc2.5 总体架构图注册中心:nacos,替代方案eureka、consul、zookeeper配置中心: nacos ,替代方案sc config、consul config服务调用:feign,替代方案:resttempate熔断:sentinel,替代方案:Resilience4j熔断监控:sentinel dashboard负载均衡:sc loadbalancer网关:spring cloud gateway链路:spring cloud sleuth+zipkin,替代方案:skywalking等。3.使用nacos作为注册中心3.1 下载nacos,并启动Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。下载地址https://github.com/alibaba/nacos/releases,下载最新版的2.0版本。下载完成后,解压,在解压后的文件的/bin目录下,windows系统点击startup.cmd就可以启动nacos。linux或mac执行以下命令启动nacos。sh startup.sh -m standalone登陆页面:http://localhost:8848/nacos/,登陆用户nacos,登陆密码为nacos。极致省事版-docker安装# 拉取nacos容器镜像 docker pull nacos/nacos-server # 快速启动nacos服务容器 docker run -d --name nacos -p 8848:8848 -e MODE=standalone nacos/nacos-server3.2 工程案例父工程pom文件引入相关的依赖 <!-- spring boot 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>3.0.0</version> <type>pom</type> <scope>import</scope> </dependency> <!-- spring cloud 依赖 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>2022.0.0</version> <type>pom</type> <scope>import</scope> </dependency> <!-- spring cloud alibaba 依赖 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2022.0.0.0-RC1</version> <type>pom</type> <scope>import</scope> </dependency>服务提供者provider在provider的pom文件引入依赖: <!-- spring-boot-starter-web依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- nacos注册中心--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>2022.0.0.0-RC1</version> </dependency>配置文件application.yml:server: port: 8762 spring: application: name: provider cloud: nacos: discovery: server-addr: 172.19.112.2:8848写一个接口:@SpringBootApplication @RestController @EnableDiscoveryClient public class ProviderApplication { public static void main(String[] args) { SpringApplication.run(ProviderApplication.class, args); } @Value("${server.port}") String port; @GetMapping("/hi") public String hi(@RequestParam(value = "name", defaultValue = "feign",required = false) String name) { return "hello " + name + ", i'm provider ,my port:" + port; } }运行并查看nacos管理界面服务消费者consumer在pom文件引入以下依赖:需要注意的是引入openfeign,必须要引入loadbalancer,否则无法启动。<!-- spring-boot-starter-web依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- nacos-discovery依赖 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>2022.0.0.0-RC1</version> </dependency> <!-- openfeign依赖 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> <version>4.0.4</version> </dependency> <!-- loadbalancer依赖 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> <version>4.0.4</version> </dependency>配置文件application.yml:server: port: 8763 spring: application: name: consumer cloud: nacos: discovery: server-addr: 172.19.112.2:8848在工程的启动文件开启FeignClient的功能:@SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class ConsumerApplication { public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class, args); } }写一个FeignClient,去调用provider服务的接口:@FeignClient(value = "provider" ) public interface ProviderClient { @GetMapping("/hi") String hi(@RequestParam(value = "name", defaultValue = "feifn", required = false) String name); 写一个接口,让consumer去调用provider服务的接口:@RestController public class ConsumerController { @Autowired ProviderClient providerClient; @GetMapping("/hi-feign") public String hiFeign(){ return providerClient.hi("feign"); } }运行并查看nacos管理界面3.3 服务调用在浏览器上输入http://localhost:8763/hi-feign,浏览器返回响应:hello feign, i'm provider ,my port:8762可见浏览器的请求成功调用了consumer服务的接口,consumer服务也成功地通过feign成功的调用了provider服务的接口。3.4使用sc loadbanlancer作为负载均衡其实feign使用了spring cloud loadbanlancer作为负载均衡器。 可以通过修改provider的端口,再在本地启动一个新的provider服务,那么本地有2个provider 服务,端口分别为8760和8762。在浏览器上多次调用http://localhost:8763/hi-feign,浏览器会交替显示:hello feign, i'm provider ,my port:8762 hello feign, i'm provider ,my port:87604.使用nacos作为配置中心Nacos除了可以作为服务注册中心,它还有服务配置中心的功能。类似于consul config,Nacos 是支持热加载的。配置中心和注册中心的依赖包是不同的,注册中心的依赖包是 nacos discovery,而配置中心的依赖包是 nacos config,它的具体如下。4.1 工程案例在工程的pom文件引入nacos-config的Spring cloud依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-alibaba-nacos-config</artifactId> <version>0.9.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- spring-cloud服务模块必须依赖,否则无法启动 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> <version>4.0.2</version> </dependency> <!-- nacos配置中心 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> <version>2022.0.0.0-RC1</version> </dependency>在bootstrap.yml(一定是bootstrap.yml文件,不是application.yml文件)文件配置以下内容:spring: application: name: nacos-config cloud: nacos: config: server-addr: 172.19.112.2:8848 file-extension: yaml prefix: nacos-config profiles: active: dev在上面的配置中,配置了nacos config server的地址,配置的扩展名是ymal(目前仅支持ymal和properties)。注意是没有配置server.port的,sever.port的属性在nacos中配置。上面的配置是和Nacos中的dataId 的格式是对应的,nacos的完整格式如下:${prefix}-${spring.profile.active}.${file-extension}prefix 默认为 spring.application.name 的值,也可以通过配置项 spring.cloud.nacos.config.prefix来配置。spring.profile.active 即为当前环境对应的 profile,详情可以参考 Spring Boot文档。注意:当 spring.profile.active 为空时,对应的连接符 - 也将不存在,dataId 的拼接格式变成 ${prefix}.${file-extension}file-exetension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension 来配置。目前只支持 properties 和 yaml 类型。启动nacos,创建一个data id :nacos-config-dev.yaml,完整的配置为:server: port: 8761 nacosvar: jupiter-nacos-config-dev写一个RestController,在Controller上添加 @RefreshScope 实现配置的热加载。代码如下:@RestController @RefreshScope public class ConfigController { @Value("${nacosvar:null}") private String nacosvar; @RequestMapping("/nacosvar") public String get() { return "nacosvar="+nacosvar; } }启动工程nacos-config,在浏览器上访问localhost:8761/nacosvar:5.OpenFeignTODO参考资料【狂神说Java】SpringCloud笔记(5万字保姆级笔记)_一只小逸白的博客-CSDN博客SpringCloud从入门到精通(超详细文档一)_cuiqwei的博客-CSDN博客SpringCloud 2020版本教程1:使用nacos作为注册中心和配置中心 - 方志朋的博客 (fangzhipeng.com)一文快速上手 Nacos 注册中心+配置中心!-阿里云开发者社区 (aliyun.com)spring cloud alibaba springboot nacos 版本对应_FH-Admin的博客-CSDN博客Nacos Spring Boot 快速开始springboot3.0集成nacos2.2.1(一)_nacos和springboot版本对应_魔锋剑上缺的博客-CSDN博客
2023年08月24日
55 阅读
0 评论
0 点赞
2023-08-21
nmap主机&端口扫描工具学习笔记
1.Nmap简介Nmap是一款非常强大的主机发现和端口扫描工具,而且nmap运用自带的脚本,还能完成漏洞检测,同时支持多平台。官网为:www.nmap.org。一般情况下,Nmap用于列举网络主机清单、管理服务升级调度、监控主机或服务运行状况。Nmap可以检测目标机是否在线、端口开放情况、侦测运行的服务类型及版本信息、侦测操作系统与设备类型等信息。1.1 Nmap的优点:灵活。支持数十种不同的扫描方式,支持多种目标对象的扫描强大。Nmap可以用于扫描互联网上大规模的计算机可移植。支持主流操作系统:Windows/Linux/Unix/MacOS等等;源码开放,方便移植简单。提供默认的操作能覆盖大部分功能,基本端口扫描nmap targetip,全面的扫描nmap –A targetip自由。Nmap作为开源软件,在GPL License的范围内可以自由的使用文档丰富。Nmap官网提供了详细的文档描述。Nmap作者及其他安全专家编写了多部Nmap参考书籍社区支持。Nmap背后有强大的社区团队支持1.2 Nmap包含四项基本功能:主机发现 (Host Discovery)端口扫描 (Port Scanning)版本侦测 (Version Detection)操作系统侦测 (Operating System Detection)而这四项功能之间,又存在大致的依赖关系(通常情况下的顺序关系,但特殊应用另外考虑),首先需要进行主机发现,随后确定端口状态,然后确定端口上运行的具体应用程序和版本信息,然后可以进行操作系统的侦测。而在这四项功能的基础上,nmap还提供防火墙和 IDS 的规避技巧,可以综合运用到四个基本功能的各个阶段。另外nmap还提供强大的NSE(Nmap Scripting Language)脚本引擎功能,脚本可以对基本功能进行补充和扩展。2.常用命令2.1 端口扫描2.1.1 默认扫描1000个端口扫描主机的「开放端口」,在nmap后面直接跟主机IP(默认扫描1000个端口)alpine1:~# nmap 127.0.0.1 Starting Nmap 7.80 ( https://nmap.org ) at 2023-08-21 08:19 UTC Nmap scan report for alpine1.mshome.net (127.0.0.1) Host is up (0.0000070s latency). Not shown: 998 closed ports PORT STATE SERVICE 22/tcp open ssh 80/tcp open http2.1.2 指定端口扫描nmap 192.168.31.180 -p 80 nmap 192.168.31.180 -p 1-80 nmap 192.168.31.180 -p 80,3389,22,21 nmap 192.168.31.180 -p 1-655352.1.3 设置扫描方式-sS 使用TCP的SYN进行扫描 -sT 使用TCP进行扫描 -sA 使用TCP的ACK进行扫描 -sU UDP扫描 -sI Idle扫描 -sF FIN扫描 -b<FTP中继主机> FTP反弹扫描2.2 主机探测扫描网段中有哪些主机在线,使用 -sP 参数,不扫描端口,只扫描「存活主机」。本质上是Ping扫描,能Ping通有回包,就判定主机在线。alpine1:~# nmap -sP 172.31.126.0/24 Starting Nmap 7.80 ( https://nmap.org ) at 2023-08-21 08:29 UTC Nmap scan report for alpine1.mshome.net (172.31.126.219) Host is up. Nmap done: 256 IP addresses (1 host up) scanned in 10.44 seconds2.3 服务识别扫描端口时,默认显示端口对应的服务,但不显示服务版本。想要识别具体的「服务版本」,可以使用 -sV 参数。nmap 127.0.0.1 -p 80 -sV2.4 操作系统识别想要识别「操作系统版本」,可以使用 -O 参数。alpine1:~# nmap -p 80 -O 127.0.0.1 Starting Nmap 7.80 ( https://nmap.org ) at 2023-08-21 08:37 UTC Nmap scan report for alpine1.mshome.net (127.0.0.1) Host is up (0.000051s latency). PORT STATE SERVICE 80/tcp open http Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port Device type: general purpose Running: Linux 2.6.X OS CPE: cpe:/o:linux:linux_kernel:2.6.32 OS details: Linux 2.6.32 Network Distance: 0 hops OS detection performed. Please report any incorrect results at https://nmap.org/submit/ . Nmap done: 1 IP address (1 host up) scanned in 1.74 seconds参考资料黑客工具之Nmap详细使用教程 - 掘金 (juejin.cn)Nmap使用教程图文教程(超详细) - 知乎 (zhihu.com)nmap命令-----基础用法 - nmap - 博客园 (cnblogs.com)
2023年08月21日
39 阅读
0 评论
0 点赞
2023-08-21
java数据随机初始化&bean随机初始化
0.背景最近在开发一个web小demo,其中进行数据测试需要对一些字段特别多的java bean进行初始化,一番百度之后查找到了几个解决方案,记录如下。1.Jpopulator1.1 pom依赖<dependency> <groupId>io.github.benas</groupId> <artifactId>jpopulator</artifactId> <version>1.0.1</version> </dependency>1.2 使用方式及测试实体类import lombok.Data; @Data public class Person { private int id; private String name; private String gender; }测试代码@Test public void testJpopulator(){ Populator populator = new PopulatorBuilder().build(); Person person = (Person) populator.populateBean(Person.class); System.out.println(person); List<Person> persons = populator.populateBeans(Person.class, 2); System.out.println(persons); }Person(id=-1042426959, name=nOBdJpkFbB, gender=bCwLDjOxXb) [Person(id=813095571, name=zxDkvjecWM, gender=SztfWBTZEj), Person(id=1664076867, name=ckMadNdfcX, gender=qmxsXZOiOI)]2.PODAM2.1 pom依赖<dependency> <groupId>uk.co.jemos.podam</groupId> <artifactId>podam</artifactId> <version>7.1.1.RELEASE</version> </dependency>2.2 使用方式及测试实体类import lombok.Data; @Data public class Person { private int id; private String name; private String gender; }基本使用@Test public void testPodam(){ PodamFactory factory = new PodamFactoryImpl(); Person person = factory.manufacturePojo(Person.class); System.out.println(person); }Person(id=1889825458, name=s6tv8LgsPD, gender=zY5put8aZT)2.3 进阶自定义数据生成策略比较复杂,不推荐。参考文档:Java Unit Tests make easy - Random Values with PODAM (onloadcode.com)3.common-random参考文档:https://github.com/yindz/common-random支持的数据类型:数字(int/long/double)汉字(简体)邮箱地址中文人名(简体)英文人名虚拟身份证号码(中国大陆)虚拟信用卡号码(Visa/Mastercard/JCB/银联/AmericanExpress)手机号码(中国大陆)省份和城市(中国大陆)邮编(中国大陆)联系地址(中国大陆)车牌号(中国大陆,包括新能源车型)域名静态URL日期(特定日期之前/特定日期之后)时间(过去/未来)时间戳强密码网络昵称(登录名)拼音网络昵称(登录名)IPv4地址端口号QQ号码非主流QQ网名学历小学名称、年级、班级中学名称、年级、班级高校名称(数据取自教育部网站)公司名称经纬度(中国)中文短句User-Agent(PC/Android/iOS)网卡MAC地址RGB颜色值HEX颜色值股票名称+股票代码开放式基金名称+基金代码缺点:只能逐一生成单个的随机字段,数据、生成对象需要逐一对对象的属性进行填充吗。对于字段较多的对象生成比较麻烦4.Jmockdata(★★★)(推荐)支持类型:Java基本类型字符串枚举日期数组多维数组集合[List|Set|Map]Java对象4.1 pom依赖<dependency> <groupId>com.github.jsonzou</groupId> <artifactId>jmockdata</artifactId> <version>4.2.0</version> </dependency>4.2 Java常用类型的生成@Test public void testJmockdata(){ int randomInt = JMockData.mock(int.class); long randomLong = JMockData.mock(long.class); double randomDouble = JMockData.mock(double.class); float randomFloat = JMockData.mock(float.class); String randomString = JMockData.mock(String.class); BigDecimal randomBigDecimal = JMockData.mock(BigDecimal.class); System.out.println("randomInt:"+randomInt); System.out.println("randomLong:"+randomLong); System.out.println("randomDouble:"+randomDouble); System.out.println("randomFloat:"+randomFloat); System.out.println("randomString:"+randomString); System.out.println("randomBigDecimal:"+randomBigDecimal); }randomInt:1116 randomLong:6722 randomDouble:1303.83 randomFloat:9700.79 randomString:cggMvc randomBigDecimal:3029.84.3 对象的生成实体类@Data public class Student { private int id; private String name; private int age; private LocalDateTime createTime; }生成测试数据@Test public void testJmockdata(){ Student student1 = JMockData.mock(Student.class); System.out.println("默认生成策略生成结果:"+student1); //按照规则定义Student对象里面单个字段生成,没配置的就按默认的生成邪恶了 MockConfig mockConfig = new MockConfig() .subConfig("id").intRange(1,10) .subConfig("name").subConfig("[a-zA-Z_]{1}[a-z0-9_]{5,15}") .subConfig("age").intRange(18,21) .globalConfig(); Student student2 = JMockData.mock(Student.class,mockConfig); System.out.println("自定义生成策略生成结果:"+student2); }执行结果默认生成策略生成结果:Student(id=7105, name=s, age=9755, createTime=2039-09-15T13:53:47.294) 自定义生成策略生成结果:Student(id=1, name=akXrp, age=18, createTime=2050-12-04T07:05:53.299)4.4 根据正则模拟数据配合4.3共同完成对象属性的初始化。/** * 根据正则模拟数据 * 正则优先于其他规则 */ @Test public void testRegexMock() { MockConfig mockConfig = new MockConfig() // 随机段落字符串 .stringRegex("I'am a nice man\\.And I'll just scribble the characters, like:[a-z]{2}-[0-9]{2}-[abc123]{2}-\\w{2}-\\d{2}@\\s{1}-\\S{1}\\.?-.") // 邮箱 .subConfig(RegexTestDataBean.class,"userEmail") .stringRegex("[a-z0-9]{5,15}\\@\\w{3,5}\\.[a-z]{2,3}") // 用户名规则 .subConfig(RegexTestDataBean.class,"userName") .stringRegex("[a-zA-Z_]{1}[a-z0-9_]{5,15}") // 年龄 .subConfig(RegexTestDataBean.class,"userAge") .numberRegex("[1-9]{1}\\d?") // 用户现金 .subConfig(RegexTestDataBean.class,"userMoney") .numberRegex("[1-9]{2}\\.\\d?") // 用户的得分 .subConfig(RegexTestDataBean.class,"userScore") .numberRegex("[1-9]{1}\\d{1}") // 用户身价 .subConfig(RegexTestDataBean.class,"userValue") .numberRegex("[1-9]{1}\\d{3,8}") .globalConfig(); }参考资料Jpopulator测试数据生成工具-腾讯云开发者社区-腾讯云 (tencent.com)Podam 一个Pojo填充随机值利器_这个程序员像只猴的博客-CSDN博客jmockdata: Jmockdta是一款实现模拟JAVA类型或对象的实例化并随机初始化对象的数据的工具框架。单元测试的利器 (gitee.com)GitHub - yindz/common-random: 简单易用的随机数据生成器。生成各种比较真实的假数据。一般用于开发和测试阶段的数据填充模拟。支持各类中国特色本地化的数据格式。An easy-to use random data generator. Generally used for data filling, simulation, demonstration and other scenarios in the development and test phase.JmockData---jfairy 学习记录(生成测试数据)_"小王"的博客-CSDN博客Jmockdta是一款实现模拟JAVA类型或对象的实例化并随机初始化对象的数据的工具框架-面圈网 (mianshigee.com)
2023年08月21日
45 阅读
0 评论
0 点赞
2023-08-16
Swagger学习笔记
1.简介1.1 产生背景故事还是要从前后端分离讲起啊前后端分离:VUE+SpringBoot 基本上都用这一套后端时代:前端只用管理静态页面,html===》后端,使用模版引擎 jsp=》后端主力前后端分离时代:后端:后端控制层,服务层,数据访问层【后端团队】前端:前端控制层,视图层,【前端团队】伪造后端数据,json,已经存在数据,不需要后端,前端工程依旧可以跑起来前后端如何交互 ====》API前后端相对独立,松耦合前后端甚至可以部署在不同的服务器上产生一个问题:前后端联调,前端和后端人员无法做到及时协商,解决问题,导致问题爆发需要一个东西可以解决这个问题解决问题:首先指定计划,实时更新API,较低集成风险早些年:指定word计划文档1.2 Swagger介绍Swagger是一组围绕 OpenAPI 规范构建的开源工具,可帮助您设计、构建、记录和使用 REST API。主要的 Swagger 工具包括:Swagger Editor – 基于浏览器的编辑器,您可以在其中编写 OpenAPI 规范。Swagger UI – 将 OpenAPI 规范呈现为交互式 API 文档。Swagger2于17年停止维护,现在最新的版本为 Swagger3(Open Api3)2.Swagger注解2.1 常用注解-swagger2版本swagger2是通过扫描很多的注解来获取数据帮我们展示在ui界面上的,下面就介绍下常用的注解。注解类方法属性@Api(tags)标注一个类为 Swagger 资源, 设置资源名称, 默认是类名 @ApiOperation(value) 标注一个请求, 设置该请求的名称, 默认是方法名 @ApiParam (不常用) 仅用于 JAX-RS @ApiImplicitParam (常用) 功能同 @ApiParame, 可用于 Servlet @ApiImplicitParams 包裹多个参数描述注解 @ApiModel标注一个实体类 @ApiModelProperty 标注实体属性, 设置属性的备注信息@ApiResponse 描述响应码,以及备注信息 @ApiResponses 包裹多个响应描述注解 @ApiIgnore使swagger忽略某个资源使swagger忽略某个接口使swagger忽略某个属性1、@Api():用在请求的类上,表示对类的说明,也代表了这个类是swagger2的资源参数:tags:说明该类的作用,参数是个数组,可以填多个。 value="该参数没什么意义,在UI界面上不显示,所以不用配置" description = "用户基本信息操作"2、@ApiOperation():用于方法,表示一个http请求访问该方法的操作参数:value="方法的用途和作用" notes="方法的注意事项和备注" tags:说明该方法的作用,参数是个数组,可以填多个。 格式:tags={"作用1","作用2"} (在这里建议不使用这个参数,会使界面看上去有点乱,前两个常用)3、@ApiModel():用于响应实体类上,用于说明实体作用参数:description="描述实体的作用" 4、@ApiModelProperty:用在属性上,描述实体类的属性参数:value="用户名" 描述参数的意义 name="name" 参数的变量名 required=true 参数是否必选5、@ApiImplicitParams:用在请求的方法上,包含多@ApiImplicitParam6、@ApiImplicitParam:用于方法,表示单独的请求参数参数:name="参数ming" value="参数说明" dataType="数据类型" paramType="query" 表示参数放在哪里 · header 请求参数的获取:@RequestHeader · query 请求参数的获取:@RequestParam · path(用于restful接口) 请求参数的获取:@PathVariable · body(不常用) · form(不常用) defaultValue="参数的默认值" required="true" 表示参数是否必须传7、@ApiParam():用于方法,参数,字段说明 表示对参数的要求和说明参数:name="参数名称" value="参数的简要说明" defaultValue="参数默认值" required="true" 表示属性是否必填,默认为false8、@ApiResponses:用于请求的方法上,根据响应码表示不同响应一个@ApiResponses包含多个@ApiResponse9、@ApiResponse:用在请求的方法上,表示不同的响应参数:code="404" 表示响应码(int型),可自定义 message="状态码对应的响应信息" 10、@ApiIgnore():用于类或者方法上,不被显示在页面上11、@Profile({"dev", "test"}):用于配置类上,表示只对开发和测试环境有用2.2 使用 swagger3 注解代替 swagger2 的(可选)这一步是可选的,因为改动太大,故 springfox对旧版的 swagger做了兼容处理。 但不知道未来会不会不兼容,这里列出如何用 swagger 3 的注解(已经在上面引入)代替 swagger 2 的 (注意修改 swagger 3 注解的包路径为io.swagger.v3.oas.annotations.)对应关系为:swagger2OpenAPI 3注解位置@Api@Tag(name = “接口类描述”)Controller 类上@ApiOperation@Operation(summary =“接口方法描述”)Controller 方法上@ApiImplicitParams@ParametersController 方法上@ApiImplicitParam@Parameter(description=“参数描述”)Controller 方法上 @Parameters 里@ApiParam@Parameter(description=“参数描述”)Controller 方法的参数上@ApiIgnore@Parameter(hidden = true) 或 @Operation(hidden = true) 或 @Hidden-@ApiModel@SchemaDTO类上@ApiModelProperty@SchemaDTO属性上Swagger2 的注解命名以易用性切入,全是 Api 开头,在培养出使用者依赖注解的习惯后,Swagger 3将注解名称规范化,工程化。3.SpringBoot集成Swagger23.1 pom.xml依赖<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.7.0</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.7.0</version> </dependency>3.2 简单集成简单编写一个controller@RestController @RequestMapping("/") public class HelloController { @RequestMapping("/hello") public String hello(){ return "hello springboot!"; } }编写 Swagger 配置类,默认什么都不写会扫描所有@Configuration @EnableSwagger2 // 开启Swagger2 public class SwaggerConfig { }访问接口文档:http://localhost:8080/swagger-ui.html备注:这里springboot3会不支持(版本太高),改为了2.6.1,否则运行会报如下错误,org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'apiDocumentationScanner' defined in URL [jar:file:/C:/Users/LuoJia/.m2/repository/io/springfox/springfox-spring-web/2.9.2/springfox-spring-web-2.9.2.jar!/springfox/documentation/spring/web/scanners/ApiDocumentationScanner.class]: Unsatisfied dependency expressed through constructor parameter 1: Error creating bean with name 'apiListingScanner' defined in URL [jar:file:/C:/Users/LuoJia/.m2/repository/io/springfox/springfox-spring-web/2.9.2/springfox-spring-web-2.9.2.jar!/springfox/documentation/spring/web/scanners/ApiListingScanner.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'apiDescriptionReader' defined in URL [jar:file:/C:/Users/LuoJia/.m2/repository/io/springfox/springfox-spring-web/2.9.2/springfox-spring-web-2.9.2.jar!/springfox/documentation/spring/web/scanners/ApiDescriptionReader.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'cachingOperationReader' defined in URL [jar:file:/C:/Users/LuoJia/.m2/repository/io/springfox/springfox-spring-web/2.9.2/springfox-spring-web-2.9.2.jar!/springfox/documentation/spring/web/scanners/CachingOperationReader.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'apiOperationReader' defined in URL [jar:file:/C:/Users/LuoJia/.m2/repository/io/springfox/springfox-spring-web/2.9.2/springfox-spring-web-2.9.2.jar!/springfox/documentation/spring/web/readers/operation/ApiOperationReader.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'documentationPluginsManager': Unsatisfied dependency expressed through field 'documentationPlugins': Error creating bean with name 'documentationPluginRegistry': FactoryBean threw exception on object creation at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:800) ~[spring-beans-6.0.11.jar:6.0.11] at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:245) ~[spring-beans-6.0.11.jar:6.0.11] ···3.3 高级集成3.3.1 相关注解SwaggerConfig相关的相关注解注解说明@Configuration用于SwaggerConfig类前表明这是个配置类,项目会自动把Swagger页面加载进来@EnableSwagger2开启Swagger页面的使用@Bean用于返回Docket的方法前,Docket持有对各个接口的具体处理。3.3.2 配置基本信息和swagger扫描范围新建config包,创建SwaggerConfig类,重点注意基于.apis和.paths配置swagger扫描范围的配置。package com.example.testspringboot.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.service.Contact; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; @Configuration @EnableSwagger2 public class Swagge2Config { // 配置swagger页面的头部 即api文档的详细信息介绍 private ApiInfo setApiInfo() { return new ApiInfoBuilder() .title("XX平台API接口文档") //页面标题 .contact(new Contact("jupiter", "https://blog.inat.top", "xxxxxx@qq.com"))//联系人 .version("1.0")//版本号 .description("系统API描述")//描述 .build(); } @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) // 指定swagger2版本 .enable(true) // 开关 .apiInfo(setApiInfo())// 配置swagger页面的头部信息 .select()// 配置扫描接口,使用过滤,必须先调用select方法; /** * 通过apis方法,basePackage可以根据包路径来生成特定类的API, * any() // 扫描所有,项目中的所有接口都会被扫描到 * none() // 不扫描接口 * withMethodAnnotation(final Class<? extends Annotation> annotation) * 通过方法上的注解扫描,如withMethodAnnotation(GetMapping.class)只扫描get请求 * withClassAnnotation(final Class<? extends Annotation> annotation) * 通过类上的注解扫描,如.withClassAnnotation(Controller.class)只扫描有controller注解的类中的接口 * basePackage(final String basePackage) // 根据包路径扫描接口 */ .apis(RequestHandlerSelectors.basePackage("com.example.testspringboot.controller")) /** *除此之外,我们还可以通过.paths方法配置接口扫描过滤 * any() // 任何请求都扫描 * none() // 任何请求都不扫描 * regex(final String pathRegex) // 通过正则表达式控制 * ant(final String antPattern) // 通过ant()控制 */ .paths(PathSelectors.any()) .build(); // 使用了select()方法后必须进行build } } 3.3.3 swagger分组如果项目大了之后可能有几百上千个接口。如果全在一个组内,找起来特别麻烦。swagger可以配置多个Docket,每个Docket都可以设置一个分组,并设定每个Docket的单独过滤规则。这样就完美设置成了一个大的功能模块对应一个分组。可以通过.groupName方法设置分组名。 @Bean public Docket docket1(){ return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .groupName("group1") .enable(swaggerModel.isEnable()) .select() .paths(PathSelectors.any()) .build(); } @Bean public Docket docket2(){ return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .groupName("group2") .enable(swaggerModel.isEnable()) .select() .paths(PathSelectors.any()) .build(); } @Bean public Docket docket3(){ return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .groupName("group3") .enable(swaggerModel.isEnable()) .select() .paths(PathSelectors.any()) .build(); }3.3.4 Swagger换皮肤皮肤的使用非常简单,只需简单的引入依赖即可。bootstrap-ui <!-- 引入swagger-bootstrap-ui包 /doc.html--> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>swagger-bootstrap-ui</artifactId> <version>1.9.1</version> </dependency>swagger-mg-ui<dependency> <groupId>com.zyplayer</groupId> <artifactId>swagger-mg-ui</artifactId> <version>1.0.6</version> </dependency>knife4j<dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-spring-ui</artifactId> <version>2.0.6</version> </dependency>4.SpringBoot集成Swagger34.1 配置文件pom.xml<dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId> <version>4.3.0</version> </dependency>application.ymlspringdoc: swagger-ui: path: /swagger-ui.html tags-sorter: alpha operations-sorter: alpha enabled: true api-docs: path: /v3/api-docs enabled: true group-configs: group: platform paths-to-match: /** packages-to-scan: com.license4j.license knife4j: enable: true setting: language: zh_cn然后,就可以启动测试输入地址http://ip:port/doc.html参考资料API Documentation & Design Tools for Teams | Swaggerswagger:快速入门_冷环渊的博客-CSDN博客Swagger3学习笔记_swagger3 @apiresponses_C.kai的博客-CSDN博客Swagger3 学习笔记 - xtyuns - 博客园 (cnblogs.com)Swagger笔记—Swagger3详细配置-腾讯云开发者社区-腾讯云 (tencent.com)齐全的swagger注解介绍 - 知乎 (zhihu.com)Swagger与Docket - ShineLe - 博客园 (cnblogs.com)SpringBoot集成swagger2报错‘apiDocumentationScanner‘ defined in URL_吃啥?的博客-CSDN博客swagger配置扫描接口、扫描路径条件_swagger docket该路径_呐呐呐-的博客-CSDN博客SpringBoot集成Swagger(六)玩转groupName()分组 | Java随笔记 - 掘金 (juejin.cn)springBoo3.0集成knife4j4.1.0(swagger3)_华义辰的博客-CSDN博客Springboot3.0.0+集成SpringDoc并配置knife4j的UI_Anakki的博客-CSDN博客
2023年08月16日
268 阅读
0 评论
0 点赞
2023-08-16
sqlite3学习笔记
1.sqlite3简介1.1 什么是 SQLite?SQLite,是一款轻型的数据库,是遵守ACID的关系型数据库管理系统,它包含在一个相对小的C库中。它是D.RichardHipp建立的公有领域项目。它的设计目标是嵌入式的,而且目前已经在很多嵌入式产品中使用了它,它占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了。它能够支持Windows/Linux/Unix等等主流的操作系统,同时能够跟很多程序语言相结合,比如 Tcl、C#、PHP、Java等,还有ODBC接口,同样比起Mysql、PostgreSQL这两款开源的世界著名数据库管理系统来讲,它的处理速度比他们都快。SQLite第一个Alpha版本诞生于2000年5月。 至2015年已经有15个年头,SQLite也迎来了一个版本 SQLite 3已经发布。1.2 为什么要用 SQLite?不需要一个单独的服务器进程或操作的系统(无服务器的)。SQLite 不需要配置,这意味着不需要安装或管理。一个完整的 SQLite 数据库是存储在一个单一的跨平台的磁盘文件。SQLite 是非常小的,是轻量级的,完全配置时小于 400KiB,省略可选功能配置时小于250KiB。SQLite 是自给自足的,这意味着不需要任何外部的依赖。SQLite 事务是完全兼容 ACID 的,允许从多个进程或线程安全访问。SQLite 支持 SQL92(SQL2)标准的大多数查询语言的功能。SQLite 使用 ANSI-C 编写的,并提供了简单和易于使用的 API。SQLite 可在 UNIX(Linux, Mac OS-X, Android, iOS)和 Windows(Win32, WinCE, WinRT)中运行。1.3 SQLite 局限性在 SQLite 中,SQL92 不支持的特性如下所示:特性描述RIGHT OUTER JOIN只实现了 LEFT OUTER JOIN。FULL OUTER JOIN只实现了 LEFT OUTER JOIN。ALTER TABLE支持 RENAME TABLE 和 ALTER TABLE 的 ADD COLUMN variants 命令,不支持 DROP COLUMN、ALTER COLUMN、ADD CONSTRAINT。Trigger 支持支持 FOR EACH ROW 触发器,但不支持 FOR EACH STATEMENT 触发器。VIEWs在 SQLite 中,视图是只读的。您不可以在视图上执行 DELETE、INSERT 或 UPDATE 语句。GRANT 和 REVOKE可以应用的唯一的访问权限是底层操作系统的正常文件访问权限。1.4 SQLite 命令与关系数据库进行交互的标准 SQLite 命令类似于 SQL。命令包括 CREATE、SELECT、INSERT、UPDATE、DELETE 和 DROP。这些命令基于它们的操作性质可分为以下几种:DDL - 数据定义语言命令描述CREATE创建一个新的表,一个表的视图,或者数据库中的其他对象。ALTER修改数据库中的某个已有的数据库对象,比如一个表。DROP删除整个表,或者表的视图,或者数据库中的其他对象。DML - 数据操作语言命令描述INSERT创建一条记录。UPDATE修改记录。DELETE删除记录。DQL - 数据查询语言命令描述SELECT从一个或多个表中检索某些记录。2.SQLite 命令如需获取可用的点命令的清单,可以在任何时候输入 ".help"。例如:sqlite>.help常用命令:命令描述.backup ?DB? FILE备份 DB 数据库(默认是 "main")到 FILE 文件。.databases列出数据库的名称及其所依附的文件。.dump ?TABLE?以 SQL 文本格式转储数据库。如果指定了 TABLE 表,则只转储匹配 LIKE 模式的 TABLE 表。.exit退出 SQLite 提示符。.header(s) ON或OFF开启或关闭头部显示。.help显示所有命令列表和帮助信息。.import FILE TABLE导入来自 FILE 文件的数据到 TABLE 表中。.mode MODE设置输出模式,MODE 可以是下列之一:csv 逗号分隔的值column 左对齐的列html HTML 的 \<table\> 代码insert TABLE 表的 SQL 插入(insert)语句line 每行一个值list 由 .separator 字符串分隔的值tabs 由 Tab 分隔的值tcl TCL 列表元素.quit退出 SQLite 提示符。.read FILENAME执行 FILENAME 文件中的 SQL。.schema ?TABLE?显示 CREATE 语句。如果指定了 TABLE 表,则只显示匹配 LIKE 模式的 TABLE 表。.separator STRING改变输出模式和 .import 所使用的分隔符。.show显示各种设置的当前值。.tables ?PATTERN?列出匹配 LIKE 模式的表的名称。3.SQLite 数据类型3.1 SQLite 存储类每个存储在 SQLite 数据库中的值都具有以下存储类之一:存储类描述NULL值是一个 NULL 值。INTEGER值是一个带符号的整数,根据值的大小存储在 1、2、3、4、6 或 8 字节中。REAL值是一个浮点值,存储为 8 字节的 IEEE 浮点数字。TEXT值是一个文本字符串,使用数据库编码(UTF-8、UTF-16BE 或 UTF-16LE)存储。BLOB值是一个 blob 数据,完全根据它的输入存储。SQLite 的存储类稍微比数据类型更普遍。INTEGER 存储类,例如,包含 6 种不同的不同长度的整数数据类型。3.2 SQLite 亲和(Affinity)类型SQLite支持列的亲和类型概念。任何列仍然可以存储任何类型的数据,当数据插入时,该字段的数据将会优先采用亲缘类型作为该值的存储方式。下表列出了当创建 SQLite3 表时可使用的各种数据类型名称,同时也显示了相应的亲和类型:数据类型亲和类型INT、INTEGER、TINYINT、SMALLINT、MEDIUMINT、BIGINT、UNSIGNED BIG INT、INT2、INT8INTEGERCHARACTER(20)、VARCHAR(255)、VARYING CHARACTER(255)、NCHAR(55)、NATIVE CHARACTER(70)、NVARCHAR(100)、TEXT、CLOBTEXTBLOB、未指定类型BLOBREAL、DOUBLE、DOUBLE PRECISION、FLOATREALNUMERIC、DECIMAL(10,5)、BOOLEAN、DATE、DATETIMENUMERIC3.3 Date 与 Time 数据类型SQLite 没有一个单独的用于存储日期和/或时间的存储类,但 SQLite 能够把日期和时间存储为 TEXT、REAL 或 INTEGER 值。存储类日期格式TEXT格式为 "YYYY-MM-DD HH:MM:SS.SSS" 的日期。REAL从公元前 4714 年 11 月 24 日格林尼治时间的正午开始算起的天数。INTEGER从 1970-01-01 00:00:00 UTC 算起的秒数。您可以以任何上述格式来存储日期和时间,并且可以使用内置的日期和时间函数来自由转换不同格式。4.SQLite 创建数据库4.1 语法sqlite3 命令的基本语法如下:$ sqlite3 DatabaseName.db通常情况下,数据库名称在 RDBMS 内应该是唯一的。另外我们也可以使用 .open 来建立新的数据库文件:sqlite>.open test.db上面的命令创建了数据库文件 test.db,位于 sqlite3 命令同一目录下。打开已存在数据库也是用 .open 命令,以上命令如果 test.db 存在则直接会打开,不存在就创建它。4.2 dump 命令您可以在命令提示符中使用 SQLite .dump 点命令来导出完整的数据库在一个文本文件中,如下所示:$sqlite3 testDB.db .dump > testDB.sql上面的命令将转换整个 testDB.db 数据库的内容到 SQLite 的语句中,并将其转储到 ASCII 文本文件 testDB.sql 中。您可以通过简单的方式从生成的 testDB.sql 恢复,如下所示:$sqlite3 testDB.db < testDB.sql4.SQLite表管理命令4.1 创建表CREATE TABLE database_name.table_name( column1 datatype, column2 datatype, column3 datatype, ..... columnN datatype, PRIMARY KEY( one or more columns ) );示例create table department( id integer not null, dept_name text not null, emp_id integet not null, primary key(id) );sqlite> .tables department4.2 查看表的完整信息可以使用 SQLite的 .schema 命令得到表的完整信息。sqlite> .schema department CREATE TABLE department( id integer not null, dept_name text not null, emp_id integet not null, primary key(id) );4.3 删除表DROP TABLE 语句的基本语法如下。您可以选择指定带有表名的数据库名称,如下所示:DROP TABLE database_name.table_name;4.4 修改表Alter 命令用来重命名已有的表的 ALTER TABLE 的基本语法如下:ALTER TABLE database_name.table_name RENAME TO new_table_name;用来在已有的表中添加一个新的列的 ALTER TABLE 的基本语法如下(新添加的列是以 NULL 值来填充的。):ALTER TABLE database_name.table_name ADD COLUMN column_def...;4.5 清空表Truncate操作在 SQLite 中,并没有 TRUNCATE TABLE 命令,但可以使用 SQLite 的 DELETE 命令从已有的表中删除全部的数据,但建议使用 DROP TABLE 命令删除整个表,然后再重新创建一遍。5.SQLite表数据增删改查、视图、事务与mysql等数据库基本保持一致6.java spring项目使用SQLite6.1 pom.xml<dependency> <groupId>org.xerial</groupId> <artifactId>sqlite-jdbc</artifactId> <version>3.25.2</version> </dependency>6.2 配置文件中设置datasource为sqlite# application-dev.yaml server: port: 80 spring: datasource: # url最容易出错,如果使用相对于项目的相对地址,那么填入 jdbc:sqlite::resource:sqlit数据库所在位置 # 注: # :resource: 指向项目的 resources 路径(resource前后两个 `:` 不能省略) url: jdbc:sqlite::resource:db/user.sqlite driver-class-name: org.sqlite.JDBC # username: 选用 sqlite 数据库不需要配置此项 # password: 选用 sqlite 数据库不需要配置此项 continue-on-error: true mybatis-plus: mapper-locations: classpath:mapper/*Mapper.xml type-aliases-package: com.example.springbootsqlite.model数据源配好以后,其他操作跟普通 springboot项目一致,mybatis及mybatis-plus 可用完整项目参考:https://gitee.com/cphovo/springboot-sqlite参考资料SQLite 教程 | 菜鸟教程 (runoob.com)sqlite3数据库小白入门(Linux) - 知乎 (zhihu.com)SQLite 教程_w3cschoolSpring Boot With SQLite | BaeldungIDEA中使用SQLite数据库_idea sqlite_牛右刀薛面的博客-CSDN博客Springboot 整合 Sqlite - kosihpc - 博客园 (cnblogs.com)springboot-sqlite: 一个以 sqlite 为数据库的 springboot-demo (gitee.com)
2023年08月16日
63 阅读
0 评论
0 点赞
2023-08-16
Hutool:小而全的Java工具类库
1.Hutool介绍1.1 简介Hutool是一个功能丰富且易用的Java工具库,通过诸多实用工具类的使用,旨在帮助开发者快速、便捷地完成各类开发任务。 这些封装的工具涵盖了字符串、数字、集合、编码、日期、文件、IO、加密、数据库JDBC、JSON、HTTP客户端等一系列操作, 可以满足各种不同的开发需求。1.2 Hutool名称的由来Hutool = Hu + tool,是原公司项目底层代码剥离后的开源库,“Hu”是公司名称的表示,tool表示工具。Hutool谐音“糊涂”,一方面简洁易懂,一方面寓意“难得糊涂”。1.3 Hutool理念来自官方:Hutool既是一个工具集,也是一个知识库,我们从不自诩代码原创,大多数工具类都是搬运而来,因此:你可以引入使用,也可以拷贝和修改使用,而不必标注任何信息,只是希望能把bug及时反馈回来。我们努力健全中文注释,为源码学习者提供良好地学习环境,争取做到人人都能看得懂。1.4 官方文档中文文档中文备用文档参考API视频介绍1.5 前端pom.xml引入全部包<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.16</version> </dependency>2.Hutool包含组件一个Java基础工具类,对文件、流、加密解密、转码、正则、线程、XML等JDK方法进行封装,组成各种Util工具类,同时提供以下组件:模块介绍hutool-aopJDK动态代理封装,提供非IOC下的切面支持hutool-bloomFilter布隆过滤,提供一些Hash算法的布隆过滤hutool-cache简单缓存实现hutool-core核心,包括Bean操作、日期、各种Util等hutool-cron定时任务模块,提供类Crontab表达式的定时任务hutool-crypto加密解密模块,提供对称、非对称和摘要算法封装hutool-dbJDBC封装后的数据操作,基于ActiveRecord思想hutool-dfa基于DFA模型的多关键字查找hutool-extra扩展模块,对第三方封装(模板引擎、邮件、Servlet、二维码、Emoji、FTP、分词等)hutool-http基于HttpUrlConnection的Http客户端封装hutool-log自动识别日志实现的日志门面hutool-script脚本执行封装,例如Javascripthutool-setting功能更强大的Setting配置文件和Properties封装hutool-system系统参数调用封装(JVM信息等)hutool-jsonJSON实现hutool-captcha图片验证码实现hutool-poi针对POI中Excel和Word的封装hutool-socket基于Java的NIO和AIO的Socket封装hutool-jwtJSON Web Token (JWT)封装实现可以根据需求对每个模块单独引入,也可以通过引入hutool-all方式引入所有模块。3.各模块功能使用参考官方文档:https://www.hutool.cn/docs参考资料hutool: 小而全的Java工具类库,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 (gitee.com)Hutool——国产良心工具包,让你的java变得更甜 - 知乎 (zhihu.com)入门和安装 (hutool.cn)
2023年08月16日
43 阅读
0 评论
0 点赞
2023-08-15
redis学习笔记
1.Redis介绍1.1 Redis 简介Redis 是完全开源的,遵守 BSD 协议,是一个高性能的 key-value 数据库。Redis 与其他 key - value 缓存产品有以下三个特点:Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。Redis支持数据的备份,即master-slave模式的数据备份。1.2 Redis 优势性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性。1.3 Redis与其他key-value存储有什么不同?Redis有着更为复杂的数据结构并且提供对他们的原子性操作,这是一个不同于其他数据库的进化路径。Redis的数据类型都是基于基本数据结构的同时对程序员透明,无需进行额外的抽象。Redis运行在内存中但是可以持久化到磁盘,所以在对不同数据集进行高速读写时需要权衡内存,因为数据量不能大于硬件内存。在内存数据库方面的另一个优点是,相比在磁盘上相同的复杂的数据结构,在内存中操作起来非常简单,这样Redis可以做很多内部复杂性很强的事情。同时,在磁盘格式方面他们是紧凑的以追加的方式产生的,因为他们并不需要进行随机访问。2.Redis安装和配置略3.Redis常用命令3.1 Redis 客户端连接服务器# 连接远程服务器语法 $ redis-cli -h host -p port -a password # 连接本机无密码redis服务器示例 并使用ping命令检测服务器是否启动 alpine1:/home/gitlab/etc# redis-cli -h 127.0.0.1 -p 6379 127.0.0.1:6379> ping PONG3.2 Redis 键(key)Redis 键命令用于管理 redis 的键。# Redis 键命令基本语法 redis 127.0.0.1:6379> COMMAND KEY_NAME # 删除key,如果键被删除成功,命令执行后输出 (integer) 1,否则将输出 (integer) 0 127.0.0.1:6379> del key (integer) 1 # 检查给定 key 是否存在。 127.0.0.1:6379> exists key (integer) 1 127.0.0.1:6379> exists noexistkey (integer) 0 # 为给定 key 设置过期时间,以秒计。 127.0.0.1:6379> expire key 100 (integer) 1 # 以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)。 127.0.0.1:6379> ttl key (integer) 44 # 设置 key 的过期时间以毫秒计。 127.0.0.1:6379> pexpire key 1000000 (integer) 1 # 以毫秒为单位返回 key 的剩余的过期时间。 127.0.0.1:6379> pttl key (integer) 994201 # 移除 key 的过期时间,key 将持久保持。 127.0.0.1:6379> persist key (integer) 1 127.0.0.1:6379> ttl key (integer) -1 # 查找所有符合给定模式( pattern)的 key 。 127.0.0.1:6379> keys * 1) "key" 2) "key1" # 修改 key 的名称 127.0.0.1:6379> get key1 "hello" 127.0.0.1:6379> rename key1 key2 OK 127.0.0.1:6379> get key2 "hello" # 仅当 newkey 不存在时,将 key 改名为 newkey 。 127.0.0.1:6379> renamenx key2 key (integer) 0 127.0.0.1:6379> renamenx key2 key3 (integer) 1 # 查看key 所储存的值的类型。 127.0.0.1:6379> type key string3.3 Redis 字符串命令# 设置指定 key 的值。 127.0.0.1:6379> set key helloworld OK # 获取指定 key 的值。 127.0.0.1:6379> get key "helloworld" # 将 key 中储存的数字值增一。 127.0.0.1:6379> set number 100 OK 127.0.0.1:6379> incr number (integer) 101 # 将 key 所储存的值加上给定的增量值(increment) 。 127.0.0.1:6379> incrby number 101 (integer) 202 # 将 key 中储存的数字值减一。 127.0.0.1:6379> decr number (integer) 201 # key 所储存的值减去给定的减量值(decrement) 。 127.0.0.1:6379> decrby number 100 (integer) 101 # 指定的 value 追加到该 key 原来值(value)的末尾。 127.0.0.1:6379> set appendstr hello OK 127.0.0.1:6379> append appendstr world (integer) 10 127.0.0.1:6379> get appendstr "helloworld" # 将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。 127.0.0.1:6379> setex key seconds value3.4 Redis 哈希(Hash)命令Redis hash 是一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。类似java中的HashMapRedis 中每个 hash 可以存储 $2^32$ - 1 键值对(40多亿)。# 批量设置hash对象的属性 # 语法 hmset key field value [field value ...] 127.0.0.1:6379> hmset person name zhangsan age 22 school caus OK # 获取在哈希表中指定 key 的所有字段和值 127.0.0.1:6379> hgetall person 1) "name" 2) "zhangsan" 3) "age" 4) "22" 5) "school" 6) "caus" # 将哈希表 key 中的字段 field 的值设为 value 。 # 语法 hset key field value 127.0.0.1:6379> hset person girlfirend xiaohong (integer) 1 127.0.0.1:6379> hgetall person 1) "name" 2) "zhangsan" 3) "age" 4) "22" 5) "school" 6) "caus" 7) "girlfirend" 8) "xiaohong" # 删除一个或多个哈希表字段 # 语法 hdel key field [field ...] 127.0.0.1:6379> hdel person girlfirend (integer) 1 127.0.0.1:6379> hgetall person 1) "name" 2) "zhangsan" 3) "age" 4) "22" 5) "school" 6) "caus" # 获取存储在哈希表中指定字段的值。 # 语法 hget key field 127.0.0.1:6379> hmget person name 1) "zhangsan"3.5 Redis 列表(List)命令Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)# 将一个或多个值插入到列表头部 # 语法 lpush key value [value ...] 127.0.0.1:6379> lpush list a b c d (integer) 4 # 查看列表指定区间元素、查看列表全部元素 # 语法 lrange key start stop 127.0.0.1:6379> lrange list 0 -1 1) "d" 2) "c" 3) "b" 4) "a" 127.0.0.1:6379> lrange list 0 2 1) "d" 2) "c" # 通过索引获取列表中的元素 # 语法 lindex key index 127.0.0.1:6379> lindex list 2 "b" # 获取列表长度 127.0.0.1:6379> llen list (integer) 4 # 移出并获取列表的第一个元素 127.0.0.1:6379> lpop list "d" # 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。 # 语法 ltrim key start stop 127.0.0.1:6379> ltrim list 0 1 OK 127.0.0.1:6379> lrange list 0 -1 1) "c" 2) "b" # 在列表中添加一个或多个值到列表尾部 # 语法 rpush key value [value ...] 127.0.0.1:6379> rpush list e f i g h i j k (integer) 10 127.0.0.1:6379> lrange list 0 -1 1) "c" 2) "b" 3) "e" 4) "f" 5) "i" 6) "g" 7) "h" 8) "i" 9) "j" 10) "k" # 移除列表的最后一个元素,返回值为移除的元素。 127.0.0.1:6379> rpop list "k"3.6 Redis 集合(Set)命令Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。集合对象的编码可以是 intset 或者 hashtable。Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。# 向集合添加一个或多个成员 # 语法 sadd key member [member ...] 127.0.0.1:6379> sadd myset hello world (integer) 2 # 查看集合所有元素 127.0.0.1:6379> smembers myset 1) "world" 2) "hello"3.7 Redis 有序集合(sorted set)命令Redis 有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。有序集合的成员是唯一的,但分数(score)却可以重复。# 向有序集合添加一个或多个成员,或者更新已存在成员的分数 # 语法 zadd key score1 member1 [score2 member2] 127.0.0.1:6379> zadd sortedset 80 xiaoming 90 xiaohong 66 zhangsan (integer) 3 # 通过索引区间返回有序集合指定区间内的成员 # 语法 zrange key start stop [WITHSCORES] 127.0.0.1:6379> zrange sortedset 0 100 WITHSCORES 1) "zhangsan" 2) "66" 3) "xiaoming" 4) "80" 5) "xiaohong" 6) "90" 127.0.0.1:6379> zrange sortedset 0 100 1) "zhangsan" 2) "xiaoming" 3) "xiaohong" # 计算在有序集合中指定区间分数的成员数 # 语法 zcount key min max 127.0.0.1:6379> zcount sortedset 80 90 (integer) 2 # 有序集合中对指定成员的分数加上增量 increment # 语法 zincrby key increment member 127.0.0.1:6379> zincrby sortedset 10 zhangsan "76" # 移除有序集合中的一个或多个成员 # 语法 zrem key member [member ...] 127.0.0.1:6379> zrem sortedset xiaoming (integer) 1 127.0.0.1:6379> zrange sortedset 0 100 WITHSCORES 1) "zhangsan" 2) "76" 3) "xiaohong" 4) "90"4.Redis 发布订阅Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。Redis 客户端可以订阅任意数量的频道。下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client3 和 client1 之间的关系:当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:# 订阅给定的一个或多个频道的信息。 # 语法 subscribe channel [channel ...] # 将信息发送到指定的频道。 # publish channel message示例# 第一个客户端--订阅者 127.0.0.1:6379> subscribe jupizhe Reading messages... (press Ctrl-C to quit) 1) "subscribe" 2) "jupiter" 3) (integer) 1 1) "message" 2) "jupiter" 3) "hello, welcom to subscribe" 1) "message" 2) "jupiter" 3) "who are you" # 第二个客户端--发布者 127.0.0.1:6379> publish jupiter "who are you" (integer) 15.Redis Stream6.Java maven项目使用 Redis6.1 pom.xml<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency6.2 连接到 redis 服务@Test void testRedisConnect(){ //连接 Redis 服务 Jedis jedis = new Jedis("172.31.126.219"); // 如果 Redis 服务设置了密码,需要下面这行,没有就不需要 // jedis.auth("123456"); System.out.println("连接成功"); //查看服务是否运行 System.out.println("服务正在运行: "+jedis.ping()); }连接成功 服务正在运行: PONG6.3 Redis Java String(字符串) 实例@Test void testRedisString() throws InterruptedException { //连接 Redis 服务 Jedis jedis = new Jedis("172.31.126.219"); // 如果 Redis 服务设置了密码,需要下面这行,没有就不需要 // jedis.auth("123456"); System.out.println("连接成功"); //设置 redis 字符串数据 jedis.setex("verifycode",300,"387432"); // 获取存储的数据并输出 System.out.println("redis 存储的字符串为: "+ jedis.get("verifycode")); Thread.sleep(3000); System.out.println("redis 存储的字符串有效期为: "+ jedis.ttl("verifycode")+" 秒"); }连接成功 redis 存储的字符串为: 387432 redis 存储的字符串有效期为: 297 秒6.4 Redis Java List(列表) 实例@Test void testRedisList() { //连接Redis 服务 Jedis jedis = new Jedis("172.31.126.219"); // 如果 Redis 服务设置了密码,需要下面这行,没有就不需要 // jedis.auth("123456"); System.out.println("连接成功"); //存储数据到列表中 jedis.lpush("site-list", "JinDong"); jedis.lpush("site-list", "Google"); jedis.lpush("site-list", "Taobao"); // 获取存储的数据并输出 List<String> list = jedis.lrange("site-list", 0 ,2); for(int i=0; i<list.size(); i++) { System.out.println("列表项为: "+list.get(i)); }连接成功 列表项为: Taobao 列表项为: Google 列表项为: JinDong其他调用参考redis常用命令参考资料Redis 教程 | 菜鸟教程 (runoob.com)Redis命令大全(超详细) - 蚂蚁小哥 - 博客园 (cnblogs.com)Redis(第1期):如何在Java中优雅的使用Redis - 知乎 (zhihu.com)Redis命令大全(超详细) - 蚂蚁小哥 - 博客园 (cnblogs.com)
2023年08月15日
45 阅读
0 评论
0 点赞
2023-08-10
Git学习笔记
1.Git简介1.1 简介Git 是一个开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目。Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。Git 与常用的版本控制工具 CVS, Subversion 等不同,它采用了分布式版本库的方式,不必服务器端软件支持。1.2 git工作流程克隆 Git 资源作为工作目录。在克隆的资源上添加或修改文件。如果其他人修改了,你可以更新资源。在提交前查看修改。提交修改。在修改完成后,如果发现错误,可以撤回提交并再次修改并提交。1.3 Git 工作区、暂存区和版本库工作区:就是你在电脑里能看到的目录。暂存区:英文叫 stage 或 index。一般存放在 .git 目录下的 index 文件(.git/index)中,所以我们把暂存区有时也叫作索引(index)。版本库:工作区有一个隐藏目录 .git,这个不算工作区,而是 Git 的版本库。2.Git 安装和配置用户信息Git 各平台安装包下载地址为:http://git-scm.com/downloads2.1 Linux 平台上安装Git 的工作需要调用 curl,zlib,openssl,expat,libiconv 等库的代码,所以需要先安装这些依赖工具。在有 yum 的系统上(比如 Fedora)或者有 apt-get 的系统上(比如 Debian 体系),可以用下面的命令安装:各 Linux 系统可以使用其安装包管理工具(apt-get、yum 等)进行安装:Debian/Ubuntu$ apt-get install libcurl4-gnutls-dev libexpat1-dev gettext libz-dev libssl-dev $ apt-get install gitCentos/RedHat$ yum install curl-devel expat-devel gettext-devel openssl-devel zlib-devel $ yum -y install git-core2.2 Windows 平台上安装在 Windows 平台上安装 Git 同样轻松,有个叫做 msysGit 的项目提供了安装包,可以到 GitHub 的页面上下载 exe 安装文件并运行:安装包下载地址:https://gitforwindows.org/官网慢,可以用国内的镜像:https://npm.taobao.org/mirrors/git-for-windows/。2.3 配置用户信息配置个人的用户名称和电子邮件地址:$ git config --global user.name "runoob" $ git config --global user.email test@runoob.com如果用了 --global 选项,那么更改的配置文件就是位于你用户主目录下的那个,以后你所有的项目都会默认使用这里配置的用户信息。如果要在某个特定的项目中使用其他名字或者电邮,只要去掉 --global 选项重新配置即可,新的设定保存在当前项目的 .git/config 文件里。要检查已有的配置信息,可以使用 git config --list 命令:$ git config --list http.postbuffer=2M user.name=runoob user.email=test@runoob.com3.Git 基本操作Git 常用的是以下 6 个命令:git clone、git push、git add 、git commit、git checkout、git pullworkspace:工作区staging area:暂存区/缓存区local repository:版本库或本地仓库remote repository:远程仓库3.1 创建仓库命令git init # 初始化仓库 git clone # 拷贝一份远程仓库,也就是下载一个项目。3.2 提交与修改git add # 添加文件到暂存区 git status # 查看仓库当前的状态,显示有变更的文件。 git diff # 比较文件的不同,即暂存区和工作区的差异。 git commit # 提交暂存区到本地仓库。 git reset # 回退版本。 git rm # 将文件从暂存区和工作区中删除。 git mv # 移动或重命名工作区文件。3.3 查看提交日志git log # 查看历史提交记录 git blame <file> # 以列表形式查看指定文件的历史修改记录3.4 远程操作git remote # 远程仓库操作 git fetch # 从远程获取代码库 git pull # 下载远程代码并合并 git push # 上传远程代码并合并4.Git 分支管理几乎每一种版本控制系统都以某种形式支持分支,一个分支代表一条独立的开发线。使用分支意味着你可以从开发主线上分离开来,然后在不影响主线的同时继续工作。创建的新分支会以当前分支的状态为基础。4.1 列出分支$ git branch * master4.2 创建分支如果我们要手动创建一个分支。执行 git branch (branchname) 即可。$ git branch testing $ git branch * master testing4.3 切换分支git checkout (branchname)4.4 创建和切换分支小实验(★★★)# 初始化仓库 $ mkdir GitStudy $ cd GitStudy $ git init # 在master分支下创建master-branch.txt文件并提交到master分支 $ touch master-branch.txt $ git add . $ git commit -m "master first commit" [master (root-commit) f2248bb] master first commit 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 master-branch.txt # 创建test分支,会以master分支为基础,这个时候ls看到的文件状态和master分支的是一致的 $ git branch test $ git checkout test Switched to branch 'test' $ ls master-branch.txt # 在test分支下创建test-branch.txt文件并提交到test分支 $ touch test-branch.txt $ ls master-branch.txt test-branch.txt $ git add . $ git commit -m "test branch firsh commit" [test 070c6a3] test branch firsh commit 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test-branch.txt # 切换回master分支查看test分支的修改是否对master分支造成了影响 $ git checkout master Switched to branch 'master' $ ls master-branch.txt 4.5 合并分支一旦某分支有了独立内容,你终究会希望将它合并回到你的主分支。 你可以使用以下命令将任何分支合并到当前分支中去。git merge branchname执行合并命令会将指定分支合并到当前工作分支上,我们假设当前工作分支为main,合并过程如下图所示。合并完后就可以删除分支:$ git branch -d branchname 4.6 分支合并小实验# 查看主分支已提交的文件 $ git branch * master test $ ls master-branch.txt master.txt # 查看test分支文件已提交的文件 $ git checkout test Switched to branch 'test' $ git branch master * test $ ls master-branch.txt test-branch.txt # 合并test分支到master分支 $ git checkout master Switched to branch 'master' $ git merge test Merge made by the 'ort' strategy. test-branch.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test-branch.txt # 查看合并后的文件,均为已提交的文件 $ ls master-branch.txt master.txt test-branch.txt # 删除test分支 $ git branch -d test Deleted branch test (was 070c6a3). $ git branch * master 4.7 分支合并冲突小实验# 该实验在空白文件夹进行实验 # 初始化master分支并创建init.txt文件并提交 $ git init Initialized empty Git repository in C:/Users/LuoJia/Desktop/WorkSpace/GitStudy/. git/ $ touch init.txt $ git add . $ git commit -m "master add init.txt" [master (root-commit) 8af249b] master add init.txt 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 init.txt # 新建test分支 $ git branch test $ git checkout test Switched to branch 'test' # 在test分支下创建test.txt和confict.txt文件并提交 $ touch test.txt $ vim confict.txt $ cat confict.txt <?php echo 'test'; ?> $ git add . warning: in the working copy of 'confict.txt', LF will be replaced by CRLF the n ext time Git touches it $ git commit -m "test write" [test 30bfb3c] test write 2 files changed, 3 insertions(+) create mode 100644 confict.txt create mode 100644 test.txt # 回到master分支,创建master.txt和confict.txt文件并提交 $ git checkout master Switched to branch 'master' $ ls init.txt $ touch master.txt $ vim confict.txt $ cat confict.txt <?php echo 'master'; ?> $ git add . warning: in the working copy of 'confict.txt', LF will be replaced by CRLF the n ext time Git touches it $ git commit -m "master write" [master dd7620b] master write 2 files changed, 3 insertions(+) create mode 100644 confict.txt create mode 100644 master.txt # 尝试合并test分支,会发现发生了文件冲突 $ git merge test Auto-merging confict.txt CONFLICT (add/add): Merge conflict in confict.txt Automatic merge failed; fix conflicts and then commit the result. $ cat confict.txt <?php <<<<<<< HEAD echo 'master'; ======= echo 'test'; >>>>>>> test ?> # 需要手动编辑解决冲突 $ vim confict.txt $ cat confict.txt <?php echo 'master'; echo 'test'; ?> # 手动解决冲突后需要对冲突文件进行重新提交 $ git add confict.txt $ git commit -m "solve confict of confict.txt" [master 9b4bbfc] solve confict of confict.txt 参考资料1. Git 教程 | 菜鸟教程 (runoob.com)2. Git 常用基本命令使用详细大全_git命令_坚强的小水滴的博客-CSDN博客3. 使用分支——Git Merge命令 - 知乎 (zhihu.com)
2023年08月10日
52 阅读
0 评论
0 点赞
2023-05-15
python上传文件到阿里云oss
安装依赖包pip install oss2核心代码import oss2 access_key_id = 'LTA*******************' access_key_secret = 'ZAx*******************************' bucket_name = 'caucshop' endpoint = 'oss-cn-beijing.aliyuncs.com' # 创建bucket对象 bucket = oss2.Bucket(oss2.Auth(access_key_id, access_key_secret), endpoint, bucket_name) # 待上传的文件路径 file_path_local = "./Snipaste_2023-05-13_18-54-02.jpg" # 上传到oss后保保存的路径 file_path_oss = "goodImgsCompresss/"+file_path_local.split("/")[-1] # 读取文件 with open(file_path_local, "rb") as f: data = f.read() # 上传文件 bucket.put_object(file_path_oss, data) # 获取文件的url file_url_oss = "https://"+bucket_name+"."+endpoint+"/"+file_path_oss; print(file_url_oss)执行结果,得到文件在oss中的存储地址,我这里采用的是公共读的权限https://caucshop.oss-cn-beijing.aliyuncs.com/goodImgsCompresss/Snipaste_2023-05-13_18-54-02.jpg参考资料【python】 文件/图片上传 阿里云OSS ,获取外网链接 实例_oss图片外链_维玉的博客-CSDN博客
2023年05月15日
365 阅读
0 评论
0 点赞
2023-05-10
SpringBoot调用阿里云内容审核API实现文本和图片审核
1.服务开通地址:https://vision.aliyun.com/imageaudit?spm=5176.11065253.1411203.3.7e8153f6mehjzV2.引入公共POM依赖<!--json转换依赖--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>2.0.25</version> </dependency> <!--文字内容审核依赖及图片审核依赖共用--> <dependency> <groupId>com.aliyun</groupId> <artifactId>imageaudit20191230</artifactId> <version>2.0.6</version> </dependency>3.文本审核3.1 核心代码private static final String accessKeyId = "<your-access-key-id>"; private static final String accessKeySecret = "<your-access-key-secret>"; @PostMapping("/scanText") public String scanText(@RequestBody HashMap<String,String> reqMap) throws Exception { // 获取待检测的文字 String text = reqMap.get("text"); System.out.println("text="+text); // 返回结果的变量 Map<String,String> resMap = new HashMap<>(); //实例化客户端 Config config = new Config() // 必填,您的 AccessKey ID .setAccessKeyId(accessKeyId) // 必填,您的 AccessKey Secret .setAccessKeySecret(accessKeySecret); config.endpoint = "imageaudit.cn-shanghai.aliyuncs.com"; Client client = new Client(config); /** * spam:文字垃圾内容识别 * politics:文字敏感内容识别 * abuse:文字辱骂内容识别 * terrorism:文字暴恐内容识别 * porn:文字鉴黄内容识别 * flood:文字灌水内容识别 * contraband:文字违禁内容识别 * ad:文字广告内容识别 */ // 设置待检测类型 ScanTextRequest.ScanTextRequestLabels labels0 = new ScanTextRequest.ScanTextRequestLabels() .setLabel("politics"); ScanTextRequest.ScanTextRequestLabels labels1 = new ScanTextRequest.ScanTextRequestLabels() .setLabel("contraband"); ScanTextRequest.ScanTextRequestLabels labels2 = new ScanTextRequest.ScanTextRequestLabels() .setLabel("terrorism"); ScanTextRequest.ScanTextRequestLabels labels3 = new ScanTextRequest.ScanTextRequestLabels() .setLabel("abuse"); ScanTextRequest.ScanTextRequestLabels labels4 = new ScanTextRequest.ScanTextRequestLabels() .setLabel("spam"); ScanTextRequest.ScanTextRequestLabels labels5 = new ScanTextRequest.ScanTextRequestLabels() .setLabel("ad"); // 设置待检测内容 ScanTextRequest.ScanTextRequestTasks tasks0 = new ScanTextRequest.ScanTextRequestTasks() .setContent(text); ScanTextRequest scanTextRequest = new ScanTextRequest() .setTasks(java.util.Arrays.asList( tasks0 )) .setLabels(java.util.Arrays.asList( labels0, labels1, labels2, labels3, labels4, labels5 )); RuntimeOptions runtime = new RuntimeOptions(); ScanTextResponse response = null; try { // 复制代码运行请自行打印 API 的返回值 response = client.scanTextWithOptions(scanTextRequest, runtime); resMap.put("data",JSON.toJSONString(response.getBody().getData().getElements().get(0).getResults())); //调用后获取到他的返回对象, 然后判断我们的文字 是什么内容 List<ScanTextResponseBody.ScanTextResponseBodyDataElementsResultsDetails> responseDetails = response.getBody().getData().getElements().get(0).getResults().get(0).getDetails(); if (responseDetails.size()>0){ resMap.put("state","block"); StringBuilder error = new StringBuilder("检测到:"); for (ScanTextResponseBody.ScanTextResponseBodyDataElementsResultsDetails detail : responseDetails) { if ("abuse".equals(detail.getLabel())) error.append("辱骂内容、"); if ("spam".equals(detail.getLabel())) error.append("垃圾内容、"); if ("politics".equals(detail.getLabel())) error.append("敏感内容、"); if ("terrorism".equals(detail.getLabel())) error.append("暴恐内容、"); if ("porn".equals(detail.getLabel())) error.append("黄色内容、"); if ("flood".equals(detail.getLabel())) error.append("灌水内容、"); if ("contraband".equals(detail.getLabel())) error.append("违禁内容、"); if ("ad".equals(detail.getLabel())) error.append("广告内容、"); } resMap.put("msg",error.toString()); return JSON.toJSONString(resMap); }else { resMap.put("state","pass"); resMap.put("msg","未检测出违规!"); return JSON.toJSONString(resMap); } } catch (Exception _error) { resMap.put("state","review"); resMap.put("msg","阿里云无法进行判断,需要人工进行审核,错误详情:"+_error); return JSON.toJSONString(resMap); } }3.2 调用结果req{ "text":"hello word! 卧槽6666" }res{ "state": "block", "msg": "检测到:辱骂内容、", "data": { "details": [{ "contexts": [{ "context": "卧槽" }], "label": "abuse" }], "label": "abuse", "rate": 99.91, "suggestion": "block" } }4.图片审核4.1 核心代码private static final String accessKeyId = "<your-access-key-id>"; private static final String accessKeySecret = "<your-access-key-secret>"; @PostMapping("/scanImage") public String scanImage(@RequestBody HashMap<String,String> reqMap) throws Exception { // 获取待检测的文字 String image = reqMap.get("image"); System.out.println("image="+image); // 返回结果的变量 Map<String,String> resMap = new HashMap<>(); //实例化客户端 Config config = new Config() // 必填,您的 AccessKey ID .setAccessKeyId(accessKeyId) // 必填,您的 AccessKey Secret .setAccessKeySecret(accessKeySecret); config.endpoint = "imageaudit.cn-shanghai.aliyuncs.com"; Client client = new Client(config); // 设置待检测内容 ScanImageRequest.ScanImageRequestTask task0 = new ScanImageRequest.ScanImageRequestTask().setImageURL(image); // 封装检测请求 /** * porn:图片智能鉴黄 * terrorism:图片敏感内容识别、图片风险人物识别 * ad:图片垃圾广告识别 * live:图片不良场景识别 * logo:图片Logo识别 */ ScanImageRequest scanImageRequest = new ScanImageRequest() .setTask(java.util.Arrays.asList( task0 )) .setScene(java.util.Arrays.asList( "porn","terrorism","live" )); RuntimeOptions runtime = new RuntimeOptions(); // 调用API获取检测结果 ScanImageResponse response = client.scanImageWithOptions(scanImageRequest, runtime); resMap.put("data",JSON.toJSONString(response.getBody().getData().getResults().get(0))); // 检测结果解析 try { List<ScanImageResponseBody.ScanImageResponseBodyDataResultsSubResults> responseSubResults = response.getBody().getData().getResults().get(0).getSubResults(); for(ScanImageResponseBody.ScanImageResponseBodyDataResultsSubResults responseSubResult : responseSubResults){ if(responseSubResult.getSuggestion()!="pass"){ resMap.put("state",responseSubResult.getSuggestion()); String msg = ""; switch (responseSubResult.getLabel()){ case "porn": msg = "图片智能鉴黄未通过"; break; case "terrorism": msg = "图片敏感内容识别、图片风险人物识别未通过"; break; case "ad": msg = "图片垃圾广告识别未通过"; break; case "live": msg = "图片不良场景识别未通过"; break; case "logo": msg = "图片Logo识别未通过"; break; } return JSON.toJSONString(resMap); } } } catch (Exception error) { resMap.put("state","review"); resMap.put("msg","发生错误,详情:"+error); return JSON.toJSONString(resMap); } resMap.put("state","pass"); return JSON.toJSONString(resMap); }4.2 调用结果req{ "image":"https://jupite-aliyun.oss-cn-hangzhou.aliyuncs.com/second_hand_shop/client/img/goodImgs/1683068284289.jpg" }res{ "data": { "imageURL": "http://jupite-aliyun.oss-cn-hangzhou.aliyuncs.com/second_hand_shop/client/img/goodImgs/1683068284289.jpg", "subResults": [ { "label": "normal", "rate": 99.9, "scene": "porn", "suggestion": "pass" }, { "label": "normal", "rate": 99.88, "scene": "terrorism", "suggestion": "pass" }, { "label": "normal", "rate": 99.91, "scene": "live", "suggestion": "pass" } ] }, "state": "pass" }参考资料https://next.api.aliyun.com/api/imageaudit/2019-12-30/ScanImage阿里云文本检测 使用教程(Java)https://vision.aliyun.com/imageaudit?spm=5176.11065253.1411203.3.7e8153f6mehjzV
2023年05月10日
508 阅读
0 评论
0 点赞
2023-05-02
SpringBoot整合阿里云短信发送
0.业务场景短信发送验证码实现注册,登录...1.开通阿里云短信服务去到阿里云官方网址:https://www.aliyun.com/ 选择短信服务,在这里能获取到我们需要的4个参数,分别是accessKeyId、accessKeySecret、短信签名、模板code。2.整合进SpringBoot-方法一(推荐方法二)2.1导入依赖<dependency> <groupId>com.aliyun</groupId> <artifactId>aliyun-java-sdk-core</artifactId> <version>4.3.3</version> </dependency>2.2 封装成工具类或者服务类package top.inat.shop.utils; import com.aliyuncs.CommonRequest; import com.aliyuncs.CommonResponse; import com.aliyuncs.DefaultAcsClient; import com.aliyuncs.IAcsClient; import com.aliyuncs.http.MethodType; import com.aliyuncs.profile.DefaultProfile; import org.springframework.stereotype.Component; import java.util.Random; /** * @program: server * @ClassName AliMessageUtil * @description: 验证码工具类 * @author: jupiter * @create: 2023-04-23 10:17 * @Version 1.0 **/ @Component public class AliMessageUtil { /** * 需要配置的参数 */ // 阿里云的id和秘钥 从个人中心进行创建 private static final String accessKeyId="XXXXXXXX"; private static final String secret="XXXXXXXX"; //申请的阿里云的签名名称 private static final String SignName="smile佳"; //申请的阿里云的短信模板code private static final String TemplateCode = "SMS_147439706"; /** * 生成6位数字验证码函数 */ public static String generateVerifiCode() { int n = 6; StringBuilder code = new StringBuilder(); Random ran = new Random(); for (int i = 0; i < n; i++) { code.append(Integer.valueOf(ran.nextInt(10)).toString()); } return code.toString(); } /** * 通过阿里云短信发送验证码 * @param code 验证码 * @param phone 手机号 * @return */ public static boolean sendMsmVerifyCode(String phone,String code) { //default 地域节点,默认就好 后面是 阿里云的 id和秘钥 DefaultProfile profile = DefaultProfile.getProfile("default", accessKeyId, secret); IAcsClient client = new DefaultAcsClient(profile); // 组装请求对象 SendSmsRequest request = new SendSmsRequest(); request.putQueryParameter("PhoneNumbers", phone); request.putQueryParameter("SignName",SignName); request.putQueryParameter("TemplateCode", TemplateCode); request.putQueryParameter("TemplateParam", "{\"code\":\"" + code + "\"}"); try { CommonResponse response = client.getCommonResponse(request); System.out.println(response.getData()); return response.getHttpResponse().isSuccess(); } catch (Exception e) { e.printStackTrace(); } return false; } }进行测试 @Test void aliMessageTest(){ String code = AliMessageUtil.generateVerifiCode(); System.out.println("生成的验证码为:"+code); String phone = "18673918533"; boolean sendRes = AliMessageUtil.sendMsmVerifyCode(phone,code); System.out.println("短信发送结果:"+sendRes); }2.3 运行结果生成的验证码为:196573 {"Message":"OK","RequestId":"97D16831-6EB8-5300-AF5F-25EC86638C26","Code":"OK","BizId":"405312082956966726^0"} 短信发送结果:true2.整合进SpringBoot-方法二2.1导入依赖<dependency> <groupId>com.aliyun</groupId> <artifactId>dysmsapi20170525</artifactId> <version>2.0.9</version> </dependency> <!-- fastjson 打印详细的发送返回的结果用的,只看发送成功失败的话可以去掉 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.35</version> </dependency>2.2 封装成工具类或者服务类package top.inat.shop.utils; import com.alibaba.fastjson.JSON; import com.aliyun.dysmsapi20170525.Client; import com.aliyun.dysmsapi20170525.models.SendSmsRequest; import com.aliyun.dysmsapi20170525.models.SendSmsResponse; import com.aliyun.dysmsapi20170525.models.SendSmsResponseBody; import com.aliyun.teaopenapi.models.Config; import org.springframework.stereotype.Component; import java.util.Random; /** * @program: server * @ClassName AliMessageUtil * @description: 验证码工具类 * @author: jupiter * @create: 2023-04-23 10:17 * @Version 1.0 **/ @Component public class AliMessageUtil { /** * 需要配置的参数 */ private static final String accessKeyId="LTAI5t7Lg3SECa8JSvyYrhoj";//这里修改为个人中心生成的AccessKey ID private static final String accessKeySecret="AXeyeLFKUU8MkgUSnTj2qTLqnZv2rL";//这里修改为个人中心生成的AccessKey Secret private static final String SignName="smile佳"; //申请的阿里云的签名名称 private static final String TemplateCode = "SMS_147439706"; ////申请的阿里云的短信模板code /** * 生成6位数字验证码 */ public static String generateVerifiCode() { int n = 6; StringBuilder code = new StringBuilder(); Random ran = new Random(); for (int i = 0; i < n; i++) { code.append(Integer.valueOf(ran.nextInt(10)).toString()); } return code.toString(); } /** * 通过阿里云短信发送验证码 * @param code 验证码 * @param phone 手机号 * @return */ public static boolean sendMsmVerifyCode(String phone,String code) throws Exception { Config config = new Config().setAccessKeyId(accessKeyId).setAccessKeySecret(accessKeySecret).setEndpoint( "dysmsapi.aliyuncs.com"); Client client = new Client(config); SendSmsRequest request = new SendSmsRequest(); request.setPhoneNumbers(phone); request.setSignName(SignName); request.setTemplateCode(TemplateCode); request.setTemplateParam("{\"code\":\"" + code + "\"}"); SendSmsResponse response = client.sendSms(request); SendSmsResponseBody body = response.getBody(); System.out.println(JSON.toJSONString(body));//不用fastjson打印结果就注释掉这一行 if("OK".equals(body.getCode())){ return true; } return false; } } 进行测试 @Test void aliMessageTest(){ String code = AliMessageUtil.generateVerifiCode(); System.out.println("生成的验证码为:"+code); String phone = "18673918533"; boolean sendRes = AliMessageUtil.sendMsmVerifyCode(phone,code); System.out.println("短信发送结果:"+sendRes); }2.3 运行结果生成的验证码为:196573 {"Message":"OK","RequestId":"97D16831-6EB8-5300-AF5F-25EC86638C26","Code":"OK","BizId":"405312082956966726^0"} 短信发送结果:true参考资料SpringBoot整合阿里云短信服务详细过程(保证初学者也能实现)SpringBoot集成阿里云短信服务发送短信阿里云——Java实现手机短信验证码功能
2023年05月02日
349 阅读
0 评论
0 点赞
2023-02-18
Spring&SpringMVC高频面试题梳理
0.题目汇总Spring的IOC和AOP机制?Spring中Autowired和Resouree关键字的区别和联系?依赖注入的方式有几种,各是什么?讲一下什么是Spring?Spring框架中都用到了哪些设计模式?Spring框架中都用到了哪些设计模式?1.Spring的IOC和AOP机制?IOC(Inversion of Control):IOC是控制反转的意思,这是一种面向对象编程的设计思想,可以帮我们维护对象与对象之间的依赖关系,降低对象之间的耦合度。简单来说,就是将原本在程序中自己手动创建对象的控制权,交由 Spring 框架来管理,Spring IOC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。 IOC 容器负责创建对象,将对象连接在一起,配置这些对象,并从创建中处理这些对象的整个生命周期,直到它们被完全销毁。在spring中IOC是通过DI(Dependency Injection)/依赖注入实现的。AOP(Aspect Oriented Programing)是面向切面编程思想,这种思想是对OOP的补充,它可以在OOP的基础上进一步提高编程的效率。简单来说,它可以统一解决一批组件的共性需求(如权限检查、记录日志、事务管理等)。它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。2.Spring中Autowired和Resouree关键字的区别和联系?联系@Autowired和@Resource注解都是作为bean对象注入的时候使用的两者都可以声明在字段和setter方法上注意:如果声明在字段上,那么就不需要再写setter方法。但是本质上,该对象还是作为set方法的实参,通过执行set方法注入,只是省略了setter方法罢了区别@Autowired注解是Spring提供的,而@Resource注解是J2EE本身提供的@Autowired注解默认通过byType方式注入,而@Resource注解默认通过byName方式注入@Autowired注解注入的对象需要在IOC容器中存在,否则需要加上属性required=false,表示忽略当前要注入的bean,如果有直接注入,没有跳过,不会报错讲一下什么是Spring?3.依赖注入的方式有几种,各是什么?构造器注入将被依赖对象通过构造函数的参数注入给依赖对象,并且在初始化对象的时候注入。<!-- 第一种根据index参数下标设置 --> <bean id="userT" class="com.kuang.pojo.UserT"> <!-- index指构造方法 , 下标从0开始 --> <constructor-arg index="0" value="kuangshen2"/> </bean> <!-- 第二种根据参数名字设置 --> <bean id="userT" class="com.kuang.pojo.UserT"> <!-- name指参数名 --> <constructor-arg name="name" value="kuangshen2"/> </bean> <!-- 第三种根据参数类型设置 --> <bean id="userT" class="com.kuang.pojo.UserT"> <constructor-arg type="java.lang.String" value="kuangshen2"/> </bean>setter方法注入通过调用成员变量提供的setter函数将被依赖对象注入给依赖类。<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="address" class="com.hui.pojo.Address"> <property name="address" value="背景"/> </bean> <!-- 依赖注入之set注入 --> <bean id="student" class="com.hui.pojo.Student"> <!--1. 普通值注入,value--> <property name="name" value="李家辉"/> <!--2. bean注入,ref--> <property name="address" ref="address"/> <!--3. 数组注入,array-value --> <property name="books"> <array> <value>李</value> <value>家</value> <value>辉</value> </array> </property> <!--list注入,list-value --> <property name="hobbys"> <list> <value>语文</value> <value>数学</value> <value>英语</value> </list> </property> <!--Map注入,map-entry-key-value --> <property name="card"> <map> <entry key="身份证" value="123"/> <entry key="银行卡" value="456"/> </map> </property> <!--Set注入,set-value --> <property name="games"> <set> <value>IOC</value> <value>DI</value> </set> </property> <!--null注入--> <property name="wife"> <null/> </property> <!----> <property name="info"> <props> <prop key="学号">2019</prop> <prop key="username">男</prop> <prop key="password">123456</prop> </props> </property> </bean> </beans>c命名空间与p命名空间p命名空间就是对应setter注入(property);c命名空间就是对应构造方法注入(constructor-arg)。<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--p命名空间注入,可以直接注入属性的值:property。p命名空间注入就是对应set注入的属性注入--> <!--需要导入约束 xmlns:p="http://www.springframework.org/schema/p" --> <bean id="user" class="com.hui.pojo.User" p:name="李家辉" p:age="22"/> <!--c命名空间注入,对应所有的构造器注入,constructor-arg --> <!--需要导入约束 xmlns:c="http://www.springframework.org/schema/c" --> <bean id="user1" class="com.hui.pojo.User" c:name="李家毅" c:age="22"/> </beans>autowire byName (按名称自动装配)<bean id="user" class="com.kuang.pojo.User" autowire="byName"> <property name="str" value="qinjiang"/> </bean>autowire byType (按类型自动装配)<bean id="dog" class="com.kuang.pojo.Dog"/> <bean id="cat" class="com.kuang.pojo.Cat"/> <bean id="cat2" class="com.kuang.pojo.Cat"/> <bean id="user" class="com.kuang.pojo.User" autowire="byType"> <property name="str" value="qinjiang"/> </bean>使用注解实现自动装配@Autowired+@Qualifier@Autowired是根据类型自动装配的,加上@Qualifier则可以根据byName的方式自动装配@Qualifier不能单独使用。@Resource@Resource如有指定的name属性,先按该属性进行byName方式查找装配;其次再进行默认的byName方式进行装配;如果以上都不成功,则按byType的方式自动装配。都不成功,则报异常。4.讲一下什么是Spring?Spring是一个轻量级的IoC和AOP容器框架。是为Java应用程序提供基础性服务的一套框架,目的是用于简化企业应用程序的开发,它使得开发者只需要关心业务需求。简单来说,它是一个容器框架,用来装 javabean(java对象),中间层框架(万能胶)可以起一个连接作用,可以把各种技术粘合在一起运用。主要由以下几个模块组成:Spring Core:SpringCore模块是Spring的核心容器,它实现了IOC模式,提供了Spring框架的基础功能。此模块中包含的BeanFactory类是Spring的核心类,负责JavaBean的配置与管理。它采用Factory模式实现了IOC即依赖注入。Spring AOP:Spring在它的AOP模块中提供了对面向切面编程的丰富支持,Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中,可以自定义拦截器、切点、日志等操作Spring DAO:提供了一个JDBC的抽象层和异常层次结构,消除了烦琐的JDBC编码和数据库厂商特有的错误代码解析, 用于简化JDBCSpring ORM:对现有的ORM框架的支持;Spring Context:提供框架式的Bean访问方式,以及企业级功能(JNDI、定时任务等);Spring Web:提供了基本的面向Web的综合特性,例如多方文件上传;Spring MVC:提供面向Web应用的Model-View-Controller实现。5.解释Spring支持的几种bean的作用域。类型说明singleton在Spring容器中仅存在一个实例,即Bean以单例的形式存在。prototype每次调用getBean()时,都会执行new操作,返回一个新的实例。request每次HTTP请求都会创建一个新的Bean。session同一个HTTP Session共享一个Bean,不同的HTTP Session使用不同的Bean。globalSession同一个全局的Session共享一个Bean,一般用于Portlet环境6.Spring框架中都用到了哪些设计模式?工厂模式:ApplicationContext类使用工厂模式创建Bean对象单例模式:Spring中的Bean的作用域默认就是单例Singleton的原型模式:在 spring 中用到的原型模式有: scope="prototype" ,每次获取的是通过克隆生成的新实例,对其进行修改时对原有实例对象不造成任何影响。代理模式:Spring AOP基于动态代理实现的模板模式:Spring中以Template结尾的类,比如jdbcTemplate、SqlSessionTemplate等,都是使用了模板方法模式装饰器模式(动态地给对象添加一些额外的属性或者行为 和继承相比,装饰器模式更加灵活):Spring中配置DataSource时 ,DataSource可以是不同的数据库和数据源.为了在少修改原有类的代码下动态切换不同的数据源,这时就用到了装饰器模式责任链模式:DispatcherServlet 中的 doDispatch() 方法中获取与请求匹配的处理器HandlerExecutionChain,this.getHandler() 方法的处理使用到了责任链模式。观察者模式:Spring 中的 Event 和 Listener7.什么是MVC?MVC是一种架构模式,在这种模式下软件被分为三层,即Model(模型)、View(视图)、Controller(控制器)。Model代表的是数据,View代表的是用户界面,Controller代表的是数据的处理逻辑,它是Model和View这两层的桥梁。将软件分层的好处是,可以将对象之间的耦合度降低,便于代码的维护。8.谈谈对 Spring MVC 的理解Spring MVC 是一款很优秀的 MVC 框架。可以让我们的开发更简洁,而且它和 Spring 是无缝集成,是 Spring 的一个子模块,是我们上面提到 Spring 大家族中 Web 模块。Spring MVC 框架主要由 DispatcherServlet 、处理器映射、处理器(控制器)、视图解析器、视图组成。Spring MVC框架像许多其他MVC框架一样, 以请求为驱动 , 围绕一个中心Servlet分派请求及提供其他功能,DispatcherServlet是一个实际的Servlet (它继承自HttpServlet 基类)。springMVC执行流程9.SpringMVC怎么样设定重定向和转发的?Spring MVC 请求方式分为转发、重定向 2 种,分别使用 forward 和 redirect 关键字在 controller 层进行处理。重定向是将用户从当前处理请求定向到另一个视图(例如 JSP)或处理请求,以前的请求(request)中存放的信息全部失效,并进入一个新的 request 作用域;转发是将用户对当前处理的请求转发给另一个视图或处理请求,以前的 request 中存放的信息不会失效。转发是服务器行为,重定向是客户端行为。1)转发过程客户浏览器发送 http 请求,Web 服务器接受此请求,调用内部的一个方法在容器内部完成请求处理和转发动作,将目标资源发送给客户;在这里转发的路径必须是同一个 Web 容器下的 URL,其不能转向到其他的 Web 路径上,中间传递的是自己的容器内的 request。在客户浏览器的地址栏中显示的仍然是其第一次访问的路径,也就是说客户是感觉不到服务器做了转发的。转发行为是浏览器只做了一次访问请求。2)重定向过程客户浏览器发送 http 请求,Web 服务器接受后发送 302 状态码响应及对应新的 location 给客户浏览器,客户浏览器发现是 302 响应,则自动再发送一个新的 http 请求,请求 URL 是新的 location 地址,服务器根据此请求寻找资源并发送给客户。在这里 location 可以重定向到任意 URL,既然是浏览器重新发出了请求,那么就没有什么 request 传递的概念了。在客户浏览器的地址栏中显示的是其重定向的路径,客户可以观察到地址的变化。重定向行为是浏览器做了至少两次的访问请求。在 Spring MVC 框架中,重定向与转发的示例代码如下:@RequestMapping(“/login”) public String login() { //转发到一个请求方法(同一个控制器类可以省略/index/) return “forward:/index/isLogin”; } @RequestMapping(“/isLogin”) public String isLogin() { //重定向到一个请求方法 return “redirect:/index/isRegister”; }在 Spring MVC 框架中,控制器类中处理方法的 return 语句默认就是转发实现,只不过实现的是转发到视图。@RequestMapping(“/register”) public String register() { return “register”; //转发到register.jsp }10.SpringMVC常用的注解有哪些?组件型注解:作用:被注解的类将被spring初始话为一个bean,然后统一管理。@Component 在类定义之前添加@Component注解,他会被spring容器识别,并转为bean。@Repository 对Dao实现类进行注解 (特殊的@Component)@Service 用于对业务逻辑层进行注解, (特殊的@Component)@Controller 用于控制层注解 , (特殊的@Component)==待完成==参考资料@Autowired和@Resource注解的区别和联系(十分详细,不看后悔)依赖注入三种方式设计模式_spring框架中常用的8种设计模式
2023年02月18日
606 阅读
0 评论
0 点赞
2022-09-14
操作系统高频面试题梳理
0.题目汇总线程和进程的区别?进程之间的通信方式说一说进程的状态说说僵尸进程和孤儿进程。死锁的必要条件周转时间和带权周转时间的计算页面置换算法1.线程和进程的区别?1、根本区别: 进程是操作系统资源分配和独立运行的最小单位;线程是任务调度和系统执行的最小单位。2、地址空间区别: 每个进程都有独立的地址空间,一个进程崩溃不影响其它进程;一个进程中的多个线程共享该 进程的地址空间,一个线程的非法操作会使整个进程崩溃。3、上下文切换开销区别: 每个进程有独立的代码和数据空间,进程之间上下文切换开销较大;线程组共享代码和数据空间,线程之间切换的开销较小。2.进程之间的通信方式每个进程的用户地址空间都是独立的,一般而言是不能互相访问的,但内核空间是每个进程都共享的,所以进程之间要通信必须通过内核。管道(内核中的缓冲区)管道本质上就是内核中的一个缓冲区,程序通过对缓存进行读写完成通信匿名管道:在内核中申请一块固定大小的缓冲区,程序拥有写入和读取的权利。// 创建匿名管道,并返回了两个描述符,一个是管道的读取端描述符 fd[0],另一个是管道的写入端描述符 fd[1] int fd[2]; if(pipe(fd)==-1) ERR_EXIT("pipe error");看到这,你可能会有疑问了,这两个描述符都是在一个进程里面,并没有起到进程间通信的作用,怎么样才能使得管道是跨过两个进程的呢?可以使用 fork 创建子进程,创建的子进程会复制父进程的文件描述符,这样就做到了两个进程各有两个「 fd[0] 与 fd[1]」,两个进程就可以通过各自的 fd 写入和读取同一个管道文件实现跨进程通信了。可以使用 fork 创建的两个子进程之间也可以使用匿名管道实现通信。命名管道: 在内核中申请一块固定大小的缓冲区,程序拥有写入和读取的权利,没有血缘关系的进程也可以进程间通信。mkfifo("tp",0644); int infd; infd = open("abc",o_RDONLY); if (infd ==-1)ERR_EXIT("open"); int outfd; outfd = open ("tp",O_WRONLY);优点实现简单,自带同步互斥机制缺点每个消息的最大长度是有上限的(MSGMAX)效率低下,不适合进程间频繁的交换数据半双工通信,同一时刻只有一个进程可以进行读写。(可以用两个管道实现双向通信)消息队列(内核中的队列)在内核中创建一队列,不同的进程可以通过句柄去访问这个队列,队列中每个元素是一个数据报。优点消息队列可实现双向通信。消息队列允许一个或多个进程写入或者读取消息。缺点每个消息的最大长度是有上限的(MSGMAX)消息队列的总数也有⼀个上限(MSGMNI)存在用户态和内核态之间的数据拷贝问题。进程往消息队列写入数据时,会发送用户态拷贝数据到内核态的过程,同理读取数据时会发生从内核态到用户态拷贝数据的过程。共享内存将同一块物理内存一块映射到不同的进程的虚拟地址空间中,实现不同进程间对同一资源的共享。这样一个进程写入的东西,另一个进程马上就能够看到,不需要进行拷贝。优点速度快:不需要陷入内核态或者系统调用,大大提高了通信的速度,享有最快的进程间通信方式之名缺点多进程竞争同个共享资源会造成数据的错乱:如果有多个进程同时往共享内存写入数据,有可能先写的进程的内容会被其他进程覆盖。信号量(内核中的信号量集合)在内核中创建一个信号量集合(本质是个数组),数组的元素(信号量)都是1,使用P操作进行-1,使用V操作+1,主要用于实现进程间的互斥与同步,而不是用于缓存进程间通信的数据。P(sv):如果sv的值⼤大于零,就给它减1;如果它的值为零,就挂起该进程的执⾏ 。V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运⾏,如果没有进程因等待sv⽽挂起,就给它加1。信号上面说的进程间通信,都是常规状态下的工作模式。对于异常情况下的工作模式,就需要用「信号」的方式来通知进程。信号是进程间通信机制中唯一的异步通信机制。在Linux中,为了响应各种事件,提供了几十种信号,可以通过kill -l命令查看。$ kill -l 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1 64) SIGRTMAX运行在 shell 终端的进程,我们可以通过键盘输入某些组合键的时候,给进程发送信号。例如Ctrl+C 产生 SIGINT 信号,表示终止该进程;Ctrl+Z 产生 SIGTSTP 信号,表示停止该进程,但还未结束;如果进程在后台运行,可以通过 kill 命令的方式给进程发送信号,但前提需要知道运行中的进程 PID 号,例如:kill -9 1050 ,表示给 PID 为 1050 的进程发送 SIGKILL 信号,用来立即结束该进程;Socket前面提到的管道、消息队列、共享内存、信号量和信号都是在同一台主机上进行进程间通信,那要想跨网络与不同主机上的进程之间通信,就需要 Socket 通信了。实际上,Socket 通信不仅可以跨网络与不同主机的进程间通信,还可以在同主机上进程间通信。针对 TCP 协议通信的 socket 编程模型针对 UDP 协议通信的 socket 编程模型3.说一说进程的状态创建状态:进程在创建时需要申请一个空白PCB,向其中填写控制和管理进程的信息,完成资源分配。如果创建工作无法完成,比如资源无法满足,就无法被调度运行,把此时进程所处状态称为创建状态就绪状态:进程已经准备好,已分配到所需资源,只要分配到CPU就能够立即运行执行状态:进程处于就绪状态被调度后,进程进入执行状态阻塞状态:正在执行的进程由于某些事件(I/O请求,申请缓存区失败)而暂时无法运行,进程受到阻塞。在满足请求时进入就绪状态等待系统调用终止状态:进程结束,或出现错误,或被系统终止,进入终止状态。无法再执行4.说说僵尸进程和孤儿进程。我们知道在unix/linux中,正常情况下,子进程是通过父进程创建的,子进程在创建新的进程。子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程 到底什么时候结束。 当一个 进程完成它的工作终止之后,它的父进程需要调用wait()或者waitpid()系统调用取得子进程的终止状态。孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵尸进程。5.死锁的必要条件互斥条件:一个资源每次只能被一个进程使用; 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放;不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺;循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系;6.周转时间和带权周转时间的计算$$ 周转时间=作业完成时刻-作业到达时刻\\ 带权周转时间=\frac{周转时间}{服务时间}\\ 平均周转时间=\frac{作业周转总时间}{作业个数}\\ 平均带权周转时间=\frac{带权周转总时间}{作业个数}\\ $$7.页面置换算法在进程运行的过程当中,进程所要访问的页面不在内存中,我们就需要把这个不存在的页面调入内存,但内存已经没有空闲空间了,这时候就要求系统从内存中调出一个页面,将其移入磁盘的对换区中。将哪个页面调出来,就要通过算法来确定。我们把选择换出页面的算法就叫做页面置换算法。先进先出置换算法FIFO思想:先进先出算法总是淘汰最先进入内存的页面。出发点是近期调入的页面被再次访问的概率要大于早期调入页面的概率。例题:考虑下述页面走向:1,2,3,4,2,1,5,6,2,1,2,3,7,6,3,2,1,2,3,6。当内存块数量分别为3时,试问先进先出置换算法(FIFO)的缺页次数是多少?最近最少使用页面置换算法LRU思想:每次选择内存中离当前时刻最久未使用过的页面淘汰,依据的原理是局部性原理例题:考虑下述页面走向:1,2,3,4,2,1,5,6,2,1,2,3,7,6,3,2,1,2,3,6。当内存块数量分别为3时,试问最近最少使用页面置换算法(LRU)的缺页次数是多少?最佳置换算法OPT思想:选择的被淘汰页面是以后用不使用的,或者是在未来最长一段时间内不再被访问的页面。例题:考虑下述页面走向:1,2,3,4,2,1,5,6,2,1,2,3,7,6,3,2,1,2,3,6。当内存块数量分别为3时,试问最佳置换法(OPT)的缺页次数是多少?Clock置换算法略参考资料面试题:进程、线程及协程的区别六种进程间通信方式进程间的通信方式(六种)
2022年09月14日
335 阅读
0 评论
0 点赞
2022-09-07
JavaSE高频面试题梳理-2.多线程与并发
0.题目汇总Java中实现多线程有几种方法如何停止一个正在运行的线程notify()和notifyAll()有什么区别?sleep()和wait()有什么区别volatile是什么?可以保证有序性吗?Thread 类中的start()和run()方法有什么区别?为什么wait, notify 和notifyAll这些方法不在thread类里面?为什么wait, notify方法要在同步块中调用?Java中interrupted和isInterruptedd方法的区别Java中synchronized和ReentrantLock有什么不同?有三个线程T1, T2, T3,如何保证顺序执行?SynchronizedMap 和 ConcurrentHashMap 有什么区别?Thread类中的yield方法有什么作用?Java线程池中submit()和execute()方法有什么区别?volatile关键字的作用?常用的线程池有哪些?简述一下你对线程池的理解Runnable接口和Callable接口的区别1.Java中实现多线程有几种方法继承 Thread 类实现 Runnable 接口实现 Callable 接口:Runnable接口是没有返回值的,Callable有返回值,可以抛出异常;Thread类并不接受Callable对象。可以使用FutureTask类实现Runnable接口和Future接口;线程池线程池的概念:线程池就是首先创建一些线程,它们的集合称为线程池。使用线程池可以很好地提高性能,线程池在系统启动时即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任务,执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。使用线程池的原因:多线程运行时间,系统不断的启动和关闭新线程,成本非常高,会过渡消耗系统资源,以及过渡切换线程的危险,从而可能导致系统资源的崩溃。这时,线程池就是最好的选择了。2.如何停止一个正在运行的线程使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。使用stop方法强行终止,但是不推荐这个方法,因为这个方法是不安全的,而且是已被废弃的方法。使用interrupt方法中断线程3.notify()和notifyAll()有什么区别?二者都是用来用来唤醒调用wait()方法进入等待锁资源队列的线程,区别在于:notify():唤醒正在等待此对象监视器的单个线程。 如果有多个线程在等待,则选择其中一个随机唤醒(由调度器决定),唤醒的线程享有公平竞争资源的权利notifyAll():唤醒正在等待此对象监视器的所有线程,唤醒的所有线程公平竞争资源4.sleep()和wait()有什么区别sleep()和wait()都是线程暂停执行的方法。1、这两个方法来自不同的类分别是Thread和Object,sleep方法属于Thread类中的静态方法,wait属于Object的成员方法。2、sleep()是线程类(Thread)的方法,不涉及线程通信,调用时会暂停此线程指定的时间,但监控依然保持,不会释放对象锁,到时间自动恢复;wait()是Object的方法,用于线程间的通信,调用时会放弃对象锁,进入等待队列,待调用notify()/notifyAll()唤醒指定的线程或者所有线程,才进入对象锁定池准备获得对象锁进入运行状态。3、wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用(使用范围)。4、sleep()方法必须捕获异常InterruptedException,而wait()、notify()和notifyAll()不需要捕获异常。注意:sleep方法只让出了CPU,而并不会释放同步资源锁。线程执行sleep()方法后会转入阻塞状态。sleep()方法指定的时间为线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就开始执行。5.volatile是什么?可以保证有序性吗?volatile是java并发编程的一个关键字。volatile可以保证可见性,有序性(一定程度上),但不能保证原子性因为volatile关键字能禁止指令重排,所以一定程度上保证了有序性;volatile关键字禁止指令重排的两层意思:(1)当程序执行volatile关键字进行读操作或写操作时,volatile关键字前面所有程序操作已经全部完成且结果对后面的所有操作均显示,volatile关键字后面的操作已经还没有进行。(2)进行指令优化时,不能将volatile关键字后面的语句放在volatile关键字前面执行,也不能将volatile关键字前面的语句放在volatile关键字后面执行;可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值有序性即程序执行的顺序按照代码的先后顺序执行原子性即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。volatile关键字的应用状态标记量单例模式中的double check指令重排序一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。比如上面的代码中,语句1和语句2谁先执行对最终的程序结果并没有影响,那么就有可能在执行过程中,语句2先执行而语句1后执行。但是有依赖关系的语句不会进行重排序,如下面求圆面积的代码double pi = 4.14 //A double r = 1.0 //B double area = pi * r * r //c 程序的执行顺序只有下面这2个形式A->B->C和B->A->C,因为A和C之间存在依赖关系,同时B和C之间也存在依赖关系。因此最终执行的指令序列中C不能被重排序到A和B前面。6.Thread 类中的start()和run()方法有什么区别?start():使用start可以启动线程,创建一个新的线程,真正实现了多线程运行,在内部调用run方法且无需等待run方法体代码执行完毕而直接继续执行下面的代码。run():run()方法只是类的一个普通方法而已,使用run不会创建新线程,而是在之前的线程中执行了我们在run里面重写的内容。7.为什么wait, notify 和notifyAll这些方法不在thread类里面?Java提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。简单的说,由于wait,notify,notifyAll都是锁级别的操作,所以把他们定义在object类中因为锁属于对象。8.为什么wait, notify方法要在同步块中调用?参考:面试官:为什么wait()方法要放在同步块中?9.Java中interrupted和isInterruptedd方法的区别?interruptinterrupt 方法用于中断线程。调用该方法的线程的状态为将被置为”中断”状态。注意:线程中断仅仅是置线程的中断状态位,不会停止线程。需要用户自己去监 视线程的状态为并做处理。支持线程中断的方法(也就是线程中断后会抛出 interruptedException 的方法)就是在监视线程的中断状态,一旦线程的中断状 态被置为“中断状态”,就会抛出中断异常。interrupted查询当前线程的中断状态,并且清除原状态。如果一个线程被中断了,第一次调 用 interrupted 则返回 true,第二次和后面的就返回 false 了。isInterrupted仅仅是查询当前线程的中断状态10.Java中synchronized和ReentrantLock有什么不同?用法不同:synchronized 可以用来修饰普通方法、静态方法和代码块,而 ReentrantLock 只能用于代码块(lock()和unlock()方法配合try/finally语句块来完成)。获取锁和释放锁的机制不同:synchronized 是自动加锁和释放锁的,而 ReentrantLock 需要手动加锁和释放锁。锁类型不同:synchronized 是非公平锁,而 ReentrantLock 默认为非公平锁,也可以手动指定为公平锁。响应中断不同:ReentrantLock 可以响应中断(catch InterruptedException),解决死锁的问题,而 synchronized 不能响应中断。底层实现不同:synchronized 是 JVM 层面通过监视器实现的,而 ReentrantLock 是基于 AQS 实现的。公平锁是指多个线程在等待同一个锁时,必须按照申请的时间顺序来依次获得锁;而非公平锁则不能保证这一点。非公平锁在锁被释放时,任何一个等待锁的线程都有机会获得锁。11.有三个线程T1, T2, T3,如何保证顺序执行?用线程类的join()方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。为了确保三个线程的顺序你应该先启动最后一个(T3调用T2,T2调用T1),这样T1就会先完成而T3最后完成。12.SynchronizedMap 和 ConcurrentHashMap 有什么区别?SynchronizedMap 一次锁住整张表来保证线程安全,所以每次只能有一个线程来访问Map.ConcurrentHashMap 使用分段锁来保证在多线程下的性能。ConcurrentHashMap 中则是一次锁住一个桶。ConcurrentHashMap 默认将 hash 表分为 16 个桶,诸如get、put、remove 等常用操作只锁当前需要用到的桶。这样,原来只能一个线程进入,现在却能同时有 16 个写线程执行,并发性能的提升是显而易见的。另外 ConcurrrentHashMap 使用一种不同的迭代方式。在这种迭代方式中,当 iterator 被创建后集合再发生改变就不再是抛出 ConcurrentModificationException,取而代之的是在改变 new 新的数据从而不影响原有的数据,iterator 完成后再将头指针替换为新的数据,这样 iterator 线程可以使用原来老的数据,而写线程也可以并发的完成改变。13.Thread类中的yield方法有什么作用?yield 即 "谦让",也是 Thread 类的方法。它让掉当前线程 CPU 的时间片,使正在运行中的线程重新变成就绪状态,并重新竞争 CPU 的调度权。它可能会获取到,也有可能被其他线程获取到。yield 和 sleep 的异同1)yield, sleep 都能暂停当前线程,sleep 可以指定具体休眠的时间,而 yield 则依赖 CPU 的时间片划分。2)yield, sleep 两个在暂停过程中,如已经持有锁,则都不会释放锁资源。3)yield 不能被中断,而 sleep 则可以接受中断。14.Java线程池中submit()和execute()方法有什么区别?两个方法都可以向线程池提交任务,execute()方法的返回类型是 void,它定义在Executor 接口中。而 submit()方法可以返回持有计算结果的 Future 对象,它定义在ExecutorService 接口中,它扩展了 Executor 接口,其它线程池类像ThreadPoolExecutor 和 ScheduledThreadPoolExecutor 都有这些方法。15.volatile关键字的作用?volatile关键字的作用保证可见性;防止指令重排;16.常用的线程池有哪些?17.简述一下你对线程池的理解线程池本质上是一种池化技术,而池化技术是一种资源复用的思想。它的核心设计目标,我认为有两个:减少线程的频繁创建和销毁带来的性能开销,因为线程创建会涉及到CPU上下文切换、内存分配等工作。线程池本身会有参数来控制线程创建的数量,这样就可以避免无休止的创建线程带来的资源利用率过高的问题,起到了资源保护的作用。18.Runnable接口和Callable接口的区别Runnable需要实现run()方法Callable需要实现call()方法Runnable从jdk1.1开始加入Callable从jdk1.5开始加入区别1: 两者最大的区别,实现Callable接口的任务线程能返回执行结果,而实现Runnable接口的任务线程不能返回执行结果注意点:Callable接口支持返回执行结果,此时需要调用FutureTask.get()方法实现,此方法会阻塞线程直到获取“将来”的结果,当不调用此方法时,主线程不会阻塞区别2:Callable接口实现类中run()方法允许将异常向上抛出,也可以直接在内部处理(try…catch); 而Runnable接口实现类中run()方法的异常必须在内部处理掉,不能向上抛出参考资料Github上365道Java高频面试复习题,助你拿爆大厂offerJava 实现多线程的四种方式详解Java实现多线程的三种方式Java四种常用线程池的详细介绍notify() 和 notifyAll()方法的使用和区别并发关键字:volatile如何保证可见性和有序性?面试官:为什么wait()方法要放在同步块中?Java面试突击之synchronized和ReentrantLock有什么区别?多线程 Thread.yield 方法到底有什么用?5种常用的线程池【小明】谈谈你对线程池的理解【建议收藏】【Java面试】简述一下你对线程池的理解?
2022年09月07日
326 阅读
0 评论
0 点赞
2022-09-01
排序算法重梳理
0.复杂度和稳定性汇总版本一名词解释:n:数据规模k:"桶"的个数In-place:占用常数内存,不占用额外内存Out-place:占用额外内存稳定性:排序后 2 个相等键值的顺序和排序之前它们的顺序相同版本二 其中:k表示计数排序中最大值和最小值之间的差值;l表示桶排序中桶的个数;d表示基数排序中最大值的位数,r表示是多少进制;希尔排序的时间复杂度很大程度上取决于增量gap sequence的选择,不同的增量会有不同的时间复杂度。文中使用的“gap=length/2”和“gap=gap/2”是一种常用的方式,也被称为希尔增量,但其并不是最优的。其实希尔排序增量的选择与证明一直都是个数学难题,而下图列出的是迄今为止大部分的gap sequence选择的方案:1.逐一代码实现1.1 冒泡排序每次循环都比较前后两个元素的大小,如果前者大于后者,则将两者进行交换。这样做会将每次循环中最大的元素替换到末尾,逐渐形成有序集合。将每次循环中的最大(小)元素逐渐由队首转移到队尾的过程形似“冒泡”过程,故因此得名。一个优化冒泡排序的方法就是 如果在一次循环的过程中没有发生交换,则可以立即退出当前循环,因为此时已经排好序了(也就是时间复杂度最好情况下是$O(n)$的由来)。import java.util.Arrays; class Solution { // 交换数组元素位置 public static void swap(int[] array,int index1,int index2){ int temp = array[index1]; array[index1] = array[index2]; array[index2] = temp; } public static void bubbleSort(int[] array){ for (int i = 0; i < array.length-1; i++) { boolean flag = false;//记录本轮是否发生冒泡 for (int j = 0; j < array.length-1-i; j++) { if(array[j]>array[j+1]){ swap(array,j,j+1); flag = true; } } if (!flag) { //本轮没有发生冒泡则表示数组已经有序,可以直接break break; } } } public static void main(String[] args) { int[] array = new int[]{1,9,4,8,2,3,0,7,5,6}; bubbleSort(array); System.out.println(Arrays.toString(array)); } }1.2 选择排序每次循环都会找出当前循环中最小(大)的元素,然后和此次循环中的队首元素进行交换。import java.util.Arrays; class Solution { // 交换数组元素位置 public static void swap(int[] array,int index1,int index2){ int temp = array[index1]; array[index1] = array[index2]; array[index2] = temp; } public static void selectSort(int[] array){ for (int i = 0; i < array.length; i++) { int minIndex = i; for (int j = i+1; j < array.length; j++) { minIndex = array[j]<array[minIndex]?j:minIndex; } swap(array,i,minIndex); } } public static void main(String[] args) { int[] array = new int[]{1,9,4,8,2,3,0,7,5,6}; selectSort(array); System.out.println(Arrays.toString(array)); } }1.3 快速排序import java.util.Arrays; class Solution { // 交换数组元素位置 public static void swap(int[] array,int index1,int index2){ int temp = array[index1]; array[index1] = array[index2]; array[index2] = temp; } /** * @brief 快速排序-partition * 大体思想:把比pivot小的换到前面 */ public static int partition(int[] array,int left,int right){ // 取最后一个元素作为中心元素 int pivot = array[right]; // 遍历数组中的所有元素,将比中心元素大的放在右边,比中心元素小的放在左边---该步骤有点类似于选择排序 int i = left; for (int j = left; j < right; j++) { if (array[j] <= pivot) { swap(array,i,j);//比pivot小的,全部换到前面去 i++; } } //此时,i指向的元素一定大于等于pivot,把privot换回到中间 swap(array,i,right); return i; } /** * @brief 快速排序-递归划分 */ public static void quickSort(int[] array){ int mid = partition(array,0, array.length-1); quickSort(array,0,mid-1); quickSort(array,mid+1,right); } public static void main(String[] args) { int[] array = new int[]{1,9,4,8,2,3,0,7,5,6}; quickSort(array); System.out.println(Arrays.toString(array)); } }1.4 插入排序插入排序的精髓在于每次都会在先前排好序的子集合中插入下一个待排序的元素,每次都会判断待排序元素的上一个元素是否大于待排序元素,如果大于,则将元素右移,然后判断再上一个元素与待排序元素...以此类推。直到小于等于比较元素时就是找到了该元素的插入位置。这里的等于条件放在哪里很重要,因为它是决定插入排序稳定与否的关键。import java.util.Arrays; class Solution { // 交换数组元素位置 public static void swap(int[] array,int index1,int index2){ int temp = array[index1]; array[index1] = array[index2]; array[index2] = temp; } public static void insertSort(int[] array){ for (int i = 1; i < array.length; i++) { int curItem = array[i];//缓存下一个待排序的元素 // 把有序集合中的所有比curItem大的元素都往后移一位 int j = i-1; while (j>=0&&array[j]>curItem){ array[j+1] = array[j--]; } // 把待排序元素插入到有序序列中 array[j+1] = curItem; } } public static void main(String[] args) { int[] array = new int[]{1,9,4,8,2,3,0,7,5,6}; insertSort(array); System.out.println(Arrays.toString(array)); } }1.5 希尔排序希尔排序可以认为是插入排序的改进版本。首先按照初始增量来将数组分成多个组,每个组内部使用插入排序。然后缩小增量来重新分组,组内再次使用插入排序...重复以上步骤,直到增量变为1的时候,这个时候整个数组就是一个分组,进行最后一次完整的插入排序即可结束。在排序开始时的增量较大,分组也会较多,但是每个分组中的数据较少,所以插入排序会很快。随着每一轮排序的进行,增量和分组数会逐渐变小,每个分组中的数据会逐渐变多。但因为之前已经经过了多轮的分组排序,而此时的数组会趋近于一个有序的状态,所以这个时候的排序也是很快的。而对于数据较多且趋向于无序的数据来说,如果只是使用插入排序的话效率就并不高。所以总体来说,希尔排序的执行效率是要比插入排序高的。import java.util.Arrays; class Solution { // 交换数组元素位置 public static void swap(int[] array,int index1,int index2){ int temp = array[index1]; array[index1] = array[index2]; array[index2] = temp; } public static void shellSort(int[] array){ int gap = array.length >>> 1; // 希尔排序的初始增量 while (gap>0){ for (int i = 0; i < gap; i++) { //对根据增量划分的组执行插入排序 // 一次排序一个增量组--插入排序 for (int j = i+gap; j < array.length; j+=gap) { int curItem = array[j]; // 待排序元素 int k = j - gap; while (k>=i&&array[k]>curItem){ array[k+gap] = array[k]; k-=gap; } array[k+gap] = curItem; } } gap >>>= 1; } } public static void main(String[] args) { int[] array = new int[]{1,9,4,8,2,3,0,7,5,6}; shellSort(array); System.out.println(Arrays.toString(array)); } }1.6 堆排序堆排序的过程是首先构建一个大(小)顶堆,大顶堆首先是一棵完全二叉树,其次它保证堆中任意节点的值总是不大(小)于其父节点的值。因为大顶堆中的最大元素肯定是根节点,所以每次取出根节点即为当前大顶堆中的最大元素,取出后剩下的节点再重新构建大顶堆,再取出根节点,再重新构建…重复这个过程,直到数据都被取出,最后取出的结果即为排好序的结果。import java.util.Arrays; class Solution { // 交换数组元素位置 public static void swap(int[] array,int index1,int index2){ int temp = array[index1]; array[index1] = array[index2]; array[index2] = temp; } /** * 维护堆的性质 * @param array 存储堆的数组 * @param size 堆的大小 * @param i 待维护节点的下标 */ public static void heapify(int[] array,int size,int i ){ int largest = i; int lson = i*2+1; int rson = i*2+2; if(lson<size&&array[lson]>array[largest]) largest = lson; if(rson<size&&array[rson]>array[largest]) largest = rson; if(largest!=i){ swap(array,largest,i); heapify(array,size,largest); } } public static void heapSort(int[] array,int size){ // 建堆 for (int i = size/2-1; i>=0 ; i--) { // 从最后一个元素的父节点开始维护 heapify(array,size,i); } // 排序 for (int i = size-1; i >=0 ; i--) { swap(array,i,0); // 将堆的最后一个元素和堆顶元素进行交换 heapify(array,i,0); // 将堆顶元素移出堆,并维护堆顶元素的性质 } } public static void main(String[] args) { int[] array = new int[]{1,9,4,8,2,3,0,7,5,6}; heapSort(array, array.length); System.out.println(Arrays.toString(array)); } }1.7 归并排序归并排序使用的是分治的思想,首先将数组不断拆分,直到最后拆分成两个元素的子数组,将这两个元素进行排序合并,再向上递归。不断重复这个拆分和合并的递归过程,最后得到的就是排好序的结果。import java.util.Arrays; class Solution { // 交换数组元素位置 public static void swap(int[] array,int index1,int index2){ int temp = array[index1]; array[index1] = array[index2]; array[index2] = temp; } // 合并 public static void merge(int[] array, int left, int mid, int right) { int[] temp = new int[right - left + 1]; // 临时数组 int p1 = left; // 标记左半区第一个未排序的元素 int p2 = mid + 1; // 标记右半区第一个未排序的元素 int k = 0; //临时数组元素的下标 // 合并两个有序数组 while (p1 <= mid && p2 <= right) { if (array[p1] <= array[p2]) { temp[k++] = array[p1++]; } else { temp[k++] = array[p2++]; } } // 把剩余的数组直接放到temp数组中 while (p1 <= mid) { temp[k++] = array[p1++]; } while (p2 <= right) { temp[k++] = array[p2++]; } // 复制回原数组 for (int i = 0; i < temp.length; i++) { array[i + left] = temp[i]; } } public static void mergeSort(int[] array,int left,int right){ //如果只有一个元素,那么就不需要继续划分 //只有一个元素的区域,本生就是有序的,只需要被归并即可 if(left<right){ //找中间点,这里没有选择“(left + right) / 2”的方式,是为了防止数据溢出 int mid = left + ((right - left) >>> 1); // 递归划分左半区 mergeSort(array, left, mid); // 递归划分右半区 mergeSort(array, mid + 1, right); // 对子数组进行合并 merge(array, left, mid, right); } } public static void main(String[] args) { int[] array = new int[]{1,9,4,8,2,3,0,7,5,6}; mergeSort(array,0, array.length); System.out.println(Arrays.toString(array)); } }1.8 计数排序计数排序会创建一个临时的数组,里面存放每个数出现的次数。比如一个待排序的数组是[2,4,1,2,5,3,4,8,7],那么这个临时数组中记录的数据就是[0,1,2,1,2,1,0,1,1]。那么最后只需要遍历这个临时数组中的计数值就可以了。import java.util.Arrays; class Solution { // 交换数组元素位置 public static void swap(int[] array,int index1,int index2){ int temp = array[index1]; array[index1] = array[index2]; array[index2] = temp; } public static int[] countingSort(int[] array,int left,int right){ //记录待排序数组中的最大值 int max = array[0]; //记录待排序数组中的最小值 int min = array[0]; for (int item : array) { if (item > max) max = item; if (item < min) min = item; } //记录每个数出现的次数 int[] temp = new int[max - min + 1]; for (int item : array) { temp[item - min]++; } // 将结果复制回原数组 int index = 0; for (int i = 0; i < temp.length; i++) { //当输出一个数之后,当前位置的计数就减一,直到减到0为止 while (temp[i]-- > 0) { array[index++] = i + min; } } return array; } public static void main(String[] args) { int[] array = new int[]{1,9,4,8,2,3,0,7,5,6}; countingSort(array,0, array.length); System.out.println(Arrays.toString(array)); } }1.9 桶排序上面的计数排序在数组最大值和最小值之间的差值是多少,就会生成一个多大的临时数组,也就是生成了一个这么多的桶,而每个桶中就只插入一个数据。如果差值比较大的话,会比较浪费空间。那么我能不能在一个桶中插入多个数据呢?当然可以,而这就是桶排序的思路。桶排序类似于哈希表,通过一定的映射规则将数组中的元素映射到不同的桶中,每个桶内进行内部排序,最后将每个桶按顺序输出就行了。桶排序执行的高效与否和是否是稳定的取决于哈希散列的算法以及内部排序的结果。需要注意的是,这个映射算法并不是常规的映射算法,要求是每个桶中的所有数都要比前一个桶中的所有数都要大,这样最后输出的才是一个排好序的结果。比如说第一个桶中存1-30的数字,第二个桶中存31-60的数字,第三个桶中存61-90的数字...以此类推import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; class Solution { // 交换数组元素位置 public static void swap(int[] array,int index1,int index2){ int temp = array[index1]; array[index1] = array[index2]; array[index2] = temp; } public static int[] bucketSort(int[] array,int left,int right){ if (array == null || array.length < 2) { return array; } //记录待排序数组中的最大值 int max = array[0]; //记录待排序数组中的最小值 int min = array[0]; for (int i : array) { if (i > max) { max = i; } if (i < min) { min = i; } } //计算桶的数量(可以自定义实现) int bucketNumber = (max - min) / array.length + 1; List<Integer>[] buckets = new ArrayList[bucketNumber]; //计算每个桶存数的范围(可以自定义实现或者不用实现) int bucketRange = (max - min + 1) / bucketNumber; for (int value : array) { //计算应该放到哪个桶中(可以自定义实现) int bucketIndex = (value - min) / (bucketRange + 1); //延迟初始化 if (buckets[bucketIndex] == null) { buckets[bucketIndex] = new ArrayList<>(); } //放入指定的桶 buckets[bucketIndex].add(value); } int index = 0; for (List<Integer> bucket : buckets) { if (bucket == null) { continue; } //对每个桶进行内部排序,我这里使用的是快速排序,也可以使用别的排序算法,当然也可以继续递归去做桶排序 bucket.sort(new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o1-o2; } }); //将不为null的桶中的数据按顺序写回到array数组中 for (Integer integer : bucket) { array[index++] = integer; } } return array; } public static void main(String[] args) { int[] array = new int[]{1,9,4,8,2,3,0,7,5,6}; bucketSort(array,0, array.length); System.out.println(Arrays.toString(array)); } }1.10 基数排序基数排序不是根据一个数的整体来进行排序的,而是将数的每一位上的数字进行排序。比如说第一轮排序,我拿到待排序数组中所有数个位上的数字来进行排序;第二轮排序我拿到待排序数组中所有数十位上的数字来进行排序;第三轮排序我拿到待排序数组中所有数百位上的数字来进行排序...以此类推。每一轮的排序都会累加上一轮所有前几位上排序的结果,最终的结果就会是一个有序的数列。基数排序一般是对所有非负整数进行排序的,但是也可以有别的手段来去掉这种限制(比如都加一个固定的数或者都乘一个固定的数,排完序后再恢复等等)。基数排序和桶排序很像,桶排序是按数值的区间进行划分,而基数排序是按数的每一位的值进行划分。同时这两个排序都是需要依靠其他排序算法来实现的(如果不算递归调用桶排序本身的话)。基数排序每一轮的内部排序会使用到计数排序来实现,因为每一位上的数字无非就是0-9,是一个小范围的数,所以使用计数排序很合适。import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; class Solution { // 交换数组元素位置 public static void swap(int[] array,int index1,int index2){ int temp = array[index1]; array[index1] = array[index2]; array[index2] = temp; } public static void radixSort(int[] array) { //定义一个二维数组,表示10个桶,每个桶就是一个一维数组 int[][] bucket = new int[10][array.length];//很明显,基数排序使用了空间换时间 //为了记录每个桶中实际存放了多少个数据,定义一个一维数组来记录每次放入数据的个数 //比如bucketElementCounts[0]=3,意思是bucket[0]存放了3个数据 int[] bucketElementCounts = new int[10]; int digitOfElement = 0;//每次取出的元素的位数 //找到数组中最大数的位数 int max = 0; for (int i = 0; i < array.length; i++) { if (max < String.valueOf(array[i]).length()) { max = String.valueOf(array[i]).length(); } } int index = 0; for (int i = 0, n = 1; i < max; i++, n *= 10) { //第i+1轮排序(针对每个元素的位进行排序处理) for (int j = 0; j < array.length; j++) { digitOfElement = array[j] / n % 10;//取出每个元素的位 bucket[digitOfElement][bucketElementCounts[digitOfElement]] = array[j];//放入对应的桶 bucketElementCounts[digitOfElement]++; } //按照桶的顺序(一维数组的下标取出数据),放入原来的数组 index = 0; //遍历每一个桶,并将桶中数据放入原数组 for (int k = 0; k < bucketElementCounts.length; k++) { //如果桶中有数据,我们才放到原数组 if (bucketElementCounts[k] != 0) { //循环第k个桶,放入 for (int l = 0; l < bucketElementCounts[k]; l++) { array[index] = bucket[k][l]; index++; } } bucketElementCounts[k] = 0;//置零!!!!! } } } public static void main(String[] args) { int[] array = new int[]{1,9,4,8,2,3,0,7,5,6}; radixSort(array); System.out.println(Arrays.toString(array)); } }参考资料排序算法:快速排序【图解+代码】排序算法:堆排序【图解+代码】[排序算法:希尔排序【图解+代码】]()排序算法:归并排序【图解+代码】十种经典排序算法总结基数排序(Java)
2022年09月01日
813 阅读
0 评论
0 点赞
1
2
3
4
...
6