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

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


  • 首页

  • 归档

  • 搜索

IDEA 启动、编译、clean、安装maven等、报错Pr

发表于 2021-11-09

报错原因:找不到settings.xml


解决方法:

​

本文转载自: 掘金

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

MyBatis常用的几种分页方式

发表于 2021-11-09

MyBatis常用的几种分页方式

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

为什么要分页?

如果前端需要展示数据时,由于数据量庞大,一次性展示,这时页面将会出现一大片数据,而且还不一定加载完成,效率很差,此时分页查询就可以很好的解决这一问题,将庞大的数据按照一定数目显示出,还可以通过点击下一页或者上一页展示其它数据,效率更高!

下面来介绍下mybatis常用的几种分页查询!
首先看一下数据库里所有的数据,细节代码里有注解

image.png

1、使用Limit分页

客户端通过传递start(页码) ,pageSize(每页显示的条数) 两个参数去分页查询数据库表中的数据

sql使用的核心语句

1
2
3
4
sql复制代码-- 语法:表示从startIndex下标开始,一页显示pageSize个
select * from users limit startIndex,pageSize;
-- 语法:表示显示[0,n]范围的数据
select * from users limit n;

使用Mybatis实现分页,基于sql实现

  1. 编写接口
1
2
java复制代码 //分页查询
List<User> getUserLImit(Map<String,Object> map);
  1. 编写Mapper.xml
1
2
3
4
xml复制代码<!--    分页查询-->
<select id="getUserLImit" parameterType="map" resultType="pojo.User">
select * from firend_mq.users limit #{startIndex},#{pageSize}
</select>
  1. 测试类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码    //测试分页查询
@Test
public void getUserLImit(){
SqlSession sqlSession = Mybatisutil.getSqlSession();
UserDao mapper = sqlSession.getMapper(UserDao.class);

Map<String, Object> map = new HashMap<>();
map.put("startIndex",0);
map.put("pageSize",3);
List<User> userLImit = mapper.getUserLImit(map);
for (User user : userLImit) {
System.out.println(user);
}
sqlSession.close();
}

查询结果:

在这里插入图片描述

2、RowBounds实现分页

基于RowBounds类对象实现,基于java代码

  1. 编写接口
1
2
java复制代码//RowBounds实现分页查询
List<User> getUserLRowBounds();
  1. 编写Mapper.xml,查询的其实是全部用户
1
2
3
4
xml复制代码   <!--   RowBounds 分页查询-->
<select id="getUserLRowBounds" resultType="pojo.User">
select * from firend_mq.users
</select>
  1. 测试类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码    //RowBounds分页查询
@Test
public void getUserLRowBounds(){
SqlSession sqlSession = Mybatisutil.getSqlSession();
//RowBounds对象 参数(起点,个数)
RowBounds rowBounds = new RowBounds(2, 3);

//通过java代码层面实现分页,第一个参数是接口类的方法路径
List<User> userlist = sqlSession.selectList("dao.UserDao.getUserLRowBounds", null, rowBounds);
for (User user : userlist) {
System.out.println(user);
}
sqlSession.close();
}

结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4Rjv22Cy-1619774727249)(image-20210430171348906.png)]

3、使用分页插件实现

image.png

感兴趣的可以了解下,放个该插件的官网链接,有官方使用文档,自行了解PageHelper分页插件

写在最后

🤫今天是忙里偷闲的一天啊。

灯.jpg

本文转载自: 掘金

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

SpringBoot集成Swagger(一) Java随

发表于 2021-11-09

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


相关文章

Java随笔记:Java随笔记

前言

  • 哈喽,不知道各位小伙伴们在公司中前后端联调是以什么方式的?
  • 说说我自己的吧~
+ 上家公司用的是Yapi,我们后端开发时为了方便都是在PostMan中测试接口。然后写好之后在Yapi中添加进去,将地址发给前端,前端进行调试。
+ 现在这家是使用Swagger,写的时候稍微麻烦点,但是联调的时候太爽歪歪了,啥都不用管,前端自己玩去吧!哈哈哈~
  • 当然,如果你们公司还是前后端不分离的话。。。以上的当我没说。。。
  • 既然工作中用到了,那么肯定要研究研究的,虽然这玩意很简单,但是还是可以学习学习的嘛!

一、SpringBoot集成Swagger

  • 首先新建一个SpringBoot项目,只需要web服务即可。
  • 新建方式可以看我以前的文章,在此不在赘述。创建一个SpringBoot项目的两种方式
  • 项目建好之后我们导入Swagger相关配置
+ 
1
2
3
4
5
6
7
8
9
10
11
12
xml复制代码<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
   <groupId>io.springfox</groupId>
   <artifactId>springfox-swagger2</artifactId>
   <version>2.9.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
   <groupId>io.springfox</groupId>
   <artifactId>springfox-swagger-ui</artifactId>
   <version>2.9.2</version>
</dependency>
  • 项目中新建Swagger的配置类
+ 
1
2
3
4
5
6
7
8
9
kotlin复制代码package com.dayu.dyswagger.config;
​
import org.springframework.context.annotation.Configuration;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
​
@Configuration //配置类
@EnableSwagger2// 开启Swagger2的自动配置
public class SwaggerConfig {  
}
  • 大体结构如下:
+ ![image-20211109210135313.png](https://gitee.com/songjianzaina/juejin_p14/raw/master/img/983094bc01380156f769847f29d6467e167ed50bcdccdb21e786ee2fa898051a)
  • 启动项目,访问:http://127.0.0.1:8080/swagger-ui.html#/
+ ![image-20211109212000108.png](https://gitee.com/songjianzaina/juejin_p14/raw/master/img/e2e3572975e9f7f1ea68e721829136ac559d4da2176201da32b2ffb0d49324f7)
  • 恭喜你,Swagger已经成功被集成到SpringBoot项目中啦!
  • 至于为啥会有basic-error-controller这个玩意,,这是因为SpringBoot项目本身有个error的返回。
  • 比如随便输个路由看看:
+ ![image-20211109212220872.png](https://gitee.com/songjianzaina/juejin_p14/raw/master/img/ce4a276ad2c0a98690cce0aa3693058f0f132d837ad1d2b3372b146577c2a460)

二、自定义Controller

  • 新建SwaggerTestController类
+ 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
kotlin复制代码/**
* @program: dyswagger
* @description: Swagger测试
* @author: DaYu
*/
@RestController
public class SwaggerTestController {
​
   @RequestMapping(value = "test-swagger",method = RequestMethod.GET)
   public String dyTest(){
       return "大鱼,你好呀!";
  }
​
}
  • 再次启动项目访问看看
+ ![image-20211109212446958.png](https://gitee.com/songjianzaina/juejin_p14/raw/master/img/3a84a4d3c8bf66a898bb7cdde7febd20399dfea9a9c9d94b65b75959e5a4c832)
  • 完美
  • 既然是水文,那么肯定要把一篇文章能讲完的东西分开来写呀~
  • 下一篇文章我们再继续详细解释下,SwaggerConfig里面的配置及源码讲解。
  • 对不起兄弟们,为了参加活动我也是拼了~
  • 请叫我大水笔

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

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

本文转载自: 掘金

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

git 中 merge 和 rebase 小记

发表于 2021-11-09

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

作为一线工程师的你,对于 git一定不会陌生,git 作为一个开源的分布式版本控制系统,有着广泛的用户基础。git 使用有很多可视化的工具,idea 自身也大多都集成了 git 套件,如下:

image.png

不过相比于这些可视化工具,我更喜欢使用命令行的方式。

本篇背景也是源于实际工作,一个同学遇到的问题是:他从 master 分支自己拉了一个开发分支,但是由于时间很长,所以在提交之前他执行了:

1
bash复制代码git pull

然后 push ,当提交 mr 时发现,自己的提交里有很多别人的 commit 记录。ok,这里背景交代清楚,我们通过一个实际的 case 来看看。

git pull 发生了什么?

  • 从 master 分支切出一个 dev 分支
  • 切到 master 分支,代码修改,然后提交
  • 切回 dev ,执行 git pull

在执行 git pull 之前,先看下 git log

image.png

执行 git pull 之后

image.png

所以可以非常明显的看到,git pull 操作中有把 master merge 到当前 dev,实际上 git pull 是下面两个指令的整合:

1
base复制代码git pull = git fetch + git merge

那既然是 merge,所以 dev 中后续的提交,都会带上此次 merge 的 commit 记录;

image.png

这就是那位同学遇到的问题。如果我们不想让自己的提交中含有其他无关的 commit 怎么办?此时就需要 rebase 出场了。

git rebase

  • 从 master 切 一个 rebase-dev 分支
  • 从 master 切 一个 rebase-dev2 分支
  • 修改 rebase-dev2,然后提交,merge 到 master

image.png

两次提交记录;这里基于前面提到的 git pull 行为,如果我们期望 rebase-dev 的提交不包括 rebase-dev2 的提交,但是从 log 看, rebase-dev 已经在最新 commit 后面了

image.png

git rebase 本质上是需要给 rebase-dev 变基,就是将基线拉到最新 commit 之前,在 rebase-dev 分支下,执行 git rebase master,此时再观察下 log

image.png

可以看到 rebase-dev 已经跑到上面了,下面在 rebase-dev 做修改提交

image.png

可以看下,此时提交就没有 rebase-dev2 的 commit 记录了。

关于 git merge 和 git rebase 的小结

  • git merge:分支代码合并后不会破坏原分支的代码提交记录,但是会产生额外的提交记录并进行两条分支的合并
  • git rebase:不会新增提交记录到目标分支,rebase 后可以将对象分支的提交历史续到目标分支上,形成线性提交历史记录

所以你学废了吗?

本文转载自: 掘金

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

Linux 多线程实践(一) POSIX Thread

发表于 2021-11-09

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

在Linux环境中使用POSIX Thread API可以很方便的写出多线程程序,本系列将对其API进行介绍, 本文为系列的第一篇文章, 主要介绍线程的基础操作。

在Linux中同一进程中的不同线程:

共享:

  • 打开的文件
  • 当前的工作目录
  • 用户和组
  • 信号和信号句柄

拥有自己的:

  • 线程Id
  • errno 错误码 (使用thread-local实现)
  • 优先级
  • 独立的线程栈

API

以下API均定义在头文件pthread.h中, 使用GCC编译时需要链接到pthread库上, 如下所示

1
shell复制代码gcc ./test.c -l pthread

pthread_create

pthread_create用于创建线程, 如果线程创建成功该函数返回0, 创建失败返回错误码,创建失败的原因可能是:

  • EAGAIN 系统资源不足或者进程中的线程数量达到限制, 我们可以通过cat /proc/sys/kernel/threads-max查看当前系统对于线程数量的限制
    image.png
  • EINVAL attr参数中有非法的设置
  • EPERM attr参数中有不被允许的设置

该函数定义了以下参数:

  • thread 指向线程id的指针, 创建线程成功后会通过该指针将线程id保存到其中
  • attr 用于设置线程相关的参数, 如果想使用默认参数传NULL即可。如: 设置线程的栈大小以及栈的地址(此场景常见于嵌入式设备需要限制线程栈空间大小的场景)
  • start_routine 线程要执行的函数, 该函数的出入参均为一个无类型指针void*
  • arg 传给函数的参数
1
2
3
4
5
6
c复制代码#include <pthread.h>

int pthread_create(pthread_t *restrict thread,
const pthread_attr_t *restrict attr,
void *(*start_routine)(void *),
void *restrict arg);

pthread_self

pthread_self 用于获取当前线程的id.

1
c复制代码 pthread_t pthread_self(void);

pthread_join

调用pthread_join会等待目标线程执行完成之后才退出, 并获取线程的执行结果, 下文中我们将会给出Demo, 返回是否执行成功.

1
c复制代码int pthread_join(pthread_t thread, void **retval);

pthread_exit

终止当前线程的运行(调用此方法的线程), 并设置线程的返回值, 如果有其他线程正在阻塞等待(调用pthread_join)此线程返回它将接收到这一返回值。

1
c复制代码void pthread_exit(void *retval);

pthread_detached

pthread_detached用于分离一个线程, 调用此方法的线程在终止后资源会被回收掉,并且不可等待(即其他线程不可调用pthread_join等待此线程)

1
c复制代码int pthread_detach(pthread_t thread);

Demo

创建线程并传递参数

以下Demo展示了如何创建一个线程, 并传入参数以及取得该线程的返回值并进行打印。

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
c复制代码#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>

void* just_print(void *arg) {
char *str = (char *)arg;
printf("Thread say: %s\n", str);
char *res_str = (char *)malloc(256);
sprintf(res_str, "My thread id is %ld", pthread_self());
return (void *)res_str;
}

int main() {
pthread_t tid;
void* res;
printf("thread create\n");
int s = pthread_create(&tid, NULL, just_print, "Hello");
if (s) {
perror("thread create failed");
return -1;
}
pthread_join(tid, &res);
printf("thread exit \n");

printf("Main thread recv msg: %s\n", (char *)res);
return 0;
}

输出如下所示:

image.png

限制线程栈空间大小

上文中提到在创建线程时,我们可以通过pthread_attr_t结构体设置一些参数来改变线程的一些行为。 在一些特殊场景下这个功能是非常实用的, 比如:

  • 如果程序运行在嵌入式设备(内存受限), 为了节约内存我们可以通过pthread_attr_t来调小线程栈的大小以节约内存
  • 在执行一些比较复杂运算的情况下,我们也可通过此参数来调大线程的栈内存以获取更多的栈空间

操作系统的默认线程栈大小我们可以通过ulimit -s进行查看, 效果如下

image.png

如一下Demo所示, 我们通过pthread_attr_init来初始化pthread_attr_t结构体, 之后调用pthread_attr_set_stacksize来设置线程栈的大小。

注意设置线程栈大小的时候, 如果设置的栈大小小于PTHREAD_STACK_MIN时会返回非法参数的错误, 对于部分系统如果栈大小不是系统的页大小的倍数,也会引发错误。

如以下Demo所示, 我们使用THREAD_STACK_SIZE来表示分配给线程的栈空间大小, ALLOC_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
c复制代码#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <string.h>

#define THREAD_STACK_SIZE 4096 * 6
#define ALLOC_SIZE 4096 * 4

void* malloc_mem(void *arg) {
char msg[ALLOC_SIZE];
printf("stack malloc done! \n");
}

int main() {
pthread_t tid;
pthread_attr_t attr;
if (pthread_attr_init(&attr) != 0) {
perror("init attr failed");
return -1;
}
int r = pthread_attr_setstacksize(&attr, THREAD_STACK_SIZE);
if(r != 0) {
printf("set stack size failed, err=%s\n", strerror(r));
return -1;
}
int size = 1024;
int s = pthread_create(&tid, &attr, malloc_mem, &size);
if (s) {
perror("thread create failed:");
return -1;
}
pthread_join(tid, NULL);
return 0;
}

测试用例1, THREAD_STACK_SIZE = ALLOC_SIZE = 4096 * 6, 输出如下所示:

image.png

系统提示Segmentation fault, 原因是栈空间不足.

测试用例2, THREAD_STACK_SIZE = 4096 * 6, ALLOC_SIZE = 4096 * 4, 输出如下所示:

image.png

测试用例3 THREAD_STACK_SIZE = 4096 * 1, ALLOC_SIZE = 1024 输出如下所示

image.png

提示参数错误, 原因时分配给线程的栈空间小于PTHREAD_STACK_MIN

本文转载自: 掘金

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

Go语言高阶21,语法糖规则,可别掉入陷阱 简短变量声明

发表于 2021-11-09

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

简短变量声明 :=

答题热身

下面两个程序运行结果是?

1
2
3
4
5
go复制代码func main() {
i := 0
i, j := 1, 2
fmt.Printf("i = %d, j = %d", i, j)
}
1
2
3
4
5
6
7
8
go复制代码func main() {
i, j := 0, 0
if true {
j, k := 1, 1
fmt.Printf("j = %d, k = %d\n", j, k)
}
fmt.Printf("i = %d, j = %d\n", i, j)
}
  1. 下面程序为什么不能通过编译
1
2
3
4
go复制代码func test(i int) {
i := 0
fmt.Println(i)
}

答案:

i = 1, j = 2

j = 1, k = 1

i = 0, j = 0

形参已经声明了变量i,使用 := 再次声明是不允许的。

多变量赋值可能会重新声明

使用 := 一次可以声明多个变量,例如:

1
2
go复制代码i, j := 0, 0
j, k := 1, 1
  • 当 := 左侧存在新的变量时(如 k),那么已经声明的变量(如 j)会被重新声明。这并没有引入新的变量,只是把变量的值改变了。
  • 当 := 左侧没有新变量编译报错。

不能用于函数外部

  • := 这种简短变量声明只能用于函数中,用来初始化全局变量是不行的。
  • 可以理解成 := 会拆分成两个语句,即声明和赋值。赋值语句不能出现在函数外部的。

可变参数 …

  • 可变参数必须在函数参数列表的最后一个(否则会引起编译时歧义);
  • 可变参数在函数内部是作为切片来解析的;
  • 可变参数可以不填,不填时函数内部当成 nil 切片处理;
  • 可变参数可以填入切片;
  • 可变参数必须是相同类型的(如果需要是不同类型的可以定义为interface{}类型);

例如:

1
2
3
4
5
6
7
go复制代码func sum(a int, vals...int) int {
total := a
for _, val := range vals {
total += val
}
return total
}

不传值

1
go复制代码fmt.Println(sum(1))          // "1"

传递多个参数

1
go复制代码fmt.Println(sum(1, 2, 3, 4, 5)) // "15"

传递切片

参数部分需要使用 slice… 来表示切片,例如

1
2
go复制代码values := []int{1, 2, 3, 4, 5}
fmt.Println(sum(1, values...)) // "16"

本文转载自: 掘金

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

初始C语言1 C语言本身特点 数据类型 常量变量 字符串+转

发表于 2021-11-09

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

@[TOC]

闲话少说,先上思维导图。

​

如图所示,现在还是初识C语言的第一部分,本次只介绍了C语言本身特点,数据类型,常量变量,字符串转义字符注释,选择循环语句,函数,数组。   


接下来请和我一起粗略地探讨其中内涵所在。  

C语言本身特点

这是C语言的定义:

C语言是一门通用计算机编程语言,广泛应用于底层开发。  

C语言提供了许多低级处理的功能,仍然保持良好跨平台特性,以一个标准规格写出的C语言程序可在许多电脑平台上进行编译。

为了避免各开发厂商用的C语言语法产生差异,由美国国家标准局为C语言制定了一套完整的美国国家标准语法,称为ANSI C,作为C语言最初的标准。

C语言是一门面向过程的计算机编程语言,与C++,Java等面向对象的编程语言有所不同。其编译器主要有Clang、GCC、WIN-TC、SUBLIME、MSVC、Turbo C等。

但总结其特点就是1.底层开发,2.国际标准 ANSI C,3.面向过程 
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
arduino复制代码#include <stdio.h>
//stdio-标准输入输出 standard input & output headfile

//int -整型-整数的类型
//主函数
//main函数是程序的入口
//唯一性有且仅有一个

int main()
{
//库函数-打印函数-输出函数
printf("hello world!\n");
printf("hello world!\n");
printf("hello world!\n");


return 0;
}

//这种写法明确告诉你,main函数不需要传参
int main(void)
{

}

//这种写法有用,但不是重点
int mian(int argc, char* argv[])
{

}

数据类型

C语言主要有六种数据类型,分别为
  • 字符型 char
  • 短整型 short
  • 整形 int
  • 长整型 long
  • 单精度浮点型 float
  • 双精度浮点型 double
这六种分别有自己的字符长度即其所建变量所占的空间,char最小只要1个字节,short占2个,int占4个,long一般是4个,也有可能是8个,反正只要满足long的字节长度大于等于int就可以了。两个浮点型的最为特殊,分别是4和8,显然双精度的精度更高。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
c复制代码int main()
{
//如何计算一个类型创建的变量所占空间的大小
//sizeof();
printf("%d\n", sizeof(char));//1个字节
printf("%d\n", sizeof(short));//2个字节
printf("%d\n", sizeof(int));//4个字节
printf("%d\n", sizeof(long));//4个字节
printf("%d\n", sizeof(long long));//8个字节
printf("%d\n", sizeof(float));//4个字节
printf("%d\n", sizeof(double));//8个字节

return 0;
}

)​

常量变量

变量分类

显而易见的是,分为两类变量和常量讲述。


变量又分两类,一是局部变量,二是全局变量。


顾名思义,局部变量是定义在一对大括号内的,显然我们平时在main函数里创建的都是局部变量,全局变量则为定义在大括号外面的变量。


当二者同名时,局部变量优先,当然,这种代码谁写谁sb。

使用小建议

在程序中使用scanf函数,若程序为 scanf(“%d%d”);

%d%d之间不管有没有空格,输入变量时都要用空格隔开,若二者之间有逗号则逗号隔开。

生命周期作用域

对这二者讨论他们的作用域和生命周期的话,
  • 全局变量
+ 作用域为整个工程文件,若想在其他.c文件中使用则需要extern声明一下。
+ 生命周期则是整个工程的生命周期,也就是main函数的生命周期。
  • 局部变量
+ 作用域为其定义的大括号{ }内,进作用域生命开始,出则结束。
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
c复制代码//验证局部变量的作用域 
int main()
{
int b = 100;//主函数任意位置
{
int a = 10;//局部变量作用域:局部所在范围
printf("a=%d\n", a);
printf("b=%d\n", b);
}
//printf("a=%d\n", a);
printf("b=%d\n", b);



return 0;
}


//验证全局变量的作用域为整个工程
int a = 200;

void test()
{
printf("test:%d\n", a);
}
int main()
{
test();
printf("main:%d\n", a);//全局变量可以任意使用

return 0;
}

//全局变量在其他源文件的使用
//转到test1.c文件查看

int g_val = 110;


//验证局部变量的生命周期为进作用域到出作用域
int main()
{
{
int a = 100;
printf("%d\n", a);
}
printf("%d\n", a);//出作用域即生命周期结束

return 0;
}


//全局变量的生命周期为整个工程即main函数生命周期

int main()
{
printf("%d\n", g_val);


return 0;
}

常量分类及其特点

常量呢,大致分为四种,
  1. 字面常量
  2. const修饰的常变量
  3. #define定义的标识符常量
  4. 枚举常量
  • 第一种,字面常量就是随便写出来的一个数,如3.14就是一个字面常量。
  • 第二种,const修饰的常变量,即在创建变量时加上const修饰,如const int a=0;这样变量a就具有了常属性,不可被修改。但值得注意的是,常变量一词,它仍然是个变量,不可用于定义数组。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
c复制代码//const修饰的变量
int main()
{
//局部变量
int a = 200;

//不可被改变的属性,常属性

const int a = 110;
a = 100;
printf("%d\n", a);

//const修饰的变量不可定义数组
//(常变量)
int n = 110;
int arr[n] = { 0 };//数组



return 0;
}

  • 第三种,#define定义的常量,写法如 #define N 10,这是真真正正的常量,可以用来定义数组。同样也是不可修改的(常量当然不可被修改)。
1
2
3
4
5
6
7
8
9
10
11
12
13
c复制代码#define PAI 314
int main()
{
int a = PAI;
printf("a=%d\n", a);

//MAX = 300;//#define定义的变量不可被修改

//可用于定义数组
int arr[PAI] = { 0 };

return 0;
}

  • 第四种,枚举常量,具体使用方法像enum sex {MALE,FEMALE,SECRET};这样就得到了枚举常量,各自都是有初值的,分别为0,1,2依次类推,当然也可以自己赋值。
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
c复制代码//枚举常量 SEX性别举例

//可以一一列举的值,性别,三原色
enum Sex
{
//枚举的可能取值
MALE=3,//赋初值
FEMALE=8,
SECRET//向后延一个为9

};

int main()
{
//FEMALE = 99;//ERR 枚举变量值不可修改

enum Sex a=MALE;
enum Sex s = FEMALE;

printf("%d\n", MALE);
printf("%d\n", FEMALE);
printf("%d\n", SECRET);

return 0;
}

​字符串+转义字符+注释

字符串

单引号引起来的,如'w','r'是字符,被双引号引起来的"yyx","abcdef"是字符串。


字符串一般以'\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
24
25
26
27
28
29
30
31
32
33
c复制代码//字符,字符串
int main()
{
//'a', ' ' , 'X' - 字符
//"abc" "123" - 字符串
//"abcdef"; //字符串字面值

//①
char ch1[] = "abcdef";//初始化字符数组
//a ,b ,c ,d ,e ,f ,\0
//%s - 打印字符串
//strlen - 打印字符串长度,(遇\0则停止,本身不算)\0是字符串结束标志
printf("%s\n", ch1);
//abcdef
printf("%d\n", strlen(ch1));//6

//②
char ch2[] = { 'a','b','c','d','e','f' };
//a ,b ,c ,e ,f
printf("%s\n", ch2);
//abcdef烫烫烫烫烫abcdef
printf("%d\n", strlen(ch2));//22,找不到\0

//③
char ch3[] = { 'a','b','c','d','e','f','\0' };
//a ,b ,c ,d ,e ,f ,\0
printf("%s\n", ch3);
//abcdef
printf("%d\n", strlen(ch3));//6


return 0;
}

转义字符

  • 防止单引号,双引号,反斜杠被转移的’ ‘ ‘ , ‘ “ ‘,’ \ ‘ 。
  • 警告,蜂鸣的’ \a ‘
  • 退格符’ \b ‘
  • 进纸符’ \f ‘
  • 换行符’ \n ‘
  • 回车符’ \r ‘
  • 水平制表符’ \t ‘
  • 垂直制表位’ \v ‘
  • 八进制数字’ \ddd ‘
  • 十六进制数字’ \xdd ‘
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
c复制代码#include <stdio.h>
int main()
{
printf("c:\code\test.c\n");//c:code est.c
printf("c:\code\test.c\n");//c:\code est.c
//单个\是转义序列符,但这里又没有\c的转义字符,所以系统自动忽略该\
//想要出现该\,则必须要搭配两个\,防止其被转义


return 0;
}

// ?
//三字母词
// ??) --> ] ??( --> [

//' "
int main()
{
printf("%c\n", 'a');
printf("%c\n", 'b');
printf("%c\n",''');//在’前面加上\,以单纯的将其视为字符'
printf("%s\n", """);// "

return 0;
}
//\a \b \f \v \t \n \r

//\ddd八进制数字 \xdd十六进制数字
int main()
{
printf("%c\n", '\165');
//打印八进制数字065转化为十进制数字53所代表的字符(ASCII码)
printf("%d\n", '\165');
//打印八进制数字065转化为十进制数字53

printf("%c\n", '\x15');
printf("%d\n", '\x15');//21

return 0;
}

两种注释

C++的风格 //xxxxx

这种只能注释一行。

C语言的风格 /xxxxx/

不可嵌套注释。

)​

选择循环语句

选择语句,实现分支,有if..else..语句和switch语句。循环语句,有while循环,do…while循环和for循环。

1
2
3
4
5
ini复制代码具体情况为 
if ( 条件 ) 
{ 语句 };    
else ( 条件 ) 
  { 语句 };

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
c复制代码int main()
{
int i = 0;
printf("加入比特\n");
printf("要好好敲代码吗(1/0)?\n");
scanf("%d", &i);
if (i == 1)
{
printf("好offer\n");
}
else
{
printf("药丸\n");
}

return 0;
}

int main()
{
int line = 0;
printf("加入比特\n");

while (line<20000)
{

printf("敲%d了代码\n",line);
line++;

}
printf("牛逼坏了拿到了好offer\n");

return 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
c复制代码int ADD(int num1, int num2)
{
scanf("%d %d", &num1, &num2);
int sum = num1 + num2;
return sum;

}
int main()
{
int num1 = 0;
int num2 = 0;

////输入
//scanf("%d %d", &num1, &num2);
////相加
//int sum = num1+num2;
////输出
//printf("sum=%d\n", sum);


//int output = ADD(num1,num2);
//printf("%d\n", output);

printf("%d\n", ADD(num1, num2));


return 0;
}

数组

定义:一组相同类型的元素组成的集合。

数组的使用是根据下标来的,从0开始数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
c复制代码int main()
{
//数组 - 相同元素的集合
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%3d", arr[i]);

}
printf("\n");
for (i = 9; i >= 1; i--)
{
printf("%3d", arr[i]);
}

return 0;
}

)​

​

本文转载自: 掘金

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

SpringCloud升级之路20200x版-30 F

发表于 2021-11-09

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

本系列代码地址:github.com/JoJoTec/spr…

需要重试的场景

微服务系统中,会遇到在线发布,一般的发布更新策略是:启动一个新的,启动成功之后,关闭一个旧的,直到所有的旧的都被关闭。Spring Boot 具有优雅关闭的功能,可以保证请求处理完再关闭,同时会拒绝新的请求。对于这些拒绝的请求,为了保证用户体验不受影响,是需要重试的。

云上部署的微服务,对于同一个服务,同一个请求,很可能不会所有实例都同时异常,例如:

  1. Kubernetes 集群部署的实例,可能同一个虚拟机 Node 在闲时部署了多个不同微服务实例,当压力变大时,就需要迁移和扩容。这时候由于不同的微服务压力不同,当时处于哪一个 Node 也说不定,有的可能处于压力大的,有的可能处于压力小的。对于同一个微服务,可能并不会所有实例位于的 Node 压力都大。
  2. 云上部署一般会跨可用区部署,如果有一个可用区异常,另一个可用区还可以继续提供服务。
  3. 某个业务触发了 Bug,导致实例一直在 GC,但是这种请求一般很不常见,不会发到所有实例上。

这时候,就需要我们对请求进行无感知的重试。

重试需要考虑的问题

  1. 重试需要重试与之前不同的实例,甚至是不处于同一个虚拟机 Node 的实例,这个主要通过 LoadBalancer 实现,可以参考之前的 LoadBalancer 部分。后面的文章,我们还会改进 LoadBalancer
  2. 重试需要考虑到底什么请求能重试,以及什么异常能重试:
  • 假设我们有查询接口,和没有做幂等性的扣款接口,那么很直观的就能感觉出查询接口是可以重试的,没有做幂等性的扣款接口是不能重试的。
  • 业务上不能重试的接口,对于特殊的异常(其实是表示请求并没有发出去的异常),我们是可以重试的。虽然是没有做幂等性的扣款接口,但是如果抛出的是原因是 Connect Timeout 的 IOException,这样的异常代表请求还没有发出去,是可以重试的。
  1. 重试策略:重试几次,重试间隔。类比多处理器编程模式中的 Busy Spin 策略会造成很大的总线通量从而降低性能这个现象,如果失败立刻重试,那么在某一个实例异常导致超时的时候,会在同一时间有很多请求重试到其他实例。最好加上一定延迟。

使用 resilience4j 实现 FeignClient 重试

FeignClient 本身带重试,但是重试策略相对比较简单,同时我们还想使用断路器以及限流器还有线程隔离,resilience4j 就包含这些组件。

原理简介

Resilience4J 提供了 Retryer 重试器,官方文档地址:resilience4j.readme.io/docs/retry

从配置上就能理解其中的原理,但是官方文档配置并不全面,如果想看所有的配置,最好还是通过源码:

RetryConfigurationProperties.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
less复制代码//重试间隔,默认 500ms
@Nullable
private Duration waitDuration;

//重试间隔时间函数,和 waitDuration 只能设置一个,默认就是 waitDuration
@Nullable
private Class<? extends IntervalBiFunction<Object>> intervalBiFunction;

//最大重试次数,包括本身那次调用
@Nullable
private Integer maxAttempts;

//通过抛出的异常判断是否重试,默认是只要有异常就会重试
@Nullable
private Class<? extends Predicate<Throwable>> retryExceptionPredicate;

//通过结果判断是否重试,默认是只要获取到结果就不重试
@Nullable
private Class<? extends Predicate<Object>> resultPredicate;

//配置抛出这些异常以及子类则会重试
@SuppressWarnings("unchecked")
@Nullable
private Class<? extends Throwable>[] retryExceptions;

//配置抛出这些异常以及子类则不会重试
@SuppressWarnings("unchecked")
@Nullable
private Class<? extends Throwable>[] ignoreExceptions;

//启用 ExponentialBackoff 延迟算法,初次重试延迟时间为 waitDuration,之后每次重试延迟时间都乘以 exponentialBackoffMultiplier,直到 exponentialMaxWaitDuration
@Nullable
private Boolean enableExponentialBackoff;

private Double exponentialBackoffMultiplier;

private Duration exponentialMaxWaitDuration;

//启用随机延迟算法,范围是 waitDuration - randomizedWaitFactor*waitDuration ~ waitDuration + randomizedWaitFactor*waitDuration
@Nullable
private Boolean enableRandomizedWait;

private Double randomizedWaitFactor;

@Nullable
private Boolean failAfterMaxAttempts;

引入 resilience4j-spring-boot2 的依赖,就可以通过 Properties 配置的方式去配置 Retryer 等所有 resilience4j 组件,例如:

application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
yaml复制代码resilience4j.retry:
configs:
default:
## 最大重试次数,包括第一次调用
maxRetryAttempts: 2
## 重试等待时间
waitDuration: 500ms
## 启用随机等待时间,范围是 waitDuration - randomizedWaitFactor*waitDuration ~ waitDuration + randomizedWaitFactor*waitDuration
enableRandomizedWait: true
randomizedWaitFactor: 0.5
test-client1:
## 最大重试次数,包括第一次调用
maxRetryAttempts: 3
## 重试等待时间
waitDuration: 800ms
## 启用随机等待时间,范围是 waitDuration - randomizedWaitFactor*waitDuration ~ waitDuration + randomizedWaitFactor*waitDuration
enableRandomizedWait: true
randomizedWaitFactor: 0.5

这样,我们就可以通过如下代码,获取到配置对应的 Retryer:

1
2
3
4
5
6
7
ini复制代码@Autowired
RetryRegistry retryRegistry;
//读取 resilience4j.retry.configs.test-client1 下的配置,构建 Retry,这个 Retry 命名为 retry1
Retry retry1 = retryRegistry.retry("retry1", "test-client1");
//读取 resilience4j.retry.configs.default 下的配置,构建 Retry,这个 Retry 命名为 retry1
//不指定配置名称即使用默认的 default 下的配置
Retry retry2 = retryRegistry.retry("retry2");

引入 resilience4j-spring-cloud2 的依赖,就相当于引入了 resilience4j-spring-boot2 的依赖。并在其基础上,针对 spring-cloud-config 的动态刷新 RefreshScope 机制,增加了兼容。

1
2
3
4
xml复制代码<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-cloud2</artifactId>
</dependency>

使用 resilience4j-feign 给 OpenFeign 添加重试

官方提供了粘合 OpenFeign 的依赖库,即 resilience4j-feign

1
2
3
4
xml复制代码<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-feign</artifactId>
</dependency>

接下来,我们使用这个依赖,给 OpenFeign 添加重试,首先启用 OpenFeign Client 并指定默认配置:

OpenFeignAutoConfiguration

1
python复制代码@EnableFeignClients(value = "com.github.jojotech", defaultConfiguration = DefaultOpenFeignConfiguration.class)

在这个默认配置中,通过覆盖默认的 Feign.Builder 的方式粘合 resilience4j 添加重试:

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
ini复制代码@Bean
public Feign.Builder resilience4jFeignBuilder(
List<FeignDecoratorBuilderInterceptor> feignDecoratorBuilderInterceptors,
FeignDecorators.Builder builder
) {
feignDecoratorBuilderInterceptors.forEach(feignDecoratorBuilderInterceptor -> feignDecoratorBuilderInterceptor.intercept(builder));
return Resilience4jFeign.builder(builder.build());
}

@Bean
public FeignDecorators.Builder defaultBuilder(
Environment environment,
RetryRegistry retryRegistry
) {
String name = environment.getProperty("feign.client.name");
Retry retry = null;
try {
retry = retryRegistry.retry(name, name);
} catch (ConfigurationNotFoundException e) {
retry = retryRegistry.retry(name);
}

//覆盖其中的异常判断,只针对 feign.RetryableException 进行重试,所有需要重试的异常我们都在 DefaultErrorDecoder 以及 Resilience4jFeignClient 中封装成了 RetryableException
retry = Retry.of(name, RetryConfig.from(retry.getRetryConfig()).retryOnException(throwable -> {
return throwable instanceof feign.RetryableException;
}).build());

return FeignDecorators.builder().withRetry(
retry
);
}

微信搜索“我的编程喵”关注公众号,每日一刷,轻松提升技术,斩获各种offer

本文转载自: 掘金

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

springboot集成E签宝修改个人或者企业电子账户信息(

发表于 2021-11-09

在使用e签宝签署合同的时候,会经常遇到,注册电子账户的时候,个人身份证、姓名、企业名称、法人、法人身份证、统一信用码输入错误,导致签署合同出现不必要的错误,那这个时候,就需要大家修改电子账户的信息,接下来我用代码给大家讲解一下

一、修改个人账户信息,根据个人电子账户

1
2
3
4
5
6
7
8
ini复制代码    UpdatePersonAccountByAccountId
personAccountByAccountId=Account.updatePersonAccountByAccountId("xxxxxxxxxxx");//个人电子账户
personAccountByAccountId.setIdNumber("xxxx");//身份证号
personAccountByAccountId.setMobile("xxxx");//手机号
personAccountByAccountId.setName("张三");
UpdatePersonAccountByAccountIdResponse r=personAccountByAccountId.execute(appids[0],
appKeys[0]);
System.out.println(r.getBody());

二、修改企业电子账户id修改企业信息

1
2
3
4
5
6
7
8
9
10
11
ini复制代码UpdateOrganizationsByOrgId organizationsByOrgId =
Account.updateOrganizationsByOrgId("xxxx");//电子账户id
organizationsByOrgId.setOrgId("xxxxx");//电子账户id
organizationsByOrgId.setName("xxx");//企业名称
organizationsByOrgId.setOrgLegalName("xxx");//法人姓名
organizationsByOrgId.setOrgLegalIdNumber("xxx");//法人身份证号


UpdateOrganizationsByOrgIdResponse response =
organizationsByOrgId.execute(appids[0], appKeys[0]);
System.out.println(response.getMessage());

三、企业信息修改为啥没有企业统一信用码修改呢

企业如果修改统一信用码,必须先注销掉电子账户重新注册,是无法修改的
如何注销呢

1
2
ini复制代码DeleteOrganizationsByOrgId  r= Account.deleteOrganizationsByOrgId("xxxxx");//企业电子账户id
DeleteOrganizationsByOrgIdResponse re= r.execute(appids[0], appKeys[0]);

接下来在重新注册就可以了,关于注册大家看上一篇文章就可以看到了

本文转载自: 掘金

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

喵喵那么可爱,做个猫咪主题的日历 前言 下载猫咪图片 下载依

发表于 2021-11-09

image.png

一起用代码吸猫!本文正在参与【喵星人征文活动】

前言

近期云吸猫活动火热,猫咪太可爱了,趁着工作间隙,做一份2022年猫咪主题的日历给自己吧。

话不多说,我们来开始动手做吧!

下载猫咪图片

我们在网页上,关键字搜索”猫咪”,可以搜索出上万张猫咪的图片

image.png

选择自己喜欢的图片,下载到文件中,且为每一张图片命名为从1~12的数字

image.png

下载依赖库

本次,我们需要下载第三方依赖库-openpyxl完成写入编辑excel表格操作

  • openpyxl 模块介绍

openpyxl 是用于对Excel文件进行读取写入操作的库

  • openpyxl 模块下载

1
python复制代码pip install openpyxl

导入依赖库

根据日历的需求:要生成日历,并且写入到Excel表格,所以我们需要导入一下库

1
2
3
4
5
6
7
8
9
10
11
12
python复制代码import openpyxl
# 日历模块
import calendar

# 设置单元格样式
from openpyxl.styles import Alignment,PatternFill,Font
# 获取列的字母
from openpyxl.utils import get_column_letter
# 向Excel写入图片
from openpyxl.drawing.image import Image
# 加载Excel文件
from openpyxl import load_workbook

生成12个月的日历

根据日历的需求分析,做法如下

  • 首先使用openpyxl.Workbook()创建新

1
Python复制代码wb = openpyxl.Workbook()
  • 使用 openpyxl.Workbook实例化对象wb调用create_sheet创建表

1
python复制代码sheet = wb.create_sheet(index=0,title=sheet_name[i])
  • 使用calendar.monthcalendar(year,i)获取指定月的二维列表数据

1
2
3
4
5
6
7
8
python复制代码print(calendar.monthcalendar(2022,1))

# [[0, 0, 0, 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],
[24, 25, 26, 27, 28, 29, 30],
[31, 0, 0, 0, 0, 0, 0]]
  • 使用两个for循环就可以得到12个月的二维列表

1
2
3
4
5
Python复制代码for j in range(len(calendar.monthcalendar(year,i))):

for k in range(len(calendar.monthcalendar(year,i)[j])):

value = calendar.monthcalendar(year,i)[j][k]
  • 条件判断当得到的数据是0是,则表格中写入空;否则写入具体的日期

1
2
3
4
5
6
7
8
9
Python复制代码f value == 0:

value = ''
sheet.cell(row=j+9,column=k+1).value=value

else:
sheet.cell(row=j+9,column=k+1).value=value

sheet.cell(row=j+9,column=k+1).font=Font(u"微软雅黑",size=11)
  • 为日历添加标题和样式设置

+ 由于设置日历firstday设置成周天为一周的第一天
+ 传入的日历标题为 ["星期日","星期一","星期二","星期三","星期四","星期五","星期六"]
+ 字体设置成微软雅黑,大小为11
+ 单元格居中:Alignment(horizontal="right",vertical="center")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Python复制代码for R1 in range(1,100):

for C2 in range(1,100):

sheet.cell(row=R1,column=C2).fill = fill

days = ["星期日","星期一","星期二","星期三","星期四","星期五","星期六"]

num = 0

# 设置日历单元格样式
for k3 in range(1,8):

sheet.cell(row=8,column=k3).value = days[num]
sheet.cell(row=8, column=k3).alignment= align
sheet.cell(row=8, column=k3).font = Font(u"微软雅黑",size=11)

sheet.column_dimensions[get_column_letter(k3)].width = 12

num +=1
1
2
3
4
5
6
7
* 最后我们设置一下日历的表格格式
---------------


+ 单元格高度设置为30
+ 单元格字体样式为微软雅黑
+ 单元格水平居中

python复制代码for k4 in range(8,15):

sheet.row_dimensions[k4].height = 30

sheet.cell(row=3,column=1).value = year+"年"

sheet.cell(row=4,column=1).value = sheet_name[i]

sheet.cell(row=3, column=1).font = Font(u"微软雅黑", size=16,bold=True,color="FF7887")

sheet.cell(row=4, column=1).font = Font(u"微软雅黑", size=16, bold=True, color="FF7887")

sheet.cell(row=3, column=1).alignment = align
sheet.cell(row=4, column=1).alignment = align
1
2
* 保存生成好的日历文件
----------

python复制代码wb.save(filename)

1
2
3
4
5
6
7
8
9


添加喵咪图片
======


* wb.load\_workbook()打开日历文件
* wb.get\_sheet\_names()获取Excel表格名字列表
* 加载出来的文件对象使用add\_image()方法把图片添加到对应表中

python复制代码
def add_image(filepath):

wb = load_workbook(filepath)

sheet_names = wb.get_sheet_names()

for i in range(0,len(sheet_names)):

    wb = load_workbook(filepath)

    ws = wb[sheet_names[i]]

    ws.merge_cells('I1:P20')

    img = Image(f'file/{i}.jpg')

    ws.add_image(img,'I1')

    wb.save(filepath)


最后来看一下效果吧
=========


![效果](https://gitee.com/songjianzaina/juejin_p12/raw/master/img/2d390d9005ae0e1a0d2339406b348b8e0e716398c4993794fcca4913c7ab6e37)


总结
==


本期,使用Python简单制作一个猫咪主题的日历,大家可以动手做一个个性化的日历吧~


以上是本期内容,欢迎大佬们点赞评论,下期见~



**本文转载自:** [掘金](https://juejin.cn/post/7028544246575529992)

*[开发者博客 – 和开发相关的 这里全都有](https://dev.newban.cn/)*
1…385386387…956

开发者博客

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