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

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


  • 首页

  • 归档

  • 搜索

Mysql在什么情况下会使用内部临时表?

发表于 2021-10-24

union执行

为了便于分析,使用一下sql来进行举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
sql复制代码CREATE TABLE t1 ( id INT PRIMARY KEY, a INT, b INT, INDEX ( a ) );

delimiter ;;
CREATE PROCEDURE idata ( ) BEGIN
DECLARE
i INT;
SET i = 1;
WHILE
( i <= 1000 ) DO
INSERT INTO t1
VALUES
( i, i, i );
SET i = i + 1;
END WHILE;
END;;

delimiter ;
CALL idata ( );

然后我们执行以下sql

1
sql复制代码(select 1000 as f) union (select id from t1 order by id desc limit 2);

这段sql的语义是,取两个子查询的的并集,并且去重
接着我们通过explain分析对应的执行计划

image.png
可以看到,第二行的key是primary,说明第二个子查询使用索引id。第三行的Extra字段,表示在子查询union的时候,使用了临时表(Using temporary)。

这个语句的执行流程是这样的:

1)创建一个内存临时表,这个临时表只有一个整形字段f,并且f是主键字段

2)执行第一个子查询,将1000存在临时表

3)执行第二个子查询,拿到第一行id=1000,并试图插入到临时表,但是由于1000这个值已经存在临时表了,违法了唯一性约束,所以插入失败,接着取到第二行数据999,插入临时表成功

4)从临时表中按行取出数据,返回结果,并删除临时表,结果中包含两条数据就是1000和999

可以看到,临时表起到了暂存数据的作用,而且存在唯一性约束,实现了union去重的语义

group by

另外一个常见的使用临时表的例子就是group by,我们看一下以下sql

1
csharp复制代码select id%10 as m, count(*) as c from t1 group by m;

这个语句就是根据t1表的数据,根据id%10进行分组,并按照m的结果排序后输出
接着我们分析sql对应的执行计划

1635084988(1).jpg
在Extra字段中,我们看到了三个信息:

1)Using index,表示这个语句使用了覆盖索引,选择了索引 a;

2)Using temporary,表示使用了临时表;

3)Using filesort,表示需要排序;

这个语句的执行流程是这样的:

1)创建内存临时表,表里有字段m和c,主键是m;

2)扫描表t1的索引a,依次取出叶子节点上面的id值,计算id%10的结果,记为x;
如果临时表没有主键x,就插入一个记录(x,1);
如果表中有主键x的行,就将x这一行的c值加1;

3)遍历完成之后,再根据字段m做排序,得到结果

内存临时表的大小是有限制的,参数tmp_table_size就是控制这个内存大小的,默认是16M,如果内存临时表大小达到了上线,这时候就会把内存临时表转成磁盘临时表,磁盘临时表的默认引擎是InnoDB,如果表的数据量很大,很可能查询就会占用大量的磁盘空间

本文转载自: 掘金

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

「 性能测试技术笔记系列(二)」之性能测试工具选型|收藏版

发表于 2021-10-24
  • 💜大家好,我是陈大圣,来自重庆,非科班出身,半路出家的程序员。
  • 💜崇尚自由、开源和分享。
  • 💜目前还没有掘金任何创作头衔,努力前行,总会成为自己心中的那道光,加油

一、浅谈为什么需要工具

我们来看下工具的定义:它原指工作时所需用的器具,后引申为为达到、完成或促进某一事物的手段。(—来自百度的解释)

1、从人类进化的角度来看,会制造并使用工具是人和猿人最根本的区别,因为工具可以帮助我们提高生产力和效率。
image.png

2、想象下如果不使用工具进行性能测试会怎么样?

我们可以从性能测试的定义的角度来分析 , 性能测试是指通过自动化的测试工具模拟多种正常、峰值以及异常负载条件来对系统的各项性能指标进行测试。如果不使用工具,仅靠人工进行性能测试会存在以下的弊端:

a)测试需要投入大量的资源:

为了模拟多种负载、并发的场景需要多人协同工作,通常测试没有很多的资源,而且就算有资源人工的效果也会大打折扣,甚至于某些场景仅凭人工是无法完成的。

b)可重复性非常差:

性能测试经常需要反复调优和测试执行,如果没有工具的帮助,全靠人工实在不敢想象。

c)测试准确性较差:

由于需要模拟多种负载和并发场景,如果由人工来操作,难免会存在误差,而且相对工具或程序来说这种误差会更大,对测试结果影响也非常大。

d)结果的收集、整理和呈现形式差:

如果没有工具,全凭人工采集数据相对工具来说也会存在较大的误差。

二、性能测试与性能测试工具的关系

1、性能测试从测试阶段来划分属于系统测试,其和具体使用什么工具并没有直接的关系。使用工具只是为了提高性能测试效率和准确性的一种方法和手段。从本质上来看,同做其它事情时使用工具没有什么实质性的区别。

2、性能测试不等于Loadrunner,LR只是性能测试工具其中的一种,而且它也不是万能的,在某些情况下它也并不能派上用场。

3、自动化测试工具与性能测试工具的区别:性能测试工具一般是基于通信协议的(客户器与服务器交换信息所遵守的约定),它可以不关心系统的UI,而自动化使用的是对象识别技术,关注UI界面。自动化无法或很难造成负载,但是通过协议很容易。

三、性能测试工具选型参考:

通常在公司或项目中,我们选择任何工具时都会做一些调研,目的就是为了选择适合公司或项目的工具。那么性能测试工具也不例外,通常可以从以下几个方面进行考虑:

1、成本方面:

a)工具成本:工具通常分为商业闭(shou)源(fei)和非商业开(mian)源(fei)两种,商业工具通常功能比较强大,收费,由于收费所以可提供售后服务,如果出了问题有专业人士帮忙处理。而开源工具通常是免费的,功能有限,维护工具的组织也是自发的,所以如果碰到问题需要自行解决,没有专人提供服务。具体选择商业还是开源的工具,需要根据公司的情况,比如公司规模、愿意承担的成本、项目综合情况等方面考虑。一般来看大公司通常可以承担的起工具的费用,会考虑购买商业工具。而小公司由于资金压力,可能会选择开源的工具。

b)学习成本:使用任何工具都需要进行学习,这样一来就会产生学习成本(比如:时间),因此我们在选择工具时也需要考虑到项目组成员的学习成本。如果有两种工具A和B都能满足项目组测试的需求,如果A工具大部分人都会使用,而B工具只有极少部分人会使用,那么建议优先考虑A工具。通常,对于测试人员最好熟悉一款流程的商业(性能)工具,一款开源免费(性能)工具,还需要熟悉常见的(性能)脚本开发语言等,这是基本要求。

2、支持的协议:

性能测试通常跟协议联系非常紧密,比如B/S的系统通常使用http协议进行客户端和服务器商的信息交换,C/S的系统通常使用socket协议进行信息交换。在选择工具时,需要考虑项目使用的协议。一个测试工具能否满足测试需求,能否达到令人满意的测试结果,是选择测试工具要考虑的最基本的问题。

3、生命力:

现在的性能测试工具非常多,比如LR,jmeter这类较大众的工具网上相关的资料非常多,但一些小众工具可能网上资料比较少。如果在工具使用过程中碰到了比较极手的问题,在录求解决方案或帮助时,大众的的工具相对来说会比较有优势一点,毕竟使用的人越多,资料越多,那么自己碰到的问题也许别人早就碰到并解决了,即时之前没有人碰到过,由于使用研究的人多,通过社区或论坛的帮助相信总会有高手能协助解决的。

4、跨平台:

这一点自不必多说,看看JAVA为什么一直在流行就知道了。

5、二次开发的能力

工具支持二次开发,能够更好地契合测试需求,更方面地统计结果数据,并能很好地与公司现有系统做集成。

四、常见性能测试工具:

性能测试工具,从理论上来讲在性能测试过程中使用到的所有工具都可以称其为性能测试工具,通常分为以下几类:

image.png

说明:

  • 服务器端性能测试工具:需要支持产生压力和负载,录制和生成脚本,设置和部署场景,产生并发用户和向系统施加持续的压力。
  • web前端性能测试工具:需要关于心浏览器等客户端工具对具体需要展现的页面的处理过程。
  • 移动端性能测试工具:同web端性能测试工具也需要关心页面的处理过程,另外还要具体数据采集的功能,比如:手机CPU、内存、电量,启动时间等数据的记录。
  • 资源监控工具:这个主要是能够收集性能测试过程中的数据以及良好的结果展现方式。

PS:本篇文章主要总结下服务器端性能测试工具LR和Jmeter、kylinTOP,后面也会对这三个工具进行简单的对分。

五、常见性能测试工具特点

  • JMeter:采用的是多线程模型,扩展性很强,不过制造压力没有那么高。它很适合用来压一些Tomcat服务,或者一些后端接口。JMeter的缺点是压力值不能精确控制,难以适应高并发的情况,而且由于是JAVA编写的,本身比较消耗资源。
  • LoadRunner:更像是一个模拟器,它比较适用于前端构造较复杂场景的情况,比如模拟100个用户登录的场景,LoadRunner对非技术人员提供了很好的支持。LoadRunner不适用后端接口。
  • kylinTOP:是一款国产的集性能测试、自动化测试(UI、接口、APP)、业务&接口监控于一体的测试平台,支持跨平台(WINDOWS/LINUX/SOLARIS/麒麟/MAC等)运行。

下面为JMeter和LoadRunner、kylinTOP对比表:

描述 JMeter LoadRunner kylinTOP
架构原理 通过中间代理,监控和收集并发客户端的指令,把他们生成脚本,再发送的应用服务器,再监控应用服务器反馈的过程 同JMeter BS架构,企业级平台,支持多人同时操作;支持项目管理、模块管理、用户管理、脚本用例管理、参数文件管理;支持多次运行报告历史对比、单个接口多次运行历史对比
安装 简单,解压即可,比较灵活 LoadRunner安装包比较大,安装比较麻烦,工具本身相对比较笨重 一路傻瓜式安装,记住安装目录位置
支持的协议 支持多种协议:HTTP、HTTPS、SOAP、FTP、Database via JDBC、JMS等,但相对LR还是不够全面,由于此原因相对来说jemter比较灵活,轻便 支持的协议非常多,比较全面,但正因此显得工具本身比较笨重,不够灵活 支持多种协议,支持HTTP/HTTP2、RTSP、RTMP、Socket、JAVA自定义等
脚本录制 提供了一个利用本地ProxyServer(代理服务器)来录制生成测试脚本的功能,也支持badboy录制再生成JMeter脚本 自带录制功能强大,可直接录制回放 支持浏览器代理录制、抓包文件录制
并发模型 通过增加线程组的数目,或者是设置循环次数来增加并发用户 支持多种并发模型,通过在场景中选择要设置什么样的场景,然后选择虚拟用户数 强大且灵活的并发模型,包括用户数/秒、CAPS/秒、目标模型,详细请点击链接
分布式测试 支持,可设置多台代理,通过远程控制实现多台机器并发压力 同JMeter 支持控制多个压测机实现分布式并发测试
资源监控 通过JMeterPlugins插件和ServerAgent实现 自带资源监控功能 提供Monitor代理器,支持CPU、内存、I/O、交换区、磁盘读写数、吞吐量等资源指标
报告分析 通过与Ant集成,生成HTML报告 自身支持生成HTML、Word报告 支持合并指标曲线、曲线过滤、曲线放大缩小、比较不同测试结果、日志分析,生成HTML报告
虚拟IP 不支持 支持 支持系统虚拟IP与工具虚拟IP(支持更多IP占用资源更低)
网速模拟 不支持 支持 支持限制每个用户上传/下载带宽
扩展性 开源,可根据需求修改源码 通过扩展函数库实现 支持JAVA语言扩展,解决私有算法或业务处理,如加解密,JavaScript计算
学习成本 主要是自学官网上的资料 网上资料和相关培训很多,购买正版的话,还有技术支持 部分功能付费的,大家判断是否可用,可以去看看价格

六、总结:

商用性能工具在易用性(脚本生成)、并发模型、统计指标上要比开源免费软件要好很多,可以大大提高工作效率,降低使用难度,在统计指标上要丰富的多。

参考资料

如果你已经工作但是经常觉得难点很多,觉得自己测试方面学的不够精想要继续学习的,想转型怕学不会的,都可以关注我【程序员一凡】,公众号内可领取最新软件测试大厂面试资料和我一路走来的全套学习资源。

  • ✔️如果这篇文章对你有用,记得点个赞👍🏻加个关注支持我一下~
  • ✔️我们下期见!👋👋👋

🌻推荐阅读:

  • 随手写个接口测试工具,让学妹瞧瞧我的能耐
  • 「 性能测试技术笔记系列(一)」之性能指标行业参考|收藏版
  • ✨ 实战系列 ✨ 1️⃣ 微信小程序自动化测试实践(附 Python 源码)❤️
  • “我转行做测试开发的这一年多,月薪5K变成了24K”,中文系萌妹的自白

本文转载自: 掘金

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

MVVM 进阶版:MVI 架构了解一下~

发表于 2021-10-24

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

前言

Android开发发展到今天已经相当成熟了,各种架构大家也都耳熟能详,如MVC,MVP,MVVM等,其中MVVM更是被官方推荐,成为Android开发中的显学。

不过软件开发中没有银弹,MVVM架构也不是尽善尽美的,在使用过程中也会有一些不太方便之处,而MVI可以很好的解决一部分MVVM的痛点。

本文主要包括以下内容

  1. MVC,MVP,MVVM等经典架构介绍
  2. MVI架构到底是什么?
  3. MVI架构实战

需要重点指出的是,标题中说MVI架构是MVVM的进阶版是指MVI在MVVM非常相似,并在其基础上做了一定的改良,并不是说MVI架构一定比MVVM适合你的项目

各位同学可以在分析比较各个架构后,选择合适项目场景的架构

经典架构介绍

MVC架构介绍

MVC是个古老的Android开发架构,随着MVP与MVVM的流行已经逐渐退出历史舞台,我们在这里做一个简单的介绍,其架构图如下所示:

MVC架构主要分为以下几部分

  1. 视图层(View):对应于xml布局文件和java代码动态view部分
  2. 控制层(Controller):主要负责业务逻辑,在android中由Activity承担,同时因为XML视图功能太弱,所以Activity既要负责视图的显示又要加入控制逻辑,承担的功能过多。
  3. 模型层(Model):主要负责网络请求,数据库处理,I/O的操作,即页面的数据来源

由于android中xml布局的功能性太弱,Activity实际上负责了View层与Controller层两者的工作,所以在android中mvc更像是这种形式:

因此MVC架构在android平台上的主要存在以下问题:

  1. Activity同时负责View与Controller层的工作,违背了单一职责原则
  2. Model层与View层存在耦合,存在互相依赖,违背了最小知识原则

MVP架构介绍

由于MVC架构在Android平台上的一些缺陷,MVP也就应运而生了,其架构图如下所示

MVP架构主要分为以下几个部分

  1. View层:对应于Activity与XML,只负责显示UI,只与Presenter层交互,与Model层没有耦合
  2. Presenter层: 主要负责处理业务逻辑,通过接口回调View层
  3. Model层:主要负责网络请求,数据库处理等操作,这个没有什么变化

我们可以看到,MVP解决了MVC的两个问题,即Activity承担了两层职责与View层与Model层耦合的问题

但MVP架构同样有自己的问题

  1. Presenter层通过接口与View通信,实际上持有了View的引用
  2. 但是随着业务逻辑的增加,一个页面可能会非常复杂,这样就会造成View的接口会很庞大。

MVVM架构介绍

MVVM 模式将 Presenter 改名为 ViewModel,基本上与 MVP 模式完全一致。

唯一的区别是,它采用双向数据绑定(data-binding):View的变动,自动反映在 ViewModel,反之亦然

MVVM架构图如下所示:

可以看出MVVM与MVP的主要区别在于,你不用去主动去刷新UI了,只要Model数据变了,会自动反映到UI上。换句话说,MVVM更像是自动化的MVP。

MVVM的双向数据绑定主要通过DataBinding实现,不过相信有很多人跟我一样,是不喜欢用DataBinding的,这样架构就变成了下面这样

  1. View观察ViewModle的数据变化并自我更新,这其实是单一数据源而不是双向数据绑定,所以其实MVVM的这一大特性我其实并没有用到
  2. View通过调用ViewModel提供的方法来与ViewMdoel交互

小结

  1. MVC架构的主要问题在于Activity承担了View与Controller两层的职责,同时View层与Model层存在耦合
  2. MVP引入Presenter层解决了MVC架构的两个问题,View只能与Presenter层交互,业务逻辑放在Presenter层
  3. MVP的问题在于随着业务逻辑的增加,View的接口会很庞大,MVVM架构通过双向数据绑定可以解决这个问题
  4. MVVM与MVP的主要区别在于,你不用去主动去刷新UI了,只要Model数据变了,会自动反映到UI上。换句话说,MVVM更像是自动化的MVP。
  5. MVVM的双向数据绑定主要通过DataBinding实现,但有很多人(比如我)不喜欢用DataBinding,而是View通过LiveData等观察ViewModle的数据变化并自我更新,这其实是单一数据源而不是双向数据绑定

MVI架构到底是什么?

MVVM架构有什么不足?

要了解MVI架构,我们首先来了解下MVVM架构有什么不足

相信使用MVVM架构的同学都有如下经验,为了保证数据流的单向流动,LiveData向外暴露时需要转化成immutable的,这需要添加不少模板代码并且容易遗忘,如下所示

1
2
3
4
5
6
7
8
9
10
kotlin复制代码class TestViewModel : ViewModel() {
//为保证对外暴露的LiveData不可变,增加一个状态就要添加两个LiveData变量
private val _pageState: MutableLiveData<PageState> = MutableLiveData()
val pageState: LiveData<PageState> = _pageState
private val _state1: MutableLiveData<String> = MutableLiveData()
val state1: LiveData<String> = _state1
private val _state2: MutableLiveData<String> = MutableLiveData()
val state2: LiveData<String> = _state2
//...
}

如上所示,如果页面逻辑比较复杂,ViewModel中将会有许多全局变量的LiveData,并且每个LiveData都必须定义两遍,一个可变的,一个不可变的。这其实就是我通过MVVM架构写比较复杂页面时最难受的点。

其次就是View层通过调用ViewModel层的方法来交互的,View层与ViewModel的交互比较分散,不成体系

小结一下,在我的使用中,MVVM架构主要有以下不足

  1. 为保证对外暴露的LiveData是不可变的,需要添加不少模板代码并且容易遗忘
  2. View层与ViewModel层的交互比较分散零乱,不成体系

MVI架构是什么?

MVI 与 MVVM 很相似,其借鉴了前端框架的思想,更加强调数据的单向流动和唯一数据源,架构图如下所示

其主要分为以下几部分

  1. Model: 与MVVM中的Model不同的是,MVI的Model主要指UI状态(State)。例如页面加载状态、控件位置等都是一种UI状态
  2. View: 与其他MVX中的View一致,可能是一个Activity或者任意UI承载单元。MVI中的View通过订阅Model的变化实现界面刷新
  3. Intent: 此Intent不是Activity的Intent,用户的任何操作都被包装成Intent后发送给Model层进行数据请求

单向数据流

MVI强调数据的单向流动,主要分为以下几步:

  1. 用户操作以Intent的形式通知Model
  2. Model基于Intent更新State
  3. View接收到State变化刷新UI。

数据永远在一个环形结构中单向流动,不能反向流动:

上面简单的介绍了下MVI架构,下面我们一起来看下具体是怎么使用MVI架构的

MVI架构实战

总体架构图

我们使用ViewModel来承载MVI的Model层,总体结构也与MVVM类似,主要区别在于Model与View层交互的部分

  1. Model层承载UI状态,并暴露出ViewState供View订阅,ViewState是个data class,包含所有页面状态
  2. View层通过Action更新ViewState,替代MVVM通过调用ViewModel方法交互的方式

MVI实例介绍

添加ViewState与ViewEvent

ViewState承载页面的所有状态,ViewEvent则是一次性事件,如Toast等,如下所示

1
2
3
4
5
6
kotlin复制代码data class MainViewState(val fetchStatus: FetchStatus, val newsList: List<NewsItem>)  

sealed class MainViewEvent {
data class ShowSnackbar(val message: String) : MainViewEvent()
data class ShowToast(val message: String) : MainViewEvent()
}
  1. 我们这里ViewState只定义了两个,一个是请求状态,一个是页面数据
  2. ViewEvent也很简单,一个简单的密封类,显示Toast与Snackbar

ViewState更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
kotlin复制代码class MainViewModel : ViewModel() {
private val _viewStates: MutableLiveData<MainViewState> = MutableLiveData()
val viewStates = _viewStates.asLiveData()
private val _viewEvents: SingleLiveEvent<MainViewEvent> = SingleLiveEvent()
val viewEvents = _viewEvents.asLiveData()

init {
emit(MainViewState(fetchStatus = FetchStatus.NotFetched, newsList = emptyList()))
}

private fun fabClicked() {
count++
emit(MainViewEvent.ShowToast(message = "Fab clicked count $count"))
}

private fun emit(state: MainViewState?) {
_viewStates.value = state
}

private fun emit(event: MainViewEvent?) {
_viewEvents.value = event
}
}

如上所示

  1. 我们只需定义ViewState与ViewEvent两个State,后续增加状态时在data class中添加即可,不需要再写模板代码
  2. ViewEvents是一次性的,通过SingleLiveEvent实现,当然你也可以用Channel当来实现
  3. 当状态更新时,通过emit来更新状态

View监听ViewState

1
2
3
4
5
6
7
8
kotlin复制代码    private fun initViewModel() {
viewModel.viewStates.observe(this) {
renderViewState(it)
}
viewModel.viewEvents.observe(this) {
renderViewEvent(it)
}
}

如上所示,MVI 使用 ViewState 对 State 集中管理,只需要订阅一个 ViewState 便可获取页面的所有状态,相对 MVVM 减少了不少模板代码。

View通过Action更新State

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
kotlin复制代码class MainActivity : AppCompatActivity() {
private fun initView() {
fabStar.setOnClickListener {
viewModel.dispatch(MainViewAction.FabClicked)
}
}
}
class MainViewModel : ViewModel() {
fun dispatch(action: MainViewAction) =
reduce(viewStates.value, action)

private fun reduce(state: MainViewState?, viewAction: MainViewAction) {
when (viewAction) {
is MainViewAction.NewsItemClicked -> newsItemClicked(viewAction.newsItem)
MainViewAction.FabClicked -> fabClicked()
MainViewAction.OnSwipeRefresh -> fetchNews(state)
MainViewAction.FetchNews -> fetchNews(state)
}
}
}

如上所示,View通过Action与ViewModel交互,通过 Action 通信,有利于 View 与 ViewModel 之间的进一步解耦,同时所有调用以 Action 的形式汇总到一处,也有利于对行为的集中分析和监控

总结

本文主要介绍了MVC,MVP,MVVM与MVI架构,目前MVVM是官方推荐的架构,但仍然有以下几个痛点

  1. MVVM与MVP的主要区别在于双向数据绑定,但由于很多人(比如我)并不喜欢使用DataBindg,其实并没有使用MVVM双向绑定的特性,而是单一数据源
  2. 当页面复杂时,需要定义很多State,并且需要定义可变与不可变两种,状态会以双倍的速度膨胀,模板代码较多且容易遗忘
  3. View与ViewModel通过ViewModel暴露的方法交互,比较零乱难以维护

而MVI可以比较好的解决以上痛点,它主要有以下优势

  1. 强调数据单向流动,很容易对状态变化进行跟踪和回溯
  2. 使用ViewState对State集中管理,只需要订阅一个 ViewState 便可获取页面的所有状态,相对 MVVM 减少了不少模板代码
  3. ViewModel通过ViewState与Action通信,通过浏览ViewState 和 Aciton 定义就可以理清 ViewModel 的职责,可以直接拿来作为接口文档使用。

当然MVI也有一些缺点,比如

  1. 所有的操作最终都会转换成State,所以当复杂页面的State容易膨胀
  2. state是不变的,因此每当state需要更新时都要创建新对象替代老对象,这会带来一定内存开销

软件开发中没有银弹,所有架构都不是完美的,有自己的适用场景,读者可根据自己的需求选择使用。

但通过以上的分析与介绍,我相信使用MVI架构代替没有使用DataBinding的MVVM是一个比较好的选择~

更多

关于MVI架构更佳实践,支持局部刷新,可参见: MVI 架构更佳实践:支持 LiveData 属性监听

关于MVI架构封装,优雅实现网络请求,可参见: MVI 架构封装:快速优雅地实现网络请求

项目地址

本文所有代码可见:github.com/shenzhen201…

参考资料

基于Android的MVI架构:从双向绑定到单向数据流

Jetpack Compose 架构如何选? MVP, MVVM, MVI

站在思想层面看MVX架构

Best Architecture For Android : MVI + LiveData + ViewModel = ❤️

本文转载自: 掘金

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

为什么Redis需要两种持久化?

发表于 2021-10-24

Redis的两种持久化方式

众所周知,Redis中提供了AOF,RDB两种持久化,下面先来简单回顾一下

RDB持久化

  • RDB持久化,就是把当前时间点的数据库的状态保存到磁盘中,又称快照持久化。
  • RDB可以手动触发,也可以根据服务器配置定期执行。
  • RDB生成的文件,是一个经过压缩的二进制文件,数据库可以通过该文件还原到该时间点的状态。
  • Redis提供前台RDB持久化命令SAVE和后台RDB持久化命令BGSAVE,前台执行时,Redis的其他命令会被阻塞,而后台执行时,Redis还可以继续处理客户端的命令请求。
  • RDB二进制文件中,保存的是键值对数据,采用经过压缩的自定义编码,带校验。通过od命令可以转化为可读。
  • 主从复制时,初始化的全量复制采用RDB文件。

AOF持久化

  • AOF持久化,全称是Appen Only File,意思是追加的持久化方式,其中保存的是写命令,而非数据。
  • AOF持久化过程分为命令追加、文件写入、文件同步三个步骤。
  • 命令追加:Redis服务端每执行完一个写命令,都会以AOF协议格式将该写命令追加到服务器状态的aof_buf缓冲区末尾。
  • 文件写入:Redis中,每结束一个事件循环之前,都会调用flushAppendOnlyFile函数,将aof_buf缓冲区中的内容写入到AOF文件。
  • 文件同步:同步sync指的是文件写入到操作系统缓冲区中时,是否直接同步到磁盘中。通过配置,可以选择立即同步、每秒同步、不主动同步而由操作系统控制,这三种同步方式。关于文件I/O缓冲:www.litreily.top/2018/10/25/…
  • Redis优先使用AOF文件来恢复数据。
  • AOF文件由于存储命令,且没有经过压缩,其体积要大于RDB文件。
  • AOF文件可以定期采用BGREWRITEAOF重写,减少重复命令、已失效命令,合并命令等。
  • AOF文件支持后台重写,采用fork子进程的形式实现。子进程带有服务器进程的数据副本,再避免使用锁的情况下保证数据安全性。另外也采用AOF重写缓冲区解决了数据不一致。

两种持久化分别的优缺点

RDB的优点

  1. 文件体积小,适合拷贝做冷备
  2. 相比AOF,备份恢复速度更快

RDB的缺点

  1. 丢失数据多
  2. fork子进程来做BGSAVE,消耗一定的内存资源

AOF的优点

  1. 丢失数据少
  2. 增加了写缓冲区,无需寻址,速度快
  3. append-only,也无需做磁盘寻址,效率高

AOF的缺点

  1. 文件体积大
  2. AOF每次都需要做一下写入aof_buf的操作,开启AOF持久化后,QPS会略微降低

Redis为什么需要两种持久化?

经过上面的回顾,我们可以看到,RDB与AOF持久化有明显区别。

  1. 存储的内容:RDB存储某一时间点的数据;AOF存储执行的写命令。
  2. 文件大小:RDB文件较小;AOF文件较大。
  3. 写入方式:RDB可采用前台/后台写入方式;AOF采用每次执行写命令,都将命令存入缓冲区的方式,另外可定期重写。
  4. 数据丢失:RDB丢失从宕机到上一次RDB同步之间的所有数据;AOF根据I/O缓冲区所配置的刷新方式,不丢失或丢失1s或几秒的数据。

根据这些对比,可以看到RDB持久化更适合保存一个时间点的数据,在主从复制或者数据全量异地灾备时,拷贝到其他地方,而AOF持久化由于丢失数据较少,比较适合作为本地备份,在Reids挂掉重启时作为故障恢复。这就是我理解的为什么Redis需要两种持久化方式。

参考文献

  1. 《Redis设计与实现》,黄建宏著。
  2. www.litreily.top/2018/10/25/…

本文转载自: 掘金

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

如何用Java实现word、excel、ppt、txt等办公

发表于 2021-10-24

​小知识,大挑战!本文正在参与“ 程序员必备小知识 ”创作活动

本文同时参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金

java实现办公文件在线预览功能是一个大家在工作中也许会遇到的需求,网上些公司专门提供这样的服务,不过需要收费  

如果想要免费的,可以用openoffice,实现原理就是:

通过第三方工具openoffice,将word、excel、ppt、txt等文件转换为pdf文件流;

当然如果装了Adobe Reader XI,那把pdf直接拖到浏览器页面就可以直接打开预览,前提就是浏览器支持pdf文件浏览。

我这里介绍通过poi实现word、excel、ppt转pdf流,这样就可以在浏览器上实现预览了。

1.到官网下载Apache OpenOffice 安装包,安装运行。(不同系统的安装方法,自行百度,这里不做过多说明)

)​、

2.再项目的pom文件中引入依赖

1
2
3
4
5
6
xml复制代码        <!--openoffice-->
<dependency>
<groupId>com.artofsolving</groupId>
<artifactId>jodconverter</artifactId>
<version>2.2.1</version>
</dependency>

3.将word、excel、ppt转换为pdf流的工具类代码

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
java复制代码import com.artofsolving.jodconverter.DefaultDocumentFormatRegistry;
import com.artofsolving.jodconverter.DocumentConverter;
import com.artofsolving.jodconverter.DocumentFormat;
import com.artofsolving.jodconverter.openoffice.connection.OpenOfficeConnection;
import com.artofsolving.jodconverter.openoffice.connection.SocketOpenOfficeConnection;
import com.artofsolving.jodconverter.openoffice.converter.StreamOpenOfficeDocumentConverter;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;


/**
* 文件格式转换工具类
*
* @author tarzan
* @version 1.0
* @since JDK1.8
*/
public class FileConvertUtil {
/** 默认转换后文件后缀 */
private static final String DEFAULT_SUFFIX = "pdf";
/** openoffice_port */
private static final Integer OPENOFFICE_PORT = 8100;

/**
* 方法描述 office文档转换为PDF(处理本地文件)
*
* @param sourcePath 源文件路径
* @param suffix 源文件后缀
* @return InputStream 转换后文件输入流
* @author tarzan
*/
public static InputStream convertLocaleFile(String sourcePath, String suffix) throws Exception {
File inputFile = new File(sourcePath);
InputStream inputStream = new FileInputStream(inputFile);
return covertCommonByStream(inputStream, suffix);
}

/**
* 方法描述 office文档转换为PDF(处理网络文件)
*
* @param netFileUrl 网络文件路径
* @param suffix 文件后缀
* @return InputStream 转换后文件输入流
* @author tarzan
*/
public static InputStream convertNetFile(String netFileUrl, String suffix) throws Exception {
// 创建URL
URL url = new URL(netFileUrl);
// 试图连接并取得返回状态码
URLConnection urlconn = url.openConnection();
urlconn.connect();
HttpURLConnection httpconn = (HttpURLConnection) urlconn;
int httpResult = httpconn.getResponseCode();
if (httpResult == HttpURLConnection.HTTP_OK) {
InputStream inputStream = urlconn.getInputStream();
return covertCommonByStream(inputStream, suffix);
}
return null;
}

/**
* 方法描述 将文件以流的形式转换
*
* @param inputStream 源文件输入流
* @param suffix 源文件后缀
* @return InputStream 转换后文件输入流
* @author tarzan
*/
public static InputStream covertCommonByStream(InputStream inputStream, String suffix) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
OpenOfficeConnection connection = new SocketOpenOfficeConnection(OPENOFFICE_PORT);
connection.connect();
DocumentConverter converter = new StreamOpenOfficeDocumentConverter(connection);
DefaultDocumentFormatRegistry formatReg = new DefaultDocumentFormatRegistry();
DocumentFormat targetFormat = formatReg.getFormatByFileExtension(DEFAULT_SUFFIX);
DocumentFormat sourceFormat = formatReg.getFormatByFileExtension(suffix);
converter.convert(inputStream, sourceFormat, out, targetFormat);
connection.disconnect();
return outputStreamConvertInputStream(out);
}

/**
* 方法描述 outputStream转inputStream
*
* @author tarzan
*/
public static ByteArrayInputStream outputStreamConvertInputStream(final OutputStream out) throws Exception {
ByteArrayOutputStream baos=(ByteArrayOutputStream) out;
return new ByteArrayInputStream(baos.toByteArray());
}



public static void main(String[] args) throws IOException {
//convertNetFile("http://172.16.10.21/files/home/upload/department/base/201912090541573c6abdf2394d4ae3b7049dcee456d4f7.doc", ".pdf");
//convert("c:/Users/admin/Desktop/2.pdf", "c:/Users/admin/Desktop/3.pdf");
}
}

4.serve层在线预览方法代码

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
csharp复制代码 /**
* @Description:系统文件在线预览接口
* @Author: tarzan
*/
public void onlinePreview(String url, HttpServletResponse response) throws Exception {
//获取文件类型
String[] str = SmartStringUtil.split(url,"\\.");

if(str.length==0){
throw new Exception("文件格式不正确");
}
String suffix = str[str.length-1];
if(!suffix.equals("txt") && !suffix.equals("doc") && !suffix.equals("docx") && !suffix.equals("xls")
&& !suffix.equals("xlsx") && !suffix.equals("ppt") && !suffix.equals("pptx")){
throw new Exception("文件格式不支持预览");
}
InputStream in=FileConvertUtil.convertNetFile(url,suffix);
OutputStream outputStream = response.getOutputStream();
//创建存放文件内容的数组
byte[] buff =new byte[1024];
//所读取的内容使用n来接收
int n;
//当没有读取完时,继续读取,循环
while((n=in.read(buff))!=-1){
//将字节数组的数据全部写入到输出流中
outputStream.write(buff,0,n);
}
//强制将缓存区的数据进行输出
outputStream.flush();
//关流
outputStream.close();
in.close();
}

5.controler层代码

1
2
3
4
5
less复制代码    @ApiOperation(value = "系统文件在线预览接口 by tarzan")
@PostMapping("/api/file/onlinePreview")
public void onlinePreview(@RequestParam("url") String url, HttpServletResponse response) throws Exception{
fileService.onlinePreview(url,response);
}

​

本文转载自: 掘金

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

【开源博客】三个步骤学会Hexo快速生成精美博客! 什么是h

发表于 2021-10-24

小知识,大挑战!本文正在参与「程序员必备小知识」创作活动

本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金。

最近学委跟小伙伴搞了一个py4ever 开源站点,使用的就是hexo。

下面是站点信息:py4ever开源站

在这里插入图片描述

它就是基于hexo来生成的,当然做了一些定制。

这次,学委会一步一步的指导大家建立一个博客站。

什么是hexo

hexo 是一款博客生成工具,它基于Markdown语法的博客生成。

hexo它也发行了基于NodeJS的cli库(命令行工具),在npm上面可以查找到,下面进入安装。

可以简单理解,它就是一个博客网站工厂,专门生成博客网站的, 但是需要安照它的方式。

下面学习hexo生成博客的步骤。

安装

全局安装,把hexo命令添加到运行环境的PATH变量上,这样随处可用。

1
复制代码npm install -g hexo-cli

通常我们安装完一个库,马上就查看版本/或者查看帮助,确保安装正常

1
复制代码hexo -v

在这里插入图片描述

第一步 生成站点项目

运行下面的命令创建博客项目:

1
bash复制代码hexo init

该命令会快速的生成博客模版文件。

在这里插入图片描述

我们这里来看这个站点就是一个标准的NodeJS项目。

如下图,文件很多,重点是package.json,它复制管理项目的其他依赖工具库。

然后就是themes,这是博客的主题风格,也就是整体样式,布局方面的。就像音乐的中国风,R&B之类的,它限制了整首歌的曲调,对我们博客来说就是整个观感,整体的排版。

然后就是scaffolds:比较简单,就是创建分类/文章等的模板,可以修改的。

在这里插入图片描述

关于这个NodeJS项目更多信息可以补充学习:雷学委之NodeJS爱好系列, 我们继续博客页面的编辑。

修改站点信息

如下,我们把title先修改了:

在这里插入图片描述

还有其他细节操作,我们先生成站点。

第二步 生成站点网页

预览站点之前需要先生成网站的页面,这里使用下面的命令

1
bash复制代码hexo generate #或者hexo g

该命令帮我们把网站从md文件模版生成为html文件,可以看到项目目录内有一个public目录:

在这里插入图片描述

这个就是最终博客网站会显示的html文件了,懂html的朋友都知道,这里所见即所得,你在html放什么文字颜色块,浏览器就相应的显示出来。

第三步 预览网站

我们继续使用默认主题,运行下面的命令,先看修改后的网站效果。

1
vbscript复制代码hexo server

运行效果如下:

在这里插入图片描述

我们可以打开浏览器查看:

在这里插入图片描述

hexo 工具启动了一个服务器,渲染了我们刚刚生成的博客站点,非常方便。

同时,博客自带了一篇博客文章“Hello World”, 这是hexo默认生成的。

编辑博客页面

我们打开博客文件夹内的source目录。它放置了站点的文章。

在这里插入图片描述

这里就很好弄了,它是一个markdown(MD)格式的文件。

小白可以简单理解,它一种文档风格规范。比如‘# ’ 开头表示一个大标题,一级标题。‘## ’ 开头表示二级标题。

下面修改一下:

在这里插入图片描述

  • 修改了title为:雷学委博客教程
  • 修改了二级标题为:Quick Start / 快速开始
  • 添加了一行文本: “第一行文本(from 学委)”

我们可以根据需要修改文件内容,重新运行下面命令就能查看效果了

1
2
bash复制代码hexo generate #或者hexo g
hexo server #或者hexo s

刷新刚刚的页面,我们看到博客内容变化了。

在这里插入图片描述

总结与延伸

本文展示了一个博客站点的快速生成。

添加新博客,可以复制helloworld.md或者使用‘hexo new 博客名称’。

发布站点到网站简单来说可以复制public目录到服务器,渲染即可。但是Hexo提供了更高效的做法,我们下篇继续讲解。

下一篇,学委会具体介绍py4ever网站的技术搭建细节。

喜欢Python的朋友,请关注学习 十年老将带你做开源项目 or Python基础专栏 or Python入门到精通大专栏

持续学习持续开发,我是雷学委!

编程很有趣,关键是把技术搞透彻讲明白。

创作不易,请关注收藏点赞,或者留下评论打打气!

本文转载自: 掘金

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

【设计模式从入门到精通】08-组合模式 组合模式

发表于 2021-10-24

笔记来源:尚硅谷Java设计模式(图解+框架源码剖析)

[TOC]

组合模式

1、学校院系展示需求

编写程序展示一个学校院系结构:

需求是这样,要在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系。如图:

image-20211023221436485

传统方式解决学校院系展示(类图)

image-20211023221531826

问题分析

  • 1)将学院看做是学校的子类,系是学院的子类,这样实际上是站在组织大小来进行分层次的
  • 2)实际上我们的要求是:在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系。因此这种方案,不能很好实现的 管理 的操作,比如对学院、系的添加、删除、遍历等
  • 3)解决方案:把学校、院、系都看做是组织结构,他们之间没有继承的关系,而是一个树形结构,可以更好的实现管理操作 ==> 组合模式

2、组合模式基本介绍

  • 1)组合模式(Composite Pattern),又叫部分整体模式。它创建了对象组的树形结构,将对象组合成树状结构以表示“整体-部分”的层次关系
  • 2)组合模式依据树形结构来组合对象,用来表示部分以及整体层次
  • 3)这种类型的设计模式属于结构型模式
  • 4)组合模式使得用户对单个对象和组合对象的访问具有一致性,即:组合能让客户以一致的方式处理个别对象以及组合对象

原理类图

image

对原理结构图的说明一即组合模式的角色及职责

  • 1)Component:这是组合中对象声明接口。在适当情况下,实现所有类共有的接口默认行为,用于访问和管理 Component子部件。Component可以是抽象类或者接口
  • 2)Leaf:在组合中表示叶子结点,叶子结点没有子节点
  • 3)Composite:非叶子结点,用于存储子部件,在Component接口中实现子部件的相关操作。比如增加、删除

解决的问题

组合模式解决这样的问题,当我们的要处理的对象可以生成一棵树形结构,而我们要对树上的节点和叶子进行操作时,它能够提供一致的方式,而不用考虑它是节点还是叶子

image-20211023222839054

3、组合模式解决学校院系展示

UML 类图

image-20211023230113883

核心代码

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
java复制代码// Component 抽象类
public abstract class OrganizationComponent {
private String name;

public OrganizationComponent(String name) {
this.name = name;
}

public String getName() {
return name;
}

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

public void add(OrganizationComponent organizationComponent) {
throw new UnsupportedOperationException();
}

public void remove(OrganizationComponent organizationComponent) {
throw new UnsupportedOperationException();
}

public abstract void print();
}
// Composite 非叶子节点
public class University extends OrganizationComponent {
List<organizationcomponent> organizationComponentList = new ArrayList<>();

public University(String name) {
super(name);
}

@Override
public void add(OrganizationComponent organizationComponent) {
organizationComponentList.add(organizationComponent);
}

@Override
public void remove(OrganizationComponent organizationComponent) {
organizationComponent.remove(organizationComponent);
}

@Override
public void print() {
for (OrganizationComponent organizationComponent : organizationComponentList) {
organizationComponent.print();
}
}
}
public class College extends OrganizationComponent {
List<organizationcomponent> organizationComponentList = new ArrayList<>();

public College(String name) {
super(name);
}

@Override
public void add(OrganizationComponent organizationComponent) {
organizationComponentList.add(organizationComponent);
}

@Override
public void remove(OrganizationComponent organizationComponent) {
organizationComponent.remove(organizationComponent);
}

@Override
public void print() {
System.out.println("=============" + getName() + "=============");
for (OrganizationComponent organizationComponent : organizationComponentList) {
organizationComponent.print();
}
}
}
// Leaf 叶子结点
public class Major extends OrganizationComponent {
public Major(String name) {
super(name);
}

@Override
public void print() {
System.out.println(getName());
}
}
// 客户端
public class Client {
public static void main(String[] args) {
//大学
OrganizationComponent university = new University("清华大学");
//学院
OrganizationComponent computerCollege = new College("计算机学院");
OrganizationComponent infoEngineerCollege = new College("信息工程学院");
//专业
computerCollege.add(new Major("软件工程"));
computerCollege.add(new Major("网络工程"));
computerCollege.add(new Major("计算机科学与技术"));
infoEngineerCollege.add(new Major("通信工程"));
infoEngineerCollege.add(new Major("信息工程"));

university.add(computerCollege);
university.add(infoEngineerCollege);
university.print();
//=============计算机学院=============
//软件工程
//网络工程
//计算机科学与技术
//=============信息工程学院=============
//通信工程
//信息工程
}
}

4、JDK 源码分析

Java 的集合类—— HashMap 就使用了组合模式

UML 类图

image-20211024160404083

核心代码

1
2
3
4
5
6
7
8
9
10
java复制代码// Component
public interface Map<k,v> {
interface Entry<k,v> {}
}
public abstract class AbstractMap<k,v> implements Map<k,v> {}
// Composite
public class HashMap<k,v> extends AbstractMap<k,v> implements Map<k,v>, Cloneable, Serializable {
// Leaf
static class Node<k,v> implements Map.Entry<k,v> {}
}

说明

  • 1)Map 就是一个抽象的构建,类似Component
  • 2)HashMap 是一个中间的构建,类似Composite,实现 / 继承了相关方法 put、putAll
  • 3)Node 是 HashMap 的静态内部类,类似Leaf叶子节点,这里就没有 put

5、注意事项和细节

  • 1)简化客户端操作:客户端只需要面对一致的对象,而不用考虑整体部分或者节点叶子的问题
  • 2)具有较强扩展性:当我们要更改组合对象时,我们只需要调整内部的层次关系,客户端不用做出任何改动
  • 3)方便创建复杂的层次结构:客户端不用理会组合里面的组成细节,容易添加节点或者叶子,从而创建出复杂的树形结构
  • 4)需要遍历组织机构,或者处理的对象具有树形结构时,非常适合使用组合模式
  • 5)要求较高的抽象性,如果节点和叶子有很多差异性的话,比如很多方法和属性都不一样,不适合使用组合模式

本文转载自: 掘金

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

在一台机器上安装并使用多版本的 Go ?

发表于 2021-10-24
  1. 需求背景

实际开发中会接触到不同的开源项目,而这些项目有可能是不同团队开发的,使用的 go 版本都是不一样的。

以我为例,最近在研究 KubeVirt (一个能在 K8S 下管理虚拟机的开源项目),为了实现自己的需求,需要对 KubeVirt 进行二次开发。

通过 go.mod 得知 KubeVirt 是使用 go 1.13 开发的,而同时为了能在 KubeVirt 下的虚拟机能使用 LVM 这种存储方式,我们又引入了阿里研发的 lvm-csi 插件,同样的,这个插件已经实现的功能距离我们的预期还有点差距,因此同样要进行二次开发。查看 go.mod 发现,lvm-csi 是在更老的版本下开发的,也就是 go 1.12 。

而同时我的机器上很早之前就安装过 go 1.14 的版本,也一直使用它来开发。

问题就来了,如何让才能在我的机器上能同时安装这么多个版本的 golang ,而且互不冲突呢?

  1. 安装多版本的 Go

要想不同版本的 go 互不冲突,只要将他们安装在不同的目录下就可以了。

首先下载两个版本的安装包

1
2
shell复制代码$ wget https://dl.google.com/go/go1.12.linux-amd64.tar.gz
$ wget https://dl.google.com/go/go1.13.linux-amd64.tar.gz

然后分别解压至不同目录

1
2
3
4
5
6
7
shell复制代码# 解压go 1.12
sudo tar -C /tmp/ -xzf go1.12.linux-amd64.tar.gz
mv /tmp/go /usr/local/go12

# 解压go 1.13
sudo tar -C /tmp/ -xzf go1.13.linux-amd64.tar.gz
mv /tmp/go /usr/local/go13
  1. 简单粗暴的方案

在同一台机器中安装多个版本的 Golang 需要注意

  • 区分不同版本的 go 入口
  • 切换使用不同的环境变量

区分不同版本 go 入口

当你在环境中有多个 go ,那么当你使用 go build 等命令时,系统如何知道你想要使用的是哪个版本呢?

系统并不知道,因此你需要为不同版本的 go 起不同的名字

  • go 1.12 的 go 入口就改成 /usr/local/go12/bin/go12
  • go 1.13 的 go 入口就改成 /usr/local/go13/bin/go13
  • 以此列推

如此一来,就不会混淆了

1
2
3
4
shell复制代码$ go12 version
go version go1.12 linux/amd64
$ go13 version
go version go1.13 linux/amd64

切换使用不同的环境变量

使用了不同的go入口,对应的环境变量也要变量,比如 GOROOT,它可不会随着你使用哪个版本的 go 就自动切换,看下面他们输出的都是一个值。

1
2
3
4
shell复制代码$ go12 env GOROOT
/usr/local/go
$ go13 env GOROOT
/usr/local/go

因此,这个切换不同环境变量得自己来做

另外要注意的是

  1. go 1.12 没有 go env -w 中的 -w 参数
  2. go 1.13 中虽然有 -w 但不能覆盖OS级别的环境变量,修改前需先 unset

知道了这几点之后,便可以自行实现切换的方案。

我想的方案是可以在 /usr/local/go13/bin/ 写一个修改环境变量的脚本

1
2
3
4
5
6
shell复制代码$ cat << EOF >/usr/local/go12/bin/goto12
unset GOROOT
go env -w GOROOT="/usr/local/go12/"
EOF

$ chmod +x /usr/local/go12/bin/goto12

下次要使用 go 1.12 的时候,直接使用下面命令就可以修改环境变量

1
shell复制代码$ source goto12

go 1.13 也是同理,不敢赘述。

实现的效果如下

1
2
3
4
5
6
7
8
9
10
shell复制代码$ go env GOROOT
/usr/local/go

$ source goto12
$ go12 env GOROOT
/usr/local/go12

$source goto13
$ go13 env GOROOT
/usr/local/go13
  1. 比上面更优的方案

到这里,你应该已经感受到了,每次切换都要手动 source 一下,还是有点麻烦。

其实我还有一个更好的方法,这种方案,可以不用去改 go 入口的名字,同样连 source 都不用。

只要执行下面两条件命令即可(每个版本两条,两个版本就是四条)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
shell复制代码$ cat << EOF >/usr/local/go12/bin/go12
unset GOROOT
go env -w GOROOT="/usr/local/go12/"
/usr/local/go12/bin/go $@
EOF

$ chmod + /usr/local/go12/bin/go12

$ cat << EOF >/usr/local/go13/bin/go13
unset GOROOT
go env -w GOROOT="/usr/local/go13/"
/usr/local/go13/bin/go $@
EOF

$ chmod + /usr/local/go13/bin/go13

如果一来,当你执行 go12 就会自动修改 go12 的环境变量,执行 go13 就会自动修改 go13 的环境变量,并且不会影响默认的 go 的行为。

当然以上我都只改了 GOROOT 这一环境变量,若在不同的 go 版本还有其他不一样的地方,可以继续在 go12 或者go13 中添加对应的 shell 代码。

絮叨一下

我在掘金上写过很多的 Python 相关文章,其中包括 Python 实用工具,Python 高效技巧,PyCharm 使用技巧,很高兴得到了很多知乎朋友的认可和支持。

在他们的鼓励之下,我将过往文章分门别类整理成三本 PDF 电子书

PyCharm 中文指南

《PyCharm 中文指南》使用 300 多张 GIF 动态图的形式,详细讲解了最贴合实际开发的 105个 PyCharm 高效使用技巧,内容通俗易懂,适合所有 Python 开发者。

在线体验地址:pycharm.iswbm.com

Python 黑魔法指南

《Python黑魔法指南》目前迎来了 v3.0 的版本,囊集了 100 多个开发小技巧,非常适合在闲时进行碎片阅读。

在线体验地址:magic.iswbm.com

Python 中文指南

学 Python 最好的学习资料永远是 Python 官方文档,可惜现在的官方文档大都是英文,虽然有中文的翻译版了,但是进度实在堪忧。为了照顾英文不好的同学,我自己写了一份 面向零基础的朋友 的在线 Python 文档 – 《Python中文指南》

在线体验地址:python.iswbm.com

**有帮助的话,记得帮我 点个赞哟~

本文转载自: 掘金

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

我在掘金更文 100 篇,说说我的感想。 个人总结 后续的计

发表于 2021-10-24

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

2020年11月,我加入掘金,目前(2021年10月24日)我已经在掘金更文 115 篇。这 1 年左右的时间,我主要写了 Spring, Nacos, Sentinel, JVM, Java 并发, MySQL, Redis, Kafka 等几个后端相关的方向。也尝试过写了一些算法(LeetCode)相关的几篇文章,但是由于自己太菜没有继续坚持。

小黄鸭.png

个人总结

1 年左右的更文,自己基本上也是学了非常多的源码(大概看了两三遍 Spring, MyBatis, Nacos 源码),也写了一些的笔记和文章,自己也有一定的提升。还认识了几个朋友一起学习,一起卷。

认真学习.webp

更文数据,我的文章应该算比较普通,目前的总阅读量在 3.1w。算下来一篇文章的阅读量在 300 左右,感觉比较低。最好的一篇文章与阅读量在 2600 左右,很遗憾的没有单篇文章上万的阅读量。

更文的状态,我基本上是每个月都有文章更新的,前期的话是围绕自己学习的框架路线为更新文章的主线。目前两三个月主要是围绕自己的学习,以及掘金的一些更文活动持续的更文章。

不过不得不夸一下掘金,更文的活动还是非常多,如果你有优质的文章,周边福利领不停!

你很棒.webp

目前还有两个活动,大家没车的赶紧上车。

我没上车.webp

活动列表

  • 程序员必备小知识
  • 掘力星计划

后续的计划

小目标.jpeg

后续的计划,我还是保持对掘金一些活动的持续参与,以及自己要去积累更多的知识,做一些优质的输出,目标能够在今年单篇文章能够达到 1w 的阅读量。围绕一个方向能够深入,持续的去加深理解的深度。毕竟术业有专攻,感觉前面一年更新的文章还是有一些浅,以及缺少深度,很多地方有些泛泛而谈。

对掘金的评价

刚开始用掘金社区更文,主要是觉得掘金比较清爽,么有什么广告之类的。现在也是没有广告也提供了 App 端是相当的方便。

我主要是使用的掘金就是两个功能,第一个就是更新文章,第二个就是沸点。沸点有点像是微博这种但是还是没有广告的非常不错。以及现在有很多的活动也是非常不错的,我也是经常参与也能够领取到一些周边。也算是对咱们码字的肯定。

评价.webp

在说说沸点里面有时候会发一些资讯,感觉还是有时间可以刷一刷的。如果你没有对象的话可以去下 相亲角, 说不定去了就又了耶。

看上你了.webp

个人好文推荐

下面是几篇文章浏览,评论数据都还不错,希望能够推荐给大家。如果对你有帮助,希望能够点个赞。

放大招.jpeg

  • >>> 多条件判断场景中规则执行器的设计
  • >>> Sentinel 流控原理
  • >>> Sentinel 和常见限流算法

「欢迎在评论区讨论,掘金官方将在掘力星计划活动结束后,在评论区抽送100份掘金周边,抽奖详情见活动文章」

本文转载自: 掘金

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

python用turtle画一个旋转的风车 设计叶子 用循环

发表于 2021-10-24

上次用turtle做了一个爱心,这次我们来做一个旋转的风车,先放下图,看一下完成后的效果

1.gif

现在我们开始代码的编写

设计叶子

一片叶子我们用一深一浅两个三角形就可以实现了,下面以0度方向的叶子为例,我们写出的代码如下

1.png

用循环改为生成4个叶子

有了一个叶子,生成4个叶子也比较容易了,我们只要写一个循环,然后循环里用90的倍数去指定下方向角,剩下的绘制代码就一样了

2.png

加一个棒子

给风车的下面加一个稍稍粗一些的棒子,我们只要设置下画笔的粗细画一跟直线就可以了

3.png

风车的旋转

风车已经画完了,但是下面我们怎么才能让它转起来呢?
看到我们用循环出4个叶子的代码,在那里我们是指定了角度是90的倍数,把叶子的方向写成固定的了,如果我们把那个角度变成一个变化的变量,就可以让叶子往不同的角度转了。为方便使用,我们先定一个draw函数,让它可以接收自己指定的角度。

修改下大概是下面的代码

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
scss复制代码import turtle as t
t.speed(0)
def draw(r):
# 画一个风车的棒子
t.pensize(4) # 棒子4像素粗
t.penup()
t.goto(0,0)
t.pendown()
t.goto(0,-150)
t.pensize(1)

for i in range(4):
t.setheading(i * 90 + r)
t.penup()
t.goto(0,0)
t.pendown()
# 浅蓝色三角形
t.fillcolor('#50B2F8')
t.begin_fill()
t.forward(100)
t.left(150)
t.forward(70)
t.end_fill()
# 靠近中心的深蓝色三角形
t.fillcolor('#063EC5')
t.begin_fill()
t.left(30)
t.forward(40)
t.left(90)
t.forward(35)
t.end_fill()

draw(30) # 尝试不同的角度,叶子已经可以转到我们想要的角度了

t.done()

角度可以变了,但是转动的动画还没有,这个要怎么做呢?
我们可以写一个循环去调用这个draw函数

1
2
3
4
5
6
7
scss复制代码for r in range(0,360*100,3):  # 这里是旋转100圈,也可以用while循环,让它不停转,步长3对应的是转速,也可以改为1、2看看效果
t.tracer(False)
t.clear()
draw(r)
time.sleep(0.01)
t.hideturtle()
t.tracer(True)

好了,现在风车终于可以转起来了,最后我们贴下完成的代码

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
scss复制代码import turtle as t
import time
def draw(r):
# 画一个风车的棒子
t.pensize(4) # 棒子4像素粗
t.penup()
t.goto(0,0)
t.pendown()
t.goto(0,-150)
t.pensize(1)

for i in range(4):
t.setheading(i * 90 + r)
t.penup()
t.goto(0,0)
t.pendown()
# 浅蓝色三角形
t.fillcolor('#50B2F8')
t.begin_fill()
t.forward(100)
t.left(150)
t.forward(70)
t.end_fill()
# 靠近中心的深蓝色三角形
t.fillcolor('#063EC5')
t.begin_fill()
t.left(30)
t.forward(40)
t.left(90)
t.forward(35)
t.end_fill()

# 用速度1播放一次绘制过程
t.speed(1)
draw(0)

for r in range(0,360*100,3): # 这里是旋转100圈,也可以用while循环,让它不停转,步长3对应的是转速,也可以改为1、2看看效果
t.tracer(False)
t.clear()
draw(r)
time.sleep(0.01)
t.hideturtle()
t.tracer(True)

t.done()

本文转载自: 掘金

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

1…472473474…956

开发者博客

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