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

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


  • 首页

  • 归档

  • 搜索

Windows下搭建Vagrant+VirtualBox环境

发表于 2021-11-28

前言

最近在折腾一些东西,需要使用docker,但是我的机器配置很神奇,无法直接安装docker,没得法子我只好曲线救国了—使用Vagrant+VirtualBox。

安装 VirtualBox

下载 VirtualBox 安装包

VirtualBox 下载页面地址(不保证长期有效):

1
html复制代码https://www.virtualbox.org/wiki/Downloads

我现在的是window版本,具体链接如下(不保证长期有效):

1
html复制代码https://download.virtualbox.org/virtualbox/6.1.30/VirtualBox-6.1.30-148432-Win.exe

大家注意 vagrant 和 VirtualBox 的版本,不同的版本之间可能会出现兼容性问题,大家最好和我的保持一致,毕竟坑我已经趟过了。virtualbox:6.1.30版本;vagrant :2.2.19

安装vagrant

下载vagrant 安装包

vagrant下载页面地址:

1
html复制代码https://www.vagrantup.com/downloads

下载界面
直接下载64位的即可,我下载时的版本为vagrant_2.2.19,具体链接如下(不保证长期有效):

1
html复制代码https://releases.hashicorp.com/vagrant/2.2.19/vagrant_2.2.19_x86_64.msi
安装vagrant

没啥特别的直接next下一步下一步就行,当然可以自行选择安装地址,这个看自己的意愿。

下载虚拟机镜像

这个虚拟机镜像下载的速度真的是很感人!所以我们要先下好镜像,然后直接用命令添加到vagrant的box中就可以了。下载页面的地址如下(不保证长期有效):

1
html复制代码https://app.vagrantup.com/centos/boxes/7

我下载的是centos7镜像,具体链接如下(不保证长期有效):

别问为什么还使用centos7,问就是习惯了centos。

1
html复制代码https://cloud.centos.org/centos/7/vagrant/x86_64/images/CentOS-7-x86_64-Vagrant-2004_01.VirtualBox.box
自定义项目地址

通过 Vagrant 创建虚机需要先导入镜像文件,也就是 box,它们默认存储的位置在用户目录下的 .vagrant.d 目录下,对于 Windows 系统来说,就是 C:\Users\用户名.vagrant.d。如果后续可能会用到较多镜像,或者你的 C 盘空间比较紧缺,可以通过设置环境变量 VAGRANT_HOME 来设置该目录。

window系统

1
2
shell复制代码setx VAGRANT_HOME "D:\ProgramFiles\vagrant\boxData"
setx VAGRANT_HOME "D:\ProgramFiles\vagrant\boxData" /M

linux 系统

1
shell复制代码export VAGRANT_HOME="/path/to/vagrant_home"

window系统下使用cmd执行上述命令后,记得重新打开cmd配置才会生效。

添加本地镜像

我将镜像放在D:\ProgramFiles\vagrant\data目录下,在cmd上执行下面的命令添加镜像:

1
shell复制代码vagrant box add centos/7 ./CentOS-7-x86_64-Vagrant-2004_01.VirtualBox.box

如果提示没有权限则请使用管理员权限启动cmd

执行完毕提示下面的文字则镜像添加成功:

1
shell复制代码==> box: Successfully added box 'centos/7' (v0) for 'virtualbox'!
新建虚机

还是在D:\ProgramFiles\vagrant\data目录下,执行下面的命令:

1
shell复制代码vagrant init centos/7

出现下面的文字提示则认为初始化成功:

1
2
3
4
shell复制代码A `Vagrantfile` has been placed in this directory. You are now
ready to `vagrant up` your first virtual environment! Please read
the comments in the Vagrantfile as well as documentation on
`vagrantup.com` for more information on using Vagrant.
启动虚拟机

执行下面的命令之前,记得先启动VirtualBox。

1
shell复制代码vagrant up

出现下面的文字提示则认为虚拟机启动成功:

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
shell复制代码D:\ProgramFiles\vagrant\data>vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
==> default: Clearing any previously set forwarded ports...
==> default: Clearing any previously set network interfaces...
==> default: Preparing network interfaces based on configuration...
default: Adapter 1: nat
default: Adapter 2: hostonly
==> default: Forwarding ports...
default: 22 (guest) => 2222 (host) (adapter 1)
==> default: Booting VM...
==> default: Waiting for machine to boot. This may take a few minutes...
default: SSH address: 127.0.0.1:2222
default: SSH username: vagrant
default: SSH auth method: private key
==> default: Machine booted and ready!
==> default: Checking for guest additions in VM...
default: No guest additions were detected on the base box for this VM! Guest
default: additions are required for forwarded ports, shared folders, host only
default: networking, and more. If SSH fails on this machine, please install
default: the guest additions and repackage the box to continue.
default:
default: This is not an error message; everything may continue to work properly,
default: in which case you may ignore this message.
==> default: Configuring and enabling network interfaces...
==> default: Rsyncing folder: /cygdrive/d/ProgramFiles/vagrant/data/ => /vagrant
==> default: Machine already provisioned. Run `vagrant provision` or use the `--provision`
==> default: flag to force provisioning. Provisioners marked to run always will still run.

这个地方坑就比较多了,一是前面提到的vagrant和virtualbox两者版本兼容的问题。二就是中文用户名的问题。前面说到创建成功的镜像文件保存在 C:\Users\用户名.vagrant.d路径下,vagrant采用ruby语言编写会遇到一些编码的问题,碰巧我的用户名也是中文,网上解决方案折腾了半天,最后我直接新建了一个windows用户解决。

接下来你就可以在VirtualBox中看到一个已经启动的虚拟机。

虚拟机启动成功
附上我的Vagrantfile配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
shell复制代码# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
# The most common configuration options are documented and commented below.
# For a complete reference, please see the online documentation at
# https://docs.vagrantup.com.

# Every Vagrant development environment requires a box. You can search for
# boxes at https://vagrantcloud.com/search.
config.vm.box = "centos/7"
config.vm.network "private_network", ip: "192.168.33.10"

end
登录虚拟机

vagrant默认有两个账户,具体如下:

1
2
shell复制代码账户:vagrant 密码: vagrant
账户:root 密码: vagrant

其实vagrant默认的是使用密钥登录,直接执行vagrant ssh命令即可登录,但是登录的是vagrant 账户,但在虚拟机中安装一些软件,例如docker是需要使用root账户权限的,所以该如何解决呢?
vagrant ssh命令登录 虚拟机

直接使用 su 命令,然后输入密码 vagrant即可。

可以使用 vagrant ssh-config 查看具体的登录配置:

1
2
3
4
5
6
7
8
9
10
11
shell复制代码D:\ProgramFiles\vagrant\data>vagrant ssh-config
Host default
HostName 127.0.0.1
User vagrant
Port 2222
UserKnownHostsFile /dev/null
StrictHostKeyChecking no
PasswordAuthentication no
IdentityFile D:/ProgramFiles/vagrant/data/.vagrant/machines/default/virtualbox/private_key
IdentitiesOnly yes
LogLevel FATAL

上面说到使用vagrant ssh 登录虚拟机,但是有些地方还是不太方便,所以还是需要使用专业的终端软件登录虚拟机,这里我使用的是MobaXterm,以前我都是使用xshell+xftp的,无奈后面没法白*嫖,就换了这个MobaXterm,用起来感觉还行。

配置如下
这个host我尝试使用前面Vagrantfile文件中的IP地址 192.168.33.10,但是无法连接。

安装jdk8

接下来我们就登录虚拟机,先安装一下jdk8。下面的步骤都是在虚拟机中操作的。

下载jdk8安装包

Oracle的Java8下载页面地址(不保证长期有效):

1
html复制代码https://www.oracle.com/java/technologies/downloads/#java8

我选择的是x64 Compressed Archive版本,具体下载地址如下(这个链接应该也会随时失效的):

1
html复制代码https://download.oracle.com/otn/java/jdk/8u311-b11/4d5417147a92418ea8b615e228bb6935/jdk-8u311-linux-x64.tar.gz?AuthParam=1638083601_4ec49a992915eff50f4be9d904997568

下载地址
可以在window下下载好然后传到Linux里,不过我建议直接在Linux中下载,毕竟就是一个命令的事情:

1
2
3
shell复制代码cd /usr/local/src

wget https://download.oracle.com/otn/java/jdk/8u311-b11/4d5417147a92418ea8b615e228bb6935/jdk-8u311-linux-x64.tar.gz?AuthParam=1638083601_4ec49a992915eff50f4be9d904997568

现在下载需要登录账户了,这里提供一个Oracle登录账户

账户:pigide3064@fxseller.com
密码:Azerty1!

账户随时会失效,若是失效自己百度或者直接注册一个账户吧!

安装jdk8
  1. 创建安装目录
1
shell复制代码mkdir /usr/local/java
  1. 将安装包解压至安装目录
1
shell复制代码tar -zxvf /usr/local/src/jdk-8u311-linux-x64.tar.gz\?AuthParam\=1638083601_4ec49a992915eff50f4be9d904997568  -C /usr/local/java/
  1. 设置环境变量,将Java添加至环境变量中

打开文件

1
shell复制代码vim /etc/profile

在文件的末尾处增加

1
2
3
4
5
shell复制代码
export JAVA_HOME=/usr/local/java/jdk1.8.0_311
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
export PATH=${JAVA_HOME}/bin:$PATH

执行source命令重载profile文件

1
shell复制代码source /etc/profile

添加软连接(这一步不做好像也是可以的)

1
shell复制代码ln -s /usr/local/java/jdk1.8.0_311/bin/java /usr/bin/java
  1. 检查效果如何:
1
shell复制代码java -version

结果出现下面的文字则代表java安装完成。
检查效果

安装docker

在安装docker之前记得先切换到root用户。

  1. 不管三七二十一先执行下面的命令,卸载系统自带的docker。
1
2
3
4
5
6
7
8
shell复制代码yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
  1. 开始安装docker,执行的相关命令如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
shell复制代码
# step 1: 安装必要的一些系统工具
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
# Step 2: 添加软件源信息
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
# Step 3: 更新并安装Docker-CE
sudo yum makecache fast
sudo yum -y install docker-ce
# Step 4: 开启Docker服务
sudo service docker start

# 开机启动
systemctl enable docker

参考资料参见我之前的文章 在CentOS7上使用yum安装Docker

安装docker composer

这都是用docker了那容器编排起码也要用一下,这里我就用docker composer了。安装的相关命令如下:

  1. 运行以下命令下载 Docker Compose 的当前稳定版本:
1
shell复制代码sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

这里使用的是官方文档里的示例,安装的是1.29.2版本。链接:https://docs.docker.com/compose/install/

  1. 将可执行权限应用于二进制文件:
1
shell复制代码chmod +x /usr/local/bin/docker-compose
  1. 测试安装结果:
1
shell复制代码docker-compose --version

如果展现下面提示则代表docker-compose安装完成。

1
2
3
shell复制代码[root@localhost vagrant]# docker-compose --version
docker-compose version 1.29.2, build 5becea4c
[root@localhost vagrant]#

总结

这样我们就完成了Vagrant+VirtualBox环境配置,也在虚拟机中配置好了Java环境和docker。

本文转载自: 掘金

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

Netty零拷贝机制分析二 Netty中的零拷贝

发表于 2021-11-28

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

前言

零拷贝指避免在 用户态(User-space) 与 内核态(Kernel-space) 之间来回拷贝数据,而netty的另拷贝都是基于用户态的操作,更多的是在优化数据操作上。其零拷贝主要体现在以下几个方面:

image.png

我们从netty读写源码分析一下

Netty中的零拷贝

1. 通过directe buffer实现零拷贝

Netty的接收和发送ByteBuffer采用DIRECT BUFFERS,使用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的JVM堆内存(HEAP BUFFERS)进行Socket读写,JVM会将堆内存Buffer拷贝一份到直接内存中,然后才能写入Socket中。JVM堆内存的数据是不能直接写入Socket中的。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。

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
java复制代码@Override
public final void read() {
final ChannelConfig config = config();
if (shouldBreakReadReady(config)) {
clearReadPending();
return;
}
final ChannelPipeline pipeline = pipeline();
final ByteBufAllocator allocator = config.getAllocator();
final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
allocHandle.reset(config);

ByteBuf byteBuf = null;
boolean close = false;
try {
do {
byteBuf = allocHandle.allocate(allocator);
allocHandle.lastBytesRead(doReadBytes(byteBuf));
if (allocHandle.lastBytesRead() <= 0) {
// nothing was read. release the buffer.
byteBuf.release();
byteBuf = null;
close = allocHandle.lastBytesRead() < 0;
if (close) {
// There is nothing left to read as we received an EOF.
readPending = false;
}
break;
}

allocHandle.incMessagesRead(1);
readPending = false;
pipeline.fireChannelRead(byteBuf);
byteBuf = null;
} while (allocHandle.continueReading());

allocHandle.readComplete();
pipeline.fireChannelReadComplete();

if (close) {
closeOnRead(pipeline);
}
} catch (Throwable t) {
handleReadException(pipeline, byteBuf, t, close, allocHandle);
} finally {
// Check if there is a readPending which was not processed yet.
// This could be for two reasons:
// * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method
// * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method
//
// See https://github.com/netty/netty/issues/2254
if (!readPending && !config.isAutoRead()) {
removeReadOp();
}
}
}

直接内存

直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,某些情况下这部分内存也会被频繁地使用,而且也可能导致OutOfMemoryError异常出现。Java里用DirectByteBuffer可以分配一块直接内存(堆外内存),元空间对应的内存也叫作直接内存,它们对应的都是机器的物理内存。

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
61
62
63
64
65
66
67
68
java复制代码public class MyMemoryTest {
public static void heapAccess() {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
//分配堆内存
ByteBuffer buffer = ByteBuffer.allocate(1000);
for (int i = 0; i < 100000; i++) {
for (int j = 0; j < 200; j++) {
buffer.putInt(j);
}
buffer.flip();
for (int j = 0; j < 200; j++) {
buffer.getInt();
}
buffer.clear();
}
stopWatch.stop();
System.out.println("堆内存访问:" + stopWatch.getTotalTimeMillis());
}

public static void directAccess() {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
//分配直接内存
ByteBuffer buffer = ByteBuffer.allocateDirect(1000);
for (int i = 0; i < 100000; i++) {
for (int j = 0; j < 200; j++) {
buffer.putInt(j);
}
buffer.flip();
for (int j = 0; j < 200; j++) {
buffer.getInt();
}
buffer.clear();
}
stopWatch.stop();
System.out.println("直接内存访问:" + stopWatch.getTotalTimeMillis());
}
public static void heapAllocate() {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
for (int i = 0; i < 100000; i++) {
ByteBuffer.allocate(100);
}
stopWatch.stop();
System.out.println("堆内存申请:" + stopWatch.getTotalTimeMillis());
}
public static void directAllocate() {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
for (int i = 0; i < 100000; i++) {
ByteBuffer.allocateDirect(100);
}
stopWatch.stop();
System.out.println("直接内存申请:" + stopWatch.getTotalTimeMillis());
}
public static void main(String args[]) {
for (int i = 0; i < 10; i ++) {
heapAccess();
directAccess();
}
System.out.println();
for (int i = 0; i < 10; i ++) {
heapAllocate();
directAllocate();
}
}
}

运行结果

image.png

从结果看出直接内存申请较慢,但访问效率高。在java虚拟机实现上,本地IO会直接操作直接内存(直接内存=>系统调用=>硬盘/网卡),而非直接内存则需要二次拷贝(堆内存=>直接内存=>系统调用=>硬盘/网卡)。

2. 通过CompositeByteBuf 实现零拷贝

CompositeByteBuf 类, 它可以将多个 ByteBuf 合并为一个逻辑上的 ByteBuf, 避免了各个byteBuf间的拷贝。

比如我们有两个byteBuf,header和body,现在我们想把它合成一个ByteBuf使用,通常的做法是:

1
2
3
4
5
6
7
8
java复制代码ByteBuf header = Unpooled.buffer(2);
header.writeChar('1');
ByteBuf body = Unpooled.buffer(4);
body.writeChar('2');

ByteBuf allBuf = Unpooled.buffer(header.readableBytes()+body.readableBytes());
allBuf.writeBytes(header);
allBuf.writeBytes(body);
1
2
java复制代码CompositeByteBuf compositeByteBuf = Unpooled.compositeBuffer();
compositeByteBuf.addComponents(true,header,body);

3. 通过wrap操作来实现零拷贝

比如有个byte数组,我们希望变成一个byteBuf对象, 通常的做法

1
2
3
java复制代码byte[] b = new byte[10];
ByteBuf byteBuf = Unpooled.buffer(10);
byteBuf.writeBytes(b);

使用wrap操作则可以避免这次额外的拷贝

1
java复制代码byteBuf = Unpooled.wrappedBuffer(b);

3. 通过slice的方式实现零拷贝

slice 操作和 wrap 操作刚好相反, Unpooled.wrappedBuffer 可以将多个 ByteBuf 合并为一个, 而 slice 操作可以将一个 ByteBuf 切片 为多个共享一个存储区域的 ByteBuf 对象.

ByteBuf 提供了两个 slice 操作方法:

image.png

image.png

1
2
3
ini复制代码ByteBuf byteBuf = ...
ByteBuf header = byteBuf.slice(0, 5);
ByteBuf body = byteBuf.slice(5, 10);

用 slice 方法产生 header 和 body 的过程是没有拷贝操作的, header 和 body 对象在内部其实是共享了 byteBuf 存储空间的不同部分而已. 即:

image.png

5. 通过 FileRegion 实现零拷贝

Netty 中使用 FileRegion 实现文件传输的零拷贝, 不过在底层 FileRegion 是依赖于 Java NIO FileChannel.transfer的零拷贝功能.

下面是传统的实现文件拷贝的功能,

1
2
3
4
5
6
7
8
9
10
11
java复制代码public static void copyFile(String srcFile, String destFile) throws Exception {
byte[] temp = new byte[1024];
FileInputStream in = new FileInputStream(srcFile);
FileOutputStream out = new FileOutputStream(destFile);
int length;
while ((length = in.read(temp)) != -1) {
out.write(temp, 0, length);
}
in.close();
out.close();
}

从源文件中读取定长数据到 temp 数组中, 然后再将 temp 中的内容写入目的文件, 这样的拷贝操作对于小文件倒是没有太大的影响, 但是如果我们需要拷贝大文件时, 频繁的内存拷贝操作就消耗大量的系统资源了.

用 Java NIO 的 FileChannel 实现零拷贝。

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码public static void copyFileWithFileChannel(String srcFileName, String destFileName) throws Exception {
RandomAccessFile srcFile = new RandomAccessFile(srcFileName, "r");
FileChannel srcFileChannel = srcFile.getChannel();

RandomAccessFile destFile = new RandomAccessFile(destFileName, "rw");
FileChannel destFileChannel = destFile.getChannel();

long position = 0;
long count = srcFileChannel.size();

srcFileChannel.transferTo(position, count, destFileChannel);
}

可以看到, 使用了 FileChannel 后, 我们就可以直接将源文件的内容直接拷贝(transferTo) 到目的文件中, 而不需要额外借助一个临时 buffer, 避免了不必要的内存操作.

我们来看一下在 Netty 中是怎么使用 FileRegion 来实现零拷贝传输一个文件的:
样例代码我们直接用netty中的代码,FileServerHandler

image.png

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
java复制代码@Override
public void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
RandomAccessFile raf = null;
long length = -1;
try {
// 1. 通过 RandomAccessFile 打开一个文件.
raf = new RandomAccessFile(msg, "r");
length = raf.length();
} catch (Exception e) {
ctx.writeAndFlush("ERR: " + e.getClass().getSimpleName() + ": " + e.getMessage() + '\n');
return;
} finally {
if (length < 0 && raf != null) {
raf.close();
}
}

ctx.write("OK: " + raf.length() + '\n');
if (ctx.pipeline().get(SslHandler.class) == null) {
// SSL not enabled - can use zero-copy file transfer.
// 2. 调用 raf.getChannel() 获取一个 FileChannel.
// 3. 将 FileChannel 封装成一个 DefaultFileRegion
ctx.write(new DefaultFileRegion(raf.getChannel(), 0, length));
} else {
// SSL enabled - cannot use zero-copy file transfer.
ctx.write(new ChunkedFile(raf));
}
ctx.writeAndFlush("\n");
}

本文转载自: 掘金

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

OpenCV中利用鼠标事件动态绘制图形

发表于 2021-11-28

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

使用鼠标事件动态绘制

对于使用 OpenCV 绘制图形和文本,我们已经耳熟能详了。在本文中,我们将利用 OpenCV 中的绘图函数,学习如何使用鼠标事件执行动态绘图。

动态绘制图形

为了利用鼠标事件进行动态绘图,我们必须首先了解如何使用 OpenCV 处理鼠标事件,在 OpenCV 中使用 cv2.setMouseCallback() 函数执行此功能,该函数的用法如下:

1
python复制代码cv2.setMouseCallback(windowName, onMouse, param=None)

此函数为名为 windowName 的窗口创建鼠标处理程序,onMouse 函数是回调函数,在发生鼠标事件(例如,双击、左键按下、左键按下等)时会进行调用;可选的 param 参数用于向回调函数传递附加信息。
因此,为了处理鼠标事件,第一步是创建回调函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
python复制代码def draw_circle(event, x, y, flags, param):
if event == cv2.EVENT_LBUTTONDBLCLK:
print("event: EVENT_LBUTTONDBLCLK")
cv2.circle(image, (x, y), 20, colors['magenta'], -1)

if event == cv2.EVENT_MOUSEMOVE:

print("event: EVENT_MOUSEMOVE")

if event == cv2.EVENT_LBUTTONUP:
print("event: EVENT_LBUTTONUP")

if event == cv2.EVENT_LBUTTONDOWN:
print("event: EVENT_LBUTTONDOWN")
cv2.rectangle(image,(x,y),(x+20,y+20),colors['cyan'],1)

draw_circle() 函数接收特定事件和每个鼠标事件的坐标 (x, y),当执行左键双击 (cv2.EVENT_LBUTTONDBLCLK ) 时,我们在事件的相应 (x, y) 坐标处绘制一个圆圈;而当执行左键单击 (cv2.EVENT_LBUTTONDOWN ) 时,在相应 (x, y) 坐标处绘制一个正方形。此外,我们还打印了一些消息以查看其他生成的事件,但我们暂时不使用它们来执行任何其他操作。

接下来,创建一个命名窗口,将其命名为 Mouse event。这个命名窗口将与鼠标回调函数相关联:

1
python复制代码cv2.namedWindow('Image mouse')

最后,将鼠标回调函数设置为我们之前创建的函数:

1
python复制代码cv2.setMouseCallback('Image mouse', draw_circle)

此时,当鼠标左键双击时,会以执行的双击的 (x, y) 位置为中心绘制一个填充的洋红色圆圈,当执行左键单击时,在相应 (x, y) 坐标处绘制一个正方形。

动态绘制图形

动态绘制图形和文本

在此实战程序中,将结合鼠标事件动态绘制图形和文本。首先,绘制文本以显示如何使用鼠标事件来执行特定操作:

1
2
3
4
5
6
7
8
9
10
11
12
python复制代码def draw_text():
# We set the position to be used for drawing text:
menu_pos = (10, 540)
menu_pos2 = (10, 555)
menu_pos3 = (10, 570)
menu_pos4 = (10, 585)

# 绘制文本以显示如何使用鼠标事件来执行特定操作
cv2.putText(image, 'Double left click: add a circle', menu_pos, cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255))
cv2.putText(image, 'Simple right click: delete last circle', menu_pos2, cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255))
cv2.putText(image, 'Double right click: delete all circle', menu_pos3, cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255))
cv2.putText(image, 'Press \'q\' to exit', menu_pos4, cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255))

从上述代码中,我们知道代码需要实现以下操作:

  1. 使用双击左键添加一个圆圈,同时显示圆心坐标
  2. 使用右键单击删除最后添加的圆圈
  3. 使用双击右键删除所有圆圈

为了实现这些功能,我们首先创建一个名为 circles 的列表,我们在其中维护用户绘制的当前圆圈。此外,我们还使用渲染文本创建备份图像。当产生鼠标事件时,我们从圆圈列表中添加或删除圆圈以及文本。之后,在绘制时,我们只绘制列表中的当前圆圈及其圆心位置文本,而当用户执行右键单击时,最后添加的圆圈将从列表中删除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
python复制代码def draw_circle(event, x, y, flags, param):
global circles
if event == cv2.EVENT_LBUTTONDBLCLK:
# 将圆心坐标添加到列表中
print("event: EVENT_LBUTTONDBLCLK")
circles.append((x, y))

if event == cv2.EVENT_RBUTTONDBLCLK:
# 删除所有圆形
print("event: EVENT_RBUTTONDBLCLK")
circles[:] = []
elif event == cv2.EVENT_RBUTTONDOWN:
# 删除最后添加的圆形
print("event: EVENT_RBUTTONDOWN")
try:
circles.pop()
except (IndexError):
print("no circles to delete")
if event == cv2.EVENT_MOUSEMOVE:
print("event: EVENT_MOUSEMOVE")
if event == cv2.EVENT_LBUTTONUP:
print("event: EVENT_LBUTTONUP")
if event == cv2.EVENT_LBUTTONDOWN:
print("event: EVENT_LBUTTONDOWN")

动态绘制图形和文本

本文转载自: 掘金

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

小谈startup类ConfigureServices方法的

发表于 2021-11-28

「这是我参与11月更文挑战的第8天,活动详情查看:2021最后一次更文挑战」
这个是我在面试中遇到的一道面试题,记录下来分享给大家。
简单说ConfigureServices是配置服务器的DI容器,可以添加一些服务进到依赖注入容器中。具体来说就是把中间件等添加到DI容器中,最后都是添加到IServiceCollection中,比如下面的代码:

1
2
3
4
5
6
7
csharp复制代码 services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryApiResources(Config.GetResource())
.AddInMemoryClients(Config.GetClients())
.AddTestUsers(Config.GetTestUsers())
.AddProfileService<ProfileService>()
.AddResourceOwnerValidator<LoginValidator>();

对于.AddProfileService()来说它已经内置了一个默认实现IProfileService接口的类,默认注入内置的DefaultProfileServer。其实里面的实现是当遇到IProfileService实例化成自定义类ProfileService,而不使用内置的。ASP.NET Core依赖注入在应用程序启动时提供服务。我们可以通过在Startup类的构造方法或Configure方法中包含适当的接口作为参数来请求这些服务。ConfigureServices方法只能接受IServiceCollection参数,但是可以从这个集合中检索任何已注册的服务,因此不需要额外参数。下面由启动方法请求的服务:

位置 服务
构造方法中 IHostingEnvironment,ILogger
ConfigureServices方法中 IServiceCollection
Configure方法中 IApplicationBuilder, IHostingEnvironment, ILoggerFactory

Startup类构造方法或Configure方法可以请求由WebHostBuilde ConfigureServices方法添加的任何服务。使用WebHostBuilder在启动方法中提供需要的任何服务。

本文转载自: 掘金

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

将数字变成 0 的操作次数 LeetCode刷题笔记 二

发表于 2021-11-28

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


相关文章

LeetCode刷题汇总:LeetCode刷题

一、题目描述


将数字变成 0 的操作次数

给你一个非负整数 num ,请你返回将它变成 0 所需要的步数。 如果当前数字是偶数,你需要把它除以 2 ;否则,减去 1 。

二、思路分析


  • 看看题目的示例,我们来理一理这个思路~
  • 示例 1:
1
2
3
4
5
6
7
8
9
ini复制代码输入:num = 14
输出:6
解释:
步骤 1) 14 是偶数,除以 2 得到 7 。
步骤 2) 7 是奇数,减 1 得到 6 。
步骤 3) 6 是偶数,除以 2 得到 3 。
步骤 4) 3 是奇数,减 1 得到 2 。
步骤 5) 2 是偶数,除以 2 得到 1 。
步骤 6) 1 是奇数,减 1 得到 0 。
  • 示例2:
1
2
3
4
5
6
7
ini复制代码输入:num = 8
输出:4
解释:
步骤 1) 8 是偶数,除以 2 得到 4 。
步骤 2) 4 是偶数,除以 2 得到 2 。
步骤 3) 2 是偶数,除以 2 得到 1 。
步骤 4) 1 是奇数,减 1 得到 0 。
  • 示例3:
1
2
ini复制代码输入:num = 123
输出:12
  • 说明
+ 思维定势了我,第一反应就是直接暴力来干就完事儿了!
+ 首先,定义中间数`temp`暂存计算完次数的,每次计算完改变传入的nums的值,一直循环。
+ 里面增加判断奇数偶数即可。
+ 话不多说,直接看代码。

三、AC 代码


  • 暴力破解:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
dart复制代码class Solution {
  public int numberOfSteps(int num) {
      int temp = 0;
      while(num != 0){
          if(num % 2 == 0){
              num /= 2;
          }else{
              num -= 1;
          }
          temp++;
      }
      return temp;
  }
}
  • 执行结果:
  • image-20211128192526308.png
  • 位运算:
+ 都说计算机最快的计算方式是`位运算`
+ 那么我们思路不变,把所有的判断还有计算变成位运算,是不是更`nice`了呀!
+ 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
dart复制代码class Solution {
  public int numberOfSteps(int num) {
      int temp = 0;
      while(num != 0){
          //判断当前数的奇偶性
          if((num & 1) == 0){
              num = num >> 1;
          }else{
              num = num ^ 1;
          }
          temp++;
      }
      return temp;
  }
}
+ 执行结果: + ![image-20211128192948332.png](https://gitee.com/songjianzaina/juejin_p5/raw/master/img/a5c17763675f70fee942e911d9db3d5061d0cd7a1a2d460af7edefb1d98725ac)
  • 实话实说,这题,是LeetCode上第三好解的题目!
  • image-20211128193141985.png

路漫漫其修远兮,吾必将上下求索~

如果你认为i博主写的不错!写作不易,请点赞、关注、评论给博主一个鼓励吧~hahah

本文转载自: 掘金

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

为什么Mysql有时会抖一下?

发表于 2021-11-28

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

一条SQL语句,正常执行的时候特别快,但是有时也不知道怎么回事,它就会变得特别慢,并且这样的场景很难复现,它不只随机,而且持续时间还很短。

SQL语句为什么变“慢”了?

InnoDB在处理更新语句的时候,只做了写日志这一个磁盘操作。这个日志叫作redo log(重做日志),在更新内存写完redo log后,就返回给客户端,本次更新成功。

数据库总要找时间把数据进行下更新(内存里的数据写入磁盘,术语就是flush)。在这个flush操作执行之前,内存中的数据与磁盘中的数据是不一致的。 当内存数据页跟磁盘数据页内容不一致的时候,我们称这个内存页为“脏页”。内存数据写入到磁盘后,内存和磁盘上的数据页的内容就一致了,称为“干净页”。不论是脏页还是干净页,都在内存中。

平时执行很快的更新操作,其实就是在写内存和日志,而MySQL偶尔“抖”一下的那个瞬间,可能就是在刷脏页(flush)。

那么,什么情况会引发数据库的flush过程呢?

  1. InnoDB的redo log写满了。 这时候系统会停止所有更新操作,把checkpoint往前推进,redo log留出空间可以继续写。
  2. 系统内存不足。 当需要新的内存页,而内存不够用的时候,就要淘汰一些数据页,空出内存给别的数据页使用。如果淘汰的是“脏页”,就要先将脏页写到磁盘。
  3. MySQL认为系统“空闲”的时候。 MySQL缝插针地找时间,只要有机会就刷一点“脏页”。
  4. MySQL正常关闭的情况。 这时候,MySQL会把内存的脏页都flush到磁盘上,这样下次MySQL启动的时候,就可以直接从磁盘上读数据,启动速度会很快。

分析一下上面四种场景对性能的影响

第三种情况是属于MySQL空闲时的操作,这时系统没什么压力,而第四种场景是数据库本来就要关闭了。这两种情况下,不会太关注“性能”问题。

第一种是“redo log写满了,要flush脏页”,这种情况是InnoDB要尽量避免的。因为出现这种情况的时候,整个系统就不能再接受更新了,所有的更新都必须堵住。如果从监控上看,这时候更新数会跌为0。

第二种是“内存不够用了,要先将脏页写到磁盘”,这种情况其实是常态。InnoDB用缓冲池(buffer pool)管理内存,缓冲池中的内存页有三种状态:

  • 第一种是,还没有使用的;
  • 第二种是,使用了并且是干净页;
  • 第三种是,使用了并且是脏页。

InnoDB的策略是尽量使用内存,因此对于一个长时间运行的库来说,未被使用的页面很少。

而当要读入的数据页没有在内存的时候,就必须到缓冲池中申请一个数据页。这时候只能把最久不使用的数据页从内存中淘汰掉:如果要淘汰的是一个干净页,就直接释放出来复用;但如果是脏页呢,就必须将脏页先刷到磁盘,变成干净页后才能复用。

所以,刷脏页虽然是常态,但是出现以下这两种情况,都是会明显影响性能的:

  1. 一个查询要淘汰的脏页个数太多,会导致查询的响应时间明显变长;
  2. 日志写满,更新全部堵住,写性能跌为0,这种情况对敏感业务来说,是不能接受的。

所以,InnoDB需要有控制脏页比例的机制,来尽量避免上面的这两种情况。

本文至此结束,希望对你有所帮助!

本文转载自: 掘金

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

SSM整合步骤(超详细) SSM整合(超详细)

发表于 2021-11-28

SSM整合(超详细)

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

我们整合ssm,并实现一个查询数据库里的博客,并展示在页面上的功能。

一、工具

  • idea
  • MySQL 8.0.22
  • Tomcat 9

项目结构:

image.png

二、数据库准备

  1. 创建一个存放博客的数据库表,并插入一些数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sql复制代码create table blog(
id int primary key comment '博客id',
title varchar(100) not null comment '博客标题',
author varchar(30) not null comment '博客作者',
create_time varchar(50) not null comment '创建时间',
views int(30) not null comment '浏览量'
)

insert into blog values(1,'javaWeb教程','黑马程序员',now(),1000)
insert into blog values(2,'安卓软件开发','周世凯,陈小龙',now(),1000)
insert into blog values(3,'数据结构','清华大学出版社',now(),10000)
insert into blog values(4,'人文基础与应用','毛灿月',now(),560)
insert into blog values(5,'java教程','小钱',now(),123456)
insert into blog values(6,'C语言','谭浩强',now(),10000)
insert into blog values(7,'C语言','小毛',now(),10000)

三、基本环境搭建

1. 新建一个Maven项目,并添加web的支持

image.png

image.png

2. 在pom.xml导入我们需要用到的依赖

如果我们的MySQL版本高于8.0,就需要导入高于MySQL以上的版本,否则可能会报Connections could not be acquired from the underlying database!

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
xml复制代码 <dependencies>
<!--数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<!-- 数据库连接池-->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<!-- junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
<!-- Servlet - JSP-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.1</version>
</dependency>

<!--spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>

3. 在pom.xml中添加Maven资源过滤,预防资源无法导出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
xml复制代码 <build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>

4. 建立基本结构和配置框架!

image.png

  • com.mq.controller
  • com.mq.dao
  • com.mq.pojo
  • com.mq.service
  • mybatis-config.xml
1
2
3
4
5
6
7
8
9
xml复制代码<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!-- configuration核心配置文件-->
<configuration>


</configuration>
  • applicationContext.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
xml复制代码<?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:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">

</beans>

四、MyBatis层编写

1. 数据库配置文件 database.properties

注意: 数据库连接可能会碰到:驱动问题、SSL安全访问的问题和时区问题。
MySQL 8.0以上的版本要配置时区,serverTimezone=GMT%2B8

高版本的驱动已经由:"com.mysql.jdbc.Driver"
变为:"com.mysql.cj.jdbc.Driver"

否则会报500错误:Failed to obtain JDBC Connection; nested exception is java.sql.SQLException: Connections could not be acquired from the underlying database!

1
2
3
4
5
java复制代码jdbc.driver=com.mysql.cj.jdbc.Driver
# mysql 8.0+,要配置时区
jdbc.url=jdbc:mysql://localhost:3306/firend_mq?useSSL=false&useUnicode=&characterEncodeing=UTF-8&serverTimezone=GMT%2B8
jdbc.username=root
jdbc.password=root

2. 使用idea连接数据库

image.png

image.png
如果连接失败检查是不是没有配置时区。

3. 编写实体类

在pojo包下创建Blog实体类,使用lombok插件要导入依赖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码package com.mq.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Blog {
private int id;
private String title;
private String author;
private String create_time;
private int views;
}

4. 编写dao层接口和Mapper.xml

如果只是单纯整合,那么不用写接口,我这里为了测试。

  1. 在dao包下创建BlogMapper接口
1
2
3
4
5
6
7
8
9
10
11
java复制代码package com.mq.dao;

import com.mq.pojo.Blog;

import java.util.List;

public interface BlogMapper {

//查询所有的博客
List<Blog> queryAllBLog();
}
  1. 编写BlogMapper对应的mapper.xml文件
1
2
3
4
5
6
7
8
9
10
11
xml复制代码<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace:绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.mq.dao.BlogMapper">
<!--查询全部Blog-->
<select id="queryAllBLog" resultType="Blog">
select * from firend_mq.blog
</select>
</mapper>

5. 编写MyBatis的核心配置文件

我们把配置数据源的步骤交给spring去做。

关于Mybatis的配置详情可以看MyBatis的配置详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
xml复制代码<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!-- configuration核心配置文件-->
<configuration>

<!--配置数据源,交给spring做-->

<!--扫描pojo包下的实体类,并取别名-->
<typeAliases>
<package name="com.mq.pojo"/>
</typeAliases>

<!--注册mapper-->
<mappers>
<mapper class="com.mq.dao.BlogMapper"/>
</mappers>
</configuration>

6. 编写Service层的接口和实现类

接口:

1
2
3
4
5
6
7
8
9
10
java复制代码package com.mq.service;

import com.mq.pojo.Blog;

import java.util.List;

public interface BlogService {
//查询所有的博客
List<Blog> queryAllBLog();
}

实现类:

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
java复制代码package com.mq.service;

import com.mq.dao.BlogMapper;
import com.mq.pojo.Blog;

import java.util.List;

public class BlogServiceImpl implements BlogService{

//调用dao层的操作,设置一个set接口,方便Spring管理
private BlogMapper blogMapper;

public BlogServiceImpl(BlogMapper blogMapper) {
this.blogMapper = blogMapper;
}


@Override
public List<Blog> queryAllBLog() {
return blogMapper.queryAllBLog();
}

public void setBlogMapper(BlogMapper blogMapper) {
}
}

五、Spring层编写

这里我们将spring编写拆成三个配置的编写,每一个相当于整合两种。方便理解。
在这里插入图片描述

1.spring-dao.xml

Spring整合Mybatis的相关的配置文件,主要就是我们以前在mybatis-config.xml需要配置数据源的工作,现在交给spring去做了,以及获取到SqlSessionFactory对象等全部由spring去做。代码有详细注释。

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
xml复制代码<?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:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">

<!-- 1.关联数据库文件 -->
<context:property-placeholder location="classpath:database.properties"/>

<!-- 2.数据库连接池 -->
<!--数据库连接池
dbcp 半自动化操作 不能自动连接
c3p0 自动化操作(自动的加载配置文件 并且设置到对象里面)
-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!-- 配置连接池属性 -->
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!-- c3p0连接池的私有属性 -->
<property name="maxPoolSize" value="30"/>
<property name="minPoolSize" value="10"/>
<!-- 关闭连接后不自动commit -->
<property name="autoCommitOnClose" value="false"/>
<!-- 获取连接超时时间 -->
<property name="checkoutTimeout" value="10000"/>
<!-- 当获取连接失败重试次数 -->
<property name="acquireRetryAttempts" value="2"/>
</bean>

<!-- 3.配置SqlSessionFactory对象 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 注入数据库连接池 -->
<property name="dataSource" ref="dataSource"/>
<!-- 配置MyBaties全局配置文件:mybatis-config.xml -->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>
<!-- 4.配置扫描Dao接口包,动态实现Dao接口注入到spring容器中 -->
<!--解释 :https://www.cnblogs.com/jpfss/p/7799806.html-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 注入sqlSessionFactory -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!-- 给出需要扫描Dao接口包 -->
<property name="basePackage" value="com.mq.dao"/>
</bean>
</beans>

2. spring-service.xml

编写spring Ioc的依赖注入

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
xml复制代码<?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:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">

<!-- 扫描service相关的bean -->
<context:component-scan base-package="com.mq.service" />

<!--BlogServiceImpl注入到IOC容器中-->
<bean id="BlogServiceImpl" class="com.mq.service.BlogServiceImpl">
<property name="blogMapper" ref="blogMapper"/>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据库连接池 -->
<property name="dataSource" ref="dataSource" />
</bean>
</beans>

六、SpringMVC层编写

1. spring-mvc.xml

主要编写注册视图解析器 :InternalResourceViewResolver
注解驱动。

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
xml复制代码<?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:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">

<!-- 注解驱动-->
<mvc:annotation-driven/>
<!--静态资源过滤-->
<mvc:default-servlet-handler/>
<!-- 扫描包-->
<context:component-scan base-package="com.mq.controller"/>

<!--视图解析器-->
<!-- 配置jsp 显示ViewResolver视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>

2. 编写applicationContext.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
xml复制代码<?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:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">

<import resource="spring-mvc.xml"/>
<import resource="spring-dao.xml"/>
<import resource="spring-service.xml"/>
</beans>

3. web.xml

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
xml复制代码<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--DispatcherServlet-->
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!--一定要注意:我们这里加载的是总的配置文件,之前被这里坑了!-->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

<!--encodingFilter过滤器-->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encodeing</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<!--session过期时间-->
<session-config>
<session-timeout>15</session-timeout>
</session-config>
</web-app>

到这里我们的整合就已经结束了!

我们来测试一下

七、测试

1. 在controller新建BlogController类测试

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
java复制代码package com.mq.controller;

import com.mq.pojo.Blog;
import com.mq.service.BlogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.List;

@Controller
@RequestMapping("/blog")
public class BlogController {

@Autowired
@Qualifier("BlogServiceImpl")
private BlogService service;

@RequestMapping("/allblog")
public String alllist(Model model){
List<Blog> blogs = service.queryAllBLog();
model.addAttribute("blog",blogs);
return "allblog";
}
}

2. 编写视图层

index.jsp

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
jsp复制代码<%--
Created by IntelliJ IDEA.
User: hp
Date: 2021/5/21
Time: 13:18
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
<style type="text/css">
a {
text-decoration: none;
color: black;
font-size: 18px;
}
h3 {
width: 180px;
height: 38px;
margin: 100px auto;
text-align: center;
line-height: 38px;
background: deepskyblue;
border-radius: 4px;
}
</style>
</head>
<body>
<h3>
<a href="${pageContext.request.contextPath}/blog/allblog">进入博客页面</a>
</h3>
</body>
</html>

allblog.jsp

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
jsp复制代码<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%--
Created by IntelliJ IDEA.
User: hp
Date: 2021/5/21
Time: 14:28
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<!-- 引入 Bootstrap -->
<link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">

<title>博客展示</title>
</head>
<body>
<div class="container">
<div class="row clearfix">
<div class="col-md-12 column">
<div class="page-header">
<h1>
<small>博客列表 </small>
</h1>
</div>
</div>
</div>

<div class="row clearfix">
<div class="col-md-12 column">
<table class="table table-hover table-striped">
<thead>
<tr>
<th>博客编号</th>
<th>博客标题</th>
<th>博客作者</th>
<th>博客创建时间</th>
<th>博客浏览量</th>
</tr>
</thead>
<tbody>
<c:forEach var="blog" items="${blog}">
<tr>
<td>${blog.id}</td>
<td>${blog.title}</td>
<td>${blog.author}</td>
<td>${blog.create_time}</td>
<td>${blog.views}</td>
</tr>
</c:forEach>
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>
  1. 运行结果:

image.png

image.png

到这里我们的整合并且测试已经完成了!

完美收工!

写在最后

如果有误,欢迎大佬指出,不胜感激。

💌 低级的欲望通过放纵就可获得,高级的欲望通过困自律方可获得,顶级的欲望通过煎熬才可获得

一个心怀浪漫宇宙,也珍惜人间日常的码农

6ed39a100413ca58ab65d70d5165d65.jpg

本文转载自: 掘金

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

Excel神办公—【三】使用EasyExce指定写入列&复杂

发表于 2021-11-28

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

hello,你好呀,我是灰小猿!一个超会写bug的程序猿!

前两篇文章和大家分享了有关使用easyexcel技术实现eaxcel简单读写的操作,总结一句话就是“简直不要太简洁!”.

所以今天这一篇文章,我就继续和大家分享一些有关于使用easyexcel进行数据导出时的操作。体会easyexcel的强大魅力。\

导入所需依赖

在pom文件中导入easyexcel所需的依赖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
xml复制代码        <!--poi依赖03版本-->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>3.17</version>
        </dependency>
        <!--poi依赖07版本-->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>3.17</version>
        </dependency>
        <!--easyexcel依赖-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>2.2.6</version>
        </dependency>

一、根据参数只导出指定列

根据参数只导出指定列的作用就是,如果我们的数据对象中的元素属性比较多,但是在导出到excel的时候,我们又不想要导出全部属性列,那么我们就可以指定只导出哪些列,或者指定哪些列不导出。数据对象如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
less复制代码/**
 * 基本数据demodata
 */
@Data
public class DemoData {
    @ExcelProperty(value = "字符串标题")
    private String stringTitle;
    @ExcelProperty(value = "时间标题")
    private Date dateTitle;
    @ColumnWidth(50)
    @ExcelProperty(value = "数字标题")
    private int doubleTitle;
}

指定不包含哪些列

指定在导出时不包含哪些列其实是比较简单的,我们只需要写入一个set集合,在其中写入不包含的属性名。之后将这个set集合作为参数传入即可。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
arduino复制代码    /**
     * 根据参数在导出时不包括指定列
     */
    public void excludeColumnWrite() {
        log.info("根据参数在导出时不包括指定列~~~");
//        存储不包含的那些列的列名属性
        Set<String> excludeColumnNames = new HashSet<String>();
        excludeColumnNames.add("dateTitle");

        EasyExcel.write(FILEPATH + "testExcel_1.xlsx", DemoData.class)
//                指定在导出时不包括哪些列
                .excludeColumnFiledNames(excludeColumnNames)
                .sheet("测试表1")
                .doWrite(demoData);

        log.info("导出成功~~~");
    }

效果如下:

指定只包括哪些列

指定只包括哪些列的操作和指定不包括哪些列的操作一样,我们需要给includeColumnFiledNames()中传入需要指定写入的列的set集合即可。 具体操作看下面的实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
arduino复制代码    /**
     * 根据参数在导出时只包含指定列
     */
    public void includeColumnWrite() {
        log.info("根据参数在导出时只包含指定列~~~");
//        存储只包含的那些列的列名属性
        Set<String> includeColumnNames = new HashSet<String>();
        includeColumnNames.add("dateTitle");
        
//        数据写入excel
        EasyExcel.write(FILEPATH + "testExcel_1.xlsx", DemoData.class)
//                指定在导出时只包括哪些列
                .includeColumnFiledNames(includeColumnNames)
                .sheet("测试表1")
                .doWrite(demoData);

        log.info("导出成功~~~");
    }

效果如下:

二、复杂头写入&合并表头

通常我们在写入Excel的时候会遇到的一种情况就是需要对部分表头列进行合并,那么这个时候就需要用到复杂头的写入。在easyexcel中对于复杂头的写入,可以直接使用注解的形式。

例如我们要将“字符串标题”,“时间标题”,“数字标题”在上方合并成“主标题”,

那么我们只需要在数据对象的@ExcelProperty注解中指定即可。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
kotlin复制代码/**
 * 复杂头写入,
 * 比如主标题、合并单元格等操作
 */
public class ComplexHeadData {
    @ExcelProperty({"主标题","字符串标题"})
    private String stringData;
    @ExcelProperty({"主标题","时间标题"})
    private Date date;
    @ExcelProperty({"主标题","数字标题"})
    private double doubleData;
}

测试实例:

1
2
3
4
5
6
7
8
csharp复制代码    /**
     * 复杂头写入
     */
    public void complexHeadWrite() {
        EasyExcel.write(FILEPATH + "testExcel_w1.xlsx", ComplexHeadData.class)
                .sheet("测试1")
                .doWrite(demoData);
    }

效果如下:

以上就是使用easyexcel技术来操作Excel都两种基本操作,之后我们在写入数据到Excel的时候就会,使用注解的方式就会更加便捷。

觉得不错记得点赞关注哟!之后继续和大家分享easyexcel的实用小技巧。

我是灰小猿,我们下期见!

本文转载自: 掘金

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

剑指offer 08:二叉树的下一个节点

发表于 2021-11-28

一、问题

给定一个二叉树和其中的一个节点,如何找出中序遍历序列的下一个节点?树中的节点除了有两个分别指向左右子节点的指针,还有一个指向父节点的指针。

示例

输入: inorder = [9,3,15,20,7], node = 9

输出: 20

二、解法

解法一

思路:中序遍历的栈实现

  • 首先通过遍历当前节点的父节点找出根节点
  • 然后中序遍历找出node节点的下一个节点

具体实现: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
java复制代码/**
* 中序遍历的下一个节点
* @param nodeFa
* @return
*/
public static TreeNodeFa nextNode1(TreeNodeFa nodeFa){
TreeNodeFa p = nodeFa;
//找到根节点
while(p.father != null){
p = p.father;
}
//中序遍历,找到下一个节点返回
Stack<TreeNodeFa> stack = new Stack<>();
TreeNodeFa pre = null;
while(p != null || !stack.isEmpty()){
if (p != null){
stack.push(p);
p = p.left;
} else {
p = stack.pop();
if (pre != null){
return p;
}
if (p == nodeFa){
pre = p;
}
p = p.right;
}
}
return null;
}

解法二

思路:分情况讨论

  • 情况一:如果当前节点有右子树,那么中序遍历的下一个节点就是右子树的最左节点
  • 情况二:如果当前节点无右子树,并且当前节点是其父节点的左子树,那么中序遍历的下一个节点就是父节点。
  • 情况三:如果当前节点无右子树,并且当前节点是其父节点的右子树,那么找到其第一个祖先节点P(P是其父节点的左子树)。那么P的父节点就是其下一个节点。

具体实现: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
java复制代码/**
* 中序遍历的下一个节点
* @param nodeFa
* @return
*/
public static TreeNodeFa nextNode(TreeNodeFa nodeFa){
if (null == nodeFa) {
return null;
}
//如果存在右节点
if (nodeFa.right != null) {
TreeNodeFa p = nodeFa.right;
while (p.left != null){
p = p.left;
}
return p;
}
//如果右节点为null
TreeNodeFa p = nodeFa.father;
//如果是他父节点的左节点
if (p.left == nodeFa){
return p;
} else {
//找到祖先节点是其父节点左子树的节点
while(p.father != null) {
if (p.father.left == p){
break;
}
p = p.father;
}
if (p.father != null) {
p = p.father;
return p;
}
}
return null;
}

三、思考

如果是前序和后续遍历的下一个节点呢?怎么实现?

本文转载自: 掘金

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

Java中方法覆盖与多态

发表于 2021-11-28

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

怎么进行方法覆盖

理解方法覆盖之前,我们回顾一下方法重载(Overload),什么情况下考虑使用方法的重载呢?带着同样的疑问,怎么进行方法的覆盖呢?请看如下代码进行体会:

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
typescript复制代码public class People{
private String name;
public String getName(){
return name;
}
public void setName(String name){
this.name=name;
}
public void speakHi(){
System.out.println(this.name+"和别人打招呼!");
}
}

public class ChinaPeople extends People{
public void speakHi(){
System.out.println("你好,我叫"+this.getName()+",很高兴认识你!");
}
}

public class AmericaPeople extends People{
public void speakHi(){
System.out.println("Hi,My name is"+this.getName()+"Nice to meet you!");
}
}

public class PeopleTest{
public static void main(String[] args){
ChinaPeople cp=new ChinaPeople();
cp.setName("张三");
cp.speakHi();
AmericaPeople ap=new AmericaPeople();
ap.setName("jackson");
ap.speakHi();
}
}

//程序运行结果如下:
你好,我叫张三,很高兴认识你!
Hi,My name is jackson,Nice to meet you!

通过以上的代码学习,我们了解到只有当从父类中继承过来的方法无法满足当前子类业务需求的时候,需要将父类中继承过来的方法进行覆盖。换句话说,父类中继承过来的方法已经不够用了,子类有必要将这个方法重新再写一遍,所以方法覆盖又称为重写。当该方法被重写之后,子类对象一定会调用重写之后的方法。

方法覆盖的条件

那么,当程序具备哪些条件的时候,就能构成方法覆盖呢?

  • 1,方法覆盖发生在具有继承关系的父子类之间,这是首要条件;
  • 2,覆盖之后的方法与原方法具有相同的返回值类型、相同的方法名、相同的形式参数列表;

方法覆盖的注意事项

另外,在使用方法覆盖的时候,需要有哪些注意事项呢?

  • 1,由于覆盖之后的方法与原方法一模一样,建议在开发的时候采用复制粘贴的方式,不建 议手写,因为手写的时候非常容易出错,比如在 Object 类当中有 toString()方法,该方法中的 S 是大写的,在手写的时候很容易写成小写 tostring(),这个时候你会认为 toString()方法已经 被覆盖了,但由于方法名不一致,导致最终没有覆盖,这样就尴尬了;
  • 2,私有的方法不能被继承,所以不能被覆盖;
  • 3,构造方法不能被继承,所以也不能被覆盖;
  • 4,覆盖之后的方法不能比原方法拥有更低的访问权限,可以更高(学习了访问控制权限修 饰符之后你就明白了);
  • 5,覆盖之后的方法不能比原方法抛出更多的异常,可以相同或更少(学习了异常之后就明 白了);
  • 6,方法覆盖只是和方法有关,和属性无关;
  • 7,静态方法不存在覆盖(不是静态方法不能覆盖,是静态方法覆盖意义不大,学习了多态 机制之后就明白了);

总结

当父类继承过来的方法无法满足当前子类业务需求的时候,子类有必要将父类中继承过来的方法进行覆盖/重写。方法覆盖发生在具有继承关系的父子类之间,方法覆盖的时候要求相同的返回值类型、相同的方法名、相同的形式参数列表。方法覆盖之后子类对象在调用的时候一定会执行覆盖之后的方法。

多态基础语法

多态(Polymorphism)属于面向对象三大特征之一,它的前提是封装形成独立体,独立体之间存在继承关系,从而产生多态机制。多态是同一个行为具有多个不同表现形式或形态的能力。

在Java 中允许这样的两种语法出现,一种是向上转型(Upcasting),一种是向下转型(Downcasting),向上转型是指子类型转换为父类型,又称为自动类型转换,向下转型是指父类型转换为子类型,又称为强制类型转换。

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
csharp复制代码public class Animal{
public void move(){
System.out.println("Animal move!");
}
}

public class Cat extends Animal{
//方法覆盖
public void move(){
System.out.println("走猫步!");
}
//子类特有
public void catchMouse(){
System.out.println("抓老鼠!");
}
}

public class Bird extends Animal{
//方法覆盖
public void move(){
System.out.println("鸟儿在飞翔!");
}
//子类特有
public void sing(){
System.out.println("鸟儿在唱歌!");
}
}

public class Test01{
public static void main(String[] args){
//创建Animal对象
Animal a=new Animal();
a.move();
//创建Cat对象
Cat c=new Cat();
c.move();
//创建鸟儿对象
Bird b=new Bird();
b.move;
}
}

//运行结果如下:
Animal move!
走猫步!
鸟儿在飞翔!

以上程序演示的就是多态,多态就是“同一行为(move)”作用在“不同的对象上”会有不同的表现结果。

多态与覆盖的联系

其实多态有三个必要的条件分别是:

  • 1,继承
  • 2,方法覆盖
  • 3,父类型引用指向子类型对象

多态显然是离不开覆盖机制的,多态就是因为编译阶段绑定父类当中的方法,程序运行阶段自动调用子类对象上的方法,如果子类对象上的方法没有进行重写,这个时候创建子类对象就没有意义了,自然多态也就没有了意义,只有子类将方法重写之后调用到子类对象上的方法产生不同效果时,多态就形成了。

多态在开发中的作用

以上我们知道了多态的基础语法,多态在实际开发中有什么作用呢?

多态在开发中联合方法覆盖一起使用,可以降低程序的耦合度, 提高程序的扩展力。 在开发中尽可能面向抽象编程,不要面向具体编程,好比电脑主板和内存条的关系一样,主板和 内存条件之间有一个抽象的符合某个规范的插槽,不同品牌的内存条都可以插到主板上使用,2个G 的内存条和4 个G 的内存条都可以插上,但最终的表现结果是不同的,2 个G的内存条处理速度慢一些, 4个G的快一些,这就是多态,所谓多态就是同一个行为作用到不同的对象上,最终的表现结果是不同的, 主要的要求就是对象是可以进行灵活切换的,灵活切換的前提就是解耦合,解耦合依赖多态机制。

这里只能做一个笼统的概述,具体还得我们在代码中体会。

本文转载自: 掘金

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

1…135136137…956

开发者博客

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