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

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


  • 首页

  • 归档

  • 搜索

Java代理模式之Java服务定位器模式 Java服务定位器

发表于 2021-11-28

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

Java服务定位器模式

服务定位器模式(Service Locator Pattern)用在我们想使用 JNDI 查询定位各种服务的时候。考虑到为某个服务查找 JNDI 的代价很高,服务定位器模式充分利用了缓存技术。在首次请求某个服务时,服务定位器在 JNDI 中查找服务,并缓存该服务对象。当再次请求相同的服务时,服务定位器会在它的缓存中查找,这样可以在很大程度上提高应用程序的性能。以下是这种设计模式的实体。

• 服务(Service) - 实际处理请求的服务。对这种服务的引用可以在 JNDI 服务器中查找到。

• Context / 初始的 Context - JNDI Context 带有对要查找的服务的引用。

• 服务定位器(Service Locator) - 服务定位器是通过 JNDI 查找和缓存服务来获取服务的单点接触。

• 缓存(Cache) - 缓存存储服务的引用,以便复用它们。

• 客户端(Client) - Client 是通过 ServiceLocator 调用服务的对象。

实现

我们将创建 ServiceLocator、InitialContext、Cache、Service 作为表示实体的各种对象。Service1 和 Service2 表示实体服务。

ServiceLocatorPatternDemo,我们的演示类在这里是作为一个客户端,将使用 ServiceLocator 来演示服务定位器设计模式。

步骤 1

创建服务接口 Service。

1
2
3
4
csharp复制代码public interface Service {
public String getName();
public void execute();
}

步骤 2

创建实体服务。

1
2
3
4
5
6
7
8
9
10
typescript复制代码public class Service1 implements Service {
public void execute(){
System.out.println("Executing Service1");
}

@Override
public String getName() {
return "Service1";
}
}
1
2
3
4
5
6
7
8
9
10
typescript复制代码public class Service2 implements Service {
public void execute(){
System.out.println("Executing Service2");
}

@Override
public String getName() {
return "Service2";
}
}

步骤 3

为 JNDI 查询创建 InitialContext。

1
2
3
4
5
6
7
8
9
10
11
12
kotlin复制代码public class InitialContext {
public Object lookup(String jndiName){
if(jndiName.equalsIgnoreCase("SERVICE1")){
System.out.println("Looking up and creating a new Service1 object");
return new Service1();
}else if (jndiName.equalsIgnoreCase("SERVICE2")){
System.out.println("Looking up and creating a new Service2 object");
return new Service2();
}
return null;
}
}

步骤 4

创建缓存 Cache。

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
java复制代码import java.util.ArrayList;
import java.util.List;

public class Cache {

private List services;

public Cache(){
services = new ArrayList();
}

public Service getService(String serviceName){
for (Service service : services) {
if(service.getName().equalsIgnoreCase(serviceName)){
System.out.println("Returning cached "+serviceName+" object");
return service;
}
}
return null;
}

public void addService(Service newService){
boolean exists = false;
for (Service service : services) {
if(service.getName().equalsIgnoreCase(newService.getName())){
exists = true;
}
}
if(!exists){
services.add(newService);
}
}
}

步骤 5

创建服务定位器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
ini复制代码public class ServiceLocator {
private static Cache cache;

static {
cache = new Cache();
}

public static Service getService(String jndiName){

Service service = cache.getService(jndiName);

if(service != null){
return service;
}

InitialContext context = new InitialContext();
Service service1 = (Service)context.lookup(jndiName);
cache.addService(service1);
return service1;
}
}

步骤 6

使用 ServiceLocator 来演示服务定位器设计模式。

1
2
3
4
5
6
7
8
9
10
11
12
ini复制代码public class ServiceLocatorPatternDemo {
public static void main(String[] args) {
Service service = ServiceLocator.getService("Service1");
service.execute();
service = ServiceLocator.getService("Service2");
service.execute();
service = ServiceLocator.getService("Service1");
service.execute();
service = ServiceLocator.getService("Service2");
service.execute();
}
}

步骤 7

执行程序,输出结果:

1
2
3
4
5
6
7
8
csharp复制代码Looking up and creating a new Service1 object
Executing Service1
Looking up and creating a new Service2 object
Executing Service2
Returning cached Service1 object
Executing Service1
Returning cached Service2 object
Executing Service2

本文转载自: 掘金

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

(实用)linux定时任务(crontab) 结束语

发表于 2021-11-28

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

简介

我们可能会对linux进行简单的监控或者定期执行一些操作,因此会用到linux自带的定时任务功能.

语法

man

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
python复制代码Usage:
crontab [options] file
crontab [options]
crontab -n [hostname]

Options:
-u <user> define user
-e edit user's crontab
-l list user's crontab
-r delete user's crontab
-i prompt before deleting
-n <host> set host in cluster to run users' crontabs
-c get host in cluster to run users' crontabs
-s selinux context
-x <mask> enable debugging

1. 查看定时任务

1)查看当前用户的

1
复制代码crontab -l

如果该服务器被挖矿,他很可能也同时执行了定时任务,用此命令可以看到挖矿的脚本

2)查看指定用户的

1
arduino复制代码crontab -l  -u zzz       # zzz为系统存在的一个用户

查看指定用户(zzz)正在执行的任务 ,只需要添加-u参数即可,其他命令也同理

3)查看系统所有用户的

1
bash复制代码cat /etc/passwd |cut -f 1 -d : |xargs -I {} crontab -l -u {}

2. 删除当前的定时任务

1
复制代码crontab -r

3.新增或编辑定时任务

1
复制代码crontab -e

进入编辑模式, 按照指定任务命令格式编辑命令, 格式参照下面的任务命令格式

4.查看支持的环境变量

1
2
3
4
5
ruby复制代码cat /etc/crontab

SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root #发送邮件到账号

任务命令格式

任务命令分为两个部分: 第一部分是cron表达是, 后面跟着sh命令

例子

1
2
3
bash复制代码* * * * *  /root/date.sh   # 每分钟执行一次date.sh脚本文件
0 * * * * echo '-------------' >> /root/date.txt # 每小时0分的时候向date.txt文件中加入一句话
* 1 * * 0 rm -rf /root/date1 #每个星期的星期天的1点左右删除date1文件

cron表达式的语法规则

1
2
3
4
5
6
7
8
scss复制代码*    *    *    *    *
- - - - -
| | | | |
| | | | +----- 星期中星期几 (0 - 6) (星期天 为0)
| | | +---------- 月份 (1 - 12)
| | +--------------- 一个月中的第几天 (1 - 31)
| +-------------------- 小时 (0 - 23)
+------------------------- 分钟 (0 - 59)

sh命令语法规则

单行的可执行的sh命令, 可以是运行shell脚本的命令,也可以是直接的sh命令

其他

1) 任务是否执行查询

在/var/log/目录下有cron开头的文件, 里面包含了crontab的执行记录

1
2
3
4
5
javascript复制代码Feb 19 10:19:01 zdc CROND[8314]: (root) CMD (/root/date.sh)
Feb 19 10:20:01 zdc CROND[8390]: (root) CMD (/root/date.sh)
Feb 19 10:21:01 zdc CROND[8440]: (root) CMD (/root/date.sh)
Feb 19 10:22:01 zdc CROND[8487]: (root) CMD (/root/date.sh)
Feb 19 10:23:01 zdc CROND[8544]: (root) CMD (/root/date.sh)

推荐将任务写成脚本, 在其中添加执行成功失败之类的输出逻辑,自定义输出到指定文件中 如

1
2
bash复制代码...业务逻辑
echo "成功" >> /root/date.info

2) 任务是否出错判断

当任务执行过程中报错后,会给当前用户发送邮件,默认情况下在/var/spool/mail 中的对应用户文件下

1
bash复制代码您在 /var/spool/mail/root 中有新邮件
1
2
3
4
5
6
7
8
9
10
11
12
13
ini复制代码...
X-Cron-Env: <XDG_SESSION_ID=4303>
X-Cron-Env: <XDG_RUNTIME_DIR=/run/user/0>
X-Cron-Env: <LANG=zh_CN.UTF-8>
X-Cron-Env: <SHELL=/bin/sh>
X-Cron-Env: <HOME=/root>
X-Cron-Env: <PATH=/usr/bin:/bin>
X-Cron-Env: <LOGNAME=root>
X-Cron-Env: <USER=root>
Message-Id: <20210222015652.48902109F2CC@mail.qq.com>
Date: Fri, 19 Feb 2021 10:31:02 +0800 (CST)

/root/date.sh:行3: success: 未找到命令

crontab发送邮件

发送到外部邮箱,以qq邮箱为例

在cat /etc/crontab 将MAILTO=root 修改为你需要的外部邮箱账号

且在crontab -e 的第一行添加MAILTO=外部邮箱账号

并且配合linux发送邮件文章使用

结束语

尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

如果您喜欢我的文章,可以[关注]+[点赞]+[评论],您的三连是我前进的动力,期待与您共同成长~

1
2
3
4
arduino复制代码    作者:ZOUZDC
链接:https://juejin.cn/post/7028963866063306760
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

本文转载自: 掘金

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

什么是Java队列?实例讲解队列的应用 Java队列

发表于 2021-11-28

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

Java队列

队列是只能在其上执行操作的对象的集合两端的队列。

队列有两个末端,称为头和尾。

在简单队列中,对象被添加到尾部并从头部删除并首先删除首先添加的对象。

Java Collections Framework支持以下类型的队列。

• 简单的队列允许在尾部插入和从头部移除。

• 优先级队列为每个元素分配优先级,并允许从队列中删除具有最高优先级的元素。

• 延迟队列向每个元素添加延迟,并仅在其延迟已过去时删除该元素。

• 双端队列允许其元件从头部和尾部插入和移除。

• 阻塞队列阻塞线程,当线程已满时向其添加元素,当线程为空时,它阻止线程从中删除元素。

• 传输队列是阻塞队列,其中对象的切换发生在生产者线程和消费者线程之间。

• 阻塞双端队列是双端队列和阻塞队列的组合。

队列的简介

• 队列可以定义为有序列表,它允许在一端执行插入操作,称为REAR,删除操作在另一端执行,称为FRONT。

• 队列被称为先进先出列表。

• 例如,排队等候铁路车票的人队列。

队列的应用

由于队列以先进先出的方式执行操作,这对于操作的排序是相当公平的。 队列的各种应用如下所述。

• 队列被广泛用作单个共享资源(如打印机,磁盘,CPU)的等待列表。

• 队列用于异步数据传输(例如,数据不以两个进程之间的相同速率传输)。 管道,文件IO,套接字。

• 队列在大多数应用程序中用作缓冲区,如MP3媒体播放器,CD播放器等。

• 队列用于维护媒体播放器中的播放列表,以便添加和删除播放列表中的歌曲。

• 队列在操作系统中用于处理中断。

时间复杂性

时间复杂性 访问 搜索 插入 删除
平均情况 θ(n) θ(n) θ(1) θ(1)
最坏情况 θ(n) θ(n) θ(1) θ(1)

简单队列

简单队列由 Queue 接口的实例表示。

队列允许您执行三个基本操作:

• 从尾部添加元素

• 从其头部移除元素

• 在元素顶部审查

Queue接口为三个操作中的每一个定义了两个方法。如果操作不可能,一个方法抛出异常,另一个方法方法返回false或null以指示失败。

方法 描述
boolean add(E e) 如果可能,向队列中添加一个元素。否则,它抛出异常。
boolean offer(E e) 如果不能添加元素,则将元素添加到队列中,而不抛出异常。 它在失败时返回false,在成功时返回true。
E remove() 删除队列的头。如果队列为空,它会抛出异常。此方法返回已移除的项目。
E poll() 从队列中删除元素。如果队列为空而不是抛出异常,则返回null。
Eelement() 偷看队列的头,而不从队列中删除它。 如果队列为空,它会抛出异常。
E peek() 查看队列,如果队列为空而不是抛出异常,则返回null。

LinkedList和PriorityQueue是Queue接口的两个实现类。LinkedList还实现了List接口。

例子

以下代码显示如何将链表用作FIFO队列。

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
arduino复制代码import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.Queue;

public class Main {
public static void main(String[] args) {
Queue<String> queue = new LinkedList<>();
queue.add("Java");
// offer() will work the same as add()
queue.offer("SQL");
queue.offer("CSS");
queue.offer("XML");

System.out.println("Queue: " + queue);

// Let"s remove elements until the queue is empty
while (queue.peek() != null) {
System.out.println("Head Element: " + queue.peek());
queue.remove();
System.out.println("Removed one element from Queue");
System.out.println("Queue: " + queue);
}
System.out.println("queue.isEmpty(): " + queue.isEmpty());
System.out.println("queue.peek(): " + queue.peek());
System.out.println("queue.poll(): " + queue.poll());
try {
String str = queue.element();
System.out.println("queue.element(): " + str);
str = queue.remove();
System.out.println("queue.remove(): " + str);
} catch (NoSuchElementException e) {
System.out.println("queue.remove(): Queue is empty.");
}
}
}

上面的代码生成以下结果。

本文转载自: 掘金

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

oracle事务

发表于 2021-11-28

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

事务是什么

事务在数据库中是工作的逻辑单元,单个事务是由一个或多个完成一组的相关行为的SQL语句组成,通过事务机制,可以确保这一组SQL语句所作的操作要么都成功执行,完成整个工作单元操作,要么一个也不执行。

事务特性

原子性(Atomicity)、一致性(consistency)、隔离性(Isolation)和持久性(Durability)

**原子性(Atomicity):**一个事务里面所有包含的SQL语句都是一个整体,是不可分割的,要么不做,要么都做。
**一致性(Consistency):**事务开始时,数据库中的数据是一致的,事务结束时,数据库的数据也应该是一致的。
**隔离性(Isolation):**数据库允许多个并发事务同时对其中的数据进行读写和修改的能力,隔离性可以防止事务在并发执行时,由于他们的操作命令交叉执行而导致的数据不一致状态。
持久性 (Durability) : 当事务结束后,它对数据库中的影响是永久的,即便系统遇到故障的情况下,数据也不会丢失。

Oracle 事务隔离级别

隔离级别 描述
已提交读取 Oracle 默认使用的事务隔离级别。事务内执行的查询只能看到查询执行前(而非事务开始前)就已经提交的数据。Oracle 的查询永远不会读取脏数据(未提交的数据)。
串行化 串行化隔离的事务只能看到事务执行前就已经提交的数据,以及事务内 INSERT , UPDATE ,及 DELETE 语句对数据的修改。串行化隔离的事务不会出现不可重复读取或不存在读取的现象。
只读模式 只读事务只能看到事务执行前就已经提交的数据,且事务中不能执行 INSERT , UPDATE ,及 DELETE 语句。

使用以下语句设定事务的隔离级别:

已提交读模式:SET TRANSACTION ISOLATION LEVEL=READ COMMITTED;

串行模式:SET TRANSACTION ISOLATION LEVEL= SERIALIZABLE;

只读模式:SET TRANSACTION= READ ONLY;

COMMIT语句(提交事务)

COMMIT 语句可以用来提交当前事务的所有更改

1
ini复制代码COMMIT [ WORK ] [ COMMENT clause ] [ WRITE clause ] [ FORCE clause ];

参数

  • WORK:可选的。它被 Oracle 添加为符合 SQL 标准。使用或不使用 WORK 参数来执行 COMMIT 将产生相同的结果。
  • COMMENT clause:可选的。 它用于指定与当前事务关联的注释。 该注释最多可以包含在单引号中的 255 个字节的文本中。 如果出现问题,它将与事务ID一起存储在名为 DBA_2PC_PENDING 的系统视图中。
  • WRITE clause:可选的。 它用于指定将已提交事务的重做信息写入重做日志的优先级。 用这个子句,有两个参数可以指定:
    • WAIT 或 NOWAIT (如果省略,WAIT是默认值)
    • IMMEDIATE 或 BATCH(IMMEDIATE是省略时的默认值)
  • FORCE clause:可选的。 它用于强制提交可能已损坏或有疑问的事务。 有了这个子句,可以用3种方式指定FORCE:
    • FORCE’string’,[integer]或FORCE CORRUPT_XID’string’ 或 FORCE CORRUPT_XID_ALL

ROLLBACK语句(回滚事务)

ROLLBACK 语句可以用来撤销当前事务或有问题的事务

1
ini复制代码ROLLBACK [ WORK ] [ TO [SAVEPOINT] savepoint_name  | FORCE 'string' ];

参数

  • WORK:可选的。 它被 Oracle 添加为符合 SQL 标准。 使用或不使用 WORK 参数来发出 ROLLBACK 会导致相同的结果。
  • TO SAVEPOINT savepoint_name:可选的。 ROLLBACK语句撤消当前会话的所有更改,直到由 savepoint_name 指定的保存点。 如果省略该子句,则所有更改都将被撤消。
  • FORCE ‘string’:可选的。它用于强制回滚可能已损坏或有问题的事务。 使用此子句,可以将单引号中的事务ID指定为字符串。 可以在系统视图中找到名为 DBA_2PC_PENDING 的事务标识。
  • 必须拥有 DBA 权限才能访问系统视图:DBA_2PC_PENDING 和 V$CORRUPT_XID_LIST。

LOCK TABLE语句(锁表)

LOCK TABLE 语句可以用来锁定表、表分区或表子分区

1
css复制代码LOCK TABLE tables IN lock_mode MODE [ WAIT [, integer] | NOWAIT ];

参数

  • tables:用逗号分隔的表格列表。
  • lock_mode:它是以下值之一:
lock_mode 描述
ROW SHARE 允许同时访问表,但阻止用户锁定整个表以进行独占访问。
ROW EXCLUSIVE 允许对表进行并发访问,但阻止用户以独占访问方式锁定整个表并以共享方式锁定表。
SHARE UPDATE 允许同时访问表,但阻止用户锁定整个表以进行独占访问。
SHARE 允许并发查询,但用户无法更新锁定的表。
SHARE ROW EXCLUSIVE 用户可以查看表中的记录,但是无法更新表或锁定SHARE表中的表。
EXCLUSIVE 允许查询锁定的表格,但不能进行其他活动。
  • WAIT:它指定数据库将等待(达到指定整数的特定秒数)以获取 DML 锁定。
  • NOWAIT:它指定数据库不应该等待释放锁。

本文转载自: 掘金

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

Java 关系运算符与示例(上)

发表于 2021-11-28

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

🌊 作者主页:海拥

🌊 作者简介:🏆CSDN全栈领域优质创作者、🥇HDZ核心组成员

🌊 粉丝福利:往期获奖记录 每周送六本书,不定期送各种小礼品

运算符构成了任何编程语言的基本构建块。Java 也提供了许多类型的运算符,可以根据需要使用它们来执行各种计算和函数,包括逻辑、算术、关系等。它们根据它们提供的功能进行分类。

运算符类型:

  1. 算术运算符
  2. 一元运算符
  3. 赋值运算符
  4. 关系运算符
  5. 逻辑运算符
  6. 三元运算符
  7. 按位运算符
  8. 移位运算符

关系运算符是一组二元运算符,用于检查两个操作数之间的关系,包括相等、大于、小于等。它们在比较后返回一个布尔结果,并广泛用于循环语句和条件 if- else 语句等等。表示关系运算符的一般格式为:

语法:

1
java复制代码变量 1  关系_运算符  变量 2

让我们看看 Java 中的每一个关系运算符:

运算符 1:“等于”运算符 (==)

该运算符用于检查两个给定的操作数是否相等。如果左侧的操作数等于右侧的操作数,则运算符返回真,否则返回假。

语法:

1
java复制代码变量 1 == 变量 2

例证:

1
2
3
4
java复制代码var1 = "haiyong" 
var2 = 20

var1 == var2 结果为假

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
java复制代码import java.io.*;
class GFG {

// 主要驱动方式
public static void main(String[] args)
{
// 初始化变量
int var1 = 5, var2 = 10, var3 = 5;

// 显示 var1、var2、var3
System.out.println("Var1 = " + var1);
System.out.println("Var2 = " + var2);
System.out.println("Var3 = " + var3);

// 比较 var1 和 var2 并打印相应的布尔值
System.out.println("var1 == var2: "
+ (var1 == var2));

// 比较 var1 和 var3 并打印相应的布尔值
System.out.println("var1 == var3: "
+ (var1 == var3));
}
}

输出

1
2
3
4
5
java复制代码Var1 = 5 
Var2 = 10
Var3 = 5
var1 == var2: false
var1 == var3: true

运算符 2:“不等于”运算符(!=)

该运算符用于检查两个给定的操作数是否相等。它的功能与等于运算符的功能相反。如果左侧的操作数不等于右侧的操作数,则返回真,否则返回假。

语法:

1
java复制代码变量 1 != 变量 2

例证:

1
2
3
4
java复制代码var1 = "haiyong" 
var2 = 20

var1 != var2 结果为真

例子

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
java复制代码import java.io.*;
class GFG {

// 主要驱动方法
public static void main(String[] args)
{
// 初始化变量
int var1 = 5, var2 = 10, var3 = 5;

// 显示 var1、var2、var3
System.out.println("Var1 = " + var1);
System.out.println("Var2 = " + var2);
System.out.println("Var3 = " + var3);

// 比较 var1 和 var2 并打印相应的布尔值
System.out.println("var1 == var2: "
+ (var1 != var2));

// 比较 var1 和 var3 并打印相应的布尔值
System.out.println("var1 == var3: "
+ (var1 != var3));
}
}

**输出**

```java
Var1 = 5
Var2 = 10
Var3 = 5
var1 == var2: true
var1 == var3: false

运算符 3:“大于”运算符(>)

这将检查第一个操作数是否大于第二个操作数。当左侧的操作数大于右侧的操作数时,运算符返回真。

句法:

1
java复制代码变量 1 > 变量 2

例证:

1
2
3
4
java复制代码var1 = 30 
var2 = 20

var1 > var2 结果为真

例子:

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复制代码import java.io.*;
class GFG {

// 主要驱动方法
public static void main(String[] args)
{
// 初始化变量
int var1 = 30, var2 = 20, var3 = 5;

// 显示 var1、var2、var3
System.out.println("Var1 = " + var1);
System.out.println("Var2 = " + var2);
System.out.println("Var3 = " + var3);

//比较 var1 和 var2 并打印相应的布尔值
System.out.println("var1 > var2: " + (var1 > var2));

// 比较 var1 和 var3 并打印相应的布尔值
System.out.println("var3 > var1: "
+ (var3 >= var1));
}
}

**输出**

```java
Var1 = 30
Var2 = 20
Var3 = 5
var1 > var2: true
var3 > var1: false

作者立志打造一个拥有100个小游戏/工具的摸鱼网站,更新进度:42/100

我已经写了很长一段时间的技术博客,并且主要通过掘金发表,这是我的一篇关于Java 关系运算符与示例(上)。我喜欢通过文章分享技术与快乐。你可以访问我的博客: juejin.cn/user/204034… 以了解更多信息。希望你们会喜欢!😊

💌 欢迎大家在评论区提出意见和建议!💌

本文转载自: 掘金

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

laravel商品评论api 一、商品评论api

发表于 2021-11-28

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

一、商品评论api

1.1 评论表增加订单id

我们发现在我们评论某个订单下的某个商品后,然后这个订单下这个商品就不允许我们再次评论了,所以我们要给评论表加上一个订单id字段。
运行命令:php artisan make:migration add_order_id_to_comments_table --table=comments
在这里插入图片描述
加入订单字段:

1
php复制代码$table->integer('order_id')->comment('评论的商品所属的订单');

在这里插入图片描述
运行迁移:php artisan migrate
在这里插入图片描述

1.2 创建评论相关的控制权

运行命令php artisan make:controller Web/CommentController
在这里插入图片描述
由于评论模型字段太多,我们这边可以写不允许批量赋值的字段,就不用一个个的去写允许批量赋值的字段了:

1
2
php复制代码    // 不允许批量赋值的字段
protected $guarded = [];

在这里插入图片描述
写入方法:

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
php复制代码/**
* 评论
*/
public function store(Request $request, Order $order) {
$request->validate([
'goods_id' => 'required',
'content' => 'required',

], [
'goods_id.required' => '商品id不能为空',
'content.required' => '评论内容不能为空',
]);

// 只有确认收货才可以评论 status = 4
if ($order->status != 4) {
return $this->response->errorBadRequest('订单状态异常!');
}

// 要评论的商品必须是这个订单里面的
if (!in_array($request->input('goods_id'), $order->orderDetails()->pluck('goods_id')->toArray())) {
return $this->response->errorBadRequest('此商品不在此订单中!');
}

// 已经评论过的不能再评论
$checkComment = Comment::where('user_id', auth('api')->id())
->where('order_id', $order->id)
->where('goods_id', $request->input('goods_id'))
->count();
if ($checkComment > 0) {
return $this->response->errorBadRequest('此订单下商品已经评论了!');
}

// 生成评论数据
$request->offsetSet('user_id', auth('api')->id());
$request->offsetSet('order_id', $order->id);

Comment::create($request->all());

return $this->response->created();
}

在这里插入图片描述


1.3 创建评论商品路由

1
2
php复制代码// 评论商品
$api->post('orders/{order}/comment', [CommentController::class, 'store']);

在这里插入图片描述

1.4 测试

在这里插入图片描述
在这里插入图片描述

在学习的php的路上,如果你觉得本文对你有所帮助的话,那就请关注点赞评论三连吧,谢谢,你的肯定是我写博的另一个支持。

本文转载自: 掘金

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

快速入门PHP第一天,常用标识符,运算符,循环语句

发表于 2021-11-28

​

  • 这是我参与11月更文挑战的第28天,活动详情查看:2021最后一次更文挑战
    PHP 是一种创建动态交互性站点的强有力的服务器端脚本语言。

PHP 是免费的,并且使用非常广泛。同时,对于像微软 ASP 这样的竞争者来说,PHP 无疑是另一种高效率的选项。

第一节

创建一个以自己为名的拼音文件夹,把阿帕奇服务器压缩包解压进去

conf是配置文件

hadoc目录下才能运行(是网站的根目录)

localhost(本地登录)

第二节

代码格式

1
2
3
4
5
6
php复制代码
<?php

代码

?>

基本常用符号

echo:输出

变量以 $ 开头

小数为浮点型

非0为真0为假(除了0都是真)

空串也是假

双引号会解析变量

单引号是直接输出,不解析变量

定界符<<<(名字自定义)

<<<std

和双引号没啥区别,最后要顶格写

std;

\转义字符(反斜杠)

unset 销毁变量

(integer) 转为整数,不改变原来的值

settype 改变原值

. 相当与js的+,字符串连接符

第三节

运算符和循环

闰年:能被4整除并不能被100整除

&&或and 且的关系,都为true才是真

||或or 有一个为true就为真

xor 跟且差不多,不常用

! 非,真假颠倒

== 相等的操作数返回true

=== 相等并且数据类型一样

@ 禁止报错

三元运算符

(a>b)?”真值”:”假值”

rand(1,100) 1到100的随机数

跟js的if语句一样

1
2
3
4
5
6
7
8
9
php复制代码if(){

echo true输出

}else{

echo 输出

}

js的switch一样

$day 获取时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
php复制代码switch($day){

case 1:

echo "周一";

break; //找到之后就跳出循环,节省时间

case 2:

echo "周二"

default:

echo ""

}

while循环

1
2
3
php复制代码while(){

}

do while循环

1
2
3
4
php复制代码
do{

}while()

最少循环一次

  • 不知道循环次数用while循环
  • 知道循环次数就用for
1
2
3
4
5
6
php复制代码
for($i=1;$i<=10;$++){

echo "这是第$次循环";

}

九九乘法表的实现思路;外层循环控制行,内层控制列

下列控制循环语句的跳出

break 跳出循环

continue 跳出本次循环

最后

知识记录学习,大佬勿喷

如果对您有帮助,希望能给个👍评论/收藏/三连!

博主为人老实,无偿解答问题哦❤

本文转载自: 掘金

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

MySQL ACID与MVCC浅谈 前言 ACID介绍 隔离

发表于 2021-11-28

前言

我们都知道在做事务的概念,就是一个完整的操作动作要么都执行,要么都不执行,这是一个不可分割的工作单位,ACID又是事务的四大特征。那么ACID具体是什么呢?

ACID介绍

原子性(atomicity)

一个事务必须被视为一个不可分割的最小工作单元,整个事务中即使包含几个步骤,但所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作,这就是事务的原子性。

一致性(consistency)

一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。

只要新的事务T没有提交完成,那在不管在事务T发起前,还是操作过程等某一个时刻来获取数据库中的结果,结果都是一样的。

隔离性(isolation)

一个事务所做的修改在最终提交以前,其修改对其他事务是不可见的,这就是隔离性。

InnoDB支持的隔离级别有:

  • 读未提交(READ UNCOMMITTED)
  • 读提交(READ COMMITTED)
  • 可重复读(REPEATABLE READ)
  • 可串性化(SERIALIZABLE)

对于InnoDB默认的隔离级别是可重复读(REPEATABLE READ)。

持久性(durability)

一旦事务提交了,则其所做的修改就会永久保存到数据库中。即使此时系统崩溃,修改的数据也不会丢失。(当然持久化也分不同级别的)

隔离级别介绍

读未提交(READ UNCOMMITTED)

在READ UNCOMMITTED级别,事务中的修改,即使没有提交,对其他事务也都是可见的。事务可以读取未提交的数据,这称为脏读(Dirty Read) 。

时间 事务A 事务B
T1 开始事务 开始事务
T2 修改账号user1余额,将100改为200
T3 查询user1的余额,结果是200【脏读】
T4 操作出错,事务回滚
T5 从余额取出50,余额被更改为150(200-50)
T6 提交事务

备注:按照正确逻辑,此时账户余额应该是50。

这个级别会导致一些不太可靠的结果,从性能上来说,也不会比其他级别好太多。请谨慎使用此隔离级别,并注意结果可能不一致或无法重现,具体取决于其他事务同时执行的操作。通常,具有此隔离级别的事务仅执行查询,而不执行插入、更新或删除操作。

读提交(READ COMMITTED)

这个级别有时候也叫做不可重复读。只有事务提交了,才能被读取到,但是可能会存在两个阶段读取到的数值不一致的情况,就是在另外事务提交前读取的是一个数值,在事务提交之后读取到的又是另一个数值。这称为不可重复读(Nonrepeatable Read)

时间 事务A 事务B
T1 开始事务
T2 第一次查询,user1余额是100
T3 开始事务
T4 其他操作
T5 修改账号user1余额,将100改为200
T6 提交事务
T7 第二次查询,user1余额是200【不可重复读】
T8 继续其他操作

备注:按照正确逻辑,事务A前后两次读取到的数值应该一致。

在读提交隔离级别下,除了会出现「不可重复读」的情况,还会出现幻读(Phantom Read) 。

时间 事务A 事务B
T1 开始事务
T2 第一次查询,数据总量是100
T3 开始事务
T4 其他操作
T5 新插入100条数据
T6 提交事务
T7 第二次查询,数据总量为200条【幻读】
T8 继续其他操作

备注:按照正确逻辑,事务A前后两次读取到的数据总量应该一致。

可重复读(REPEATABLE READ)

InnoDB默认的隔离级别是可重复读。该级别的隔离,保证同一个事务中多次被读取同样数据结果是一致的。

可重复读解决了不可重复读 的问题。但是依旧不能解决幻读的情况。

InnoDB通过多版本并发控制(MVCC,Multiversion Concurrency Control)解决幻读的问题。

不可重复读和幻读到底有什么区别呢?

  1. 不可重复读是读取了其他事务更改的数据,针对UPDATE操作

解决方式:使用行级锁,锁定该行,事务A多次读取操作完成后才释放该锁,这个时候才允许其他事务更改刚才的数据。

  1. 幻读是读取了其他事务新增的数据,针对INSERT和DELETE操作

解决方式:使用表级锁,锁定整张表,事务A多次读取数据总量之后才释放该锁,这个时候才允许其他事务新增/删除数据。

可串行化(SERIALIZABLE)

这是最高的隔离级别。通过强制事务每一个步串行执行,在读取的每一行数据上都加锁,所以可能导致大量的超时和抢锁问题。

实际应用中也很少用到这个隔离级别,只有在非常需要确保数据的一致性而且可以接受没有并发的情况下,才考虑采用。

以上隔离级别从低到高,可以逐个解决脏读、不可重复读、幻读这几类问题。

隔离级别 脏读 不可重复读 幻读 加锁读
读未提交 Yes Yes Yes No
读提交 No Yes Yes No
可重复读 No No Yes No
可串行化 No No No Yes

MVCC介绍

概念

MVCC叫做多版本并发控制协议(Multiversion Concurrency Control),即多版本并发控制,它可以保存一行记录的多个历史版本(理解为快照),这些历史版本信息保存在 system tablespaces 或 undo tablespaces 中,统一叫做 rollback segment。用这些信息来支持事物的回滚操作和一致性读(可重复读)。

MVCC 的读操作有两个概念:快照读和当前读。

  • 快照读:快照读的实现是基于MVCC,它读取的数据可能是历史数据。
  • 当前读:当前读即读取的是最新的数据,会对读取的记录进行加排它锁,保证读取时其它事务不能修改当前记录。

MVCC的核心概念主要是:

  • 每行数据会增加三个隐藏字段:DB_TRX_ID、DB_ROLL_PTR 和 DB_ROW_ID。
  • Undo logs
  • 视图(read view)

MVCC的设计就是为了实现读-写冲突不加锁,提高性能(这个读就是快照读,非当前读)。主要解决了不可重复读的问题,也解决了幻读的问题。

隐藏字段

InnoDB的MVCC是通过在每一行记录后面添加三个字段来实现:

  1. DB_TRX_ID。存储修改(插入、更新和删除)这行数据的最后一个事务的ID(6字节)。此外删除操作在内部视为更新操作,将该行中的特殊bit位标记为已删除。
  2. DB_ROLL_PTR。存储指向上一个版本数据在undo log 里的位置指针(7字节)。指向 rollback segment 中的一个回滚日志记录,如果一行被更新了,则回滚日志中记录了如何还原的信息。
  3. DB_ROW_ID。随着插入新行而单调增加的行ID(6字节)。当创建表没有合适的索引作为聚集索引而自动生成聚集索引时,会用该隐藏的行ID创建聚集索引。

所以当你插入一行记录,实际在InnoDB中应该是这样的:

id name age DB_TRX_ID DB_ROLL_PTR DB_ROW_ID
1 张三 15 5 344434 1

Undo logs

基本概念

在数据修改的时候,不仅记录了redo,还记录了相对应的undo,如果因为某些原因导致事务失败或回滚了,可以借助该undo进行回滚。

undo log和redo log记录物理日志不一样,它是逻辑日志。可以认为当delete一条记录时,undo log中会记录一条对应的insert记录,反之亦然,当update一条记录时,它记录一条对应相反的update记录。

当执行rollback时,就可以从undo log中的逻辑记录读取到相应的内容并进行回滚。有时候应用到行版本控制的时候,也是通过undo log来实现的:当读取的某一行被其他事务锁定时,它可以从undo log中分析出该行记录以前的数据是什么,从而提供该行版本信息,让用户实现非锁定一致性读取。

存储方式

Undo log 存在于 undo log segment 中,undo log segment 存在于 rollback segment,rollback segment 存在于 system tablespace、undo tablespaces 和 temporary tablespaces。

innodb存储引擎对undo的管理采用段的方式。rollback segment称为回滚段,InnoDB最多支持 128 个回滚段,其中 32 个分配给临时表空间。这留下了 96 个回滚段,可以分配给修改常规表中数据的事务。每个回滚段中有1024个undo log segment。

回收机制

Undo log 在 rollback segment 中,分为了插入日志和更新日志:

  1. 插入日志:只有在回滚时需要,事务提交后,该日志很快就被删除。
  2. 更新日志:用来进行一致性读,当没有事务需要时会被删除。

插入日志只要事务提交后,该日志就会很快删除。下面主要分析更新日志的内部机制。

当事务提交的时候,innodb不会立即删除undo log,因为后续还可能会用到undo log,如隔离级别为repeatable read时,事务读取的都是开启事务时的最新提交行版本,只要该事务不结束,该行版本就不能删除,即undo log不能删除。

但是在事务提交的时候,会将该事务对应的undo log放入到删除列表中,未来通过purge来删除。并且提交事务时,还会判断undo log分配的页是否可以重用,如果可以重用,则会分配给后面来的事务,避免为每个独立的事务分配独立的undo log页而浪费存储空间和性能。

通过undo log记录delete和update操作的结果发现:(insert操作无需分析,就是插入行而已)

  • delete操作实际上不会直接删除,而是将delete对象打上delete flag,标记为删除,最终的删除操作是purge线程完成的。
  • update分为两种情况:update的列是否是主键列。
    • 如果不是主键列,在undo log中直接反向记录是如何update的。即update是直接进行的。
      • 如果是主键列,update分两部执行:先删除该行,再插入一行目标行。

⚠️注意:不能使用长事务,因为长事务会一直保留 undo log,这样日志文件会越来越大。

Read view

基本概念

视图(Read view)就是某个时间点的数据库快照,它的作用是定义事务执行期间”我能看见什么数据“。

在 MySQL 里,有两个视图的概念:

  • 一个是虚拟表,使用 create view 创建的。
  • 一个是 InnoDB 在实现 MVCC 时用到的一致性视图,用于支持 RC 和 RR 隔离级别的实现。

注意:视图的创建时间\

  1. 使用 BEGIN 或 START TRANSACTION 开启事务,然后会在第一个 SELECT 语句中进行创建。\
  2. 使用 START TRANSACTION WITH CONSISTENT SNAPSHOT 直接开启事物并创建快照。

重要名词概念:

  1. trx_id: 该行当前事务id。
  2. trx_ids: 当前系统活跃(未提交)事务版本号集合。
  3. min_trx_id: 创建当前read view 时系统正处于活跃的最小事务ID。
  4. max_trx_id: 创建当前read view 时系统应该分配的下一个事务ID。
  5. creator_trx_id: 创建当前read view的事务版本号。

判断逻辑

这样,对于当前事务的启动瞬间来说,一个数据版本的 DB_TRX_ID,有以下几种可能:

  1. 落在绿色区域。「事务ID < min_trx_id 」则显示。因为如果数据事务ID小于read view中的最小活跃事务ID,则可以肯定该数据是在当前事务启之前就已经存在了的,所以可以显示。
  2. 落在红色区域。「事务ID > max_trx_id」则不显示。因为如果数据事务ID大于read view 中的当前系统的最大事务ID,则说明该数据是在当前read view 创建之后才产生的,所以数据不予显示。
  3. 落在黄色区域。「min_trx_id < 事务ID < max_trx_id」则与trx_ids匹配。这时候会有三种情况:
    • 若事务ID不在集合中,表示这个版本是一件提交了的事务生成的,可见。
      • 若事务ID在集合中,并且事物ID等于creator_trx_id,说明当前事务是自己生成的,所以可见。
      • 若事务ID在集合中,但事物ID不等于creator_trx_id,说明当前事务不是自己生成的,并且该事务还没提交,所以不可见。

本文转载自: 掘金

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

【Spring Boot 快速入门】二十一、基于Spring

发表于 2021-11-28

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

前言

  嗨,大家好,现在微信使用的用户很多,作为开发人员也可以建立一个自己的微信小程序,本期与大家分享一下作者建立微信小程序的开发流程。

申请

  百度搜索微信公众号平台,然后扫码登录注册一个微信公众号,
图片.png

  进入申请页面之后,需要及时完善小程序相关信息。
图片.png
  首次进行微信小程序的开发,需要在开发者管理中,找到开发者设置中可以查看我们需要的开发者ID,开发者ID主要包含:AppID(小程序ID)和AppSecret(小程序密钥),需要注意的是出于安全考虑,AppSecret不再被明文保存,忘记密钥请点击重置,因此我们申请了和AppSecret之后,需要记录下来,如果忘记的话,就需要重新申请可能导致线上项目失效。

  在开发者ID最下面,可以设置常用的域名信息。需要注意的是所有设置的域名必须是HTTPS的域名,设置好域名之后就可以开发微信小程序啦。

图片.png

开发

后端

  开发微信小程的后端框架选择,本次开发一个【在线实用工具箱】,主要是供个人使用的,所以后台就选择了Spring Boot 作为基础进行开发,数据库是使用MySQL。整个项目的目录如下所示。

图片.png
  整个后端的项目开发与正常项目开发一致,主要是给小程序提供服务接口信息。目前已经实现的主要功能:服装尺码对照表、谚语大全、车牌查询、全国邮编查询、全国区号查询、好玩手持弹葆、BM计算器、数字转大写、笑话大全、生肖查询、血遗传查询、历史朝代表等功能,更多功能正在完善中。

  需要特别注意的是,在有用户输入新碟接口中,需要对输入信息进行敏感词校验,否则在审核的过程中无法通过。小程序为我们提供了敏感信息校验的接口,这个接口是免费的,因此直接调用即可。

检查一段文本是否含有违法违规内容
调用接口代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
js复制代码Map<String,String> map = new HashMap<>();
map.put("content",text);
String post1 = HttpUtil.post(postUrl+token,
JSON.toJSONString(map));
log.info("请求参数是:{},数据校验返回信息是:{}",text,post1);
JSONObject jsonObject = JSONObject.parseObject(post1);
int code = (int) jsonObject.get("errcode");
if(code==0){
return "0";
}else {
return "1";
}

  由于需要与微信小程序进行交互,因此在配置文件中需要把经常使用的微信服务的后台地址配置在配置文件中,当调用时直接取值即可。
application.properties配置文件如下

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
js复制代码server.port=8080


# mysql
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test?useSSL=false&useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&serverTimezone=Asia/Shanghai
spring.datasource.username=test
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

mybatis.mapper-locations=classpath*:mapper/**/*.xml

# 开启swagger bootstrap ui
swagger.enable=true
#swagger.enable=true
wx.access_token_url=https://api.weixin.qq.com/cgi-bin/token
wx.access_token_grant_type=client_credential
wx.access_token_appid=wx123456789
wx.access_token_secret=2e741123456789
wx.access_token_post=https://api.weixin.qq.com/wxa/msg_sec_check?access_token=

wx.access_token_rk_url=https://api.weixin.qq.com/cgi-bin/token
wx.access_token_grant_rk_type=client_credential
wx.access_token_rk_appid=wx0123456789
wx.access_token_rk_secret=430c123456789
wx.access_token_rk_post=https://api.weixin.qq.com/wxa/msg_sec_check?access_token=

  在与微信小程序交互的过程中,需要根据appid和secret去获取当前请求的小程序的token。在请求其他服务的过程中需要使用到token信息。例如下面进行的敏感文本信息校验功能等。

1
2
3
4
5
6
7
8
9
js复制代码
public String getToken(){
String token = HttpUtil.get(url+"?grant_type=" + type + "&appid=" + appid + "&secret=" + secret);
log.info("获取token返回信息是:"+token);
JSONObject jsonObject1 = JSONObject.parseObject(token);
String accessToken = (String) jsonObject1.get("access_token");

return accessToken;
}

  后台使用的都是最基础的服务,主要是为小程序提供接口,启动项目之后,在Swagger中可以看到所有的接口信息。

图片.png
  调用一下查询车牌归属地的接口,输入北京,可以看到北京地区的车牌归属地信息已经正常返回即可为微信小程序提供查询服务。

图片.png

前端

  上面的接口服务已经开发完成了,下面将基于微信开发者工具进行前端的开发,前端采用ColorUI组件库进行开发,在配置文件中引入即可使用。整体的开发页面如下图所示。

图片.png
  需要注意的是在本地调试的过程中如果遇到无法访问服务接口的情况,需要在详情,本地配置中选择不校验合法域名这一条,否则本地的基本上请求不到服务。

图片.png
  将上面后台中的本地服务启动,然后将域名信息配置到app.js中。这样就可以全局使用host: “http://localhost:8080”。
如下图:

图片.png
  点击其中的一个页面,输入查询数据,可以看到正常访问到数据了,可以正常访问喽。

图片.png

结语

  好了,以上就是基于Spring Boot 开发一个微信小程序的过程,感谢您的阅读,希望您喜欢,如对您有帮助,欢迎点赞收藏。如有不足之处,欢迎评论指正。下次见。

  作者介绍:【小阿杰】一个爱鼓捣的程序猿,JAVA开发者和爱好者。公众号【Java全栈架构师】维护者,欢迎关注阅读交流。

本文转载自: 掘金

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

二叉树如何遍历 递归算法遍历 迭代算法遍历

发表于 2021-11-28

递归算法遍历

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
scss复制代码/**
* 前序遍历
*/
public static int[] preTraversalTree(TreeNode rootNode) {
ArrayList<Integer> arrayList = new ArrayList<>();
preOrder(rootNode, arrayList);
return arrayList.stream().mapToInt(Integer::intValue).toArray();
}

/**
* 前序遍历
*/
public static void preOrder(TreeNode rootNode, ArrayList<Integer> list) {
if (rootNode == null) {
return;
}
list.add(rootNode.val);
preOrder(rootNode.left, list);
preOrder(rootNode.right, list);
}

/**
* 中序遍历
*
* @param rootNode
* @param list
*/
public static int[] inTraversalTree(TreeNode rootNode) {
ArrayList<Integer> arrayList = new ArrayList<>();
inOrder(rootNode, arrayList);
return arrayList.stream().mapToInt(Integer::intValue).toArray();
}

/**
* 中序遍历
*
* @param rootNode
* @param list
*/
public static void inOrder(TreeNode rootNode, ArrayList<Integer> list) {
if (rootNode == null) {
return;
}
inOrder(rootNode.left, list);
list.add(rootNode.val);
inOrder(rootNode.right, list);
}

/**
* 后序遍历
*
* @param rootNode
*/
public static int[] postTraversalTree(TreeNode rootNode) {
ArrayList<Integer> arrayList = new ArrayList<>();
postOrder(rootNode, arrayList);
return arrayList.stream().mapToInt(Integer::intValue).toArray();
}

/**
* 后序遍历
*
* @param rootNode
* @param list
*/
public static void postOrder(TreeNode rootNode, ArrayList<Integer> list) {
if (rootNode == null) {
return;
}
postOrder(rootNode.left, list);
postOrder(rootNode.right, list);
list.add(rootNode.val);
}

迭代算法遍历

前序遍历

思路与算法

我们也可以用迭代的方式实现方法一的递归函数,两种方式是等价的,区别在于递归的时候隐式地维护了一个栈,而我们在迭代的时候需要显式地将这个栈模拟出来,其余的实现与细节都相同,具体可以参考下面的代码。

Image.png

Image [2].png、

Image [3].png

Image [4].png

Image [5].png

Image [7].png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ini复制代码public static List<Integer> preOrderTraverse(TreeNode rootNode) {
Stack<TreeNode> stack = new Stack<>();
ArrayList<Integer> arrayList = new ArrayList<>();
while (rootNode != null || !stack.isEmpty()) {
while (rootNode != null) {
arrayList.add(rootNode.val);
stack.push(rootNode);
rootNode = rootNode.left;
}
TreeNode pop = stack.pop();
rootNode = pop.right;
}
return arrayList;
}

后序遍历

Image [8].png

Image [9].png

Image [10].png

Image [11].png

Image [12].png

Image [13].png

Image [14].png

Image [15].png

Image [16].png

Image [17].png

Image [18].png

Image [19].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
ini复制代码public static List<Integer> postOrderTraverse(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
if (root == null) {
return res;
}

Stack<TreeNode> stack = new Stack<>();
TreeNode prev = null;
while (root != null || !stack.isEmpty()) {
while (root != null) {
stack.push(root);
root = root.left;
}
root = stack.pop();
if (root.right == null || root.right == prev) {
res.add(root.val);
prev = root;
root = null;
} else {
stack.push(root);
root = root.right;
}
}
return res;
}

中序遍历

1
2
3
4
5
6
7
8
9
10
11
12
ini复制代码Stack<TreeNode> stack = new Stack<>();
ArrayList<Integer> arrayList = new ArrayList<>();
while (root != null || !stack.isEmpty()) {
while (root != null) {
stack.push(root);
root = root.left;
}
root = stack.pop();
arrayList.add(root.val);
root = root.right;
}
return arrayList;

【参考】

【1】leetcode

本文转载自: 掘金

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

1…136137138…956

开发者博客

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