开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

一文带你了解Mybatis架构原理 Mybatis架构

发表于 2021-11-21

这是我参与11月更文挑战的第19天,活动详情查看:2021最后一次更文挑战

Mybatis架构

1.1 Mybatis架构设计图

image.png

从上图知道,一般把Mybatis分成三层

  • Api接口层
+ 接口层提供给外部使⽤的接⼝API,开发⼈员通过这些本地API来操纵数据库。接⼝层⼀接收到调⽤请求就会调⽤数据处理层来完成具体的数据处理。
+ MyBatis和数据库的交互有两种⽅式:


a.使⽤传统的MyBatis提供的API;


b.使⽤Mapper代理的⽅式
  • 数据处理层
+ 负责具体的SQL查找、SQL解析、SQL执⾏和执⾏结果映射处理等。它主要的⽬的是根据调⽤的请求完成⼀次数据库操作。
  • 框架支撑层
+ 负责最基础的功能⽀撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共⽤的东⻄,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的⽀撑

1.2 主要构件和相互之间的关系

构件 描述
SqlSession 作为MyBatis⼯作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能
Executor MyBatis执⾏器,是MyBatis调度的核⼼,负责SQL语句的⽣成和查询缓 存的维护
StatementHandler 封装了JDBC Statement操作,负责对JDBC statement的操作,如设置参数、将Statement结果集转换成List集合。
ParameterHandler 负责对⽤户传递的参数转换成JDBC Statement所需要的参数,
ResultSetHandler 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;
TypeHandler 负责java数据类型和jdbc数据类型之间的映射和转换
MappedStatement MappedStatement维护了⼀条<select
SqlSource 负责根据⽤户传递的parameterObject,动态地⽣成SQL语句,将信息封 装到BoundSql对象中,并返回
BoundSql 表示动态⽣成的SQL语句以及相应的参数信息

Mybatis图解层次结构

image.png

1.3 Mybatis总体流程

(1) 加载配置并初始化 触发条件:加载配置⽂件

配置来源于两个地⽅,⼀个是配置⽂件(主配置⽂件conf.xml,mapper⽂件*.xml),—个是java代码中的注解,将主配置⽂件内容解析封装到Configuration,将sql的配置信息加载成为⼀个mappedstatement对象,存储在内存之中

(2) 接收调⽤请求

触发条件:调⽤Mybatis提供的API
传⼊参数:为SQL的ID和传⼊参数对象
处理过程:将请求传递给下层的请求处理层进⾏处理。

(3) 处理操作请求

触发条件:API接⼝层传递请求过来

传⼊参数:为SQL的ID和传⼊参数对象处理过程:

  • (A)根据SQL的ID查找对应的MappedStatement对象。
  • (B)根据传⼊参数对象解析MappedStatement对象,得到最终要执⾏的SQL和执⾏传⼊参数。
  • (C)获取数据库连接,根据得到的最终SQL语句和执⾏传⼊参数到数据库执⾏,并得到执⾏结果。
  • (D)根据MappedStatement对象中的结果映射配置对得到的执⾏结果进⾏转换处理,并得到最终的处理 结果。
  • (E)释放连接资源。

(4) 返回处理结果

将最终的处理结果返回。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

MySQL执行count()计数为什么会这么慢 计数的实现方

发表于 2021-11-21

「这是我参与11月更文挑战的第21天,活动详情查看:2021最后一次更文挑战」

在开发中,我们经常会做一些界面查询的功能,基本上基础数据的查询都会采用分页模式,会在每次查询的时候都查询出总的条数,根据查询的总条数,然后在计算出需要展示的页面总数,渲染到浏览器让用户能够分页查询数据。查询的语句一般都如下:

1
SQL复制代码SELECT count(*) FROM table_name;

这样的查询语句,在系统最开始上线的时候数据量不是很多,这样查询的性能比较快,当数据里达到几百万条的数据,查询的时间可能会话费几秒剩下更久,这样的话一个查询页面就会出现数据很久才会出来影响用户体验。接下来彻底的了解下MySQL为什么查询总数会这么慢,有助于以后我们功能优化。

计数的实现方式

MySQL比较常用的引擎是MyISAM、InnoDB两种引擎,这两种引擎对于count(*)统计总数都有不同的实现方式:

  • MyISAM引擎:在数据增加或删除的时候,每次都会将表的总行数存在磁盘上,在每次执行count(*)查询总数的时候,会直接将总数返回,这样的话,很快就会把总数返回;
  • InnoDB引擎:在查询总数的时候,会去一行行的数条数进行累加,这样就导致每次查询都会去数一下总条数,导致查询总数耗费极多的时间;

为什么InnoDB不能够学MyISAM引擎一样,直接记录一个总数,每次查询总数的时候就直接拿出来返回就可以呢?

在InnoDB内部是记录了一个总条数,但是这个数据是个大概,不准确的,在需要知道准确数据的时候,InnoDB不会将这个数据返回给客户端,而是去现场数出总数,这样做的原因:

  • InnoDB支持事务,为了保证数据的隔离性,采用了MVCC多版本并发控制,在不同的会话中,根据rodo log日志,及每个会话的事务隔离级别需要判断当前需要能够可见那些数据,这样的话就需要将当前会话的可见记录统计计数,不同的会话级别查询出来的总数可能不一致。

InnoDB在计算总数的时候,是基于索引树是查找总数的,在构建的索引中,有写索引树比较小,只是单单的计算总数,InnoDB会选取一颗比较小的索引数来遍历,这样可减少扫描的数据量,提高查询的效率,又能够得到准确的总数值。

count(*)、count(1)、count(主键)、count(普通字段)效率

上面的几种求总数的写法,想必大家都用过,其中用的最多的是前面三种:count(*)、count(1)、 count(主键) 那么上面的四种写法到底执行效率怎么样呢?、

count()属于一个聚合函数,所以在计算总数的时候,会根据返回的结果集,一行行的判断,如果count的参数不是NULL,就会累加1,如果为NULL,则不累计。

  • count(字段) 与count(主键):实现的逻辑是一样的,都会遍历整张表,把每一行的主键或字段取出来,然后给server层,server拿到数据后,判断是否为空:不为空,就累加1,如果字段被允许定义为NULL,就需要把值取出来再判断一下,不是NULL才累加。因为字段可能存在NULL,就需要多步判断,由此可以得出查询效率:count(主键) > count(字段)
  • count(1):跟上面的遍历逻辑一样,不会取字段值,直接返回1数字,server对于返回的每一行都为1,判断是不可能为空,直接按行累加,相比count(id), 在查询的时候没有取具体的字段值,这会减少一部分时间,所以count(1)的查询效率会高于count(主键)
  • count():这种场景对于MySQL是特殊处理的场景,对于count()这种语法,在server层的时候,不会去判断是否为空,直接累加计数,这样的话查询速度就会略高于count(1)

经过上面的分析,可以得出这样的查询效果:count(*) ≈ count(1) > count(主键) > count(字段)

如何避免count计数

在我的工作项目中,我们整个项目组对于页面的查询都没有计算总数,而是给用户查询的时候,默认只查询第一页,不会告知用户当前条件总数,如果查询返回的条数小于当前页码个数,则认为是最后一页,用户不可以点击下一次,这样的话,用户只能够一页一页的点击下一页,而且点击的下一页最多只能够查询10页,如果用户点击的页数越多,分页越生,查询效率也会很低下;

在做这个方案之前,我们对用户的查询页码数有过分页点击调查统计,用户点击到后面10页的可能性极低,如果用户想看更多数据就走异步导出功能,这样的话就会加快用户界面查询的速度,也会减少服务器的查询压力,提高数据库的性能。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【每日算法】力扣217 存在重复元素

发表于 2021-11-21

这是我参与11月更文挑战的第21天,活动详情查看:2021最后一次更文挑战。

前言

前面刷了牛客上入门级别的五道算法题,个人感觉比较吃力,所以我打算去刷一下数据结构相关的算法,做一个数据结构的专项训练,我本来想在牛客上刷的,但是怎么都找不到数据结构的专项训练,我记得之前有的,所以现在只能在力扣上刷了。

image.png
确定。

让我们来做第一题。

描述

给定一个整数数组,判断是否存在重复元素。

如果存在一值在数组中出现至少两次,函数返回 true 。如果数组中每个元素都不相同,则返回 false 。

1
2
3
4
5
6
7
8
9
10
11
12
makefile复制代码示例 1:

输入: [1,2,3,1]
输出: true
示例 2:

输入: [1,2,3,4]
输出: false
示例 3:

输入: [1,1,1,3,3,4,3,2,4,2]
输出: true

做题

个人感觉关于数组的题,循环是跑不了的了,主要的问题就是怎么能够在一次循环中判断一个元素出现了两次。

使用 Set 集合,把遍历的结果都加入集合,当返回为 false 就说明了有重复的了。感觉这个方案不错,一次遍历搞定。

我突然想到,如果我不认识 Set,那我可能就会采用循环内再套一层循环的方式来做这道题,这就显得我很菜了。

开始敲代码!

1
2
3
4
5
6
7
8
9
10
java复制代码public boolean containsDuplicate(int[] nums) {
Set set=new HashSet();
for (int i = 0; i < nums.length; i++) {
if (!set.add(nums[i])) {
//元素重复
return true;
}
}
return false;
}

执行!

image.png

居然战绩平平,这我不能接受!

神奇的评论区

浏览了一下评论区,发现了很多人都再发一行代码解决这道题。

有这样的

1
2
3
java复制代码 public boolean containsDuplicate(int[] nums) {
return Arrays.stream(nums).distinct().count() < nums.length;
}

还有其他语言的

1
2
3
c复制代码class Solution:
def containsDuplicate(self, nums: List[int]) -> bool:
return not (len(nums)==len(set(nums)))

我浏览了一下 Arrays.stream(nums).distinct().count()的代码,看到了这一段代码。

image.png

我就感觉它和我写的原理差不多,只不过它是 Java 提供好的方法,有时候我们为了追求代码的简洁,降低造轮子,我们就可以是使用 Java 为我们造好的轮子。

今天就到这里啦。

这里是程序员徐小白,【每日算法】是我新开的一个专栏,在这里主要记录我学习算法的日常,也希望我能够坚持每日学习算法,不知道这样的文章风格您是否喜欢,不要吝啬您免费的赞,您的点赞、收藏以及评论都是我下班后坚持更文的动力。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

SpringBoot:springprofilesact

发表于 2021-11-21

「这是我参与11月更文挑战的第14天,活动详情查看:2021最后一次更文挑战」。

现在在的公司用spring.profiles.active=@profiles.active@ 当我看到这个的时候,一脸蒙蔽,这个@ 是啥意思。

这里其实是配合 maven profile进行选择不同配置文件进行开发

使用 spring.profiles.active 参数,搭配@Profile注解,可以实现不同环境下(开发、测试、生产)配置参数的切换。
我们除application.properties外,还可以根据命名约定命名格式:application-{profile}.properties)来配置,如果active赋予的参数没有与使用该命名约定格式文件相匹配的话,app则会默认从名为application-default.properties 的配置文件加载配置. 如:spring.profiles.active=hello-world,sender,dev 有三个参数,其中 dev 正好匹配下面配置中application-dev.properties 配置文件,所以app启动时,项目会先从application-dev.properties加载配置,再从application.properties配置文件加载配置,如果有重复的配置,则会以application.properties的配置为准。

实战

1.构建一个springboot 项目

这里使用idea进行构建的,这个过程省略

2.pom文件配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
xml复制代码<profiles>
<profile>
<!-- 生产环境 -->
<id>prod</id>
<properties>
<profiles.active>prod</profiles.active>
</properties>
</profile>
<profile>
<!-- 本地开发环境 -->
<id>dev</id>
<properties>
<profiles.active>dev</profiles.active>
</properties>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<!-- 测试环境 -->
<id>test</id>
<properties>
<profiles.active>test</profiles.active>
</properties>
</profile>
</profiles>
  • 这里默认dev配置

3.配置多个配置文件

application.properties

注意这里的profiles.active 要和pom文件的对应上

1
ini复制代码spring.profiles.active=@profiles.active@

application-dev.properties

1
ini复制代码name = "dev"

application-prod.properties

1
ini复制代码name = "prod"

application-test.properties

1
ini复制代码name = "test"

4.编写个测试的controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
kotlin复制代码
/**
* @author kevin
* @date 2019/6/28 16:12
*/
@RestController
public class HelloController {

@Value("${name}")
private String name;

@RequestMapping(value = {"/hello"},method = RequestMethod.GET)
public String say(){
return name;
}
}

5.启动测试

使用idea工具启动开发

默认是dev,假如想要使用prod配置文件,如上图选择prod,注意下面的导入,重启项目

1
2
bash复制代码D:\dev_code\profiles-demo\target>curl http://localhost:8080/hello
"prod"

6 打包

这里使用idea打包不再介绍,如果你使用命令

1
css复制代码mvn clean package -P dev

则是使用dev配置

其实如果现在配合注册中心,进行使用,比如nacos,则可以使用namespace进行操作

也可以用来区分不同环境之间不同的操作。其实是很有必要。

好了,玩的开心

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

leetcode 1130 Minimum Cost Tr

发表于 2021-11-21

「这是我参与11月更文挑战的第21天,活动详情查看:2021最后一次更文挑战」

描述

Given an array arr of positive integers, consider all binary trees such that:

  • Each node has either 0 or 2 children;
  • The values of arr correspond to the values of each leaf in an in-order traversal of the tree.
  • The value of each non-leaf node is equal to the product of the largest leaf value in its left and right subtree, respectively.

Among all possible binary trees considered, return the smallest possible sum of the values of each non-leaf node. It is guaranteed this sum fits into a 32-bit integer.

A node is a leaf if and only if it has zero children.

Example 1:

1
2
3
4
makefile复制代码Input: arr = [6,2,4]
Output: 32
Explanation: There are two possible trees shown.
The first has a non-leaf node sum 36, and the second has non-leaf node sum 32.

Example 2:

1
2
ini复制代码Input: arr = [4,11]
Output: 44

Note:

1
2
3
vbnet复制代码2 <= arr.length <= 40
1 <= arr[i] <= 15
It is guaranteed that the answer fits into a 32-bit signed integer (i.e., it is less than 2^31).

解析

根据题意,给定一个正整数数组 arr ,考虑所有二叉树,使得:

  • 每个节点有 0 或 2 个孩子
  • arr 的值对应于树的中序遍历中每个叶子的值
  • 每个非叶节点的值分别等于其左右子树中最大叶值的乘积

可能会有多种二叉树,题目要求我们返回所有非叶节点的值的最小可能总和值。 题目会保证该总和在 32 位整数以内。一个节点是叶子当且仅当它有零个孩子。

首先我们通过简单的观察案例发现,要尽量把大的值放到深度较浅的叶子结点,把小的值放到深度较深的叶子结点,这样最后乘法的结果才会尽量小。一般有三种情况,我们举三种简单情况的例子:

  • 例子一:[1,2,3] ,相乘的方法为 1 和 2 先乘得 2 ,然后 2 和 3 相乘,即从左往右操作
  • 例子二:[3,2,1] ,相乘的方法为 1 和 2 先乘得 2 ,然后 2 和 3 相乘,即从右往左操作
  • 例子三:[3,1,2] ,取 3 和 2 的较小值为 2 ,先对 1 和 2 相乘得 2 ,再 2 和 3 相乘

因为题目中对值的限制在 1 到 15 之间,在例子一前面加一个超大值 100 ,就和例子三一样规律一样,所以一共有两种情况。我们维护一个栈 stack 在最前面加一个超大值 100 ,先按照例子三的逻辑计算乘积和,再按照例子二的逻辑计算乘积和。

解答

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
python复制代码class Solution(object):
def mctFromLeafValues(self, arr):
"""
:type arr: List[int]
:rtype: int
"""
stack = [100]
result = 0
for current in arr:
while stack[-1]<=current:
drop = stack.pop()
result += drop*(min(stack[-1], current))
stack.append(current)
while len(stack)>2:
result += stack.pop() * stack[-1]
return result

运行结果

1
2
erlang复制代码Runtime: 28 ms, faster than 46.53% of Python online submissions for Minimum Cost Tree From Leaf Values.
Memory Usage: 13.4 MB, less than 74.26% of Python online submissions for Minimum Cost Tree From Leaf Values.

原题链接:leetcode.com/problems/mi…

您的支持是我最大的动力

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

如何将nodejs项目程序部署到阿里云服务器上 一、概述 二

发表于 2021-11-21

这是我参与11月更文挑战的第20天,活动详情查看:2021最后一次更文挑战

平常做完项目都很少自己部署,但是感觉作为一个开发者不会部署有点过不去,于是询问了身边的小伙伴一波,做出以下总结。

一、概述

本文将从以下三个个方面来讲解:

配置oneinstack

将nodejs项目上传至服务器

使用pm2运行nodejs项目

二、具体步骤

1、拥有自己的服务器

先到阿里云买一台学生机,买完以后记得到控制台重置密码,密码设置的尽可能的复杂;

2、下载Xshell

之后需要到Xshell官方网站下载XShell;

3、 oneinstack配置web环境

用oneinstack配web环境,具体配置如下:

在这里插入图片描述

4、 XShell连接远程主机

配置完以后,XShell连接远程主机;

在这里插入图片描述

在这里插入图片描述

5、更新系统软件

连接完成之后, XShell 控制台上的小红点会变成小绿点,显示小绿点即表示连接成功,如果还是小红点则说明连接失败,需重返检查下原来的配置是否有错误,及时纠正。

成功连接后, XShell 控制台输入yum update -y来更新系统软件,更新完成之后,将第三步 oneinstack 最底部生成的安装命令粘贴继续粘贴进控制台,稍等片刻,将完成重置和登录。

6、在服务器上安装node环境

登录之后,我们需要在服务器安装一个node环境。这里需要注意的一点是,如果需求没有很大的话,用命令yum install -y nodejs在 XShell 控制台即可直接安装,但是 XShell 里面的版本是在比较低,比如我安装的时候是 10.21.0 ,实际上 node 已经出到14版本了,所以建议是手动安装。接下来讲解在服务器手动安装最新版本nodejs环境的做法:

①首先需要在本地下载nodejs最新版本,在node官网下载服务器能够运行的版本,因为我的是centOS系统,选择linux Binaries(x64)。

PS :不要像我一样傻以为是在本地安装一个 node 环境,本地的 node 环境和远程的 node 环境完全不是同一个东西。

在这里插入图片描述

②在本地下载完成后,需要将安装包(即压缩包文件)复制到服务器里面,如何复制呢,这个根据个人需求传到服务器对应的路径下。( windows 系统推荐使用winscp软件或者Xftp软件进行上传,速度很快)

③上传后,在服务端进行解压,这里需要用命令行在XShell里面进行解压。(注意:node-v14.15.4-linux-x64.tar.xz是你node安装包的名字)
解压思路是:需要先将xz文件解压成tar文件,之后将tar文件解压成文件夹完成解压

1
2
bash复制代码xz -d node-v14.15.4-linux-x64.tar.xz
tar -xvf node-v14.15.4-linux-x64.tar

④解压后,将node文件夹下的所有内容复制到/usr/local/node,配置环境变量。这里路径/usr/local/node下的node文件需要新建,用mkdir -p 路径名可以直接建立路径下的文件夹。

⑤配置环境变量。linux的环境变量文件是/etc/profile,可执行文件在/usr/local/node/bin里,所以要把这个路径加入到这个环境变量文件中。

1
bash复制代码vi /etc/profile

输入命令后进入界面,再输入i进入编辑模式,PageDown到最后一行,不换行,添加环境变量。

1
bash复制代码: /usr/local/node/bin

⑥保存退出。

1
2
bash复制代码ESC
Shift zz

⑦保存退出后执行如下命令,将环境变量生效。

1
bash复制代码source /etc/profile

⑧装完之后node -v查看版本号是否一一对应。

7、部署项目到服务器上

安装完 node 环境,接下来需要把我们的项目部署到服务器上。(这里介绍从 git 上拉项目到服务器上)

①先到 /home 下面创建一个属于你自己用户名的文件夹,比如名字为 monday ,那么就会有 /home/monday ,之后把自己的项目文件克隆到monday文件下,为 /home/monday/project-name 。

②进入自己用户名的文件夹,git clone 你的项目地址,之后 npm i 。

③上传 nodejs 项目后,配置数据库。

8、安装pm2并启动nodejs项目

①使用npm全局安装pm2:npm install pm2 -g;

②进入nodejs项目目录,我把项目上传到了 /home/monday/project-name ,所以,输入cd /home/monday/project-name 的项目目录回车,进入项目目录启动nodejs项目,看个人对自己项目的设计来决定启动方式,我的是npm run prd;

③检查nodejs项目是否启动:输入pm2 list回车,如果出现下面的列表,就说明 nodejs 项目已经顺利在后台建立了服务。

在这里插入图片描述
至此,我们就把服务启动起来啦!

三、快捷指令

1、linux的常用命令

  • rm -rf 目录 删除目录
  • cp -R conf.example conf 复制conf.example(已存在文件)为conf(未存在)
  • curl www.linux.com 用于测试一台服务器是否可以到达一个网站
  • ①yum install screen -y;②screen -S lnmp 用于解决连接非正常中断,重新连接会话

2、pm2的常用命令

  • pm2 list——查看进程列表
  • pm2 restart 0——重启服务
  • pm2 stop 0——停止服务
  • pm2 delete 0——停止后要对进程进行删除
  • pm2 info 0——可以查看当前进程的所有信息以及一些录像
  • pm2 log 0——查看日志
  • pm2 monit——监控进程列表

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

springboot注解详解(二)Spring Bean 注

发表于 2021-11-21

「这是我参与11月更文挑战的第21天,活动详情查看:2021最后一次更文挑战」

一、Spring Bean 注解

在本小节中,主要列举与Spring Bean相关的4个注解以及它们的使用方式。

1、@ComponentScan

@ComponentScan注解用于配置Spring需要扫描的被组件注解注释的类所在的包。可以通过配置其basePackages属性或者value属性来配置需要扫描的包路径。value属性是basePackages的别名。

2、@Component

@Component注解用于标注一个普通的组件类,它没有明确的业务范围,只是通知Spring被此注解的类需要被纳入到Spring Bean容器中并进行管理。

3、@Service

@Service注解是@Component的一个延伸(特例),它用于标注业务逻辑类。与@Component注解一样,被此注解标注的类,会自动被Spring所管理。

4、@Repository

@Repository注解也是@Component注解的延伸,与@Component注解一样,被此注解标注的类会被Spring自动管理起来,@Repository注解用于标注DAO层的数据持久化类。

二、Spring DI注解与Scops注解

1、@DependsOn

@DependsOn注解可以配置Spring IoC容器在初始化一个Bean之前,先初始化其他的Bean对象。

2、@Bean

@Bean注解主要的作用是告知Spring,被此注解所标注的类将需要纳入到Bean管理工厂中。@Bean注解的用法很简单,在这里,着重介绍@Bean注解中initMethod和destroyMethod的用法。

3、@Scope

@Scope注解可以用来定义@Component标注的类的作用范围以及@Bean所标记的类的作用范围。@Scope所限定的作用范围有:singleton、prototype、request、session、globalSession或者其他的自定义范围。这里以prototype为例子进行讲解。当一个Spring Bean被声明为prototype(原型模式)时,在每次需要使用到该类的时候,Spring IoC容器都会初始化一个新的改类的实例。在定义一个Bean时,可以设置Bean的scope属性为prototype:scope=“prototype”,也可以使用@Scope注解设置,如下:

@Scope(value=ConfigurableBeanFactory.SCOPE_PROPTOTYPE)

4、@Scope 单例模式

当@Scope的作用范围设置成Singleton时,被此注解所标注的类只会被Spring IoC容器初始化一次。在默认情况下,Spring IoC容器所初始化的类实例都为singleton。同样的原理,此情形也有两种配置方式。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Java基础学习20之类集、类加载器 Java基础学习20之

发表于 2021-11-21

Java基础学习20之类集、类加载器

「这是我参与11月更文挑战的第20天,活动详情查看:2021最后一次更文挑战」。

关于作者

  • 作者介绍

🍓 博客主页:作者主页

🍓 简介:JAVA领域优质创作者🥇、一名在校大三学生🎓、在校期间参加各种省赛、国赛,斩获一系列荣誉🏆

🍓 关注我:关注我学习资料、文档下载统统都有,每日定时更新文章,励志做一名JAVA资深程序猿👨‍💻


类集

认识类集

如果现在要想保存多个对象,肯定使用对象数组完成,但是对象数组本身有一个最大问题在于确定了数据的长度,所以后来使用了链表完成了动态对象数组的开发,可是链表的开发难度实在是很大,而且如果一个链表要想真正的去使用,只依靠之前多编写的还不够,还需要进行一些代码的调优。

而在JDK1.2之后正式引入了类集的概念,类集是一种动态的对象数组,属于各个数据结构的实现类,在整个类集之中主要的组成是一些核心的操作接口:Collection,List,Set,Map,Iterator,Enumeration。

Collection集合接口

Collection是单个集合保存最大的父接口。而在Collection接口的定义如下:

1
java复制代码public interface Collection<E> extends Iterable<E>

从JDK1.5之后Collection接口上追加有泛型应用,这样的直接好处就是避免了ClassCastException异常,里面的所有的数据的保存类型应该是相同的。对于此类的常用方法有如下几个:

方法名称 类型 描述
public boolean add(E e) 普通 向集合中添加数据
boolean addAll(Collection<? extends E> c) 普通 向集合添加一组数据
public void clear() 普通 清空集合数据
public boolean contains(Object o) 普通 查询数据是否存在
public Boolean isEmpty() 普通 判断集合是否有元素
public Iterator iterator() 普通 取得Iterator接口对象,用于输出
public boolean remove(Object o) 普通 删除数据,需要equals()方法
public int size() 普通 取得集合的长度
public Object[] toArray() 普通 将集合数据返回

在开发之中,add()和iterator()方法使用率极高,其他的方法几乎使用不到。接口只是一个存储数据的标准,而并不能区分存储类型,例如:如果要存放数据可能需要区分重复与不重复。所以在实际的开发之中,往往会去考虑使用Collection接口的子接口:List(允许重复)、Set(不允许重复)。

image-20210822185639319

List接口简介

List是Collection的一个最常用的子接口,并且允许重复的子接口。

方法名称 类型 描述
public E get(int index) 普通 取得指定索引位置上的数据
public E set(int index,E element) 普通 修改指定索引上的数据
public ListIterator listIterator() 普通 为ListIterator接口实例化

List子接口与Collection接口相比最大的特点在于其有一个get()方法,可以根据索引取得内容。但是List本身还属于我们的一个接口而如果取得接口的实例化对象就必须有子类,在List接口下有三个常用子类:ArrayList、Vector、LinkedList。

image-20210822191336145

最终的操作应该还是以接口为主,那么既然要以接口为主,所以所有的方法只参考接口的定义即可。

ArrayList子类

ArrayList是一个针对于List接口的数组操作实现。

List基本操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码package com.day17.demo;

import java.util.ArrayList;
import java.util.List;

public class ArrayListDemo {
public static void main(String[] args) {
List<String> all = new ArrayList<>();//此时集合里面只适合保存String类型数据
System.out.println(all.size() + " " + all.isEmpty());
all.add("Hello");
all.add("Hello"); //重复数据
all.add("world~!");
all.add("zsr~");
System.out.println(all.contains("zsr~"));
System.out.println(all.contains("zsr"));
System.out.println(all);
}
}

通过我们的代码我们可以证实List允许保存重复数据。

List中存在get()方法,可以利用get()方法结合索引取得数据。

List的get()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码package com.day17.demo;

import java.util.ArrayList;
import java.util.List;

public class ArrayListDemo {
public static void main(String[] args) {
List<String> all = new ArrayList<>();//此时集合里面只适合保存String类型数据
all.add("Hello");
all.add("Hello"); //重复数据
all.add("world~!");
all.add("zsr~");
for (int i = 0; i < all.size(); i++) {
System.out.println(all.get(i));
}
}
}

但是千万记住,get()方法是List子接口的,如果现在使用不是List而是Collection,对于此时的数据取出,只能够将集合变为对象数组的操作了。

(开发一般不使用)Collection进行输出处理并取出数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码package com.day17.demo;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;

public class CollectionDemo {
public static void main(String[] args) {
Collection<String> all = new ArrayList<>();//此时集合里面只适合保存String类型数据
all.add("Hello");
all.add("Hello"); //重复数据
all.add("world~!");
all.add("zsr~");
//操作以Object形式返回,那么就有可能需要向下转型,有可能造成ClassCastException的安全隐患
Object result [] = all.toArray();//变为Object对象数组
System.out.println(Arrays.toString(result));
}
}

集合与简单Java类

在实际的开发之中,集合里面保存最多的数据类型,就是简答java类。

向集合保存简单java类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
java复制代码package com.day17.demo;

import java.util.ArrayList;
import java.util.List;

class Person{
private String name;
private Integer age;
public Person(String name, Integer age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
if(this == obj){
return true;
}
if (obj == null){
return false;
}
if(!(obj instanceof Person)){
return false;
}
Person per = (Person) obj;//这个对象自己操作可以向下转型
return this.name.equals(per.name) && this.age.equals(per.age);
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}

}
public class ListDemo {
public static void main(String[] args) {
List<Person> all = new ArrayList<>();
all.add(new Person("张三",10));
all.add(new Person("李四",21));
all.add(new Person("王五",19));
//对于remove()、contains()方法必须类中有equals()的支持
all.remove(new Person("李四",21));
System.out.println(all.contains(new Person("李四",21)));
for (int i = 0; i < all.size(); i++) {
System.out.println(all.get(i));
}
}
}

该List集合如果使用remove()、contains()方法必须有equals()方法的支持。简单的java类里面的使用是很少使用。

Vector子类

Vector是旧的子类,这个类是从JDK1.0退出,ArrayList实在JDK1.2推出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码package com.day17.demo;

import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

public class ArrayListDemo {
public static void main(String[] args) {
List<String> all = new Vector<>();//此时集合里面只适合保存String类型数据
all.add("Hello");
all.add("Hello"); //重复数据
all.add("world~!");
all.add("zsr~");
for (int i = 0; i < all.size(); i++) {
System.out.println(all.get(i));
}
}
}

面试题:请解释ArrayList与Vector区别?

区别 ArrayList Vector
历史时间 JDK1.2 JDK1.0
处理形式 异步处理,形式更高 同步处理,性能降低
数据安全 非线程安全 线程安全
输出形式 Iterator、ListIterator、foreach Iterator、ListIterator、foreach、Enumeration

LinkedList子类

在List接口里面还有一个LinkedList子类,如果向我们的父接口转型的话,使用的形式和之前没有任何的区别。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码package com.day17.demo;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class ArrayListDemo {
public static void main(String[] args) {
List<String> all = new LinkedList<>();//此时集合里面只适合保存String类型数据
all.add("Hello");
all.add("Hello"); //重复数据
all.add("world~!");
all.add("zsr~");
for (int i = 0; i < all.size(); i++) {
System.out.println(all.get(i));
}
}
}

面试题:请解释ArrayList与LinkedList区别?

区别 ArrayList LinkedList
构造方法 public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } public LinkedList() { }
开辟长度 开辟定长的大小 动态开辟
时间复杂度 时间复杂度为1 时间复杂度为n

ClassLoader类加载器

Class类描述的是类的整个信息,在Class类中提供的forName()方法它所能处理的只是通过CLASSPATH配置的路径进行加载,而我们的类加载的路径可能是网络、文件、数据库。这是ClassLoader类主要作用。

认识类加载器

首先Class观察一个方法:public ClassLoader getClassLoader();

编写简单的反射程序,观察ClassLoader的存在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码package com.day17.demo;
class Member{//自定义类一定在CLASSPATH之中

}
public class TestDemo1 {
public static void main(String[] args) {
Class<?> cls = Member.class;
System.out.println(cls.getClassLoader());
System.out.println(cls.getClassLoader().getParent());
System.out.println(cls.getClassLoader().getParent().getParent());
}
}

/*
sun.misc.Launcher$AppClassLoader@73d16e93
sun.misc.Launcher$ExtClassLoader@15db9742
null
*/

出现两个加载器AppClassLoader(应用程序类加载器)、ExtClassLoader(扩展类加载器)。

image-20210822132640087

对于第三方程序类库除了CLASSPATH之外,实际上在java里面还有一个加载目录:C:\ProgramFiles\Java\jdk1.8.0_241\jre\lib\ext

image-20210822133615839

观察类我们发现ClassLoader里有一个方法:public 类<?> loadClass(String name) throws ClassNotFoundException,进行类的加载操作处理。

自定义ClassLoader

实现文件的类加载器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
java复制代码package com.day17.demo;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

class MyClassLoader extends ClassLoader{
/**
* 实现一个自定义的类加载器,传入类名称后,通过指定文件路径加载
* @param className
* @return
* @throws Exception
*/
public Class<?> loadData(String className) throws Exception{
byte classDate [] = this.loadClassData();
return super.defineClass(className, classDate, 0, classDate.length);

}
/**
* 通过指定文件路径进行类的文件加载,进行二进制读取
* @return
* @throws Exception
*/
private byte [] loadClassData() throws Exception{
InputStream input = new FileInputStream("f:" + File.separator + "java" + File.separator + "Member.class");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte [] data = new byte [20];//定义读取的缓冲区
int temp = 0;
while((temp = input.read(data)) != -1){
bos.write(data,0,temp);
}
byte ret [] = bos.toByteArray();
input.close();
bos.close();
return ret;
}
}
public class ClassLoaderDemo {
public static void main(String[] args) throws Exception{
Class<?> cls = new MyClassLoader().loadData("com.day17.test.Member");
System.out.println(cls.newInstance());
}
}

类加载器给我们用户最大的帮助就是在于可以通过动态的路径实现类的加载处理操作。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

动态规划攻略之:下载插件

发表于 2021-11-21

「这是我参与11月更文挑战的第21天,活动详情查看:2021最后一次更文挑战」

题目

小扣打算给自己的 VS code 安装使用插件,初始状态下带宽每分钟可以完成 1 个插件的下载。假定每分钟选择以下两种策略之一:

  • 使用当前带宽下载插件
  • 将带宽加倍(下载插件数量随之加倍)

请返回小扣完成下载 n 个插件最少需要多少分钟。

注意:实际的下载的插件数量可以超过 n 个

示例1:

1
2
3
4
5
6
7
8
9
ini复制代码输入:n = 2

输出:2

解释:
以下两个方案,都能实现 2 分钟内下载 2 个插件

方案一:第一分钟带宽加倍,带宽可每分钟下载 2 个插件;第二分钟下载 2 个插件
方案二:第一分钟下载 1 个插件,第二分钟下载 1 个插件

示例2:

1
2
3
4
5
6
7
8
9
ini复制代码输入:n = 4

输出:3

解释:
最少需要 3 分钟可完成 4 个插件的下载,以下是其中一种方案:
第一分钟带宽加倍,带宽可每分钟下载 2 个插件;
第二分钟下载 2 个插件;
第三分钟下载 2 个插件。。

解题思路

根据题意,我们可以利用动态规划的方法来解答此题。

我们定义 dp[i] 为下载 i 个插件所需的最优时间。

依照当前速度下载所需的时间为 dp[i] = dp[i - 1] + 1。

速度加倍所需的时间为 dp[i] = d[(i + 1) / 2] + 1。

那为什么 dp[i] = dp[(i + 1) / 2] 呢!

因为当我们速度加倍了,下载 i / 2 个插件时速度加倍,下一次便恰好下载到 i 个插件。也就是 i / 2 时才是最优解。

代码实现

1
2
3
4
5
6
7
8
9
10
java复制代码class Solution {
public int leastMinutes(int n) {
int[] dp = new int[n+1];
dp[1] = 1;
for (int i = 2; i <= n; i++){
dp[i] = Math.min(dp[i-1]+1, dp[(i+1)/2]+1);
}
return dp[n];
}
}

最后

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

**往期文章:

  • 二叉树刷题总结:二叉搜索树的属性
  • 二叉树总结:二叉树的属性
  • 二叉树总结:二叉树的修改与构造
  • StoreKit2 有这么香?嗯,我试过了,真香
  • 看完这篇文章,再也不怕面试官问我如何构造二叉树啦!
  • 那帮做游戏的又想让大家氪金,太坏了!
  • 手把手带你撸一个网易云音乐首页 | 适配篇
  • 手把手带你撸一个网易云音乐首页(三)
  • 手把手带你撸一个网易云音乐首页(二)
  • 手把手带你撸一个网易云音乐首页(一)
  • 代码要写注释吗?写你就输了
  • Codable发布这么久我就不学,摸鱼爽歪歪,哎~就是玩儿
  • iOS 优雅的处理网络数据,你真的会吗?不如看看这篇
  • UICollectionView 自定义布局!看这篇就够了

请你喝杯 ☕️ 点赞 + 关注哦~

  1. 阅读完记得给我点个赞哦,有👍 有动力
  2. 关注公众号— HelloWorld杰少,第一时间推送新姿势

最后,创作不易,如果对大家有所帮助,希望大家点赞支持,有什么问题也可以在评论区里讨论😄~**

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

MySQL学习-索引的基础篇(下)

发表于 2021-11-21

「这是我参与11月更文挑战的第21天,活动详情查看:2021最后一次更文挑战」

作者:汤圆

个人博客:javalover.cc

前言

前面介绍了索引的基础知识,包括索引的模型、分类、重建等等;

本篇我们再延伸一下索引的分类,比如覆盖索引、联合索引;

索引的分类从的方面来说,分为主键索引和非主键索引,像即将介绍的这些索引都属于非主键索引;

目录

  1. 覆盖索引
  2. 联合索引
  3. 最左前缀原则
  4. 索引下推

正文

1. 覆盖索引

定义:如果一条查询语句中,非主键索引覆盖了我们的查询需求,那么这个索引就是覆盖索引

怎么理解呢?

就比如我们执行下面的查询语句:

1
sql复制代码select id from T where k between 3 and 5;

这里面id是主键,k是非主键索引,非主键索引中存储的就是主键的值,所以这里的查询需求(id)被覆盖了,我们不需要回表进行二次查询;

简单点来理解就是,如果一条语句只查询主键的值,且用到了非主键索引,那么这个非主键索引就是覆盖索引;

这个覆盖索引的好处就是提升了查询效率;

2. 联合索引

定义:同时将多个字段设置为一个索引,这个索引就是联合索引;

一般的索引都是将一个字段设置为索引;

为啥要弄出一个联合索引呢?

为了提高查询效率,因为联合索引可以查询联合索引的所有字段的值;

表面上看有点类似覆盖索引,都是为了简化查询,不用回表;

例子:

比如我们现在有两个字段:身份证号、姓名;然后将这两个字段设置为联合索引,那么我们在通过身份证号查询姓名时,就可以一次查到,不用回表;

如果没有联合索引,那就需要先通过身份证号获取主键ID值,然后再根据主键ID值获取姓名;

那联合索引有没有缺点呢?

有的,缺点跟下面即将介绍的最左前缀原则有关,那就是如果查询条件不是联合索引的第一个字段,那么该联合索引就会失效;

还是上面的身份证号+姓名的例子,如果我们不是通过身份证号查询姓名,而是通过姓名查询身份证号,那么此时联合索引就失效了;

怎么解决这个缺点呢?

就是根据业务需要调整联合索引中字段的顺序,像上面这个业务,根据身份证号查询姓名应该是比较常用的,因为身份证号有唯一性,所以就可以将身份证号字段放到第一位;然后将姓名字段单独再设为一个索引;

怎么来确定联合索引中字段的顺序呢?

简单点来说,就是先设定一个顺序,如果该顺序可以少建立一个其他的索引,那么该顺序就是可以接受的;

3. 最左前缀原则

定义:当查询条件符合联合索引的最左N个字段,或者普通索引(字符串类型)的最左N个字符时,就会用到对应的索引;此时用的原则就是最左前缀原则

比如上面的联合索引 身份证号+姓名,只有一个查询条件身份证号时,也是可以用到该联合索引的;

根据这个原则,我们可以再来回答一下上面的问题:怎么来确定联合索引中字段的顺序?

上面提到了要考虑会不会增加其他索引;

其实还有一个方面是要考虑空间占用问题,下面我们用例子来说明;

例子:

比如有需求为:根据姓名查询年龄,也要根据年龄查询姓名,那此时要怎么建立索引呢?

根据最左前缀原则,他俩的顺序其实怎么建都一样;

但是考虑到空间占用问题,就需要将姓名放到前面,因为姓名字段肯定比年龄字段占用空间大,此时的联合索引为(name, age);

然后再为年龄字段单独建立一个普通索引 (age);

4. 索引下推

定义:根据联合索引中的字段顺序,从前往后挨个筛选,这个就是索引下推

例子:

1
sql复制代码select * from tuser where name like '张%' and age=10 and ismale=1;

这里假设联合索引为 (name, age),此时的查询过程如下:

  1. 先去联合索引中筛选 name 以”张”开头的用户;
  2. 再在结果集中筛选 age = 10 的用户;
  3. 最后遍历这些结果集,依次取出主键ID的值,再 回表 查询 ismale=1的数据;

这里的前面两步都是直接在联合索引中就可以查到的,因为联合索引保存了联合字段的值;

只有最后一步, ismale 不在联合字段中,才需要回表查询;

这里需要注意的一点是:索引下推是在mysql5.6之后才有的,也就是说mysql5.6之前,联合索引只保存了联合字段中的第一个字段,此时的查询除了第一个字段,后面的字段都需要回表查询

总结

本篇主要介绍了覆盖索引和联合索引,以及相关的一些规则,比如最左前缀原则,索引下推等

  • 覆盖索引:如果要查询的结果字段已经存储在索引中,那么这个索引就是覆盖索引;
  • 联合索引:就是把多个字段联合起来,建立一个索引,这个索引就是联合索引;联合索引需要考虑怎么将多个字段排序,可以少建立其他索引的顺序就是好顺序;
  • 最左前缀原则:比如上面的联合索引 身份证号+姓名,如果只有一个查询条件 身份证号,那么此时就会用到最左前缀原则;普通索引(字符串类型),如果查询条件为字符串的左边N个字符,那么也会用到最左前缀原则
  • 索引下推:还是基于联合索引,当查询条件的多个字段在联合索引中时,这些字段会在联合索引中进行挨个查询,不需要回表;

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…257258259…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%