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

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


  • 首页

  • 归档

  • 搜索

python--datetime(timedelta类) 基

发表于 2021-11-22

「这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战」前一段获取页面的实时时间,页面的时间是CT时间,我们需要验证的确实实际的北京时间,这里面就有一个时间换算, datetime 模块就解决了这个问题

基础

遇到不熟悉的内容,我喜欢自己看官方文档和源码实现,看别人写的源码,对自己的编码能力很有帮助,有时候不会就是看的太少,这是官方文档链接
docs.python.org/zh-cn/3/lib…

从官方文档可以知道,datetime模块和time模块一样都是处理日期和时间的类,在支持日期时间数学运算的同时,更关注如何能够有效地解析其属性用于格式化输出和数据操作。

timedelta类

主要总结一下timedelta的使用,先看源码

carbon.png

源码里大量用了python的typing, 想了解typing的可以参考我以前的文章
juejin.cn/post/700029…

这个类主要结合其他时间对象来进行时间的各种运算,它有days, seconds, mircroseconds,milliseconds等参数,默认这些参数的值是0, 我们可以任意给其中的一些或全部参数赋值为整数或者浮点数,也可以是正数或者负数,只会返回一个含有days, seconds, microseconds(微妙)的类对象,其他参数会根据一个标准转化
数单位的换算规则如下:

  • 1毫秒会转换成1000微秒。
  • 1分钟会转换成60秒。
  • 1小时会转换成3600秒。
  • 1星期会转换成7天。

举例

图片.png

应用

时区

结合到开头的那个例子,北京时间和CT时间的互相转换,北京时间和CT时间相差13个小时
那么我们可以给timedelta的参数hours赋值为负数13,得到一个时间差
具体例子

图片.png

对于转换时区的这个例子,还可以用astimezone,这个时候还需要额外的库pytz

图片.png

为当前时间加减

图片.png

总结

若是粗略估算程序的运行时间差,我们直接用datetime.datetime.now()方法获取程序运行前后的时间,再进行相减即可,若是对时间例如时区有些特殊的处理转换用timedelta比较好

本文转载自: 掘金

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

递归总结

发表于 2021-11-22

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

1 什么是递归

::: tip 递归的含义
递归,在数学与计算机科学中,是指在函数的定义中使用函数自身的方法。也就是说,递归算法是一种直接或者间接调用自身函数或者方法的算法。通俗来说,递归算法的实质是把问题分解成规模缩小的同类问题的子问题,然后递归调用方法来表示问题的解。
:::

2 递归的基本原理

  • 每一级的函数调用都有自己的变量。
  • 每一次函数调用都会有一次返回。
  • 递归函数中,位于递归调用前的语句和各级被调用函数具有相同的执行顺序。
  • 递归函数中,位于递归调用后的语句的执行顺序和各个被调用函数的顺序相反。
  • 虽然每一级递归都有自己的变量,但是函数代码并不会得到复制。

3 递归的优缺点

3.1 优点

  • 实现简单
  • 可读性好

3.2 缺点

  • 递归调用,占用空间大
  • 递归太深,易发生栈溢出
  • 可能存在重复计算

4 递归的三大要素

4.1 明确你这个函数想要干什么

::: tip 明确函数作用
先不管函数里面的代码什么,而是要先明白,你这个函数的功能是什么,要完成什么样的一件事。 例如,我定义了一个函数,代码如下

1
2
3
go复制代码func jieshen(n int) int{
//计算阶乘的具体方法
}

:::

4.2 寻找递归结束条件

::: tip 递归结束条件
所谓递归,就是会在函数内部代码中,调用这个函数本身,所以,我们必须要找出递归的结束条件,不然的话,会一直调用自己,进入无底洞。 我们需要找出当参数为啥时,递归结束,之后直接把结果返回,请注意,这个时候我们必须能根据这个参数的值,能够直接知道函数的结果是什么。

1
2
3
4
5
6
go复制代码func jieshen(n int) int {
if n<=2 {
return n
}
//f(n)的通用方法
}

:::

4.3 找出函数的等价关系式

::: tip 等价关系式
我们要不断缩小参数的范围,缩小之后,我们可以通过一些辅助的变量或者操作,使原函数的结果不变。 这个等价关系式的寻找,可以说是最难的一步了,如果你不大懂也没关系,因为你不是天才,你还需要多接触几道题。上面阶乘的最终结果代码如下

1
2
3
4
5
6
go复制代码 func jieshen(n int) int {
if n <= 2 {
return n
}
return n * jieshen(n-1)
}

:::

递归调用的过程

5 常见递归练习

一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。根据三大要素进行一步步分析如下

5.1 第一递归函数功能

1
2
3
go复制代码func f(n int) int {

}

5.2 找出递归结束条件

求递归结束的条件,你直接把 n 压缩到很小很小就行了,因为 n 越小,我们就越容易直观着算出 f(n) 的多少,所以当 n = 1时,你知道 f(1) 为多少吧?够直观吧?即 f(1) = 1。代码如下:

1
2
3
4
5
go复制代码func f(n int) int {
if n==1 {
return 1
}
}

5.3 找出函数的等价关系式

每次跳的时候,小青蛙可以跳一个台阶,也可以跳两个台阶,也就是说,每次跳的时候,小青蛙有两种跳法。

第一种跳法:第一次我跳了一个台阶,那么还剩下n-1个台阶还没跳,剩下的n-1个台阶的跳法有f(n-1)种。

第二种跳法:第一次跳了两个台阶,那么还剩下n-2个台阶还没,剩下的n-2个台阶的跳法有f(n-2)种。

所以,小青蛙的全部跳法就是这两种跳法之和了,即 f(n) = f(n-1) + f(n-2)。至此,等价关系式就求出来了。于是写出代码:

1
2
3
4
5
6
go复制代码func f(n int) int {
if n==1 {
return 1
}
return f(n-1)+f(n-2)
}

大家觉得上面的代码对不对?

答是不大对,当 n = 2 时,显然会有 f(2) = f(1) + f(0)。我们知道,f(0) = 0,按道理是递归结束,不用继续往下调用的,但我们上面的代码逻辑中,会继续调用 f(0) = f(-1) + f(-2)。这会导致无限调用,进入死循环。

这也是我要和你们说的,关于递归结束条件是否够严谨问题,有很多人在使用递归的时候,由于结束条件不够严谨,导致出现死循环。也就是说,当我们在第二步找出了一个递归结束条件的时候,可以把结束条件写进代码,然后进行第三步,但是请注意,当我们第三步找出等价函数之后,还得再返回去第二步,根据第三步函数的调用关系,会不会出现一些漏掉的结束条件。就像上面,f(n-2)这个函数的调用,有可能出现 f(0) 的情况,导致死循环,所以我们把它补上。代码如下:

1
2
3
4
5
6
7
go复制代码func f(n int) int {
//f(0) = 0,f(1) = 1,等价于 n<=2时,f(n) = n。
if n<=2 {
return n
}
return f(n-1)+f(n-2)
}

5.4 思考

有人可能会说,我不知道我的结束条件有没有漏掉怎么办?别怕,多练几道就知道怎么办了。

本文转载自: 掘金

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

数据库上云教程(体验有礼)

发表于 2021-11-22

开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。首先,远程登陆到 源数据库 ,查看源数据库中的信息。然后,登陆到阿里云管理控制台,建立目标数据库。最后,使用 数据传输 服务,实现ECS自建数据库迁移到目标数据库RDS。11 月 9 日至 11 月 23 日期间,完成部署即可获得“TOMY 多美卡合金车模一辆”。

O1CN019If3vm1vDFjunR9QE_!!2921416138-0-cib.jpg

地址:developer.aliyun.com/adc/series/…

简介

阿里云体验实验室地址(阿里云ECS体验场景体验)
developer.aliyun.com/adc/scenari…

背景知识

场景体验目标
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。首先,远程登陆到 源数据库 ,查看源数据库中的信息。然后,登陆到阿里云管理控制台,建立目标数据库。最后,使用 数据传输 服务,实现ECS自建数据库迁移到目标数据库RDS。
在这里插入图片描述

阿里云关系型数据库(Relational Database Service,简称 RDS)是一种稳定可靠、可弹性伸缩的在线数据库服务。基于阿里云分布式文件系统和高性能存储,RDS 支持 MySQL、SQL Server、PostgreSQL 和 PPAS(Postgre Plus Advanced Server,一种高度兼容 Oracle 的数据库)引擎,并且提供了容灾、备份、恢复、监控、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。

创建资源(尚未使用过相关,可以在上述链接体验)

  1. 请点击页面左侧的 云产品资源,在下拉栏中,查看本次实验资源信息;
  2. 在资源下拉栏点击 免费开通 按钮,开始创建实验资源。

说明:资源创建过程需要1-3分钟。完成实验资源的创建后,用户可以通过 云产品资源 查看实验中所需的资源信息,例如:IP地址、用户名、密码等。

现有数据库查看

本小节主要内容:登录本地部署的MySQL数据库实验环境,并查看本地MySQL源数据库中的信息。

  1. 通过本地远程连接服务,远程登录到 云产品资源 中的 MySQL源数据库 ECS实例。

说明:远程连接的详细操作步骤,请参考 帮助文档 的 远程连接服务器ECS 中 远程访问Linux系统。登录时,请使用 云产品资源 提供ECS实例的 弹性IP ,用户 和 密码 。

  1. 运行如下命令,登录到ECS实例的MySQL数据库:
1
css复制代码mysql -u root -p

Enter password: (输入123456,输入的密码不会显示)

在这里插入图片描述

  1. 通过如下操作,查看源数据库bakery、其中的表customer和product,以及表中的数据。

1)执行如下命令,查看数据库信息。

1
ini复制代码show databases;

在这里插入图片描述

2)执行如下命令,切换数据库为bakery:

1
ini复制代码use bakery;

在这里插入图片描述

3)执行如下命令,查看表中数据:

1
2
3
4
5
6
7
sql复制代码show columns from customer;

show columns from product;

select * from customer;

select * from product;

在这里插入图片描述

上云迁移

  1. 首先,通过如下操作步骤,登录到阿里云RDS管理控制台:

1)点击右侧 图标,切换到远程桌面操作界面。

2)双击打开虚拟桌面 FireFox ESR浏览器,在RAM用户登录框中点击 下一步,复制 云产品资源 列表中 子用户密码 粘贴(lx shell粘贴快捷键ctrl+shift+v,其他地方粘贴是ctrl+v)到浏览器 RAM用户登录界面,即可登录当前子账号。
3)顶部导航栏处点击【产品与服务】,在主页面的【数据库】条目下找到【云数据库RDS版】,点击进入阿里云RDS管理控制台。

在这里插入图片描述
2. 点击左侧的【实例列表】,在实例列表页面,选择云产品资源提供的 地域,然后点击目标实例右侧的【管理】进入RDS实例的管理控制台。
在这里插入图片描述
3. 通过如下步骤,进入数据库账号创建页面:
1)在RDS实例的管理页面,点击左侧栏的【账号管理】,进入数据库账号管理页面。
在这里插入图片描述

2)在【用户账号】页面,点击右侧的【创建账号】。

在这里插入图片描述
2. 填写如下信息,完成数据库账号创建:

1)数据库账号:请根据 输入框 下端的 命名规则 ,输入 数据库账号名称,例如:lab_user 。

2)账号类型:选择【普通账号】。

3)密码 :请根据 输入框 下端的 密码规则 ,输入 密码,例如:Passw0rd 。

4)确认密码:再次输入创建的密码。

5)完成如上信息后,点击【创建】。
在这里插入图片描述
6)返回 账号管理 的 用户账号 ,查看到新建账号 lab_user 的 状态 为 激活 。
在这里插入图片描述
3. 通过如下操作步骤,进入数据库创建页面:

1)点击左侧栏的【数据库管理】,进入数据库管理页面。
在这里插入图片描述
2)在右侧的【数据库管理】页面,点击左上角的【创建数据库】,进入创建页面。

在这里插入图片描述
4. 在弹出的创建页面中,添加如下信息:

1)数据库(DB)名称:请根据 输入框 下端的 命名规则 ,输入 数据库名称,例如:lab_db 。

2)支持字符集:默认设为 utf8 。

3)授权帐号:选择新建数据库账号 lab_user。

4)账号类型:默认设置为 读写 。

5)完成如上配置信息后,点击底部的【创建】,完成数据库的创建。
在这里插入图片描述
6)在【数据库管理】页面中,等待1分钟左右,点击右上角的【刷新】,可以查看到数据库 lab_db 的 数据库状态 为 运行中 ,且 绑定账号 为 新建数据库账号 lab_user 。

在这里插入图片描述
5. 通过如下步骤,进入数据迁移管理页面:

1)点击页面右上角的【导入数据库】。进入 数据迁移服务 的管理页面。

在这里插入图片描述
2)点击左侧栏【数据迁移】,进入数据迁移的管理页面,然后点击【数据迁移】右上角的【创建迁移任务】。

在这里插入图片描述
6. 新建一个迁移任务,按照如下步骤,输入 源库信息 的配置信息:

  1. 任务名称:自定义一个名称,以便能找到自己的数据迁移任务。

2)实例类型:选择 有弹性IP的自建数据库 。

3)实例地区: 选择 云产品资源 中分配的 地域 ,例如:华东1

4)数据库类型:选择 MySQL 。

5)主机名或IP地址:输入 云产品资源 中分配的 MySQL源数据库 ECS实例的 弹性IP。

6)端口:使用默认端口号 3306

7)数据库账号:输入ECS上自建MySQL的 数据库账号 ,root;数据库密码:自建MySQL的访问密码,123456

8)完成如上配置后,点击右侧的 【测试连接】 ,测试自建MySQL数据库的连通性,若显示 测试通过 ,证明连接成功。否则,请检查如上配置信息是否正确。
在这里插入图片描述
7. 按照如下步骤,完成 目标库信息 的配置信息:

1)实例类型:选择 RDS实例

2)实例地区:选择 实验资源 中分配的 地域 ,例如:华东1

3)RDS实例ID :选择 实验资源 中提供的 目标数据库 的 实例ID

4)数据库账号:输入RDS实例中新建的 数据库账号 ,lab_user ;数据库密码:新建数据库的密码,Passw0rd

5)完成如上配置后,点击右侧的 测试链接 ,测试RDS账号的连通性,若显示 测试通过 ,证明连接成功。否则,请检查如上配置信息是否正确。

6)完成如上的配置后,点击右下角的【 授权白名单并进入下一步 】。
在这里插入图片描述
8. 通过如下步骤,配置迁移任务的 迁移类型及列表:

1)点击左侧 迁移对象 中的本地MySQL数据库中的bakery

2)点击 >

3)数据库 bakery 移动到 已选择对象 的列表中。
在这里插入图片描述
4)完成如上配置后,点击右下角的【 预检查并启动】 。

在这里插入图片描述
说明:若预检查未通过,请根据错误提示和如上的操作步骤,核对迁移任务的配置是否正确。
在这里插入图片描述
10. 按照如下内容,购买迁移任务的配置:

1)链路地域:使用默认地域

2)链路规格:选择 small

3)勾选 《数据传输(按量付费)服务条款》

4)完成如上配置后,点击 【立即购买并启动】

在这里插入图片描述
11. 此时,页面迁移任务的状态为 迁移中 ,等待3-4分钟,状态变为 已完成 。

在这里插入图片描述

数据库迁移结果确认

  1. 顶部导航栏处点击【产品与服务】,在主页面的【数据库】条目下找到【云数据库RDS版】,点击进入阿里云RDS管理控制台。

在这里插入图片描述
2. 在 实例列表 界面中,点击右上角的【登录数据库】,进入数据库管理登录界面。
6a40963b9574473bad1fee2784d2d77c.jpg

  1. 在弹出的页面中,输入如下的信息:

网络地址:端口:请输入 云产品资源 中,RDS 目标数据库 提供的 链接地址 以及端口号 3306。
例如:提供的RDS 链接地址为 rm-uf6l90d950j1o7890.mysql.rds.aliyuncs.com,则输入信息为:rm-uf6l90d950j1o7890.mysql.rds.aliyuncs.com:3306

用户名:输入新建数据库账号 lab_user

密码:输入新建数据库密码 Passw0rd
完成后,点击【登录】。
在这里插入图片描述
4. 如下图,进入数据库管理页面,证明数据库迁移成功。

28f0e924e9b944709d50c4f2272d66bd.jpg

本文转载自: 掘金

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

Adder & Accumulator 区别

发表于 2021-11-22

前面介绍了 AtomicLong 与LongAdder区别,也就是Atomic* 与*Adder区别,具体参考前面的文章# 并发操作 AtomicLong & LongAdder区别, 本文说明一下 Adder 和 Accumulator区别。

首先我们要知道 Adder 和 Accumulator 都是 Java 8 引入的,是相对比较新的类。对于 Adder 而言,比如最典型的 LongAdder,在具体参考前面的文章[# 并发操作 AtomicLong & LongAdder区别]已经介绍过了,在高并发下 LongAdder 比 AtomicLong 效率更高,因为对于 AtomicLong 而言,它只适合用于低并发场景,否则在高并发的场景下,由于 CAS 的冲突概率大,会导致经常自旋,影响整体效率。

而 LongAdder 引入了分段锁的概念,当竞争不激烈的时候,所有线程都是通过 CAS 对同一个 Base 变量进行修改,但是当竞争激烈的时候,LongAdder 会把不同线程对应到不同的 Cell 上进行修改,降低了冲突的概率,从而提高了并发性。

Accumulator

那么 Accumulator 又是做什么的呢?Accumulator 和 Adder 非常相似,实际上 Accumulator 就是一个更通用版本的 Adder,比如 LongAccumulator 是 LongAdder 的功能增强版,因为 LongAdder 的 API 只有对数值的加减,而 LongAccumulator 提供了自定义的函数操作。

具体通过代码示例演示一下,我们都听过数学家高斯的故事。高斯小时候,老师出了一道数学题,从1,2…100内的数,相加等于多少。知道这个故事的同学应该很清楚,高斯的算法,就是拿1+99,2+98…最后得出的结果。这里就模拟这道题,通过LongAccumulator 自定义函数的方式,计算1~10直接的结果
代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
java复制代码public class LongAccumulatorDemo {



    public static void main(String[] args) throws InterruptedException {

        LongAccumulator accumulator = new LongAccumulator((x, y) -> x + y, 0);

        ExecutorService executor = Executors.newFixedThreadPool(8);

        IntStream.range(1, 11).forEach(i -> executor.submit(() -> accumulator.accumulate(i)));



        Thread.sleep(2000);

        System.out.println(accumulator.getThenReset());

    }

}

这段代码中:

  • 首先新建了一个 LongAccumulator,同时给它传入了两个参数;
  • 然后又新建了一个 8 线程的线程池,并且利用整形流也就是 IntStream 往线程池中提交了从 1 ~ 10 这 10 个任务;
  • 之后等待了两秒钟,这两秒钟的作用是等待线程池的任务执行完毕;
  • 最后把 accumulator 的值打印出来。

这段代码的运行结果是 55,当然计算机并不是按照高斯的想法算的的记过,这里的计算过程还是一个个数累计的。代表 0+1+2+3+…+8+9+10=55 的结果,这个结果怎么理解呢?我们先重点看看新建的 LongAccumulator 的这一行语句:

1
java复制代码LongAccumulator accumulator = new LongAccumulator((x, y) -> x + y, 0);

在这个语句中,传入了两个参数:LongAccumulator 的构造函数的第一个参数是二元表达式;第二个参数是 x 的初始值,传入的是 0。在二元表达式中,x 是上一次计算的结果(除了第一次的时候需要传入),y 是本次新传入的值。

我们来看一下上面这段代码执行的过程,当执行 accumulator.accumulate(1) 的时候,首先要知道这时候 x 和 y 是什么,第一次执行时, x 是 LongAccumulator 构造函数中的第二个参数,也就是 0,而第一次执行时的 y 值就是本次 accumulator.accumulate(1) 方法所传入的 1;然后根据表达式 x+y,计算出 0+1=1,这个结果会赋值给下一次计算的 x,而下一次计算的 y 值就是 accumulator.accumulate(2) 传入的 2,所以下一次的计算结果是 1+2=3。

我们在 IntStream.range(1, 10).forEach(i -> executor.submit(() -> accumulator.accumulate(i))); 这一行语句中实际上利用了整型流,分别给线程池提交了从 1 ~ 10 这 10 个任务,相当于执行了:

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码accumulator.accumulate(1);

accumulator.accumulate(2);

accumulator.accumulate(3);

...

accumulator.accumulate(8);

accumulator.accumulate(9);

accumulator.accumulate(10);

这里需要指出的是,这里的加的顺序是不固定的,并不是说会按照顺序从 1 开始逐步往上累加,它也有可能会变,比如说先加 5、再加 3、再加 6。但总之,由于加法有交换律,所以最终加出来的结果会保证是 55。这就是这个类的一个基本的作用和用法。

拓展

继续看一下它的功能强大之处。举几个例子,刚才我们给出的表达式是 x + y,其实同样也可以传入 x * y,或者写一个 Math.min(x, y),相当于求 x 和 y 的最小值。同理,也可以去求 Math.max(x, y),相当于求一个最大值。根据业务的需求来选择就可以了。代码如下:

1
2
3
4
5
6
7
java复制代码LongAccumulator counter = new LongAccumulator((x, y) -> x + y, 0);

LongAccumulator result = new LongAccumulator((x, y) -> x * y, 0);

LongAccumulator min = new LongAccumulator((x, y) -> Math.min(x, y), 0);

LongAccumulator max = new LongAccumulator((x, y) -> Math.max(x, y), 0);

这时你可能会有一个疑问:在这里为什么不用 for 循环呢?比如说我们之前的例子,从 0 加到 10,我们直接写一个 for 循环不就可以了吗?

确实,用 for 循环也能满足需求,但是用 for 循环的话,它执行的时候是串行,它一定是按照 0+1+2+3+…+8+9+10 这样的顺序相加的,但是 LongAccumulator 的一大优势就是可以利用线程池来为它工作。一旦使用了线程池,那么多个线程之间是可以并行计算的,效率要比之前的串行高得多。这也是为什么刚才说它加的顺序是不固定的,因为我们并不能保证各个线程之间的执行顺序,所能保证的就是最终的结果是确定的。

使用场景

第一点需要满足的条件,就是需要大量的计算,并且当需要并行计算的时候,我们可以考虑使用 LongAccumulator。

当计算量不大,或者串行计算就可以满足需求的时候,可以使用 for 循环;如果计算量大,需要提高计算的效率时,我们则可以利用线程池,再加上 LongAccumulator 来配合的话,就可以达到并行计算的效果,效率非常高。

第二点需要满足的要求,就是计算的执行顺序并不关键,也就是说它不要求各个计算之间的执行顺序,也就是说线程 1 可能在线程 5 之后执行,也可能在线程 5 之前执行,但是执行的先后并不影响最终的结果。

一些非常典型的满足这个条件的计算,就是类似于加法或者乘法,因为它们是有交换律的。同样,求最大值和最小值对于顺序也是没有要求的,因为最终只会得出所有数字中的最大值或者最小值,无论先提交哪个或后提交哪个,都不会影响到最终的结果。

本文转载自: 掘金

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

Java中那些让你爱不释手工具库,精炼代码量 Java中那些

发表于 2021-11-22

Java中那些让你爱不释手工具库,精炼代码量

一、JDK1.8 Stream新特性

1、Stream流的常见生成方式

①Collection体系的集合可以使用默认方法stream()生成流

1
2
3
4
5
6
7
8
ini复制代码//list转stream流 List<String> list = new ArrayList<>();     
Stream<String> listStream = list.stream();
//Set转stream流 Set<String> set = new HashSet<>();
Stream<String> setStream = set.stream();
//map转stream流Map<String, Integer> map = new HashMap<>();
Stream<String> keyStream = map.keySet().stream();
Stream<Integer> valueStream = map.values().stream();
Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();

②数组转stream流

1
2
sql复制代码//第一种: 使用java.util.Arrays.stream(T[] array)方法用数组创建流   
int[] array={1,3,5,6,8}; IntStream stream = Arrays.stream(array); ​//第二种: 可以通过Stream接口的静态方法of(T... values)生成流 String[] strArray = {"hello", "world", "java"}; Stream<String> strArrayStream = Stream.of(strArray); Stream<String> strArrayStream2 = Stream.of("hello", "world", "java"); Stream<Integer> intStream = Stream.of(10, 20, 30);

2、void forEach(Consumer action)

1
ruby复制代码 Stream<String> stringStream = Stream.of("景天", "雪见", "长卿", "紫萱");        stringStream.forEach(System.out::println);         //打印结果: ["景天", "雪见", "长卿", "紫萱"]

3、Stream

filter(Predicate predicate)

(说明: 用于对流中的数据进行过滤)

1
2
rust复制代码List<String> nameList = Arrays.asList("景天", "雪见", "长卿", "紫萱");
nameList.stream().filter(s -> s.startsWith("紫")).forEach(System.out::println);//打印结果: 紫萱

4、Stream map(Function mapper)

(说明: 可以将流中的元素映射到另一个流中)

1
scss复制代码 List<Integer> num = Arrays.asList(1, 2, 3, 4, 5);    num.stream().map(n -> n * 2).forEach(System.out::println);     //打印结果: [2, 4, 6, 8, 10]​

5、Stream flatMap(Function function)

(说明:flatmap是stream的一种中间操作,对流进行 “扁平化” 处理,是它和stream的map一样,是一种收集类型的stream中间操作,但是与map不同的是,它可以对stream流中单个元素再进行拆分(切片),从另一种角度上说,使用了它,就是使用了双重for循环。)

1
vbnet复制代码 // map 生成的是个 1:1 映射,每个输入元素,都按照规则转换成为另外一个元素。还有一些场景,是一对多映射关系的,这时需要 flatMap       String[] strs = {"java8", "is", "easy", "to", "use"};     List<String[]> distinctStrs = Arrays.stream(strs)     .map(str -> str.split(""))    // 映射成为Stream<String[]>        .distinct().collect(Collectors.toList()); //distinct操作是以 “字符串数组“为单元进行去重        /* 在执行map操作以后,我们得到是一个包含多个字符串数组的流,打印结果如下所示   [j, a, v, a, 8]       [i, s]      [e, a, s, y]         [t, o]         [u, s, e]      */  ​List<String> distinctStr = Arrays.stream(strs)    .map(str -> str.split("")) // 映射成为Stream<String[]>           .flatMap(Arrays::stream)  // 扁平化为Stream<String>       .distinct().collect(Collectors.toList());      ​/* flatMap将由map映射得到的Stream<String[]>,转换成由各个字符串数组映射成的流Stream<String>,        再将这些小的流扁平化成为一个由所有字符串构成的大流Steam<String>,   从而能够达到我们的目的。               打印结果:               [j, a, v, 8, i, s, e, y, t, o, u]     */

6、Stream

limit(long maxSize)

(说明: 返回此流中的元素组成的流,截取前指定参数个数的数据)

1
ruby复制代码 List<String> limitList = Arrays.asList("景天", "雪见", "长卿", "紫萱");    //取前3个数据在控制台输出       limitList.stream().limit(3).forEach(System.out::println);     //打印结果:["景天", "雪见", "长卿"]

7、Stream

skip(long n)

(说明: 跳过指定参数个数的数据,返回由该流的剩余元素组成的流)

1
ini复制代码//跳过3个元素,把剩下的元素在控制台输出     List<String> list = Arrays.asList("景天", "雪见", "长卿", "紫萱");   list.stream().skip(3).forEach(System.out::println);    //打印结果:["紫萱"]

8、static

Stream

concat(Stream a, Stream b)

(说明: 合并a和b两个流为一个流)

1
vbnet复制代码List<String> concatList1 = Arrays.asList("景天", "雪见", "长卿", "紫萱");   List<String> concatList2 = Arrays.asList("重楼", "茂茂", "必平", "龙葵");     Stream<String> stream1 = concatList1.stream();   Stream<String> stream2 = concatList2.stream();      Stream.concat(stream1, stream2).forEach(System.out::println);   // 打印结果: ["景天", "雪见", "长卿", "紫萱","重楼", "茂茂", "必平", "龙葵"]

9、Stream

distinct()

(说明:对流中相同的元素进行去重处理)

1
ruby复制代码List<String> distinctList = Arrays.asList("景天", "雪见", "长卿", "紫萱", "紫萱", "雪见"); distinctList.stream().distinct().forEach(System.out::println); // 打印结果: ["景天", "雪见", "长卿", "紫萱"]

10、Stream

sorted()

(说明:返回由此流的元素组成的流,根据自然顺序排序)

1
css复制代码 //题目: 按照字母顺序把数据在控制台输出 sorted()是正序List<String> sortList = Arrays.asList("Lucy", "Jack", "Anny", "Vincent", "Charles","William");sortList.stream().sorted().forEach(System.out::println);//打印结果: [Anny,Charles,Jack,Lucy,Vincent,William] //题目: 按照名称长度顺序从短到长 把数据在控制台输出 sortList.stream().sorted(Comparator.comparingInt(String::length))    .forEach(System.out::println); //打印结果:  [Lucy,Jack,Anny,Vincent,Charles,William]

11、Stream

parallelStream()

(说明: 并行的操作,多线程执行,在大数据量下会优于 stream(), parallelStream()的底层思想是ForkJoinPool主要用来使用分治法(Divide-and-Conquer Algorithm)来解决问题)

1
csharp复制代码  List<String> stringList = new ArrayList<>();        for (int i = 0; i < 10; i++) {         stringList.add("第" + i + "条数据");         }         long parallelStreamNowTime = System.currentTimeMillis();   stringList.parallelStream().forEach(s -> {             try {                       Thread.sleep(1000);          } catch (InterruptedException e) {                 e.printStackTrace();              }     });        long parallelStreamTime = System.currentTimeMillis();      System.out.println("Stream需要时间" + (parallelStreamTime - parallelStreamNowTime));        //打印结果:  Stream需要时间2027

PS:除了直接创建并行流,还可以通过parallel()把顺序流转换成并行流

1
scss复制代码Optional<Integer> findFirst = list.stream().parallel().filter(x->x>6).findFirst();

12、count()

(说明: 返回集合流中元素的个数)

1
vbnet复制代码List<String> countList = Arrays.asList("Lucy", "Jack", "Anny", "Vincent","Charles", "William","William"); long count1 = countList.stream().count();System.out.println("总元素个数是:"+count1);  // 打印结果 : 总元素个数是:7 ​long count2 = countList.stream().distinct().count(); System.out.println("去重之后元素个数是:"+count2);  // 打印结果 : 去重之后元素个数是:6

13、boolean allMatch(Predicate predicate)

(说明:allMatch表示,判断条件里的元素,所有的元素都满足条件,就返回true)

1
scss复制代码 List<Integer> integerList  = Arrays.asList(1,2,3,4,5); if(integerList.stream().allMatch(i->i>3)){      System.out.println( "值都大于3");  }

14、boolean anyMatch(Predicate predicate)

(说明: anyMatch表示,判断的条件里,任意一个元素符合条件,就返回true)

1
scss复制代码 List<Integer> integerList  = Arrays.asList(1,2,3,4,5);  if(integerList.stream().anyMatch(i->i>3)){      System.out.println( "存在大于3的值");  }

15、boolean noneMatch(Predicate predicate)

(说明: noneMatch跟allMatch相反,判断条件里的元素,所有的都不符合条件,才返回true)

1
scss复制代码List<Integer> integerList  = Arrays.asList(1,2,3,4,5);  if(integerList.stream().noneMatch(i -> i > 3)){      System.out.println("值都小于3"); }

14、A[] toArray(IntFunction

generator);

(说明: 使用提供的 generator函数返回一个包含此流的元素的数组,以分配返回的数组,以及分区执行或调整大小可能需要的任何其他数组。)

15、Stream concat(Stream a, b)

(说明:合并2个stream流)

1
vbnet复制代码List<String> strings = Arrays.asList("abc", "def", "gkh", "abc");//concat 合并流List<String> strings2 = Arrays.asList("xyz", "jqx");​List<String> concatList = Stream.concat(strings2.stream(),strings.stream())    .collect(Collectors.toList());System.out.println(concatList);  //打印结果: [xyz, jqx, abc, def, gkh, abc]

16、IntSummaryStatistics summaryStatistics()

(说明: 对stream流中的数据进行统计分析)

1
sql复制代码//对数组的统计,比如用List<Integer> number = Arrays.asList(1, 2, 5, 4);IntSummaryStatistics statistics = number.stream().mapToInt((x) -> x).summaryStatistics();System.out.println("列表中最大的数 : "+statistics.getMax());System.out.println("列表中最小的数 : "+statistics.getMin());System.out.println("平均数 : "+statistics.getAverage());System.out.println("所有数之和 : "+statistics.getSum());

17、Optional

findAny()

(说明:findAny()操作,返回的元素是不确定的,对于同一个列表多次调用findAny() 有可能会返回不同的值。使用findAny()是为了更高效的性能。

①串行流情况下,一般会返回符合条件的第一个结果;

1
ruby复制代码 List<String> list1 = Arrays.asList("A1", "B1", "C1", "A2", "B2", "C2", "A3", "B3","C3");  for(int i=0;i<10;i++) {     Optional<String> c = list1.stream().filter(s -> s.contains("A")).findAny();   System.out.println("====串行流findAny()======" + c.get());  } //打印结果:  //  ====串行流findAny()======A1 //  ====串行流findAny()======A1 //  ====串行流findAny()======A1  //  ====串行流findAny()======A1  //  ====串行流findAny()======A1  //  ====串行流findAny()======A1 //  ====串行流findAny()======A1  //  ====串行流findAny()======A1  //  ====串行流findAny()======A1 //  ====串行流findAny()======A1

②并行流的情况,在给定的元素中随机取一个元素,那就不能确保是第一个。)

1
ruby复制代码List<String> list1 = Arrays.asList("A1", "B1", "C1", "A2", "B2", "C2", "A3", "B3","C3"); Optional<String> a = list1.parallelStream().filter(s -> s.contains("A")).findAny(); System.out.println("====findAny()======" + a.get()); ​//打印结果:  每次执行打印的是A1或者A2或者A3中任意一个 // ====并行流findAny()======A3// ====并行流findAny()======A3 // ====并行流findAny()======A2 // ====并行流findAny()======A1 // ====并行流findAny()======A1 // ====并行流findAny()======A2 // ====并行流findAny()======A3 // ====并行流findAny()======A3 // ====并行流findAny()======A3 // ====并行流findAny()======A2

18、Optional

findFirst()

(说明:返回Stream中的第一个元素)

1
less复制代码List<String> list2 = Arrays.asList("A1", "B1", "C1", "A2", "B2", "C2", "A3", "B3","C3"); Optional<String> b = list2.parallelStream().filter(s -> s.contains("B")).findFirst(); System.out.println("====findFirst()====" + b.get());// 打印结果:   ====findFirst()====B1

19、Optional

max(Comparator comparator)

(说明:返回比较器比较之后 结果最大的那个元素)

1
vbnet复制代码//获取String集合中最长的元素。List<String> maxList = Arrays.asList("Lucy", "Jack", "Anny", "Vincent","Charles","William");Optional<String> max = maxList.stream().    max(Comparator.comparing(String::length));System.out.println("最长的字符串:" + max.get());//打印结果: 最长的字符串:Vincent

PS: Optional min(Comparator<? super T> comparator) 也是类似的,我就不多说了!

20、Optional

reduce(BinaryOperator accumulator)

(说明: 根据指定的计算模型将Stream中的值计算得到一个最终结果)

1
vbnet复制代码List<Integer> reduceList = Arrays.asList(1, 3, 2, 8, 11, 4);  // 求和方式1 Optional<Integer> sum = reduceList.stream().reduce((x, y) -> x + y); // 求和方式2 Optional<Integer> sum2 = reduceList.stream().reduce(Integer::sum); // 求和方式3 Integer sum3 = reduceList.stream().reduce(0, Integer::sum);​  // 求乘积  Optional<Integer> product = reduceList.stream().reduce((x, y) -> x * y);​  // 求最大值方式1  Optional<Integer> max2 = reduceList.stream().reduce((x, y) -> x > y ? x : y);  // 求最大值写法2  Integer max3 = reduceList.stream().reduce(1, Integer::max);​  System.out.println("list求和:" + sum.get() + "," + sum2.get() + "," + sum3);  //打印结果:  list求和:29,29,29  System.out.println("list求积:" + product.get());  //打印结果:  list求积:2112  System.out.println("list求最大值:" + max2.get() + "," + max3);  //打印结果: list求最大值:11,11

二、List转String

1
vbnet复制代码 // 如何把list集合拼接成以逗号分隔的字符串 a,b,c List<String> list = Arrays.asList("a", "b", "c");​// 第一种方法,可以用stream流 String join = list.stream().collect(Collectors.joining(",")); System.out.println(join); // 输出 a,b,c​ // 第二种方法,其实String也有join方法可以实现这个功能,合并数组为单一字符串,可传分隔符 String join1 = String.join(",", list); System.out.println(join1); // 输出 a,b,c

三、玩转StringUtils工具类

1
perl复制代码String str = "I love JavaAlliance forever";// 1、删除字符串中指定字符,返回一个stringString remove = StringUtils.remove(str, "forever"); // 移除字符串中的 foreverSystem.out.println(remove); // 打印结果是  I love JavaAlliance​// 2、将字符串反转,返回一个stringString reverse = StringUtils.reverse(str);System.out.println(reverse); // 打印结果是  reverof ecnaillAavaJ evol I​// 3、比较两个字符串是否相等,如果两个均为null,则也认为相等StringUtils.equals("Java", "Java"); // 结果是trueStringUtils.equals("", ""); // 结果是trueStringUtils.equals(null, null); // 结果是trueStringUtils.equals(null, ""); // 结果是falseStringUtils.equals("", null); // 结果是falseStringUtils.equals(null, ""); // 结果是falseStringUtils.equalsIgnoreCase("java", "JAVA"); // 不区分大小写--结果是true​// 4、重复拼接字符串String str1 = StringUtils.repeat("ab", 2);System.out.println(str1); // 输出abab​// 5、首字母转成大写String str2 = "javaAlliance";String capitalize = StringUtils.capitalize(str2);System.out.println(capitalize); // 输出JavaAlliance​// 6、字符串固定长度填充StringUtils.leftPad("test", 8, "0"); // 字符串固定长度 8位,若不足,往左补 0StringUtils.rightPad("test", 8, "0"); // 字符串固定长度 8位,若不足,往右补 0​// 7、字符串关键字替换StringUtils.replace("aba", "a", "z")   = "zbz"; // 默认替换所有关键字StringUtils.replaceOnce("aba", "a", "z")   = "zba";// 替换关键字,仅替换一次StringUtils.replacePattern("ABCabc123", "[^A-Z0-9]+", "")   = "ABC123"; // 使用正则表达式替换​// 8、将数组拼接成字符串StringUtils.join(["a", "b", "c"], ",") ; //拼接结果是  "a,b,c"​// 9、字符串拆分StringUtils.split("a..b.c", '.')  //拆分结果是 ["a", "b", "c"]StringUtils.splitByWholeSeparatorPreserveAllTokens("a..b.c", ".")  //拆分结果是 ["a","", "b", "c"]​​​​

四、玩转BeanUtils工具类

1
perl复制代码User user = new User();user.setUserName("JavaAlliance").setEmail("196432@qq.com");// 对象转mapMap<String, String> map = BeanUtils.describe(user);System.out.println(map); // 输出// map转对象User newUser = new User();BeanUtils.populate(newUser, map);System.out.println(newUser); // 输出

五、玩转DateUtils/DateFormatUtils工具类

1
vbnet复制代码// Date类型转String类型String date = DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss");System.out.println(date); // 输出 2021-05-01 01:01:01​// String类型转Date类型   该方法会将日期字符串按照第二参数中的String数组,依次比对,选择合适的Pattern来解析。Date date1 = DateUtils.parseDate("2021-05-01 01:01:01", new String[]{"yyyy-MM-dd HH:mm:ss"});​Date now = new Date();// Date 加 1 天Date addDays = DateUtils.addDays(now, 1);// Date 加 33 分钟Date addMinutes = DateUtils.addMinutes(now, 33);// Date 减去 233 秒Date addSeconds = DateUtils.addSeconds(now, -233);// 判断是否 Wie 同一天boolean sameDay = DateUtils.isSameDay(addDays, addMinutes);// 过滤时分秒,若 now 为 2020-05-07 22:13:00 调用 truncate 方法以后// 返回时间为 2020-05-07 00:00:00Date truncate = DateUtils.truncate(now, Calendar.DATE);​

六、玩转LocalDateTime工具类

1
sql复制代码Date now = new Date();// Date转成LocalDateTime 这里指定使用当前系统默认时区LocalDateTime localDateTime = now.toInstant().atZone(ZoneId.systemDefault())    .toLocalDateTime();​// LocalDateTime转成Date 这里指定使用当前系统默认时区Date date = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());​// 按照 yyyy-MM-dd HH:mm:ss 转化时间LocalDateTime dateTime = LocalDateTime.parse("2020-05-07 22:34:00", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));​// 将 LocalDateTime 格式化字符串String format = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(dateTime);​//LocalDateTime 获取当前时间年份,月份LocalDateTime now = LocalDateTime.now();// 年int year = now.getYear();// 月int month = now.getMonthValue();// 日int day = now.getDayOfMonth();​// LocalDateTime 进行日期加减,获取下一天的时间LocalDateTime now = LocalDateTime.now();// 当前时间加一天LocalDateTime plusDays = now.plusDays(1l);// 当前时间减一个小时LocalDateTime minusHours = now.minusHours(1l);// 还有很多其他方法​

七、玩转CollectionUtils工具类

1
ruby复制代码String[] arrayA = new String[]{"1", "2", "3", "4"};String[] arrayB = new String[]{"3", "4", "5", "6"};List<String> listA = Arrays.asList(arrayA);List<String> listB = Arrays.asList(arrayB);// 1、并集 unionSystem.out.println(CollectionUtils.union(listA, listB));// 输出: [1, 2, 3, 4, 5, 6]​// 2、交集 intersectionSystem.out.println(CollectionUtils.intersection(listA, listB));// 输出:[3, 4]​// 3、交集的补集(析取)disjunctionSystem.out.println(CollectionUtils.disjunction(listA, listB));// 输出:[1, 2, 5, 6]​// 4、差集(扣除)System.out.println(CollectionUtils.subtract(listA, listB));// 输出:[1, 2]​​  // 1、交集 List<String> intersectionList = new ArrayList<>(listA); intersectionList.retainAll(listB); System.out.println(intersectionList); // 输出:[3, 4]​ // 2、差集 List<String> differenceList = new ArrayList<>(listA); differenceList.removeAll(listB); System.out.println(differenceList); // 输出:[1, 2]​// 3、并集 (先做差集再做添加所有)List<String> unionList = new ArrayList<>(listA);unionList.removeAll(listB); // unionList [1, 2]unionList.addAll(listB); // 添加[3,4,5,6]System.out.println(unionList);// 输出:[1, 2, 3, 4, 5, 6]​

注意 : 以上有2种取交集的方式即intersection和retainAll,我们这里说下它们之间的差别 ,要注意的是它们的返回类型是不一样的,intersection返回的是一个新的List集合,而retainAll返回是Bollean类型那就说明retainAll方法是对原有集合进行处理再返回原有集合,会改变原有集合中的内容。

思路点拨:

1、从性能角度来考虑的话,List自带会高点,因为它不用再创建新的集合。

2、需要注意的是:因为retainAll因为会改变原有集合,所以该集合需要多次使用就不适合用retainAll。

注意: Arrays.asList将数组转集合不能进行add和remove操作。因为调用Arrays.asList()生产的List的add、remove方法时报异常,这是由Arrays.asList() 返回的市Arrays的内部类ArrayList, 而不是java.util.ArrayList。Arrays的内部类ArrayList和java.util.ArrayList都是继承AbstractList,remove、add等方法AbstractList中是默认throw UnsupportedOperationException而且不作任何操作。java.util.ArrayList重新了这些方法而Arrays的内部类ArrayList没有重新,所以会抛出异常。

八、玩转 Guava工具包

1、Multiset

Multiset是什么?顾名思义,Multiset和Set的区别就是可以保存多个相同的对象。在JDK中,List和Set有一个基本的区别,就是List可以包含多个相同对象,且是有顺序的,而Set不能有重复,且不保证顺序(有些实现有顺序,例如LinkedHashSet和SortedSet等)

所以Multiset占据了List和Set之间的一个灰色地带:允许重复,但是不保证顺序。

常见使用场景:Multiset有一个有用的功能,就是跟踪每种对象的数量,所以你可以用来进行数字统计。 常见的普通实现方式如下:

1
vbnet复制代码 String str = "张三 李四 李四 王五 王五 王五"; String[] strArr = str.split(" "); List<String> words = new ArrayList<>(Arrays.asList(strArr));//创建一个HashMultiset集合,并将words集合数据放入 Multiset<String> wordMultiset = HashMultiset.create(); wordMultiset.addAll(words);//将不同的元素放在一个集合set中 for (String key : wordMultiset.elementSet()) {   //查看指定元素的个数   System.out.println(key + "-->" + wordMultiset.count(key));     //打印结果:  李四-->2    张三-->1   王五-->3 }

2、Multimap

Multimap能够实现一个键对应到多个值的效果

1
vbnet复制代码Multimap<String, String> myMultimap = ArrayListMultimap.create();myMultimap.put("Fruits", "Bannana");myMultimap.put("Fruits", "Apple");myMultimap.put("Fruits", "Pear");myMultimap.put("Fruits", "Pear");myMultimap.put("Vegetables", "Carrot");​// 查询Multimap中的存储的元素个数System.out.println(myMultimap.size()); // 打印结果: 5​//查询键为“Fruits”所对应的valueCollection<String> fruits = myMultimap.get("Fruits");System.out.println(fruits);//打印结果:  [Bannana, Apple, Pear, Pear]Collection<String> vegetables = myMultimap.get("Vegetables");System.out.println(vegetables); //打印结果: [Carrot]​// 循环迭代遍历整个 Multimap里存储的value值for (String value : myMultimap.values()) {    System.out.println(value);      //打印结果:      //Carrot      //Bannana      //Apple      //Pear      //Pear }​//移除其中一个元素myMultimap.remove("Fruits", "Pear");System.out.println(myMultimap.get("Fruits"));//打印结果: [Bannana, Apple, Pear]​//将键为"Fruits"所对应的value内容替换为集合  Arrays.asList("178","910","123")//返回的oldValues是之前的旧值Collection<String> oldValues = myMultimap.replaceValues("Fruits", Arrays.asList("178","910","123"));​System.out.println("oldValues="+oldValues);//打印结果: oldValues=[Bannana, Apple, Pear]System.out.println("myMultimap="+myMultimap);//打印结果: myMultimap={Vegetables=[Carrot], Fruits=[178, 910,123]}​//移除Multimap中键为“Fruits”的所有元素myMultimap.removeAll("Fruits");System.out.println(myMultimap.get("Fruits"));//打印结果: []

3、BiMap

BiMap 可以用来实现键值对的双向映射需求,这样我们就可以通过 Key 查找对对应的 Value,也可以使用 Value 查找对应的 Key。 Bimap要求key和value都唯一,如果key不唯一则覆盖key,如果value不唯一则直接报错

1
vbnet复制代码//双向mapBiMap<Integer,String> biMap=HashBiMap.create();biMap.put(1,"张三");biMap.put(2,"李四");biMap.put(3,"王五");biMap.put(4,"赵六");biMap.put(5,"李七");biMap.put(4,"小小");​//通过key值得到value值(注意key里面的类型根据泛行String value= biMap.get(1);System.out.println("id为1的value值 --"+value); //打印结果: id为1的value值 --张三​//通过value值得到key值int key= biMap.inverse().get("张三");System.out.println("value为张三的key值 --"+key); //打印结果: value为张三的key值 --1​//通过key值重复,那么vakue值会被覆盖。String valuename= biMap.get(4);System.out.println("id为4的value值 --"+valuename);//打印结果: id为4的value值 --小小

BiMap的常用实现有:

1、HashBiMap: key 集合与 value 集合都有 HashMap 实现

2、EnumBiMap: key 与 value 都必须是 enum 类型

3、ImmutableBiMap: 不可修改的 BiMap

九、玩转FileUtils-文件操作工具类

文件操作工具类提供一系列方法,可以让我们快速读取写入文件。

快速实现文件/文件夹拷贝操作 ,FileUtils.copyDirectory/FileUtils.copyFile

1、获取指定文件夹上所有文件

1
vbnet复制代码// 按照指定文件后缀如java,txt等去查找指定文件夹下的文件File directory = new File("E:\\test");FileUtils.listFiles(directory, new String[]{"txt"}, false);

2、读取该文件所有行

1
ini复制代码// 读取指定文件所有行 不需要使用 while 循环读取流了List<String> lines = FileUtils.readLines(fileA)

3、写文件

1
vbnet复制代码// 1、 向一个文件写入一段文字FileUtils.write(new File("D:/a/1.txt"), "文件内容", "UTF-8", true);    // 2、以追加的方式写入FileUtils.writeStringToFile(new File("D:/a/1.txt"), "author:apple", "UTF-8", true);    //3、写入多行List<String> list= new ArrayList<String>();list.add("第一行");list.add("第二行");FileUtils.writeLines(new File("D:/a/1.txt"), list, true);

4、读文件

1
vbscript复制代码//读文件  System.out.println(FileUtils.readFileToString(new File("D:/a/1.txt"), "UTF-8"));​//返回一个list System.out.println(FileUtils.readLines(new File("D:/a/1.txt"), "UTF-8"));

5、删除文件/文件夹

1
sql复制代码// 删除文件夹 FileUtils.deleteDirectory(new File("D:/a"));        // 文件夹不是空仍然可以被删除,永远不会抛出异常  FileUtils.deleteQuietly(new File("D:/a"));

6、复制文件

1
sql复制代码//结果是a和a1在同一目录  FileUtils.copyDirectory(new File("D:/a"), new File("D:/a1"));   ​//结果是将a拷贝到a2下  FileUtils.copyDirectoryToDirectory(new File("D:/a"), new File("D:/a2"));        //拷贝文件  方式1 FileUtils.copyFile(new File("d:/1.xml"), new File("d:/1.xml.bak"));  //拷贝文件  方式2Writer write = new FileWriter("D:/abc_bak.txt");InputStream ins = new FileInputStream(new File("D:/abc.txt"));IOUtils.copy(ins, write);write.close();IOUtils.closeQuietly(ins);​​//拷贝文件到目录中  FileUtils.copyFileToDirectory(new File("d:/1.xml"), new File("d:/a"));      //拷贝url到文件  FileUtils.copyURLToFile(new URL("http://www.a.com/1.xml"), new File("d:/1.xml"));//可实现快速下载URL url = new URL("http://hzf-image-test.oss-cn-beijing.aliyuncs.com/hr_image/HF306268301810/1513632067664AbIB40pv_defalut.JPG?x-oss-process=image/resize,h_400");File file = new File("/Users/jjs/Desktop/pic.jpg");FileUtils.copyURLToFile(url, file);

7、移动文件

1
sql复制代码//移动文件 或 文件夹  //static void moveDirectory(File srcDir, File destDir)   FileUtils.moveDirectory(new File("D:/a1"), new File("D:/a2")); //注意这里 第二个参数文件不存在会引发异常      //static void moveDirectoryToDirectory(File src, File destDir, boolean createDestDir)   FileUtils.moveDirectoryToDirectory(new File("D:/a2"), new File("D:/a3"), true);      /* 上面两个方法的不同是:  * moveDirectory:D:/a2里的内容是D:/a1的内容。  * moveDirectoryToDirectory:D:/a2文件夹移动到到D:/a3里  */

8、实现快速下载文件

1
vbnet复制代码//下载方式1URL url = new URL("http://www.baidu.com/img/baidu_logo.gif");File file = new File("/Users/jjs/Desktop/baidu1.gif");FileUtils.copyURLToFile(url, file);    //下载方式2InputStream in = new URL("http://www.baidu.com/img/baidu_logo.gif").openStream();byte[] gif = IOUtils.toByteArray(in);FileUtils.writeByteArrayToFile(new File("D:/baidu2.gif"), gif);IOUtils.closeQuietly(in);​//下载方式3InputStream in3 = new URL("http://www.baidu.com/img/baidu_logo.gif").openStream();byte[] gif3 = IOUtils.toByteArray(in3);IOUtils.write(gif3, new FileOutputStream(new File("D:/baidu3.gif")));IOUtils.closeQuietly(in3);

专注Java技术进阶,系统设计,计算机网络,数据结构算法,操作系统,设计模式,计算机组成原理等等更多精彩内容,尽情期待

本文转载自: 掘金

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

Xv6 Lab Utilities Xv6 Lab Util

发表于 2021-11-22

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

0072Vf1pgy1foxlhvnh9qj31hc0u0tmu.jpg

Xv6 Lab Utilities

6.S081 Lab 1: Xv6 and Unix utilities

参考: Lab: Xv6 and Unix utilities

sleep

Implement the UNIX program sleep for xv6; your sleep should pause for a user-specified number of ticks. A tick is a notion of time defined by the xv6 kernel, namely the time between two interrupts from the timer chip. Your solution should be in the file user/sleep.c.

Some hints:

  • Before you start coding, read Chapter 1 of the xv6 book.
  • Look at some of the other programs in user/ (e.g., user/echo.c, user/grep.c, and user/rm.c) to see how you can obtain the command-line arguments passed to a program.
  • If the user forgets to pass an argument, sleep should print an error message.
  • The command-line argument is passed as a string; you can convert it to an integer using atoi (see user/ulib.c).
  • Use the system call sleep.
  • See kernel/sysproc.c for the xv6 kernel code that implements the sleep system call (look for sys_sleep), user/user.h for the C definition of sleep callable from a user program, and user/usys.S for the assembler code that jumps from user code into the kernel for sleep.
  • Make sure main calls exit() in order to exit your program.
  • Add your sleep program to UPROGS in Makefile; once you’ve done that, make qemu will compile your program and you’ll be able to run it from the xv6 shell.
  • Look at Kernighan and Ritchie’s book The C programming language (second edition) (K&R) to learn about C.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
c复制代码#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int
main(int argc, char *argv[])
{
int ticks; // time to sleep

if(argc <= 1){
fprintf(2, "usage: sleep ticks\n");
exit(1);
}
ticks = atoi(argv[1]);

sleep(ticks);

exit(0);
}

build & run:

1
2
3
4
5
6
sh复制代码$ make qemu
...
init: starting sh
$ sleep 10
(nothing happens for a little while)
$

pingpong

Write a program that uses UNIX system calls to ‘’ping-pong’’ a byte between two processes over a pair of pipes, one for each direction. The parent should send a byte to the child; the child should print “<pid>: received ping“, where <pid> is its process ID, write the byte on the pipe to the parent, and exit; the parent should read the byte from the child, print “<pid>: received pong“, and exit. Your solution should be in the file user/pingpong.c.

Some hints:

  • Use pipe to create a pipe.
  • Use fork to create a child.
  • Use read to read from the pipe, and write to write to the pipe.
  • Use getpid to find the process ID of the calling process.
  • Add the program to UPROGS in Makefile.
  • User programs on xv6 have a limited set of library functions available to them. You can see the list in user/user.h; the source (other than for system calls) is in user/ulib.c, user/printf.c, and user/umalloc.c.
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 "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int
main(int argc, char *argv[])
{
int p[2]; // file descriptors for pipe
char recv_buf[128];

pipe(p);

if (fork() == 0) { // child
read(p[0], recv_buf, 4);
close(p[0]);

printf("%d: received %s\n", getpid(), recv_buf);

write(p[1], "pong", 4);
close(p[1]);

exit(0);
} else { // parent
write(p[1], "ping", 4);
close(p[1]);

read(p[0], recv_buf, 4);
close(p[0]);

printf("%d: received %s\n", getpid(), recv_buf);

exit(0);
}
}

Result:

1
2
3
4
5
6
sh复制代码$ make qemu
...
init: starting sh
$ pingpong
4: received ping
3: received pong

primes

Write a concurrent version of prime sieve using pipes. This idea is due to Doug McIlroy, inventor of Unix pipes. The picture halfway down this page and the surrounding text explain how to do it. Your solution should be in the file user/primes.c.

Your goal is to use pipe and fork to set up the pipeline. The first process feeds the numbers 2 through 35 into the pipeline. For each prime number, you will arrange to create one process that reads from its left neighbor over a pipe and writes to its right neighbor over another pipe. Since xv6 has limited number of file descriptors and processes, the first process can stop at 35.

Some hints:

  • Be careful to close file descriptors that a process doesn’t need, because otherwise your program will run xv6 out of resources before the first process reaches 35.
  • Once the first process reaches 35, it should wait until the entire pipeline terminates, including all children, grandchildren, &c. Thus the main primes process should only exit after all the output has been printed, and after all the other primes processes have exited.
  • Hint: read returns zero when the write-side of a pipe is closed.
  • It’s simplest to directly write 32-bit (4-byte) ints to the pipes, rather than using formatted ASCII I/O.
  • You should create the processes in the pipeline only as they are needed.
  • Add the program to UPROGS in Makefile.
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
c复制代码#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

#define MAX 36
#define FIRST_PRIME 2

int generate_natural(); // -> out_fd
int prime_filter(int in_fd, int prime); // -> out_fd

int
main(int argc, char *argv[])
{
int prime;

int in = generate_natural();
while (read(in, &prime, sizeof(int))) {
// printf("prime %d: in_fd: %d\n", prime, in); // debug
printf("prime %d\n", prime);
in = prime_filter(in, prime);
}

exit(0);
}

// 生成自然数: 2, 3, 4, ..< MAX
int
generate_natural() {
int out_pipe[2];

pipe(out_pipe);

if (!fork()) {
for (int i = FIRST_PRIME; i < MAX; i++) {
write(out_pipe[1], &i, sizeof(int));
}
close(out_pipe[1]);

exit(0);
}

close(out_pipe[1]);

return out_pipe[0];
}

// 素数筛
int
prime_filter(int in_fd, int prime)
{
int num;
int out_pipe[2];

pipe(out_pipe);

if (!fork()) {
while (read(in_fd, &num, sizeof(int))) {
if (num % prime) {
write(out_pipe[1], &num, sizeof(int));
}
}
close(in_fd);
close(out_pipe[1]);

exit(0);
}

close(in_fd);
close(out_pipe[1]);

return out_pipe[0];
}

这个程序是参考 《Go语言高级编程》1.6 常见的并发模式 中的那个 Golang 版本写的。Golang 的并发模型和 UNIX Pipe 本身就很像(refer: Effective Go: Share by communicating),这里只需把 chan 换成 pipe,Goroutine 换成 fork 的进程。但是,一定要、一定要、一定要注意那些在子进程中使用的文件描述符,父进程不用就要关了,不然就凉了。

运行·结果:

1
2
3
4
5
6
7
8
9
10
11
12
sh复制代码$ primes
prime 2
prime 3
prime 5
prime 7
prime 11
prime 13
prime 17
prime 19
prime 23
prime 29
prime 31

find

Write a simple version of the UNIX find program: find all the files in a directory tree with a specific name. Your solution should be in the file user/find.c.

Some hints:

  • Look at user/ls.c to see how to read directories.
  • Use recursion to allow find to descend into sub-directories.
  • Don’t recurse into “.” and “..”.
  • Changes to the file system persist across runs of qemu; to get a clean file system run make clean and then make qemu.
  • You’ll need to use C strings. Have a look at K&R (the C book), for example Section 5.5.
  • Note that == does not compare strings like in Python. Use strcmp() instead.
  • Add the program to UPROGS in Makefile.
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
c复制代码#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"
#include "kernel/fcntl.h"

char*
fmtname(char *path)
{
char *p;

// Find first character after last slash.
for(p=path+strlen(path); p >= path && *p != '/'; p--)
;
p++;

return p;
}

void
find(char *path, char *targetname)
{
char buf[512], *p;
int fd;
struct dirent de;
struct stat st;

if (!strcmp(fmtname(path), targetname)) {
printf("%s\n", path);
}

if ((fd = open(path, O_RDONLY)) < 0) {
fprintf(2, "find: cannot open [%s], fd=%d\n", path, fd);
return;
}

if (fstat(fd, &st) < 0) {
fprintf(2, "find: cannot stat %s\n", path);
close(fd);
return;
}

if (st.type != T_DIR) {
close(fd);
return;
}

// st.type == T_DIR

if (strlen(path) + 1 + DIRSIZ + 1 > sizeof buf) {
printf("find: path too long\n");
close(fd);
return;
}
strcpy(buf, path);
p = buf+strlen(buf);
*p++ = '/';
while (read(fd, &de, sizeof(de)) == sizeof(de)) {
if (de.inum == 0)
continue;
memmove(p, de.name, DIRSIZ);
p[DIRSIZ] = 0;

if (!strcmp(de.name, ".") || !strcmp(de.name, ".."))
continue;

find(buf, targetname);
}
close(fd);
}

int
main(int argc, char *argv[])
{
if(argc < 3){
fprintf(2, "usage: find path filename\n");
exit(1);
}

find(argv[1], argv[2]);

exit(0);
}

主要就是抄 user/ls.c。这个指针玩的,,太骚了[捂脸]。C 还是有意思啊。

结果:

1
2
3
4
5
6
sh复制代码$ echo > b
$ mkdir a
$ echo > a/b
$ find . b
./b
./a/b

xargs

Write a simple version of the UNIX xargs program: read lines from the standard input and run a command for each line, supplying the line as arguments to the command. Your solution should be in the file user/xargs.c.

The following example illustrates xarg’s behavior:

1
2
3
sh复制代码$ echo hello too | xargs echo bye
bye hello too
$

Note that the command here is “echo bye” and the additional arguments are “hello too”, making the command “echo bye hello too”, which outputs “bye hello too”.

Please note that xargs on UNIX makes an optimization where it will feed more than argument to the command at a time. We don’t expect you to make this optimization. To make xargs on UNIX behave the way we want it to for this lab, please run it with the -n option set to 1. For instance

1
2
3
4
sh复制代码$ echo "1\n2" | xargs -n 1 echo line
line 1
line 2
$

Some hints:

  • Use fork and exec to invoke the command on each line of input. Use wait in the parent to wait for the child to complete the command.
  • To read individual lines of input, read a character at a time until a newline (‘\n’) appears.
  • kernel/param.h declares MAXARG, which may be useful if you need to declare an argv array.
  • Add the program to UPROGS in Makefile.
  • Changes to the file system persist across runs of qemu; to get a clean file system run make clean and then make qemu.

xargs, find, and grep combine well:

1
sh复制代码$ find . b | xargs grep hello

will run “grep hello” on each file named b in the directories below “.”.

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
c复制代码#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/param.h" // MAXARG

#define is_blank(chr) (chr == ' ' || chr == '\t')

int
main(int argc, char *argv[])
{
char buf[2048], ch;
char *p = buf;
char *v[MAXARG];
int c;
int blanks = 0;
int offset = 0;

if(argc <= 1){
fprintf(2, "usage: xargs <command> [argv...]\n");
exit(1);
}

for (c = 1; c < argc; c++) {
v[c-1] = argv[c];
}
--c;

while (read(0, &ch, 1) > 0) {
if (is_blank(ch)) {
blanks++;
continue;
}

if (blanks) { // 之前有过空格
buf[offset++] = 0;

v[c++] = p;
p = buf + offset;

blanks = 0;
}

if (ch != '\n') {
buf[offset++] = ch;
} else {
v[c++] = p;
p = buf + offset;

if (!fork()) {
exit(exec(v[0], v));
}
wait(0);

c = argc - 1;
}
}

exit(0);
}

主要是字符串操作麻烦。。

运行:

1
2
3
4
5
6
7
8
sh复制代码$ echo hello too | xargs echo bye
bye hello too
$ find . b
./b
./a/b
$ find . b | xargs echo hello
hello ./b
hello ./a/b

参考

  1. MIT. Lab guidance. pdos.csail.mit.edu/6.S081/2020…
  2. MIT. Lab: Xv6 and Unix utilities. pdos.csail.mit.edu/6.S081/2020…
  3. KatyuMarisa. MIT 6.S081 xv6调试不完全指北. www.cnblogs.com/KatyuMarisa…
  4. EASTON MAN. xv6操作系统实验 – 质数筛. blog.eastonman.com/blog/2020/1…
  5. ABCDLSJ. MIT 6.828 Lab 1: Xv6 and Unix utilities. abcdlsj.github.io/post/mit-6.…

By CDFMLR

顶部图片来自于网络,系随机选取的图片,仅用于检测屏幕显示的机械、光电性能,与文章的任何内容及观点无关,也并不代表本人局部或全部同意、支持或者反对其中的任何内容及观点。如有侵权,联系删除。

本文转载自: 掘金

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

什么是 Python 编程语言?

发表于 2021-11-22

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

🌊 作者主页:海拥

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

🌊 粉丝福利:粉丝群 每周送六本书,不定期送各种小礼品

Python是世界上最流行的解释型编程语言之一。Python 由 Guido van Rossum 设计,作为“ABC”编程语言的继承者,于 1991 年首次发布。它是一种高级通用语言,其设计理念是通过使用缩进来强调代码的可读性。Python 的语言结构旨在帮助程序员为小型和大型项目编写逻辑代码。

该语言是动态类型的,支持多种编程范式。它完全支持面向对象和结构化编程,它的一些特性支持函数式和面向方面的编程。

Python 被设计为一种高度可扩展的语言。这种模块化使得它作为一种向已经存在的应用程序添加接口的方式非常流行。由于其全面的标准库,它通常被描述为“包含电池”的语言。我们需要感谢 ABC 提供的这个特性,因为 Python 的设计者对于一个拥有大型标准库的小型核心语言的愿景源于他对 ABC 语言的挫败感,而 ABC 语言是基于相反的方法。

语法

Python 的格式在视觉上很整洁,并且经常使用关键字;然而,许多其他语言依赖于标点符号。Python 的另一个显着区别是它不使用大括号来分隔块。与 C 等其他语言相比,它的语法异常和特殊情况要少得多。

缩进

Python 使用空格缩进来分隔块。在某些语句之后可能会增加缩进,但缩进的减少意味着程序段的结束。这使得程序的视觉结构准确地表示了程序的语义结构。

此功能称为“越位规则”。它可能不是python独有的,而是带有语义的;另一方面,大多数语言的缩进没有任何语义意义。

变量

在 Python 中,变量名是一个引用持有者,没有与之关联的固定数据类型。它们可以随时反弹到任何物体上。尽管在给定时间,变量将引用某个对象,该对象本身具有类型。这称为动态类型。

这与 Java、C++、FORTRAN、Scala 和 Pascal 等语言完全相反,因为它们是静态类型的编程语言,其中每个变量只能包含特定类型的值。

表达式

表达式是编程语言中的语法实体,可以对其进行评估以确定其值。它是编程语言解释和计算以产生值的常量、变量、函数和运算符的组合。

在 Python 中,表达式和语句是有区别的。也就是说,语句不能是表达式的组成部分。这个特性并不常见,因为它在一些主要语言中没有,比如 Scheme、Common Lisp 或 Ruby。但是这会导致重复某些功能。

类型

Python 使用鸭子类型,这是一种用于确定对象是否可以用于特定目的的应用程序。在这种语言中,编译时不检查类型约束。对对象执行操作失败意味着给定的对象不是合适的类型。

Python 是一种强类型语言,因此它不允许定义不明确的操作,而不是默默地尝试理解它们。

它允许程序员使用类定义自己的类型。可以通过调用类来构造类的新实例。

方法

这里的“方法”是与消息和对象相关联的过程。一个对象由数据和行为组成;这些包括一个接口,该接口指定对象如何被其任何消费者使用。

Python 方法有一个显式的 self 参数来访问实例数据。Python 还提供了方法,通常称为 dunder 方法,允许用户定义的类修改它们如何被本地操作处理,例如长度、比较、算术运算、类型转换等。


库

Python 有一个很大的标准库。它能够提供适用于许多任务的工具。它包括用于创建图形用户界面 (GUI)、连接到关系数据库、生成伪随机数、操作正则表达式、单元测试等的模块。

大多数标准库是跨平台的 Python 代码,因此只有少数模块需要更改或重写以进行变体实现。

Python的应用

Python 可以作为 Web 应用程序的脚本语言。有了 Web 服务器网关接口,标准 API 已经发展到可以促进这些应用程序。

NumPy、SciPy 和 Matplotlib 等库允许在科学计算中有效地使用 Python。Biopython 和 Astropy 等库提供特定领域的功能。SageMath 是一个计算机代数系统,带有可在 Python 中编程的笔记本界面。它的库可以涵盖数学的各个方面,例如代数、组合、数值数学、数论和微积分。

在 TensorFlow、Keras、Pytorch 和 Scikit-learn 等库的帮助下,Python 常用于人工智能项目和机器学习项目。Python 因其模块化架构、简单的语法和富文本处理工具而常用于自然语言处理。

Python 也可以用来创建游戏,使用 Pygame 等库可以制作 2D 游戏。

GNU Debugger 使用 Python 作为漂亮的打印机来显示复杂的结构,例如 C++ 容器。Esri 将 Python 推广为在 ArcGIS 中编写脚本的最佳选择。它已被用作 Google App Engine 中三种可用编程语言中的第一种。

许多操作系统都将 Python 作为标准组件。它随大多数 Linux 发行版一起提供,并且可以从命令行终端使用。许多 Linux 发行版使用用 Python 编写的安装程序。例如,Ubuntu 使用 Ubiquity 安装程序,而 Red Hat Linux 和 Fedora 使用 Anaconda 安装程序。

Python 还广泛用于信息安全行业,包括漏洞利用开发。


值得学习Python吗?

自 2003 年以来,Python 一直位居最流行的十大编程语言之列。它在 2007 年、2010 年、2018 年和 2020 年被评为年度编程语言,是唯一四次获得该奖项的语言。

仅在过去三年中,Python 的价值就出现了显着增长。在可预见的未来,它并没有失去它的价值。因此,学习 Python 绝对值得你花费时间和精力。

写在最后的

送三本 《Python编程从小白到大牛》
关注公众号【海拥】回复【领书】参与

18b22d088fdb874d8fac0127ca96b5f.jpg

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

我已经写了很长一段时间的技术博客,并且主要通过掘金发表,这是我的一篇关于什么是 Python 编程语言的文章。我喜欢通过文章分享技术与快乐。你可以访问我的博客: juejin.cn/user/204034… 以了解更多信息。希望你们会喜欢!😊

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

本文转载自: 掘金

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

用代码玩剧本杀?第3届83行代码大赛剧情官方解析 前言 线索

发表于 2021-11-22

简介: 由阿里云云效主办的2021年第3届83行代码挑战赛已经收官。超2万人围观,近4000人参赛,85个团队组团来战。大赛采用游戏闯关玩儿法,融合元宇宙科幻和剧本杀元素,让一众开发者玩得不亦乐乎。

说到剧本杀,很多人很好奇,代码大赛究竟是如何与剧本杀元素结合的?在关卡里面收集到的线索,到底如何串联起来?我们请来了本次代码剧本杀的主要设计者陈少滨(阿里云云效代码平台)来具体讲讲。

前言

代码挑战赛、剧本杀、元宇宙,这些词可能单独列出来你都认识,但是放到一起可能就会有点疑惑:这三有啥关系?为了使83行代码挑战赛更加有趣,我们史无前例地将剧本杀元素、元宇宙科幻元素与代码挑战赛结合,打造了一场别开生面的代码剧本杀。

既然是剧本杀,那么自然就要有线索。玩家每次通关,都能够获取code83的定制线索,其中不仅包含了关键的剧情信息,还有决赛出题人担心题目太难,特意留下的提示,最重要的是,参与的玩家还可以通过获取的线索换取云效83行代码xCHERRY定制机械键盘等豪华大礼。

下面,我们来揭秘一下这场代码剧本杀,看看你是否破获了真相。

线索设计

这次线索的设计的时候,每一条线索都包含了决赛题目相关的提示和剧情相关的提示,先给大家解释一下决赛题目相关的线索是怎么设计的,看大家有没有错过一些提示。

“<(?.*)> 可以表示什么呢?”:第一条线索是提示决赛有正则类型的bug,由于?这种用法不是特别常用,所以在线索里提前给出提示。

“NPE:No People Emo”:这个就是很直接地提示决赛里有NPE类型的问题,当决赛答题的时候,需要多关注一下有没有什么地方可能会是null,触发NPE。

“不是什么都会等你,很多事情都是一次性的”:这个是提示可能是有一些“一次性”类型的bug,如果经验比较丰富的话,可能就能意识到这个是指流类型一次性读取的问题。

“我们通常以为admin就是admin”:这其实是在提示决赛中admin可能不是admin,那可能是什么呢?可能是ADMIN,或者admin123这种,具体的大家在决赛题目中就会了解到。

“大家都喜欢全局视角,但全局视角也有它的问题”:其实是在提醒你全局类型的变量可能有问题,你可能需要把全局变量换成局部类型的变量。

“好的单元测试 => 你看懂了单元测试 = 你看懂了代码”:决赛题目中大部分的bug都是在单元测试调用的函数中,如果能聚焦地分析单元测试调用到的函数,就能把大部分的bug解决掉。

“有的bug之所以难找,就是因为它只在运行时出现”:这个是在提示有一些bug可能是无法直接看代码就能分析到的,需要结合运行时的调试和诊断,才能发现这些bug。

“别得意忘形了,快收好你的尾巴!”:这个是在提示正则匹配读取尾巴的问题。

“贪多嚼不烂”:这个是在提示正则匹配的贪婪模式的问题。

“断点调试是万能的吗?不影响应用本身的观测或许才是更优选择”:这个是在提示本次决赛的场景是无法正常使用断点调试的,所以可能需要使用应用观测的能力。

剧情设计

下面我们进行一下剧情向的解析。最开始我们收到的是这样的邀请函:

你收到了这封邀请函,但只有一个 YES 选择键,没办法拒绝,只能接受。接受了之后,你就进入了我们的故事线。在我们的故事线中,当前的时间线是这样的:

你是一个程序猿 -> 收到邀请函,进入宇宙基地 -> 进入第一扇门,存在缝隙,暗手会偷走宝贵的东西 -> 进入第二扇门,百万人名涌动,根据关键词找到对应人名 -> 进入第三扇门,重塑人生 -> 进入第四扇门,发现了自己的人生被代码定义。

这部分的剧情内容是通过 WebIDE 的剧情对话给出的,而剧情线索中,会给出过往的时间线:

高考过后,走上程序猿道路 -> 电脑中存在后门,导致公司机密泄漏,被开除 -> 接触到云晓(阿里云云效),了解云端开发 -> 开除后成为数学老师,怀念柯西(Cosy)带来的高效和充满技术感的生活 -> 疫情来临,钉钉助力在线教育 -> 意识觉醒,想用技术创造价值。

在了解了所有剧情之后,就需要回答三个问题,分别是:你来自什么宇宙、你的真实身份是什么、怎么创造生命的意义。

你来自什么宇宙,其实是在问你从哪里来;你的真实身份是什么,其实在问你是谁;怎么创造生命的意义,其实是问你要做什么,你要到哪里去。这是三个经典的人生问题。

那么具体怎么回答这三个问题呢?想要解决这三个问题,需要先将决赛关卡的bug解决完之后,会发现client的会返回一些字符串,这些字符串有三个开头(三个通道),如果根据这三个通道进行筛选,就会发现其实是这三段代码:

通过阅读这三段代码,会发现其实这三段代码正好就是过往时间线里面的三个阶段。

所以从剧情上的话,可以理解到“你”的人生其实早就被代码定义了。

**第一个问题:**你来自什么宇宙?当然毫无疑问就是在代码宇宙中。另外,从代码的包名也可以看出:

com.code.universe。

**第二个问题:**你的真实身份是什么呢?从代码中也很明确了,就是programmer(程序猿)。当然,了解完全部剧情之后,更准确的答案应该是一段代码(code)或者说一段程序(program)。

**最后一个问题:**怎么创造生命的意义?这个其实是稍微有一点难的,不像前两题这么直观,需要看完整个剧情,就会发现剧情里主要强调一点是技术的力量,第三段代码中也明确提到了在经历了人生的大转折之后,向往Technology的力量,学习了DevStudio的知识,Yunxiao的知识,Devops的知识,Cosy的知识,思考生命的价值。所以这一题的答案是技术(Technology),或者DevStudio、Yunxiao、Devops、Cosy都是可以的。

上述就是本次比赛的完整剧情了,最后还有一个附加题:

之前提到了故事线中的时间线,我们再来对比一下现实的时间线:

故事的时间线:

你是一个程序猿 -> 收到邀请函,进入宇宙基地 -> 进入第一扇门,存在缝隙,暗手会偷走宝贵的东西 -> 进入第二扇门,百万人名涌动,根据关键词找到对应人名 -> 进入第三扇门,重塑人生,最终人生(职业)未改变,过程更加丰富 -> 进入第四扇门,发现了自己的人生被代码定义 -> 发现真相,阿里云云效邀请你一起用技术创造生命的价值

现实的时间线:

你是一个程序猿 -> 你收到开赛邀请,参与83行代码大赛 -> 开启第一关,解决会造成漏洞的安全问题 -> 开启第二关,从百万级别类名中,寻找指定前缀的类名 -> 开启第三关,重构代码,最终业务逻辑不变,代码质量更好 -> 开启第四关,解决bug,解锁真相 -> 揭晓结局,阿里云云效邀请你一起用技术创造生命的价值

当我们看完这时间线之后,最后我们可以再回过来看这三个问题:你来自什么宇宙?你的真实身份是什么?

怎么创造生命的价值?这三个问题就留给大家思考了。

结语

2021年第3届83行代码挑战赛已经正式收官。你参加的,到底是一场代码挑战赛,还是一场剧本杀,亦或是?你看清真相了吗?

原文链接

本文为阿里云原创内容,未经允许不得转载。

本文转载自: 掘金

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

【ExcelUtil】实现文件写出到客户端下载全过程

发表于 2021-11-22

前言

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

需求分析

将省份详情信息作为数据源导出 Excel,要求表头为中文,全部内容宽度实现自适应,并设置工作簿名称为省份表,导出文件名设置为导出省份信息。

代码实现

首先,分析表头字段包含哪些,写对应的 VO 对象:

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
java复制代码/**
*@description: TODO
*@author: HUALEI
*@date: 2021-11-21
*@time: 19:29
*/
@Data
public class ProvinceExcelVO implements Serializable {

private static final long serialVersionUID = 877981781678377000L;

/**
* 省份
*/
private String province;

/**
* 省份的简称
*/
private String abbr;

/**
* 省份的面积(km²)
*/
private Integer area;

/**
* 省份的人口(万)
*/
private BigDecimal population;

/**
* 省份的著名景点
*/
private String attraction;

/**
* 省会的邮政编码
*/
private String postcode;

/**
* 省会名
*/
private String city;

/**
* 省会的别名
*/
private String nickname;

/**
* 省会的气候类型
*/
private String climate;

/**
* 省会的车牌号
*/
private String carcode;
}

然后,就是根据所需信息写 SQL 进行查询,具体写法参见我的文章 【MP】还在用 QueryWrapper 吗? - 掘金 (juejin.cn) ,具体写法不作赘述!

其次,写 Service 层,导出需要数据源,先得获取所有省份详情信息,作为导出 Excel 的数据源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码/**
* 获取所有省份详情信息
*
* @return 导出 Excel 数据源
*/
List<ProvinceExcelVO> getAllProvinceDetails();

/**
* 将省份详请信息以 Excel 的形式写出到客户端
*
* @param response HttpServletResponse 对象
* @param dataSource 省份详情信息数据源
* @param fileName 文件名
* @param sheetName sheet 工作表名
*
* @throws IOException IO 流异常
*/
void exportProvinceDetailsExcel(HttpServletResponse response, List<ProvinceExcelVO> dataSource, String fileName, String sheetName) throws IOException;

有数据源还不行,还得将其写入到 Excel 当中,作为一个接口,调用后客户端下载该文件,所以要用到 HttpServletResponse 设置响应头和响应体信息。

接着,实现接口中的方法,这一步很关键,导出的格式、异常亦或是数据问题都出在这个地方,一定要考虑周全!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码@Override
public List<ProvinceExcelVO> getAllProvinceDetails() {
List<Province> provinces = this.provinceMapper.selectByAll(new Province());

if (CollUtil.isNotEmpty(CollUtil.removeNull(provinces))) {
return provinces.stream()
.map(p -> {
ProvinceExcelVO provinceExcelVO = new ProvinceExcelVO();
BeanUtil.copyProperties(p, provinceExcelVO);
BeanUtil.copyProperties(p.getCapital(), provinceExcelVO);
return provinceExcelVO;
}).collect(Collectors.toList());
}
return null;
}

CollUtil.removeNull(provinces) 很关键!倘若数据库表中有空记录的话,这里不做移除就会引发 NPE 异常,这里做一个过滤操作,保险起见。

这里选择使用 BigWriter 而不是使用 Writer,主要是因为前者不容易引发内存溢出,对于大量数据的输出更为安全、可靠,虽然数据源的量并不大,用着舒心就完事了,并不用担心和 ExcelWriter 用法不一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
java复制代码@Override
public void exportProvinceDetailsExcel(HttpServletResponse response, List<ProvinceExcelVO> dataSource, String fileName, String sheetName) throws IOException {
// 默认创建 xls 格式, 通过 isXlsx => true 创建 xlsx 格式
// ExcelUtil.getWriter(true);
ExcelWriter excelWriter = ExcelUtil.getBigWriter();

// 设置表头别名
excelWriter.addHeaderAlias("province", "省份名称");
excelWriter.addHeaderAlias("abbr", "简称");
excelWriter.addHeaderAlias("area", "面积(km²)");
excelWriter.addHeaderAlias("population", "人口数量(万)");
excelWriter.addHeaderAlias("attraction", "著名景点");
excelWriter.addHeaderAlias("postcode", "邮政编码");
excelWriter.addHeaderAlias("city", "省会城市");
excelWriter.addHeaderAlias("nickname", "别称");
excelWriter.addHeaderAlias("climate", "气候类型");
excelWriter.addHeaderAlias("carcode", "车牌号");

......
......
}

设置表头别名,ExcelWriter 类提供了两种方法,一种是上面代码给出的,另一种则是通过键值对的方式进行设置,具体见 Hutool 工具不糊涂 - 掘金 (juejin.cn) ExcelUtil Excel 操作工具类,不过要注意的是使用无序的 HashMap 入参表头也跟着无序,需使用 LinkedHashMap 来保证保存的有序性。

image.png

表头别名设置好后,直接向 Excel 的 Workbook 中写入数据源即可,写入完后要响应客户端,写出文件到客户端,所以需要设置响应体和响应头,在响应头中传入导出文件名,如果是中文文件名的话,需要进行编码不能直接塞入,否则会出现文件名乱码问题,这时你就能看到弹出的下载框,点击后就能成功看到导出的数据列表了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码
excelWriter.write(dataSource, true);

// response 为 HttpServletResponse 对象
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");

// 中文文件名编码
fileName = URLEncoder.encode(fileName, CharsetUtil.UTF_8) + ".xlsx";
response.setHeader("Content-Disposition","attachment;filename="+ fileName);
// 设置 Sheet 工作表名称
excelWriter.renameSheet(sheetName);

ServletOutputStream out = response.getOutputStream();
excelWriter.flush(out, true);

// 关闭 writer,释放内存
excelWriter.close();
// 关闭输出 Servlet 流
IoUtil.close(out);

注意: 只有调用 flush 或者 close 方法后才会真正写出文件,并释放 Workbook 对象资源,否则带有数据的 Workbook 一直会常驻内存。

最后,在控制层暴露接口给前端进行调用:

1
2
3
4
5
6
7
8
9
10
11
java复制代码@GetMapping("provinces/excel/export")
public void provincesExcelExport(HttpServletResponse response) throws IOException {
// 获取数据源
List<ProvinceExcelVO> provinceExcelList = this.provinceService.getAllProvinceDetails();
// 导出文件名
String fileName = "导出省份信息";
// Sheet 工作表名称
String sheetName = "省份表";
// 导出 Excel
this.provinceService.exportProvinceDetailsExcel(response, provinceExcelList, fileName, sheetName);
}

0071E46E.gif

激动的心,颤抖的手,点击 SEND 按钮:

image.png

不看不知道,一看吓一跳:

image.png

摆在我眼前的问题该如何去解决呢?

一行代码 excelWriter.autoSizeColumnAll(); ??并不能设置所有的列框为自动根据内容进行调整,于是被度娘告知需开启自动跟踪所有列自动调整大小,所以我们首先的获取当前 Sheet:

1
2
3
java复制代码
// 获取 sheet 表
SXSSFSheet sheet = (SXSSFSheet) excelWriter.getSheet();

这里需要将 Sheet 强转成 SXSSFSheet 类型,开启方法是属于 SXSSFSheet 类的

1
2
3
4
5
6
java复制代码
// 开启跟踪工作表中的所有列,以便自动调整大小
sheet.trackAllColumnsForAutoSizing();

// 列宽自适应,只有开启后才会生效
excelWriter.autoSizeColumnAll();

解决中文自适应宽度不足的问题,对已定义的每一列设置列宽:

1
2
3
4
5
6
7
8
9
10
java复制代码
// 获取表头行,作为数据的所属列
if (sheet.getRow(1) != null) {
// 获取表头已定义单元格的数目
int physicalNumberOfCells = sheet.getRow(1).getPhysicalNumberOfCells();
for (int i = 0; i < physicalNumberOfCells; i++) {
// 对已定义的每一列设置列宽,解决中文自适应宽度不足的问题
sheet.setColumnWidth(i, sheet.getColumnWidth(i) * 17 / 10);
}
}

接口测试

导出测试.gif

完美,撒花 ✿✿ヽ(°▽°)ノ✿

总结 & 思考

虽然实现了预期需求,但是我觉得还不够完美,代码不够优雅、简洁,并不具备通用性!!

如果导出的是其他数据列表呢,上述代码也不能照搬照抄的呀,所以说复用性较差,为了能优雅地对指定数据类型进行表格的导出,就必须对 Hutool 中的 ExcelUtil 进行二次封装或者自己站在 POI 包上造轮子,考虑到发量我毅然决然选择第一种方案,这波直接站在巨人的巨人的肩膀上进行开发。

20190825677236_pFKAVu.gif

考虑到 导出的效率 、 功能扩展性 以及 使用便捷性 ,在封装的基础上写一个自定义 @Excel 注解,实现一“解”多用,麻麻再也不用担心我不会写 Excel 导出接口了 (^-^)V

具体实现代码详解,请参考 【ExcelUtil】二次封装,注解驱动,用起来不要太舒服! - 掘金 (juejin.cn) 。

对 Hutool 工具包中常用工具类(ExcelUtil…)有疑问,移步至 Hutool 工具不糊涂 - 掘金 (juejin.cn) 。

结尾

撰文不易,欢迎大家点赞、评论,你的关注、点赞是我坚持的不懈动力,感谢大家能够看到这里!Peace & Love。

本文转载自: 掘金

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

Netty编程(二)—— EventLoop

发表于 2021-11-22

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

EventLoop和EventLoopGroup

事件循环对象 EventLoop 本质是一个单线程执行器(同时维护了一个 Selector),里面有 run 方法处理一个或多个 Channel 上源源不断的 IO 事件。

事件循环组 EventLoopGroup 是一组 EventLoop,Channel 一般会调用 EventLoopGroup 的 register 方法来绑定其中一个 EventLoop,后续这个 Channel 上的 IO 事件都由此 EventLoop 来处理(保证了 IO 事件处理时的线程安全)。

EventLoop的使用

创建EventLoopGroup:

1
2
ini复制代码EventLoopGroup group = new NioEventLoopGroup();
EventLoopGroup group = new DefaultEventLoopGroup( );

第一种NioEventLoopGroup是用来处理io事件、普通任务和定时任务,第二种DefaultEventLoopGroup只能处理普通任务和定时任务,在新建时可以传入一个int数表示group中的线程数,否则使用默认的线程数。

此外可以使用group.next()来获取下一个LoopGroup对象,使用execute()方法执行普通任务,使用 scheduleAtFixedRate()方法来处理定时任务

使用 shutdownGracefully() 方法来关闭EventLoopGroup。该方法会首先切换 EventLoopGroup 到关闭状态从而拒绝新的任务的加入,然后在任务队列的任务都处理完成后,停止线程的运行。从而确保整体应用是在正常有序的状态下退出的。

下面是一段操作 EventLoopGroup 的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
java复制代码public static void main(String[] args) {
EventLoopGroup group1 = new NioEventLoopGroup(3);
EventLoopGroup group2 = new DefaultEventLoopGroup(2);


System.out.println(group1.next());
System.out.println(group2.next());

group1.next().execute(()->{
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"hello "+i);
}
});


group2.next().scheduleAtFixedRate(()->{
System.out.println(Thread.currentThread().getName()+" hello2");
},0,1, TimeUnit.SECONDS);

group1.shutdownGracefully();
group2.shutdownGracefully();

}

代码结果:

在这里插入图片描述

处理IO事件

网络通信中最常见的还是要处理IO事件,可以使用EventLoopGroup来处理IO事件,代码与Netty编程(一)—— 初识Netty+超全注释 - 掘金 (juejin.cn)中的基础代码类似,重写读入函数完成

服务端代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码public class HelloServer {
public static void main(String[] args) {
new ServerBootstrap()
.group(new NioEventLoopGroup(3))
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {//添加handler
nioSocketChannel.pipeline().addLast(new StringDecoder());
nioSocketChannel.pipeline().addLast(new SimpleChannelInboundHandler<String>() {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
System.out.println(Thread.currentThread().getName()+" "+s);
}
});
}
}).bind(8080);
}
}

客户端代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码public class HelloClient {
public static void main(String[] args) throws InterruptedException {
new Bootstrap()
.group(new NioEventLoopGroup())
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel channel) throws Exception {
channel.pipeline().addLast(new StringEncoder());
}
})
.connect(new InetSocketAddress("localhost", 8080))
.sync()
.channel()
.writeAndFlush("hello world"+" "+Thread.currentThread().getName());
}
}

需要注意的是:一旦客户端和服务端建立连接,channel就和事件循环组中的某一个eventloop进行绑定了,即之后的该channel的读写事件都由这个eventloop负责,下面的图说明了这一过程,每个channel的所有事件都被同一个EventLoop处理。

在这里插入图片描述

分工细化

细化1

可以把事件循环组的EventLoop分工得更加细一些,即让一个EventLoop处理accept事件,其他的EventLoop处理读写事件。Bootstrap的group()方法可以传入两个EventLoopGroup参数,分别负责处理不同的事件。两个Group,分别为 Boss 负责serversocketchannel上的Accept事件,Worker 负责socketchannel上的读写事件

1
2
3
4
5
6
7
8
java复制代码public class MyServer {
public static void main(String[] args) {
new ServerBootstrap()
.group(new NioEventLoopGroup(1), new NioEventLoopGroup(2))

...
}
}

细化2

EventLoop虽然可以做到多路复用,但是如果有一个读写事件耗时过长,会影响这个EventLoop下的其他事件的进行,因此可以创建一个独立的EventLoopGroup处理耗时较长的事件,当有的任务需要较长的时间处理时,可以使用非NioEventLoopGroup,避免同一个NioEventLoop中的其他Channel在较长的时间内都无法得到处理。

那么就会有个问题,创建的事件循环组如何与耗时较长的handle联系起来?:在调用addLast()方法时可以传递进来,addLast()有三个参数:事件循环组(空则默认为上方建立的),循环组名称,处理函数。

下面以一个例子来说明,假设服务器端接收客户端的消息后需要6s去处理(休眠6s),那么这个休眠的事件可以放在新的EventLoopGroup中去处理,此外连续打开10个客户端连接服务端测试效果,客户端代码与上方的客户端类似,唯一不同的是使用for循环去连续连接服务端10次。

下面是服务端代码,加入了两个handler,第一个是默认的EventLoopGroup(当前ServerBootstrap的EventLoopGroup),并且使用ctx.fireChannelRead(msg)将msg传给第二个handler,第二个使用的是新建的EventLoopGroup去处理耗时较长的事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
java复制代码public class MyServer {
public static void main(String[] args) {
EventLoopGroup group = new DefaultEventLoopGroup(3);

new ServerBootstrap()
.group(new NioEventLoopGroup(), new NioEventLoopGroup(2))
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {

@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast("nioHandler",new ChannelInboundHandlerAdapter(){

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println("nioHandler"+Thread.currentThread().getName() + " " + buf.toString(StandardCharsets.UTF_8));
// 将消息传递给下一个handler
ctx.fireChannelRead(msg);
}
}).addLast(group,"myhandler",new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
Thread.sleep(5000);
System.out.println("myhandler"+Thread.currentThread().getName() + " " + buf.toString(StandardCharsets.UTF_8));
}
});
}
})
.bind(8080);
}
}

结果如下,可以看到第二个handler把并没有把第一个handler阻塞住:

在这里插入图片描述

如何切换

上面说到了在处理事件时可以从一个EventLoopGroup切换到另一个EventLoopGroup,另一个handler专门处理耗时较长的事件,降低对其他事件造成的影响,那么netty内部是怎么做到不同的EventLoopGroup切换呢?

在这里插入图片描述

上面的图描述的就是切换EventLoopGroup,当handler中绑定的EventLoopGroup不同时,需要切换EventLoopGroup来执行不同的任务,具体来说netty是使用下面这个方法进行切换:

  • 如果两个 handler 绑定的是同一个EventLoopGroup,那么就直接调用;否则,把要调用的代码封装为一个任务对象,由下一个 handler 的 EventLoopGroup 来调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);

EventExecutor executor = next.executor(); // 获得下一个EventLoop, excutor 即为 EventLoopGroup

// 如果下一个EventLoop 在当前的 EventLoopGroup中
if (executor.inEventLoop()) {//当前handler中的线程是否和eventloop是同一个线程
// 使用当前 EventLoopGroup 中的 EventLoop 来处理任务
next.invokeChannelRead(m);
} else {
// 否则让另一个 EventLoopGroup 中的 EventLoop 来创建任务并执行
executor.execute(new Runnable() {//此时需要在下一个线程中执行
public void run() {
next.invokeChannelRead(m);
}
});
}
}

本文转载自: 掘金

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

1…238239240…956

开发者博客

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