diff --git "a/2021/10/12/nginx\346\220\255\345\273\272\345\211\215\347\253\257\351\241\271\347\233\256/index.html" "b/2021/10/12/nginx\346\220\255\345\273\272\345\211\215\347\253\257\351\241\271\347\233\256/index.html" index fcd40aef5..52f1ba066 100644 --- "a/2021/10/12/nginx\346\220\255\345\273\272\345\211\215\347\253\257\351\241\271\347\233\256/index.html" +++ "b/2021/10/12/nginx\346\220\255\345\273\272\345\211\215\347\253\257\351\241\271\347\233\256/index.html" @@ -228,18 +228,18 @@

image-20211012100409206

xtfp这软件上手很容易

只需要左右拖拽文件即刻互相传输

-

1.首先在Xshell上利用yum工具下载nginx

1
yum install nginx -y
+

1.首先在Xshell上利用yum工具下载nginx

1
yum install nginx -y
-

2.测试启动nginx

1
nginx
+

2.测试启动nginx

1
nginx
-

3.配置nginx配置文件

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
1.进入/etc/nginx文件夹
cd /etc/nginx
2.编辑nginx.conf
vim nginx.conf
3.添加一个server
server实例:
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;

root /usr/share/nginx/html; #修改为root /data/www;

include /etc/nginx/default.d/*.conf;

location / {
}

error_page 404 /404.html;
location = /40x.html {
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}

+

3.配置nginx配置文件

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
1.进入/etc/nginx文件夹
cd /etc/nginx
2.编辑nginx.conf
vim nginx.conf
3.添加一个server
server实例:
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;

root /usr/share/nginx/html; #修改为root /data/www;

include /etc/nginx/default.d/*.conf;

location / {
}

error_page 404 /404.html;
location = /40x.html {
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}

-

4.贴上我自己的nginx.conf完整配置

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# For more information on configuration, see:
# * Official English Documentation: http://nginx.org/en/docs/
# * Official Russian Documentation: http://nginx.org/ru/docs/

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

# Load dynamic modules. See /usr/share/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;

events {
worker_connections 1024;
}

http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log /var/log/nginx/access.log main;

sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;

include /etc/nginx/mime.types;
default_type application/octet-stream;

# Load modular configuration files from the /etc/nginx/conf.d directory.
# See http://nginx.org/en/docs/ngx_core_module.html#include
# for more information.
include /etc/nginx/conf.d/*.conf;

server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
root /usr/share/nginx/html;

# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;

location / {
}

error_page 404 /404.html;
location = /40x.html {
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}

server {
listen 8001 default_server;
listen [::]:8001 default_server;
server_name _;

root /usr/local/java/resources; #修改为root /data/www;

include /etc/nginx/default.d/*.conf;

location / {
}

error_page 404 /404.html;
location = /40x.html {
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}

# Settings for a TLS enabled server.
#
# server {
# listen 443 ssl http2 default_server;
# listen [::]:443 ssl http2 default_server;
# server_name _;
# root /usr/share/nginx/html;
#
# ssl_certificate "/etc/pki/nginx/server.crt";
# ssl_certificate_key "/etc/pki/nginx/private/server.key";
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 10m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
#
# # Load configuration files for the default server block.
# include /etc/nginx/default.d/*.conf;
#
# location / {
# }
#
# error_page 404 /404.html;
# location = /40x.html {
# }
#
# error_page 500 502 503 504 /50x.html;
# location = /50x.html {
# }
# }

}
+

4.贴上我自己的nginx.conf完整配置

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# For more information on configuration, see:
# * Official English Documentation: http://nginx.org/en/docs/
# * Official Russian Documentation: http://nginx.org/ru/docs/

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

# Load dynamic modules. See /usr/share/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;

events {
worker_connections 1024;
}

http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log /var/log/nginx/access.log main;

sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;

include /etc/nginx/mime.types;
default_type application/octet-stream;

# Load modular configuration files from the /etc/nginx/conf.d directory.
# See http://nginx.org/en/docs/ngx_core_module.html#include
# for more information.
include /etc/nginx/conf.d/*.conf;

server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
root /usr/share/nginx/html;

# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;

location / {
}

error_page 404 /404.html;
location = /40x.html {
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}

server {
listen 8001 default_server;
listen [::]:8001 default_server;
server_name _;

root /usr/local/java/resources; #修改为root /data/www;

include /etc/nginx/default.d/*.conf;

location / {
}

error_page 404 /404.html;
location = /40x.html {
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}

# Settings for a TLS enabled server.
#
# server {
# listen 443 ssl http2 default_server;
# listen [::]:443 ssl http2 default_server;
# server_name _;
# root /usr/share/nginx/html;
#
# ssl_certificate "/etc/pki/nginx/server.crt";
# ssl_certificate_key "/etc/pki/nginx/private/server.key";
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 10m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
#
# # Load configuration files for the default server block.
# include /etc/nginx/default.d/*.conf;
#
# location / {
# }
#
# error_page 404 /404.html;
# location = /40x.html {
# }
#
# error_page 500 502 503 504 /50x.html;
# location = /50x.html {
# }
# }

}

其中8001端口为我的静态资源访问端口
/usr/local/java/resources 为我的静态资源存放位置

-

5.重启nginx

1
nginx -s reload
+

5.重启nginx

1
nginx -s reload
-

6.测试nginx静态资源访问是否成功

1
2
例如:/usr/local/java/resources下有一个index.html文件
访问则是:www.huhdcc.top:8001/index.html
+

6.测试nginx静态资源访问是否成功

1
2
例如:/usr/local/java/resources下有一个index.html文件
访问则是:www.huhdcc.top:8001/index.html

如果还不能使用的话 是服务器没有开放端口问题

进入阿里云

diff --git a/2021/10/13/leetcode_412/index.html b/2021/10/13/leetcode_412/index.html index d02aff148..a5b2368bf 100644 --- a/2021/10/13/leetcode_412/index.html +++ b/2021/10/13/leetcode_412/index.html @@ -222,7 +222,7 @@

输入:n = 3
输出:[“1”,”2”,”Fizz”]
示例 2:

输入:n = 5
输出:[“1”,”2”,”Fizz”,”4”,”Buzz”]
示例 3:

输入:n = 15
输出:[“1”,”2”,”Fizz”,”4”,”Buzz”,”Fizz”,”7”,”8”,”Fizz”,”Buzz”,”11”,”Fizz”,”13”,”14”,”FizzBuzz”]

-
1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public List<String> fizzBuzz(int n) {
List<String> li = new ArrayList<>();
for(Integer i=1;i<=n;i++)
{
if(i % 3 == 0 && i % 5 == 0) li.add("FizzBuzz");
else if(i%3==0)li.add("Fizz");
else if(i%5==0)li.add("Buzz");
else li.add(i.toString());
}
return li;
}
}
+
1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public List<String> fizzBuzz(int n) {
List<String> li = new ArrayList<>();
for(Integer i=1;i<=n;i++)
{
if(i % 3 == 0 && i % 5 == 0) li.add("FizzBuzz");
else if(i%3==0)li.add("Fizz");
else if(i%5==0)li.add("Buzz");
else li.add(i.toString());
}
return li;
}
}
diff --git "a/2021/10/13/\347\273\251\347\202\271\350\256\241\347\256\227\345\231\250/index.html" "b/2021/10/13/\347\273\251\347\202\271\350\256\241\347\256\227\345\231\250/index.html" index e16168e48..ffbfcadf2 100644 --- "a/2021/10/13/\347\273\251\347\202\271\350\256\241\347\256\227\345\231\250/index.html" +++ "b/2021/10/13/\347\273\251\347\202\271\350\256\241\347\256\227\345\231\250/index.html" @@ -223,7 +223,7 @@

所以自己想了一下自己可以写一个绩点计算器出来吗 好像也不是很难的样子

所以自己用了差不多20分钟帮这个写出来了

下面是源码

-
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
#include<stdio.h>

int main()

{

int number;

float score;

float credit,creditsum=0;

float gpasum=0.0,GPA=0.0;

printf("请输入需要计算的科目数量");

scanf("%d",&number);

int count=1;

for(int i=0;i<number;i++)

{

printf("请输入第%d科的成绩和学分",count);

scanf("%f %f",&score,&credit);

creditsum+=credit;

// printf("%.1f",(score/10-5)*credit);

gpasum+=(score/10-5)*credit;

GPA=gpasum/creditsum;

count++;

}

// printf("学分总和:%.1f",creditsum);

// printf("总分:%.1f",gpasum);

printf("您的GPA是%.1f",GPA);



}
+
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
#include<stdio.h>

int main()

{

int number;

float score;

float credit,creditsum=0;

float gpasum=0.0,GPA=0.0;

printf("请输入需要计算的科目数量");

scanf("%d",&number);

int count=1;

for(int i=0;i<number;i++)

{

printf("请输入第%d科的成绩和学分",count);

scanf("%f %f",&score,&credit);

creditsum+=credit;

// printf("%.1f",(score/10-5)*credit);

gpasum+=(score/10-5)*credit;

GPA=gpasum/creditsum;

count++;

}

// printf("学分总和:%.1f",creditsum);

// printf("总分:%.1f",gpasum);

printf("您的GPA是%.1f",GPA);



}

功能样图
在这里插入图片描述
欢迎关注微信公众号 :打码少年风萧
程序链接
链接:https://pan.baidu.com/s/1MzkRQ3XCtWadlWFxFDef4g
提取码:30qf

diff --git "a/2021/10/14/\345\211\221\346\214\207offer2_69/index.html" "b/2021/10/14/\345\211\221\346\214\207offer2_69/index.html" index 6dee7f3e7..0774176c0 100644 --- "a/2021/10/14/\345\211\221\346\214\207offer2_69/index.html" +++ "b/2021/10/14/\345\211\221\346\214\207offer2_69/index.html" @@ -216,10 +216,10 @@

剑指 Offer II 069. 山峰数组的顶部

题目

-
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
符合下列属性的数组 arr 称为 山峰数组(山脉数组) :

arr.length >= 3
存在 i(0 < i < arr.length - 1)使得:
arr[0] < arr[1] < ... arr[i-1] < arr[i]
arr[i] > arr[i+1] > ... > arr[arr.length - 1]
给定由整数组成的山峰数组 arr ,返回任何满足 arr[0] < arr[1] < ... arr[i - 1] < arr[i] > arr[i + 1] > ... > arr[arr.length - 1] 的下标 i ,即山峰顶部。

 

示例 1

输入:arr = [0,1,0]
输出:1
示例 2

输入:arr = [1,3,5,4,2]
输出:2
示例 3

输入:arr = [0,10,5,2]
输出:1
示例 4

输入:arr = [3,4,5,1]
输出:2
示例 5

输入:arr = [24,69,100,99,79,78,67,36,26,19]
输出:2
 

提示:

3 <= arr.length <= 104
0 <= arr[i] <= 106
题目数据保证 arr 是一个山脉数组
 

进阶:很容易想到时间复杂度 O(n) 的解决方案,你可以设计一个 O(log(n)) 的解决方案吗?
+
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
符合下列属性的数组 arr 称为 山峰数组(山脉数组) :

arr.length >= 3
存在 i(0 < i < arr.length - 1)使得:
arr[0] < arr[1] < ... arr[i-1] < arr[i]
arr[i] > arr[i+1] > ... > arr[arr.length - 1]
给定由整数组成的山峰数组 arr ,返回任何满足 arr[0] < arr[1] < ... arr[i - 1] < arr[i] > arr[i + 1] > ... > arr[arr.length - 1] 的下标 i ,即山峰顶部。

 

示例 1:

输入:arr = [0,1,0]
输出:1
示例 2:

输入:arr = [1,3,5,4,2]
输出:2
示例 3:

输入:arr = [0,10,5,2]
输出:1
示例 4:

输入:arr = [3,4,5,1]
输出:2
示例 5:

输入:arr = [24,69,100,99,79,78,67,36,26,19]
输出:2
 

提示:

3 <= arr.length <= 104
0 <= arr[i] <= 106
题目数据保证 arr 是一个山脉数组
 

进阶:很容易想到时间复杂度 O(n) 的解决方案,你可以设计一个 O(log(n)) 的解决方案吗?

我写的想法 第一次就想到for循环遍历 写出了O(n)的学法

-
1
2
3
4
5
6
7
8
int peakIndexInMountainArray(int* arr, int arrSize){
int temp=0;
for(;temp<arrSize-1;temp++){
if(arr[temp]>arr[temp+1])
break;
}
return temp;
}
+
1
2
3
4
5
6
7
8
int peakIndexInMountainArray(int* arr, int arrSize){
int temp=0;
for(;temp<arrSize-1;temp++){
if(arr[temp]>arr[temp+1])
break;
}
return temp;
}

三叶姐姐的解法

二分
往常我们使用「二分」进行查值,需要确保序列本身满足「二段性」:当选定一个端点(基准值)后,结合「一段满足 & 另一段不满足」的特性来实现“折半”的查找效果。

@@ -228,7 +228,7 @@

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
// 根据 arr[i-1] < arr[i] 在 [1,n-1] 范围内找值
// 峰顶元素为符合条件的最靠近中心的元素
public int peakIndexInMountainArray(int[] arr) {
int n = arr.length;
int l = 1, r = n - 1;
while (l < r) {
int mid = l + r + 1 >> 1;
if (arr[mid - 1] < arr[mid]) l = mid;
else r = mid - 1;
}
return r;
}
}
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
// 根据 arr[i-1] < arr[i] 在 [1,n-1] 范围内找值
// 峰顶元素为符合条件的最靠近中心的元素
public int peakIndexInMountainArray(int[] arr) {
int n = arr.length;
int l = 1, r = n - 1;
while (l < r) {
int mid = l + r + 1 >> 1;
if (arr[mid - 1] < arr[mid]) l = mid;
else r = mid - 1;
}
return r;
}
}

预备知识

Java Web开发 Web服务器

slf4j打印日志是好的实践

才用lombok简化Java bean编写

给线程取一个好名字

并发与并行

Rob Pike(Golang语言创始者)的一段描述

进程与线程

多线程demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class deno01 {
public static void main(String[] args) {
new Thread(() ->{
while (true)
{
System.out.println(1);
}
},"t1").start();

new Thread(() ->{
while (true)
{
System.out.println(2);
}
},"t2").start();
}

}

很明显得到的结果是交替打印1 和 2。

拓展

tasklist 查看进程

taskkill 杀死进程

Jdk自带的命令 jps

可以查看java运行的程序类名

tasklist | findstr java 可以查看java的程序

taskkill /F /PID pid端口号 可以杀死进程

top命令查看服务器资源占有情况

synchronized关键字

synchronized关键字解决的是多个线程之间访问资源的同步性

]]> + JUC Java并发

预备知识

Java Web开发 Web服务器

slf4j打印日志是好的实践

才用lombok简化Java bean编写

给线程取一个好名字

并发与并行

Rob Pike(Golang语言创始者)的一段描述

进程与线程

多线程demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class deno01 {
public static void main(String[] args) {
new Thread(() ->{
while (true)
{
System.out.println(1);
}
},"t1").start();

new Thread(() ->{
while (true)
{
System.out.println(2);
}
},"t2").start();
}

}

很明显得到的结果是交替打印1 和 2。

拓展

tasklist 查看进程

taskkill 杀死进程

Jdk自带的命令 jps

可以查看java运行的程序类名

tasklist | findstr java 可以查看java的程序

taskkill /F /PID pid端口号 可以杀死进程

top命令查看服务器资源占有情况

synchronized关键字

synchronized关键字解决的是多个线程之间访问资源的同步性

]]>
@@ -157,7 +157,7 @@ 2022-06-29T07:27:15.543Z 2022-07-07T10:13:55.120Z - Java基础复习

1、== 与equals方法

对于八种基本数据类型来说(byte short int long float double boolean char) == 是比较的值 而八种基本数据类型 是没有equals方法的

对于引用数据类型来说 == 比较的是对象的内存地址 而equals比较的字面值(例:String类型)

2、Synchronized关键字

作用

Synchronized主要有三种用法

1
2
3
synchronized void method() {
//业务代码
}
1
2
3
synchronized void staic method() {
//业务代码
}
1
2
3
synchronized(this) {
//业务代码
}

3.Java容器

一、Collection

简单来说就是单个集合的元素

分为List、Set、Queue三大类

List有ArrayList(底层由数组组成 查找快 支持随机访问) LinkedList(底层由双向链表组成 修改快 还可以做栈 队列 以及双向队列) 以及Vector(线程安全 但是效率低 很少用)

List

ArrayList

JDK 7 以无参数构造方法创建 ArrayList 时,直接创建了长度是10的Object[]数组elementData 。

JDK 8 以无参数构造方法创建 ArrayList 时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为 10。

底层为一个Object类型的数组 初始长度为0;若采用了泛型 ArrayList list = new ArrayList<>();

则生成的是String[]类型的数组 初始长度为0

扩容

当初始长度为10已经加入了十个元素之后,我们需要再加一个元素的时候,我们就需要扩容

1
2
3
4
5
6
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)
elementData = grow();
elementData[s] = e;
size = s + 1;
}

这段源代码就是ArrayList的add()方法 如果添加的元素已经满了 则调用grow()函数 很明显 这是一个扩容函数

grow函数有两个 一个有参函数 一个无参函数

无参参数会调用有参参数 进行1.5倍的扩容

1
ArrayList list1 = new ArrayList(23);

这表示着生成了一个初始长度为23的ArrayList数组

Vector

和ArrayList数组类似 线程安全 效率低 用的很少

但是我们用的Stack(栈)则是基于Vector设计的

实现栈

Stack继承Vector 是Vector的子类

LinkedList

基于双向链表实现 增删元素效率高 查询效率低

LinkedList可以用作栈 队列 以及双向队列

Set

集合 无序可去重的集合

TreeSet

​ 无序 不可重复 自动排序 相当于存放在TreeMap的Key部分

HashSet

​ 无序 不可重复 支持快速查找 存放在HashMap中相当于key部分

LinkedHashSet

​ 基于双向链表实现,具有HashSet的查找效率

Queue

LinkedList

可以用他来实现双向队列

PriorityQueue

用于堆实现 可以用它实现优先队列

二、Map

映射类型 Key - Value类型结构、

HashMap

比如最为常见的HashMap

JDK 1.7 底层是数组+链表

JDK 1.8 底层是数组+链表+红黑树 加入红黑树的目的是增加HashMap的插入和查询速率

HaashMap通过key进行hashcode与 与运算 得到下标。

HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。

HashMap 实现了 Map 接口,根据键的 HashCode 值存储数据,具有很快的访问速度,最多允许一条记录的键为 null,不支持线程同步。

HashMap 是无序的,即不会记录插入的顺序。

HashMap 继承于AbstractMap,实现了 Map、Cloneable、java.io.Serializable 接口。

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
public class Main {
public static void main(String[] args) throws IOException, Exception {
HashMap<String,String> map = new HashMap<>();
map.put("1","one");
map.put("2","two");
map.put("3","three");
map.put("4","four");
System.out.println(map);

HashMap<String, String> Sites = new HashMap<String, String>();
// 添加键值对
Sites.put("one", "Google");
Sites.put("two", "Runoob");
Sites.put("three", "Taobao");
Sites.put("four", "Zhihu");
Sites.put("apple","lll");
System.out.println(Sites);
// key=商品名称,value=价格,这里以这个例子实现按名称排序和按价格排序.
Map store = new HashMap();

store.put("iphone12", 6799);
store.put("iphone12pro", 8499);
store.put("macbookPro", 19499);
store.put("ipadAir", 6999);
store.put("watch6", 3199);

// 直接输出HashMap得到的是一个无序Map(不是Arraylist那种顺序型储存)
System.out.println(store);

// {ipadAir=6999, iphone12pro=8499, macbookPro=19499, watch6=3199, iphone12=6799}
}

}

起初我验证Map的无序的时候 输出的总是有序的 增加了样本之后才变得无序

但是输入 它内部就有机构形成 无论你是输出十遍还是一百遍 他都是输出一样的顺序

这就是HashMap的无序性和有序性

采用拉链法解决哈希冲突

JDK1.7采用头插法,有可能形成回路

JDK1.8以后采用尾插法

HashMap的默认初始容量为16

HashMap 默认加载因⼦:0.75

数组容器达到 3/4 时,开始扩容

JDK 8 之后,对 HashMap 底层数据结构(单链表)进⾏了改进

hashcode() :通过调用Hashcode()方法得到key的哈希值

通过哈希函数/哈希算法 转换成数组的下表

重写Hashcode()和equals()的原因是

需要达到散列分布均匀

4.JVM,JRE,JDK的区别

总的来说 JVM包括JRE JRE包括JVM

Java 虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM 有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。字节码和不同系统的 JVM 实现是 Java 语言“一次编译,随处可以运行”的关键所在。

]]>
+ Java基础复习

1、== 与equals方法

对于八种基本数据类型来说(byte short int long float double boolean char) == 是比较的值 而八种基本数据类型 是没有equals方法的

对于引用数据类型来说 == 比较的是对象的内存地址 而equals比较的字面值(例:String类型)

2、Synchronized关键字

作用

Synchronized主要有三种用法

1
2
3
synchronized void method() {
//业务代码
}
1
2
3
synchronized void staic method() {
//业务代码
}
1
2
3
synchronized(this) {
//业务代码
}

3.Java容器

一、Collection

简单来说就是单个集合的元素

分为List、Set、Queue三大类

List有ArrayList(底层由数组组成 查找快 支持随机访问) LinkedList(底层由双向链表组成 修改快 还可以做栈 队列 以及双向队列) 以及Vector(线程安全 但是效率低 很少用)

List

ArrayList

JDK 7 以无参数构造方法创建 ArrayList 时,直接创建了长度是10的Object[]数组elementData 。

JDK 8 以无参数构造方法创建 ArrayList 时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为 10。

底层为一个Object类型的数组 初始长度为0;若采用了泛型 ArrayList list = new ArrayList<>();

则生成的是String[]类型的数组 初始长度为0

扩容

当初始长度为10已经加入了十个元素之后,我们需要再加一个元素的时候,我们就需要扩容

1
2
3
4
5
6
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)
elementData = grow();
elementData[s] = e;
size = s + 1;
}

这段源代码就是ArrayList的add()方法 如果添加的元素已经满了 则调用grow()函数 很明显 这是一个扩容函数

grow函数有两个 一个有参函数 一个无参函数

无参参数会调用有参参数 进行1.5倍的扩容

1
ArrayList list1 = new ArrayList(23);

这表示着生成了一个初始长度为23的ArrayList数组

Vector

和ArrayList数组类似 线程安全 效率低 用的很少

但是我们用的Stack(栈)则是基于Vector设计的

实现栈

Stack继承Vector 是Vector的子类

LinkedList

基于双向链表实现 增删元素效率高 查询效率低

LinkedList可以用作栈 队列 以及双向队列

Set

集合 无序可去重的集合

TreeSet

​ 无序 不可重复 自动排序 相当于存放在TreeMap的Key部分

HashSet

​ 无序 不可重复 支持快速查找 存放在HashMap中相当于key部分

LinkedHashSet

​ 基于双向链表实现,具有HashSet的查找效率

Queue

LinkedList

可以用他来实现双向队列

PriorityQueue

用于堆实现 可以用它实现优先队列

二、Map

映射类型 Key - Value类型结构、

HashMap

比如最为常见的HashMap

JDK 1.7 底层是数组+链表

JDK 1.8 底层是数组+链表+红黑树 加入红黑树的目的是增加HashMap的插入和查询速率

HaashMap通过key进行hashcode与 与运算 得到下标。

HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。

HashMap 实现了 Map 接口,根据键的 HashCode 值存储数据,具有很快的访问速度,最多允许一条记录的键为 null,不支持线程同步。

HashMap 是无序的,即不会记录插入的顺序。

HashMap 继承于AbstractMap,实现了 Map、Cloneable、java.io.Serializable 接口。

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
public class Main {
public static void main(String[] args) throws IOException, Exception {
HashMap<String,String> map = new HashMap<>();
map.put("1","one");
map.put("2","two");
map.put("3","three");
map.put("4","four");
System.out.println(map);

HashMap<String, String> Sites = new HashMap<String, String>();
// 添加键值对
Sites.put("one", "Google");
Sites.put("two", "Runoob");
Sites.put("three", "Taobao");
Sites.put("four", "Zhihu");
Sites.put("apple","lll");
System.out.println(Sites);
// key=商品名称,value=价格,这里以这个例子实现按名称排序和按价格排序.
Map store = new HashMap();

store.put("iphone12", 6799);
store.put("iphone12pro", 8499);
store.put("macbookPro", 19499);
store.put("ipadAir", 6999);
store.put("watch6", 3199);

// 直接输出HashMap得到的是一个无序Map(不是Arraylist那种顺序型储存)
System.out.println(store);

// {ipadAir=6999, iphone12pro=8499, macbookPro=19499, watch6=3199, iphone12=6799}
}

}

起初我验证Map的无序的时候 输出的总是有序的 增加了样本之后才变得无序

但是输入 它内部就有机构形成 无论你是输出十遍还是一百遍 他都是输出一样的顺序

这就是HashMap的无序性和有序性

采用拉链法解决哈希冲突

JDK1.7采用头插法,有可能形成回路

JDK1.8以后采用尾插法

HashMap的默认初始容量为16

HashMap 默认加载因⼦:0.75

数组容器达到 3/4 时,开始扩容

JDK 8 之后,对 HashMap 底层数据结构(单链表)进⾏了改进

hashcode() :通过调用Hashcode()方法得到key的哈希值

通过哈希函数/哈希算法 转换成数组的下表

重写Hashcode()和equals()的原因是

需要达到散列分布均匀

4.JVM,JRE,JDK的区别

总的来说 JVM包括JRE JRE包括JVM

Java 虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM 有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。字节码和不同系统的 JVM 实现是 Java 语言“一次编译,随处可以运行”的关键所在。

]]>
@@ -179,7 +179,7 @@ 2022-06-28T07:37:44.595Z 2022-06-29T06:17:26.380Z - 泛型

前言之ArrayList

ArrayList大家都肯定很熟悉 在需要存储一个不定长度的时候,我们通常第一时间想的就是ArrayList。

实际上ArrayList内部就是一个Object[]数组,配合存储一个当前分配的长度,就可以充当“可变数组”:

引用自廖雪峰老师的博客

我们翻开他的底层源码 我们会发现

原型是创建了一个Object[]的数组(初始容量为空 如果add第一个元素则会吧数组大小赋成10) 刚开始的 DEFAULT_CAPACITY 为10

未指定泛型前 我们需要这样写来获取对象

很容易出现ClassCastException,因为容易“误转型”:

1
2
3
4
5
6
7
8
9
10
11
public class Main {
public static void main(String[] args) throws IOException {
ArrayList list = new ArrayList();
list.add("hello world");
String s = (String) list.get(0);
System.out.println(s);
list.add(new Integer(123));
String s2 = (String) list.get(1);
}
}

则控制台会报错ClassCastException

要解决上述问题,我们可以为String单独编写一种ArrayList

StringArrayList stringarraylist = new Stringarraylist()

底层就是String类型的数组了

1
2
3
4
5
6
7
public class StringArrayList {
private String[] array;
private int size;
public void add(String e) {...}
public void remove(int index) {...}
public String get(int index) {...}
}

这样一来,存入的必须是String,取出的也一定是String,不需要强制转型,因为编译器会强制检查放入的类型:

但是我们总不能针对Integer类型再写个IngtegerArrayList类型 针对Character再写个。。。

工程量十分的巨大 以及累赘

所以泛型的出现解决了这个问题

我们必须把ArrayList变成一种模板:ArrayList

1
2
3
4
5
6
7
public class ArrayList<T> {
private T[] array;
private int size;
public void add(T e) {...}
public void remove(int index) {...}
public T get(int index) {...}
}

T可以是任何class。这样一来,我们就实现了:编写一次模版,可以创建任意类型的ArrayList

我们可以定义任何一种类型的ArrayList啦

1
2
3
ArrayList<String> stringArrayList = new ArrayList<>();
ArrayList<Integer> IntegerArrayList = new ArrayList<>();
ArrayList<Character> characterArrayList = new ArrayList<>();

这样一来,既实现了编写一次,万能匹配,又通过编译器保证了类型安全:这就是泛型。

向上转型

在Java标准库中的ArrayList<T>实现了List<T>接口,它可以向上转型为List<T>

使用泛型

使用ArrayList时,如果不定义泛型类型时,泛型类型实际上就是Object

1
ArrayList list = new ArrayList();//此为Object
1
2
3
4
5
List list = new ArrayList();
list.add("Hello");
list.add("World");
String first = list.get(0); //这里会报错 因为默认是Object对象 需要强转
String second = (String) list.get(1);

当我们定义泛型 List<String> list = new List<>();

1
2
3
4
5
6
7
//无编译器警告:
List<String> list = new ArrayList<String>();
list.add("Hello");
list.add("World");
// 无强制转型:
String first = list.get(0);
String second = list.get(1);

当我们定义泛型类型<Number>后,List<T>的泛型接口变为强类型List<Number>

1
2
3
4
5
6
7
8
List<Number> list = new ArrayList<>();
list.add(3.14);//double类型
list.add(3.15);//float类型
list.add(1);//Integer类型
Number first = list.get(0);
Number second = list.get(1);
Number third = list.get(2);
System.out.println("first"+" "+first+" "+"second"+" "+second+" "+"third"+" "+third);

泛型接口

除了ArrayList<T>使用了泛型,还可以在接口中使用泛型。例如,Arrays.sort(Object[])可以对任意数组进行排序,但待排序的元素必须实现Comparable<T>这个泛型接口:

1
2
3
4
5
6
7
8
public interface Comparable<T> {
/**
* 返回负数: 当前实例比参数o小
* 返回0: 当前实例与参数o相等
* 返回正数: 当前实例比参数o大
*/
int compareTo(T o);
}
]]>
+ 泛型

前言之ArrayList

ArrayList大家都肯定很熟悉 在需要存储一个不定长度的时候,我们通常第一时间想的就是ArrayList。

实际上ArrayList内部就是一个Object[]数组,配合存储一个当前分配的长度,就可以充当“可变数组”:

引用自廖雪峰老师的博客

我们翻开他的底层源码 我们会发现

原型是创建了一个Object[]的数组(初始容量为空 如果add第一个元素则会吧数组大小赋成10) 刚开始的 DEFAULT_CAPACITY 为10

未指定泛型前 我们需要这样写来获取对象

很容易出现ClassCastException,因为容易“误转型”:

1
2
3
4
5
6
7
8
9
10
11
public class Main {
public static void main(String[] args) throws IOException {
ArrayList list = new ArrayList();
list.add("hello world");
String s = (String) list.get(0);
System.out.println(s);
list.add(new Integer(123));
String s2 = (String) list.get(1);
}
}

则控制台会报错ClassCastException

要解决上述问题,我们可以为String单独编写一种ArrayList

StringArrayList stringarraylist = new Stringarraylist()

底层就是String类型的数组了

1
2
3
4
5
6
7
public class StringArrayList {
private String[] array;
private int size;
public void add(String e) {...}
public void remove(int index) {...}
public String get(int index) {...}
}

这样一来,存入的必须是String,取出的也一定是String,不需要强制转型,因为编译器会强制检查放入的类型:

但是我们总不能针对Integer类型再写个IngtegerArrayList类型 针对Character再写个。。。

工程量十分的巨大 以及累赘

所以泛型的出现解决了这个问题

我们必须把ArrayList变成一种模板:ArrayList

1
2
3
4
5
6
7
public class ArrayList<T> {
private T[] array;
private int size;
public void add(T e) {...}
public void remove(int index) {...}
public T get(int index) {...}
}

T可以是任何class。这样一来,我们就实现了:编写一次模版,可以创建任意类型的ArrayList

我们可以定义任何一种类型的ArrayList啦

1
2
3
ArrayList<String> stringArrayList = new ArrayList<>();
ArrayList<Integer> IntegerArrayList = new ArrayList<>();
ArrayList<Character> characterArrayList = new ArrayList<>();

这样一来,既实现了编写一次,万能匹配,又通过编译器保证了类型安全:这就是泛型。

向上转型

在Java标准库中的ArrayList<T>实现了List<T>接口,它可以向上转型为List<T>

使用泛型

使用ArrayList时,如果不定义泛型类型时,泛型类型实际上就是Object

1
ArrayList list = new ArrayList();//此为Object
1
2
3
4
5
List list = new ArrayList();
list.add("Hello");
list.add("World");
String first = list.get(0); //这里会报错 因为默认是Object对象 需要强转
String second = (String) list.get(1);

当我们定义泛型 List<String> list = new List<>();

1
2
3
4
5
6
7
//无编译器警告:
List<String> list = new ArrayList<String>();
list.add("Hello");
list.add("World");
// 无强制转型:
String first = list.get(0);
String second = list.get(1);

当我们定义泛型类型<Number>后,List<T>的泛型接口变为强类型List<Number>

1
2
3
4
5
6
7
8
List<Number> list = new ArrayList<>();
list.add(3.14);//double类型
list.add(3.15);//float类型
list.add(1);//Integer类型
Number first = list.get(0);
Number second = list.get(1);
Number third = list.get(2);
System.out.println("first"+" "+first+" "+"second"+" "+second+" "+"third"+" "+third);

泛型接口

除了ArrayList<T>使用了泛型,还可以在接口中使用泛型。例如,Arrays.sort(Object[])可以对任意数组进行排序,但待排序的元素必须实现Comparable<T>这个泛型接口:

1
2
3
4
5
6
7
8
public interface Comparable<T> {
/**
* 返回负数: 当前实例比参数o小
* 返回0: 当前实例与参数o相等
* 返回正数: 当前实例比参数o大
*/
int compareTo(T o);
}
]]>
@@ -201,7 +201,7 @@ 2022-05-11T11:47:46.565Z 2022-05-11T12:19:51.427Z - 通过rpm源安装RabbitMQ后 15672端口开放却访问不到网页(附安装方法)

先创建两个路径 先安装erlang环境

rabbitmq资源地址https://wwb.lanzoum.com/igycS04nk2ej

erlang资源 https://wwb.lanzoum.com/ic3df04nk6sh 密码:emig

安装erlang资源

先下载erlang资源 并且安装

yum -y install esl-erlang_23.0.2-1_centos_7_amd64.rpm

检测erlang

安装Rabittmq

安装UI插件命令

rabbitmq-plugins enable rabbitmq_management systemctl start rabbitmq-server.service systemctl status rabbitmq-server.service

!!!这个很重要 我安装的时候排查了很久 发现就是忘记敲了这个命令

启用rabbitmq服务

systemctl start rabbitmq-server.service

检测服务命令

systemctl status rabbitmq-server.service

查看是否是绿色的running

访问 ip+15672

打开防火墙

进入界面

默认账号guest 密码 guest

但是是默认不是本机是登不上去的

在rabbitmq的配置文件目录下(默认为:/etc/rabbitmq)创建一个rabbitmq.config文件。 文件中添加如下配置(请不要忘记那个“.”):

cd /etc/rabbitmq

vim rabbitmq.config

加入下面一句话

[{rabbit, [{loopback_users, []}]}].

重启rabbitmq服务

systemctl restart rabbitmq-server.service

重新访问即可

Maven依赖

1
2
3
4
5
<!-- AMQP依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

配置

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
77
application.yml

RabbitMQConfig.java

spring:

\#RabbitMQ

rabbitmq:

\#服务器地址

host: 192.168.10.100

\#用户名

username: guest

\#密码

password: guest

\#虚拟主机

virtual-host: /

\#端口

port: 5672

listener:

simple:

​ \#消费者最小数量

​ concurrency: 10

​ \#消费者最大数量

​ max-concurrency: 10

​ \#限制消费者每次只处理一条消息,处理完再继续下一条消息

​ prefetch: 1

​ \#启动时是否默认启动容器,默认true

​ auto-startup: true

​ \#被拒绝时重新进入队列

default-requeue-rejected: true

template:

retry:

​ \#发布重试,默认false

​ enabled: true

​ \#重试时间 默认1000ms

​ initial-interval: 1000

​ \#重试最大次数,默认3

​ max-attempts: 3

​ \#重试最大间隔时间,默认10000ms

​ max-interval: 10000

​ \#重试间隔的乘数。比如配2.0 第一次等10s,第二次等20s,第三次等40s

​ multiplier: 1.0
]]>
+ 通过rpm源安装RabbitMQ后 15672端口开放却访问不到网页(附安装方法)

先创建两个路径 先安装erlang环境

rabbitmq资源地址https://wwb.lanzoum.com/igycS04nk2ej

erlang资源 https://wwb.lanzoum.com/ic3df04nk6sh 密码:emig

安装erlang资源

先下载erlang资源 并且安装

yum -y install esl-erlang_23.0.2-1_centos_7_amd64.rpm

检测erlang

安装Rabittmq

安装UI插件命令

rabbitmq-plugins enable rabbitmq_management systemctl start rabbitmq-server.service systemctl status rabbitmq-server.service

!!!这个很重要 我安装的时候排查了很久 发现就是忘记敲了这个命令

启用rabbitmq服务

systemctl start rabbitmq-server.service

检测服务命令

systemctl status rabbitmq-server.service

查看是否是绿色的running

访问 ip+15672

打开防火墙

进入界面

默认账号guest 密码 guest

但是是默认不是本机是登不上去的

在rabbitmq的配置文件目录下(默认为:/etc/rabbitmq)创建一个rabbitmq.config文件。 文件中添加如下配置(请不要忘记那个“.”):

cd /etc/rabbitmq

vim rabbitmq.config

加入下面一句话

[{rabbit, [{loopback_users, []}]}].

重启rabbitmq服务

systemctl restart rabbitmq-server.service

重新访问即可

Maven依赖

1
2
3
4
5
<!-- AMQP依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

配置

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
77
application.yml

RabbitMQConfig.java

spring:

\#RabbitMQ

rabbitmq:

\#服务器地址

host: 192.168.10.100

\#用户名

username: guest

\#密码

password: guest

\#虚拟主机

virtual-host: /

\#端口

port: 5672

listener:

simple:

​ \#消费者最小数量

​ concurrency: 10

​ \#消费者最大数量

​ max-concurrency: 10

​ \#限制消费者每次只处理一条消息,处理完再继续下一条消息

​ prefetch: 1

​ \#启动时是否默认启动容器,默认true

​ auto-startup: true

​ \#被拒绝时重新进入队列

default-requeue-rejected: true

template:

retry:

​ \#发布重试,默认false

​ enabled: true

​ \#重试时间 默认1000ms

​ initial-interval: 1000

​ \#重试最大次数,默认3

​ max-attempts: 3

​ \#重试最大间隔时间,默认10000ms

​ max-interval: 10000

​ \#重试间隔的乘数。比如配2.0 第一次等10s,第二次等20s,第三次等40s

​ multiplier: 1.0
]]>
@@ -334,7 +334,7 @@ 2022-04-07T00:17:05.425Z 2022-05-17T12:39:35.879Z - 阅文笔试复习

1.详细描述ThreadPoolExecutor的各个参数的含义,介绍一个任务提交到线程池后的执行流程

2.请简要说明Servlet中的生命周期

1.Servlet初始化后调用Init()方法

2.Servlet调用service()方法来处理客户端的请求。

3.Servlet销毁前调用destroy()方法终止

3.开启两个线程A,B,打印1到10 线程A打印奇数(1,3,5,7,9),线程B打印偶数(2,4,6,8,10).

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
package com.yuewen;

import java.util.Scanner;
import java.util.concurrent.locks.LockSupport;

public class ABXianC {
static Thread thread1;
static Thread thread2;
public static void main(String[] args) {
thread1 = new Thread(() -> {
for (int i = 1; i <= 9; i += 2) {
System.out.println(i);
LockSupport.unpark(thread2);
LockSupport.park();
}
});
thread2 = new Thread(() -> {
for (int i = 2; i <= 10; i = i + 2) {
LockSupport.park();
System.out.println(i);
LockSupport.unpark(thread1);
}
});
thread1.start();
thread2.start();
}

}

4.请编写代码实现单例模式,类名为Singletion

1.饿汉模式

1
2
3
4
5
6
public class Singleton{
static private Singleton instance = new Singleton();//因为无法实例化,所以必须是静态的
static public Singleton getInstance(){
return instance;
}
}

2.懒汉线程安全

1
2
3
4
5
6
7
8
9
10
11
12
package com.yuewen;

import java.util.Scanner;

public class Singletion {
private static Singleton instance;
private Singletion(){};
public static synchronized Singleton getInstance(){
if(instance == null) instance = new Singleton();
return instance;
}
}

5.写一个Map转换成JavaBean的工具类方法,实现如下mapToObject方法(使用Java反射,不允许使用第三方库)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static Object mapToObject(Map<String,Object> map,Class<?> beanClass){
if(map == null) return null;
Object obj = null;
try{
obj = beanClass.newInstance();
Field[] fields = obj.getClass().getDeclaredFields();
for(Field field : fields)
{
int mod = field.getModifiers();
if(Modifier.isStatic(mod) || Modifier.isFinal(mod)){
continue;
}
field.setAccessible(true);
field.set(obj,map.get(field.getName()));
}
catch(Exception e)
e.printStackTrace();
}
return obj;
}
}

6.数据库操作是我们经常使用的一个技能, 请你完成一个简单的用户密码验证过程 ,给定的条件如下:

数据库中存在个用户表:users ,表结构如下:

1
2
3
4
5
6
7
CREATE TABLE `users` (
`uid` bigint(20) NOT NULL COMMENT '用户ID',
`user_name` varchar(32) NOT NULL COMMENT '用户账号',
`password` varchar(64) NOT NULL COMMENT '用户混淆密码',
PRIMARY KEY (`uid`),
UNIQUE KEY `u_user_name` (`user_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'

完善以下方法

public boolean verifyPassword(String username,String password) {
Connection con = getConnection () ;// getConnection() 方法是个已有的方法可以获取到数据库连接 ,

// here is your code
}

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
public boolean verifyPassword(String username,String password){
Connection con=getConnection;
String sql="SELECT password FROM users WHERE user_name=?";
PreparedStatement pst=null;
ResultSet rs=null;
boolean flag=false;
try{
pst=con.prepareStatement(sql);
pst.setObject(1,username);
rs=pst.executeQuery();
while(rs.next()){
if(rs.getString("password").equals(password)){
flag=true;
}
}
}catch(ClassNotFoundException e){
e.printStackTrace();
}catch (SQLException e){
e.printStackTrace();
}finally {
try{
if(rs!=null) rs.close();
if(pst!=null) pst.close();
if(con!=null) con.close();
}catch(SQLException e){
e.printStackTrace();
}
}
return flag;
}

7.介绍HashMap的数据结构、扩容机制,HashMap与Hashtable的区别,是否是线程安全的,并介绍ConcurrentHashMap的实现机制。

HashMap JDK1.8之前 数组+链表

JDK1.8之后 数组+链表+红黑树

1.数组结构

数组用于存储内容,链表(红黑树)用于解决hash冲突。如果链表长度大于阈值8,但是当前数组长度小于树化阈值64,则进行数组扩容操作;如果数组长度大于树化阈值64,则进行链表树化操作,将单向链表转化为红黑树结构。

2.扩容机制:

如果不指定容量,则初始容量默认为16。如果指定容量,则初始容量设置为大于指定容量的最小2的幂数。当当前容量大于容量*负载因子(默认为0.75)时进行扩容操作,扩容为原容量的2倍。

3.HashMap与HashTable的区别

1)数据结构区别:HashMap为数组+链表(红黑树),HashTable为数组+链表,HashTable没有树化操作。

2)扩容机制区别:未指定容量情况下,HashMap容量默认16,每次扩容为2n(n:原容量)。HashTable容量默认为11,每次扩容为2n+1(n:原容量)。指定容量情况下,HashMap将保证容量为2的幂数,HashTable将直接使用指定容量。

3)数据插入方式的区别:当发生hash冲突时,HashMap使用尾插法插入链表,HashTable使用头插法插入链表。

4)线程安全区别:HashMap是非线程安全的,HashTable因为使用synchronized修饰方法,所以HashTable是线程安全的。

ConcurrentHashMap的实现机制

1)ConcurrentHashMap通过synchronized关键字和CAS操作实现线程安全,若插入的槽没有数据,使用CAS操作执行插入操作,若插入的槽有数据,通过synchronized锁住链表的头节点,从而实现效率与线程安全的平衡

8.介绍数据库连接池的实现方式。如何从连接池中获取连接、将连接放回连接池?使用连接池的优势是什么?列举一下自己用过的连接池。

连接池实现原理:

1.用户给servlet发送请求,请求Dao要Connection

2.Dao从“连接池”中取出Connection资源,与DB的通讯

3.当用户离开之后,释放该Connection,那么该Connection被释放到连接池中,等待下一个用户来

Demo目标:

通过简单的增删改查来做到下面几个关于连接池的方式,让我们更了解几种优化的方式

1.自定义一个Pool,来实现类似于现在开源连接池为我们做的一些操作

2.使用Tomcat内置的连接池(apache dbcp)

3.使用DBCP数据库连接池

4.使用C3P0数据库连接池(推荐)

数据库连接池技术带来的优势

1. 资源重用

由于数据库连接得到重用,避免了频繁创建、释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增进了系统运行环境的平稳性(减少内存碎片以及数据库临时进程/线程的数量)。

2. 更快的系统响应速度

数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而缩减了系统整体响应时间。

3. 新的资源分配手段

对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接的配置,实现数据库连接池技术,几年钱也许还是个新鲜话题,对于目前的业务系统而言,如果设计中还没有考虑到连接池的应用,那么…….快在设计文档中加上这部分的内容吧。某一应用最大可用数据库连接数的限制,避免某一应用独占所有数据库资源。

4. 统一的连接管理,避免数据库连接泄漏

在较为完备的数据库连接池实现中,可根据预先的连接占用超时设定,强制收回被占用连接。从而避免了常规数据库连接操作中可能出现的资源泄漏。一个最小化的数据库连接池实现:

9.什么是死锁?JAVA程序中什么情况下回出现死锁?如何避免出现死锁?

死锁是一种特定的程序状态,在实体之间,由于循环依赖导致彼此一直处于等待之中,没有任何个体可以继续前进。死锁不仅仅会发生在线程之间,存在资源独占的进程之间同样也可能出现死锁。通常来说,我们大多是聚焦在多线程场景中的死锁,指两个或多个线程之间,由于互相持有对方需要的锁,而永久处于阻塞的状态。

基本上死锁的发生是因为:互斥条件,类似Java中Monitor都是独占的,要么是我用,要么是你用。互斥条件是长期持有的,在使用结束之前,自己不会释放,也不能被其它线程抢占。循环依赖关系,两个或者多个个体之间出现了锁的链条环。免死锁的思路和方法。****1、如果可能的话,尽量避免使用多个锁,并且只有需要时才持有锁。2、如果必须使用多个锁,尽量设计好锁的获取顺序。

3、使用带超时的方法,为程序带来更多可控性

10. 分布式锁有几种实现方式,并介绍每种方式的优缺点。

分布式锁一般有三种实现方式:
1、 数据库锁
2、基于Redis的分布式锁
3、基于ZooKeeper的分布式锁

11. 什么是TCP粘包拆包?为什么会出现粘包拆包?如何在应用层面解决此问题?

​ 如果客户端连续不断的向服务端发送数据包时,服务端接收的数据会出现两个数据包粘在一起的情况,这就是TCP协议中经常会遇到的粘包以及拆包的问题。

1、TCP是基于字节流的,虽然应用层和传输层之间的数据交互是大小不等的数据块,但是TCP把这些数据块仅仅看成一连串无结构的字节流,没有边界;

2、在TCP的首部没有表示数据长度的字段,

基于上面两点,在使用TCP传输数据时,才有粘包或者拆包现象发生的可能。

解决

1、发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。
2、发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。
3、可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。

12 请大致描述一下BIO,AIO和NIO的区别?

BIO:同步阻塞式IO,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
NIO:同步非阻塞式IO,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
AIO:异步非阻塞式IO,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。

13 在JAVA语法中加载类的的方式有哪些?

1、创建类的实例(开辟地址空间)

2、访问某个静态类或接口的静态常量,或者对该静态变量赋值(类初始化)

3、调用类的静态访问(new,也会占用空间)

4、反射(类初始化)

5、初始化一个类的子类(继承)

6、JAVA虚拟机启动被称标明为启动类的类

7、调用某个 ClassLoader 实例的 loadClass() 方法(类不会初始化)

14 建立三个线程A、B、C,A线程打印10次字母A,B线程打印10次字母B,C线程打印10次字母C,但是要求三个线程同时运行,并且实现交替打印,即按照ABCABCABC的顺序打印。

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
package com.yuewen;

import java.util.Scanner;
import java.util.concurrent.locks.LockSupport;
public class ABC {
static Thread A, B, C;

public static void main(String[] args) {
A = new Thread(() -> {
for (int i = 0; i < 10; i++) {
LockSupport.park();
System.out.print("A");
LockSupport.unpark(B);
}
});
B = new Thread(() -> {
for (int i = 0; i < 10; i++) {
LockSupport.park();
System.out.print("B");
LockSupport.unpark(C);
}
});
C = new Thread(() -> {
for (int i = 0; i < 10; i++) {
LockSupport.unpark(A);
LockSupport.park();
System.out.print("C");
}
});
A.start();
B.start();
C.start();
}
}

15 请列举5个spring框架中的注解,并说明注解的用法以及使用场景

16 给定一组自然数,数字的值有可能会大于2^64 ,要求计算出所有数字的和

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
import org.junit.Test;

import java.util.ArrayList;

public class Solution {

/**
* 计算一组数字的和
* @param numbers string字符串ArrayList 一组自然数
* @return string字符串
*/
public String sum (ArrayList<String> numbers) {
// write code here

String result="0";
for (String number : numbers) {

if(number==null||number.length()==0){
continue;
}
int resultLen = result.length();
int curNumLen = number.length();
int sum=0;
int remain;
StringBuilder stringBuilder = new StringBuilder();
while (resultLen>0||curNumLen>0){

int resultNum=0;
if(resultLen>0){
resultNum = result.charAt(--resultLen) - '0';
}

int curNum=0;
if(curNumLen>0){
curNum = number.charAt(--curNumLen) - '0';
}

sum=sum+resultNum+curNum;
remain=sum%10;
stringBuilder.append(remain);
sum/=10;
}

if(sum!=0){
stringBuilder.append(sum);
}
result=stringBuilder.reverse().toString();


}

return result;
}

@Test
public void test(){

String num1="123456";
String num2="123456789";
String num3="123456789123";
ArrayList<String> strings = new ArrayList<>();
strings.add(num1);
strings.add(num2);
strings.add(num3);
System.out.println(sum(strings));
}
}

17 给定一个int数字 要求计算出int数字对应的二进制中1的个数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.yuewen;

import java.util.Scanner;

public class Erjinzhi {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int ans = 0;
while (n > 0){
n = n & (n-1);
ans++;
}
System.out.println(ans);
}

}

18 根据产品策略某本书可以设置包月到期时间,需要计算指定时间到包月到期时间还有多少分钟,不足60S的不计入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static String dateSub(String a, String b) {
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sdf.parse(a);
LocalDateTime t1 = LocalDateTime.parse(a, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),
t2 = LocalDateTime.parse(b, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
long pass = t1.until(t2, ChronoUnit.MINUTES);
return String.valueOf(Math.abs(pass));
} catch (Exception e) {
System.out.println(String.format("格式转化错误:%s, 检查是否格式输入错误", e.getMessage()));
}
return "0";
}

public static void main(String[] args) {
System.out.println("请输入指定的两个日期【小者在前,大者在后,格式:yyyy-MM-dd hh:mm:ss】:");
Scanner sc = new Scanner(System.in);
String a = sc.nextLine(), b = sc.nextLine();
sc.close();
System.out.println(dateSub(a, b));
}

19 map是一种开发过程中经常使用的k-v数据结构,有个map保存了书名和书字数的关系,编写代码对map里面的书按照字数进行升序排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static Map<String, Integer> sortMap(Map<String, Integer> map) {
TreeMap<Integer, List<String>> treeMap = new TreeMap<>();
map.entrySet().forEach(entry -> {
List<String> indexList = treeMap.computeIfAbsent(entry.getValue(), k -> new ArrayList<>());
indexList.add(entry.getKey());
});
Map<String, Integer> result = new ListOrderedMap();
treeMap.entrySet().forEach(entry -> {
entry.getValue().forEach(key -> result.put(key, map.get(key)));
});
return result;
}

public static Map<String, Integer> sortMap2(Map<String, Integer> map) {
Map result = new ListOrderedMap();
map.entrySet().stream().
sorted(Map.Entry.comparingByValue()).
forEachOrdered(entry -> result.put(entry.getKey(), entry.getValue()));

return result;
}


20 起点APP上允许用户对作品进行评论,为了防止用户恶意评论,发表不当内容,需要对用户发布的内容进行过滤,请写程序过滤用户发布内容中带有的QQ号(6~10位数字组成) 允许对内容严格操作,如用户发表了 作者大大666666,为你点赞 ,经过过滤后也可以为作者大大****,为你点赞 ,将666666过滤掉了。

把6-10位的数字替换成””;

1
2
3
public static String filterQQ(String s) {
return s.replaceAll("\\d{6,10}","");
}

21 质数(又称素数),是指在大于1的自然数中,除了1和它本身外,不能被其他自然数整除(除0以外)的数称之为素数(质数)。请写个程序判断输入的数字是否是质数,如果是素数请输出:true,不是请输出false

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package leecode;

public class IsPrimeDemo {
public static boolean isPrime(int n){
if (n < 1){
return false;
}
int i = 2;
int end = (int) Math.sqrt(n);
while (i <= end ){
if (n % i == 0){
return false;
}
++i;
}
return true;
}

public static void main(String[] args) {
int n = 7;
System.out.println(isPrime(n));
}
}

22 有 n 个台阶,你一次能走 1 个或者 2 个台阶,那么请问,走完这 n 个台阶共有几种方式?

经典爬楼梯问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.yuewen;

import java.util.Scanner;

public class ZouTaiJie {
public static void main(String[] args) {

Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int[] dp = new int[n];
int i = 2;
dp[0] = 1;
dp[1] = 2;
while (i < n)
{
dp[i] = dp[i-1] + dp[i-2];
i++;
}
System.out.println(dp[n-1]);
}

}

23 给定一个字符串,返回这个字符串中有多少个回文子串。两个相同的回文子串出现在不同的位置,认为是2个回文子串。a、aa、aaa、aba、aabaa、abcba均认为是回文子串。

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
import java.util.*;


public class Solution {
/**
*
* @param str string字符串
* @return int整型
*/
public int palindromeCount (String str) {
int ans = 0;
for (int center = 0; center < str.length(); center++) {
ans += expand(str, center, center) + expand(str, center, center + 1);
}
return ans;
}

private int expand(String str, int left, int right) {
int ans = 0;
while (left >= 0 && right < str.length() && str.charAt(left) == str.charAt(right)) {
ans++;
left--;
right++;
}
return ans;
}
}

24 将一个给定的单链表反转,例:1-2-3-4-5,反转为5-4-3-2-1

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
import java.util.*;

/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/

public class Solution {
/**
*
* @param head ListNode类
* @return ListNode类
*/
public ListNode reverseList (ListNode head) {
ListNode pre = null, post = head, tmp;
while (post != null) {
tmp = post.next;//临时工具人结点
post.next = pre;//反转
pre = post;//pre进1
post = tmp;//post进1
}
return pre;
}
}

25 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

例:图中给定树 {3,5,1,6,2,0,8,#,#,7,4} 中,节点6、节点4的最近公共祖先为5。

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
import java.util.*;

/*
* public class TreeNode {
* int val = 0;
* TreeNode left = null;
* TreeNode right = null;
* }
*/

public class Solution {
/**
*
* @param root TreeNode类
* @param p TreeNode类
* @param q TreeNode类
* @return TreeNode类
*/
public TreeNode nearestCommonAncestor (TreeNode root, TreeNode p, TreeNode q) {
if (root == null) {
return null;
}
if (root.val == p.val || root.val == q.val) {
return root;
}
TreeNode leftAns = nearestCommonAncestor(root.left, p, q),
rightAns = nearestCommonAncestor(root.right, p, q);
if (leftAns != null && rightAns != null) {
return root;
}
return leftAns != null ? leftAns : rightAns;
}
}

26 给定一个递增排序的数组,查找某个数字是否在数组中,如果在数组中,则返回该数字在数组中第一次出现的位置(从0开始);如果不在数组中,返回-1 。不需要考虑给定的数组不是递增的情况。务必使用二分查找的方式。

好像是错误的!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int binarySearch(int* arr, int arrLen, int a) {
// write code here
int left = 0;
int right = arrLen-1;
int mid = 0;
while(left<=right)
{
mid = (left+right)/2;
if(arr[mid]==a)
{
if(arr[mid-1]!=a)
return mid;
else
return mid-1;
}
else if(arr[mid]>a)
right = mid - 1;
else
left = mid + 1;
}
return -1;
}

27 请编写程序实现矩阵的乘法

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
import java.util.*;
public class Main {
private static int[][] matrixMuilty(int[][] a, int[][] b) {
int m = a.length, p = b.length, n = b[0].length;
int[][] res = new int[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
for (int k = 0; k < p; k++) {
res[i][j] += a[i][k] * b[k][j];
}
}
}
return res;
}

//输入三个整数,分别表示:第一个矩阵的 行 列【也就是第二个矩阵的行】 第二个矩阵的列
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
String[] ss = s.split(",");
int m = Integer.parseInt(ss[0]), p = Integer.parseInt(ss[1]), n = Integer.parseInt(ss[2]);
if (m == 0 || p == 0 || n == 0) {
sc.close();
return;
}
int[][] a = new int[m][p];
int[][] b = new int[p][n];
for (int i = 0; i < m; i++) {
s = sc.nextLine();
ss = s.split(",");
for (int j = 0; j < p; j++) {
a[i][j] = Integer.parseInt(ss[j]);
}
}
for (int i = 0; i < p; i++) {
s = sc.nextLine();
ss = s.split(",");
for (int j = 0; j < n; j++) {
b[i][j] = Integer.parseInt(ss[j]);
}
}
sc.close();
int[][] res = matrixMuilty(a, b);
for (int i = 0; i < m; i++) {
StringBuilder sb = new StringBuilder();
for (int j = 0; j < n; j++) {
sb.append(res[i][j]).append(",");
}
System.out.println(sb.substring(0, sb.length() - 1));
}
}
}

28 求出一个正整数转换成二进制后的数字“1”的个数。

例:数字23转为二进制为 10111,其中1的个数为4

1
2
3
4
5
public static int binaryTo(int num) { int sum = 0;  while (num > 0) {
sum += num % 2;
num = num / 2;
} return sum;
}

29 去除字符串中的重复字符,对于出现超过2次(包含2次)的字符,只保留第一个。

例:输入abcbdde,输出abcde。

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
import java.util.*;


public class Solution {
/**
*
* @param str string字符串
* @return string字符串
*/
public String removeDuplicatedChars (String str) {
// write code here
boolean[] isExistChar = new boolean[26];
boolean[] isExistNum = new boolean[10];
char[] chars = str.toCharArray();
StringBuilder sb = new StringBuilder();
for (char c : chars) {
//是字母
if(c >= 'a' && c <= 'z'){
if(!isExistChar[c - 'a']){
sb.append(c);
isExistChar[c - 'a'] = true;
}
}
//是数字
if(c >= '0' && c <= '9'){
if(!isExistNum[c - '0']){
sb.append(c);
isExistNum[c - '0'] = true;
}
}
}
return sb.toString();
}
}

30 给定一个整型数组,移除数组的某个元素使其剩下的元素乘积最大,如果数组出现相同的元素 ,请输出第一次出现的元素

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
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String[] nums = scanner.next().split(",");
int n = nums.length;
// dp[i] -> 除 nums[i] 以外的其余所有元素的乘积
BigDecimal[] dp = new BigDecimal[n];
dp[0] = BigDecimal.valueOf(1);
for (int i = 1; i < n; i++) {
dp[i] = dp[i - 1].multiply(BigDecimal.valueOf(Integer.parseInt(nums[i - 1])));
}
BigDecimal temp = BigDecimal.valueOf(1);
for (int i = n - 1; i >= 0; i--) {
dp[i] = dp[i].multiply(temp);
temp = temp.multiply(BigDecimal.valueOf(Integer.parseInt(nums[i])));
}
BigDecimal max = dp[0];
int idx = 0;
for (int i = 1; i < n; i++) {
if (dp[i].compareTo(max) > 0) {
max = dp[i];
idx = i;
}
}
System.out.println(idx);
}
}

31 给定一个整型正方形矩阵 Matrix,请把该矩阵调整成顺时针旋转90度的样子。

1
2
3
4
5
6
7
8
9
10
11
12
public static int[][] rotationMatrix(int[][] matrix) {
if (matrix != null && matrix.length > 0 && matrix.length == matrix[0].length) {
int[][] result = new int[matrix.length][matrix.length];
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[i].length; j++) {
result[i][j] = matrix[matrix.length - j - 1][i];
}
}
return result;
}
return null;
}

32 在字符串中找到第一个不重复的字符。

例:对于字符串“hellohehe”,第一个不重复的字符是“o”。如果每个字符都有重复,则抛出运行时异常。

1
2
3
4
5
6
7
8
9
10
11
12
public char findFirstNonRepeatChar (String str) {
int[] map = new int[128];
for (char c: str.toCharArray()) {
map[c]++;
}
for (char c: str.toCharArray()) {
if (map[c] == 1) {
return c;
}
}
throw new RuntimeException("没有只有一个的字符");
}

33.假设有N个用户,其中有些人是朋友,有些则不是。A和B是朋友,B和C是朋友,这样ABC就是一个朋友圈,请计算给定的朋友关系的朋友圈数。

给定一个 N * N 的矩阵 M,表示用户之间的朋友关系。如果M[i][j] = 1,表示已知第 i 个和 j 个人互为朋友关系,否则为不知道。你必须输出所有用户中的已知的朋友圈总数。

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
import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
String[][] relations = new String[n][n];
for (int i = 0; i < n; i++) {
relations[i] = scanner.next().split(",");
}
scanner.close();
System.out.println(friendCircle(relations));
}
private static int friendCircle(String[][] relations) {
int n = relations.length;
// graph[i] -> 用户 i 的所有朋友
Set[] graph = new HashSet[n];
for (int i = 0; i < n; i++) {
graph[i] = new HashSet();
for (int j = 0; j < n; j++) {
int relation = Integer.parseInt(relations[i][j]);
if (relation == 1) {
graph[i].add(j);
}
}
}
int ans = 0;
boolean[] visited = new boolean[n];
for (int i = 0; i < n; i++) {
if (!visited[i]) {
ans++;
dfs(graph, i, visited);
}
}
return ans;
}
private static void dfs(Set[] graph, int cur, boolean[] visited) {
visited[cur] = true;
for (int next : graph[cur]) {
if (!visited[next]) {
dfs(graph, next, visited);
}
}
}
}

34 假设有个文件,文件的每一行是书信息数据,分4个部分用逗号(,)进行分割,格式如下

id,category,words,updatetime

id 表示书id,long类型,id不重复;

category 表示书的分类,int类型,请注意全部数据的分类只有几个

words 表示书的字数,int类型

updatetime 表示书的更新时间 ,格式为2020-02-01 23:00:00

请编写程序对文件数据进行排序后输出id,排序优先级为: category>updatetime > words > id , 增序排序

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
77
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Scanner;

public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
Book[] books = new Book[n];
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
for (int i = 0; i < n; i++) {
String[] row = scanner.next().split(",");
try {
String updateTime = row[3] + " " + scanner.next();
Book book =
new Book(
Long.parseLong(row[0]),
Integer.parseInt(row[1]),
Integer.parseInt(row[2]),
dateFormat.parse(updateTime));
books[i] = book;
} catch (ParseException e) {
e.printStackTrace();
}
}
scanner.close();
// 升序排列。字段优先级:category > updateTime > words > id
Arrays.sort(books, (book1, book2) -> {
if (book1.getCategory() != book2.getCategory()) {
return book1.getCategory() - book2.getCategory();
}
long update1 = book1.getUpdateTime().getTime(), update2 = book2.getUpdateTime().getTime();
if (update1 != update2) {
return (int) (update1 - update2);
}
if (book1.getWords() != book2.getWords()) {
return book1.getWords() - book2.getWords();
}
return (int) (book1.getId() - book2.getId());
});
for (int i = 0; i < n; i++) {
System.out.println(books[i].getId());
}
}
}

class Book {
private final long id;
private final int category;
private final int words;
private final Date updateTime;

public Book(long id, int category, int words, Date updateTime) {
this.id = id;
this.category = category;
this.words = words;
this.updateTime = updateTime;
}

public long getId() {
return this.id;
}

public int getCategory() {
return this.category;
}

public int getWords() {
return this.words;
}

public Date getUpdateTime() {
return this.updateTime;
}
}

35请使用堆栈这一个数据结构实现简单FIFO(先入先出)队列,队列要实现两个方法: push、pop。

为自动测试方便,使用每行输入模拟操作:

1) push 1 表明向队列里面新增一个元素 1 , push 和元素之间用空格表示;

2) pop 表明输出当前队列里面的第一个元素,如果当前队列为空请输出null

请将每个输出以英文逗号拼接到一个字符串中。

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
static class MyQueue {
int[] queue;
int lo, hi, size, capacity;

public MyQueue(int n) {
this.lo = this.hi = this.size = 0;
this.capacity = n;
this.queue = new int[n];
}

public MyQueue() {
this(10);
}

public void push(int val) {
if (hi == capacity) {
if (lo > 0) {
int idx = 0;
for (int i = lo; i < hi; i++) {
queue[idx++] = queue[i];
}
lo = 0;
hi = idx;
} else {
//扩容
int[] newQueue = new int[capacity * 2];
System.arraycopy(queue, 0, newQueue, 0, capacity);
this.queue = newQueue;
}
}
this.queue[hi++] = val;
}

public int pop() {
if (lo == hi) return -1;
else {
return this.queue[lo++];
}
}

}

public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// int[] nums = Arrays.stream().mapToInt(Integer::parseInt).toArray();
String[] ss = scanner.nextLine().split(",");
YuewenJavaTest.MyQueue myQueue = new YuewenJavaTest.MyQueue();
for (String s: ss) {
if (s.startsWith("push")) {
myQueue.push(Integer.parseInt(s.split(" ")[1]));
} else {
System.out.println(myQueue.pop());
}
}

scanner.close();
}

36 在和外部公司联调HTTP接口时,对方要求调用的接口需要计算token,给到的规则如下:

1) 所有的参数值必须经过urlencode,编码为utf-8;

2) 对编码后数据按照key值进行字典升序排序;

3)将所有的参数按照排序的顺序拼接成字符串 ,格式如下: k1=v1&k2=v2&k3=v3;

\4) 将第三步的算出的值计算md5值,md5加密的值小写

请你编写一段方法计算token值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public String getToken(Map<String, String> params) {
List<String> ss = params.keySet().stream().sorted().map(
k -> {
String v = params.get(k);
String kv = null;
try {
kv = k + "=" + URLEncoder.encode(v, "utf-8");
} catch (Exception e) {

}
return kv;
}
).collect(Collectors.toList());
String token = null;
try {
token = new String(MessageDigest.getInstance("md5").digest(String.join("&", ss).getBytes()));
} catch (Exception e) {
}
return token;
}
]]>
+ 阅文笔试复习

1.详细描述ThreadPoolExecutor的各个参数的含义,介绍一个任务提交到线程池后的执行流程

2.请简要说明Servlet中的生命周期

1.Servlet初始化后调用Init()方法

2.Servlet调用service()方法来处理客户端的请求。

3.Servlet销毁前调用destroy()方法终止

3.开启两个线程A,B,打印1到10 线程A打印奇数(1,3,5,7,9),线程B打印偶数(2,4,6,8,10).

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
package com.yuewen;

import java.util.Scanner;
import java.util.concurrent.locks.LockSupport;

public class ABXianC {
static Thread thread1;
static Thread thread2;
public static void main(String[] args) {
thread1 = new Thread(() -> {
for (int i = 1; i <= 9; i += 2) {
System.out.println(i);
LockSupport.unpark(thread2);
LockSupport.park();
}
});
thread2 = new Thread(() -> {
for (int i = 2; i <= 10; i = i + 2) {
LockSupport.park();
System.out.println(i);
LockSupport.unpark(thread1);
}
});
thread1.start();
thread2.start();
}

}

4.请编写代码实现单例模式,类名为Singletion

1.饿汉模式

1
2
3
4
5
6
public class Singleton{
static private Singleton instance = new Singleton();//因为无法实例化,所以必须是静态的
static public Singleton getInstance(){
return instance;
}
}

2.懒汉线程安全

1
2
3
4
5
6
7
8
9
10
11
12
package com.yuewen;

import java.util.Scanner;

public class Singletion {
private static Singleton instance;
private Singletion(){};
public static synchronized Singleton getInstance(){
if(instance == null) instance = new Singleton();
return instance;
}
}

5.写一个Map转换成JavaBean的工具类方法,实现如下mapToObject方法(使用Java反射,不允许使用第三方库)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static Object mapToObject(Map<String,Object> map,Class<?> beanClass){
if(map == null) return null;
Object obj = null;
try{
obj = beanClass.newInstance();
Field[] fields = obj.getClass().getDeclaredFields();
for(Field field : fields)
{
int mod = field.getModifiers();
if(Modifier.isStatic(mod) || Modifier.isFinal(mod)){
continue;
}
field.setAccessible(true);
field.set(obj,map.get(field.getName()));
}
catch(Exception e)
e.printStackTrace();
}
return obj;
}
}

6.数据库操作是我们经常使用的一个技能, 请你完成一个简单的用户密码验证过程 ,给定的条件如下:

数据库中存在个用户表:users ,表结构如下:

1
2
3
4
5
6
7
CREATE TABLE `users` (
`uid` bigint(20) NOT NULL COMMENT '用户ID',
`user_name` varchar(32) NOT NULL COMMENT '用户账号',
`password` varchar(64) NOT NULL COMMENT '用户混淆密码',
PRIMARY KEY (`uid`),
UNIQUE KEY `u_user_name` (`user_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'

完善以下方法

public boolean verifyPassword(String username,String password) {
Connection con = getConnection () ;// getConnection() 方法是个已有的方法可以获取到数据库连接 ,

// here is your code
}

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
public boolean verifyPassword(String username,String password){
Connection con=getConnection;
String sql="SELECT password FROM users WHERE user_name=?";
PreparedStatement pst=null;
ResultSet rs=null;
boolean flag=false;
try{
pst=con.prepareStatement(sql);
pst.setObject(1,username);
rs=pst.executeQuery();
while(rs.next()){
if(rs.getString("password").equals(password)){
flag=true;
}
}
}catch(ClassNotFoundException e){
e.printStackTrace();
}catch (SQLException e){
e.printStackTrace();
}finally {
try{
if(rs!=null) rs.close();
if(pst!=null) pst.close();
if(con!=null) con.close();
}catch(SQLException e){
e.printStackTrace();
}
}
return flag;
}

7.介绍HashMap的数据结构、扩容机制,HashMap与Hashtable的区别,是否是线程安全的,并介绍ConcurrentHashMap的实现机制。

HashMap JDK1.8之前 数组+链表

JDK1.8之后 数组+链表+红黑树

1.数组结构

数组用于存储内容,链表(红黑树)用于解决hash冲突。如果链表长度大于阈值8,但是当前数组长度小于树化阈值64,则进行数组扩容操作;如果数组长度大于树化阈值64,则进行链表树化操作,将单向链表转化为红黑树结构。

2.扩容机制:

如果不指定容量,则初始容量默认为16。如果指定容量,则初始容量设置为大于指定容量的最小2的幂数。当当前容量大于容量*负载因子(默认为0.75)时进行扩容操作,扩容为原容量的2倍。

3.HashMap与HashTable的区别

1)数据结构区别:HashMap为数组+链表(红黑树),HashTable为数组+链表,HashTable没有树化操作。

2)扩容机制区别:未指定容量情况下,HashMap容量默认16,每次扩容为2n(n:原容量)。HashTable容量默认为11,每次扩容为2n+1(n:原容量)。指定容量情况下,HashMap将保证容量为2的幂数,HashTable将直接使用指定容量。

3)数据插入方式的区别:当发生hash冲突时,HashMap使用尾插法插入链表,HashTable使用头插法插入链表。

4)线程安全区别:HashMap是非线程安全的,HashTable因为使用synchronized修饰方法,所以HashTable是线程安全的。

ConcurrentHashMap的实现机制

1)ConcurrentHashMap通过synchronized关键字和CAS操作实现线程安全,若插入的槽没有数据,使用CAS操作执行插入操作,若插入的槽有数据,通过synchronized锁住链表的头节点,从而实现效率与线程安全的平衡

8.介绍数据库连接池的实现方式。如何从连接池中获取连接、将连接放回连接池?使用连接池的优势是什么?列举一下自己用过的连接池。

连接池实现原理:

1.用户给servlet发送请求,请求Dao要Connection

2.Dao从“连接池”中取出Connection资源,与DB的通讯

3.当用户离开之后,释放该Connection,那么该Connection被释放到连接池中,等待下一个用户来

Demo目标:

通过简单的增删改查来做到下面几个关于连接池的方式,让我们更了解几种优化的方式

1.自定义一个Pool,来实现类似于现在开源连接池为我们做的一些操作

2.使用Tomcat内置的连接池(apache dbcp)

3.使用DBCP数据库连接池

4.使用C3P0数据库连接池(推荐)

数据库连接池技术带来的优势

1. 资源重用

由于数据库连接得到重用,避免了频繁创建、释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增进了系统运行环境的平稳性(减少内存碎片以及数据库临时进程/线程的数量)。

2. 更快的系统响应速度

数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而缩减了系统整体响应时间。

3. 新的资源分配手段

对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接的配置,实现数据库连接池技术,几年钱也许还是个新鲜话题,对于目前的业务系统而言,如果设计中还没有考虑到连接池的应用,那么…….快在设计文档中加上这部分的内容吧。某一应用最大可用数据库连接数的限制,避免某一应用独占所有数据库资源。

4. 统一的连接管理,避免数据库连接泄漏

在较为完备的数据库连接池实现中,可根据预先的连接占用超时设定,强制收回被占用连接。从而避免了常规数据库连接操作中可能出现的资源泄漏。一个最小化的数据库连接池实现:

9.什么是死锁?JAVA程序中什么情况下回出现死锁?如何避免出现死锁?

死锁是一种特定的程序状态,在实体之间,由于循环依赖导致彼此一直处于等待之中,没有任何个体可以继续前进。死锁不仅仅会发生在线程之间,存在资源独占的进程之间同样也可能出现死锁。通常来说,我们大多是聚焦在多线程场景中的死锁,指两个或多个线程之间,由于互相持有对方需要的锁,而永久处于阻塞的状态。

基本上死锁的发生是因为:互斥条件,类似Java中Monitor都是独占的,要么是我用,要么是你用。互斥条件是长期持有的,在使用结束之前,自己不会释放,也不能被其它线程抢占。循环依赖关系,两个或者多个个体之间出现了锁的链条环。免死锁的思路和方法。****1、如果可能的话,尽量避免使用多个锁,并且只有需要时才持有锁。2、如果必须使用多个锁,尽量设计好锁的获取顺序。

3、使用带超时的方法,为程序带来更多可控性

10. 分布式锁有几种实现方式,并介绍每种方式的优缺点。

分布式锁一般有三种实现方式:
1、 数据库锁
2、基于Redis的分布式锁
3、基于ZooKeeper的分布式锁

11. 什么是TCP粘包拆包?为什么会出现粘包拆包?如何在应用层面解决此问题?

​ 如果客户端连续不断的向服务端发送数据包时,服务端接收的数据会出现两个数据包粘在一起的情况,这就是TCP协议中经常会遇到的粘包以及拆包的问题。

1、TCP是基于字节流的,虽然应用层和传输层之间的数据交互是大小不等的数据块,但是TCP把这些数据块仅仅看成一连串无结构的字节流,没有边界;

2、在TCP的首部没有表示数据长度的字段,

基于上面两点,在使用TCP传输数据时,才有粘包或者拆包现象发生的可能。

解决

1、发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。
2、发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。
3、可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。

12 请大致描述一下BIO,AIO和NIO的区别?

BIO:同步阻塞式IO,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
NIO:同步非阻塞式IO,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
AIO:异步非阻塞式IO,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。

13 在JAVA语法中加载类的的方式有哪些?

1、创建类的实例(开辟地址空间)

2、访问某个静态类或接口的静态常量,或者对该静态变量赋值(类初始化)

3、调用类的静态访问(new,也会占用空间)

4、反射(类初始化)

5、初始化一个类的子类(继承)

6、JAVA虚拟机启动被称标明为启动类的类

7、调用某个 ClassLoader 实例的 loadClass() 方法(类不会初始化)

14 建立三个线程A、B、C,A线程打印10次字母A,B线程打印10次字母B,C线程打印10次字母C,但是要求三个线程同时运行,并且实现交替打印,即按照ABCABCABC的顺序打印。

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
package com.yuewen;

import java.util.Scanner;
import java.util.concurrent.locks.LockSupport;
public class ABC {
static Thread A, B, C;

public static void main(String[] args) {
A = new Thread(() -> {
for (int i = 0; i < 10; i++) {
LockSupport.park();
System.out.print("A");
LockSupport.unpark(B);
}
});
B = new Thread(() -> {
for (int i = 0; i < 10; i++) {
LockSupport.park();
System.out.print("B");
LockSupport.unpark(C);
}
});
C = new Thread(() -> {
for (int i = 0; i < 10; i++) {
LockSupport.unpark(A);
LockSupport.park();
System.out.print("C");
}
});
A.start();
B.start();
C.start();
}
}

15 请列举5个spring框架中的注解,并说明注解的用法以及使用场景

16 给定一组自然数,数字的值有可能会大于2^64 ,要求计算出所有数字的和

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
import org.junit.Test;

import java.util.ArrayList;

public class Solution {

/**
* 计算一组数字的和
* @param numbers string字符串ArrayList 一组自然数
* @return string字符串
*/
public String sum (ArrayList<String> numbers) {
// write code here

String result="0";
for (String number : numbers) {

if(number==null||number.length()==0){
continue;
}
int resultLen = result.length();
int curNumLen = number.length();
int sum=0;
int remain;
StringBuilder stringBuilder = new StringBuilder();
while (resultLen>0||curNumLen>0){

int resultNum=0;
if(resultLen>0){
resultNum = result.charAt(--resultLen) - '0';
}

int curNum=0;
if(curNumLen>0){
curNum = number.charAt(--curNumLen) - '0';
}

sum=sum+resultNum+curNum;
remain=sum%10;
stringBuilder.append(remain);
sum/=10;
}

if(sum!=0){
stringBuilder.append(sum);
}
result=stringBuilder.reverse().toString();


}

return result;
}

@Test
public void test(){

String num1="123456";
String num2="123456789";
String num3="123456789123";
ArrayList<String> strings = new ArrayList<>();
strings.add(num1);
strings.add(num2);
strings.add(num3);
System.out.println(sum(strings));
}
}

17 给定一个int数字 要求计算出int数字对应的二进制中1的个数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.yuewen;

import java.util.Scanner;

public class Erjinzhi {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int ans = 0;
while (n > 0){
n = n & (n-1);
ans++;
}
System.out.println(ans);
}

}

18 根据产品策略某本书可以设置包月到期时间,需要计算指定时间到包月到期时间还有多少分钟,不足60S的不计入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static String dateSub(String a, String b) {
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sdf.parse(a);
LocalDateTime t1 = LocalDateTime.parse(a, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),
t2 = LocalDateTime.parse(b, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
long pass = t1.until(t2, ChronoUnit.MINUTES);
return String.valueOf(Math.abs(pass));
} catch (Exception e) {
System.out.println(String.format("格式转化错误:%s, 检查是否格式输入错误", e.getMessage()));
}
return "0";
}

public static void main(String[] args) {
System.out.println("请输入指定的两个日期【小者在前,大者在后,格式:yyyy-MM-dd hh:mm:ss】:");
Scanner sc = new Scanner(System.in);
String a = sc.nextLine(), b = sc.nextLine();
sc.close();
System.out.println(dateSub(a, b));
}

19 map是一种开发过程中经常使用的k-v数据结构,有个map保存了书名和书字数的关系,编写代码对map里面的书按照字数进行升序排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static Map<String, Integer> sortMap(Map<String, Integer> map) {
TreeMap<Integer, List<String>> treeMap = new TreeMap<>();
map.entrySet().forEach(entry -> {
List<String> indexList = treeMap.computeIfAbsent(entry.getValue(), k -> new ArrayList<>());
indexList.add(entry.getKey());
});
Map<String, Integer> result = new ListOrderedMap();
treeMap.entrySet().forEach(entry -> {
entry.getValue().forEach(key -> result.put(key, map.get(key)));
});
return result;
}

public static Map<String, Integer> sortMap2(Map<String, Integer> map) {
Map result = new ListOrderedMap();
map.entrySet().stream().
sorted(Map.Entry.comparingByValue()).
forEachOrdered(entry -> result.put(entry.getKey(), entry.getValue()));

return result;
}


20 起点APP上允许用户对作品进行评论,为了防止用户恶意评论,发表不当内容,需要对用户发布的内容进行过滤,请写程序过滤用户发布内容中带有的QQ号(6~10位数字组成) 允许对内容严格操作,如用户发表了 作者大大666666,为你点赞 ,经过过滤后也可以为作者大大****,为你点赞 ,将666666过滤掉了。

把6-10位的数字替换成””;

1
2
3
public static String filterQQ(String s) {
return s.replaceAll("\\d{6,10}","");
}

21 质数(又称素数),是指在大于1的自然数中,除了1和它本身外,不能被其他自然数整除(除0以外)的数称之为素数(质数)。请写个程序判断输入的数字是否是质数,如果是素数请输出:true,不是请输出false

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package leecode;

public class IsPrimeDemo {
public static boolean isPrime(int n){
if (n < 1){
return false;
}
int i = 2;
int end = (int) Math.sqrt(n);
while (i <= end ){
if (n % i == 0){
return false;
}
++i;
}
return true;
}

public static void main(String[] args) {
int n = 7;
System.out.println(isPrime(n));
}
}

22 有 n 个台阶,你一次能走 1 个或者 2 个台阶,那么请问,走完这 n 个台阶共有几种方式?

经典爬楼梯问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.yuewen;

import java.util.Scanner;

public class ZouTaiJie {
public static void main(String[] args) {

Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int[] dp = new int[n];
int i = 2;
dp[0] = 1;
dp[1] = 2;
while (i < n)
{
dp[i] = dp[i-1] + dp[i-2];
i++;
}
System.out.println(dp[n-1]);
}

}

23 给定一个字符串,返回这个字符串中有多少个回文子串。两个相同的回文子串出现在不同的位置,认为是2个回文子串。a、aa、aaa、aba、aabaa、abcba均认为是回文子串。

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
import java.util.*;


public class Solution {
/**
*
* @param str string字符串
* @return int整型
*/
public int palindromeCount (String str) {
int ans = 0;
for (int center = 0; center < str.length(); center++) {
ans += expand(str, center, center) + expand(str, center, center + 1);
}
return ans;
}

private int expand(String str, int left, int right) {
int ans = 0;
while (left >= 0 && right < str.length() && str.charAt(left) == str.charAt(right)) {
ans++;
left--;
right++;
}
return ans;
}
}

24 将一个给定的单链表反转,例:1-2-3-4-5,反转为5-4-3-2-1

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
import java.util.*;

/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/

public class Solution {
/**
*
* @param head ListNode类
* @return ListNode类
*/
public ListNode reverseList (ListNode head) {
ListNode pre = null, post = head, tmp;
while (post != null) {
tmp = post.next;//临时工具人结点
post.next = pre;//反转
pre = post;//pre进1
post = tmp;//post进1
}
return pre;
}
}

25 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

例:图中给定树 {3,5,1,6,2,0,8,#,#,7,4} 中,节点6、节点4的最近公共祖先为5。

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
import java.util.*;

/*
* public class TreeNode {
* int val = 0;
* TreeNode left = null;
* TreeNode right = null;
* }
*/

public class Solution {
/**
*
* @param root TreeNode类
* @param p TreeNode类
* @param q TreeNode类
* @return TreeNode类
*/
public TreeNode nearestCommonAncestor (TreeNode root, TreeNode p, TreeNode q) {
if (root == null) {
return null;
}
if (root.val == p.val || root.val == q.val) {
return root;
}
TreeNode leftAns = nearestCommonAncestor(root.left, p, q),
rightAns = nearestCommonAncestor(root.right, p, q);
if (leftAns != null && rightAns != null) {
return root;
}
return leftAns != null ? leftAns : rightAns;
}
}

26 给定一个递增排序的数组,查找某个数字是否在数组中,如果在数组中,则返回该数字在数组中第一次出现的位置(从0开始);如果不在数组中,返回-1 。不需要考虑给定的数组不是递增的情况。务必使用二分查找的方式。

好像是错误的!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int binarySearch(int* arr, int arrLen, int a) {
// write code here
int left = 0;
int right = arrLen-1;
int mid = 0;
while(left<=right)
{
mid = (left+right)/2;
if(arr[mid]==a)
{
if(arr[mid-1]!=a)
return mid;
else
return mid-1;
}
else if(arr[mid]>a)
right = mid - 1;
else
left = mid + 1;
}
return -1;
}

27 请编写程序实现矩阵的乘法

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
import java.util.*;
public class Main {
private static int[][] matrixMuilty(int[][] a, int[][] b) {
int m = a.length, p = b.length, n = b[0].length;
int[][] res = new int[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
for (int k = 0; k < p; k++) {
res[i][j] += a[i][k] * b[k][j];
}
}
}
return res;
}

//输入三个整数,分别表示:第一个矩阵的 行 列【也就是第二个矩阵的行】 第二个矩阵的列
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
String[] ss = s.split(",");
int m = Integer.parseInt(ss[0]), p = Integer.parseInt(ss[1]), n = Integer.parseInt(ss[2]);
if (m == 0 || p == 0 || n == 0) {
sc.close();
return;
}
int[][] a = new int[m][p];
int[][] b = new int[p][n];
for (int i = 0; i < m; i++) {
s = sc.nextLine();
ss = s.split(",");
for (int j = 0; j < p; j++) {
a[i][j] = Integer.parseInt(ss[j]);
}
}
for (int i = 0; i < p; i++) {
s = sc.nextLine();
ss = s.split(",");
for (int j = 0; j < n; j++) {
b[i][j] = Integer.parseInt(ss[j]);
}
}
sc.close();
int[][] res = matrixMuilty(a, b);
for (int i = 0; i < m; i++) {
StringBuilder sb = new StringBuilder();
for (int j = 0; j < n; j++) {
sb.append(res[i][j]).append(",");
}
System.out.println(sb.substring(0, sb.length() - 1));
}
}
}

28 求出一个正整数转换成二进制后的数字“1”的个数。

例:数字23转为二进制为 10111,其中1的个数为4

1
2
3
4
5
public static int binaryTo(int num) { int sum = 0;  while (num > 0) {
sum += num % 2;
num = num / 2;
} return sum;
}

29 去除字符串中的重复字符,对于出现超过2次(包含2次)的字符,只保留第一个。

例:输入abcbdde,输出abcde。

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
import java.util.*;


public class Solution {
/**
*
* @param str string字符串
* @return string字符串
*/
public String removeDuplicatedChars (String str) {
// write code here
boolean[] isExistChar = new boolean[26];
boolean[] isExistNum = new boolean[10];
char[] chars = str.toCharArray();
StringBuilder sb = new StringBuilder();
for (char c : chars) {
//是字母
if(c >= 'a' && c <= 'z'){
if(!isExistChar[c - 'a']){
sb.append(c);
isExistChar[c - 'a'] = true;
}
}
//是数字
if(c >= '0' && c <= '9'){
if(!isExistNum[c - '0']){
sb.append(c);
isExistNum[c - '0'] = true;
}
}
}
return sb.toString();
}
}

30 给定一个整型数组,移除数组的某个元素使其剩下的元素乘积最大,如果数组出现相同的元素 ,请输出第一次出现的元素

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
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String[] nums = scanner.next().split(",");
int n = nums.length;
// dp[i] -> 除 nums[i] 以外的其余所有元素的乘积
BigDecimal[] dp = new BigDecimal[n];
dp[0] = BigDecimal.valueOf(1);
for (int i = 1; i < n; i++) {
dp[i] = dp[i - 1].multiply(BigDecimal.valueOf(Integer.parseInt(nums[i - 1])));
}
BigDecimal temp = BigDecimal.valueOf(1);
for (int i = n - 1; i >= 0; i--) {
dp[i] = dp[i].multiply(temp);
temp = temp.multiply(BigDecimal.valueOf(Integer.parseInt(nums[i])));
}
BigDecimal max = dp[0];
int idx = 0;
for (int i = 1; i < n; i++) {
if (dp[i].compareTo(max) > 0) {
max = dp[i];
idx = i;
}
}
System.out.println(idx);
}
}

31 给定一个整型正方形矩阵 Matrix,请把该矩阵调整成顺时针旋转90度的样子。

1
2
3
4
5
6
7
8
9
10
11
12
public static int[][] rotationMatrix(int[][] matrix) {
if (matrix != null && matrix.length > 0 && matrix.length == matrix[0].length) {
int[][] result = new int[matrix.length][matrix.length];
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[i].length; j++) {
result[i][j] = matrix[matrix.length - j - 1][i];
}
}
return result;
}
return null;
}

32 在字符串中找到第一个不重复的字符。

例:对于字符串“hellohehe”,第一个不重复的字符是“o”。如果每个字符都有重复,则抛出运行时异常。

1
2
3
4
5
6
7
8
9
10
11
12
public char findFirstNonRepeatChar (String str) {
int[] map = new int[128];
for (char c: str.toCharArray()) {
map[c]++;
}
for (char c: str.toCharArray()) {
if (map[c] == 1) {
return c;
}
}
throw new RuntimeException("没有只有一个的字符");
}

33.假设有N个用户,其中有些人是朋友,有些则不是。A和B是朋友,B和C是朋友,这样ABC就是一个朋友圈,请计算给定的朋友关系的朋友圈数。

给定一个 N * N 的矩阵 M,表示用户之间的朋友关系。如果M[i][j] = 1,表示已知第 i 个和 j 个人互为朋友关系,否则为不知道。你必须输出所有用户中的已知的朋友圈总数。

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
import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
String[][] relations = new String[n][n];
for (int i = 0; i < n; i++) {
relations[i] = scanner.next().split(",");
}
scanner.close();
System.out.println(friendCircle(relations));
}
private static int friendCircle(String[][] relations) {
int n = relations.length;
// graph[i] -> 用户 i 的所有朋友
Set[] graph = new HashSet[n];
for (int i = 0; i < n; i++) {
graph[i] = new HashSet();
for (int j = 0; j < n; j++) {
int relation = Integer.parseInt(relations[i][j]);
if (relation == 1) {
graph[i].add(j);
}
}
}
int ans = 0;
boolean[] visited = new boolean[n];
for (int i = 0; i < n; i++) {
if (!visited[i]) {
ans++;
dfs(graph, i, visited);
}
}
return ans;
}
private static void dfs(Set[] graph, int cur, boolean[] visited) {
visited[cur] = true;
for (int next : graph[cur]) {
if (!visited[next]) {
dfs(graph, next, visited);
}
}
}
}

34 假设有个文件,文件的每一行是书信息数据,分4个部分用逗号(,)进行分割,格式如下

id,category,words,updatetime

id 表示书id,long类型,id不重复;

category 表示书的分类,int类型,请注意全部数据的分类只有几个

words 表示书的字数,int类型

updatetime 表示书的更新时间 ,格式为2020-02-01 23:00:00

请编写程序对文件数据进行排序后输出id,排序优先级为: category>updatetime > words > id , 增序排序

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
77
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Scanner;

public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
Book[] books = new Book[n];
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
for (int i = 0; i < n; i++) {
String[] row = scanner.next().split(",");
try {
String updateTime = row[3] + " " + scanner.next();
Book book =
new Book(
Long.parseLong(row[0]),
Integer.parseInt(row[1]),
Integer.parseInt(row[2]),
dateFormat.parse(updateTime));
books[i] = book;
} catch (ParseException e) {
e.printStackTrace();
}
}
scanner.close();
// 升序排列。字段优先级:category > updateTime > words > id
Arrays.sort(books, (book1, book2) -> {
if (book1.getCategory() != book2.getCategory()) {
return book1.getCategory() - book2.getCategory();
}
long update1 = book1.getUpdateTime().getTime(), update2 = book2.getUpdateTime().getTime();
if (update1 != update2) {
return (int) (update1 - update2);
}
if (book1.getWords() != book2.getWords()) {
return book1.getWords() - book2.getWords();
}
return (int) (book1.getId() - book2.getId());
});
for (int i = 0; i < n; i++) {
System.out.println(books[i].getId());
}
}
}

class Book {
private final long id;
private final int category;
private final int words;
private final Date updateTime;

public Book(long id, int category, int words, Date updateTime) {
this.id = id;
this.category = category;
this.words = words;
this.updateTime = updateTime;
}

public long getId() {
return this.id;
}

public int getCategory() {
return this.category;
}

public int getWords() {
return this.words;
}

public Date getUpdateTime() {
return this.updateTime;
}
}

35请使用堆栈这一个数据结构实现简单FIFO(先入先出)队列,队列要实现两个方法: push、pop。

为自动测试方便,使用每行输入模拟操作:

1) push 1 表明向队列里面新增一个元素 1 , push 和元素之间用空格表示;

2) pop 表明输出当前队列里面的第一个元素,如果当前队列为空请输出null

请将每个输出以英文逗号拼接到一个字符串中。

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
static class MyQueue {
int[] queue;
int lo, hi, size, capacity;

public MyQueue(int n) {
this.lo = this.hi = this.size = 0;
this.capacity = n;
this.queue = new int[n];
}

public MyQueue() {
this(10);
}

public void push(int val) {
if (hi == capacity) {
if (lo > 0) {
int idx = 0;
for (int i = lo; i < hi; i++) {
queue[idx++] = queue[i];
}
lo = 0;
hi = idx;
} else {
//扩容
int[] newQueue = new int[capacity * 2];
System.arraycopy(queue, 0, newQueue, 0, capacity);
this.queue = newQueue;
}
}
this.queue[hi++] = val;
}

public int pop() {
if (lo == hi) return -1;
else {
return this.queue[lo++];
}
}

}

public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// int[] nums = Arrays.stream().mapToInt(Integer::parseInt).toArray();
String[] ss = scanner.nextLine().split(",");
YuewenJavaTest.MyQueue myQueue = new YuewenJavaTest.MyQueue();
for (String s: ss) {
if (s.startsWith("push")) {
myQueue.push(Integer.parseInt(s.split(" ")[1]));
} else {
System.out.println(myQueue.pop());
}
}

scanner.close();
}

36 在和外部公司联调HTTP接口时,对方要求调用的接口需要计算token,给到的规则如下:

1) 所有的参数值必须经过urlencode,编码为utf-8;

2) 对编码后数据按照key值进行字典升序排序;

3)将所有的参数按照排序的顺序拼接成字符串 ,格式如下: k1=v1&k2=v2&k3=v3;

\4) 将第三步的算出的值计算md5值,md5加密的值小写

请你编写一段方法计算token值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public String getToken(Map<String, String> params) {
List<String> ss = params.keySet().stream().sorted().map(
k -> {
String v = params.get(k);
String kv = null;
try {
kv = k + "=" + URLEncoder.encode(v, "utf-8");
} catch (Exception e) {

}
return kv;
}
).collect(Collectors.toList());
String token = null;
try {
token = new String(MessageDigest.getInstance("md5").digest(String.join("&", ss).getBytes()));
} catch (Exception e) {
}
return token;
}
]]>
@@ -356,13 +356,13 @@ 2022-04-06T12:27:56.513Z 2022-04-06T12:28:18.055Z - 题干

1
2
3
4
5
6
秋天快到啦,天气慢慢凉爽了下来,所以实验室要组织去骊山进行一次野餐活动。

最底层的Lofipure被迫背背包给大家装各种零食,但是实验室的大佬们并不打算轻易放过Lofipure,他们打算把Lofipure的背包装的尽量满

现在知道Lofipure的背包容量为 V(正整数,0 <= V <= 20000),同时有 n 件小零食(0n<=30),每个小零食的重量。
现在在 n 个小零食中,任取若干个装入Lofipure的背包内,使得Lofipure背包的剩余空间为最小。借此达到压榨Lofipure的目的。

Input

输入:一个整数v,表示背包容量 一个整数n,表示有n个物品 接下来 n 个整数,分别表示这 n 个物品的各自体积

Output

输出:一个整数,表示背包最小的剩余空间

Sample Input

1
2
3
4
5
6
7
8
24
6
8
3
12
7
9
7

Sample Output

1
0

看到这道理的第一时刻想的是暴力枚举出所有零食混合装的重量 可复杂度太高 遂放弃。

然后看了一下大佬们的代码

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
#include<bits/stdc++.h>
#define int long long
#define MAXN 1000005
using namespace std;
int dp[20005];
signed main()
{
dp[0]=1;
int v,n;cin>>v>>n;
for(int i=1;i<=n;i++)
{
int t;cin>>t;
for(int j=v;j>=t;j--)
dp[j]|=dp[j-t];
}
for(int i=v;i>=0;i--)
{
if(dp[i])
{
cout<<v-i<<endl;
return 0;
}
}
}

刚开始我用Java实现的时候,

1
2
3
4
5
6
7
8
9

后面经过自己 ~~人眼比对~~ 发现这个错误 发现之后感到不解。

```dp [j]|=dp[j-t]```这个式子用来干嘛的呢

后面经过输出

```java
System.out.println(j+" "+(j-t)+" "+dp[j]+" "+dp[j - t]);

形式一下子就清楚了 dp是为了统计零食能够组成的重量

拿本题例子来说

输入 t = 8 时 只有dp[8] = dp[8] | dp[0] 才变成1 这代表着能够组成8的重量

输入 t = 3 时 有了dp[11] = dp[11] | dp[11 - 3] dp[3] = dp[3] | dp[0] 这次增加 11(8+3) 3(3+0)l两种可能

输入t = 12时 有dp[23] = 1 ,dp[20] = 1 dp[15] =1 dp[12] = 1

输入t = 7时 有 dp[22] = 1,dp[22] = 1 dp[19] = 1 dp[18] = 1 dp[15] = 1 dp[10] = 1

。。。。

到最后会使所有能够凑出重量的dp数都为1

从最大的量递减便利 当dp[i] 不为0 时 也就是为1 代表着这是能够装载着最大的重量 直接输出V-i

感叹算法的奇妙与精彩,只恨自己接触晚与学校氛围不好,蹉跎了许久时光。

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
package com.VG;

import java.util.Arrays;
import java.util.Scanner;

public class C {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int V = scanner.nextInt();
int N = scanner.nextInt();
int[] dp = new int[200005];
dp[0] = 1;
for (int i = 1; i <= N; i++) {
int t = scanner.nextInt();
for (int j = V; j >=t ; j--) {
dp[j] |= dp[j - t];
System.out.println(j+" "+(j-t)+" "+dp[j]+" "+dp[j - t]);
}
}
for (int i = V; i >= 0 ; i--) {
if(dp[i]!=0)
{
System.out.println(V- i);
return;
}
}

}
}

]]>
+ 题干

1
2
3
4
5
6
秋天快到啦,天气慢慢凉爽了下来,所以实验室要组织去骊山进行一次野餐活动。

最底层的Lofipure被迫背背包给大家装各种零食,但是实验室的大佬们并不打算轻易放过Lofipure,他们打算把Lofipure的背包装的尽量满

现在知道Lofipure的背包容量为 V(正整数,0 <= V <= 20000),同时有 n 件小零食(0<n<=30),每个小零食的重量。
现在在 n 个小零食中,任取若干个装入Lofipure的背包内,使得Lofipure背包的剩余空间为最小。借此达到压榨Lofipure的目的。

Input

输入:一个整数v,表示背包容量 一个整数n,表示有n个物品 接下来 n 个整数,分别表示这 n 个物品的各自体积

Output

输出:一个整数,表示背包最小的剩余空间

Sample Input

1
2
3
4
5
6
7
8
24
6
8
3
12
7
9
7

Sample Output

1
0

看到这道理的第一时刻想的是暴力枚举出所有零食混合装的重量 可复杂度太高 遂放弃。

然后看了一下大佬们的代码

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
#include<bits/stdc++.h>
#define int long long
#define MAXN 1000005
using namespace std;
int dp[20005];
signed main()
{
dp[0]=1;
int v,n;cin>>v>>n;
for(int i=1;i<=n;i++)
{
int t;cin>>t;
for(int j=v;j>=t;j--)
dp[j]|=dp[j-t];
}
for(int i=v;i>=0;i--)
{
if(dp[i])
{
cout<<v-i<<endl;
return 0;
}
}
}

刚开始我用Java实现的时候,

1
2
3
4
5
6
7
8
9

后面经过自己 ~~人眼比对~~ 发现这个错误 发现之后感到不解。

```dp [j]|=dp[j-t]```这个式子用来干嘛的呢

后面经过输出

```java
System.out.println(j+" "+(j-t)+" "+dp[j]+" "+dp[j - t]);

形式一下子就清楚了 dp是为了统计零食能够组成的重量

拿本题例子来说

输入 t = 8 时 只有dp[8] = dp[8] | dp[0] 才变成1 这代表着能够组成8的重量

输入 t = 3 时 有了dp[11] = dp[11] | dp[11 - 3] dp[3] = dp[3] | dp[0] 这次增加 11(8+3) 3(3+0)l两种可能

输入t = 12时 有dp[23] = 1 ,dp[20] = 1 dp[15] =1 dp[12] = 1

输入t = 7时 有 dp[22] = 1,dp[22] = 1 dp[19] = 1 dp[18] = 1 dp[15] = 1 dp[10] = 1

。。。。

到最后会使所有能够凑出重量的dp数都为1

从最大的量递减便利 当dp[i] 不为0 时 也就是为1 代表着这是能够装载着最大的重量 直接输出V-i

感叹算法的奇妙与精彩,只恨自己接触晚与学校氛围不好,蹉跎了许久时光。

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
package com.VG;

import java.util.Arrays;
import java.util.Scanner;

public class C {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int V = scanner.nextInt();
int N = scanner.nextInt();
int[] dp = new int[200005];
dp[0] = 1;
for (int i = 1; i <= N; i++) {
int t = scanner.nextInt();
for (int j = V; j >=t ; j--) {
dp[j] |= dp[j - t];
System.out.println(j+" "+(j-t)+" "+dp[j]+" "+dp[j - t]);
}
}
for (int i = V; i >= 0 ; i--) {
if(dp[i]!=0)
{
System.out.println(V- i);
return;
}
}

}
}

]]>
<p>题干</p> -<figure class="highlight excel"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br>< +<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span>< @@ -379,7 +379,7 @@ 2022-04-03T06:44:18.118Z 2022-04-03T09:33:29.183Z - AOP

什么是 AOP

面向切面编程(方面), 利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

通俗描述:不通过修改源代码方式,在主干功能里面添加新功能。

AOP 底层使用动态代理

两种情况的动态代理

1
newProxyInstance(ClassLoader loader,类<?>[] interfaces,InvacationHandle)

方法有三个参数

第一个参数:类加载器

第二个参数:增加方法所在的类。

第三个参数:

]]>
+ AOP

什么是 AOP

面向切面编程(方面), 利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

通俗描述:不通过修改源代码方式,在主干功能里面添加新功能。

AOP 底层使用动态代理

两种情况的动态代理

1
newProxyInstance(ClassLoader loader,类<?>[] interfaces,InvacationHandle)

方法有三个参数

第一个参数:类加载器

第二个参数:增加方法所在的类。

第三个参数:

]]>
@@ -401,7 +401,7 @@ 2022-04-02T13:32:25.724Z 2022-04-02T13:32:25.725Z - 回溯问题

解决一个回溯问题,实际上就是一个决策树的遍历过程,需要思考三个问题

回溯算法框架

1
2
3
4
5
6
7
8
9
10
result = []
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return

for 选择 in 选择列表:
做选择
backtrack(路径, 选择列表)
撤销选择

核心在于for循环里面的递归,在递归调用之前做选择,在递归调用之后撤销选择。

例子1 全排列问题

有n个数 每个数都只能用一次 求出所有能排列的可能性。全排列个数为n!个

PS:为了简单清晰起见,我们这次讨论的全排列问题不包含重复的数字

如果已知有多少个数的情况下,我们通常可以使用n层for循环暴力遍历所有数

例如3个数 我们可以暴力写法为

1
2
3
4
5
6
7
8
9
10
11
12
13
for(int i = 0;i < n;i++)
for(int j = 0;j < n;j++)
for(int k = 0;k < n;k++)
{
if(i!=j && i!=k && j!=k)
{
// 1 2 3 1 3 2 2 1 3 2 3 1 3 2 1 3 1 2
list.add(nums[i]);
list.add(nums[j]);
list.add(nums[k]);
res.add(list);
}
}

回溯数如下图所示,只要从根遍历这棵树,记录路径下的数字,其实就是所有的全排列,我们不妨把这棵树称之为回溯算法的决策树

为什么叫决策树呢,顾名思义,每次遍历的时候都需要做决策来去避开那些已经走过的路。

现在可以解答开头的几个名词:[2] 就是「路径」,记录你已经做过的选择;[1,3] 就是「选择列表」,表示你当前可以做出的选择;「结束条件」就是遍历到树的底层,在这里就是选择列表为空的时候。

如果明白了这几个名词,可以把「路径」和「选择」列表作为决策树上每个节点的属性,比如下图列出了几个节点的属性:

我们定义的backtrack函数就是指针一样,在这棵树上游走,同时要正确维护每个节点的属性,每当走到树的底层,其路径就是一个全排列。

再进一步,如何遍历一颗树,

1
2
3
4
5
6
void traverse(TreeNode root) {
for (TreeNode child : root.childern)
// 前序遍历需要的操作
traverse(child);
// 后序遍历需要的操作
}

前序遍历的代码在进入某一个节点之前的那个时间点执行,后序遍历代码在离开某个节点之后的那个时间点执行。

回想我们刚才说的,「路径」和「选择」是每个节点的属性,函数在树上游走要正确维护节点的属性,那么就要在这两个特殊时间点搞点动作:

回溯代码核心框架

1
2
3
4
5
6
7
8
9
for 选择 in 选择列表:
# 做选择
将该选择从选择列表移除
路径.add(选择)
backtrack(路径, 选择列表)
# 撤销选择
路径.remove(选择)
将该选择再加入选择列表

我们只要在递归之前做出选择,在递归之后撤销自己的选择,就能得到每个节点的选择路径和列表。

全排列代码

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
List<List<Integer>> res = new LinkedList<>();

/* 主函数,输入一组不重复的数字,返回它们的全排列 */
List<List<Integer>> permute(int[] nums) {
// 记录「路径」
LinkedList<Integer> track = new LinkedList<>();
backtrack(nums, track);
return res;
}

// 路径:记录在 track 中
// 选择列表:nums 中不存在于 track 的那些元素
// 结束条件:nums 中的元素全都在 track 中出现
void backtrack(int[] nums, LinkedList<Integer> track) {
// 触发结束条件
if (track.size() == nums.length) {
res.add(new LinkedList(track));
return;
}

for (int i = 0; i < nums.length; i++) {
// 排除不合法的选择
if (track.contains(nums[i]))
continue;
// 做选择
track.add(nums[i]);
// 进入下一层决策树
backtrack(nums, track);
// 取消选择
track.removeLast();
}
}

我们这里稍微做了些变通,没有显式记录「选择列表」,而是通过 numstrack 推导出当前的选择列表:

通过contains函数来判断该数是否已经被使用。

至此,我们就通过全排列问题详解了回溯算法的底层原理。当然,这个算法解决全排列不是很高效,应为对链表使用 contains 方法需要 O(N) 的时间复杂度。有更好的方法通过交换元素达到目的,但是难理解一些,这里就不写了,有兴趣可以自行搜索一下。

但是必须说明的是,不管怎么优化,都符合回溯框架,而且时间复杂度都不可能低于 O(N!),因为穷举整棵决策树是无法避免的。这也是回溯算法的一个特点,不像动态规划存在重叠子问题可以优化,回溯算法就是纯暴力穷举,复杂度一般都很高。

明白了全排列问题,就可以直接套回溯算法框架了,下面简单看看 N 皇后问题。

例子2 N皇后问题

题意

给你一个N*N的棋盘,,让你放置 N 个皇后,使得它们不能互相攻击。

PS:皇后可以攻击同一行、同一列、左上左下右上右下四个方向的任意单位。

这个问题本质上跟全排列问题差不多,决策树的每一层表示棋盘上的每一行;每个节点可以做出的选择是,在该行的任意一列放置一个皇后。

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
vector<vector<string>> res;

/* 输入棋盘边长 n,返回所有合法的放置 */
vector<vector<string>> solveNQueens(int n) {
// '.' 表示空,'Q' 表示皇后,初始化空棋盘。
vector<string> board(n, string(n, '.'));
backtrack(board, 0);
return res;
}

// 路径:board 中小于 row 的那些行都已经成功放置了皇后
// 选择列表:第 row 行的所有列都是放置皇后的选择
// 结束条件:row 超过 board 的最后一行
void backtrack(vector<string>& board, int row) {
// 触发结束条件
if (row == board.size()) {
res.push_back(board);
return;
}

int n = board[row].size();
for (int col = 0; col < n; col++) {
// 排除不合法选择
if (!isValid(board, row, col))
continue;
// 做选择
board[row][col] = 'Q';
// 进入下一行决策
backtrack(board, row + 1);
// 撤销选择
board[row][col] = '.';
}
}

IsValid()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* 是否可以在 board[row][col] 放置皇后? */
bool isValid(vector<string>& board, int row, int col) {
int n = board.size();
// 检查列是否有皇后互相冲突
for (int i = 0; i < n; i++) {
if (board[i][col] == 'Q')
return false;
}
// 检查右上方是否有皇后互相冲突
for (int i = row - 1, j = col + 1;
i >= 0 && j < n; i--, j++) {
if (board[i][j] == 'Q')
return false;
}
// 检查左上方是否有皇后互相冲突
for (int i = row - 1, j = col - 1;
i >= 0 && j >= 0; i--, j--) {
if (board[i][j] == 'Q')
return false;
}
return true;
}

函数 backtrack 依然像个在决策树上游走的指针,通过 row 和 col 就可以表示函数遍历到的位置,通过 isValid 函数可以将不符合条件的情况剪枝:

某种程度上说,动态规划的暴力求解阶段就是回溯算法。只是有的问题具有重叠子问题性质,可以用 dp table 或者备忘录优化,将递归树大幅剪枝,这就变成了动态规划。而今天的两个问题,都没有重叠子问题,也就是回溯算法问题了,复杂度非常高是不可避免的。

参考链接:https://leetcode-cn.com/problems/permutations/solution/hui-su-suan-fa-xiang-jie-by-labuladong-2/

]]>
+ 回溯问题

解决一个回溯问题,实际上就是一个决策树的遍历过程,需要思考三个问题

回溯算法框架

1
2
3
4
5
6
7
8
9
10
result = []
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return

for 选择 in 选择列表:
做选择
backtrack(路径, 选择列表)
撤销选择

核心在于for循环里面的递归,在递归调用之前做选择,在递归调用之后撤销选择。

例子1 全排列问题

有n个数 每个数都只能用一次 求出所有能排列的可能性。全排列个数为n!个

PS:为了简单清晰起见,我们这次讨论的全排列问题不包含重复的数字

如果已知有多少个数的情况下,我们通常可以使用n层for循环暴力遍历所有数

例如3个数 我们可以暴力写法为

1
2
3
4
5
6
7
8
9
10
11
12
13
for(int i = 0;i < n;i++)
for(int j = 0;j < n;j++)
for(int k = 0;k < n;k++)
{
if(i!=j && i!=k && j!=k)
{
// 1 2 3 1 3 2 2 1 3 2 3 1 3 2 1 3 1 2
list.add(nums[i]);
list.add(nums[j]);
list.add(nums[k]);
res.add(list);
}
}

回溯数如下图所示,只要从根遍历这棵树,记录路径下的数字,其实就是所有的全排列,我们不妨把这棵树称之为回溯算法的决策树

为什么叫决策树呢,顾名思义,每次遍历的时候都需要做决策来去避开那些已经走过的路。

现在可以解答开头的几个名词:[2] 就是「路径」,记录你已经做过的选择;[1,3] 就是「选择列表」,表示你当前可以做出的选择;「结束条件」就是遍历到树的底层,在这里就是选择列表为空的时候。

如果明白了这几个名词,可以把「路径」和「选择」列表作为决策树上每个节点的属性,比如下图列出了几个节点的属性:

我们定义的backtrack函数就是指针一样,在这棵树上游走,同时要正确维护每个节点的属性,每当走到树的底层,其路径就是一个全排列。

再进一步,如何遍历一颗树,

1
2
3
4
5
6
void traverse(TreeNode root) {
for (TreeNode child : root.childern)
// 前序遍历需要的操作
traverse(child);
// 后序遍历需要的操作
}

前序遍历的代码在进入某一个节点之前的那个时间点执行,后序遍历代码在离开某个节点之后的那个时间点执行。

回想我们刚才说的,「路径」和「选择」是每个节点的属性,函数在树上游走要正确维护节点的属性,那么就要在这两个特殊时间点搞点动作:

回溯代码核心框架

1
2
3
4
5
6
7
8
9
for 选择 in 选择列表:
# 做选择
将该选择从选择列表移除
路径.add(选择)
backtrack(路径, 选择列表)
# 撤销选择
路径.remove(选择)
将该选择再加入选择列表

我们只要在递归之前做出选择,在递归之后撤销自己的选择,就能得到每个节点的选择路径和列表。

全排列代码

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
List<List<Integer>> res = new LinkedList<>();

/* 主函数,输入一组不重复的数字,返回它们的全排列 */
List<List<Integer>> permute(int[] nums) {
// 记录「路径」
LinkedList<Integer> track = new LinkedList<>();
backtrack(nums, track);
return res;
}

// 路径:记录在 track 中
// 选择列表:nums 中不存在于 track 的那些元素
// 结束条件:nums 中的元素全都在 track 中出现
void backtrack(int[] nums, LinkedList<Integer> track) {
// 触发结束条件
if (track.size() == nums.length) {
res.add(new LinkedList(track));
return;
}

for (int i = 0; i < nums.length; i++) {
// 排除不合法的选择
if (track.contains(nums[i]))
continue;
// 做选择
track.add(nums[i]);
// 进入下一层决策树
backtrack(nums, track);
// 取消选择
track.removeLast();
}
}

我们这里稍微做了些变通,没有显式记录「选择列表」,而是通过 numstrack 推导出当前的选择列表:

通过contains函数来判断该数是否已经被使用。

至此,我们就通过全排列问题详解了回溯算法的底层原理。当然,这个算法解决全排列不是很高效,应为对链表使用 contains 方法需要 O(N) 的时间复杂度。有更好的方法通过交换元素达到目的,但是难理解一些,这里就不写了,有兴趣可以自行搜索一下。

但是必须说明的是,不管怎么优化,都符合回溯框架,而且时间复杂度都不可能低于 O(N!),因为穷举整棵决策树是无法避免的。这也是回溯算法的一个特点,不像动态规划存在重叠子问题可以优化,回溯算法就是纯暴力穷举,复杂度一般都很高。

明白了全排列问题,就可以直接套回溯算法框架了,下面简单看看 N 皇后问题。

例子2 N皇后问题

题意

给你一个N*N的棋盘,,让你放置 N 个皇后,使得它们不能互相攻击。

PS:皇后可以攻击同一行、同一列、左上左下右上右下四个方向的任意单位。

这个问题本质上跟全排列问题差不多,决策树的每一层表示棋盘上的每一行;每个节点可以做出的选择是,在该行的任意一列放置一个皇后。

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
vector<vector<string>> res;

/* 输入棋盘边长 n,返回所有合法的放置 */
vector<vector<string>> solveNQueens(int n) {
// '.' 表示空,'Q' 表示皇后,初始化空棋盘。
vector<string> board(n, string(n, '.'));
backtrack(board, 0);
return res;
}

// 路径:board 中小于 row 的那些行都已经成功放置了皇后
// 选择列表:第 row 行的所有列都是放置皇后的选择
// 结束条件:row 超过 board 的最后一行
void backtrack(vector<string>& board, int row) {
// 触发结束条件
if (row == board.size()) {
res.push_back(board);
return;
}

int n = board[row].size();
for (int col = 0; col < n; col++) {
// 排除不合法选择
if (!isValid(board, row, col))
continue;
// 做选择
board[row][col] = 'Q';
// 进入下一行决策
backtrack(board, row + 1);
// 撤销选择
board[row][col] = '.';
}
}

IsValid()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* 是否可以在 board[row][col] 放置皇后? */
bool isValid(vector<string>& board, int row, int col) {
int n = board.size();
// 检查列是否有皇后互相冲突
for (int i = 0; i < n; i++) {
if (board[i][col] == 'Q')
return false;
}
// 检查右上方是否有皇后互相冲突
for (int i = row - 1, j = col + 1;
i >= 0 && j < n; i--, j++) {
if (board[i][j] == 'Q')
return false;
}
// 检查左上方是否有皇后互相冲突
for (int i = row - 1, j = col - 1;
i >= 0 && j >= 0; i--, j--) {
if (board[i][j] == 'Q')
return false;
}
return true;
}

函数 backtrack 依然像个在决策树上游走的指针,通过 row 和 col 就可以表示函数遍历到的位置,通过 isValid 函数可以将不符合条件的情况剪枝:

某种程度上说,动态规划的暴力求解阶段就是回溯算法。只是有的问题具有重叠子问题性质,可以用 dp table 或者备忘录优化,将递归树大幅剪枝,这就变成了动态规划。而今天的两个问题,都没有重叠子问题,也就是回溯算法问题了,复杂度非常高是不可避免的。

参考链接:https://leetcode-cn.com/problems/permutations/solution/hui-su-suan-fa-xiang-jie-by-labuladong-2/

]]>
@@ -425,7 +425,7 @@ 2022-04-01T12:29:24.771Z 2022-04-02T11:58:36.671Z - 回溯问题

解决一个回溯问题,实际上就是一个决策树的遍历过程,需要思考三个问题

回溯算法框架

1
2
3
4
5
6
7
8
9
10
result = []
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return

for 选择 in 选择列表:
做选择
backtrack(路径, 选择列表)
撤销选择

核心在于for循环里面的递归,在递归调用之前做选择,在递归调用之后撤销选择。

例子1 全排列问题

有n个数 每个数都只能用一次 求出所有能排列的可能性。全排列个数为n!个

PS:为了简单清晰起见,我们这次讨论的全排列问题不包含重复的数字

如果已知有多少个数的情况下,我们通常可以使用n层for循环暴力遍历所有数

例如3个数 我们可以暴力写法为

1
2
3
4
5
6
7
8
9
10
11
12
13
for(int i = 0;i < n;i++)
for(int j = 0;j < n;j++)
for(int k = 0;k < n;k++)
{
if(i!=j && i!=k && j!=k)
{
// 1 2 3 1 3 2 2 1 3 2 3 1 3 2 1 3 1 2
list.add(nums[i]);
list.add(nums[j]);
list.add(nums[k]);
res.add(list);
}
}

回溯数如下图所示,只要从根遍历这棵树,记录路径下的数字,其实就是所有的全排列,我们不妨把这棵树称之为回溯算法的决策树

为什么叫决策树呢,顾名思义,每次遍历的时候都需要做决策来去避开那些已经走过的路。

现在可以解答开头的几个名词:[2] 就是「路径」,记录你已经做过的选择;[1,3] 就是「选择列表」,表示你当前可以做出的选择;「结束条件」就是遍历到树的底层,在这里就是选择列表为空的时候。

如果明白了这几个名词,可以把「路径」和「选择」列表作为决策树上每个节点的属性,比如下图列出了几个节点的属性:

我们定义的backtrack函数就是指针一样,在这棵树上游走,同时要正确维护每个节点的属性,每当走到树的底层,其路径就是一个全排列。

再进一步,如何遍历一颗树,

1
2
3
4
5
6
void traverse(TreeNode root) {
for (TreeNode child : root.childern)
// 前序遍历需要的操作
traverse(child);
// 后序遍历需要的操作
}

前序遍历的代码在进入某一个节点之前的那个时间点执行,后序遍历代码在离开某个节点之后的那个时间点执行。

回想我们刚才说的,「路径」和「选择」是每个节点的属性,函数在树上游走要正确维护节点的属性,那么就要在这两个特殊时间点搞点动作:

回溯代码核心框架

1
2
3
4
5
6
7
8
9
for 选择 in 选择列表:
# 做选择
将该选择从选择列表移除
路径.add(选择)
backtrack(路径, 选择列表)
# 撤销选择
路径.remove(选择)
将该选择再加入选择列表

我们只要在递归之前做出选择,在递归之后撤销自己的选择,就能得到每个节点的选择路径和列表。

全排列代码

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
List<List<Integer>> res = new LinkedList<>();

/* 主函数,输入一组不重复的数字,返回它们的全排列 */
List<List<Integer>> permute(int[] nums) {
// 记录「路径」
LinkedList<Integer> track = new LinkedList<>();
backtrack(nums, track);
return res;
}

// 路径:记录在 track 中
// 选择列表:nums 中不存在于 track 的那些元素
// 结束条件:nums 中的元素全都在 track 中出现
void backtrack(int[] nums, LinkedList<Integer> track) {
// 触发结束条件
if (track.size() == nums.length) {
res.add(new LinkedList(track));
return;
}

for (int i = 0; i < nums.length; i++) {
// 排除不合法的选择
if (track.contains(nums[i]))
continue;
// 做选择
track.add(nums[i]);
// 进入下一层决策树
backtrack(nums, track);
// 取消选择
track.removeLast();
}
}

我们这里稍微做了些变通,没有显式记录「选择列表」,而是通过 numstrack 推导出当前的选择列表:

通过contains函数来判断该数是否已经被使用。

至此,我们就通过全排列问题详解了回溯算法的底层原理。当然,这个算法解决全排列不是很高效,应为对链表使用 contains 方法需要 O(N) 的时间复杂度。有更好的方法通过交换元素达到目的,但是难理解一些,这里就不写了,有兴趣可以自行搜索一下。

但是必须说明的是,不管怎么优化,都符合回溯框架,而且时间复杂度都不可能低于 O(N!),因为穷举整棵决策树是无法避免的。这也是回溯算法的一个特点,不像动态规划存在重叠子问题可以优化,回溯算法就是纯暴力穷举,复杂度一般都很高。

明白了全排列问题,就可以直接套回溯算法框架了,下面简单看看 N 皇后问题。

例子2 N皇后问题

题意

给你一个N*N的棋盘,,让你放置 N 个皇后,使得它们不能互相攻击。

PS:皇后可以攻击同一行、同一列、左上左下右上右下四个方向的任意单位。

这个问题本质上跟全排列问题差不多,决策树的每一层表示棋盘上的每一行;每个节点可以做出的选择是,在该行的任意一列放置一个皇后。

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
vector<vector<string>> res;

/* 输入棋盘边长 n,返回所有合法的放置 */
vector<vector<string>> solveNQueens(int n) {
// '.' 表示空,'Q' 表示皇后,初始化空棋盘。
vector<string> board(n, string(n, '.'));
backtrack(board, 0);
return res;
}

// 路径:board 中小于 row 的那些行都已经成功放置了皇后
// 选择列表:第 row 行的所有列都是放置皇后的选择
// 结束条件:row 超过 board 的最后一行
void backtrack(vector<string>& board, int row) {
// 触发结束条件
if (row == board.size()) {
res.push_back(board);
return;
}

int n = board[row].size();
for (int col = 0; col < n; col++) {
// 排除不合法选择
if (!isValid(board, row, col))
continue;
// 做选择
board[row][col] = 'Q';
// 进入下一行决策
backtrack(board, row + 1);
// 撤销选择
board[row][col] = '.';
}
}

IsValid()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* 是否可以在 board[row][col] 放置皇后? */
bool isValid(vector<string>& board, int row, int col) {
int n = board.size();
// 检查列是否有皇后互相冲突
for (int i = 0; i < n; i++) {
if (board[i][col] == 'Q')
return false;
}
// 检查右上方是否有皇后互相冲突
for (int i = row - 1, j = col + 1;
i >= 0 && j < n; i--, j++) {
if (board[i][j] == 'Q')
return false;
}
// 检查左上方是否有皇后互相冲突
for (int i = row - 1, j = col - 1;
i >= 0 && j >= 0; i--, j--) {
if (board[i][j] == 'Q')
return false;
}
return true;
}

函数 backtrack 依然像个在决策树上游走的指针,通过 row 和 col 就可以表示函数遍历到的位置,通过 isValid 函数可以将不符合条件的情况剪枝:

某种程度上说,动态规划的暴力求解阶段就是回溯算法。只是有的问题具有重叠子问题性质,可以用 dp table 或者备忘录优化,将递归树大幅剪枝,这就变成了动态规划。而今天的两个问题,都没有重叠子问题,也就是回溯算法问题了,复杂度非常高是不可避免的。

参考链接:https://leetcode-cn.com/problems/permutations/solution/hui-su-suan-fa-xiang-jie-by-labuladong-2/

]]>
+ 回溯问题

解决一个回溯问题,实际上就是一个决策树的遍历过程,需要思考三个问题

回溯算法框架

1
2
3
4
5
6
7
8
9
10
result = []
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return

for 选择 in 选择列表:
做选择
backtrack(路径, 选择列表)
撤销选择

核心在于for循环里面的递归,在递归调用之前做选择,在递归调用之后撤销选择。

例子1 全排列问题

有n个数 每个数都只能用一次 求出所有能排列的可能性。全排列个数为n!个

PS:为了简单清晰起见,我们这次讨论的全排列问题不包含重复的数字

如果已知有多少个数的情况下,我们通常可以使用n层for循环暴力遍历所有数

例如3个数 我们可以暴力写法为

1
2
3
4
5
6
7
8
9
10
11
12
13
for(int i = 0;i < n;i++)
for(int j = 0;j < n;j++)
for(int k = 0;k < n;k++)
{
if(i!=j && i!=k && j!=k)
{
// 1 2 3 1 3 2 2 1 3 2 3 1 3 2 1 3 1 2
list.add(nums[i]);
list.add(nums[j]);
list.add(nums[k]);
res.add(list);
}
}

回溯数如下图所示,只要从根遍历这棵树,记录路径下的数字,其实就是所有的全排列,我们不妨把这棵树称之为回溯算法的决策树

为什么叫决策树呢,顾名思义,每次遍历的时候都需要做决策来去避开那些已经走过的路。

现在可以解答开头的几个名词:[2] 就是「路径」,记录你已经做过的选择;[1,3] 就是「选择列表」,表示你当前可以做出的选择;「结束条件」就是遍历到树的底层,在这里就是选择列表为空的时候。

如果明白了这几个名词,可以把「路径」和「选择」列表作为决策树上每个节点的属性,比如下图列出了几个节点的属性:

我们定义的backtrack函数就是指针一样,在这棵树上游走,同时要正确维护每个节点的属性,每当走到树的底层,其路径就是一个全排列。

再进一步,如何遍历一颗树,

1
2
3
4
5
6
void traverse(TreeNode root) {
for (TreeNode child : root.childern)
// 前序遍历需要的操作
traverse(child);
// 后序遍历需要的操作
}

前序遍历的代码在进入某一个节点之前的那个时间点执行,后序遍历代码在离开某个节点之后的那个时间点执行。

回想我们刚才说的,「路径」和「选择」是每个节点的属性,函数在树上游走要正确维护节点的属性,那么就要在这两个特殊时间点搞点动作:

回溯代码核心框架

1
2
3
4
5
6
7
8
9
for 选择 in 选择列表:
# 做选择
将该选择从选择列表移除
路径.add(选择)
backtrack(路径, 选择列表)
# 撤销选择
路径.remove(选择)
将该选择再加入选择列表

我们只要在递归之前做出选择,在递归之后撤销自己的选择,就能得到每个节点的选择路径和列表。

全排列代码

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
List<List<Integer>> res = new LinkedList<>();

/* 主函数,输入一组不重复的数字,返回它们的全排列 */
List<List<Integer>> permute(int[] nums) {
// 记录「路径」
LinkedList<Integer> track = new LinkedList<>();
backtrack(nums, track);
return res;
}

// 路径:记录在 track 中
// 选择列表:nums 中不存在于 track 的那些元素
// 结束条件:nums 中的元素全都在 track 中出现
void backtrack(int[] nums, LinkedList<Integer> track) {
// 触发结束条件
if (track.size() == nums.length) {
res.add(new LinkedList(track));
return;
}

for (int i = 0; i < nums.length; i++) {
// 排除不合法的选择
if (track.contains(nums[i]))
continue;
// 做选择
track.add(nums[i]);
// 进入下一层决策树
backtrack(nums, track);
// 取消选择
track.removeLast();
}
}

我们这里稍微做了些变通,没有显式记录「选择列表」,而是通过 numstrack 推导出当前的选择列表:

通过contains函数来判断该数是否已经被使用。

至此,我们就通过全排列问题详解了回溯算法的底层原理。当然,这个算法解决全排列不是很高效,应为对链表使用 contains 方法需要 O(N) 的时间复杂度。有更好的方法通过交换元素达到目的,但是难理解一些,这里就不写了,有兴趣可以自行搜索一下。

但是必须说明的是,不管怎么优化,都符合回溯框架,而且时间复杂度都不可能低于 O(N!),因为穷举整棵决策树是无法避免的。这也是回溯算法的一个特点,不像动态规划存在重叠子问题可以优化,回溯算法就是纯暴力穷举,复杂度一般都很高。

明白了全排列问题,就可以直接套回溯算法框架了,下面简单看看 N 皇后问题。

例子2 N皇后问题

题意

给你一个N*N的棋盘,,让你放置 N 个皇后,使得它们不能互相攻击。

PS:皇后可以攻击同一行、同一列、左上左下右上右下四个方向的任意单位。

这个问题本质上跟全排列问题差不多,决策树的每一层表示棋盘上的每一行;每个节点可以做出的选择是,在该行的任意一列放置一个皇后。

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
vector<vector<string>> res;

/* 输入棋盘边长 n,返回所有合法的放置 */
vector<vector<string>> solveNQueens(int n) {
// '.' 表示空,'Q' 表示皇后,初始化空棋盘。
vector<string> board(n, string(n, '.'));
backtrack(board, 0);
return res;
}

// 路径:board 中小于 row 的那些行都已经成功放置了皇后
// 选择列表:第 row 行的所有列都是放置皇后的选择
// 结束条件:row 超过 board 的最后一行
void backtrack(vector<string>& board, int row) {
// 触发结束条件
if (row == board.size()) {
res.push_back(board);
return;
}

int n = board[row].size();
for (int col = 0; col < n; col++) {
// 排除不合法选择
if (!isValid(board, row, col))
continue;
// 做选择
board[row][col] = 'Q';
// 进入下一行决策
backtrack(board, row + 1);
// 撤销选择
board[row][col] = '.';
}
}

IsValid()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* 是否可以在 board[row][col] 放置皇后? */
bool isValid(vector<string>& board, int row, int col) {
int n = board.size();
// 检查列是否有皇后互相冲突
for (int i = 0; i < n; i++) {
if (board[i][col] == 'Q')
return false;
}
// 检查右上方是否有皇后互相冲突
for (int i = row - 1, j = col + 1;
i >= 0 && j < n; i--, j++) {
if (board[i][j] == 'Q')
return false;
}
// 检查左上方是否有皇后互相冲突
for (int i = row - 1, j = col - 1;
i >= 0 && j >= 0; i--, j--) {
if (board[i][j] == 'Q')
return false;
}
return true;
}

函数 backtrack 依然像个在决策树上游走的指针,通过 row 和 col 就可以表示函数遍历到的位置,通过 isValid 函数可以将不符合条件的情况剪枝:

某种程度上说,动态规划的暴力求解阶段就是回溯算法。只是有的问题具有重叠子问题性质,可以用 dp table 或者备忘录优化,将递归树大幅剪枝,这就变成了动态规划。而今天的两个问题,都没有重叠子问题,也就是回溯算法问题了,复杂度非常高是不可避免的。

参考链接:https://leetcode-cn.com/problems/permutations/solution/hui-su-suan-fa-xiang-jie-by-labuladong-2/

]]>
diff --git a/css/gitalk.css b/css/gitalk.css deleted file mode 100644 index a268f1d28..000000000 --- a/css/gitalk.css +++ /dev/null @@ -1,546 +0,0 @@ -@font-face { - font-family: octicons-link; - src: url(data:font/woff;charset=utf-8;base64,d09GRgABAAAAAAZwABAAAAAACFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEU0lHAAAGaAAAAAgAAAAIAAAAAUdTVUIAAAZcAAAACgAAAAoAAQAAT1MvMgAAAyQAAABJAAAAYFYEU3RjbWFwAAADcAAAAEUAAACAAJThvmN2dCAAAATkAAAABAAAAAQAAAAAZnBnbQAAA7gAAACyAAABCUM+8IhnYXNwAAAGTAAAABAAAAAQABoAI2dseWYAAAFsAAABPAAAAZwcEq9taGVhZAAAAsgAAAA0AAAANgh4a91oaGVhAAADCAAAABoAAAAkCA8DRGhtdHgAAAL8AAAADAAAAAwGAACfbG9jYQAAAsAAAAAIAAAACABiATBtYXhwAAACqAAAABgAAAAgAA8ASm5hbWUAAAToAAABQgAAAlXu73sOcG9zdAAABiwAAAAeAAAAME3QpOBwcmVwAAAEbAAAAHYAAAB/aFGpk3jaTY6xa8JAGMW/O62BDi0tJLYQincXEypYIiGJjSgHniQ6umTsUEyLm5BV6NDBP8Tpts6F0v+k/0an2i+itHDw3v2+9+DBKTzsJNnWJNTgHEy4BgG3EMI9DCEDOGEXzDADU5hBKMIgNPZqoD3SilVaXZCER3/I7AtxEJLtzzuZfI+VVkprxTlXShWKb3TBecG11rwoNlmmn1P2WYcJczl32etSpKnziC7lQyWe1smVPy/Lt7Kc+0vWY/gAgIIEqAN9we0pwKXreiMasxvabDQMM4riO+qxM2ogwDGOZTXxwxDiycQIcoYFBLj5K3EIaSctAq2kTYiw+ymhce7vwM9jSqO8JyVd5RH9gyTt2+J/yUmYlIR0s04n6+7Vm1ozezUeLEaUjhaDSuXHwVRgvLJn1tQ7xiuVv/ocTRF42mNgZGBgYGbwZOBiAAFGJBIMAAizAFoAAABiAGIAznjaY2BkYGAA4in8zwXi+W2+MjCzMIDApSwvXzC97Z4Ig8N/BxYGZgcgl52BCSQKAA3jCV8CAABfAAAAAAQAAEB42mNgZGBg4f3vACQZQABIMjKgAmYAKEgBXgAAeNpjYGY6wTiBgZWBg2kmUxoDA4MPhGZMYzBi1AHygVLYQUCaawqDA4PChxhmh/8ODDEsvAwHgMKMIDnGL0x7gJQCAwMAJd4MFwAAAHjaY2BgYGaA4DAGRgYQkAHyGMF8NgYrIM3JIAGVYYDT+AEjAwuDFpBmA9KMDEwMCh9i/v8H8sH0/4dQc1iAmAkALaUKLgAAAHjaTY9LDsIgEIbtgqHUPpDi3gPoBVyRTmTddOmqTXThEXqrob2gQ1FjwpDvfwCBdmdXC5AVKFu3e5MfNFJ29KTQT48Ob9/lqYwOGZxeUelN2U2R6+cArgtCJpauW7UQBqnFkUsjAY/kOU1cP+DAgvxwn1chZDwUbd6CFimGXwzwF6tPbFIcjEl+vvmM/byA48e6tWrKArm4ZJlCbdsrxksL1AwWn/yBSJKpYbq8AXaaTb8AAHja28jAwOC00ZrBeQNDQOWO//sdBBgYGRiYWYAEELEwMTE4uzo5Zzo5b2BxdnFOcALxNjA6b2ByTswC8jYwg0VlNuoCTWAMqNzMzsoK1rEhNqByEyerg5PMJlYuVueETKcd/89uBpnpvIEVomeHLoMsAAe1Id4AAAAAAAB42oWQT07CQBTGv0JBhagk7HQzKxca2sJCE1hDt4QF+9JOS0nbaaYDCQfwCJ7Au3AHj+LO13FMmm6cl7785vven0kBjHCBhfpYuNa5Ph1c0e2Xu3jEvWG7UdPDLZ4N92nOm+EBXuAbHmIMSRMs+4aUEd4Nd3CHD8NdvOLTsA2GL8M9PODbcL+hD7C1xoaHeLJSEao0FEW14ckxC+TU8TxvsY6X0eLPmRhry2WVioLpkrbp84LLQPGI7c6sOiUzpWIWS5GzlSgUzzLBSikOPFTOXqly7rqx0Z1Q5BAIoZBSFihQYQOOBEdkCOgXTOHA07HAGjGWiIjaPZNW13/+lm6S9FT7rLHFJ6fQbkATOG1j2OFMucKJJsxIVfQORl+9Jyda6Sl1dUYhSCm1dyClfoeDve4qMYdLEbfqHf3O/AdDumsjAAB42mNgYoAAZQYjBmyAGYQZmdhL8zLdDEydARfoAqIAAAABAAMABwAKABMAB///AA8AAQAAAAAAAAAAAAAAAAABAAAAAA==) format('woff'); -} -/* variables */ -/* functions & mixins */ -/* variables - calculated */ -/* styles */ -.gt-container { - -webkit-box-sizing: border-box; - box-sizing: border-box; - font-size: 16px; - /* loader */ - /* error */ - /* initing */ - /* no int */ - /* link */ - /* meta */ - /* popup */ - /* header */ - /* comments */ - /* comment */ -} -.gt-container * { - -webkit-box-sizing: border-box; - box-sizing: border-box; -} -.gt-container a { - color: #6190e8; -} -.gt-container a:hover { - color: #81a6ed; - border-color: #81a6ed; -} -.gt-container a.is--active { - color: #333; - cursor: default !important; -} -.gt-container a.is--active:hover { - color: #333; -} -.gt-container .hide { - display: none !important; -} -.gt-container .gt-svg { - display: inline-block; - width: 1em; - height: 1em; - vertical-align: sub; -} -.gt-container .gt-svg svg { - width: 100%; - height: 100%; - fill: #6190e8; -} -.gt-container .gt-ico { - display: inline-block; -} -.gt-container .gt-ico-text { - margin-left: 0.3125em; -} -.gt-container .gt-ico-github { - width: 100%; - height: 100%; -} -.gt-container .gt-ico-github .gt-svg { - width: 100%; - height: 100%; -} -.gt-container .gt-ico-github svg { - fill: inherit; -} -.gt-container .gt-spinner { - position: relative; -} -.gt-container .gt-spinner::before { - content: ''; - -webkit-box-sizing: border-box; - box-sizing: border-box; - position: absolute; - top: 3px; - width: 0.75em; - height: 0.75em; - margin-top: -0.1875em; - margin-left: -0.375em; - border-radius: 50%; - border: 1px solid #fff; - border-top-color: #6190e8; - -webkit-animation: gt-kf-rotate 0.6s linear infinite; - animation: gt-kf-rotate 0.6s linear infinite; -} -.gt-container .gt-loader { - position: relative; - border: 1px solid #999; - -webkit-animation: ease gt-kf-rotate 1.5s infinite; - animation: ease gt-kf-rotate 1.5s infinite; - display: inline-block; - font-style: normal; - width: 1.75em; - height: 1.75em; - line-height: 1.75em; - border-radius: 50%; -} -.gt-container .gt-loader:before { - content: ''; - position: absolute; - display: block; - top: 0; - left: 50%; - margin-top: -0.1875em; - margin-left: -0.1875em; - width: 0.375em; - height: 0.375em; - background-color: #999; - border-radius: 50%; -} -.gt-container .gt-avatar { - display: inline-block; - width: 3.125em; - height: 3.125em; -} -@media (max-width: 479px) { - .gt-container .gt-avatar { - width: 2em; - height: 2em; - } -} -.gt-container .gt-avatar img { - width: 100%; - height: auto; - border-radius: 3px; -} -.gt-container .gt-avatar-github { - width: 3em; - height: 3em; -} -@media (max-width: 479px) { - .gt-container .gt-avatar-github { - width: 1.875em; - height: 1.875em; - } -} -.gt-container .gt-btn { - padding: 0.75em 1.25em; - display: inline-block; - line-height: 1; - text-decoration: none; - white-space: nowrap; - cursor: pointer; - border: 1px solid #6190e8; - border-radius: 5px; - background-color: #6190e8; - color: #fff; - outline: none; - font-size: 0.75em; -} -.gt-container .gt-btn-text { - font-weight: 400; -} -.gt-container .gt-btn-loading { - position: relative; - margin-left: 0.5em; - display: inline-block; - width: 0.75em; - height: 1em; - vertical-align: top; -} -.gt-container .gt-btn.is--disable { - cursor: not-allowed; - opacity: 0.5; -} -.gt-container .gt-btn-login { - margin-right: 0; -} -.gt-container .gt-btn-preview { - background-color: #fff; - color: #6190e8; -} -.gt-container .gt-btn-preview:hover { - background-color: #f2f2f2; - border-color: #81a6ed; -} -.gt-container .gt-btn-public:hover { - background-color: #81a6ed; - border-color: #81a6ed; -} -.gt-container .gt-error { - text-align: center; - margin: 0.625em; - color: #ff3860; -} -.gt-container .gt-initing { - padding: 1.25em 0; - text-align: center; -} -.gt-container .gt-initing-text { - margin: 0.625em auto; - font-size: 92%; -} -.gt-container .gt-no-init { - padding: 1.25em 0; - text-align: center; -} -.gt-container .gt-link { - border-bottom: 1px dotted #6190e8; -} -.gt-container .gt-link-counts, -.gt-container .gt-link-project { - text-decoration: none; -} -.gt-container .gt-meta { - margin: 1.25em 0; - padding: 1em 0; - position: relative; - border-bottom: 1px solid #e9e9e9; - font-size: 1em; - position: relative; - z-index: 10; -} -.gt-container .gt-meta:before, -.gt-container .gt-meta:after { - content: " "; - display: table; -} -.gt-container .gt-meta:after { - clear: both; -} -.gt-container .gt-counts { - margin: 0 0.625em 0 0; -} -.gt-container .gt-user { - float: right; - margin: 0; - font-size: 92%; -} -.gt-container .gt-user-pic { - width: 16px; - height: 16px; - vertical-align: top; - margin-right: 0.5em; -} -.gt-container .gt-user-inner { - display: inline-block; - cursor: pointer; -} -.gt-container .gt-user .gt-ico { - margin: 0 0 0 0.3125em; -} -.gt-container .gt-user .gt-ico svg { - fill: inherit; -} -.gt-container .gt-user .is--poping .gt-ico svg { - fill: #6190e8; -} -.gt-container .gt-version { - color: #a1a1a1; - margin-left: 0.375em; -} -.gt-container .gt-copyright { - margin: 0 0.9375em 0.5em; - border-top: 1px solid #e9e9e9; - padding-top: 0.5em; -} -.gt-container .gt-popup { - position: absolute; - right: 0; - top: 2.375em; - background: #fff; - display: inline-block; - border: 1px solid #e9e9e9; - padding: 0.625em 0; - font-size: 0.875em; - letter-spacing: 0.5px; -} -.gt-container .gt-popup .gt-action { - cursor: pointer; - display: block; - margin: 0.5em 0; - padding: 0 1.125em; - position: relative; - text-decoration: none; -} -.gt-container .gt-popup .gt-action.is--active:before { - content: ''; - width: 0.25em; - height: 0.25em; - background: #6190e8; - position: absolute; - left: 0.5em; - top: 0.4375em; -} -.gt-container .gt-header { - position: relative; - display: -webkit-box; - display: -ms-flexbox; - display: flex; -} -.gt-container .gt-header-comment { - -webkit-box-flex: 1; - -ms-flex: 1; - flex: 1; - margin-left: 1.25em; -} -@media (max-width: 479px) { - .gt-container .gt-header-comment { - margin-left: 0.875em; - } -} -.gt-container .gt-header-textarea { - padding: 0.75em; - display: block; - -webkit-box-sizing: border-box; - box-sizing: border-box; - width: 100%; - min-height: 5.125em; - max-height: 15em; - border-radius: 5px; - border: 1px solid rgba(0,0,0,0.1); - font-size: 0.875em; - word-wrap: break-word; - resize: vertical; - background-color: #f6f6f6; - outline: none; - -webkit-transition: all 0.25s ease; - transition: all 0.25s ease; -} -.gt-container .gt-header-textarea:hover { - background-color: #fbfbfb; -} -.gt-container .gt-header-preview { - padding: 0.75em; - border-radius: 5px; - border: 1px solid rgba(0,0,0,0.1); - background-color: #f6f6f6; -} -.gt-container .gt-header-controls { - position: relative; - margin: 0.75em 0 0; -} -.gt-container .gt-header-controls:before, -.gt-container .gt-header-controls:after { - content: " "; - display: table; -} -.gt-container .gt-header-controls:after { - clear: both; -} -@media (max-width: 479px) { - .gt-container .gt-header-controls { - margin: 0; - } -} -.gt-container .gt-header-controls-tip { - font-size: 0.875em; - color: #6190e8; - text-decoration: none; - vertical-align: sub; -} -@media (max-width: 479px) { - .gt-container .gt-header-controls-tip { - display: none; - } -} -.gt-container .gt-header-controls .gt-btn { - float: right; - margin-left: 1.25em; -} -@media (max-width: 479px) { - .gt-container .gt-header-controls .gt-btn { - float: none; - width: 100%; - margin: 0.75em 0 0; - } -} -.gt-container:after { - content: ''; - position: fixed; - bottom: 100%; - left: 0; - right: 0; - top: 0; - opacity: 0; -} -.gt-container.gt-input-focused { - position: relative; -} -.gt-container.gt-input-focused:after { - content: ''; - position: fixed; - bottom: 0%; - left: 0; - right: 0; - top: 0; - background: #000; - opacity: 0.6; - -webkit-transition: opacity 0.3s, bottom 0s; - transition: opacity 0.3s, bottom 0s; - z-index: 9999; -} -.gt-container.gt-input-focused .gt-header-comment { - z-index: 10000; -} -.gt-container .gt-comments { - padding-top: 1.25em; -} -.gt-container .gt-comments-null { - text-align: center; -} -.gt-container .gt-comments-controls { - margin: 1.25em 0; - text-align: center; -} -.gt-container .gt-comment { - position: relative; - padding: 0.625em 0; - display: -webkit-box; - display: -ms-flexbox; - display: flex; -} -.gt-container .gt-comment-content { - -webkit-box-flex: 1; - -ms-flex: 1; - flex: 1; - margin-left: 1.25em; - padding: 0.75em 1em; - background-color: #f9f9f9; - overflow: auto; - -webkit-transition: all ease 0.25s; - transition: all ease 0.25s; -} -.gt-container .gt-comment-content:hover { - -webkit-box-shadow: 0 0.625em 3.75em 0 #f4f4f4; - box-shadow: 0 0.625em 3.75em 0 #f4f4f4; -} -@media (max-width: 479px) { - .gt-container .gt-comment-content { - margin-left: 0.875em; - padding: 0.625em 0.75em; - } -} -.gt-container .gt-comment-header { - margin-bottom: 0.5em; - font-size: 0.875em; - position: relative; -} -.gt-container .gt-comment-block-1 { - float: right; - height: 1.375em; - width: 2em; -} -.gt-container .gt-comment-block-2 { - float: right; - height: 1.375em; - width: 4em; -} -.gt-container .gt-comment-username { - font-weight: 500; - color: #6190e8; - text-decoration: none; -} -.gt-container .gt-comment-username:hover { - text-decoration: underline; -} -.gt-container .gt-comment-text { - margin-left: 0.5em; - color: #a1a1a1; -} -.gt-container .gt-comment-date { - margin-left: 0.5em; - color: #a1a1a1; -} -.gt-container .gt-comment-like, -.gt-container .gt-comment-edit, -.gt-container .gt-comment-reply { - position: absolute; - height: 1.375em; -} -.gt-container .gt-comment-like:hover, -.gt-container .gt-comment-edit:hover, -.gt-container .gt-comment-reply:hover { - cursor: pointer; -} -.gt-container .gt-comment-like { - top: 0; - right: 2em; -} -.gt-container .gt-comment-edit, -.gt-container .gt-comment-reply { - top: 0; - right: 0; -} -.gt-container .gt-comment-body { - color: #333 !important; -} -.gt-container .gt-comment-body .email-hidden-toggle a { - display: inline-block; - height: 12px; - padding: 0 9px; - font-size: 12px; - font-weight: 600; - line-height: 6px; - color: #444d56; - text-decoration: none; - vertical-align: middle; - background: #dfe2e5; - border-radius: 1px; -} -.gt-container .gt-comment-body .email-hidden-toggle a:hover { - background-color: #c6cbd1; -} -.gt-container .gt-comment-body .email-hidden-reply { - display: none; - white-space: pre-wrap; -} -.gt-container .gt-comment-body .email-hidden-reply .email-signature-reply { - padding: 0 15px; - margin: 15px 0; - color: #586069; - border-left: 4px solid #dfe2e5; -} -.gt-container .gt-comment-body .email-hidden-reply.expanded { - display: block; -} -.gt-container .gt-comment-admin .gt-comment-content { - background-color: #f6f9fe; -} -@-webkit-keyframes gt-kf-rotate { - 0% { - -webkit-transform: rotate(0); - transform: rotate(0); - } - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); - } -} -@keyframes gt-kf-rotate { - 0% { - -webkit-transform: rotate(0); - transform: rotate(0); - } - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); - } -} diff --git a/css/highlight-dark.css b/css/highlight-dark.css deleted file mode 100644 index 29f08c513..000000000 --- a/css/highlight-dark.css +++ /dev/null @@ -1,64 +0,0 @@ -/* - -Dark style from softwaremaniacs.org (c) Ivan Sagalaev - -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #444; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-literal, -.hljs-section, -.hljs-link { - color: white; -} - -.hljs, -.hljs-subst { - color: #ddd; -} - -.hljs-string, -.hljs-title, -.hljs-name, -.hljs-type, -.hljs-attribute, -.hljs-symbol, -.hljs-bullet, -.hljs-built_in, -.hljs-addition, -.hljs-variable, -.hljs-template-tag, -.hljs-template-variable { - color: #d88; -} - -.hljs-comment, -.hljs-quote, -.hljs-deletion, -.hljs-meta { - color: #777; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-literal, -.hljs-title, -.hljs-section, -.hljs-doctag, -.hljs-type, -.hljs-name, -.hljs-strong { - font-weight: bold; -} - -.hljs-emphasis { - font-style: italic; -} - diff --git a/css/highlight.css b/css/highlight.css deleted file mode 100644 index 8f451dea3..000000000 --- a/css/highlight.css +++ /dev/null @@ -1,80 +0,0 @@ -/** - * GitHub Gist Theme - * Author : Anthony Attard - https://github.com/AnthonyAttard - * Author : Louis Barranqueiro - https://github.com/LouisBarranqueiro - */ - -.hljs { - display: block; - background: white; - padding: 0.5em; - color: #333333; - overflow-x: auto; -} - -.hljs-comment, -.hljs-meta { - color: #969896; -} - -.hljs-variable, -.hljs-template-variable, -.hljs-strong, -.hljs-emphasis, -.hljs-quote { - color: #df5000; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-type { - color: #d73a49; -} - -.hljs-literal, -.hljs-symbol, -.hljs-bullet, -.hljs-attribute { - color: #0086b3; -} - -.hljs-section, -.hljs-name { - color: #63a35c; -} - -.hljs-tag { - color: #333333; -} - -.hljs-title, -.hljs-attr, -.hljs-selector-id, -.hljs-selector-class, -.hljs-selector-attr, -.hljs-selector-pseudo { - color: #6f42c1; -} - -.hljs-addition { - color: #55a532; - background-color: #eaffea; -} - -.hljs-deletion { - color: #bd2c00; - background-color: #ffecec; -} - -.hljs-link { - text-decoration: underline; -} - -.hljs-number { - color: #005cc5; -} - -.hljs-string { - color: #032f62; -} - diff --git a/css/main.css b/css/main.css index ba3081b1f..c61a56bda 100644 --- a/css/main.css +++ b/css/main.css @@ -1,5 +1,5 @@ :root { - --body-bg-color: #fff; + --body-bg-color: #f5f7f9; --content-bg-color: #fff; --card-bg-color: #f5f5f5; --text-color: #555; @@ -10,11 +10,11 @@ --table-row-odd-bg-color: #f9f9f9; --table-row-hover-bg-color: #f5f5f5; --menu-item-bg-color: #f5f5f5; - --btn-default-bg: #222; - --btn-default-color: #fff; - --btn-default-border-color: #222; - --btn-default-hover-bg: #fff; - --btn-default-hover-color: #222; + --btn-default-bg: #fff; + --btn-default-color: #555; + --btn-default-border-color: #555; + --btn-default-hover-bg: #222; + --btn-default-hover-color: #fff; --btn-default-hover-border-color: #222; } html { @@ -314,7 +314,7 @@ td { .btn { background: var(--btn-default-bg); border: 2px solid var(--btn-default-border-color); - border-radius: 0; + border-radius: 2px; color: var(--btn-default-color); display: inline-block; font-size: 0.875em; @@ -356,36 +356,33 @@ td { margin-top: 3px; } .toggle.toggle-arrow .toggle-line-first { - left: 50%; top: 2px; - transform: rotate(45deg); + transform: rotate(-45deg); width: 50%; } .toggle.toggle-arrow .toggle-line-middle { - left: 2px; width: 90%; } .toggle.toggle-arrow .toggle-line-last { - left: 50%; top: -2px; - transform: rotate(-45deg); + transform: rotate(45deg); width: 50%; } .toggle.toggle-close .toggle-line-first { - transform: rotate(-45deg); top: 5px; + transform: rotate(-45deg); } .toggle.toggle-close .toggle-line-middle { opacity: 0; } .toggle.toggle-close .toggle-line-last { - transform: rotate(45deg); top: -5px; + transform: rotate(45deg); } .highlight, pre { - background: #f7f7f7; - color: #4d4d4c; + background: #1d1f21; + color: #c5c8c6; line-height: 1.6; margin: 0 auto 20px; } @@ -402,7 +399,7 @@ code { word-wrap: break-word; } .highlight *::selection { - background: #d6d6d6; + background: #373b41; } .highlight pre { border: 0; @@ -419,8 +416,8 @@ code { padding: 0; } .highlight figcaption { - background: #eff2f3; - color: #4d4d4c; + background: #000; + color: #c5c8c6; display: flex; font-size: 0.875em; justify-content: space-between; @@ -428,10 +425,10 @@ code { padding: 0.5em; } .highlight figcaption a { - color: #4d4d4c; + color: #c5c8c6; } .highlight figcaption a:hover { - border-bottom-color: #4d4d4c; + border-bottom-color: #c5c8c6; } .highlight .gutter { -moz-user-select: none; @@ -440,14 +437,14 @@ code { user-select: none; } .highlight .gutter pre { - background: #eff2f3; - color: #869194; + background: #000; + color: #888f96; padding-left: 10px; padding-right: 10px; text-align: right; } .highlight .code pre { - background: #f7f7f7; + background: #1d1f21; padding-left: 10px; width: 100%; } @@ -463,26 +460,26 @@ pre { } pre code { background: none; - color: #4d4d4c; + color: #c5c8c6; font-size: 0.875em; padding: 0; text-shadow: none; } pre .deletion { - background: #fdd; + background: #800000; } pre .addition { - background: #dfd; + background: #008000; } pre .meta { - color: #eab700; + color: #f0c674; -moz-user-select: none; -ms-user-select: none; -webkit-user-select: none; user-select: none; } pre .comment { - color: #8e908c; + color: #969896; } pre .variable, pre .attribute, @@ -497,7 +494,7 @@ pre .html .doctype, pre .css .id, pre .css .class, pre .css .pseudo { - color: #c82829; + color: #c66; } pre .number, pre .preprocessor, @@ -507,7 +504,7 @@ pre .literal, pre .params, pre .constant, pre .command { - color: #f5871f; + color: #de935f; } pre .ruby .class .title, pre .css .rules .attribute, @@ -520,11 +517,11 @@ pre .ruby .symbol, pre .xml .cdata, pre .special, pre .formula { - color: #718c00; + color: #b5bd68; } pre .title, pre .css .hexcolor { - color: #3e999f; + color: #8abeb7; } pre .function, pre .python .decorator, @@ -534,11 +531,11 @@ pre .ruby .title .keyword, pre .perl .sub, pre .javascript .title, pre .coffeescript .title { - color: #4271ae; + color: #81a2be; } pre .keyword, pre .javascript .function { - color: #8959a8; + color: #b294bb; } .blockquote-center { border-left: none; @@ -928,16 +925,16 @@ pre .javascript .function { } .main-inner { margin: 0 auto; - width: 700px; + width: calc(100% - 20px); } @media (min-width: 1200px) { .main-inner { - width: 800px; + width: 1160px; } } @media (min-width: 1600px) { .main-inner { - width: 900px; + width: 73%; } } @media (max-width: 767px) { @@ -950,16 +947,16 @@ pre .javascript .function { } .header-inner { margin: 0 auto; - width: 700px; + width: calc(100% - 20px); } @media (min-width: 1200px) { .header-inner { - width: 800px; + width: 1160px; } } @media (min-width: 1600px) { .header-inner { - width: 900px; + width: 73%; } } .site-brand-container { @@ -998,7 +995,7 @@ pre .javascript .function { margin: 0; } .site-subtitle { - color: #999; + color: #ddd; font-size: 0.8125em; margin: 10px 0; } @@ -1140,22 +1137,23 @@ pre .javascript .function { display: inline-block; } .site-author-image { - border: 2px solid #333; + border: 1px solid #eee; display: block; margin: 0 auto; - max-width: 96px; + max-width: 120px; padding: 2px; + border-radius: 50%; } .site-author-name { - color: #f5f5f5; - font-weight: normal; - margin: 5px 0 0; + color: var(--text-color); + font-weight: 600; + margin: 0; text-align: center; } .site-description { color: #999; - font-size: 1em; - margin-top: 5px; + font-size: 0.8125em; + margin-top: 0; text-align: center; } .links-of-author { @@ -1172,7 +1170,7 @@ pre .javascript .function { } .links-of-author a::before, .links-of-author span.exturl::before { - background: #16219c; + background: #f95d4e; border-radius: 50%; content: ' '; display: inline-block; @@ -1253,14 +1251,14 @@ pre .javascript .function { margin-left: 10px; } .sidebar-nav li:hover { - color: #f5f5f5; + color: #fc6423; } .sidebar-nav .sidebar-nav-active { - border-bottom-color: #87daff; - color: #87daff; + border-bottom-color: #fc6423; + color: #fc6423; } .sidebar-nav .sidebar-nav-active:hover { - color: #87daff; + color: #fc6423; } .sidebar-panel { display: none; @@ -1289,7 +1287,7 @@ pre .javascript .function { } } .sidebar-toggle:hover .toggle-line { - background: #87daff; + background: #fc6423; } .post-toc { font-size: 0.875em; @@ -1328,14 +1326,14 @@ pre .javascript .function { display: block; } .post-toc .nav .active > a { - border-bottom-color: #87daff; - color: #87daff; + border-bottom-color: #fc6423; + color: #fc6423; } .post-toc .nav .active-current > a { - color: #87daff; + color: #fc6423; } .post-toc .nav .active-current > a:hover { - color: #87daff; + color: #fc6423; } .site-state { display: flex; @@ -1350,20 +1348,20 @@ pre .javascript .function { padding: 0 15px; } .site-state-item:not(:first-child) { - border-left: 1px solid #333; + border-left: 1px solid #eee; } .site-state-item a { border-bottom: none; } .site-state-item-count { display: block; - font-size: 1.25em; + font-size: 1em; font-weight: 600; text-align: center; } .site-state-item-name { - color: inherit; - font-size: 0.875em; + color: #999; + font-size: 0.8125em; } .footer { color: #999; @@ -1380,16 +1378,16 @@ pre .javascript .function { box-sizing: border-box; margin: 0 auto; text-align: center; - width: 700px; + width: calc(100% - 20px); } @media (min-width: 1200px) { .footer-inner { - width: 800px; + width: 1160px; } } @media (min-width: 1600px) { .footer-inner { - width: 900px; + width: 73%; } } .languages { @@ -1487,7 +1485,7 @@ pre .javascript .function { color: #fff; cursor: pointer; left: 30px; - opacity: 1; + opacity: 0.6; padding: 0 6px; position: fixed; transition-property: bottom; @@ -1498,10 +1496,10 @@ pre .javascript .function { display: none; } .back-to-top:hover { - color: #87daff; + color: #fc6423; } .back-to-top.back-to-top-on { - bottom: 19px; + bottom: 30px; } @media (max-width: 991px) { .back-to-top { @@ -2141,156 +2139,301 @@ ul.breadcrumb li + li:last-child { .tag-cloud a:hover { color: #222 !important; } -@media (max-width: 767px) { - .header-inner, - .main-inner, - .footer-inner { +.header { + margin: 0 auto; + position: relative; + width: calc(100% - 20px); +} +@media (min-width: 1200px) { + .header { + width: 1160px; + } +} +@media (min-width: 1600px) { + .header { + width: 73%; + } +} +@media (max-width: 991px) { + .header { width: auto; } } .header-inner { - padding-top: 100px; + background: var(--content-bg-color); + border-radius: initial; + box-shadow: initial; + overflow: hidden; + padding: 0; + position: absolute; + top: 0; + width: 240px; } -@media (max-width: 767px) { +@media (min-width: 1200px) { .header-inner { - padding-top: 50px; + width: 240px; } } -.main-inner { - padding-bottom: 60px; +@media (max-width: 991px) { + .header-inner { + border-radius: initial; + position: relative; + width: auto; + } } -.content { - padding-top: 70px; +.main-inner { + align-items: flex-start; + display: flex; + justify-content: space-between; } -@media (max-width: 767px) { - .content { - padding-top: 35px; +@media (max-width: 991px) { + .main-inner { + width: auto; } } -embed { - display: block; - margin: 0 auto 25px auto; -} -.custom-logo .site-meta-headline { - text-align: center; +.content-wrap { + background: var(--content-bg-color); + border-radius: initial; + box-shadow: initial; + box-sizing: border-box; + padding: 40px; + width: calc(100% - 252px); } -.custom-logo .site-title { - color: #222; - margin: 10px auto 0; +@media (max-width: 991px) { + .content-wrap { + border-radius: initial; + padding: 20px; + width: 100%; + } } -.custom-logo .site-title a { - border: 0; +.header-inner { + right: 0; } -.custom-logo-image { - background: #fff; - margin: 0 auto; - max-width: 150px; - padding: 5px; +.book-mark-link { + left: 30px; } -.brand { - background: var(--btn-default-bg); +.footer-inner { + padding-right: 260px; } -@media (max-width: 767px) { - .site-nav { - border-bottom: 1px solid #ddd; - border-top: 1px solid #ddd; - left: 0; - margin: 0; - padding: 0; - width: 100%; +@media (max-width: 991px) { + .footer-inner { + padding-left: 0; + padding-right: 0; + width: auto; } } -@media (max-width: 767px) { - .menu { - text-align: left; +.site-brand-container { + background: #222; +} +@media (max-width: 991px) { + .site-brand-container { + box-shadow: 0 0 16px rgba(0,0,0,0.5); } } -.menu-item-active a, -.menu .menu-item a:hover, -.menu .menu-item span.exturl:hover { - background: transparent; - border-bottom: 1px solid var(--link-hover-color) !important; +.site-meta { + padding: 20px 0; } -@media (max-width: 767px) { - .menu-item-active a, - .menu .menu-item a:hover, - .menu .menu-item span.exturl:hover { - border-bottom: 1px dotted #ddd !important; +.brand { + padding: 0; +} +.site-subtitle { + margin: 10px 10px 0; +} +.custom-logo-image { + margin-top: 20px; +} +@media (max-width: 991px) { + .custom-logo-image { + display: none; } } -@media (max-width: 767px) { - .menu .menu-item { - margin: 0 10px; +@media (min-width: 768px) and (max-width: 991px) { + .site-nav-toggle, + .site-nav-right { + display: flex; + flex-direction: column; + justify-content: center; } } -.menu .menu-item a, -.menu .menu-item span.exturl { - border-bottom: 1px solid transparent; +.site-nav-toggle .toggle, +.site-nav-right .toggle { + color: #fff; } -@media (max-width: 767px) { - .menu .menu-item a, - .menu .menu-item span.exturl { - padding: 5px 10px; - } +.site-nav-toggle .toggle .toggle-line, +.site-nav-right .toggle .toggle-line { + background: #fff; } @media (min-width: 768px) and (max-width: 991px) { - .menu .menu-item .fa { - display: block; - line-height: 2; - margin-right: 0; - width: 100%; + .site-nav { + display: none; } } -@media (min-width: 992px) { - .menu .menu-item .fa { - display: block; - line-height: 2; - margin-right: 0; - width: 100%; +.menu-item-active a::after { + background: #bbb; + border-radius: 50%; + content: ' '; + height: 6px; + margin-top: -3px; + position: absolute; + right: 15px; + top: 50%; + width: 6px; +} +.menu .menu-item { + display: block; + margin: 0; +} +.menu .menu-item a, +.menu .menu-item span.exturl { + padding: 5px 20px; + position: relative; + text-align: left; + transition-property: background-color; +} +@media (max-width: 991px) { + .menu .menu-item.menu-item-search { + display: none; } } .menu .menu-item .badge { - background: #eee; - padding: 1px 4px; + background: #ccc; + border-radius: 10px; + color: #fff; + float: right; + padding: 2px 5px; + text-shadow: 1px 1px 0 rgba(0,0,0,0.1); + vertical-align: middle; } .sub-menu { - margin: 10px 0; + background: var(--content-bg-color); + border-bottom: 1px solid #ddd; + margin: 0; + padding: 6px 0; } .sub-menu .menu-item { display: inline-block; } -.sidebar { - left: -320px; +.sub-menu .menu-item a, +.sub-menu .menu-item span.exturl { + background: transparent; + margin: 5px 10px; + padding: initial; } -.sidebar.sidebar-active { - left: 0; +.sub-menu .menu-item a:hover, +.sub-menu .menu-item span.exturl:hover { + background: transparent; + color: #fc6423; +} +.sub-menu .menu-item a::after, +.sub-menu .menu-item span.exturl::after { + content: initial !important; +} +.sub-menu .menu-item-active a { + border-bottom-color: #fc6423; + color: #fc6423; +} +.sub-menu .menu-item-active a:hover { + border-bottom-color: #fc6423; } .sidebar { - width: 320px; - z-index: 1200; - transition-delay: 0s; - transition-duration: 0.2s; - transition-timing-function: ease-out; + background: var(--body-bg-color); + box-shadow: none; + margin-top: 100%; + position: static; + width: 240px; } -.sidebar a, -.sidebar span.exturl { - border-bottom-color: #555; - color: #999; +@media (max-width: 991px) { + .sidebar { + display: none; + } } -.sidebar a:hover, -.sidebar span.exturl:hover { - border-bottom-color: #eee; - color: #eee; +.sidebar-toggle { + display: none; } -.links-of-blogroll-item { - padding: 2px 10px; +.sidebar-inner { + background: var(--content-bg-color); + border-radius: initial; + box-shadow: initial; + box-sizing: border-box; + color: var(--text-color); + width: 240px; + opacity: 0; +} +.sidebar-inner.affix { + position: fixed; + top: 12px; +} +.sidebar-inner.affix-bottom { + position: absolute; +} +.site-state-item { + padding: 0 10px; +} +.sidebar-button { + border-bottom: 1px dotted #ccc; + border-top: 1px dotted #ccc; + margin-top: 10px; + text-align: center; +} +.sidebar-button a { + border: 0; + color: #fc6423; + display: block; +} +.sidebar-button a:hover { + background: none; + border: 0; + color: #e34603; } -.links-of-blogroll-item a, -.links-of-blogroll-item span.exturl { +.sidebar-button a:hover .fa { + color: #e34603; +} +.links-of-author { + display: flex; + flex-wrap: wrap; + margin-top: 10px; + justify-content: center; +} +.links-of-author-item { + margin: 5px 0 0; + width: 50%; +} +.links-of-author-item a, +.links-of-author-item span.exturl { box-sizing: border-box; display: inline-block; - max-width: 280px; + margin-bottom: 0; + margin-right: 0; + max-width: 216px; overflow: hidden; + padding: 0 5px; text-overflow: ellipsis; white-space: nowrap; } +.links-of-author-item a, +.links-of-author-item span.exturl { + border-bottom: none; + display: block; + text-decoration: none; +} +.links-of-author-item a::before, +.links-of-author-item span.exturl::before { + display: none; +} +.links-of-author-item a:hover, +.links-of-author-item span.exturl:hover { + background: var(--body-bg-color); + border-radius: 4px; +} +.links-of-author-item .fa { + margin-right: 2px; +} +.links-of-blogroll-item { + padding: 0; +} +.post-toc .nav .nav-child { + display: block; +} +.post-toc ol { + font-size: 13px; +} diff --git a/img/avatar.png b/img/avatar.png deleted file mode 100644 index ffd1c7793..000000000 Binary files a/img/avatar.png and /dev/null differ diff --git a/img/default.png b/img/default.png deleted file mode 100644 index 2bc2cd744..000000000 Binary files a/img/default.png and /dev/null differ diff --git a/img/fluid.png b/img/fluid.png deleted file mode 100644 index 368a58ace..000000000 Binary files a/img/fluid.png and /dev/null differ diff --git a/img/loading.gif b/img/loading.gif deleted file mode 100644 index c5126ed9c..000000000 Binary files a/img/loading.gif and /dev/null differ diff --git a/img/police_beian.png b/img/police_beian.png deleted file mode 100644 index 60190da03..000000000 Binary files a/img/police_beian.png and /dev/null differ diff --git a/index.html b/index.html index 80e593b3d..b8c84b8d9 100644 --- a/index.html +++ b/index.html @@ -443,7 +443,7 @@

- + @@ -691,10 +691,16 @@

- - + + @@ -731,7 +737,7 @@

进程与线程

多线程demo

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class deno01 {
public static void main(String[] args) {
new Thread(() ->{
while (true)
{
System.out.println(1);
}
},"t1").start();

new Thread(() ->{
while (true)
{
System.out.println(2);
}
},"t2").start();
}

}
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class deno01 {
public static void main(String[] args) {
new Thread(() ->{
while (true)
{
System.out.println(1);
}
},"t1").start();

new Thread(() ->{
while (true)
{
System.out.println(2);
}
},"t2").start();
}

}

很明显得到的结果是交替打印1 和 2。

拓展

@@ -835,17 +841,17 @@

1
2
3
synchronized void method() {
//业务代码
}
+
1
2
3
synchronized void method() {
//业务代码
}
  • 修饰静态方法: 也就是给当前类加锁,会作用于类的所有对象实例 ,进入同步代码前要获得 当前 class 的锁。因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管 new 了多少个对象,只有一份)。所以,如果一个线程 A 调用一个实例对象的非静态 synchronized 方法,而线程 B 需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁
-
1
2
3
synchronized void staic method() {
//业务代码
}
+
1
2
3
synchronized void staic method() {
//业务代码
}
  • 修饰代码块 :指定加锁对象,对给定对象/类加锁。synchronized(this|object) 表示进入同步代码库前要获得给定对象的锁synchronized(类.class) 表示进入同步代码前要获得 当前 class 的锁
-
1
2
3
synchronized(this) {
//业务代码
}
+
1
2
3
synchronized(this) {
//业务代码
}
@@ -858,13 +864,13 @@

List

则生成的是String[]类型的数组 初始长度为0

扩容

当初始长度为10已经加入了十个元素之后,我们需要再加一个元素的时候,我们就需要扩容

-
1
2
3
4
5
6
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)
elementData = grow();
elementData[s] = e;
size = s + 1;
}
+
1
2
3
4
5
6
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)
elementData = grow();
elementData[s] = e;
size = s + 1;
}

这段源代码就是ArrayList的add()方法 如果添加的元素已经满了 则调用grow()函数 很明显 这是一个扩容函数

grow函数有两个 一个有参函数 一个无参函数

无参参数会调用有参参数 进行1.5倍的扩容

-
1
ArrayList list1 = new ArrayList(23);
+
1
ArrayList list1 = new ArrayList(23);

这表示着生成了一个初始长度为23的ArrayList数组

Vector

和ArrayList数组类似 线程安全 效率低 用的很少

@@ -890,7 +896,7 @@

HashM

HashMap 是无序的,即不会记录插入的顺序。

HashMap 继承于AbstractMap,实现了 Map、Cloneable、java.io.Serializable 接口。

-
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
public class Main {
public static void main(String[] args) throws IOException, Exception {
HashMap<String,String> map = new HashMap<>();
map.put("1","one");
map.put("2","two");
map.put("3","three");
map.put("4","four");
System.out.println(map);

HashMap<String, String> Sites = new HashMap<String, String>();
// 添加键值对
Sites.put("one", "Google");
Sites.put("two", "Runoob");
Sites.put("three", "Taobao");
Sites.put("four", "Zhihu");
Sites.put("apple","lll");
System.out.println(Sites);
// key=商品名称,value=价格,这里以这个例子实现按名称排序和按价格排序.
Map store = new HashMap();

store.put("iphone12", 6799);
store.put("iphone12pro", 8499);
store.put("macbookPro", 19499);
store.put("ipadAir", 6999);
store.put("watch6", 3199);

// 直接输出HashMap得到的是一个无序Map(不是Arraylist那种顺序型储存)
System.out.println(store);

// {ipadAir=6999, iphone12pro=8499, macbookPro=19499, watch6=3199, iphone12=6799}
}

}

+
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
public class Main {
public static void main(String[] args) throws IOException, Exception {
HashMap<String,String> map = new HashMap<>();
map.put("1","one");
map.put("2","two");
map.put("3","three");
map.put("4","four");
System.out.println(map);

HashMap<String, String> Sites = new HashMap<String, String>();
// 添加键值对
Sites.put("one", "Google");
Sites.put("two", "Runoob");
Sites.put("three", "Taobao");
Sites.put("four", "Zhihu");
Sites.put("apple","lll");
System.out.println(Sites);
// key=商品名称,value=价格,这里以这个例子实现按名称排序和按价格排序.
Map store = new HashMap();

store.put("iphone12", 6799);
store.put("iphone12pro", 8499);
store.put("macbookPro", 19499);
store.put("ipadAir", 6999);
store.put("watch6", 3199);

// 直接输出HashMap得到的是一个无序Map(不是Arraylist那种顺序型储存)
System.out.println(store);

// {ipadAir=6999, iphone12pro=8499, macbookPro=19499, watch6=3199, iphone12=6799}
}

}

起初我验证Map的无序的时候 输出的总是有序的 增加了样本之后才变得无序

@@ -1010,7 +1016,7 @@

泛型未指定泛型前 我们需要这样写来获取对象

很容易出现ClassCastException,因为容易“误转型”:

-
1
2
3
4
5
6
7
8
9
10
11
public class Main {
public static void main(String[] args) throws IOException {
ArrayList list = new ArrayList();
list.add("hello world");
String s = (String) list.get(0);
System.out.println(s);
list.add(new Integer(123));
String s2 = (String) list.get(1);
}
}

+
1
2
3
4
5
6
7
8
9
10
11
public class Main {
public static void main(String[] args) throws IOException {
ArrayList list = new ArrayList();
list.add("hello world");
String s = (String) list.get(0);
System.out.println(s);
list.add(new Integer(123));
String s2 = (String) list.get(1);
}
}

则控制台会报错ClassCastException

@@ -1018,35 +1024,35 @@

泛型要解决上述问题,我们可以为String单独编写一种ArrayList

StringArrayList stringarraylist = new Stringarraylist()

底层就是String类型的数组了

-
1
2
3
4
5
6
7
public class StringArrayList {
private String[] array;
private int size;
public void add(String e) {...}
public void remove(int index) {...}
public String get(int index) {...}
}
+
1
2
3
4
5
6
7
public class StringArrayList {
private String[] array;
private int size;
public void add(String e) {...}
public void remove(int index) {...}
public String get(int index) {...}
}

这样一来,存入的必须是String,取出的也一定是String,不需要强制转型,因为编译器会强制检查放入的类型:

但是我们总不能针对Integer类型再写个IngtegerArrayList类型 针对Character再写个。。。

工程量十分的巨大 以及累赘

所以泛型的出现解决了这个问题

我们必须把ArrayList变成一种模板:ArrayList

-
1
2
3
4
5
6
7
public class ArrayList<T> {
private T[] array;
private int size;
public void add(T e) {...}
public void remove(int index) {...}
public T get(int index) {...}
}
+
1
2
3
4
5
6
7
public class ArrayList<T> {
private T[] array;
private int size;
public void add(T e) {...}
public void remove(int index) {...}
public T get(int index) {...}
}

T可以是任何class。这样一来,我们就实现了:编写一次模版,可以创建任意类型的ArrayList

我们可以定义任何一种类型的ArrayList啦

-
1
2
3
ArrayList<String> stringArrayList = new ArrayList<>();
ArrayList<Integer> IntegerArrayList = new ArrayList<>();
ArrayList<Character> characterArrayList = new ArrayList<>();
+
1
2
3
ArrayList<String> stringArrayList = new ArrayList<>();
ArrayList<Integer> IntegerArrayList = new ArrayList<>();
ArrayList<Character> characterArrayList = new ArrayList<>();

这样一来,既实现了编写一次,万能匹配,又通过编译器保证了类型安全:这就是泛型。

向上转型

在Java标准库中的ArrayList<T>实现了List<T>接口,它可以向上转型为List<T>

使用泛型

使用ArrayList时,如果不定义泛型类型时,泛型类型实际上就是Object

-
1
ArrayList list = new ArrayList();//此为Object
+
1
ArrayList list = new ArrayList();//此为Object
-
1
2
3
4
5
List list = new ArrayList();
list.add("Hello");
list.add("World");
String first = list.get(0); //这里会报错 因为默认是Object对象 需要强转
String second = (String) list.get(1);
+
1
2
3
4
5
List list = new ArrayList();
list.add("Hello");
list.add("World");
String first = list.get(0); //这里会报错 因为默认是Object对象 需要强转
String second = (String) list.get(1);

当我们定义泛型 List<String> list = new List<>();

-
1
2
3
4
5
6
7
//无编译器警告:
List<String> list = new ArrayList<String>();
list.add("Hello");
list.add("World");
// 无强制转型:
String first = list.get(0);
String second = list.get(1);
+
1
2
3
4
5
6
7
//无编译器警告:
List<String> list = new ArrayList<String>();
list.add("Hello");
list.add("World");
// 无强制转型:
String first = list.get(0);
String second = list.get(1);

当我们定义泛型类型<Number>后,List<T>的泛型接口变为强类型List<Number>

-
1
2
3
4
5
6
7
8
List<Number> list = new ArrayList<>();
list.add(3.14);//double类型
list.add(3.15);//float类型
list.add(1);//Integer类型
Number first = list.get(0);
Number second = list.get(1);
Number third = list.get(2);
System.out.println("first"+" "+first+" "+"second"+" "+second+" "+"third"+" "+third);
+
1
2
3
4
5
6
7
8
List<Number> list = new ArrayList<>();
list.add(3.14);//double类型
list.add(3.15);//float类型
list.add(1);//Integer类型
Number first = list.get(0);
Number second = list.get(1);
Number third = list.get(2);
System.out.println("first"+" "+first+" "+"second"+" "+second+" "+"third"+" "+third);

泛型接口

除了ArrayList<T>使用了泛型,还可以在接口中使用泛型。例如,Arrays.sort(Object[])可以对任意数组进行排序,但待排序的元素必须实现Comparable<T>这个泛型接口:

-
1
2
3
4
5
6
7
8
public interface Comparable<T> {
/**
* 返回负数: 当前实例比参数o小
* 返回0: 当前实例与参数o相等
* 返回正数: 当前实例比参数o大
*/
int compareTo(T o);
}
+
1
2
3
4
5
6
7
8
public interface Comparable<T> {
/**
* 返回负数: 当前实例比参数o小
* 返回0: 当前实例与参数o相等
* 返回正数: 当前实例比参数o大
*/
int compareTo(T o);
}
@@ -1143,9 +1149,9 @@

重新访问即可

Maven依赖

1
2
3
4
5
<!-- AMQP依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
+

重新访问即可

Maven依赖

1
2
3
4
5
<!-- AMQP依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
-

配置

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
77
application.yml

RabbitMQConfig.java

spring:

\#RabbitMQ

rabbitmq:

\#服务器地址

host: 192.168.10.100

\#用户名

username: guest

\#密码

password: guest

\#虚拟主机

virtual-host: /

\#端口

port: 5672

listener:

simple:

​ \#消费者最小数量

​ concurrency: 10

​ \#消费者最大数量

​ max-concurrency: 10

​ \#限制消费者每次只处理一条消息,处理完再继续下一条消息

​ prefetch: 1

​ \#启动时是否默认启动容器,默认true

​ auto-startup: true

​ \#被拒绝时重新进入队列

default-requeue-rejected: true

template:

retry:

​ \#发布重试,默认false

​ enabled: true

​ \#重试时间 默认1000ms

​ initial-interval: 1000

​ \#重试最大次数,默认3

​ max-attempts: 3

​ \#重试最大间隔时间,默认10000ms

​ max-interval: 10000

​ \#重试间隔的乘数。比如配2.0 第一次等10s,第二次等20s,第三次等40s

​ multiplier: 1.0
+

配置

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
77
application.yml

RabbitMQConfig.java

spring:

\#RabbitMQ

rabbitmq:

\#服务器地址

host: 192.168.10.100

\#用户名

username: guest

\#密码

password: guest

\#虚拟主机

virtual-host: /

\#端口

port: 5672

listener:

simple:

​ \#消费者最小数量

​ concurrency: 10

​ \#消费者最大数量

​ max-concurrency: 10

​ \#限制消费者每次只处理一条消息,处理完再继续下一条消息

​ prefetch: 1

​ \#启动时是否默认启动容器,默认true

​ auto-startup: true

​ \#被拒绝时重新进入队列

default-requeue-rejected: true

template:

retry:

​ \#发布重试,默认false

​ enabled: true

​ \#重试时间 默认1000ms

​ initial-interval: 1000

​ \#重试最大次数,默认3

​ max-attempts: 3

​ \#重试最大间隔时间,默认10000ms

​ max-interval: 10000

​ \#重试间隔的乘数。比如配2.0 第一次等10s,第二次等20s,第三次等40s

​ multiplier: 1.0
diff --git a/js/boot.js b/js/boot.js deleted file mode 100644 index 7c0dfea0e..000000000 --- a/js/boot.js +++ /dev/null @@ -1,16 +0,0 @@ -/* global Fluid */ - -Fluid.boot = {}; - -Fluid.boot.registerEvents = function() { - Fluid.events.billboard(); - Fluid.events.registerNavbarEvent(); - Fluid.events.registerParallaxEvent(); - Fluid.events.registerScrollDownArrowEvent(); - Fluid.events.registerScrollTopArrowEvent(); - Fluid.events.registerImageLoadedEvent(); -}; - -document.addEventListener('DOMContentLoaded', function() { - Fluid.boot.registerEvents(); -}); diff --git a/js/color-schema.js b/js/color-schema.js deleted file mode 100644 index 14f458287..000000000 --- a/js/color-schema.js +++ /dev/null @@ -1,266 +0,0 @@ -/* global Fluid */ - -/** - * Modified from https://blog.skk.moe/post/hello-darkmode-my-old-friend/ - */ -(function(window, document) { - var rootElement = document.documentElement; - var colorSchemaStorageKey = 'Fluid_Color_Scheme'; - var colorSchemaMediaQueryKey = '--color-mode'; - var userColorSchemaAttributeName = 'data-user-color-scheme'; - var defaultColorSchemaAttributeName = 'data-default-color-scheme'; - var colorToggleButtonSelector = '#color-toggle-btn'; - var colorToggleIconSelector = '#color-toggle-icon'; - - function setLS(k, v) { - try { - localStorage.setItem(k, v); - } catch (e) {} - } - - function removeLS(k) { - try { - localStorage.removeItem(k); - } catch (e) {} - } - - function getLS(k) { - try { - return localStorage.getItem(k); - } catch (e) { - return null; - } - } - - function getSchemaFromHTML() { - var res = rootElement.getAttribute(defaultColorSchemaAttributeName); - if (typeof res === 'string') { - return res.replace(/["'\s]/g, ''); - } - return null; - } - - function getSchemaFromCSSMediaQuery() { - var res = getComputedStyle(rootElement).getPropertyValue( - colorSchemaMediaQueryKey - ); - if (typeof res === 'string') { - return res.replace(/["'\s]/g, ''); - } - return null; - } - - function resetSchemaAttributeAndLS() { - rootElement.setAttribute(userColorSchemaAttributeName, getDefaultColorSchema()); - removeLS(colorSchemaStorageKey); - } - - var validColorSchemaKeys = { - dark : true, - light: true - }; - - function getDefaultColorSchema() { - // 取默认字段的值 - var schema = getSchemaFromHTML(); - // 如果明确指定了 schema 则返回 - if (validColorSchemaKeys[schema]) { - return schema; - } - // 默认优先按 prefers-color-scheme - schema = getSchemaFromCSSMediaQuery(); - if (validColorSchemaKeys[schema]) { - return schema; - } - // 否则按本地时间是否大于 18 点或凌晨 0 ~ 6 点 - var hours = new Date().getHours(); - if (hours >= 18 || (hours >= 0 && hours <= 6)) { - return 'dark'; - } - return 'light'; - } - - function applyCustomColorSchemaSettings(schema) { - // 接受从「开关」处传来的模式,或者从 localStorage 读取,否则按默认设置值 - var current = schema || getLS(colorSchemaStorageKey) || getDefaultColorSchema(); - - if (current === getDefaultColorSchema()) { - // 当用户切换的显示模式和默认模式相同时,则恢复为自动模式 - resetSchemaAttributeAndLS(); - } else if (validColorSchemaKeys[current]) { - rootElement.setAttribute( - userColorSchemaAttributeName, - current - ); - } else { - // 特殊情况重置 - resetSchemaAttributeAndLS(); - return; - } - - // 根据当前模式设置图标 - setButtonIcon(current); - - // 设置代码高亮 - setHighlightCSS(current); - - // 设置其他应用 - setApplications(current); - } - - var invertColorSchemaObj = { - dark : 'light', - light: 'dark' - }; - - function getIconClass(scheme) { - return 'icon-' + scheme; - } - - function toggleCustomColorSchema() { - var currentSetting = getLS(colorSchemaStorageKey); - - if (validColorSchemaKeys[currentSetting]) { - // 从 localStorage 中读取模式,并取相反的模式 - currentSetting = invertColorSchemaObj[currentSetting]; - } else if (currentSetting === null) { - // 当 localStorage 中没有相关值,或者 localStorage 抛了 Error - // 先按照按钮的状态进行切换 - var iconElement = document.querySelector(colorToggleIconSelector); - if (iconElement) { - currentSetting = iconElement.getAttribute('data'); - } - if (!iconElement || !validColorSchemaKeys[currentSetting]) { - // 当 localStorage 中没有相关值,或者 localStorage 抛了 Error,则读取默认值并切换到相反的模式 - currentSetting = invertColorSchemaObj[getSchemaFromCSSMediaQuery()]; - } - } else { - return; - } - // 将相反的模式写入 localStorage - setLS(colorSchemaStorageKey, currentSetting); - - return currentSetting; - } - - function setButtonIcon(schema) { - if (validColorSchemaKeys[schema]) { - // 切换图标 - var icon = getIconClass('dark'); - if (schema) { - icon = getIconClass(schema); - } - var iconElement = document.querySelector(colorToggleIconSelector); - if (iconElement) { - iconElement.setAttribute( - 'class', - 'iconfont ' + icon - ); - iconElement.setAttribute( - 'data', - invertColorSchemaObj[schema] - ); - } else { - // 如果图标不存在则说明图标还没加载出来,等到页面全部加载再尝试切换 - Fluid.utils.waitElementLoaded(colorToggleIconSelector, function() { - var iconElement = document.querySelector(colorToggleIconSelector); - if (iconElement) { - iconElement.setAttribute( - 'class', - 'iconfont ' + icon - ); - iconElement.setAttribute( - 'data', - invertColorSchemaObj[schema] - ); - } - }); - } - } - } - - function setHighlightCSS(schema) { - // 启用对应的代码高亮的样式 - var lightCss = document.getElementById('highlight-css'); - var darkCss = document.getElementById('highlight-css-dark'); - if (schema === 'dark') { - if (darkCss) { - darkCss.removeAttribute('disabled'); - } - if (lightCss) { - lightCss.setAttribute('disabled', ''); - } - } else { - if (lightCss) { - lightCss.removeAttribute('disabled'); - } - if (darkCss) { - darkCss.setAttribute('disabled', ''); - } - } - - setTimeout(function() { - // 设置代码块组件样式 - document.querySelectorAll('.markdown-body pre').forEach((pre) => { - var cls = Fluid.utils.getBackgroundLightness(pre) >= 0 ? 'code-widget-light' : 'code-widget-dark'; - var widget = pre.querySelector('.code-widget-light, .code-widget-dark'); - if (widget) { - widget.classList.remove('code-widget-light', 'code-widget-dark'); - widget.classList.add(cls); - } - }); - }, 200); - } - - function setApplications(schema) { - // 设置 remark42 评论主题 - if (window.REMARK42) { - window.REMARK42.changeTheme(schema); - } - - // 设置 cusdis 评论主题 - if (window.CUSDIS) { - window.CUSDIS.setTheme(schema); - } - - // 设置 utterances 评论主题 - var utterances = document.querySelector('.utterances-frame'); - if (utterances) { - var theme = window.UtterancesThemeLight; - if (schema === 'dark') { - theme = window.UtterancesThemeDark; - } - const message = { - type : 'set-theme', - theme: theme - }; - utterances.contentWindow.postMessage(message, 'https://utteranc.es'); - } - } - - // 当页面加载时,将显示模式设置为 localStorage 中自定义的值(如果有的话) - applyCustomColorSchemaSettings(); - - Fluid.utils.waitElementLoaded(colorToggleIconSelector, function() { - applyCustomColorSchemaSettings(); - var button = document.querySelector(colorToggleButtonSelector); - if (button) { - // 当用户点击切换按钮时,获得新的显示模式、写入 localStorage、并在页面上生效 - button.addEventListener('click', function() { - applyCustomColorSchemaSettings(toggleCustomColorSchema()); - }); - var icon = document.querySelector(colorToggleIconSelector); - if (icon) { - // 光标悬停在按钮上时,切换图标 - button.addEventListener('mouseenter', function() { - var current = icon.getAttribute('data'); - icon.classList.replace(getIconClass(invertColorSchemaObj[current]), getIconClass(current)); - }); - button.addEventListener('mouseleave', function() { - var current = icon.getAttribute('data'); - icon.classList.replace(getIconClass(current), getIconClass(invertColorSchemaObj[current])); - }); - } - } - }); -})(window, document); diff --git a/js/events.js b/js/events.js deleted file mode 100644 index e13ed077b..000000000 --- a/js/events.js +++ /dev/null @@ -1,167 +0,0 @@ -/* global Fluid */ - -HTMLElement.prototype.wrap = function(wrapper) { - this.parentNode.insertBefore(wrapper, this); - this.parentNode.removeChild(this); - wrapper.appendChild(this); -}; - -Fluid.events = { - - registerNavbarEvent: function() { - var navbar = jQuery('#navbar'); - if (navbar.length === 0) { - return; - } - var submenu = jQuery('#navbar .dropdown-menu'); - if (navbar.offset().top > 0) { - navbar.removeClass('navbar-dark'); - submenu.removeClass('navbar-dark'); - } - Fluid.utils.listenScroll(function() { - navbar[navbar.offset().top > 50 ? 'addClass' : 'removeClass']('top-nav-collapse'); - submenu[navbar.offset().top > 50 ? 'addClass' : 'removeClass']('dropdown-collapse'); - if (navbar.offset().top > 0) { - navbar.removeClass('navbar-dark'); - submenu.removeClass('navbar-dark'); - } else { - navbar.addClass('navbar-dark'); - submenu.removeClass('navbar-dark'); - } - }); - jQuery('#navbar-toggler-btn').on('click', function() { - jQuery('.animated-icon').toggleClass('open'); - jQuery('#navbar').toggleClass('navbar-col-show'); - }); - }, - - registerParallaxEvent: function() { - var ph = jQuery('#banner[parallax="true"]'); - if (ph.length === 0) { - return; - } - var board = jQuery('#board'); - if (board.length === 0) { - return; - } - var parallax = function() { - var pxv = jQuery(window).scrollTop() / 5; - var offset = parseInt(board.css('margin-top'), 10); - var max = 96 + offset; - if (pxv > max) { - pxv = max; - } - ph.css({ - transform: 'translate3d(0,' + pxv + 'px,0)' - }); - var sideCol = jQuery('.side-col'); - if (sideCol) { - sideCol.css({ - 'padding-top': pxv + 'px' - }); - } - }; - Fluid.utils.listenScroll(parallax); - }, - - registerScrollDownArrowEvent: function() { - var scrollbar = jQuery('.scroll-down-bar'); - if (scrollbar.length === 0) { - return; - } - scrollbar.on('click', function() { - Fluid.utils.scrollToElement('#board', -jQuery('#navbar').height()); - }); - }, - - registerScrollTopArrowEvent: function() { - var topArrow = jQuery('#scroll-top-button'); - if (topArrow.length === 0) { - return; - } - var board = jQuery('#board'); - if (board.length === 0) { - return; - } - var posDisplay = false; - var scrollDisplay = false; - // Position - var setTopArrowPos = function() { - var boardRight = board[0].getClientRects()[0].right; - var bodyWidth = document.body.offsetWidth; - var right = bodyWidth - boardRight; - posDisplay = right >= 50; - topArrow.css({ - 'bottom': posDisplay && scrollDisplay ? '20px' : '-60px', - 'right' : right - 64 + 'px' - }); - }; - setTopArrowPos(); - jQuery(window).resize(setTopArrowPos); - // Display - var headerHeight = board.offset().top; - Fluid.utils.listenScroll(function() { - var scrollHeight = document.body.scrollTop + document.documentElement.scrollTop; - scrollDisplay = scrollHeight >= headerHeight; - topArrow.css({ - 'bottom': posDisplay && scrollDisplay ? '20px' : '-60px' - }); - }); - // Click - topArrow.on('click', function() { - jQuery('body,html').animate({ - scrollTop: 0, - easing : 'swing' - }); - }); - }, - - registerImageLoadedEvent: function() { - if (!('NProgress' in window)) { return; } - - var bg = document.getElementById('banner'); - if (bg) { - var src = bg.style.backgroundImage; - var url = src.match(/\((.*?)\)/)[1].replace(/(['"])/g, ''); - var img = new Image(); - img.onload = function() { - window.NProgress && window.NProgress.inc(0.2); - }; - img.src = url; - if (img.complete) { img.onload(); } - } - - var notLazyImages = jQuery('main img:not([lazyload])'); - var total = notLazyImages.length; - for (const img of notLazyImages) { - const old = img.onload; - img.onload = function() { - old && old(); - window.NProgress && window.NProgress.inc(0.5 / total); - }; - if (img.complete) { img.onload(); } - } - }, - - billboard: function() { - if (!('console' in window)) { - return; - } - // eslint-disable-next-line no-console - console.log(` ------------------------------------------------- -| | -| ________ __ _ __ | -| |_ __ |[ | (_) | ] | -| | |_ \\_| | | __ _ __ .--.| | | -| | _| | |[ | | | [ |/ /'\`\\' | | -| _| |_ | | | \\_/ |, | || \\__/ | | -| |_____| [___]'.__.'_/[___]'.__.;__] | -| | -| Powered by Hexo x Fluid | -| GitHub: https://git.io/JqpVD | -| | ------------------------------------------------- - `); - } -}; diff --git a/js/img-lazyload.js b/js/img-lazyload.js deleted file mode 100644 index c0c8e4ef7..000000000 --- a/js/img-lazyload.js +++ /dev/null @@ -1,10 +0,0 @@ -/* global Fluid, CONFIG */ - -(function(window, document) { - for (const each of document.querySelectorAll('img[lazyload]')) { - Fluid.utils.waitElementVisible(each, function() { - each.removeAttribute('srcset'); - each.removeAttribute('lazyload'); - }, CONFIG.lazyload.offset_factor); - } -})(window, document); diff --git a/js/leancloud.js b/js/leancloud.js deleted file mode 100644 index 568faae92..000000000 --- a/js/leancloud.js +++ /dev/null @@ -1,194 +0,0 @@ -/* global CONFIG */ - -(function(window, document) { - // 查询存储的记录 - function getRecord(Counter, target) { - return new Promise(function(resolve, reject) { - Counter('get', '/classes/Counter?where=' + encodeURIComponent(JSON.stringify({ target }))) - .then(resp => resp.json()) - .then(({ results, code, error }) => { - if (code === 401) { - throw error; - } - if (results && results.length > 0) { - var record = results[0]; - resolve(record); - } else { - Counter('post', '/classes/Counter', { target, time: 0 }) - .then(resp => resp.json()) - .then((record, error) => { - if (error) { - throw error; - } - resolve(record); - }).catch(error => { - // eslint-disable-next-line no-console - console.error('Failed to create', error); - reject(error); - }); - } - }).catch((error) => { - // eslint-disable-next-line no-console - console.error('LeanCloud Counter Error:', error); - reject(error); - }); - }); - } - - // 发起自增请求 - function increment(Counter, incrArr) { - return new Promise(function(resolve, reject) { - Counter('post', '/batch', { - 'requests': incrArr - }).then((res) => { - res = res.json(); - if (res.error) { - throw res.error; - } - resolve(res); - }).catch((error) => { - // eslint-disable-next-line no-console - console.error('Failed to save visitor count', error); - reject(error); - }); - }); - } - - // 构建自增请求体 - function buildIncrement(objectId) { - return { - 'method': 'PUT', - 'path' : `/1.1/classes/Counter/${objectId}`, - 'body' : { - 'time': { - '__op' : 'Increment', - 'amount': 1 - } - } - }; - } - - // 校验是否为有效的 Host - function validHost() { - if (CONFIG.web_analytics.leancloud.ignore_local) { - var hostname = window.location.hostname; - if (hostname === 'localhost' || hostname === '127.0.0.1') { - return false; - } - } - return true; - } - - // 校验是否为有效的 UV - function validUV() { - var key = 'LeanCloud_UV_Flag'; - var flag = localStorage.getItem(key); - if (flag) { - // 距离标记小于 24 小时则不计为 UV - if (new Date().getTime() - parseInt(flag, 10) <= 86400000) { - return false; - } - } - localStorage.setItem(key, new Date().getTime().toString()); - return true; - } - - function addCount(Counter) { - var enableIncr = CONFIG.web_analytics.enable && !Fluid.ctx.dnt && validHost(); - var getterArr = []; - var incrArr = []; - - // 请求 PV 并自增 - var pvCtn = document.querySelector('#leancloud-site-pv-container'); - if (pvCtn) { - var pvGetter = getRecord(Counter, 'site-pv').then((record) => { - enableIncr && incrArr.push(buildIncrement(record.objectId)); - var ele = document.querySelector('#leancloud-site-pv'); - if (ele) { - ele.innerText = (record.time || 0) + (enableIncr ? 1 : 0); - pvCtn.style.display = 'inline'; - } - }); - getterArr.push(pvGetter); - } - - // 请求 UV 并自增 - var uvCtn = document.querySelector('#leancloud-site-uv-container'); - if (uvCtn) { - var uvGetter = getRecord(Counter, 'site-uv').then((record) => { - var incrUV = validUV() && enableIncr; - incrUV && incrArr.push(buildIncrement(record.objectId)); - var ele = document.querySelector('#leancloud-site-uv'); - if (ele) { - ele.innerText = (record.time || 0) + (incrUV ? 1 : 0); - uvCtn.style.display = 'inline'; - } - }); - getterArr.push(uvGetter); - } - - // 如果有页面浏览数节点,则请求浏览数并自增 - var viewCtn = document.querySelector('#leancloud-page-views-container'); - if (viewCtn) { - var path = eval(CONFIG.web_analytics.leancloud.path || 'window.location.pathname'); - var target = decodeURI(path.replace(/\/*(index.html)?$/, '/')); - var viewGetter = getRecord(Counter, target).then((record) => { - enableIncr && incrArr.push(buildIncrement(record.objectId)); - var ele = document.querySelector('#leancloud-page-views'); - if (ele) { - ele.innerText = (record.time || 0) + (enableIncr ? 1 : 0); - viewCtn.style.display = 'inline'; - } - }); - getterArr.push(viewGetter); - } - - // 如果启动计数自增,批量发起自增请求 - if (enableIncr) { - Promise.all(getterArr).then(() => { - incrArr.length > 0 && increment(Counter, incrArr); - }); - } - } - - var appId = CONFIG.web_analytics.leancloud.app_id; - var appKey = CONFIG.web_analytics.leancloud.app_key; - var serverUrl = CONFIG.web_analytics.leancloud.server_url; - - if (!appId) { - throw new Error('LeanCloud appId is empty'); - } - if (!appKey) { - throw new Error('LeanCloud appKey is empty'); - } - - function fetchData(api_server) { - var Counter = (method, url, data) => { - return fetch(`${api_server}/1.1${url}`, { - method, - headers: { - 'X-LC-Id' : appId, - 'X-LC-Key' : appKey, - 'Content-Type': 'application/json' - }, - body: JSON.stringify(data) - }); - }; - - addCount(Counter); - } - - var apiServer = serverUrl || `https://${appId.slice(0, 8).toLowerCase()}.api.lncldglobal.com`; - - if (apiServer) { - fetchData(apiServer); - } else { - fetch('https://app-router.leancloud.cn/2/route?appId=' + appId) - .then(resp => resp.json()) - .then((data) => { - if (data.api_server) { - fetchData('https://' + data.api_server); - } - }); - } -})(window, document); diff --git a/js/plugins.js b/js/plugins.js deleted file mode 100644 index ba89b89d1..000000000 --- a/js/plugins.js +++ /dev/null @@ -1,99 +0,0 @@ -/* global Fluid, CONFIG */ - -HTMLElement.prototype.wrap = function(wrapper) { - this.parentNode.insertBefore(wrapper, this); - this.parentNode.removeChild(this); - wrapper.appendChild(this); -}; - -Fluid.plugins = { - - typing: function(text) { - if (!('Typed' in window)) { return; } - - var typed = new window.Typed('#subtitle', { - strings: [ - ' ', - text + ' ' - ], - cursorChar: CONFIG.typing.cursorChar, - typeSpeed : CONFIG.typing.typeSpeed, - loop : CONFIG.typing.loop - }); - typed.stop(); - var subtitle = document.getElementById('subtitle'); - if (subtitle) { - subtitle.innerText = ''; - } - jQuery(document).ready(function() { - typed.start(); - }); - }, - - fancyBox: function(selector) { - if (!CONFIG.image_zoom.enable || !('fancybox' in jQuery)) { return; } - - jQuery(selector || '.markdown-body :not(a) > img, .markdown-body > img').each(function() { - var $image = jQuery(this); - var imageUrl = $image.attr('data-src') || $image.attr('src') || ''; - if (CONFIG.image_zoom.img_url_replace) { - var rep = CONFIG.image_zoom.img_url_replace; - var r1 = rep[0] || ''; - var r2 = rep[1] || ''; - if (r1) { - if (/^re:/.test(r1)) { - r1 = r1.replace(/^re:/, ''); - var reg = new RegExp(r1, 'gi'); - imageUrl = imageUrl.replace(reg, r2); - } else { - imageUrl = imageUrl.replace(r1, r2); - } - } - } - var $imageWrap = $image.wrap(` - ` - ).parent('a'); - if ($imageWrap.length !== 0) { - if ($image.is('.group-image-container img')) { - $imageWrap.attr('data-fancybox', 'group').attr('rel', 'group'); - } else { - $imageWrap.attr('data-fancybox', 'default').attr('rel', 'default'); - } - - var imageTitle = $image.attr('title') || $image.attr('alt'); - if (imageTitle) { - $imageWrap.attr('title', imageTitle).attr('data-caption', imageTitle); - } - } - }); - - jQuery.fancybox.defaults.hash = false; - jQuery('.fancybox').fancybox({ - loop : true, - helpers: { - overlay: { - locked: false - } - } - }); - }, - - imageCaption: function(selector) { - if (!CONFIG.image_caption.enable) { return; } - - jQuery(selector || `.markdown-body > p > img, .markdown-body > figure > img, - .markdown-body > p > a.fancybox, .markdown-body > figure > a.fancybox`).each(function() { - var $target = jQuery(this); - var $figcaption = $target.next('figcaption'); - if ($figcaption.length !== 0) { - $figcaption.addClass('image-caption'); - } else { - var imageTitle = $target.attr('title') || $target.attr('alt'); - if (imageTitle) { - $target.after(``); - } - } - }); - } -}; diff --git a/page/2/index.html b/page/2/index.html index 5fc7b449c..234862144 100644 --- a/page/2/index.html +++ b/page/2/index.html @@ -756,21 +756,21 @@

2.请简要说明Servlet中的生命周期

1.Servlet初始化后调用Init()方法

2.Servlet调用service()方法来处理客户端的请求。

3.Servlet销毁前调用destroy()方法终止

-

3.开启两个线程A,B,打印1到10 线程A打印奇数(1,3,5,7,9),线程B打印偶数(2,4,6,8,10).

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
package com.yuewen;

import java.util.Scanner;
import java.util.concurrent.locks.LockSupport;

public class ABXianC {
static Thread thread1;
static Thread thread2;
public static void main(String[] args) {
thread1 = new Thread(() -> {
for (int i = 1; i <= 9; i += 2) {
System.out.println(i);
LockSupport.unpark(thread2);
LockSupport.park();
}
});
thread2 = new Thread(() -> {
for (int i = 2; i <= 10; i = i + 2) {
LockSupport.park();
System.out.println(i);
LockSupport.unpark(thread1);
}
});
thread1.start();
thread2.start();
}

}

+

3.开启两个线程A,B,打印1到10 线程A打印奇数(1,3,5,7,9),线程B打印偶数(2,4,6,8,10).

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
package com.yuewen;

import java.util.Scanner;
import java.util.concurrent.locks.LockSupport;

public class ABXianC {
static Thread thread1;
static Thread thread2;
public static void main(String[] args) {
thread1 = new Thread(() -> {
for (int i = 1; i <= 9; i += 2) {
System.out.println(i);
LockSupport.unpark(thread2);
LockSupport.park();
}
});
thread2 = new Thread(() -> {
for (int i = 2; i <= 10; i = i + 2) {
LockSupport.park();
System.out.println(i);
LockSupport.unpark(thread1);
}
});
thread1.start();
thread2.start();
}

}

-

4.请编写代码实现单例模式,类名为Singletion

1.饿汉模式

1
2
3
4
5
6
public class Singleton{
static private Singleton instance = new Singleton();//因为无法实例化,所以必须是静态的
static public Singleton getInstance(){
return instance;
}
}
+

4.请编写代码实现单例模式,类名为Singletion

1.饿汉模式

1
2
3
4
5
6
public class Singleton{
static private Singleton instance = new Singleton();//因为无法实例化,所以必须是静态的
static public Singleton getInstance(){
return instance;
}
}
-

2.懒汉线程安全

1
2
3
4
5
6
7
8
9
10
11
12
package com.yuewen;

import java.util.Scanner;

public class Singletion {
private static Singleton instance;
private Singletion(){};
public static synchronized Singleton getInstance(){
if(instance == null) instance = new Singleton();
return instance;
}
}
+

2.懒汉线程安全

1
2
3
4
5
6
7
8
9
10
11
12
package com.yuewen;

import java.util.Scanner;

public class Singletion {
private static Singleton instance;
private Singletion(){};
public static synchronized Singleton getInstance(){
if(instance == null) instance = new Singleton();
return instance;
}
}
-

5.写一个Map转换成JavaBean的工具类方法,实现如下mapToObject方法(使用Java反射,不允许使用第三方库)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static Object mapToObject(Map<String,Object> map,Class<?> beanClass){
if(map == null) return null;
Object obj = null;
try{
obj = beanClass.newInstance();
Field[] fields = obj.getClass().getDeclaredFields();
for(Field field : fields)
{
int mod = field.getModifiers();
if(Modifier.isStatic(mod) || Modifier.isFinal(mod)){
continue;
}
field.setAccessible(true);
field.set(obj,map.get(field.getName()));
}
catch(Exception e)
e.printStackTrace();
}
return obj;
}
}
+

5.写一个Map转换成JavaBean的工具类方法,实现如下mapToObject方法(使用Java反射,不允许使用第三方库)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static Object mapToObject(Map<String,Object> map,Class<?> beanClass){
if(map == null) return null;
Object obj = null;
try{
obj = beanClass.newInstance();
Field[] fields = obj.getClass().getDeclaredFields();
for(Field field : fields)
{
int mod = field.getModifiers();
if(Modifier.isStatic(mod) || Modifier.isFinal(mod)){
continue;
}
field.setAccessible(true);
field.set(obj,map.get(field.getName()));
}
catch(Exception e)
e.printStackTrace();
}
return obj;
}
}

6.数据库操作是我们经常使用的一个技能, 请你完成一个简单的用户密码验证过程 ,给定的条件如下:

数据库中存在个用户表:users ,表结构如下:

-
1
2
3
4
5
6
7
CREATE TABLE `users` (
`uid` bigint(20) NOT NULL COMMENT '用户ID',
`user_name` varchar(32) NOT NULL COMMENT '用户账号',
`password` varchar(64) NOT NULL COMMENT '用户混淆密码',
PRIMARY KEY (`uid`),
UNIQUE KEY `u_user_name` (`user_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'
+
1
2
3
4
5
6
7
CREATE TABLE `users` (
`uid` bigint(20) NOT NULL COMMENT '用户ID',
`user_name` varchar(32) NOT NULL COMMENT '用户账号',
`password` varchar(64) NOT NULL COMMENT '用户混淆密码',
PRIMARY KEY (`uid`),
UNIQUE KEY `u_user_name` (`user_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'

完善以下方法

public boolean verifyPassword(String username,String password) {
Connection con = getConnection () ;// getConnection() 方法是个已有的方法可以获取到数据库连接 ,

// here is your code
}

-
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
public boolean verifyPassword(String username,String password){
Connection con=getConnection;
String sql="SELECT password FROM users WHERE user_name=?";
PreparedStatement pst=null;
ResultSet rs=null;
boolean flag=false;
try{
pst=con.prepareStatement(sql);
pst.setObject(1,username);
rs=pst.executeQuery();
while(rs.next()){
if(rs.getString("password").equals(password)){
flag=true;
}
}
}catch(ClassNotFoundException e){
e.printStackTrace();
}catch (SQLException e){
e.printStackTrace();
}finally {
try{
if(rs!=null) rs.close();
if(pst!=null) pst.close();
if(con!=null) con.close();
}catch(SQLException e){
e.printStackTrace();
}
}
return flag;
}

+
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
public boolean verifyPassword(String username,String password){
Connection con=getConnection;
String sql="SELECT password FROM users WHERE user_name=?";
PreparedStatement pst=null;
ResultSet rs=null;
boolean flag=false;
try{
pst=con.prepareStatement(sql);
pst.setObject(1,username);
rs=pst.executeQuery();
while(rs.next()){
if(rs.getString("password").equals(password)){
flag=true;
}
}
}catch(ClassNotFoundException e){
e.printStackTrace();
}catch (SQLException e){
e.printStackTrace();
}finally {
try{
if(rs!=null) rs.close();
if(pst!=null) pst.close();
if(con!=null) con.close();
}catch(SQLException e){
e.printStackTrace();
}
}
return flag;
}

7.介绍HashMap的数据结构、扩容机制,HashMap与Hashtable的区别,是否是线程安全的,并介绍ConcurrentHashMap的实现机制。

HashMap JDK1.8之前 数组+链表

JDK1.8之后 数组+链表+红黑树

@@ -817,7 +817,7 @@

14 建立三个线程A、B、C,A线程打印10次字母A,B线程打印10次字母B,C线程打印10次字母C,但是要求三个线程同时运行,并且实现交替打印,即按照ABCABCABC的顺序打印。

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
package com.yuewen;

import java.util.Scanner;
import java.util.concurrent.locks.LockSupport;
public class ABC {
static Thread A, B, C;

public static void main(String[] args) {
A = new Thread(() -> {
for (int i = 0; i < 10; i++) {
LockSupport.park();
System.out.print("A");
LockSupport.unpark(B);
}
});
B = new Thread(() -> {
for (int i = 0; i < 10; i++) {
LockSupport.park();
System.out.print("B");
LockSupport.unpark(C);
}
});
C = new Thread(() -> {
for (int i = 0; i < 10; i++) {
LockSupport.unpark(A);
LockSupport.park();
System.out.print("C");
}
});
A.start();
B.start();
C.start();
}
}

+

14 建立三个线程A、B、C,A线程打印10次字母A,B线程打印10次字母B,C线程打印10次字母C,但是要求三个线程同时运行,并且实现交替打印,即按照ABCABCABC的顺序打印。

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
package com.yuewen;

import java.util.Scanner;
import java.util.concurrent.locks.LockSupport;
public class ABC {
static Thread A, B, C;

public static void main(String[] args) {
A = new Thread(() -> {
for (int i = 0; i < 10; i++) {
LockSupport.park();
System.out.print("A");
LockSupport.unpark(B);
}
});
B = new Thread(() -> {
for (int i = 0; i < 10; i++) {
LockSupport.park();
System.out.print("B");
LockSupport.unpark(C);
}
});
C = new Thread(() -> {
for (int i = 0; i < 10; i++) {
LockSupport.unpark(A);
LockSupport.park();
System.out.print("C");
}
});
A.start();
B.start();
C.start();
}
}

15 请列举5个spring框架中的注解,并说明注解的用法以及使用场景

-
1
newProxyInstance(ClassLoader loader,类<?>[] interfaces,InvacationHandle)
+
1
newProxyInstance(ClassLoader loader,类<?>[] interfaces,InvacationHandle)

方法有三个参数

第一个参数:类加载器

@@ -1166,14 +1166,14 @@

1
2
3
4
5
6
7
8
9
10
result = []
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return

for 选择 in 选择列表:
做选择
backtrack(路径, 选择列表)
撤销选择
+
1
2
3
4
5
6
7
8
9
10
result = []
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return

for 选择 in 选择列表:
做选择
backtrack(路径, 选择列表)
撤销选择

核心在于for循环里面的递归,在递归调用之前做选择,在递归调用之后撤销选择。

例子1 全排列问题

有n个数 每个数都只能用一次 求出所有能排列的可能性。全排列个数为n!个

PS:为了简单清晰起见,我们这次讨论的全排列问题不包含重复的数字

如果已知有多少个数的情况下,我们通常可以使用n层for循环暴力遍历所有数

例如3个数 我们可以暴力写法为

-
1
2
3
4
5
6
7
8
9
10
11
12
13
for(int i = 0;i < n;i++)
for(int j = 0;j < n;j++)
for(int k = 0;k < n;k++)
{
if(i!=j && i!=k && j!=k)
{
// 1 2 3 1 3 2 2 1 3 2 3 1 3 2 1 3 1 2
list.add(nums[i]);
list.add(nums[j]);
list.add(nums[k]);
res.add(list);
}
}
+
1
2
3
4
5
6
7
8
9
10
11
12
13
for(int i = 0;i < n;i++)
for(int j = 0;j < n;j++)
for(int k = 0;k < n;k++)
{
if(i!=j && i!=k && j!=k)
{
// 1 2 3 1 3 2 2 1 3 2 3 1 3 2 1 3 1 2
list.add(nums[i]);
list.add(nums[j]);
list.add(nums[k]);
res.add(list);
}
}
@@ -1185,7 +1185,7 @@

我们定义的backtrack函数就是指针一样,在这棵树上游走,同时要正确维护每个节点的属性,每当走到树的底层,其路径就是一个全排列。

再进一步,如何遍历一颗树,

-
1
2
3
4
5
6
void traverse(TreeNode root) {
for (TreeNode child : root.childern)
// 前序遍历需要的操作
traverse(child);
// 后序遍历需要的操作
}
+
1
2
3
4
5
6
void traverse(TreeNode root) {
for (TreeNode child : root.childern)
// 前序遍历需要的操作
traverse(child);
// 后序遍历需要的操作
}
@@ -1194,12 +1194,12 @@

回溯代码核心框架

-
1
2
3
4
5
6
7
8
9
for 选择 in 选择列表:
# 做选择
将该选择从选择列表移除
路径.add(选择)
backtrack(路径, 选择列表)
# 撤销选择
路径.remove(选择)
将该选择再加入选择列表

+
1
2
3
4
5
6
7
8
9
for 选择 in 选择列表:
# 做选择
将该选择从选择列表移除
路径.add(选择)
backtrack(路径, 选择列表)
# 撤销选择
路径.remove(选择)
将该选择再加入选择列表

我们只要在递归之前做出选择,在递归之后撤销自己的选择,就能得到每个节点的选择路径和列表。

-

全排列代码

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
List<List<Integer>> res = new LinkedList<>();

/* 主函数,输入一组不重复的数字,返回它们的全排列 */
List<List<Integer>> permute(int[] nums) {
// 记录「路径」
LinkedList<Integer> track = new LinkedList<>();
backtrack(nums, track);
return res;
}

// 路径:记录在 track 中
// 选择列表:nums 中不存在于 track 的那些元素
// 结束条件:nums 中的元素全都在 track 中出现
void backtrack(int[] nums, LinkedList<Integer> track) {
// 触发结束条件
if (track.size() == nums.length) {
res.add(new LinkedList(track));
return;
}

for (int i = 0; i < nums.length; i++) {
// 排除不合法的选择
if (track.contains(nums[i]))
continue;
// 做选择
track.add(nums[i]);
// 进入下一层决策树
backtrack(nums, track);
// 取消选择
track.removeLast();
}
}
+

全排列代码

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
List<List<Integer>> res = new LinkedList<>();

/* 主函数,输入一组不重复的数字,返回它们的全排列 */
List<List<Integer>> permute(int[] nums) {
// 记录「路径」
LinkedList<Integer> track = new LinkedList<>();
backtrack(nums, track);
return res;
}

// 路径:记录在 track 中
// 选择列表:nums 中不存在于 track 的那些元素
// 结束条件:nums 中的元素全都在 track 中出现
void backtrack(int[] nums, LinkedList<Integer> track) {
// 触发结束条件
if (track.size() == nums.length) {
res.add(new LinkedList(track));
return;
}

for (int i = 0; i < nums.length; i++) {
// 排除不合法的选择
if (track.contains(nums[i]))
continue;
// 做选择
track.add(nums[i]);
// 进入下一层决策树
backtrack(nums, track);
// 取消选择
track.removeLast();
}
}

我们这里稍微做了些变通,没有显式记录「选择列表」,而是通过 numstrack 推导出当前的选择列表:

通过contains函数来判断该数是否已经被使用。

@@ -1210,10 +1210,10 @@

例子2 N皇后问题

题意

给你一个N*N的棋盘,,让你放置 N 个皇后,使得它们不能互相攻击。

PS:皇后可以攻击同一行、同一列、左上左下右上右下四个方向的任意单位。

这个问题本质上跟全排列问题差不多,决策树的每一层表示棋盘上的每一行;每个节点可以做出的选择是,在该行的任意一列放置一个皇后。

-
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
vector<vector<string>> res;

/* 输入棋盘边长 n,返回所有合法的放置 */
vector<vector<string>> solveNQueens(int n) {
// '.' 表示空,'Q' 表示皇后,初始化空棋盘。
vector<string> board(n, string(n, '.'));
backtrack(board, 0);
return res;
}

// 路径:board 中小于 row 的那些行都已经成功放置了皇后
// 选择列表:第 row 行的所有列都是放置皇后的选择
// 结束条件:row 超过 board 的最后一行
void backtrack(vector<string>& board, int row) {
// 触发结束条件
if (row == board.size()) {
res.push_back(board);
return;
}

int n = board[row].size();
for (int col = 0; col < n; col++) {
// 排除不合法选择
if (!isValid(board, row, col))
continue;
// 做选择
board[row][col] = 'Q';
// 进入下一行决策
backtrack(board, row + 1);
// 撤销选择
board[row][col] = '.';
}
}
+
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
vector<vector<string>> res;

/* 输入棋盘边长 n,返回所有合法的放置 */
vector<vector<string>> solveNQueens(int n) {
// '.' 表示空,'Q' 表示皇后,初始化空棋盘。
vector<string> board(n, string(n, '.'));
backtrack(board, 0);
return res;
}

// 路径:board 中小于 row 的那些行都已经成功放置了皇后
// 选择列表:第 row 行的所有列都是放置皇后的选择
// 结束条件:row 超过 board 的最后一行
void backtrack(vector<string>& board, int row) {
// 触发结束条件
if (row == board.size()) {
res.push_back(board);
return;
}

int n = board[row].size();
for (int col = 0; col < n; col++) {
// 排除不合法选择
if (!isValid(board, row, col))
continue;
// 做选择
board[row][col] = 'Q';
// 进入下一行决策
backtrack(board, row + 1);
// 撤销选择
board[row][col] = '.';
}
}

IsValid()函数

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* 是否可以在 board[row][col] 放置皇后? */
bool isValid(vector<string>& board, int row, int col) {
int n = board.size();
// 检查列是否有皇后互相冲突
for (int i = 0; i < n; i++) {
if (board[i][col] == 'Q')
return false;
}
// 检查右上方是否有皇后互相冲突
for (int i = row - 1, j = col + 1;
i >= 0 && j < n; i--, j++) {
if (board[i][j] == 'Q')
return false;
}
// 检查左上方是否有皇后互相冲突
for (int i = row - 1, j = col - 1;
i >= 0 && j >= 0; i--, j--) {
if (board[i][j] == 'Q')
return false;
}
return true;
}

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* 是否可以在 board[row][col] 放置皇后? */
bool isValid(vector<string>& board, int row, int col) {
int n = board.size();
// 检查列是否有皇后互相冲突
for (int i = 0; i < n; i++) {
if (board[i][col] == 'Q')
return false;
}
// 检查右上方是否有皇后互相冲突
for (int i = row - 1, j = col + 1;
i >= 0 && j < n; i--, j++) {
if (board[i][j] == 'Q')
return false;
}
// 检查左上方是否有皇后互相冲突
for (int i = row - 1, j = col - 1;
i >= 0 && j >= 0; i--, j--) {
if (board[i][j] == 'Q')
return false;
}
return true;
}

函数 backtrack 依然像个在决策树上游走的指针,通过 row 和 col 就可以表示函数遍历到的位置,通过 isValid 函数可以将不符合条件的情况剪枝:

@@ -1304,14 +1304,14 @@

1
2
3
4
5
6
7
8
9
10
result = []
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return

for 选择 in 选择列表:
做选择
backtrack(路径, 选择列表)
撤销选择
+
1
2
3
4
5
6
7
8
9
10
result = []
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return

for 选择 in 选择列表:
做选择
backtrack(路径, 选择列表)
撤销选择

核心在于for循环里面的递归,在递归调用之前做选择,在递归调用之后撤销选择。

例子1 全排列问题

有n个数 每个数都只能用一次 求出所有能排列的可能性。全排列个数为n!个

PS:为了简单清晰起见,我们这次讨论的全排列问题不包含重复的数字

如果已知有多少个数的情况下,我们通常可以使用n层for循环暴力遍历所有数

例如3个数 我们可以暴力写法为

-
1
2
3
4
5
6
7
8
9
10
11
12
13
for(int i = 0;i < n;i++)
for(int j = 0;j < n;j++)
for(int k = 0;k < n;k++)
{
if(i!=j && i!=k && j!=k)
{
// 1 2 3 1 3 2 2 1 3 2 3 1 3 2 1 3 1 2
list.add(nums[i]);
list.add(nums[j]);
list.add(nums[k]);
res.add(list);
}
}
+
1
2
3
4
5
6
7
8
9
10
11
12
13
for(int i = 0;i < n;i++)
for(int j = 0;j < n;j++)
for(int k = 0;k < n;k++)
{
if(i!=j && i!=k && j!=k)
{
// 1 2 3 1 3 2 2 1 3 2 3 1 3 2 1 3 1 2
list.add(nums[i]);
list.add(nums[j]);
list.add(nums[k]);
res.add(list);
}
}
@@ -1323,7 +1323,7 @@

我们定义的backtrack函数就是指针一样,在这棵树上游走,同时要正确维护每个节点的属性,每当走到树的底层,其路径就是一个全排列。

再进一步,如何遍历一颗树,

-
1
2
3
4
5
6
void traverse(TreeNode root) {
for (TreeNode child : root.childern)
// 前序遍历需要的操作
traverse(child);
// 后序遍历需要的操作
}
+
1
2
3
4
5
6
void traverse(TreeNode root) {
for (TreeNode child : root.childern)
// 前序遍历需要的操作
traverse(child);
// 后序遍历需要的操作
}
@@ -1332,12 +1332,12 @@

回溯代码核心框架

-
1
2
3
4
5
6
7
8
9
for 选择 in 选择列表:
# 做选择
将该选择从选择列表移除
路径.add(选择)
backtrack(路径, 选择列表)
# 撤销选择
路径.remove(选择)
将该选择再加入选择列表

+
1
2
3
4
5
6
7
8
9
for 选择 in 选择列表:
# 做选择
将该选择从选择列表移除
路径.add(选择)
backtrack(路径, 选择列表)
# 撤销选择
路径.remove(选择)
将该选择再加入选择列表

我们只要在递归之前做出选择,在递归之后撤销自己的选择,就能得到每个节点的选择路径和列表。

-

全排列代码

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
List<List<Integer>> res = new LinkedList<>();

/* 主函数,输入一组不重复的数字,返回它们的全排列 */
List<List<Integer>> permute(int[] nums) {
// 记录「路径」
LinkedList<Integer> track = new LinkedList<>();
backtrack(nums, track);
return res;
}

// 路径:记录在 track 中
// 选择列表:nums 中不存在于 track 的那些元素
// 结束条件:nums 中的元素全都在 track 中出现
void backtrack(int[] nums, LinkedList<Integer> track) {
// 触发结束条件
if (track.size() == nums.length) {
res.add(new LinkedList(track));
return;
}

for (int i = 0; i < nums.length; i++) {
// 排除不合法的选择
if (track.contains(nums[i]))
continue;
// 做选择
track.add(nums[i]);
// 进入下一层决策树
backtrack(nums, track);
// 取消选择
track.removeLast();
}
}
+

全排列代码

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
List<List<Integer>> res = new LinkedList<>();

/* 主函数,输入一组不重复的数字,返回它们的全排列 */
List<List<Integer>> permute(int[] nums) {
// 记录「路径」
LinkedList<Integer> track = new LinkedList<>();
backtrack(nums, track);
return res;
}

// 路径:记录在 track 中
// 选择列表:nums 中不存在于 track 的那些元素
// 结束条件:nums 中的元素全都在 track 中出现
void backtrack(int[] nums, LinkedList<Integer> track) {
// 触发结束条件
if (track.size() == nums.length) {
res.add(new LinkedList(track));
return;
}

for (int i = 0; i < nums.length; i++) {
// 排除不合法的选择
if (track.contains(nums[i]))
continue;
// 做选择
track.add(nums[i]);
// 进入下一层决策树
backtrack(nums, track);
// 取消选择
track.removeLast();
}
}

我们这里稍微做了些变通,没有显式记录「选择列表」,而是通过 numstrack 推导出当前的选择列表:

通过contains函数来判断该数是否已经被使用。

@@ -1348,10 +1348,10 @@

例子2 N皇后问题

题意

给你一个N*N的棋盘,,让你放置 N 个皇后,使得它们不能互相攻击。

PS:皇后可以攻击同一行、同一列、左上左下右上右下四个方向的任意单位。

这个问题本质上跟全排列问题差不多,决策树的每一层表示棋盘上的每一行;每个节点可以做出的选择是,在该行的任意一列放置一个皇后。

-
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
vector<vector<string>> res;

/* 输入棋盘边长 n,返回所有合法的放置 */
vector<vector<string>> solveNQueens(int n) {
// '.' 表示空,'Q' 表示皇后,初始化空棋盘。
vector<string> board(n, string(n, '.'));
backtrack(board, 0);
return res;
}

// 路径:board 中小于 row 的那些行都已经成功放置了皇后
// 选择列表:第 row 行的所有列都是放置皇后的选择
// 结束条件:row 超过 board 的最后一行
void backtrack(vector<string>& board, int row) {
// 触发结束条件
if (row == board.size()) {
res.push_back(board);
return;
}

int n = board[row].size();
for (int col = 0; col < n; col++) {
// 排除不合法选择
if (!isValid(board, row, col))
continue;
// 做选择
board[row][col] = 'Q';
// 进入下一行决策
backtrack(board, row + 1);
// 撤销选择
board[row][col] = '.';
}
}
+
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
vector<vector<string>> res;

/* 输入棋盘边长 n,返回所有合法的放置 */
vector<vector<string>> solveNQueens(int n) {
// '.' 表示空,'Q' 表示皇后,初始化空棋盘。
vector<string> board(n, string(n, '.'));
backtrack(board, 0);
return res;
}

// 路径:board 中小于 row 的那些行都已经成功放置了皇后
// 选择列表:第 row 行的所有列都是放置皇后的选择
// 结束条件:row 超过 board 的最后一行
void backtrack(vector<string>& board, int row) {
// 触发结束条件
if (row == board.size()) {
res.push_back(board);
return;
}

int n = board[row].size();
for (int col = 0; col < n; col++) {
// 排除不合法选择
if (!isValid(board, row, col))
continue;
// 做选择
board[row][col] = 'Q';
// 进入下一行决策
backtrack(board, row + 1);
// 撤销选择
board[row][col] = '.';
}
}

IsValid()函数

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* 是否可以在 board[row][col] 放置皇后? */
bool isValid(vector<string>& board, int row, int col) {
int n = board.size();
// 检查列是否有皇后互相冲突
for (int i = 0; i < n; i++) {
if (board[i][col] == 'Q')
return false;
}
// 检查右上方是否有皇后互相冲突
for (int i = row - 1, j = col + 1;
i >= 0 && j < n; i--, j++) {
if (board[i][j] == 'Q')
return false;
}
// 检查左上方是否有皇后互相冲突
for (int i = row - 1, j = col - 1;
i >= 0 && j >= 0; i--, j--) {
if (board[i][j] == 'Q')
return false;
}
return true;
}

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* 是否可以在 board[row][col] 放置皇后? */
bool isValid(vector<string>& board, int row, int col) {
int n = board.size();
// 检查列是否有皇后互相冲突
for (int i = 0; i < n; i++) {
if (board[i][col] == 'Q')
return false;
}
// 检查右上方是否有皇后互相冲突
for (int i = row - 1, j = col + 1;
i >= 0 && j < n; i--, j++) {
if (board[i][j] == 'Q')
return false;
}
// 检查左上方是否有皇后互相冲突
for (int i = row - 1, j = col - 1;
i >= 0 && j >= 0; i--, j--) {
if (board[i][j] == 'Q')
return false;
}
return true;
}

函数 backtrack 依然像个在决策树上游走的指针,通过 row 和 col 就可以表示函数遍历到的位置,通过 isValid 函数可以将不符合条件的情况剪枝:

diff --git a/page/3/index.html b/page/3/index.html index efdd32cc0..220f0bde3 100644 --- a/page/3/index.html +++ b/page/3/index.html @@ -575,16 +575,16 @@

输入格式

一个整数 nn。

输出格式

一个整数,表示整数 nn 的十六进制表示包含的圈圈总数。

数据范围

前三个测试点满足 0≤n≤1000≤n≤100,
所有测试点满足 0≤n≤2×1090≤n≤2×109。

-

输入样例1:

1
11
+

输入样例1:

1
11
-

输出样例1:

1
2
+

输出样例1:

1
2
-

输入样例2:

1
14
+

输入样例2:

1
14
-

输出样例2:

1
0
+

输出样例2:

1
0

简单模拟即可 注意特判0 每次都在这里栽跟头…

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// package com.ACC;

import java.util.Scanner;

public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
long n = scanner.nextLong();
long ans = 0;
if(n == 0) System.out.println(1);
else{while(n > 0)
{
long m = n% 16;
// System.out.println(m);
if(m == 0|| m == 4 || m == 6 || m==9 || m== 10||m == 13) ans++;
else if(m == 8 || m == 11) ans +=2;
n = n / 16;
}
System.out.println(ans);
}
}
}

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// package com.ACC;

import java.util.Scanner;

public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
long n = scanner.nextLong();
long ans = 0;
if(n == 0) System.out.println(1);
else{while(n > 0)
{
long m = n% 16;
// System.out.println(m);
if(m == 0|| m == 4 || m == 6 || m==9 || m== 10||m == 13) ans++;
else if(m == 8 || m == 11) ans +=2;
n = n / 16;
}
System.out.println(ans);
}
}
}

题目B

农夫约翰有 nn 片连续的农田,编号依次为 1∼n1∼n。

其中有 kk 片农田中装有洒水器。

@@ -600,12 +600,12 @@

输出格式

每组数据输出一行答案。

数据范围

前三个测试点满足 1≤n≤51≤n≤5,
所有测试点满足 1≤T≤2001≤T≤200,1≤n≤2001≤n≤200,1≤k≤n1≤k≤n,1≤xi≤n1≤xi≤n,xi−1<xixi−1<xi,TT 组数据的 nn 相加之和不超过 200200。

-

输入样例:

1
2
3
4
5
6
7
3
5 1
3
3 3
1 2 3
4 1
1
+

输入样例:

1
2
3
4
5
6
7
3
5 1
3
3 3
1 2 3
4 1
1
-

输出样例:

1
2
3
3
1
4
+

输出样例:

1
2
3
3
1
4

模拟即可

-
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
// package com.ACC;

import java.util.Scanner;

public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int T = scanner.nextInt();
int[] n = new int[T];
int[] k = new int[T];
for (int i = 0; i < T; i++) {
n[i] = scanner.nextInt();
k[i] = scanner.nextInt();
int[] a = new int[k[i]];
for (int j = 0; j < k[i]; j++) {
a[j] = scanner.nextInt();
}
int q = n[i] - a[k[i] -1]+1;
int ans = Math.max(a[0],q);
if(k[i] == 1) ans =Math.max(a[0],n[i] - a[0]+1);
else {
for (int j = 0; j + 1 < k[i]; j++) {
ans = Math.max(ans, (a[j + 1] - a[j]+2)/2);
}
}
System.out.println(ans);
}
}
}

+
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
// package com.ACC;

import java.util.Scanner;

public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int T = scanner.nextInt();
int[] n = new int[T];
int[] k = new int[T];
for (int i = 0; i < T; i++) {
n[i] = scanner.nextInt();
k[i] = scanner.nextInt();
int[] a = new int[k[i]];
for (int j = 0; j < k[i]; j++) {
a[j] = scanner.nextInt();
}
int q = n[i] - a[k[i] -1]+1;
int ans = Math.max(a[0],q);
if(k[i] == 1) ans =Math.max(a[0],n[i] - a[0]+1);
else {
for (int j = 0; j + 1 < k[i]; j++) {
ans = Math.max(ans, (a[j + 1] - a[j]+2)/2);
}
}
System.out.println(ans);
}
}
}

题目C

@@ -1338,16 +1338,16 @@

语法

1.无参数,无返回值

1
2
Runnable t = () -> System.out.println("Hello Lambda")
t.run();
+

语法

1.无参数,无返回值

1
2
Runnable t = () -> System.out.println("Hello Lambda")
t.run();
-

2.有参数,无返回值

1
(x)-> System.out.println(x)
+

2.有参数,无返回值

1
(x)-> System.out.println(x)

若只有一个参数,小括号可以省略不写

-
1
x-> System.out.println(x)
+
1
x-> System.out.println(x)

3.有两个以上的参数,有返回值,并且 Lambda体中有多条语句

-
1
Comparator<Integer> com =(x,y)->(System.out.print1n("函数式接口") return Integer.compare(x,y)
+
1
Comparator<Integer> com =(x,y)->(System.out.print1n("函数式接口") return Integer.compare(x,y)

…待续

diff --git a/page/4/index.html b/page/4/index.html index fbf5ec7dd..e0bd05500 100644 --- a/page/4/index.html +++ b/page/4/index.html @@ -252,7 +252,7 @@

== 与 equals

String类中的equals方法是重写过的 因为Object的equals类方法是比较的对象的内存地址 而String类中的equals方法比较的是对象的值

当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象

Stringequals()方法:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}

HashCode() 与 equals()

@@ -527,26 +527,26 @@

准备找到这一项 点开

记录下这个的信息 userAccount即是学号 encodedd是学号+密码的base64加密

maven依赖

-
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
77
78
79
80
81
82
83
84
85
86
87
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>JWXT</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>JWXT</name>
<description>JWXT</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.10.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-mail -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
<version>2.5.4</version>
</dependency>
<!--Lang3工具类-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
</dependency>
<!-- 发送邮件 -->
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.4.7</version>
</dependency>

<!--HttpClient-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.6</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.3.4</version>
</dependency>
<!--阿里巴巴 json-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.51</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

+
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
77
78
79
80
81
82
83
84
85
86
87
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>JWXT</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>JWXT</name>
<description>JWXT</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.10.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-mail -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
<version>2.5.4</version>
</dependency>
<!--Lang3工具类-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
</dependency>
<!-- 发送邮件 -->
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.4.7</version>
</dependency>

<!--HttpClient-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.6</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.3.4</version>
</dependency>
<!--阿里巴巴 json-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.51</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

在IDEA新建Springboot框架 然后在新建类LoginPZ

-
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
77
package com.example.jwxt;


import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;

import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* 登录
*/
public class LoginPz {
public static String hello() {
Map<String,String> param = new HashMap<>();
String userAccount = "你的学号";
String userPassword = "密码";
String encoded = "上文提到的加密信息";//加密信息
param.put("userAccount",userAccount);
param.put("userPassword",userPassword);
param.put("encoded",encoded);
List<URI> redirectLocations = null;
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
String resultString = "";
try {
// 创建Http Post请求
HttpPost httpPost = new HttpPost("http://218.75.197.123:83/jsxsd/xk/LoginToXk");
//请求头
httpPost.addHeader("Content-Type","application/x-www-form-urlencoded");
httpPost.addHeader("Cookie","JSESSIONID=22B4C4CE6240C6C53FF6BC3C197E3B83; SERVERID=121; JSESSIONID=8FFFAEA49DC840CE5A3135330C06CED3");
// 创建参数列表
if (param != null) {
List<NameValuePair> paramList = new ArrayList<>();
for (String key : param.keySet()) {
paramList.add(new BasicNameValuePair(key, param.get(key)));
}
// 模拟表单登录
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList);
httpPost.setEntity(entity);
}
// 执行http请求
HttpClientContext context = HttpClientContext.create();
response = httpClient.execute(httpPost,context);
//获取Cookie信息,得到两个参数 JSESSIONID 、 Serverid
List<Cookie> cookies = context.getCookieStore().getCookies();
for (Cookie cookie : cookies) {
String name = cookie.getName();
String value = cookie.getValue();
// System.out.println("name:"+name+","+value);
return value;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return " ";
}
}
+
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
77
package com.example.jwxt;


import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;

import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* 登录
*/
public class LoginPz {
public static String hello() {
Map<String,String> param = new HashMap<>();
String userAccount = "你的学号";
String userPassword = "密码";
String encoded = "上文提到的加密信息";//加密信息
param.put("userAccount",userAccount);
param.put("userPassword",userPassword);
param.put("encoded",encoded);
List<URI> redirectLocations = null;
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
String resultString = "";
try {
// 创建Http Post请求
HttpPost httpPost = new HttpPost("http://218.75.197.123:83/jsxsd/xk/LoginToXk");
//请求头
httpPost.addHeader("Content-Type","application/x-www-form-urlencoded");
httpPost.addHeader("Cookie","JSESSIONID=22B4C4CE6240C6C53FF6BC3C197E3B83; SERVERID=121; JSESSIONID=8FFFAEA49DC840CE5A3135330C06CED3");
// 创建参数列表
if (param != null) {
List<NameValuePair> paramList = new ArrayList<>();
for (String key : param.keySet()) {
paramList.add(new BasicNameValuePair(key, param.get(key)));
}
// 模拟表单登录
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList);
httpPost.setEntity(entity);
}
// 执行http请求
HttpClientContext context = HttpClientContext.create();
response = httpClient.execute(httpPost,context);
//获取Cookie信息,得到两个参数 JSESSIONID 、 Serverid
List<Cookie> cookies = context.getCookieStore().getCookies();
for (Cookie cookie : cookies) {
String name = cookie.getName();
String value = cookie.getValue();
// System.out.println("name:"+name+","+value);
return value;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return " ";
}
}

这是模拟教务系统登陆 然后我们新建类GetUserInfo

代码如下

-
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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package com.example.jwxt;

import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.stream.Collectors;

/**
* 登录后获取用户信息
* 相关解析HTMl的操作不是固定的
* 需要结合自己的需求来操作
* 此处仅作为演示。
*/
public class GetUserInfo {
static int idx = 0;
public static void cj() throws Exception {
for (int i = 0; i < 1000000000; ) {
LoginPz loginPz = new LoginPz();
String cookies = loginPz.hello();
CloseableHttpClient httpClient = HttpClients.createDefault();
try {
HttpGet httpGet = new HttpGet("http://218.75.197.123:83/jsxsd/kscj/cjcx_list?kksj=2021-2022-1");//2021-2022-1为学期信息 可自行更改
//增加头信息
//注意此处需要修改为正确的JSESSIONID 和 SERVERID
httpGet.addHeader("Cookie", "JSESSIONID" + "=" + cookies + "; SERVERID=121; JSESSIONID=8FFFAEA49DC840CE5A3135330C06CED3");
httpGet.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36");
httpGet.addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9");
httpGet.addHeader("Connection", "keep-alive");

//执行
CloseableHttpResponse response = httpClient.execute(httpGet);
String html = EntityUtils.toString(response.getEntity(), "utf8");
Document parse = Jsoup.parse(html);
parseHtml(parse);
Thread thread = new Thread();
thread.sleep(600000);
} catch (Exception e) {
}

}
}

private static void parseHtml(Document parse) throws Exception {
// int idx = 2;//目前已出成绩科目
//选择table
Element table = parse.getElementById("dataList");
//选择tr
Elements cells = table.select("tr");

StringBuilder stringBuilder = new StringBuilder(); //用来存储成绩信息
//自己存储的每一行数据
List<List<String>> tables = new ArrayList<>();

for (int index = 1; index < cells.size(); index++) {
//第一行是表头 index = 0 跳过

//第二行开始table数据

Element row = cells.get(index);

//搜索tr下的所有的td
Elements rows = row.select("td");

//每一行的数据
List<String> dataList = new ArrayList<>();

for (Element element : rows) {
dataList.add(element.text());
}
tables.add(dataList);
}

//获取表头
Elements headers = cells.get(0).select("th");
List<String> tableHeader = headers.stream()
.map(Element::text)
.collect(Collectors.toList());

//打印数据
for (String str : tableHeader) {
System.out.printf(str + " ");
stringBuilder.append(str + " ");
}
stringBuilder.append("\r\n");
System.out.println("");
for (List<String> strs : tables) {
for (String str : strs) {
System.out.printf(str + " ");
stringBuilder.append(str + " ");
}
stringBuilder.append("\r\n");
System.out.println("");
}
System.out.println();
// System.out.println(tables.size());
if (tables.size() > idx)//idx 为原先的成绩数量123
{
//发送邮件给自己
System.out.println("这是一个标志");
SendMailUtil.sendEmail("你的邮箱 xxxx@qq.com", "成绩更新", stringBuilder.toString());
idx = tables.size();
}
}
}

+
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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package com.example.jwxt;

import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.stream.Collectors;

/**
* 登录后获取用户信息
* 相关解析HTMl的操作不是固定的
* 需要结合自己的需求来操作
* 此处仅作为演示。
*/
public class GetUserInfo {
static int idx = 0;
public static void cj() throws Exception {
for (int i = 0; i < 1000000000; ) {
LoginPz loginPz = new LoginPz();
String cookies = loginPz.hello();
CloseableHttpClient httpClient = HttpClients.createDefault();
try {
HttpGet httpGet = new HttpGet("http://218.75.197.123:83/jsxsd/kscj/cjcx_list?kksj=2021-2022-1");//2021-2022-1为学期信息 可自行更改
//增加头信息
//注意此处需要修改为正确的JSESSIONID 和 SERVERID
httpGet.addHeader("Cookie", "JSESSIONID" + "=" + cookies + "; SERVERID=121; JSESSIONID=8FFFAEA49DC840CE5A3135330C06CED3");
httpGet.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36");
httpGet.addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9");
httpGet.addHeader("Connection", "keep-alive");

//执行
CloseableHttpResponse response = httpClient.execute(httpGet);
String html = EntityUtils.toString(response.getEntity(), "utf8");
Document parse = Jsoup.parse(html);
parseHtml(parse);
Thread thread = new Thread();
thread.sleep(600000);
} catch (Exception e) {
}

}
}

private static void parseHtml(Document parse) throws Exception {
// int idx = 2;//目前已出成绩科目
//选择table
Element table = parse.getElementById("dataList");
//选择tr
Elements cells = table.select("tr");

StringBuilder stringBuilder = new StringBuilder(); //用来存储成绩信息
//自己存储的每一行数据
List<List<String>> tables = new ArrayList<>();

for (int index = 1; index < cells.size(); index++) {
//第一行是表头 index = 0 跳过

//第二行开始table数据

Element row = cells.get(index);

//搜索tr下的所有的td
Elements rows = row.select("td");

//每一行的数据
List<String> dataList = new ArrayList<>();

for (Element element : rows) {
dataList.add(element.text());
}
tables.add(dataList);
}

//获取表头
Elements headers = cells.get(0).select("th");
List<String> tableHeader = headers.stream()
.map(Element::text)
.collect(Collectors.toList());

//打印数据
for (String str : tableHeader) {
System.out.printf(str + " ");
stringBuilder.append(str + " ");
}
stringBuilder.append("\r\n");
System.out.println("");
for (List<String> strs : tables) {
for (String str : strs) {
System.out.printf(str + " ");
stringBuilder.append(str + " ");
}
stringBuilder.append("\r\n");
System.out.println("");
}
System.out.println();
// System.out.println(tables.size());
if (tables.size() > idx)//idx 为原先的成绩数量123
{
//发送邮件给自己
System.out.println("这是一个标志");
SendMailUtil.sendEmail("你的邮箱 xxxx@qq.com", "成绩更新", stringBuilder.toString());
idx = tables.size();
}
}
}

新建发邮件的类SendMailUtil
邮箱建议使用qq邮箱 需要拿到授权码

-
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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
package com.example.jwxt;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Properties;

import javax.mail.Address;
import javax.mail.Message;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

import com.sun.mail.util.MailSSLSocketFactory;

public class SendMailUtil {

//邮件服务器主机名
// QQ邮箱的 SMTP 服务器地址为: smtp.qq.com
private static String myEmailSMTPHost = "smtp.qq.com";

//发件人邮箱
private static String myEmailAccount = "xx@qq.com";

//发件人邮箱密码(授权码)
//在开启SMTP服务时会获取到一个授权码,把授权码填在这里
private static String myEmailPassword = "授权码 需要自己去qq邮箱拿";

/**
* 邮件单发(自由编辑短信,并发送,适用于私信)
*
* @param toEmailAddress 收件箱地址
* @param emailTitle 邮件主题
* @param emailContent 邮件内容
* @throws Exception
*/
public static void sendEmail(String toEmailAddress, String emailTitle, String emailContent) throws Exception{

Properties props = new Properties();

// 开启debug调试
props.setProperty("mail.debug", "true");

// 发送服务器需要身份验证
props.setProperty("mail.smtp.auth", "true");

// 端口号
props.put("mail.smtp.port", 465);

// 设置邮件服务器主机名
props.setProperty("mail.smtp.host", myEmailSMTPHost);

// 发送邮件协议名称
props.setProperty("mail.transport.protocol", "smtp");

/**SSL认证,注意腾讯邮箱是基于SSL加密的,所以需要开启才可以使用**/
MailSSLSocketFactory sf = new MailSSLSocketFactory();
sf.setTrustAllHosts(true);

//设置是否使用ssl安全连接(一般都使用)
props.put("mail.smtp.ssl.enable", "true");
props.put("mail.smtp.ssl.socketFactory", sf);

//创建会话
Session session = Session.getInstance(props);

//获取邮件对象
//发送的消息,基于观察者模式进行设计的
Message msg = new MimeMessage(session);

//设置邮件标题
msg.setSubject(emailTitle);

//设置邮件内容
//使用StringBuilder,因为StringBuilder加载速度会比String快,而且线程安全性也不错
StringBuilder builder = new StringBuilder();

//写入内容
builder.append("\n" + emailContent);

//设置显示的发件时间
msg.setSentDate(new Date());

//设置邮件内容
msg.setText(builder.toString());

//设置发件人邮箱
// InternetAddress 的三个参数分别为: 发件人邮箱, 显示的昵称(只用于显示, 没有特别的要求), 昵称的字符集编码
msg.setFrom(new InternetAddress(myEmailAccount,"我的工作站", "UTF-8"));

//得到邮差对象
Transport transport = session.getTransport();

//连接自己的邮箱账户
//密码不是自己QQ邮箱的密码,而是在开启SMTP服务时所获取到的授权码
//connect(host, user, password)
transport.connect( myEmailSMTPHost, myEmailAccount, myEmailPassword);

//发送邮件
transport.sendMessage(msg, new Address[] { new InternetAddress(toEmailAddress) });

//将该邮件保存到本地
OutputStream out = new FileOutputStream("MyEmail.eml");
msg.writeTo(out);
out.flush();
out.close();

transport.close();
}

}

+
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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
package com.example.jwxt;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Properties;

import javax.mail.Address;
import javax.mail.Message;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

import com.sun.mail.util.MailSSLSocketFactory;

public class SendMailUtil {

//邮件服务器主机名
// QQ邮箱的 SMTP 服务器地址为: smtp.qq.com
private static String myEmailSMTPHost = "smtp.qq.com";

//发件人邮箱
private static String myEmailAccount = "xx@qq.com";

//发件人邮箱密码(授权码)
//在开启SMTP服务时会获取到一个授权码,把授权码填在这里
private static String myEmailPassword = "授权码 需要自己去qq邮箱拿";

/**
* 邮件单发(自由编辑短信,并发送,适用于私信)
*
* @param toEmailAddress 收件箱地址
* @param emailTitle 邮件主题
* @param emailContent 邮件内容
* @throws Exception
*/
public static void sendEmail(String toEmailAddress, String emailTitle, String emailContent) throws Exception{

Properties props = new Properties();

// 开启debug调试
props.setProperty("mail.debug", "true");

// 发送服务器需要身份验证
props.setProperty("mail.smtp.auth", "true");

// 端口号
props.put("mail.smtp.port", 465);

// 设置邮件服务器主机名
props.setProperty("mail.smtp.host", myEmailSMTPHost);

// 发送邮件协议名称
props.setProperty("mail.transport.protocol", "smtp");

/**SSL认证,注意腾讯邮箱是基于SSL加密的,所以需要开启才可以使用**/
MailSSLSocketFactory sf = new MailSSLSocketFactory();
sf.setTrustAllHosts(true);

//设置是否使用ssl安全连接(一般都使用)
props.put("mail.smtp.ssl.enable", "true");
props.put("mail.smtp.ssl.socketFactory", sf);

//创建会话
Session session = Session.getInstance(props);

//获取邮件对象
//发送的消息,基于观察者模式进行设计的
Message msg = new MimeMessage(session);

//设置邮件标题
msg.setSubject(emailTitle);

//设置邮件内容
//使用StringBuilder,因为StringBuilder加载速度会比String快,而且线程安全性也不错
StringBuilder builder = new StringBuilder();

//写入内容
builder.append("\n" + emailContent);

//设置显示的发件时间
msg.setSentDate(new Date());

//设置邮件内容
msg.setText(builder.toString());

//设置发件人邮箱
// InternetAddress 的三个参数分别为: 发件人邮箱, 显示的昵称(只用于显示, 没有特别的要求), 昵称的字符集编码
msg.setFrom(new InternetAddress(myEmailAccount,"我的工作站", "UTF-8"));

//得到邮差对象
Transport transport = session.getTransport();

//连接自己的邮箱账户
//密码不是自己QQ邮箱的密码,而是在开启SMTP服务时所获取到的授权码
//connect(host, user, password)
transport.connect( myEmailSMTPHost, myEmailAccount, myEmailPassword);

//发送邮件
transport.sendMessage(msg, new Address[] { new InternetAddress(toEmailAddress) });

//将该邮件保存到本地
OutputStream out = new FileOutputStream("MyEmail.eml");
msg.writeTo(out);
out.flush();
out.close();

transport.close();
}

}

拿授权码过程

拿到授权码之后复制到上面去

再到Application添加启动类

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.example.jwxt;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class JwxtApplication {

public static void main(String[] args) throws Exception {
SpringApplication.run(JwxtApplication.class, args);
GetUserInfo getUserInfo = new GetUserInfo();
getUserInfo.cj();
}

}

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.example.jwxt;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class JwxtApplication {

public static void main(String[] args) throws Exception {
SpringApplication.run(JwxtApplication.class, args);
GetUserInfo getUserInfo = new GetUserInfo();
getUserInfo.cj();
}

}

如果自己有服务器就把这个项目打包成jar包上跑起来

没有的话只能把项目一直跑着不关

@@ -558,7 +558,7 @@

准备然后用xshell 一直cd到jar包所在的路径

使用命令

-
1
nohup java -jar JWXT-0.0.1-SNAPSHOT.jar 2>&1 &
+
1
nohup java -jar JWXT-0.0.1-SNAPSHOT.jar 2>&1 &

然后就大功告成了 一般你跑起来之后就会收到一封邮件提醒

github的项目地址 https://github.com/fengxiaop/JWXT

@@ -721,14 +721,14 @@

我试了一下我的打表方式 20分钟还没打出最后一个数

直接搬官方的吧

-
1
2
3
4
5
   class Solution {
public boolean checkPerfectNumber(int num) {
return num == 6 || num == 28 || num == 496 || num == 8128 || num == 33550336;
}
}
+
1
2
3
4
5
   class Solution {
public boolean checkPerfectNumber(int num) {
return num == 6 || num == 28 || num == 496 || num == 8128 || num == 33550336;
}
}

方法二 枚举

-
1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public boolean checkPerfectNumber(int num) {
int sum = 0;
if(num==1) return false;
for(int i = 1;i <= num/2;i++)
{
if(num%i==0)
sum+=i;
}
return sum==num;
}
}
+
1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public boolean checkPerfectNumber(int num) {
int sum = 0;
if(num==1) return false;
for(int i = 1;i <= num/2;i++)
{
if(num%i==0)
sum+=i;
}
return sum==num;
}
}

这个稍微时间稍微长了一点

-
1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public boolean checkPerfectNumber(int num) {
if (num == 1) return false;
int ans = 1;
for (int i = 2; i <= num / i; i++) {
if (num % i == 0) {
ans += i;
if (i * i != num) ans += num / i;
}
}
return ans == num;
}
}
+
1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public boolean checkPerfectNumber(int num) {
if (num == 1) return false;
int ans = 1;
for (int i = 2; i <= num / i; i++) {
if (num % i == 0) {
ans += i;
if (i * i != num) ans += num / i;
}
}
return ans == num;
}
}

这个就很快了

同样是枚举 别人写的和我写的 感觉自己在时间复杂度方法还得加强

@@ -960,13 +960,13 @@

50. Pow(x, n)

难度中等8

实现 pow(x, n) ,即计算 x 的 n 次幂函数(即,xn)。

示例 1:

-
1
2
输入:x = 2.00000, n = 10
输出:1024.00000
+
1
2
输入:x = 2.00000, n = 10
输出:1024.00000

示例 2:

-
1
2
输入:x = 2.10000, n = 3
输出:9.26100
+
1
2
输入:x = 2.10000, n = 3
输出:9.26100

示例 3:

-
1
2
3
输入:x = 2.00000, n = -2
输出:0.25000
解释:2-2 = 1/22 = 1/4 = 0.25
+
1
2
3
输入:x = 2.00000, n = -2
输出:0.25000
解释:2-2 = 1/22 = 1/4 = 0.25

提示: