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

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


  • 首页

  • 归档

  • 搜索

说一说 MySQL的锁机制(行锁、表锁、间隙锁、Next-K

发表于 2020-07-05
锁的操作类型分类
  • 读锁:共享锁,多个读操作可以对同一份数据同时进行而不会互相影响。
  • 写锁:排他锁,在写操作未完成之前,会阻止其他的写锁与读锁。
锁的操作粒度分类
  • 表锁: 偏向于读,MyiSAM
  • 行锁:偏向于写,InnoDB

MyiSAM

  • 在进行SELECT 操作前,MyiSAM会给涉及到的表加读锁。这个时候其他Session可以正常对未加锁的表进行操作。但是对加了读锁的表,只能对其进行查询(共享锁),对其修改则会阻塞,等待至表解锁后,才会生效。
  • Session1 Session2
    给TABLE_A加读锁 无操作
    可以对TABLE_A进行查询,不能对TABLE_A进行修改 可以对TABLE_A进行查询,不能对TABLE_A进行修改
    查询修改 TABLE_B 报错 查询修改 TABLE_B正常
    修改TABLE_A报错 修改TABLE_A阻塞
    Unlock tables;解锁 TABLE_A被修改
  • 在进行写操作前,MyiSAM会给涉及到的表加写锁。这个时候其他Session可以正常对未加锁的表进行操作。但是对加了写锁的表,对其进行读或写,都会阻塞,直至写锁释放后,才会进行相应操作。
+ | Session1 | Session2 |
| --- | --- |
| 给TABLE\_A加写锁 | 无操作 |
| 可以对TABLE\_A进行修改,不能对TABLE\_A进行查询 | 不能对TABLE\_A进行查询,修改 |
| 不能对TABLE\_B进行操作 | 可以对TABLE\_B进行操作 |
| 查询TABLE\_A报错 | 查询或者修改TABLE\_A阻塞 |
| Unlock tables;解锁 | TABLE\_A被查询出结果或被修改 |
  • MyiSAM的读写锁调度是写优先,这也是MyiSAM不适合作为以写为主的引擎。因为写锁后,其他线程不能进行任何操作,大量的写入操作会使得查询很难得到锁,从而造成永久堵塞。

IMPORTANT

通俗地讲:

  • 读锁就是:我要备份,后面来的先别乱动(修改)东西;
  • 写锁就是:我要修改,后面来的别急,排队(不管你是要读还是要改,都得等我这次改完)。

InnoDB

InnoDB的锁机制为行锁,行锁支持事务。

事务的ACID属性:
  • A 原子性(Atomic):指一个事务为最小单位不可再往下划分,要么全执行,要么都不执行。
  • C 一致性(Consistency):指数据在事务执行之前是一致的,执行之后也是一致的,即:不会破坏数据库的完整性(完整性:逻辑与业务上的符合逻辑即为完整性)。
  • I 隔离性(Isolation):指事务在执行的过程不会被外界的其他事务或数据库操作干扰,数事务执行过程中的中间态对外不可见。
  • D 持久性(Durability):事务执行完成之后对数据库的影响是持久的,不会回滚。
并发事务带来的问题:
  • 更新丢失:多个事务同时更新同一行的数据,并且不知道其他事务的存在,导致最后有的更新失效(丢失)了。
  • 脏读:事务在更新数据的过程中(已经修改了数据尚未提交),去读取数据,读到了中间态的数据,这些数据不符合一致性要求,即为脏读。
  • 不可重复读:在一次的事务操作中,对某一数据进行了两次(及以上)的读操作,此时有另一个事务在两次读操作的中间修改了数据,造成了两次读取的数据不同,即不可以重复读(不然数据会不一样)。
  • 幻读:在一次的事务操作中,先读取了几行数据后,另一个事务又增加或删除了数据,在此之后,此事务又去读取数据,发现数据凭空生成或消失,跟幻觉一样,即幻读。

总结:更新丢失为多个事务几乎同时修改数据出现的问题;脏读为一个事务在修改数据,另一个事务读取(SELECT)事务出现的问题;不可重复读为一个事务在查询数据,中间有另一个事务修改(UPDATE)了数据的问题;幻读为一个事务在查询数据,另一个事务在插入或删除(INSERT or DELETE)数据的问题。

事务的隔离级别:
  • 未提交读Read Uncommitted:最低级别,只能避免不读到物理修改数据过程的数据,数据的逻辑修改的中间态依然存在,破坏了数据一致性,上述问题同时存在。
  • 已提交读Read Committed:语句级,保证了语句的原子性,只能读到已经提交的内容,但是在修改数据过程中并没有加锁,为什么只会读到已经提交的数据内容呢?,这是使用了“快照读”的方式优化了,使得我们在修改数据的同时,查询不会被阻塞,可以完成高并发的查询,大大提高了效率;但是因为修改的过程中没有加锁,则会出现两次查询数据的过程中,数据中间被其他事务修改或者增添数据了,造成不可重复读和幻读。
  • 可重复读Repeated Read:事务级,MySQL默认隔离级别,从名字看就知道了,避免了不可重复读的问题。普通的查询同样是通过“快照读”的方式,来避免脏读的出现,在此基础上又加入了一个在事务开启的同时,不能对涉及到的数据行进行修改,从而避免了“同一次事务中读到的数据不一致”的不可重复读的问题,但是没有避免幻读(在InnoDB中解决了幻读「间隙锁加行锁」)。
  • 可序列号Serializable:最高级别,事务级,串行执行事务,即一个一个排队执行事务,这种级别下,所有的并发事务问题都会被避免,但是由于从并行操作变成了串行排队操作,效率大大降低。

已提交读与可重复读是实际开发中最经常使用到的两种事务隔离级别,这两者主要是通过MVCC(Multi Version Concurrency Control)对并发事务问题的解决。

  • Read Commited的做法是在事务的每一条SQL语句执行前生成一个快照,此时其他并发事务去读取这个数据时,避免了脏读的出现。
  • Repeated Read的做法是在事务的第一次查询前生成一个快照,之后在这一次事务的读取过程中,都去读取这一次快照,从而避免了脏读和不可重复读。

总结锁与隔离级别与并发问题的关系:

在默认的隔离级别下,我们在对某几行数据进行修改或者查询的时候,只会锁住这几行数据不被修改,从而保证避免了不可重复读的出现;而我们即使对整张表所有行都进行操作了,那也是锁住了这张表的所有行,而不是锁住这张表,不能阻止表插入新的行,从而依然会出现幻读的情况(间隙锁+行锁的Next-Key Lock解决了此问题),而最高的隔离级别则是通过将事务串行化,我们在执行查询事务的同时是不可能有其他事务来插入数据的,从而避免了幻读的出现。

间隙锁(Gap Lock)

当我们在查询语句时,条件为范围查询时,InnoDB不管这个区间是否有数据,都会将其锁住,向这个区间的“间隙”(不存在的行)插入或删除数据都会阻塞。

Next-Key Lock

Next-Key Lock = Record Lock + Gap Lock InnoDB在默认隔离级别(Repeated Read)下,使用Next-Key Lock的方案解决了幻读的问题。

即在进行范围性的SELECT时,我们先对已经存在的Records加上Record Lock,再对此区间的间隙加上Gap Lock,从而解决了幻读的问题。

索引失效会使得行锁变成表锁

原因:索引失效导致的全表扫描,使得从行锁->表锁。

如何锁定一行

select … for update 语句

优化建议

  • 尽可能让所有数据的检索都通过索引完成,避免无索引行锁升级为表锁。
  • 合理设计索引,尽量缩小锁的访问。
  • 尽可能减少检索条件 避免间隙锁的危害。
  • 尽量控制事务大小,减少锁定资源量和时间长度。
  • 尽可能低级别事务隔离。
InnoDB与MyiSAM最大的不同点:是InnoDB支持事务、行锁、外键,MyiSAM不支持。具体文章

本文转载自: 掘金

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

分享 10个酷炫的后台模板,给不爱写页面的程序员

发表于 2020-07-05

本文收录在个人博客:www.chengxy-nds.top,技术资源共享,一起进步

早前在粉丝群里说过,想要从零开始开发一个开源项目,这个项目的目的很简单:新人可以练手,一步一步搭建项目,把时下流行的技术整合进来,在用这些技术的特性来实现具体业务功能,而对于有经验的老鸟可以把自学的技术在项目中得以实践。

在这里插入图片描述

在这里插入图片描述

琢磨了挺长时间,前段时间工作比较忙、家中事情也比较多,导致最近才着手弄。

感兴趣的同学可以关注一波,项目进度可能会缓慢,但脚步绝不会停止

作为一个后端程序员,虽然平时也会做一些管理后台的页面,可那都是本着能用就行的原则,美观都是次要的。但项目要对外开源这可是门面,不美美哒多没面子,所以选一个酷炫的页面模板成了首要任务,选了10个模板出来,大伙给参谋下。

1、Kharna Admin

这是一个基于Bootstrap4.1.3,响应式后台管理模板,自适应分辨率,兼容PC端和移动端,全套模板,包括仪表盘、小部件、UI元素、按钮、日历、范围滑块、时间轴、发票、用户、邮件收件箱、邮件撰写、图标、基本表格、表单、图表、地图、轮廓、定价表、注册、登录等HTML后台模板页面。

预览传送门

在这里插入图片描述

在这里插入图片描述

2、layui 商品管理系统

一款基于 layui制作的商品后台管理系统。

预览传送门

在这里插入图片描述

在这里插入图片描述

3、MFAN

该后台基于layui制作,响应式设计,自适应分辨率,兼容PC端和移动端,全套模板,包括登录、前台菜单、后台菜单、文章管理、单页管理、邮件系统、个人信息、系统设置等HTML后台模板页面。

预览传送门

在这里插入图片描述

在这里插入图片描述

4、Amaze Admin

该模板是一个响应式数据统计后台管理模板,自适应分辨率,兼容PC端和移动端。

预览传送门

在这里插入图片描述

在这里插入图片描述

5、物流地图

物流大数据服务平台后台模板,DIV+CSS布局设计,仪表盘、可视化图表模板,非常酷炫的一个统计大屏。

预览传送门

在这里插入图片描述

在这里插入图片描述

6、文章管理后台

基于Layui制作,响应式设计,自适应分辨率,兼容PC端和移动端。

预览传送门

在这里插入图片描述

在这里插入图片描述

7、商城交易后台

HTML5 响应式商城后台管理框架模板,自适应分辨率,兼容PC端和移动端。

预览传送门

在这里插入图片描述

在这里插入图片描述

8、H-admin

用H-ui前端框架开发的轻量级响应式网站后台管理模版,采用原生HTML语言,iframe结构布局,多选项卡效果。

预览传送门

在这里插入图片描述

在这里插入图片描述

9、考试答题

在线考试答题页面模板基于jquery-1.11.3.min.js 制作,主要以答题卡为主,左边是题目,右边是答题卡。

预览传送门

在这里插入图片描述

在这里插入图片描述

10、NICE ADMIN

这套模板基于Bootstrap3.0.0制作,响应式设计,自适应分辨率,兼容PC端和移动端。

预览传送门

在这里插入图片描述
模板已经整理好,关注公号回复【模板】领取吧!

原创不易,燃烧秀发输出内容,如果有一丢丢收获,点个再看鼓励一下吧!

整理了几百本各类技术电子书,送给小伙伴们。关注公号回复【666】自行领取。和一些小伙伴们建了一个技术交流群,一起探讨技术、分享技术资料,旨在共同学习进步,如果感兴趣就扫码加入我们吧!

本文转载自: 掘金

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

SpringMVC源码分析七、DispatcherServl

发表于 2020-07-05

经过前面几篇文章的分析, 此时此刻我们再来看SpringMVC执行请求的流程就基本不会遇到盲点了, 在整个SpringMVC
的源码分析中, 只分析了HandlerMapping、HandlerAdapter两个组件, 至于ViewResovler, 笔者决定不去分析了,
因为这个组件不太难, 如果将HandlerAdapter都拿下来了的话, 读ViewResolver的源码就会很轻松, 因为都是类似
的, 找到对应的视图解析器, 调用解析的方法进行解析, 这前提是我们返回的是一个视图名称, 如果用了
@ResponseBody注解, 那么视图解析也就没啥必要性了, 因为数据已经在ReturnValueResolver中利用消息转换器写
回到请求端了, 所以视图解析器其实是针对于我们返回了视图的情况, 有了前面的Model以及ModelAndViewContainer
知识的铺垫, 看这一块代码简直不要太轻松……….本篇文章则对SpringMVC的执行流程进行分析, 看看我们的请求
到达了Servlet后到底是如何被处理的

引入

1
2
3
4
5
6
7
8
9
10
11
12
13
复制代码在前面的文章中, 我们对DispatcherServlet的初始化流程进行了分析, 首先要明白的一点是, 经过这次的初始化,
整个Web容器已经被创建好了, SpringMVC的九大组件也已经被放置到了DispatcherServlet中了, 请求的处理其实就
是对这些组件的应用而已, 我们假设有如下的测试用例:
@Controller
public class TestController {
@RequestMapping( value="/test", method = RequestMethod.GET )
@ResponseBody
public List<String> test () {
return Arrays.asList( "a", "b" );
}
}

通过浏览器请求了如下域名: localhost/test

DispatcherServlet请求的处理

入口点: FrameworkServlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
复制代码protected void service(HttpServletRequest request, HttpServletResponse response) {
HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
if (HttpMethod.PATCH == httpMethod || httpMethod == null) {
processRequest(request, response);
}
else {
super.service(request, response);
}
}

分析:
FrameworkServlet重写了HttpServlet的service方法, 请求进来的时候, 首先是进入到了这个service方法,
通过从请求中拿到请求方法, 将其转为枚举类HttpMethod, 最后还是调用了HttpServlet的service方法, 之所
以重写这个方法, 原因是增加了对请求方法类型为patch的处理, 在HttpServlet的service中是没有对这个类型
的请求进行处理的, 我们先不理会processRequest方法是做啥的, 首先需要知道, 当请求是其他类型的时候, 最
终会调用到HttpServlet的service方法的, 在这个方法中有各种doXXX方法, 然而!!FrameworkServlet重写了
这些doXXX方法, 以doPost为例:
protected final void doPost(HttpServletRequest request, HttpServletResponse response) {
processRequest(request, response);
}

ok, 到这里为止, 大家应该就清楚了, 真正处理请求的是这个processRequest方法, 所有的请求最终都会到
FrameworkServlet中的doXX方法, 最终用processRequest方法来调用

processRequest: 真正处理请求的方法

  • processRequest引入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
复制代码protected final void processRequest(HttpServletRequest request, HttpServletResponse response) {
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);

RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes
= buildRequestAttributes(request, response, previousAttributes);

initContextHolders(request, localeContext, requestAttributes);

try {
doService(request, response);
} finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}

分析:
这个方法主要分为六个部分, 接下来我们对这些进行拆分, 一个个分析
  • LocalContext&RequestAttributes
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
复制代码首先来看前三个部分, LocalContext、RequestAttributes、initContextHolders

先来说一下什么是LocalContext吧, 翻译过来是语言环境, 比如国际化相关的功能就跟语言环境有关, 中文环境还是
英文环境这样的意思, request请求对象中有一个getLocale方法用来获取当前的语言环境, 将其保存在LocalContext
中的局部变量中, 以SimpleLocaleContext为例:
public class SimpleLocaleContext implements LocaleContext {
private final Locale locale;
public SimpleLocaleContext(@Nullable Locale locale) {
this.locale = locale;
}

public Locale getLocale() { return this.locale; }
}

很简单的一个代码, 说明了语言环境其实是将Local对象保存了起来而已, 如果想要在整个请求任何地方都能拿到这个
语言环境, 或者说拿到这个Local对象, 大家应该很容易想到用ThreadLocal来保存就好了, LocaleContextHolder
就是一个ThreadLocal, 用来保存LocaleContext的, 接下来看看上面这个processRequest的第一部分:
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);

很简单, 从TheadLocal中拿到之前的语言环境, 临时保存起来, 然后利用buildLocaleContext方法构建当前的语言
环境对象, 这个方法被DispatcherServlet类重写了:
protected LocaleContext buildLocaleContext(final HttpServletRequest request) {
LocaleResolver lr = this.localeResolver;
if (lr instanceof LocaleContextResolver) {
return ((LocaleContextResolver) lr).resolveLocaleContext(request);
}
else {
return () -> (lr != null ? lr.resolveLocale(request) : request.getLocale());
}
}

看到上面的代码, 可以看到, 最终都是利用一个LocaleResolver来解析语言环境对象的, 即SpringMVC利用这个组件
来完成对Local的解析, 那这第一部分就很清晰了, 将之前的语言环境临时保存, 构建本次请求的语言环境, 在原生的
请求上增加了LocaleResolver语言环境解析器来进行解析

ServletRequestAttributes对象, 其实这个对象在之前我们就用到了, 之前讲解@SessionAttributes注解的时候,
将Model数据放入到session中最终就是利用这个ServletRequestAttributes对象的, 该对象的功能很简单, 保存了
当前请求的HttpServletRequest、HttpServletResponse、HttpSession, 提供了对session属性的设置和获取, 此
时再来看这第二部分的代码:
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes
= buildRequestAttributes(request, response, previousAttributes);

先从RequestContextHolder中获取原来的ServletRequestAttributes, 临时保存起来, 然后利用request、
response、previousAttributes构建一个ServletRequestAttributes, 在上面是构建LocaleContext

到此为止, LocaleContext和ServletRequestAttributes就已经构建好了, 我们之前只看到了从
LocaleContextHolder和RequestContextHolder中获取这两个对象, 接下来第三部分的代码就是将当前请求的这两个
对象放入到对应的ThreadLocal中, 即initContextHolders(request, localeContext, requestAttributes);
的调用, 我们来看看即initContextHolders方法:

private void initContextHolders(HttpServletRequest request, LocaleContext localeContext,
RequestAttributes requestAttributes) {

if (localeContext != null) {
LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);
}
if (requestAttributes != null) {
RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
}
}

小小的总结:
整个processRequest方法的前三部分, 就是构建LocaleContext、ServletRequestAttributes, 然后将他们保
存到ThreadLocal中, 这样我们在整个请求的任何地方都能获取到这两个对象了, 举个例子:
HttpServletRequest request = ( (ServletRequestAttributes)RequestContextHolder
.getRequestAttributes() ).getRequest();

这样我们就能在请求的任意地方获取HttpServletRequest对象了
  • resetContextHolders&publishRequestHandledEvent
1
2
3
4
复制代码在processRequest方法的finally语句中, 调用了这两个方法, resetContextHolders方法的调用就是因为此时请求
结束了, 需要将当前线程上下文ThreaLocal中的LocaleContext以及ServletRequestAttributes恢复到请求之前的
状态, 而publishRequestHandledEvent方法就是发布了一个事件而已, 大家有兴趣看下, 这个对我们分析执行流程
没多大意义, 就不进行分析了
  • doService方法
1
2
3
4
5
6
7
8
9
复制代码一共六个部分, 前面已经分析了五个部分, 代码都比较简单, 而第四部分doService方法才是真正用来处理请求的, 这
个方法里面做的事情对执行流程的分析也没多大意义, 我们简单的过一下, 最后着重来讲该方法中触发执行流程处理的
核心方法, 在这个方法中, 先是对request attributes中满足一定规则的key-value进行了保存, 最后在finally中
进行了restore, 这里我们不用去理会, 如果大家感兴趣可以研究下是干嘛的, 笔者也没去研究, 之后往request的
Attribute中添加了一些key-value, 将WebApplicationContext、localeResolver、themeResolver、
ThemeSource放到了请求域中, 然后是对flashMapManager的处理, 这个涉及到重定向相关的应用, 大家有兴趣了解
一下, 因为重定向的时候是多个不同的请求, 请求域是不能共享的, 那么如果将一些数据从A请求带到重定向后的B请求
又不借助session呢, SpringMVC提供了一个FlashMap组件专门完成这样的功能, 大家有兴趣可以了解下, 由于用的
不多就不进行展开了, 最后, 调用了doDispatch方法开始真正的处理请求!!!同时也是处理请求中最为复杂的部分!!

doDispatch处理请求的核心

  • 大致的流程分析
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
复制代码在前面的文章中, 我们对HandlerMapping和HandlerAdapter进行了详细的分析, 由此可以知道, 日常工作中使用的
最多的@RequestMapping注解来表示一个请求的情况下, @RequestMapping注解本身由RequestMappingInfo这个类
对象来表示, 而被该注解标注的方法则是用HandlerMethod来表示的, 在AbstractHandlerMethodMapping中有一个
MappingRegistry对象, url到RequestMappingInfo, RequestMappingInfo到HandlerMethod的映射都是在这个对
象中存储的, 这也是HandlerMapping的作用, 我们能通过url找到对应的HandlerMethod

上面描述中处理请求的HandlerMethod在SpringMVC中也被称为handler, handler的类型是不确定的, 仅仅说对于
@RequestMapping这样的情况下handler的表现形式是HandlerMethod而已, 当我们不是通过@RequestMapping来完成
映射的时候, handler就不一样了, 比如说Controller接口, 注意, 不是@Controller注解, 该接口也可以被用来处
理请求, 只不过是被配置在SimpleUrlHandlerMapping里面而已, Controller接口的实现类也被称为handler

handler的种类这么多, 为了统一调用, 从而引入了适配器模式, 提供一个公共的适配器, 不同类型的handler通过实
现该接口的方法的公共方法来实现自己的调用, 从而就有了各种HandlerAdapter, 我们之前着重讲解了
RequestMappingHandlerAdapter, 所有的适配器中, 通过supports方法来判断该适配器是否能够处理当前的
handler, 如果能, 则调用handle方法来完成调用

以上的内容在前面几篇文章中笔者已经详细的讲解了其中的细节, 而doDispatch处理请求很简单, 通过当前的url以及
遍历所有的HandlerMapping, 如果在一个HandlerMapping中能够通过url找到对应的url, 则返回其中存储的Handler
对于RequestMappingHandlerMapping来说, 返回的就是一个HandlerMethod, 而对于SimpleUrlHandlerMapping来
说, 可能返回的就是Controller的实现类, 注意, 这里是可能....因为还可能有其他类型的handler也是存储在这个
HandlerMapping的

在SpringMVC中, 利用handler处理请求的时候, 同时提供了拦截器, 即HandlerInterceptor, 拦截器有三个方法,
分别可以在handler被调用前, 调用后, 调用完成的同时被调用, 伪代码如下:
try {
interceptor.preHandle();
handler.handle();
interceptor.postHandle();
} catch (Exception) {}
finally {
interceptor.afterCompletion();
}

当我们在HandlerMapping中找到了对应的handler后, 就会将handler和HandlerInterceptor封装为
HandlerExecutionChain对象, 从而方便直接获取两个对象, 再往后, 遍历所有的HandlerAdapter, 调用其
supports方法对该handler进行验证, 如果找到了对应的Adapter则返回, 之后调用这个Adapter的handle方法完成对
请求的处理, 该方法返回一个ModelAndView对象, 当我们返回一个字符串表示jsp文件的时候, 这个jsp的路径就被存
储在了这个ModelAndView中, 其实一开始是存储在之前我们分析的ModelAndViewContainer中的, 只不过最后将其放
在了ModelAndView返回了而已, 最后利用ModelAndView进行视图的渲染, 大致的流程就是这样, 接下来我们开始对
源码进行分析
  • doDispatch源码(省略了一些异常处理以及异步请求的内容)
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
复制代码protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;

try {
ModelAndView mv = null;
Exception dispatchException = null;

processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);

mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}

HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
} catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
}

纵观整个精简后的doDispatch方法, 结构非常的清晰, processedRequest表示真正的请求对象, mappedHandler就
是之前我们分析的, 将handler和HandlerInterceptor封装起来的对象, multipartRequestParsed表示是否对文件
上传这样的功能进行了解析, 因为SpringMVC是有提供文件上传功能的, 所以不能直接用HttpServletRequest来表示
如果是文件上传, 那么还要进行一次请求的解析, 才能够得到最终的请求对象processedRequest

mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}

这段代码就是找到对应的handler, 将handler和HandlerIntercepter进行合并, 变成一个HandlerExecutionChain
对象, 如果通过url没有找到handler, 就执行if语句块的内容, 即抛出一个异常

HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

mappedHandler.getHandler()获取对应的handler, 利用这个handler来遍历所有的HandlerAdapter, 找到合适的
HandlerAdapter并返回

if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; }
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);

遍历HandlerExecutionChain中的所有拦截器, 调用其preHandle方法, 如果返回true, 那么就继续执行, 返回
false则就不执行了, 调用HandlerAdapter的handle方法, 如果是@RequestMapping的情况, 则调用的是
AbstractHandlerMethodAdapter的hanlde方法, 返回一个ModelAndView对象, 这个方法的调用我们在上一篇文章已
经详细的讲解了, 这里就不再进行展开了, 最后调用HandlerExecutionChain中所有拦截器的postHandle方法,
applyDefaultViewName是因为当我们返回的ModelAndView中没有View的时候, 比如我们@RequestMapping标注的方
法返回的是void或者有被@ResponseBody标注的时候, 就是没有视图的, 此时会赋予一个默认的视图, 里面的代码很
简单, 大家有兴趣可以看下

在上面doDispatch的代码中, 我们可以看到在异常捕获后调用了triggerAfterCompletion方法, 里面其实就是对
HandlerExecutionChain中所有拦截器的afterCompletion方法的调用, 代码也很简单
  • processDispatchResult处理视图
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
复制代码上面的分析中, 已经将doDispatch分析完了, 有了前面文章的铺垫, 对这个方法的分析简直是太简单了, 我们还剩下
一个事情没有做, 假设返回了视图, 怎么对这个视图进行处理呢?比如:
@RequestMapping( "/test" )
public String test () {
return "index"
}

下面我们直接利用注释对processDispatchResult方法进行描述, 因为比较简单, 就不采用之前的方式进行分析了:

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) {
boolean errorView = false;

/**
* 在doDispatch中, 会将异常进行捕获, 放入到一个Exception对象中, 对视图进行解析的时候会传进来
* 如果存在异常, 那么就对异常进行处理, 比如说返回一个异常视图(如果配置了异常视图的话)
*/
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}

// 如果视图不是空的, 就开始渲染视图到前端页面
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
}

// 调用所有拦截器的afterCompletion方法
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}

render方法就不进行分析了, 大家有兴趣看下, 其实就是利用ViewResolver解析viewName, 获取到一个视图View对
象, 然后对http的状态进行一下设置, 最后调用视图对象View的render方法完成渲染, 对于JSP文件来说, 其实就是
forward到对应的jsp文件而已

本文转载自: 掘金

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

入门alibaba的EasyExcel 一、关于EasyEx

发表于 2020-07-05

一、关于EasyExcel

1、什么是EasyExcel,有什么作用?

EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel。

2、为什么选择EasyExcel,而不是Apache poi或者jxl?

Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。easyexcel重写了poi对07版Excel的解析,能够原本一个3M的excel用POI sax依然需要100M左右内存降低到几M,并且再大的excel不会出现内存溢出,03版依赖POI的sax模式。在上层做了模型转换的封装,让使用者更加简单方便。

3、怎样使用?

以下章节能让你快速使用EasyExcel对excel文件进行读写操作。

二、入门EasyExcel

1、新建一个maven项目、导入easyexcel的jar包。

maven项目的jar包可以在mvnrepository.com/里面搜索,能快速的搜索到想要的jar包,以及版本。

我这里选择的是当前最新的2.1.4版本。

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
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
复制代码package com.hgl.entity;

import java.util.Date;

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.alibaba.excel.annotation.format.NumberFormat;
import com.alibaba.excel.annotation.write.style.ColumnWidth;

// 表示列宽
@ColumnWidth(20)
public class UserEntity {

// index--表示属性在第几列,value--表示标题
@ExcelProperty(value = "姓名", index = 0)
private String name;

// @DateTimeFormat--对日期格式的转换
@DateTimeFormat("yyyy-MM-dd")
@ExcelProperty(value = "生日", index = 1)
private Date birthday;

@ExcelProperty(value = "电话", index = 2)
private String telphone;

// @NumberFormat--对数字格式的转换
@NumberFormat("#.##")
@ExcelProperty(value = "工资", index = 3)
private double salary;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Date getBirthday() {
return birthday;
}

public void setBirthday(Date birthday) {
this.birthday = birthday;
}

public String getTelphone() {
return telphone;
}

public void setTelphone(String telphone) {
this.telphone = telphone;
}

public double getSalary() {
return salary;
}

public void setSalary(double salary) {
this.salary = salary;
}

}

3、提供一个供外部调用写入Excel的接口

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

import java.io.IOException;
import java.net.URLEncoder;
import java.util.Date;
import java.util.List;

import javax.servlet.http.HttpServletResponse;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.alibaba.excel.EasyExcel;
import com.google.common.collect.Lists;
import com.hgl.entity.UserEntity;

@RestController
@RequestMapping("/excel")
public class ExcelWriteController{


/**
* 测试写入Excel文件
*
* @param response
* @throws IOException
*/
@GetMapping("/download")
public void doDownLoad(HttpServletResponse response) throws IOException {
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode("第一个文件", "UTF-8");
response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
EasyExcel.write(response.getOutputStream(), UserEntity.class).sheet("模板").doWrite(getData());
}

/**
* 构造假数据,实际上应该从数据库查出来
*
* @return List<UserEntity>
*/
private List<UserEntity> getData(){
List<UserEntity> users = Lists.newArrayList();
for (int i = 1; i <= 9; i++) {
UserEntity user = new UserEntity();
user.setBirthday(new Date());
user.setName("user_" + i);
user.setSalary(1.285 * i);
user.setTelphone("1888888888" + i);
users.add(user);
}
return users;
}
}

下载结果:

4、提供一个供外部调用读取Excel的接口

首先需要写一个监听器:

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
复制代码package com.hgl.listener;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import com.hgl.entity.UserEntity;

/**
* 1、注意监听器不能由spring容器管理,每次调用时都需要手动new
* 2、监听器内部需要使用ioc中的bean时,可以通过构造方法传入
*
* @author guilin
*
*/
public class UserListener extends AnalysisEventListener<UserEntity>{

private List<UserEntity> data = Lists.newArrayList();

private static final Logger LOGGER = LoggerFactory.getLogger(UserListener.class);

/**
* 解析每条数据时都会调用
*/
@Override
public void invoke(UserEntity user, AnalysisContext context) {
data.add(user);
}

/**
* 所有数据解析完之后调用
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 可以在此处执行业务操作
// 本例就打印到控制台即可,表示读取完成
LOGGER.info(JSON.toJSONString(data));
}

}

读取方法:

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.hgl.controller;

import java.io.IOException;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import com.alibaba.excel.EasyExcel;
import com.hgl.entity.UserEntity;
import com.hgl.listener.UserListener;

@RestController
@RequestMapping("/excel")
public class ExcelReadController {

/**
* 测试读取Excel文件
*
* @param file
* @return String
*/
@PostMapping("/read")
public String doDownLoad(MultipartFile file) throws IOException {
EasyExcel.read(file.getInputStream(), UserEntity.class, new UserListener()).sheet().doRead();
return "success";
}
}

使用postman测试读数据:

成功读到数据:

三、收尾

这篇文章提供了最简单的使用EasyExcel进行读写操作的例子,更多使用方法请参考官方文档或开源社区。

附上官方文档地址:alibaba-easyexcel.github.io/

GitHub开源地址:github.com/alibaba/eas…

本文转载自: 掘金

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

面试竟被问到Redis事务,触及知识盲区,脸都绿了

发表于 2020-07-04

前言

前几天有读者说自己面试被问到Redis的事务,虽然不常用,但是面试竟然被问到,平时自己没有注意Redis的事务这一块,面试的时候被问到非常不好受。

虽然,这位读者面试最后算是过了,但是薪资方面没有拿到自己理想的薪资。

其实这个也是正常的,一般面试被问到烂大街的,谁还问你啊,专门挑一些不常见的来问你,就是为了压你的薪资。

所以在这里写一篇文章对Redis的事务进行详细的讲解,估计对Redis事务从理解到原理深入这一篇就够了。

以后面试都不用担心了再被问道Redis的事务了,这一篇主要讲解Redis事务原理和实操的演练,理解理论的同时也通过实操来证实理论。

事务介绍

Redis事务是一组命令的集合,将多个命令进行打包,然后这些命令会被顺序的添加到队列中,并且按顺序的执行这些命令。

「Redis事务中没有像Mysql关系型数据库事务隔离级别的概念,不能保证原子性操作,也没有像Mysql那样执行事务失败会进行回滚操作」。

这个与Redis的特点:「快速、高效」有着密切的关联,「因为一些列回滚操作、像事务隔离级别那这样加锁、解锁,是非常消耗性能的」。所以,Redis中执行事务的流程只需要简单的下面三个步骤:

  1. 开始事务(MULTI)
  2. 命令入队
  3. 执行事务(EXEC)、撤销事务(DISCARD )

在Redis中事务的实现主要是通过如下的命令实现的:

命令 功能描述
MULTI 「事务开始的命令」,执行该命令后,后面执行的对Redis数据类型的「操作命令都会顺序的放进队列中」,等待执行EXEC命令后队列中的命令才会被执行
DISCARD 「放弃执行队列中的命令」,你可以理解为Mysql的回滚操作,「并且将当前的状态从事务状态改为非事务状态」。
EXEC 执行该命令后「表示顺序执行队列中的命令」,执行完后并将结果显示在客户端,「将当前状态从事务状态改为非事务状态」。若是执行该命令之前有key被执行WATCH命令并且又被其它客户端修改,那么就会放弃执行队列中的所有命令,在客户端显示报错信息,若是没有修改就会执行队列中的所有命令。
WATCH key 表示指定监视某个key,「该命令只能在MULTI命令之前执行」,如果监视的key被其他客户端修改,「EXEC将会放弃执行队列中的所有命令」
UNWATCH 「取消监视之前通过WATCH 命令监视的key」,通过执行EXEC 、DISCARD 两个命令之前监视的key也会被取消监视

以上就是一个Redis事务的执行过程包含的命令,下面就来详细的围绕着这几个命令进行讲解。

开始事务

MULTI 命令表示事务的开始,当看到OK表示已经进入事务的状态:

该命令执行后客户端会将「当前的状态从非事务状态修改为事务状态」,这一状态的切换是将客户端的flags属性中打开REDIS_MULTI来完成的,该命令可以理解关系型数据库Mysql的BEGIN TRANCATION语句:

命令入队

执行完MULTI命令后,后面执行的操作Redis五种类型的命令都会按顺序的进入命令队列中,该部分也是真正的业务逻辑的部分。

Redis客户端的命令执行后若是当前状态处于事务状态命令就会进入队列中,并且返回QUEUED字符串,表示该命令已经进入了命令队列中,并且「事务队列是以先进先出(FIFO)的方式保存入队的命令」的。

若是当前状态是非事务状态就会立即执行命令,并将结果返回客户端。在事务状态「执行操作事务的命令就会被立即执行」,如EXEC、DISCARD、UNWATCH。

结合上面的分析,Redis执行命令的流程如下图所示:

事务的命令队列中有三个参数分别是:「要执行的命令」、「命令的参数」、「参数的个数」。例如:通过执行如下的命令:

1
2
3
4
5
6
复制代码redis> MULTI
OK
redis> SET name "黎杜"
QUEUED
redis> GET name
QUEUED

那么对应上面的队列中三个参数如下表格所示:

执行的命令 命令的参数 参数的个数
SET [“name”, “黎杜”] 2
GET [“name”] 1

执行事务

当客户端执行EXEC命令的时候,上面的命令队列就会被按照先进先出的顺序被执行,当然执行的结果有成功有失败,这个后面分析。

上面说到当客户端处于非事务的状态命令发送到服务端会被立即执行,若是客户端处于事务状态命令就会被放进命令队列。

命令入队的时候,会按照顺序进入队列,队列以先进先出的特点来执行队列中的命令。

若是客户端处于事务状态,执行的是EXEC、DISCARD、UNWATCH这些操作事务的命令,也会被立即执行。

「(1)正常执行」

还是上面的例子,执行如下的代码:

1
2
3
4
5
6
复制代码redis> MULTI
OK
redis> SET name "黎杜"
QUEUED
redis> GET name
QUEUED

所有的命令进入了队列,当最后执行EXEC,首先会执行SET命令,然后执行GET命令,并且执行后的结果也会进入一个队列中保存,最后返回给客户端:

回复的类型 回复的内容
status code reply OK
bulk reply “黎杜”

所以最后你会在客户端看到「OK、黎杜」,这样的结果显示,这个也就是一个事务成功执行的过程。

至此一个事务就完整的执行完成,并且此时客户端也从事务状态更改为非事务状态。

「(2)放弃事务」

当然你也可以放弃执行该事务,只要你再次执行DISCARD操作就会放弃执行此次的事务。具体代码如下所示:

1
2
3
4
5
6
7
8
复制代码redis> MULTI
OK
redis> SET name "黎杜"
QUEUED
redis> GET name
QUEUED
redis> DISCARD // 放弃执行事务
OK

DISCARD命令取消一个事务的时候,就会将命令队列清空,并且将客户端的状态从事务状态修改为非事务的状态。

「Redis的事务是不可重复的」,当客户端处于事务状态的时候,再次向服务端发送MULTI命令时,直接就会向客户端返回错误。

WATCH 命令

WATCH命令是在MULTI命令之前执行的,表示监视任意数量的key,与它对应的命令就是UNWATCH命令,取消监视的key。

WATCH命令有点「类似于乐观锁机制」,在事务执行的时候,若是被监视的任意一个key被更改,则队列中的命令不会被执行,直接向客户端返回(nil)表示事务执行失败。

下面我们来演示一下WATCH命令的操作流程,具体实现代码如下:

1
2
3
4
5
6
7
8
9
复制代码redis> WATCH num
OK
redis> MULTI
OK
redis> incrby num 10
QUEUED
redis> decrby num 1
QUEUED
redis> EXEC // 执行成功

这个是WATCH命令的正常的操作流程,若是在其它的客户端,修改了被监视的任意key,就会放弃执行该事务,如下图所示:

客户端一 客户端二
WATCH num
MULTI
incrby num 10 get num
decrby num 1
EXEC
执行失败,返回(nil)

WATCH命令的底层实现中保存了watched_keys 字典,「字典的键保存的是监视的key,值是一个链表,链表中的每个节点值保存的是监视该key的客户端」。

若是某个客户端不再监视某个key,该客户端就会从链表中脱离。如client3,通过执行UNWATCH命令,不再监视key1:

错误处理

上面说到Redis是没有回滚机制的,那么执行的过程,若是不小心敲错命令,Redis的命令发送到服务端没有被立即执行,所以是暂时发现不到该错误。

那么在Redis中的错误处理主要分为两类:「语法错误」、「运行错误」。下面主要来讲解一下这两类错误的区别。

「(1)语法错误」

比如执行命令的时候,命令的不存在或者错误的敲错命令、参数的个数不对等都会导致语法错误。

下面来演示一下,执行下面的四个命令,前后的两个命令是正确的,中间的两个命令是错误的,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
复制代码127.0.0.1:6379> multi
OK
127.0.0.1:6379> set num 1
QUEUED
127.0.0.1:6379> set num
(error) ERR wrong number of arguments for 'set' command
127.0.0.1:6379> ssset num 3
(error) ERR unknown command 'ssset'
127.0.0.1:6379> set num 2
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.

语法错误是在Redis语法检测的时候就能发现的,所以当你执行错误命令的时候,也会即使的返回错误的提示。

最后,即使命令进入队列,只要存在语法错误,该队列中的命令都不会被执行,会直接向客户端返回事务执行失败的提示。

「(2)运行错误」

执行时使用不同类型的操作命令操作不同数据类型就会出现运行时错误,这种错误时Redis在不执行命令的情况下,是无法发现的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
复制代码127.0.0.1:6379> multi
OK
127.0.0.1:6379> set num 3
QUEUED
127.0.0.1:6379> sadd num 4
QUEUED
127.0.0.1:6379> set num 6
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
3) OK
127.0.0.1:6379> get key
"6"

这样就会导致,正确的命令被执行,而错误的命令不会不执行,这也显示出Redis的事务并不能保证数据的一致性,因为中间出现了错误,有些语句还是被执行了。

这样的结果只能程序员自己根据之前执行的命令,自己一步一步正确的回退,所谓自己的烂摊子,自己收拾。

Redis事务与Mysql事务

我们知道关系性数据库Mysql中具有事务的四大特性:「原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)」。

但是Redis的事务为了保证Redis除了客户端的请求高效,去除了传统关系型数据库的「事务回滚、加锁、解锁」这些消耗性能的操作,Redis的事务实现简单。

原子性中Redis的事务只能保证单个命令的原子性,多个命令就无法保证,如上面索道的运行时错误,即使中间有运行时错误出现也会正确的执行后面正确的命令,不具有回滚操作。

既然没有了原子性,数据的一致性也就无法保证,这些都需要程序员自己手动去实现。

Reids在进行事务的时候,不会被中断知道事务的运行结束,也具有一定的隔离性,并且Redis也能持久化数据。

本文转载自: 掘金

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

Autowired注解与resource注解的区别(十分

发表于 2020-07-04

背景

今天下班路上看到一个大货车,于是想到了装配,然后脑海里跳出了一个注解@Autowired(自动装配),于是又想到最近工作项目用的都是@Resource注解来进行装配。于是本着学什么东西都要一钻到底才能从菜鸟变大神的精神!!我就认真研究了一下,在此总结一波。以下内容先分别解释一下两个注解,再进行共同点与不同点的总结。

@Autowired
@Autowired为Spring提供的注解,需要导入包org.springframework.beans.factory.annotation.Autowired。

@Autowired采取的策略为按照类型注入。

1
2
3
4
复制代码public class UserService {
@Autowired
private UserDao userDao;
}

如上代码所示,这样装配回去spring容器中找到类型为UserDao的类,然后将其注入进来。这样会产生一个问题,当一个类型有多个bean值的时候,会造成无法选择具体注入哪一个的情况,这个时候我们需要配合着@Qualifier使用。

@Qualifier告诉spring具体去装配哪个对象。

1
2
3
4
5
复制代码public class UserService {
@Autowired
@Qualifier(name="userDao1")
private UserDao userDao;
}

这个时候我们就可以通过类型和名称定位到我们想注入的对象。

@Resource

@Resource注解由J2EE提供,需要导入包javax.annotation.Resource。

@Resource默认按照ByName自动注入。

1
2
3
4
5
6
7
8
9
10
复制代码public class UserService {
@Resource
private UserDao userDao;
@Resource(name="studentDao")
private StudentDao studentDao;
@Resource(type="TeacherDao")
private TeacherDao teacherDao;
@Resource(name="manDao",type="ManDao")
private ManDao manDao;
}

①如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。

②如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。

③如果指定了type,则从上下文中找到类似匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。

④如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配。

总结

Spring属于第三方的,J2EE是Java自己的东西。使用@Resource可以减少代码和Spring之间的耦合。

两者都可以写在字段和setter方法上。两者如果都写在字段上,那么就不需要再写setter方法。

当存在多个类型,却又没有指定的时候,会报如下的错误:

1
复制代码严重: Exception sendingcontext initialized event to listener instance of classorg.springframework.web.context.ContextLoaderListenerorg.springframework.beans.factory.BeanCreationException: Error creating beanwith name 'sequenceServiceImpl': Injection of resource dependencies failed;nested exception isorg.springframework.beans.factory.NoUniqueBeanDefinitionException: Noqualifying bean of type

本文转载自: 掘金

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

你不知道的常用工具类

发表于 2020-07-04

1. 空值判断

1. 空字符串判断

1
java复制代码boolean isEmpty =  StringUtils.isEmpty(str);

2. 空集合判断

1
java复制代码boolean isEmpty  = CollectionUtils.isNotEmpty(list);

3. 对象判空,在某些其它的工具类中 StringUtils既可以对字符串判空,也可以对对象进行判空

  • org.apache.commons.lang3包
1
2
3
4
5
java复制代码StringUtils.isEmpty(CharSequence cs);

public static boolean isEmpty(CharSequence cs){
return cs == null || cs.length() ==0
}
  • 在org.springframework.util参数类型是Object的
1
2
3
java复制代码public static boolean isEmpty(Object obj){
return obj == null || "".equals(obj);
}

4. 字符串转集合

1
2
3
java复制代码   // str = "2,5,8,7";
String str = "2,5,8,7";
List<Integer> idList = StringUtil.stringToList(str,",");

2.stream流操作相关的工具

1. 集合去重过滤

1
2
3
java复制代码List<User> userList = userService.getList();
//去掉某个字段为空的数据
userList = userList.stream().filter(s =>StringUtils.isEmpty(s.getPhone()).collect(Collectors.toList());

2. 遍历操作

1
2
3
4
5
java复制代码
userList = userList.stream().map(user -> user.getName());
userList = userList.stream().map(user -> user.getName()).forEach(str -> {System.out.println(str)})
//提取所有的id
List<Integer> ids = userList.stream().map(User::getId()).collect(Collectors.toList());

3. 排序操作

1
2
3
4
5
6
7
8
java复制代码//根据id进行排序
userList = userList.stream().sorted(User::getId()).collect(Collectors.toList());
//倒序 不加reversed 顺序
userList = list.stream()
.sorted(Comparator.comparing(User::getAge).reversed())
.collect(Collectors.toList());
//根据其它排序
userList = userList.stream().sorted((In1,In2) -> In1- In2).collect(Collectors.toList());

4. 判断操作

1
2
3
4
java复制代码//判断是否有名字为jack的 
boolean isExsit = userList.stream().anyMacth(s ->"jack".equals(s.getName()));
//判断某个字段是否全为空
boolean isEmpty = userList.stream().nonoMatch(s -> s.getEmail().isEmpty());

5. 对象集合分组去重

1
2
3
4
5
6
7
8
9
10
11
java复制代码Map<Integer, List<PurchaseUpdate>> maps = updates.stream().collect(Collectors.groupingBy(PurchaseUpdate::getWriteId, Collectors.toList()));
maps.forEach((k,v) ->{
v.forEach(update ->{
vo.setId(update.getId());
vo.setNumber(update.getCount());
purchaseService.update(vo);

build.append("xxxxx---------xxxxxx");
})

});

6. 对象集合抽取某个元素组成新的数据或者按照符号拼接

1
2
java复制代码 String currentIds = list.stream().map(p ->p.getCurrentUser() == null ? null : p.getCurrentUser().toString()).collect(Collectors.joining(","));
List<Integer> ids = list.stream().map(User::getId).collect(Collectors.toList());

7. 集合某个字段求和

1
2
3
4
5
java复制代码IntSummaryStatistics st = list.stream().mapToInt(Info::getAge).summaryStatistics();
System.out.println("求和:" + st.getSum());
System.out.println("平均数" + st.getAverage());
System.out.println("最大值:" + st.getMax());
System.out.println("总数:" +st.getCount());

8. 集合转Map

1
2
java复制代码Map<Integer,String> map = list.stream().collect(Collectors.toMap(Info::getAge ,Info::getName));
System.out.println(map.toString());

9. list转set

1
2
3
4
java复制代码Set<Info> collect = list.stream().collect(Collectors.toSet());
collect.forEach(e -> {
System.out.println(e.toString());
});

10. list中对对个字段计算并求和

计算所有订单总共销售额

1
2
3
4
5
6
7
8
java复制代码double  total = details.stream()
.filter(e -> e.getPrice() != null && e.getAmountActual() != null)
.reduce(0.0 ,
( x, y)-> x + (y.getPrice().doubleValue() * y.getAmountActual().doubleValue()) ,Double::sum)

//如果本身字段是 BigDecimal类型
BigDecimal ss = details.stream().reduce(BigDecimal.ZERO, (x, y) ->
x.add(y.getAmountActual().multiply(y.getPrice())), BigDecimal::add);

3.OkHttp3 请求, http请求

一个新的http客户端,使用简单,性能极好,可以完美代替HttpClient

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
java复制代码//get 请求 

OkHttpClient client = new OkHttpClient();

String run (String url) {
Request request = new Request.Builder()
.url(url)
.build();

try {
Response res = client.newCall(request).execute();
return res.body().string();
} catch (Exception e){
return e;
}
}

//Post请求
public static final MediaType JSON = MediaType.get("application/json;charset=utf-8");
String run (String url ,String json) {
RequestBody body = RequestBody.create(json,JSON);
Request request = new Request.Builder()
.url(url)
.body(body)
.build();
try {
Response res = client.newCall(request).execute();
return res.body().string();
}
}

4. 集合转符号分割的字符串

1
2
3
4
5
6
7
java复制代码List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
list.add("3");
String result = Joiner.on("-").join(list);

> 1-2-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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
java复制代码public List<WaterQualityDTO> getWaterQuality(String date, List<Integer> processIds) {
List<WaterQualityDTO> list = new ArrayList<>();
List<TaskOrderContent> taskList = contentRepository.findByTime(date,processIds);
//查询所有的化验指标数据
List<Indicators> indicators = indicatorsRepository.findAll();
Map<Long,Indicators> indicatMap = new HashMap<>();
if(!CollectionUtils.isEmpty(indicators)){
//list 转成Map 以id 和对象为 key和value indicatMap.putAll(indicators.stream().collect(Collectors.toMap(Indicators::getId, Function.identity())));
}
if(!CollectionUtils.isEmpty(taskList)){
//去重 合并
String ids = taskList.stream().map(TaskOrderContent::getProcessId)
.distinct()
.map(String::valueOf)
.collect(Collectors.joining(","));

Map<Integer, List<ProcessChainDTO>> processNames = processService.getProcessChainsByProcessIds(ids);

Map<Integer,List<TaskOrderContent>> group = taskList.stream().collect(
Collectors.groupingBy(TaskOrderContent ::getProcessId,Collectors.toList())
);
List<WaterQualityDTO.WaterRes> resList = new ArrayList<>();
group.forEach((k,v) ->{
resList.clear();
WaterQualityDTO waterAssay = new WaterQualityDTO();
String processName = processNames.get(k).stream().map(ProcessChainDTO::getName).collect(Collectors.joining("-"));
waterAssay.setPointName(processName);
waterAssay.setProcessId(k);
waterAssay.setTestDate(date);

v.stream().forEach(e ->{
WaterQualityDTO.WaterRes res = new WaterQualityDTO.WaterRes();
res.setCityName(processName);

res.setValue(e.getValue());

});

});
}


return null;
}

本文转载自: 掘金

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

操作系统-预防进程死锁的银行家算法-详细设计附源码 一、需求

发表于 2020-07-03

一、需求分析

1、程序设计的任务和目的

通过这次实验,加深对进程死锁的理解,进一步掌握进程资源的分配、死锁的检测和安全序列的生成方法。

2、输入的形式和输入值的范围

进程个数n,资源种类m,T0时刻各个进程的资源分配情况(可以运行输入,也可以在程序中设置)

3、输出的形式

如果安全,输出安全的进程序列,不安全则提示信息。

4、程序所能达到的功能

程序模拟预防进程死锁的银行家算法的工作过程。假设系统中有n个进程P1, … ,Pn,有m类可分配的资源R1, … ,Rm,在T0时刻,进程Pi分配到的j类资源为Allocationij个,它还需要j类资源Need ij个,系统目前剩余j类资源Workj个,现采用银行家算法进行进程资源分配预防死锁的发生。

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
34
35
复制代码请输入进程个数n:5
请输入资源种类m:3
请输入进程1的资源1的最大允许数量Max[1]:7
请输入进程1的资源2的最大允许数量Max[2]:5
请输入进程1的资源3的最大允许数量Max[3]:3
请输入进程1的资源1的已分配数量Allocation[1]:0
请输入进程1的资源2的已分配数量Allocation[2]:1
请输入进程1的资源3的已分配数量Allocation[3]:0
请输入进程2的资源1的最大允许数量Max[1]:3
请输入进程2的资源2的最大允许数量Max[2]:2
请输入进程2的资源3的最大允许数量Max[3]:2
请输入进程2的资源1的已分配数量Allocation[1]:2
请输入进程2的资源2的已分配数量Allocation[2]:0
请输入进程2的资源3的已分配数量Allocation[3]:0
请输入进程3的资源1的最大允许数量Max[1]:9
请输入进程3的资源2的最大允许数量Max[2]:0
请输入进程3的资源3的最大允许数量Max[3]:2
请输入进程3的资源1的已分配数量Allocation[1]:3
请输入进程3的资源2的已分配数量Allocation[2]:0
请输入进程3的资源3的已分配数量Allocation[3]:2
请输入进程4的资源1的最大允许数量Max[1]:2
请输入进程4的资源2的最大允许数量Max[2]:2
请输入进程4的资源3的最大允许数量Max[3]:2
请输入进程4的资源1的已分配数量Allocation[1]:2
请输入进程4的资源2的已分配数量Allocation[2]:1
请输入进程4的资源3的已分配数量Allocation[3]:1
请输入进程5的资源1的最大允许数量Max[1]:4
请输入进程5的资源2的最大允许数量Max[2]:3
请输入进程5的资源3的最大允许数量Max[3]:3
请输入进程5的资源1的已分配数量Allocation[1]:0
请输入进程5的资源2的已分配数量Allocation[2]:0
请输入进程5的资源3的已分配数量Allocation[3]:2
请输入可用资源1的可用数量:3
请输入可用资源2的可用数量:3
请输入可用资源3的可用数量:2

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
复制代码SafeOrder为:      进程   2      进程   4      进程   5      进程   1      进程   3

剩余资源情况为:
资源 1剩余数量: 3
资源 2剩余数量: 3
资源 3剩余数量: 2

是否还有新请求,若有请输入进程序号,若无请输入0:2
请输入进程2需求的资源1的数量:1
请输入进程2需求的资源2的数量:0
请输入进程2需求的资源3的数量:2
SafeOrder为: 进程 2 进程 4 进程 5 进程 1 进程 3

剩余资源情况为:
资源 1剩余数量: 2
资源 2剩余数量: 3
资源 3剩余数量: 0

错误用例(输入负值,影响正确结果)

输入

1
2
3
4
5
6
7
8
9
10
11
复制代码请输入进程个数n:1
请输入资源种类m:3
请输入进程1的资源1的最大允许数量Max[1]:1
请输入进程1的资源2的最大允许数量Max[2]:1
请输入进程1的资源3的最大允许数量Max[3]:1
请输入进程1的资源1的已分配数量Allocation[1]:-1
请输入进程1的资源2的已分配数量Allocation[2]:-1
请输入进程1的资源3的已分配数量Allocation[3]:-1
请输入可用资源1的可用数量:2
请输入可用资源2的可用数量:2
请输入可用资源3的可用数量:2

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
复制代码SafeOrder为:      进程   1

剩余资源情况为:
资源 1剩余数量: 2
资源 2剩余数量: 2
资源 3剩余数量: 2

是否还有新请求,若有请输入进程序号,若无请输入0:1
请输入进程1需求的资源1的数量:2
请输入进程1需求的资源2的数量:2
请输入进程1需求的资源3的数量:2
SafeOrder为: 进程 1

剩余资源情况为:
资源 1剩余数量: 0
资源 2剩余数量: 0
资源 3剩余数量: 0

二、概要设计

1、抽象数据类型的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
复制代码int n;//进程个数n
int m;//资源种类m
int Available[MaxNumber];//可用资源数量
int Request[MaxNumber];//进程请求的资源数量
int SafeOrder[MaxNumber];//安全进程序列

//定义进程的数据结构
typedef struct {
int Max[MaxNumber];
int Allocation[MaxNumber];
int Need[MaxNumber];
bool Finished;//完成状态
} Progress;

2、主程序的流程

1
2
3
4
5
6
7
复制代码int main() {
BankerAlgorithm bankerAlgorithm{};
bankerAlgorithm.Input();
bankerAlgorithm.Order(bankerAlgorithm.progress, bankerAlgorithm.Available);

return 0;
}

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
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
复制代码int main() {
BankerAlgorithm bankerAlgorithm{};
bankerAlgorithm.Input();
bankerAlgorithm.Order(bankerAlgorithm.progress, bankerAlgorithm.Available);

return 0;
}

//输入函数调用InputAlgorithm函数选择输入函数
void Input() {
···
}

//进行安全进程检测并输出安全序列
void Order(Progress pg[MaxNumber], int a[MaxNumber]) {
//定义下标数
int orderNum = 0;

//复制进程操作
Progress progressCopy[MaxNumber];
for (int i = 1; i <= n; i++) {
progressCopy[i] = pg[i];
}

//复制可用需求数
int available[MaxNumber];
for (int i = 1; i <= m; i++) {
available[i] = a[i];
}

//调用NewFinish函数判断所有进程是否完成
while (!NewFinish(progressCopy)) {
//调用IsSafe函数判断现在是否安全
if (IsSafe(progressCopy, available)) {
for (int i = 1; i <= n; i++) {
if (!progressCopy[i].Finished &&
//调用IsExecutable函数判断可分配资源可否满足该进程
IsExecutable(progressCopy[i], available)) {
//只有同时满足进程未完成、可分配需求足够才进行分配
···
}
}
} else {
cout << "不安全" << endl;
exit(0);
}
}

//输出SafeOrder
···

NewRequest(pg, a);
}

//判断是否有新请求,若无则退出,若有则输入其请求资源数
void NewRequest(Progress pg[MaxNumber], int a[MaxNumber]) {
···

//输出剩余资源数量参考
···

//判断新请求,若有可用新请求,调用Order函数重新进行银行家算法安全计算
cout << endl << "是否还有新请求,若有请输入进程序号,若无请输入0:";
cin >> newRequest;
if (newRequest != 0) {
for (int i = 1; i <= m; i++) {
//若可分配资源不足或超出需求,则重新调用NewRequest函数
NewRequest(pg, a);
}
···
Order(pg, a);
} else {
return;
}
}

//判断所有进程是否完成
bool NewFinish(Progress pg[MaxNumber]) {
···
}

//判断可分配资源可否满足该进程
bool IsExecutable(Progress pg, const int a[MaxNumber]) {
···
}

//判断现在是否安全
bool IsSafe(Progress pg[MaxNumber], int a[MaxNumber]) {
···
}

三、详细设计

实现程序模块的具体算法

1、Order银行家算法排序

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
复制代码//进行安全进程检测并输出安全序列
void Order(Progress pg[MaxNumber], int a[MaxNumber]) {
//定义下标数
int orderNum = 0;

//复制进程操作
···

//复制可用需求数
···

//调用NewFinish函数判断所有进程是否完成
while (!NewFinish(progressCopy)) {
//调用IsSafe函数判断现在是否安全
if (IsSafe(progressCopy, available)) {
for (int i = 1; i <= n; i++) {
if (!progressCopy[i].Finished &&
//调用IsExecutable函数判断可分配资源可否满足该进程
IsExecutable(progressCopy[i], available)) {
//只有同时满足进程未完成、可分配需求足够才进行分配
···
}
}
} else {
cout << "不安全" << endl;
exit(0);
}
}

//输出SafeOrder
···
//若为安全状态,重新调用NewRequest函数请求下一个需求输入
NewRequest(pg, a);
}

2、NewRequest发起新请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
复制代码//判断是否有新请求,若无则退出,若有则输入其请求资源数
void NewRequest(Progress pg[MaxNumber], int a[MaxNumber]) {
···

//输出剩余资源数量参考
···

//判断新请求,若有可用新请求,调用Order函数重新进行银行家算法安全计算
cout << endl << "是否还有新请求,若有请输入进程序号,若无请输入0:";
cin >> newRequest;
if (newRequest != 0) {
for (int i = 1; i <= m; i++) {
//若可分配资源不足或超出需求,则重新调用NewRequest函数
NewRequest(pg, a);
}
···
Order(pg, a);
} else {
return;
}
}

3、NewFinish函数判断进程完成

1
2
3
4
5
6
7
8
复制代码//判断所有进程是否完成
bool NewFinish(Progress pg[MaxNumber]) {
bool finished = true;
for (int i = 1; i <= n; i++) {
finished *= pg[i].Finished;
}
return finished;
}

4、IsExecutable函数判断资源充足与否

1
2
3
4
5
6
7
8
复制代码//判断可分配资源可否满足该进程
bool IsExecutable(Progress pg, const int a[MaxNumber]) {
bool isExecutable = true;
for (int i = 1; i <= m; i++) {
isExecutable *= (pg.Need[i] <= a[i]);
}
return isExecutable;
}

5、IsSafe函数判断当前进程是否安全

1
2
3
4
5
6
7
8
9
10
11
复制代码//判断现在是否安全
bool IsSafe(Progress pg[MaxNumber], int a[MaxNumber]) {
bool isExecutable = false;
for (int i = 1; i <= n; i++) {
if (IsExecutable(pg[i], a)) {
isExecutable = true;
return isExecutable;
}
}
return isExecutable;
}

四、调试分析

调试过程中遇到的问题以及解决方法,设计与实现的回顾讨论和分析

算法的性能分析(包括基本操作和其它算法的时间复杂度和空间复杂度的分析)及其改进设想

性能分析

算法 时间复杂度 空间复杂度
Order算法安全进程检测 T(n) = O(n2) S(n) = O(n)
NewRequest算法请求资源 T(n) = O(n2) S(n) = O(n2)
NewFinish算法判断所有进程是否完成 T(n) = O(n) S(n) = O(n)
IsExecutable算法判断可分配资源可否满足该进程 T(n) = O(n) S(n) = O(n)
IsSafe算法判断进程是否处于安全状态 T(n) = O(n) S(n) = O(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
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
复制代码D:\Documents\MyCourse\OperatingSystem\cmake-build-debug\chapter03.exe
请输入进程个数n:5
请输入资源种类m:3
请输入进程1的资源1的最大允许数量Max[1]:7
请输入进程1的资源2的最大允许数量Max[2]:5
请输入进程1的资源3的最大允许数量Max[3]:3
请输入进程1的资源1的已分配数量Allocation[1]:0
请输入进程1的资源2的已分配数量Allocation[2]:1
请输入进程1的资源3的已分配数量Allocation[3]:0
请输入进程2的资源1的最大允许数量Max[1]:3
请输入进程2的资源2的最大允许数量Max[2]:2
请输入进程2的资源3的最大允许数量Max[3]:2
请输入进程2的资源1的已分配数量Allocation[1]:2
请输入进程2的资源2的已分配数量Allocation[2]:0
请输入进程2的资源3的已分配数量Allocation[3]:0
请输入进程3的资源1的最大允许数量Max[1]:9
请输入进程3的资源2的最大允许数量Max[2]:0
请输入进程3的资源3的最大允许数量Max[3]:2
请输入进程3的资源1的已分配数量Allocation[1]:3
请输入进程3的资源2的已分配数量Allocation[2]:0
请输入进程3的资源3的已分配数量Allocation[3]:2
请输入进程4的资源1的最大允许数量Max[1]:2
请输入进程4的资源2的最大允许数量Max[2]:2
请输入进程4的资源3的最大允许数量Max[3]:2
请输入进程4的资源1的已分配数量Allocation[1]:2
请输入进程4的资源2的已分配数量Allocation[2]:1
请输入进程4的资源3的已分配数量Allocation[3]:1
请输入进程5的资源1的最大允许数量Max[1]:4
请输入进程5的资源2的最大允许数量Max[2]:3
请输入进程5的资源3的最大允许数量Max[3]:3
请输入进程5的资源1的已分配数量Allocation[1]:0
请输入进程5的资源2的已分配数量Allocation[2]:0
请输入进程5的资源3的已分配数量Allocation[3]:2
请输入可用资源1的可用数量:3
请输入可用资源2的可用数量:3
请输入可用资源3的可用数量:2

SafeOrder为: 进程 2 进程 4 进程 5 进程 1 进程 3

剩余资源情况为:
资源 1剩余数量: 3
资源 2剩余数量: 3
资源 3剩余数量: 2

是否还有新请求,若有请输入进程序号,若无请输入0:2
请输入进程2需求的资源1的数量:1
请输入进程2需求的资源2的数量:0
请输入进程2需求的资源3的数量:2
SafeOrder为: 进程 2 进程 4 进程 5 进程 1 进程 3

剩余资源情况为:
资源 1剩余数量: 2
资源 2剩余数量: 3
资源 3剩余数量: 0

是否还有新请求,若有请输入进程序号,若无请输入0:4
请输入进程4需求的资源1的数量:1
超出需求,请重新输入


剩余资源情况为:
资源 1剩余数量: 2
资源 2剩余数量: 3
资源 3剩余数量: 0

是否还有新请求,若有请输入进程序号,若无请输入0:5
请输入进程5需求的资源1的数量:1
请输入进程5需求的资源2的数量:3
请输入进程5需求的资源3的数量:0
不安全

Process finished with exit code 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
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
复制代码#include <iostream>
#include <iomanip>

using namespace std;
#define MaxNumber 100

class BankerAlgorithm {
public:

int n;//进程个数n
int m;//资源种类m
int Available[MaxNumber];//可用资源数量
int Request[MaxNumber];//进程请求的资源数量
int SafeOrder[MaxNumber];//安全进程序列

//定义进程的数据结构
typedef struct {
int Max[MaxNumber];
int Allocation[MaxNumber];
int Need[MaxNumber];
bool Finished;//完成状态
} Progress;

Progress progress[MaxNumber];

//输入进程数、资源种类、各进程有关资源的Max、Allocation、Need数量
void Input() {
cout << "请输入进程个数n:";
cin >> n;
cout << "请输入资源种类m:";
cin >> m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cout << "请输入进程" << i << "的资源" << j << "的最大允许数量Max[" << j << "]:";
cin >> progress[i].Max[j];
}
for (int j = 1; j <= m; j++) {
cout << "请输入进程" << i << "的资源" << j << "的已分配数量Allocation[" << j << "]:";
cin >> progress[i].Allocation[j];
}
for (int j = 1; j <= m; j++) {
progress[i].Need[j] = progress[i].Max[j] - progress[i].Allocation[j];
}
}

for (int i = 1; i <= m; i++) {
cout << "请输入可用资源" << i << "的可用数量:";
cin >> Available[i];
}

cout << endl;
}

//判断是否有新请求,若无则退出,若有则输入其请求资源数
void NewRequest(Progress pg[MaxNumber], int a[MaxNumber]) {
int newRequest;

//剩余资源数量参考
cout << endl << endl << "剩余资源情况为:" << endl;
for (int i = 1; i <= m; i++) {
cout << setw(6) << "资源" << setw(2) << i << setw(6) << "剩余数量:" << setw(2) << a[i] << endl;
}

//判断新请求
cout << endl << "是否还有新请求,若有请输入进程序号,若无请输入0:";
cin >> newRequest;
if (newRequest != 0) {
for (int i = 1; i <= m; i++) {
cout << "请输入进程" << newRequest << "需求的资源" << i << "的数量:";
cin >> Request[i];
if (Request[i] > a[i]) {
cout << "可分配资源不足,请重新输入" << endl;
NewRequest(pg, a);
}
if (Request[i] > pg[newRequest].Need[i]) {
cout << "超出需求,请重新输入" << endl;
NewRequest(pg, a);
}
}

for (int i = 1; i <= m; i++) {
a[i] -= Request[i];
pg[newRequest].Allocation[i] += Request[i];
pg[newRequest].Need[i] -= Request[i];
}
Order(pg, a);
} else {
return;
}
}

void Order(Progress pg[MaxNumber], int a[MaxNumber]) {
//定义下标数
int orderNum = 0;

//复制进程操作
Progress progressCopy[MaxNumber];
for (int i = 1; i <= n; i++) {
progressCopy[i] = pg[i];
}

//复制可用需求数
int available[MaxNumber];
for (int i = 1; i <= m; i++) {
available[i] = a[i];
}

while (!NewFinish(progressCopy)) {//若有
if (IsSafe(progressCopy, available)) {
for (int i = 1; i <= n; i++) {
if (!progressCopy[i].Finished &&
IsExecutable(progressCopy[i], available)) {//只有同时满足进程未完成、可分配需求足够才进行分配
progressCopy[i].Finished = true;
for (int j = 1; j <= m; j++) {
available[j] += progressCopy[i].Allocation[j];
}
SafeOrder[++orderNum] = i;
}
}
} else {
cout << "不安全" << endl;
exit(0);
}
}

//输出SafeOrder
cout << "SafeOrder为:";
for (int i = 1; i <= n; i++) {
cout << setw(12) << "进程" << setw(4) << SafeOrder[i];
}

NewRequest(pg, a);
}

//判断所有进程是否完成
bool NewFinish(Progress pg[MaxNumber]) {
bool finished = true;
for (int i = 1; i <= n; i++) {
finished *= pg[i].Finished;
}
return finished;
}

//判断可分配资源可否满足该进程
bool IsExecutable(Progress pg, const int a[MaxNumber]) {
bool isExecutable = true;
for (int i = 1; i <= m; i++) {
isExecutable *= (pg.Need[i] <= a[i]);
}
return isExecutable;
}

//判断现在是否安全
bool IsSafe(Progress pg[MaxNumber], int a[MaxNumber]) {
bool isExecutable = false;
for (int i = 1; i <= n; i++) {
if (IsExecutable(pg[i], a)) {
isExecutable = true;
return isExecutable;
}
}
return isExecutable;
}
};

int main() {
BankerAlgorithm bankerAlgorithm{};
bankerAlgorithm.Input();
bankerAlgorithm.Order(bankerAlgorithm.progress, bankerAlgorithm.Available);

return 0;
}

本文转载自: 掘金

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

grep、sed、awk高效文件处理3剑客

发表于 2020-07-03

grep、sed、awk我们叫他们三剑客,掌握它们可以更好的运维,提升工作效率,即使不是运维,对我们处理数据都是非常方便的~就很多数据处理来讲,写程序肯定是也能处理的,但是远没有已经存在特定功能的命令更高效,我们只需要操作命令即可。通过本文可以讲解三剑客的一些基础知识和实用,希望大家可以自己动手敲,毕竟自己体会过的印象更深刻,后面还会持续更新。。。

grep

简介

grep是一款强大的文本搜索工具,支持正则表达式。

全称( global search regular expression(RE) and print out the line)

语法:grep [option]… PATTERN [FILE]…

常用:

1
2
3
4
复制代码usage: grep [-abcDEFGHhIiJLlmnOoqRSsUVvwxZ] [-A num] [-B num] [-C[num]]
[-e pattern] [-f file] [--binary-files=value] [--color=when]
[--context[=num]] [--directories=action] [--label] [--line-buffered]
[--null] [pattern] [file ...]

常用参数:

1
2
3
4
5
6
7
复制代码            -v        取反
-i 忽略大小写
-c 符合条件的行数
-n 输出的同时打印行号
^* 以*开头
*$ 以*结尾
^$ 空行

实际使用

准备好一个小故事txt:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
复制代码[root@iz2ze76ybn73dvwmdij06zz ~]# cat monkey
One day,a little monkey is playing by the well.一天,有只小猴子在井边玩儿.
He looks in the well and shouts :它往井里一瞧,高喊道:
“Oh!My god!The moon has fallen into the well!” “噢!我的天!月亮掉到井里头啦!”
An older monkeys runs over,takes a look,and says,一只大猴子跑来一看,说,
“Goodness me!The moon is really in the water!” “糟啦!月亮掉在井里头啦!”
And olderly monkey comes over.老猴子也跑过来.
He is very surprised as well and cries out:他也非常惊奇,喊道:
“The moon is in the well.” “糟了,月亮掉在井里头了!”
A group of monkeys run over to the well .一群猴子跑到井边来,
They look at the moon in the well and shout:他们看到井里的月亮,喊道:
“The moon did fall into the well!Come on!Let’get it out!”
“月亮掉在井里头啦!快来!让我们把它捞起来!”
Then,the oldest monkey hangs on the tree up side down ,with his feet on the branch .
然后,老猴子倒挂在大树上,
And he pulls the next monkey’s feet with his hands.拉住大猴子的脚,
All the other monkeys follow his suit,其他的猴子一个个跟着,
And they join each other one by one down to the moon in the well.
它们一只连着一只直到井里.
Just before they reach the moon,the oldest monkey raises his head and happens to see the moon in the sky,正好他们摸到月亮的时候,老猴子抬头发现月亮挂在天上呢
He yells excitedly “Don’t be so foolish!The moon is still in the sky!”
它兴奋地大叫:“别蠢了!月亮还好好地挂在天上呢!
直接查找符合条件的行
1
2
3
4
5
6
7
8
9
复制代码[root@iz2ze76ybn73dvwmdij06zz ~]# grep moon monkey
“Oh!My god!The moon has fallen into the well!” “噢!我的天!月亮掉到井里头啦!”
“Goodness me!The moon is really in the water!” “糟啦!月亮掉在井里头啦!”
“The moon is in the well.” “糟了,月亮掉在井里头了!”
They look at the moon in the well and shout:他们看到井里的月亮,喊道:
“The moon did fall into the well!Come on!Let’get it out!”
And they join each other one by one down to the moon in the well.
Just before they reach the moon,the oldest monkey raises his head and happens to see the moon in the sky,正好他们摸到月亮的时候,老猴子抬头发现月亮挂在天上呢
He yells excitedly “Don’t be so foolish!The moon is still in the sky!”
查找反向符合条件的行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
复制代码[root@iz2ze76ybn73dvwmdij06zz ~]# grep -v  moon monkey
One day,a little monkey is playing by the well.一天,有只小猴子在井边玩儿.
He looks in the well and shouts :它往井里一瞧,高喊道:
An older monkeys runs over,takes a look,and says,一只大猴子跑来一看,说,
And olderly monkey comes over.老猴子也跑过来.
He is very surprised as well and cries out:他也非常惊奇,喊道:
A group of monkeys run over to the well .一群猴子跑到井边来,
“月亮掉在井里头啦!快来!让我们把它捞起来!”
Then,the oldest monkey hangs on the tree up side down ,with his feet on the branch .
然后,老猴子倒挂在大树上,
And he pulls the next monkey’s feet with his hands.拉住大猴子的脚,
All the other monkeys follow his suit,其他的猴子一个个跟着,
它们一只连着一只直到井里.
它兴奋地大叫:“别蠢了!月亮还好好地挂在天上呢!”
直接查找符合条件的行数
1
2
复制代码[root@iz2ze76ybn73dvwmdij06zz ~]# grep -c  moon monkey
8
忽略大小写查找符合条件的行数

先来看一下直接查找的结果

1
复制代码[root@iz2ze76ybn73dvwmdij06zz ~]# grep my monkey

忽略大小写查看

1
2
复制代码[root@iz2ze76ybn73dvwmdij06zz ~]# grep -i my monkey
“Oh!My god!The moon has fallen into the well!” “噢!我的天!月亮掉到井里头啦!”
查找符合条件的行并输出行号
1
2
3
4
5
6
7
8
9
复制代码[root@iz2ze76ybn73dvwmdij06zz ~]# grep -n monkey monkey
1:One day,a little monkey is playing by the well.一天,有只小猴子在井边玩儿.
4:An older monkeys runs over,takes a look,and says,一只大猴子跑来一看,说,
6:And olderly monkey comes over.老猴子也跑过来.
9:A group of monkeys run over to the well .一群猴子跑到井边来,
13:Then,the oldest monkey hangs on the tree up side down ,with his feet on the branch .
15:And he pulls the next monkey’s feet with his hands.拉住大猴子的脚,
16:All the other monkeys follow his suit,其他的猴子一个个跟着,
19:Just before they reach the moon,the oldest monkey raises his head and happens to see the moon in the sky,正好他们摸到月亮的时候,老猴子抬头发现月亮挂在天上呢
查找开头是J的行
1
2
复制代码[root@iz2ze76ybn73dvwmdij06zz ~]# grep '^J' monkey
Just before they reach the moon,the oldest monkey raises his head and happens to see the moon in the sky,正好他们摸到月亮的时候,老猴子抬头发现月亮挂在天上呢
查找结尾是呢的行
1
2
复制代码[root@iz2ze76ybn73dvwmdij06zz ~]# grep "呢$" monkey
Just before they reach the moon,the oldest monkey raises his head and happens to see the moon in the sky,正好他们摸到月亮的时候,老猴子抬头发现月亮挂在天上呢

大家可以grep –help,查看更多相关的命令,这里就不一一演示了。

小结

有了强大的网络以后,很多东西都可以在网上找到,但是基础的一定要自己
熟练掌握,才回在遇到事情的时候不慌。

sed

sed是一种流编辑器,是一款处理文本比较优秀的工具,可以结合正则表达式一起使用。

sed执行过程

sed命令

命令: sed

语法 : sed [选项]… {命令集} [输入文件]…

常用命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
复制代码            d  删除选择的行    
s 查找
y 替换
i 当前行前面插入一行
a 当前行后面插入一行
p 打印行
q 退出

替换符:

数字 :替换第几处
g : 全局替换
\1: 子串匹配标记,前面搜索可以用元字符集\(..\)
&: 保留搜索刀的字符用来替换其他字符

操作:

替换

查看文件:

1
2
3
4
5
6
复制代码➜  happy cat word
Twinkle, twinkle, little star
How I wonder what you are
Up above the world so high
Like a diamond in the sky
When the blazing sun is gone

替换:

1
2
3
4
5
6
复制代码➜  happy sed 's/little/big/' word
Twinkle, twinkle, big star
How I wonder what you are
Up above the world so high
Like a diamond in the sky
When the blazing sun is gone

查看文本:

1
2
3
4
5
6
7
复制代码➜  happy cat word1
Oh if there's one thing to be taught
it's dreams are made to be caught
and friends can never be bought
Doesn't matter how long it's been
I know you'll always jump in
'Cause we don't know how to quit

全局替换:

1
2
3
4
5
6
7
复制代码➜  happy sed 's/to/can/g' word1
Oh if there's one thing can be taught
it's dreams are made can be caught
and friends can never be bought
Doesn't matter how long it's been
I know you'll always jump in
'Cause we don't know how can quit

按行替换(替换2到最后一行)

1
2
3
4
5
6
7
复制代码➜  happy sed '2,$s/to/can/' word1
Oh if there's one thing to be taught
it's dreams are made can be caught
and friends can never be bought
Doesn't matter how long it's been
I know you'll always jump in
'Cause we don't know how can quit
删除:

查看文本:

1
2
3
4
5
6
复制代码➜  happy cat word
Twinkle, twinkle, little star
How I wonder what you are
Up above the world so high
Like a diamond in the sky
When the blazing sun is gone

删除:

1
2
3
4
5
复制代码➜  happy sed '2d' word
Twinkle, twinkle, little star
Up above the world so high
Like a diamond in the sky
When the blazing sun is gone

显示行号:

1
2
3
4
5
6
7
8
9
10
复制代码➜  happy sed '=;2d' word
1
Twinkle, twinkle, little star
2
3
Up above the world so high
4
Like a diamond in the sky
5
When the blazing sun is gone

删除第2行到第四行:

1
2
3
4
5
6
7
8
复制代码➜  happy sed '=;2,4d' word
1
Twinkle, twinkle, little star
2
3
4
5
When the blazing sun is gone
添加行:

向前插入:

1
2
3
复制代码➜  happy echo "hello" | sed 'i\kitty'
kitty
hello

向后插入:

1
2
3
复制代码➜  happy echo "kitty" | sed 'i\hello'
hello
kitty
修改行:

替换第二行为hello kitty

1
2
3
4
5
6
复制代码➜  happy sed '2c\hello kitty' word
Twinkle, twinkle, little star
hello kitty
Up above the world so high
Like a diamond in the sky
When the blazing sun is gone

替换第二行到最后一行为hello kitty

1
2
3
复制代码➜  happy sed '2,$c\hello kitty' word
Twinkle, twinkle, little star
hello kitty
写入行

把带star的行写入c文件中,c提前创建

1
2
3
复制代码➜  happy sed -n '/star/w c' word
➜ happy cat c
Twinkle, twinkle, little star
退出

打印3行后,退出sed

1
2
3
4
复制代码➜  happy sed '3q' word
Twinkle, twinkle, little star
How I wonder what you are
Up above the world so high

awk

名字由来

创始人 Alfred Aho 、Peter Weinberger 和 Brian Kernighan 姓氏的首个字母。

强大的文本处理工具

比起sed和grep,awk不仅仅是一个小工具,也可以算得上一种小型的编程语言了,支持if判断分支和while循环语句还有它的内置函数等,是一个要比grep和sed更强大的文本处理工具,但也就意味着要学习的东西更多了。

下面来说一下awk的一些基础概念以及实际操作。

语法

常用

Usage: awk [POSIX or GNU style options] -f progfile [–] file …

Usage: awk [POSIX or GNU style options] [–] ‘program’ file …

域

类似数据库列的概念,但它是按照序号来指定的,比如我要第一个列就是2,依此类推。$0就是输出整个文本的内容。默认用空格作为分隔符,当然你可以自己通过-F设置适合自己情况的分隔符。

提前自己编了一段数据,学生以及学生成绩数据表。

列数 名称 描述
1 Name 姓名
2 Math 数学
3 Chinese 语文
4 English 英语
5 History 历史
6 Sport 体育
8 Grade 班级

“Name Math Chinese English History Sport grade
输出整个文本

1
2
3
4
5
复制代码[root@iz2ze76ybn73dvwmdij06zz ~]# awk '{print $0}' students_store
Xiaoka 60 80 40 90 77 class-1
Yizhihua 70 66 50 80 90 class-1
kerwin 80 90 60 70 60 class-2
Fengzheng 90 78 62 40 62 class-2

输出第一列(姓名列)

1
2
3
4
5
复制代码[root@iz2ze76ybn73dvwmdij06zz ~]# awk '{print $1}' students_store
Xiaoka
Yizhihua
kerwin
Fengzheng

模式&动作

1
复制代码awk '{[pattern] action}' {filenames}

模式

pattern
可以是

  • 条件语句
  • 正则

模式的两个特殊字段 BEGIN 和 END (不指定时匹配或打印行数)

  • BEGIN : 一般用来打印列名称。
  • END : 一般用来打印总结性质的字符。

动作

action 在{}内指定,一般用来打印,也可以是一个代码段。

示例

给上面的文本加入标题头:

1
2
3
4
5
6
7
8
复制代码[root@iz2ze76ybn73dvwmdij06zz ~]#  awk 'BEGIN {print "Name     Math  Chinese  English History  Sport grade\n----------------------------------------------"} {print $0}' students_store

Name Math Chinese English History Sport grade
----------------------------------------------------------
Xiaoka 60 80 40 90 77 class-1
Yizhihua 70 66 50 80 90 class-1
kerwin 80 90 60 70 60 class-2
Fengzheng 90 78 62 40 62 class-2

仅打印姓名、数学成绩、班级信息,再加一个文尾(再接再厉):

1
2
3
4
5
6
7
8
9
复制代码[root@iz2ze76ybn73dvwmdij06zz ~]# awk 'BEGIN {print "Name   Math  grade\n---------------------"} {print $1 2 "\t" $7} END {print "continue to exert oneself"}' students_store

Name Math grade
---------------------
Xiaoka 60 class-1
Yizhihua 70 class-1
kerwin 80 class-2
Fengzheng 90 class-2
continue to exert oneself

结合正则

像grep和sed也是支持正则表达式的。这边就不介绍正则表达式了,如果有兴趣,我单出一个文章。

使用方法:

符号 ~ 后接正则表达式

此时我们再加入一条后来的新同学,并且没有分班。

先来看下现在的数据

1
2
3
4
5
6
复制代码[root@iz2ze76ybn73dvwmdij06zz ~]# cat students_store
Xiaoka 60 80 40 90 77 class-1
Yizhihua 70 66 50 80 90 class-1
kerwin 80 90 60 70 60 class-2
Fengzheng 90 78 62 40 62 class-2
xman - - - - - -

模糊匹配|查询已经分班的学生

1
2
3
4
5
复制代码[root@iz2ze76ybn73dvwmdij06zz ~]# awk '$0 ~/class/' students_store
Xiaoka 60 80 40 90 77 class-1
Yizhihua 70 66 50 80 90 class-1
kerwin 80 90 60 70 60 class-2
Fengzheng 90 78 62 40 62 class-2

精准匹配|查询1班的学生

1
2
3
复制代码[root@iz2ze76ybn73dvwmdij06zz ~]# awk '$7=="class-1" {print $0}'  students_store
Xiaoka 60 80 40 90 77 class-1
Yizhihua 70 66 50 80 90 class-1

反向匹配|查询不是1班的学生

1
2
3
4
复制代码[root@iz2ze76ybn73dvwmdij06zz ~]# awk '$7!="class-1" {print $0}'  students_store
kerwin 80 90 60 70 60 class-2
Fengzheng 90 78 62 40 62 class-2
xman - - - - - -

比较操作

查询数学大于80的

1
2
3
4
复制代码[root@iz2ze76ybn73dvwmdij06zz ~]# awk '$2>60 {print $0}'  students_store
Yizhihua 70 66 50 80 90 class-1
kerwin 80 90 60 70 60 class-2
Fengzheng 90 78 62 40 62 class-2

查询数学大于英语成绩的

1
2
3
4
5
复制代码[root@iz2ze76ybn73dvwmdij06zz ~]# awk '$2 > $4  {print $0}'  students_store
Xiaoka 60 80 40 90 77 class-1
Yizhihua 70 66 50 80 90 class-1
kerwin 80 90 60 70 60 class-2
Fengzheng 90 78 62 40 62 class-2

匹配指定字符中的任意字符

在加一列专业,让我们来看看憨憨们的专业,顺便给最后一个新来的同学分个班吧。

然后再来看下此时的数据。

1
2
3
4
5
6
复制代码[root@iz2ze76ybn73dvwmdij06zz ~]# cat students_store
Xiaoka 60 80 40 90 77 class-1 Java
Yizhihua 70 66 50 80 90 class-1 java
kerwin 80 90 60 70 60 class-2 Java
Fengzheng 90 78 62 40 62 class-2 java
xman - - - - - class-3 php

或关系匹配|查询1班和3班的学生

1
2
3
4
复制代码root@iz2ze76ybn73dvwmdij06zz ~]# awk '$0 ~/(class-1|class-3)/' students_store
Xiaoka 60 80 40 90 77 class-1 Java
Yizhihua 70 66 50 80 90 class-1 java
xman - - - - - class-3 php

任意字符匹配|名字第二个字母是

字符解释:

^ : 字段或记录的开头。

. : 任意字符。

1
2
3
4
复制代码root@iz2ze76ybn73dvwmdij06zz ~]# awk '$0 ~/(class-1|class-3)/' students_store
Xiaoka 60 80 40 90 77 class-1 Java
Yizhihua 70 66 50 80 90 class-1 java
xman - - - - - class-3 php

复合表达式

&& AND

的关系,必同时满足才行哦~

查询数学成绩大于60并且语文成绩也大于60的童鞋。

1
2
3
4
复制代码[root@iz2ze76ybn73dvwmdij06zz ~]# awk '{ if ($2 > 60 && $3 > 60) print $0}' students_store
Yizhihua 70 66 50 80 90 class-1 java
kerwin 80 90 60 70 60 class-2 Java
Fengzheng 90 78 62 40 62 class-2 java

|| OR

查询数学大于80或者语文大于80的童鞋。

1
2
复制代码[root@iz2ze76ybn73dvwmdij06zz ~]#  awk '{ if ($2 > 80 || $4 > 80) print $0}' students_store
Fengzheng 90 78 62 40 62 class-2 java

printf 格式化输出

除了能达到功能以外,一个好看的格式也是必不可少的,因此格式化的输出看起来会更舒服哦~

语法

printf ([格式],参数)

printf %x(格式) 具体参数 x代表具体格式

符号 说明
- 左对齐
Width 域的步长
.prec 最大字符串长度或小数点右边位数

格式转化符

其实和其他语言大同小异的

常用格式

符号 描述
%c ASCII
%d 整数
%o 八进制
%x 十六进制数
%f 浮点数
%e 浮点数(科学记数法)
% s 字符串
%g 决定使用浮点转化e/f

具体操作示例

ASCII码🐎

1
2
复制代码[root@iz2ze76ybn73dvwmdij06zz ~]# echo "66" | awk '{printf "%c\n",$0}'
B

浮点数

1
2
复制代码[root@iz2ze76ybn73dvwmdij06zz ~]# awk 'BEGIN {printf "%f\n",100}'
100.000000

16进制数

1
2
复制代码[root@iz2ze76ybn73dvwmdij06zz ~]# awk 'BEGIN {printf "%x",996}'
3e4

更多操作,小伙伴有兴趣可以挨个试试~

内置变量

频率较高常用内置变量

NF : 记录浏览域的个数,在记录被读后设置。

NR : 已读的记录数。

FS : 设置输入域分隔符

A R G C : 命令行参数个数,支持命令行传入。

RS : 控制记录分隔符

FIlENAME : awk当前读文件的名称

操作

输出学生成绩表和域个数以及已读记录数。

1
2
3
4
5
6
复制代码[root@iz2ze76ybn73dvwmdij06zz ~]# awk '{print $0, NF , NR}' students_store
Xiaoka 60 80 40 90 77 class-1 Java 8 1
Yizhihua 70 66 50 80 90 class-1 java 8 2
kerwin 80 90 60 70 60 class-2 Java 8 3
Fengzheng 90 78 62 40 62 class-2 java 8 4
xman - - - - - class-3 php 8 5

内置函数

常用函数

length(s) 返回s长度

index(s,t) 返回s中字符串t第一次出现的位置

match (s,r) s中是否包含r字符串

split(s,a,fs) 在fs上将s分成序列a

gsub(r,s) 用s代替r,范围全文本

gsub(r,s,t) 范围t中,s代替r

substr(s,p) 返回字符串s从第p个位置开始后面的部分(下标是从1 开始算的,大家可以自己试试)

substr(s,p,n) 返回字符串s从第p个位置开始后面n个字符串的部分

操作

length
1
2
复制代码[root@iz2ze76ybn73dvwmdij06zz ~]# awk 'BEGIN {print length(" hello,im xiaoka")}'
16
index
1
2
复制代码[root@iz2ze76ybn73dvwmdij06zz ~]# awk 'BEGIN {print index("xiaoka","ok")}'
4
match
1
2
复制代码[root@iz2ze76ybn73dvwmdij06zz ~]# awk 'BEGIN {print match("Java小咖秀","va小")}'
3
gsub
1
2
复制代码[root@iz2ze76ybn73dvwmdij06zz ~]# awk 'gsub("Xiaoka","xk") {print $0}' students_store
xk 60 80 40 90 77 class-1 Java
substr(s,p)
1
2
复制代码[root@iz2ze76ybn73dvwmdij06zz ~]# awk 'BEGIN {print substr("xiaoka",3)}'
aoka
substr(s,p,n)
1
2
复制代码[root@iz2ze76ybn73dvwmdij06zz ~]# awk 'BEGIN {print substr("xiaoka",3,2)}'
ao
split
1
2
3
4
5
6
复制代码[root@iz2ze76ybn73dvwmdij06zz ~]# str="java,xiao,ka,xiu"
[root@iz2ze76ybn73dvwmdij06zz ~]# awk 'BEGIN{split('"\"$str\""',ary,","); for(i in ary) {if(ary[i]>1) print ary[i]}}'
xiu
java
xiao
ka

awk脚本

前面说过awk是可以说是一个小型编程语言。如果命令比较短我们可以直接在命令行执行,当命令行比较长的时候,可以使用脚本来处理,比命令行的可读性更高,还可以加上注释。

写一个完整的awk脚本并执行步骤

1.先创建一个awk文件
1
复制代码[root@iz2ze76ybn73dvwmdij06zz ~]# vim printname.awk
2.脚本第一行要指定解释器
1
复制代码#!/usr/bin/awk -f
3.编写脚本内容,打印一下名称
1
2
3
4
复制代码[root@iz2ze76ybn73dvwmdij06zz ~]# cat printname.awk
#!/usr/bin/awk -f
#可以加注释了,哈哈
BEGIN { print "my name is Java小咖秀"}
4.既然是脚本,必不可少的可执行权限安排上~
1
2
3
复制代码[root@iz2ze76ybn73dvwmdij06zz ~]# chmod +x printname.awk
[root@iz2ze76ybn73dvwmdij06zz ~]# ll printname.awk
-rwxr-xr-x 1 root root 60 7月 1 15:23 printname.awk
5.有了可执行权限,我们来执行下看结果
1
2
复制代码[root@iz2ze76ybn73dvwmdij06zz ~]# ./printname.awk
my name is Java小咖秀

了解了写awk脚本的步骤以后大家就可以自己去写一波了~

相关内容还有很多,容我后续再分享~

后面还会继续完善我的Linux实战命令小手册,持续更新中。。。

感谢小伙伴们的支持抱拳了

今天又到了一本书,持续更新。。⛽️

参考

《awk介绍》

《Linux命令行与shell脚本》

《鸟哥的Linux私房菜》

《快乐的命令行》

百度百科


本文使用 mdnice 排版

本文转载自: 掘金

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

《面试补习》- 多线程知识梳理

发表于 2020-07-03

一、基本概念

1.1、进程

进程是系统资源分配的最小单位。由 文本区域,数据区域和堆栈 组成。

  • 文本区域存储处理器执行的代码
  • 数据区域存储变量和进程执行期间使用的动态分配的内存;
  • 堆栈区域存储着活动过程调用的指令和本地变量。

涉及问题: cpu抢占,内存分配(虚拟内存/物理内存),以及进程间通信。

1.2、线程

线程是操作系统能够进行运算调度的最小单位。

一个进程可以包括多个线程,线程共用进程所分配到的资源空间

涉及问题: 线程状态,并发问题,锁

1.3、协程

子例程: 某个主程序的一部分代码,也就是指某个方法,函数。

维基百科:执行过程类似于 子例程 ,有自己的上下文,但是其切换由自己控制。

1.4、常见问题

  • 1、进程和线程的区别
1
2
3
复制代码进程拥有自己的资源空间,而线程需要依赖于进程进行资源的分配,才能执行相应的任务。
进程间通信需要依赖于 管道,共享内存,信号(量)和消息队列等方式。
线程不安全,容易导致进程崩溃等
  • 2、什么是多线程
1
2
3
复制代码线程是运算调度的最小单位,即每个处理器在某个时间点上只能处理一个线程任务调度。
在多核cpu 上,为了提高我们cpu的使用率,从而引出了多线程的实现。
通过多个线程任务并发调度,实现任务的并发执行。也就是我们所说的多线程任务执行。

二、Thread

2.1、使用多线程

2.1.1、继承 Thread 类

1
2
3
4
5
6
7
8
9
10
11
12
13
复制代码class JayThread extends Thread{
@Override
public void run(){
System.out.println("hello world in JayThread!");
}
}

class Main{
public static void main(String[] args){
JayThread t1 = new JayThread();
t1.start();
}
}

2.1.2、实现 Runnable 接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
复制代码class JayRunnable implements Runnable{

@Override
public void run(){
System.out.println("hello world in JayRunnable!")
}
}


class Main{
public static void main(String[] args){
JayRunnable runnable = new JayRunnable();
Thread t1 = new Thread(runnable);
t1.start();
}
}

2.1.3、实现 Callable 接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
复制代码class JayCallable implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("run in JayCallable " + Thread.currentThread().getName());
return "Jayce";
}
}


class Main{
public static void main(String[] args) {
Thread.currentThread().setName("main thread");
ThreadPoolExecutor executor =new ThreadPoolExecutor(10,20,60, TimeUnit.SECONDS,new ArrayBlockingQueue<>(10));
Future<String> future = executor.submit(new JayCallable());
try {
future.get(10, TimeUnit.SECONDS);
}catch (Exception e){
System.out.println("任务执行超时");
}
}
}

2.1.4、常见问题

  • 1、使用多线程有哪些方式

常用的方式主要由上述3种,需要注意的是 使用 ,而不是创建线程,从实现的代码我们可以看到,Java 创建线程只有一种方式, 就是通过 new Thread() 的方式进行创建线程。

  • 2、Thread(),Runnable() 与 Callable()之间的区别

Thread 需要继承,重写run()方法,对拓展不友好,一个类即一个线程任务。

Runnbale 通过接口的方式,可以实现多个接口,继承父类。需要创建一个线程进行装载任务执行。

Callable JDK1.5 后引入, 解决 Runnable 不能返回结果或抛出异常的问题。需要结合 ThreadPoolExecutor 使用。

  • 3、Thread.run() 和 Thread.start() 的区别

Thread.run()

1
2
3
4
5
6
7
8
复制代码    public static void main(String[] args){
Thread.currentThread().setName("main thread");
Thread t1 = new Thread(()->{
System.out.println("run in "+Thread.currentThread().getName());
});
t1.setName("Jayce Thread");
t1.run();
}

输出结果:

Thread.start()

1
2
3
4
5
6
7
8
复制代码    public static void main(String[] args){
Thread.currentThread().setName("main thread");
Thread t1 = new Thread(()->{
System.out.println("run in "+Thread.currentThread().getName());
});
t1.setName("Jayce Thread");
t1.start();
}

输出结果:

1
2
3
复制代码start() 方法来启动线程,使当前任务进入 cpu 等待队列(进入就绪状态,等待cpu分片),获取分片后执行run方法。

run() 方法执行,会被解析成一个普通方法的调用,直接在当前线程执行。

2.2、线程状态

线程状态,也称为线程的生命周期, 主要可以分为: 新建,就绪,运行,死亡,堵塞等五个阶段。

图片引用 芋道源码

2.2.1 新建

新建状态比较好理解, 就是我们调用 new Thread() 的时候所创建的线程类。

2.2.2 就绪

就绪状态指得是:

1、当调用 Thread.start 时,线程可以开始执行, 但是需要等待获取 cpu 资源。区别于 Thread.run 方法,run 方法是直接在当前线程进行执行,沿用其 cpu 资源。

2、运行状态下,cpu 资源使用完后,重新进入就绪状态,重新等待获取cpu 资源. 从图中可以看到,可以直接调用Thread.yield 放弃当前的 cpu资源,进入就绪状态。让其他优先级更高的任务优先执行。

2.2.3 运行

在步骤2 就绪状态中,获取到 cpu资源 后,进入到运行状态, 执行对应的任务,也就是我们实现的 run() 方法。

2.2.4 结束

1、正常任务执行完成,run() 方法执行完毕

2、异常退出,程序抛出异常,没有捕获

2.2.5 阻塞

阻塞主要分为: io等待,锁等待,线程等待 这几种方式。通过上述图片可以直观的看到。

io等待: 等待用户输入,让出cpu资源,等用户操作完成后(io就绪),重新进入就绪状态。

锁等待:同步代码块需要等待获取锁,才能进入就绪状态

线程等待: sleep() , join() 和 wait()/notify() 方法都是等待线程状态的阻塞(可以理解成当前线程的状态受别的线程影响)

二、线程池

2.1 池化技术

池化技术,主要是为了减少每次资源的创建,销毁所带来的损耗,通过资源的重复利用提高资源利用率而实现的一种技术方案。常见的例如: 数据库连接池,http连接池以及线程池等。都是通过池同一管理,重复利用,从而提高资源的利用率。

使用线程池的好处:

  • 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度:当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  • 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

2.2 线程池创建

2.2.1 Executors (不建议)

Executors 可以比较快捷的帮我们创建类似 FixedThreadPool ,CachedThreadPool 等类型的线程池。

1
2
3
4
5
6
7
8
9
10
复制代码// 创建单一线程的线程池
public static ExecutorService newSingleThreadExecutor();
// 创建固定数量的线程池
public static ExecutorService newFixedThreadPool(int nThreads);
// 创建带缓存的线程池
public static ExecutorService newCachedThreadPool();
// 创建定时调度的线程池
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize);
// 创建流式(fork-join)线程池
public static ExecutorService newWorkStealingPool();

存在的弊端:

1
2
3
复制代码FixedThreadPool 和 SingleThreadExecutor :允许请求的队列长度为 Integer.MAX_VALUE ,可能堆积大量的请求,从而导致OOM。

CachedThreadPool 和 ScheduledThreadPool :允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。

2.2.2 ThreadPoolExecuotr

构造函数:

1
2
3
4
5
6
7
复制代码       public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)

几个核心的参数:

  • 1、corePoolSize: 核心线程数
  • 2、maximumPoolSize: 最大线程数
  • 3、keepAliveTime: 线程空闲存活时间
  • 4、unit: 时间单位
  • 5、workQueue: 等待队列
  • 6、threadFactory: 线程工厂
  • 7、handler: 拒绝策略

与上述的 ExecutorService.newSingleThreadExecutor 等多个api进行对比,可以比较容易的区分出底层的实现是依赖于 BlockingQueue 的不同而定义的线程池。

主要由以下几种的阻塞队列:

  • 1、ArrayBlockingQueue,队列是有界的,基于数组实现的阻塞队列
  • 2、LinkedBlockingQueue,队列可以有界,也可以无界。基于链表实现的阻塞队列 对应了: Executors.newFixedThreadPool()的实现。
  • 3、SynchronousQueue,不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作将一直处于阻塞状态。对应了:Executors.newCachedThreadPool()的实现。
  • 4、PriorityBlockingQueue,带优先级的无界阻塞队列

拒绝策略主要有以下4种:

  • 1、CallerRunsPolicy : 在调用者线程执行
  • 2、AbortPolicy : 直接抛出RejectedExecutionException异常
  • 3、DiscardPolicy : 任务直接丢弃,不做任何处理
  • 4、DiscardOldestPolicy : 丢弃队列里最旧的那个任务,再尝试执行当前任务

2.3 线程池提交任务

往线程池中提交任务,主要有两种方法,execute()和submit()

1、 execute()

无返回结果,直接执行任务

1
2
3
4
复制代码public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.execute(() -> System.out.println("hello"));
}

2、submit()

submit() 会返回一个 Future 对象,用于获取返回结果,常用的api 有 get() 和 get(timeout,unit) 两种方式,常用于做限时处理

1
2
3
4
5
6
7
8
复制代码public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(2);
Future<String> future = executor.submit(() -> {
System.out.println("hello world! ");
return "hello world!";
});
System.out.println("get result: " + future.get());
}

三、线程工具类

3.1 ThreadlLocal

ThreadLocal,很多地方叫做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多。可能很多朋友都知道ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。

3.2 Semaphore

Semaphore ,是一种新的同步类,它是一个计数信号. 使用示例代码:

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
复制代码 // 线程池
ExecutorService exec = Executors.newCachedThreadPool();
// 只能5个线程同时访问
final Semaphore semp = new Semaphore(5);
// 模拟20个客户端访问
for (int index = 0; index < 50; index++) {
final int NO = index;
Runnable run = new Runnable() {
public void run() {
try {
// 获取许可
semp.acquire();
System.out.println("Accessing: " + NO);
Thread.sleep((long) (Math.random() * 6000));
// 访问完后,释放
semp.release();
//availablePermits()指的是当前信号灯库中有多少个可以被使用
System.out.println("-----------------" + semp.availablePermits());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
exec.execute(run);
}
// 退出线程池
exec.shutdown();

3.3 CountDownLatch

可以理解成是一个栅栏,需要等所有的线程都执行完成后,才能继续往下走。

CountDownLatch 默认的构造方法是 CountDownLatch(int count) ,其参数表示需要减少的计数,主线程调用 #await() 方法告诉 CountDownLatch 阻塞等待指定数量的计数被减少,然后其它线程调用 CountDownLatch 的 #countDown() 方法,减小计数(不会阻塞)。等待计数被减少到零,主线程结束阻塞等待,继续往下执行。

3.4 CyclicBarrier

CyclicBarrier 与 CountDownLatch 有点相似, 都是让线程都到达某个点,才能继续往下走, 有所不同的是 CyclicBarrier 是可以多次使用的。 示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
复制代码  
CyclicBarrier barrier;

public TaskThread(CyclicBarrier barrier) {
this.barrier = barrier;
}

@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println(getName() + " 到达栅栏 A");
barrier.await();
System.out.println(getName() + " 冲破栅栏 A");

Thread.sleep(2000);
System.out.println(getName() + " 到达栅栏 B");
barrier.await();
System.out.println(getName() + " 冲破栅栏 B");
} catch (Exception e) {
e.printStackTrace();
}
}

四、总结

最后贴一个新生的公众号 (Java 补习课),欢迎各位关注,主要会分享一下面试的内容(参考之前博主的文章),阿里的开源技术之类和阿里生活相关。 想要交流面试经验的,可以添加我的个人微信(Jayce-K)进群学习~

本文转载自: 掘金

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

1…797798799…956

开发者博客

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