初级篇·简单时间戳生成
在使用jmeter做接口测试的时候,经常会要用到日期函数,让系统自动生成一些格式化的数据,方便接口测试,jmeter自身就带有时间戳的函数
1、__time():获取时间戳、格式化时间
(1)${__time(yyyy-MM-dd HH:mm:ss:SSS,time)} :格式化生成时间格式 2018-10-26 11:08:23:635
(2)${__time(yyyy-MM-dd HH:mm:ss,time)} :格式化生成时间格式 2018-10-26 11:08:23
(3)${__time(,)} :默认该公式精确到 毫秒 级别, 13位数 1527822855323
(4)${__time(/1000,)} :该公式精确到 秒 级别, 10位数 1527822871
(5)${__time(yyyy-MM-dd,)} : 该公式格式化生成的常规时间为:2018-10-26
(6)${__time(yyMMdd,)} : 该公式格式化生成的时间为:181026
2、__timeShift(格式,日期,移位,语言环境,变量)函数,可以将时间进行移位,对当前时间增加或者减少对应的时间
(1)格式 - 将显示创建日期的格式。如果该值未被传递,则以毫秒为单位创建日期。
(2)日期 - 这是日期值。用于如果要通过添加或减去特定天数,小时或分钟来创建特定日期的情况。如果参数值未通过,则使用当前日期。
(3)移位 - 表示要从日期参数的值中添加或减去多少天,几小时或几分钟。如果该值未被传递,则不会将任何值减去或添加到日期参数的值中。
“P1DT2H4M5S” 解析为“添加1天2小时4分钟5秒” “P-6H3M”解析为“-6小时+3分钟” “-P6H3M”解析为“-6小时-3分钟” “-P-6H + 3M”解析为“+6小时和-3分钟” (4)、区域设置 - 设置创建日期的显示语言。不是必填项
(5)、变量 - 创建日期的值将被分配给的变量的名称。不是必填项
${__timeShift(yy-MM-dd,2018-10-26,P2D,,)}这种返回的时间就是2018-10-28。 注意2018-10-26必须对应格式yy-MM-dd,否则会返回当天日期。
3、__randomDate(格式,开始时间,结束时间):时间段内随机获取时间
(1)格式默认为yyyy-MM-dd
${__randomDate(yyyy-MM-dd,2018-10-01,2018-10-30)},这种函数就会自动返回20181001-20181030之间的一个日期。
4 获取当天时间的23:59:59,然后转换为时间戳
${__groovy(new java.text.SimpleDateFormat('yyyy-MM-dd HH:mm:ss').parse('${__time(yyyy-MM-dd)} 23:59:59').getTime(),)} 解释:${__time(yyyy-MM-dd)} 23:59:59 是你输入的时间,程序会按照你输入的时间转换为13位毫秒时间戳
你还可以结合时间移位使用:${__groovy(new java.
01背包问题如何求解背包中的具体物品(JAVA) 引言:
01背包问题是比较基本的一种背包类型,起特征是背包内的物品只能取一次。
常规解法
对于常规解法就是构建一个二维的dp数组进行求解,主要问题在于选与不选
这里贴上完整的代码
private static int knapsack(int w, int n, int[] wt, int[] val) { // 二维数组:状态定义:dp[i][j]表示从0-i个物品中选择不超过j重量的物品的最大价值 int[][] dp = new int[n + 1][w + 1]; int[][] g = new int[n + 1][w + 1]; //base case // 初始化:第一列都是0,第一行表示只选取0号物品最大价值 for (int j = w; j >= wt[0]; j--) { dp[0][j] = dp[0][j - wt[0]] + val[0]; // System.out.println(dp[0][j]); g[0][j] = 1; } for (int i = 1; i < wt.
前言 首先咱们得先了解一下RabbitMQ不然光照着步骤来,不出一个礼拜有忘记了,首先了RabbitMQ是一个消息队列用到时AMQP协议,使用的是Erlang语言开发。
over 知道了这些就可以开始咱们的安装了,在上面我说过了,它是Erlang语言开发,所以咱们不着急下载RabbitMQ,得先去下载好Erlang配置好Erlang的环境,再去下载咱们的这么一个RabbitMQ,理由也很简单,RabbitMQ基于Erlang,就像咱们要运行Tomact是不是得先有Java的环境才可以呀,同理。
然后呢,RabbitMQ也自带了一个可视化工具,这样的话,咱们也可以更好的对咱们的消息进行一个监控,或者是说管理,但是它没有激活,需要咱们手动的去激活一下。
步骤: ok,总结一下 咱们要做哪些事情:
1、下载Erlang 配置环境变量。
2、下载RabbitMQ
3、激活可视化工具
下载Erlang 配置环境变量 在下载erlang之前呢,我们需要注意一个点,就是他们对应的版本,版本不对应的话,后面会很麻烦。
因为我RabbitMQ的版本是3.9.5的所有我下载的是23.2版本的Erlang,大家可以对照此图去参考,这张图也是我在官网上截取下来的。
下载地址:Erlang Programming Language
下载完之后,直接无脑式下一步就好了,当然安装的地址可以选一下,人家默认安装到c盘的,但是我这电脑性能不是很高,就把它安装到f盘了。
之后进入咱们安装的地址,到bin上一个目录下,复制路径,举例:F:\soft\erl\erl-23.2
右击我的电脑-----属性------高级系统设置-----环境变量,创建一个ERLANG_HOME,值则是咱们上一步复制的路径
再然后点击path新建 %ERLANG_HOME%\bin
保存退出,win+r,cmd 输入 erl
这样咱们的Erlang环境就配置完啦~
RabbitMQ下载安装 下载地址:RabbitMQ Project Announcements — RabbitMQ
也是直接下载安装,傻瓜式下一步就好。
当然rabbitmq也需要配置环境变量,还是找到咱们sbin的上一个目录
举例:F:\soft\RabbitMQ\rabbitmq_server-3.9.5
再次重复上面的步骤:右击我的电脑-----属性------高级系统设置-----环境变量
新建一个系统变量:RABBITMQ_SERVER
再在path选项中新建:%RABBITMQ_SERVER%\sbin
然后win+r,cmd,输入rabbitmqctl status
这样咱们RabbitMQ也安装好啦~
激活可视化插件 找到咱们RabbitMQ安装的位置下的sbin目录下
在目录上输入cmd,这样shell就默认打开了该目录。紧接着执行命令:rabbitmq-plugins.bat enable rabbitmq_management 出现了这个界面咱们就可以去浏览器上访问:http://localhost:15672
账号,密码都默认为guest
登录进来的样子:
今天的文章就分享到这里啦
文章目录 LC并联电路LC串联电路RL低通滤波器RL高通滤波器其他RLC电路变压器的用法——阻抗匹配稳压二极管实现稳压稳压二极管限幅电路简易直流电源 LC并联电路 特点:当Vin的输入信号达到谐振频率时,LC并联电路阻抗表现为无穷大,相当于断开。LC并联电路允许接近谐振频率的信号通过。
谐振频率:
LC串联电路 特点:当Vin的输入信号达到谐振频率时,LC并联电路阻抗表现为无穷小,相当于短路。LC串联电路起到了滤除某一频率信号的作用。
RL低通滤波器 特点:电阻与电感串联形成一个低通滤波器,Vin中频率低于fc的信号才能通过低通滤波器到达Vout。
RL高通滤波器 特点:电阻与电感串联形成一个高通滤波器,Vin中频率高于fc的信号才能通过低通滤波器到达Vout。
其他RLC电路 这个分频器实际上是一个低通滤波器,扬声器SP1具有阻抗,所以和电阻组成RL低通滤波器,低频信号才可以进入扬声器中被还原。
变压器的用法——阻抗匹配 变压器常常用于帮助阻抗匹配,如上电路,输入阻抗为2k欧,输出阻抗为20欧,阻抗不匹配,直接连接就会有较大信号损失,这时可在俩级间添加一个合适的变压器。
稳压二极管实现稳压 这是一个简单的直流稳压电路,ZPD5.1代表的是稳压二极管的型号,表示稳压值为5.1V,R1是限流电阻。
稳压二极管能通过的最大电流:
限流电阻的阻值应当为:
把稳压二极管嫁接到电源滤波电路,就得到了稳压二极管简易稳压电路,使9vDC输出稳定在5.1V。
稳压二极管限幅电路 输入波形与输出波形如下:
简易直流电源 简易直流电源由变压器,桥式整流,滤波电容构成。在设计直流电源时,常常在初级变压器和次级变压器上添加保险丝,当变压器出现意外短路时保险丝熔断。
yocto项目简介:
https://blog.csdn.net/qq_28992301/article/details/52872209?spm=1001.2014.3001.5501
http://www.fmddlmyy.cn/text43.html
yocto项目参考手册 (Yocto Project Reference Manual ),文章中有*.bb文件的所用到的名词/函数解析https://www.yoctoproject.org/docs/latest/ref-manual/ref-manual.html
SUMMARY = "test application" SECTION = "test" LICENSE = "MIT" LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302" #file:// 表示在本地目录 SRC_URI = "file://test.bin" SRC_URI += "file://test_log.conf" RDEPENDS_${PN} = " openssl " INSANE_SKIP_${PN} += "already-stripped" #inhert 引用外部配方文件 inherit update-rc.d INITSCRIPT_PACKAGES = "test" INITSCRIPT_NAME_identify = "test.sh" INITSCRIPT_PARAMS_identify = "start 60 S ." #编译,这个任务的默认行为是,如果找到一个makefile (makefile, makefile或GNUmakefile),就运行oe_runmake函数 do_compile() { cp ../test.bin test.bin cp ../test_log.conf log.conf cp ../test.sh test.sh } #打包到文件系统 do_install() { install -d ${D}${bindir} install -m 0755 test.
虚拟输入输出(Virtual Input Output,VIO)核是一个可定制的IP核,它可用于实时监视和驱动内部FPGA的信号,如图所示。
可以定制VIO的输入和输出端口的数量与宽度,用于和FPGA设计进行连接。由于VIO核与被监视和驱动的设计同步,因此应用于设计的时钟约束也适用于VIO核内的元件。当使用这个核进行实时交互时,需要使用Vivado逻辑分析特性。
接下来将介绍VIO的原理及应用,内容主要包括设计原理、添加VIO核、生成比特流文件和下载并调试设计。
设计原理 设计以下源码的工程,并添加VIO核:
`timescale 1ns / 1ps // // Company: // Engineer: // // Create Date: 2021/08/22 23:44:58 // Design Name: // Module Name: top // Project Name: // Target Devices: // Tool Versions: // Description: // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // // module top( input clk, input a, input b, output reg [5:0] z ); reg [5:0]z_tmp; wire [5:0] z_vio; reg a_tmp,b_tmp; wire a_in,b_in; wire sel; wire a_vio,b_vio; assign a_in=sel ?
并发编程网组织新的一期系列文章翻译,这期是GO官网教程 https://golang.google.cn/doc/ 相关技术文章,欢迎大家踊跃参加。
如何领取 通过本文留言或网站原文( http://ifeve.com/go/ )评论领取想要翻译的文章,每次领取一章或一节(根据内容长短),翻译完后再领取其他章节。领取完成之后,译文最好在一个星期内翻译完成,如果不能完成翻译,也欢迎你邀请其他同学和你一起完成翻译。请谨慎领取,很多文章领取了没有翻译,导致文章很长时间没人翻译。
如何提交? 翻译完成之后请登录到并发网提交成待审核状态,会有专门的编辑校对后进行发布。如果多篇文章翻译被评为A级会升级您为译者,并加入译者沟通微信群。如果在本站翻译超过十篇文章,将有礼品赠送,比如签名版的《Java并发编程的艺术》或者荣誉译者奖杯等。如果你喜欢使用markdown编写文章,可以将markdown生成后的HTML复制到网站上进行提交,mac下推荐使用MacDown软件。
Getting started Installing Go ( https://golang.google.cn/doc/install ) Instructions for downloading and installing Go.
Tutorial: Getting started ( https://golang.google.cn/doc/tutorial/getting-started.html ) A brief Hello, World tutorial to get started. Learn a bit about Go code, tools, packages, and modules.(该章节已翻译)
Tutorial: Create a module ( https://golang.google.cn/doc/tutorial/create-module.html ) A tutorial of short topics introducing functions, error handling, arrays, maps, unit testing, and compiling.
Writing Web Applications (https://golang.
1 参考文档 开发文档
https://github.com/surmon-china/vue-quill-editor
例子
vue-quill-editor | Homepage | Surmon's projects
中文开发文档
前言 · Quill官方中文文档 · 看云
版本号(注意:不适用于vue3)
"quill": "^1.3.7",
"quill-image-drop-module": "^1.0.3",
"quill-image-resize-module": "^3.0.0",
"vue": "^2.6.11",
"vue-quill-editor": "^3.0.6"
2 截图 3 源代码 3.1 vue.config.js // 解决import模块quill-image-resize-module错误 const webpack = require('webpack') module.exports = { chainWebpack: config => { config.plugin('provide').use(webpack.ProvidePlugin, [{ 'window.Quill': 'quill/dist/quill.js', 'Quill': 'quill/dist/quill.js' }]); } } 3.2 vue源代码 <template> <div> <quill-editor ref="myTextEditor" v-bind:options="editorOption" v-model="content"> </quill-editor> </div> </template> <script> // 设置基本的编辑框 import { quillEditor } from 'vue-quill-editor' import 'quill/dist/quill.
我们在开发中反复修改类、页面等资源,每次修改后都是需要重新启动才生效,这样每次启动都很麻烦,浪费了大量的时间。 能不能在我修改代码后不重启就能生效呢?可以,由于Spring Boot应用只是普通的Java应用,所以JVM热交换(hot-swapping)也能开箱即用。不过JVM热交换能替换的字节码有限制,想要更彻底的解决方案可以使用Spring Loaded项目或JRebel。 spring-boot-devtools 模块也支持应用快速重启(restart),即实现“热部署”。
1. 在pom文件中添加spring-boot-devtools热部署依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> 2. IDEA中热部署设置 选择【File】→【Settings】选项,打开Compiler面板设置页。目的:设置为自动编译 在项目任意界面中,使用快捷键“Ctrl+Shift+Alt+/”打开Maintenance选项框,选中并打开Registry页面。 目的:指定IDEA工具在程序运行过程中自动编译
3. 热部署测试 启动chapter01项目,通过浏览器访问http://localhost:8080/hello 修改类页面请求控制类(例:hellocontroller)中的请求处理方法hello()的返回值,刷新浏览器。
到这里 SpringBoot的热部署已经完成!
用Swap函数交换同一个数组的两个值(C语言) #include<stdio.h> void Swap(int a[], int i, int j) { int temp = a[i]; a[i] = a[j]; a[j] = temp; } void main() { int a[] = { 1,2,3,4,5,6,7,8,9 }; Swap(a, 2, 3); printf("%d\n", a[2]); printf("%d\n", a[3]); }
由于某些网站只支持ie浏览器,如果我们想免去安装ie浏览器的麻烦,不妨在谷歌浏览器安装个ie浏览器插件
准备工作 安装能上外网的工具
下载插件 安装后在右上角的扩展下有ie tab
点击上面的ie tab后发现会自动下载一个ietabhelper的应用
双击应用就可以在谷歌浏览器里使用ie浏览器了
只需要在网址栏输入对应的网址就可以进去了
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 的那 两个 整数,并返回它们的数组下标。
一、双层for循环暴力破解
public static void main(String[] args) { int[] a = {1,2,3,4,5,6,7,8,9}; int b = 17; outer: for(int i =0;i<a.length;i++){ for(int j=i+1;j<a.length;j++){ if(a[i]+a[j] == b){ System.out.println("i=="+i); System.out.println("j=="+j); break outer; } } } } 二、使用HashMap进行处理
public static void main(String[] args) { int[] a = {1,2,3,4,5,6,7,8,9}; int b = 17; Map map = new HashMap<>(); for (int i = 0; i < a.length; i++) { map.
简单选择排序(C语言) #include<stdio.h> void Select_Sort(int a[], int n) { int i, j; for (i = 0; i < n - 1; i++) { int min = i; //i表示当前最小元素应该在的位置,min记录最小元素的实际位置 for (int j = i + 1; j < n; j++) if (a[j] < a[min]) min = j; if (min != i) { int temp = a[min]; a[min] = a[i]; a[i] = temp; } } }
在C++语言中,struct对C语言中的strcut进行了扩充,已经不仅仅是一个包含不同数据类型的数据结构体了,在C++语言中,strcut可以包含成员函数,可以实现继承,可以实现多态。
在C++语言中,结构体struct与类class的最本质区别即为:默认方式控制,结构体struct默认是公有的,而类class默认是私有的。
实例代码:
struct TStructA
{
};
struct TStructB : TStructA
{
};
在该示例中,TStructB是public继承于TStructA的,将上述的strcut关键字改为class关键字,那么TStruct是private继承于TStrcutB的。推荐写程序时,public继承则标注为public,private继承则标注为private而不是采用strcut与class的默认访问权限的特性,例如:struct TStrcutB : public TStrcutA 。
结构体可以继承类,而类也可以继承结构体,这时的访问权限取决于子类或子结构体而非父类或福结构体,例如:
struct A{};
struct B : A {}; //public继承
class C : A {}; //private继承
struct作为数据结构的实现体,它默认的数据访问控制是public的,而class作为对象的实现体。它默认的访问控制是private的。虽然struct作为数据结构的实现体可以向class一样使用,但是在这里依旧将struct里的变量称为数据,class里的变量称为成员。使用struct或class完全依据个人的爱好,在程序中,将所有的class全部替换为struct,程序依旧可以正常运行。前提条件是:对于访问控制应该明确给出而不是依赖于默认访问控制特性,这既是一个良好的习惯,同时也可使代码更具可读性。
struct与class除了访问控制这一区别外,还有一点即为class关键字可用于定义模板参数,类似于typename,但struct不用于定义模板参数。
struct TstructA
{
char c;
int a;
double d;
};
A obja = {'a', 12, 3.14}; //定义时直接赋值
struct 可以在定义时使用{} 进行赋初始值,若此时struct改为class则会报错。若在struct TStructA中添加一个构造函数或者虚函数,就会发现struct也不能使用{}赋初始值了。采用{}的方式进行赋初始值,只是用一个初始化列表来对数据进行按顺序的初始化,{'b', 11}也可以给数据 c 与 a 进行初始化,不对 d 进行初始化,像这种简单的copy的操作只能使用在简单的数据结构上,而不能使用在对象上。加入一个构造函数或虚函数使得struct体现出一种对象的特性,使得{}操作不再起作用。从本质上说,加入这样一个函数使得结构体的内部结构发生了变化。如果加入一个普通函数会发现{}赋初始值操作依旧可用,因为普通函数可被理解为数据结构的一种算法。
而将上面的struct改为class之后使得访问控制由public变为了private,自然而然{}直接赋初始化值就不能使用了。
问题 解决方案 sudo apt-get install tofrodos; sudo ln -s /usr/bin/fromdos /usr/bin/dos2unix
一、埃氏筛选法
埃拉托斯特尼筛法,简称埃氏筛或爱氏筛,是一种由希腊数学家埃拉托斯特尼所提出的一种简单检定素数的算法。要得到自然数n以内的全部素数,必须把不大于根号n的所有素数的倍数剔除,剩下的就是素数。
要得到自然数n以内的全部素数,必须把不大于 的所有素数的倍数剔除,剩下的就是素数。
给出要筛数值的范围n,找出以内的素数。先用2去筛,即把2留下,把2的倍数剔除掉;再用下一个质数,也就是3筛,把3留下,把3的倍数剔除掉;接下去用下一个质数5筛,把5留下,把5的倍数剔除掉;不断重复下去…。
二、题解描述
三、题解代码:
class Solution { public int countPrimes(int n) { if(n<=2){ return 0; } Boolean isPrimes[] = new Boolean[n]; for(int i=0; i<n; i++){ isPrimes[i] = true; } //埃氏筛选算法主体 for(int i=2 ; i<n ; i++){ if(isPrimes[i]){ int k=3; for(int j=i*2; j<n;){ isPrimes[j]=false; j=i*k; k++; } } } int count=0; for(int i=2; i<n ; i++){ if(isPrimes[i]){ count++; } } return count; } }
作者:汤波
来源:https://tbwork.org/2018/10/25/layed-dev-arch/
本文获得阿里巴巴《第二届研发效能征文》优秀文章奖,并在阿里第二届能效峰会上展出。
引言 看标题感觉这个东西很理论,比起“高并发、多线程”、“分布式CAP、一致性、Paxos”、“高可用SLA”等具体的干货技术点,软件体系知识显得很“湿”,似乎人人都有自己的认识,但又很少有人能说完整,有一点可以确定的是,如果你未来需要独立设计一个复杂的系统中台,并使之未来能快速应对各种需求变化的话,科学合理的领域划分和边界界定需要我们“处女座级”的坚持下去,这对防止人力失控、减少项目烂尾很有帮助。合理的界定了边界后,即便某个微服务很糟糕,也可以就输入输出以很少的人力投入进行重构,相反的就是牵一发而动全身,加上业务需求频繁而来,很容易烂尾或是达不到如期的效果。
其实很多技术大神都是某一个技术点的好手,但可能在整体软件体系上思考并不多,每个人都有自己的设计方法,大部分容易想到的设计方法处理一般的系统已经够了,后面发生问题慢慢打补丁就行了,当我们面对各种需求变化陷入开发困境的时候我们就该想想了,咱们系统的体系设计上是否出了问题?
本文不打算涉及领域建模和设计模式等代码级别的详述,而是探讨如何将一个复杂的大系统进行分层和拆分,这是设计一个优美系统的第一步,相信对各BU同事们快速搭建系统中台也是很有参考意义的。文中的一些例子大家也可能遇到过,大家如果在开发中遇到困境,可以多来圈子交流和发表问题,大家一起学习进步。大概知道内容背景的可以直接跳到第3部分。想了解一个大项目如何进行科学人员安排的可以直接看5.4部分。如果你的组里还有人把数据库模型当接口契约用,可以建议他看下5.1部分。假如你在开发过程中遇到一些别人的开发设计习惯,你觉得不是很好,但是又不知道如何说服他,都可以到评论区聊聊,大家一起讨论讨论。
1.摘要 本文阐述了一种将分层设计和DDD领域设计思想应用于微服务体系架构的方案实践,也是个人的最佳实践。对于大部分互联网公司来说,我们主张将其Web服务架构分为五层:基础设施层、领域服务层、应用服务层、网关层和用户界面层(表示层)。
领域服务层和应用服务层均可以采用微服务设计进行拆分,其中领域服务层将按照DDD领域设计进行领域划分,设计为一个个领域模块微服务,每个微服务高度内聚,仅关注自己的业务,领域服务间通过接口调用进行松耦合。这种设计方案可以大大简化大系统,并且在后期的维护中优势会日渐凸显,然而把大系统分而治之拆成微服务同时也对架构师和开发人员提出了更高的要求。
第2部分介绍了相关背景,接着第3部分探讨了分层设计以及每一层的功能,第4部分结合微服务和DDD对领域服务层进行服务模块划分和设计。第5部分则就分层设计和DDD领域设计中常见的问题进行了整理。
2.背景介绍 想写这样一篇文章很久了,虽然本科学的是软件工程,但碍于自己能力有限,从08年写代码以来一直断断续续的思考,始终对项目模块设计和分层结构设计没有一个可以让自己觉得满意且无纠结点的答案,假设了某个设计,很快在实践中又会发现其存在着一些问题。直到2014年毕业工作了解了DDD领域驱动设计后,才有了相对清晰的方向。实际上早在2004年,Eric Envas的《领域驱动设计:软件核心复杂性应对之道》就已出版,毕竟软件开发自计算机普及以来已经存在很长一段时间了,早期国外程序员对软件开发理论的研究也十分兴盛,如今成熟后反而研究的相对少了,基本上依葫芦画瓢即可。
DDD领域驱动设计对软件设计各个环节的人员都有较高的要求,用《领域驱动设计》一书的话来说它需要一个“领域驱动团队”[1],它要求从分析阶段,产品经理、项目经理、架构师以及开发工程师就使用统一的模型语言(Ubiquitous Language)来进行沟通,并且他们都懂一些代码、产品和建模相关的知识,事实上这在国内很难实施,国内的产品经理约等于需求整理工,对其计算机基础的要求是少之又少,在我所从事的公司里,也曾发生过产品经理直接指导开发,以至于后面双方理解的同一个词有着不同含义的情况。所以本文不打算去阐述DDD领域内部建模代码级别的实践,甚至本文并不认为贫血模型是不好的,本文主要探讨领域之间的划分和分层设计,正如引言说提到的,这是设计优美系统的第一步。另外提一句:其实合理设计的微服务体系中的服务本身就是功能单一边界清晰的小应用,届时贫血也好、DDD领域建模也好,其实都可以胜任。
近年来,随着分布式的发展,传统中小型机集中式服务器已经不在流行,所以微服务体系也成为了各大互联网公司主流的选择。直观的感受下微服务和DDD两者,似乎一个是微系统,另一个则是大系统的设计方法,似乎两者天生互斥,微服务化的小系统也用不着DDD,其实并不是,DDD是针对整个复杂的软件解决方案的一种科学设计方法,微服务化也是把复杂的大系统拆分为小系统,方便维护和管理,所以两者都有一个特点——为复杂的大系统服务。下面咱们就来探讨下,如何把DDD的领域设计和其主张的分层设计应用到微服务体系架构中。需要说明的是本文主要是个人多年来的一点总结,未必适合所有场景,有更好通用性更为广泛的方案请不吝赐教。
3.分层设计 准确的说分层设计(Layered Architecture)跟DDD没有必然的联系,我最早接触分层设计是在携程网,当时内部使用的应该只是简单的业务层(Biz)和表示层,数据库访问之类的也是放在各自的业务包下的。后来接触和学习了《领域驱动设计:软件核心复杂性应对之道》,书的第4章“分离领域”中说到了四层分层设计,即:基础设施层、领域层、应用层和用户界面层(表示层)。DDD产生的年代微服务还未流行,当时甚至基于浏览器的Web应用都比较少,更多的是PC软件和EJB等网络应用,所以作者更多的是想表达对复杂系统的逻辑分层,并不在意每个领域是单独的系统还是一个软件系统内不同的模块。
所以为了跟其做区分,我们建议的四层为在其基础上引入“服务”两个字,即:基础设施层、领域服务层、应用服务层和用户界面层。这样做的意图是让开发人员立刻可以了解到——每个领域模块即一个微服务(一个领域可以对应一个或者多个模块Module)。摘要中提到我们主张的分层体系中还有一个层,即网关层,这又是什么鬼呢。刚刚提到的DDD的时代背景,PC软件系统或者企业内部使用的网络应用系统是根本没有网关层(有也是网络网关设备)这一说的,而现如今互联网公司产品的输出形式无外乎Web应用(网站、或者网络服务),并且为了更好的适配PC站和App,一般会采用前后端分离的应用设计方案,这时候会产生一个需求——内部网络应用系统如何把自己的服务输出到互联网上,供外部系统或者浏览器网页访问。最直接的方式就是把应用层直接暴露在公网上,但我们不建议这么做,应用层服务更多的是关注业务应用,对网络级的系统安全性(防DDOS、钓鱼、跨域等)、请求监控等缺乏考虑,这些工作交给网关层统一管理会轻松很多(比如淘宝的TOP平台)。
这时候我们在Web应用系统中引入网关层用于衔接表示层和应用层 ,因为这样可以更好的划分各层的职能。网关层也可以看作是应用服务层的对外包装层。如果一定要把网关层做到应用服务层里理论上也是可行的,比如针对于Spring Cloud这种框架下的微服务体系,可以考虑直接暴露应用层,只需辅助一些运维手段进行统一的安全验证和监控即可。假设我们选择引入网关层,那么我们就得到了以下网络应用系统分层体系:
其中,各层的职能和作用为[2]:
用户界面层:负责向用户显示和解释用户指令。这里指的用户可以是另一个计算机系统,不一定是使用用户界面的人(比如外部应用调用对应接口)。在全系统的视角看,通常情况下,这里指的是前端,但实际上,随着前端的日益复杂,前端也可以分为多层,那种情况另当别论。网关层: 负责提供对外的HTTP服务或者其他网络应用层协议(这里是指OSI七层协议中的应用层,别混淆了哦)服务。该层从非业务逻辑角度对暴露到外部的接口进行鉴权、计费、风控、资源调配等操作。有些简易的系统中,改成可以合并入应用服务层中作为一个AOP存在。应用服务层:定义软件要完成的任务,并且指挥表达领域概念的对象来解决问题。这一层所负责的工作对业务来说意义重大,也是与其他系统的应用层进行交互的必要渠道。应用层要尽量简单,不包含业务规则或者知识,而只为下一层中的领域对象协调任务,分配工作,使他们互相协作。它没有反应业务情况的状态,但是却可以具有另外一种状态,为用户或者程序显示某个任务的进度。领域服务层:负责表达业务概念,业务状态信息以及业务规则。尽管保存业务状态的技术细节是由基础设施层实现的,但是反应业务情况的状态是由本层控制并且使用的。领域层是业务软件的核心。基础设施层:为上面各层提供通用的技术能力,为应用层传递消息,为领域层提供持久化机制,为用户界面层绘制屏幕组件(PS:这个在互联网应用中几乎用不到)等等。互联网Web应用系统中基础设施包含了数据持久化服务,中间件服务(数据库,Redis,Memcached,zookeeper,ELK等等)以及第三方服务等。 各层除了实现自己的功能外,还需要遵守以下原则:
每一层设计保持内聚,并且只依赖于它的下方的层。下层向上层发起的通信只能通过中间件等间接方式进行。[2]上层和下层只能有松散耦合(各自为独立个体,通过简单引用关联)。在某些微服务框架比如Dubbo中,可以把api包提供给上层引用即可。这也符合依赖倒置原则。 这里重点说明应用服务层和领域服务层之间的关系。举一个我经常跟部门其他开发举的一个例子:有一家上市企业A公司,靠卖水果发家,其首席架构师科学合理的按照DDD搭建了一套基于微服务体系的卖水果应用,其架构图如下:
今年水果行情一般,而房地产十分火热,A公司高层发现房地产带动的五金行业也十分火热,于是下达任务给技术部,要求其立即着手搭建五金销售系统,货源已经谈好。得益于首席架构师之前优秀的架构设计,他发现只需要做一个卖五金的网站以及另外对微服务进行微量的调整即可满足老板的需求——因为卖五金和卖水果并无本质区别,他们涉及的环节几乎一致。加入五金售卖的系统架构图如下:
可见应用服务层代表是某一个业务应用,它代表的更多的是从需求出发的应用定义,而领域服务层则是业务领域按照自身的边界进行设计的一个高内聚的服务体。应用层通过协调和组合各个领域服务即可形成一个新的应用服务。《领域驱动设计》中明确指出,在设计领域服务时无需考虑表示层和持久层服务的东西。我在现实开发中总是遇到大量工程师按照产品的设计稿一溜烟的从上至下设计应用层服务和领域层服务,完全没有考虑业务领域的概念,导致后面微服务数量膨胀,功能重复度高。这种开发习惯代表的是《领域驱动设计》作者极力吐槽的一种模式——SMART UI “反模式”[5]。
4.领域划分和微服务化 根据DDD理论,领域建模主要发生在领域服务层,各领域模块都应该是高内聚低耦合的,具有清晰的业务边界。本文不打算讨论具体的DDD建模(服务,工厂,仓库,实体,值对象,聚合等),这需要对DDD有较深入的研究,就目前所从事过的公司来看,似乎没有一家真正严格按照DDD进行项目代码设计的,就像摘要中说的,这对整个软件工程链路上的人员都有较高的要求。有机会可以单独写一篇关于自己对DDD建模的思考和建议,本文更多的是讨论高视角下的领域服务拆分,从而搭建一个低耦合高内聚的微服务体系。如果一定要将微服务和DDD联系起来的话,领域层的微服务就对应了DDD中的领域模块Module,每个Module由多个Service模式对象以及对应的模型对象(实体, 值对象以及它们的聚合)组成。
从《领域驱动设计:软件核心复杂性应对之道。》中我学到的主要有两块:领域设计思想和领域建模模式。本文更多的是对前者的运用,后者的对立模式是贫血模型,大家日常用到的也都是贫血模型,我也觉得贫血模型有存在的必要性,所以本文我们主要从其中借鉴一下领域设计思想。本文所描述的设计理念,并不影响具体的模型设计方法,我们仍然可以在每个微服务中使用DDD领域建模。
如何切分领域模块并没有一个明确的规则,不同的场景下可能相同的业务块边界也不尽相同。这里提几点领域划分的个人心得:
领域设计一定要有清晰的功能边界。一个领域服务对应了一个功能集合,这些功能一定是有一些共性的。比如,订单服务,那么创建订单、修改订单、查询订单列表,一般是订单域的功能集合。领域拆分并不是一步到位的,应当根据实际情况逐步展开。从单体应用到微服务体系的拆分过程能很好的说明这个问题,一上来拆的很细的改造方案一定会死的很惨。所以如果一开始不知道应该划分多细,完全可以先粗粒度划分,然后随着需要,初步拆分。比如一个电商一开始索性可以拆分为商品服务和交易服务,一个负责展示商品,一个负责购买支付。随后随着交易服务越来越复杂,就可以逐步的拆分成订单服务和支付服务。领域拆分并不是一成不变的,应当具体情况具体分析。2015年在大众点评的时候,其订单服务就拆分为了order-service和order-query-service,一来为了读写分离,二来order-query-service作为单独应用可以按需水平扩容。领域可以是多个子领域的一个虚拟集合,换句话说多个微服务也可以形成一个大域,不必纠结于领域和微服务之间的数量对应关系。我们在做架构设计PPT的时候可能就把订单域作为一个领域,代表了这个域就是关于订单的,具体该有几个微服务,这需要更细的详细设计来提供。领域层服务设计应当是调用者无关的。这一点有点像第一点,但是它强调的是领域层服务的设计不应该受调用者的影响,这个观点在《领域驱动设计:软件核心复杂性应对之道》这本书里也可以找得到[4]。领域层服务开发和设计的理念是关注自己的域,一旦边界划分清楚了,开发所需要考虑的永远都只是输入和输出,提供的服务一定是尽可能通用的,面向功能来开发的,而不是面向调用方来开发的。比如某个调用方提出了一个需求:调用方B希望A服务提供一个买汽车的接口,那么A服务设计的接口就应该是buyCar(),而不是buyCarForB()。 5.Q&A 5.1 能不能在所有层使用数据持久层模型,简单快捷?
大家一定听说过不同层的数据模型的叫法不同的概念,比如数据持久层的模型对象叫DBO(database object)或者DPO(data persistence object),领域层的模型对象叫DMO(Domain Model Object)或者就叫Model,数据传输层的模型对象叫DTO(Data Transfer Object)。那为啥要这么多模型呢,直接使用Mybatis等ORM框架生成DBO,然后一路吐给前端不是更爽(还真有同事尝试立项写Mybatis插件来实现这种所谓的代码自动化)。我个人建议如果您真的是要搭建一个复杂的大系统,大平台,一定不要偷这种懒,最好的就是做到”一层一模型”(网关层使用应用层模型即可)。各层之间采用手动的数据赋值(getter,setter)来完成,或者使用一些转换框架来简化转换代码,个人在用getter/setter时感觉并不会耽误什么,在一个个set的时候,恰好可以对模型的字段细节进一步确认,并且拒绝使用BeanUtils.copyProperties()这种工具类,因为这样的工具类会让”一层一模型”形同虚设,开发会热衷于把DPO拷贝到领域中换个名字以保证可以用拷贝工具。下面我们来细谈下不能在每一层都是用数据持久层模型的具体原因:
应用层对接网关层,是向面向C端或者调用者的一个数据出口,但是调用者只需要这个出口输出用户感兴趣的数据,并且有些敏感数据不能吐出去。如果应用层(面向调用者)使用的仍然是数据库模型,而开发人员没有在应用层把无关值置空的话(相信我,需求一多,工作一忙,鬼才会在意这些细节),那么数据库里的整条记录就作为接口输出吐出去了。比如订单记录,用户订单列表可能只需要订单ID,商品名称,订单金额。而像商家结算价这种就不能吐出去,万一被有心人察觉到了,用户一定会投诉——你跟商家结算价200,卖给我400?前端或者接口调用方会很痛苦,一个接口契约多一两个无关字段是没关系的,但是一个契约里给的30个字段,我只用到5个,前端会骂娘的,我亲眼见过这种事,设计原则里有个原则叫”迪米特法则”,也叫最小知识原则,接口设计也可以参考这个原则,尽量让你的调用方知道尽可能少的信息点就能完成相关的任务。“一层一模型”本质是解耦模型依赖。我在上家公司做架构师时为了兼顾开发的感受,决定让他们可以在领域层和基础设施层都是用数据持久层模型,而只需要在应用层做数据控制(解决第一个问题),然而我的妥协也慢慢露出弊端,开发 java教程有时候觉得某个数据库字段命名不合适修改之后,整个引用了该模型的微服务都需要修改,如果一层一模型的话,只需要关联数据库访问的服务修改下DPO和DM的映射就行了,其他上层微服务都是依赖DM的。虽然我们不鼓励随意改动数据库字段,但设计框架上最好能支持这种情况。 刚开始推广”一层一模型”的时候,会有耍小聪明的开发去把下一层的模型POJO直接拷贝过来改个名字,然后用BeanUtils.copyProperties()完成赋值,这样跟直接使用数据持久层模型就没有区别了,所以要杜绝这种情况的发生。
5.2 为啥需要应用层,领域层微服务直接通过网关暴露不就行了吗?
对于习惯了单体应用开发者来说,一个微服务很可能就直观对应成了一个个垂直的应用服务,每个服务间的关系是这样的:
其实这样的体系本质上仍然不能解决软件的复杂性,这只是把系统简单粗暴的拆分了,耦合问题仍然很严重,甚至这很有可能比原来的单体应用更复杂(多对多依赖),如果使用微服务体系来处理复杂系统,其服务体系应当是这样的:
这两幅图的区别在于,其实第一幅图中的每个服务都包含了完整的2~3层,所以不再需要单独的应用层。而第二幅图各个领域模块互相协作,对外提供服务时,则需要有一层直面用户需求的应用层。
达成了微服务体系是解决复杂系统的出路之一这个共识后,我们再来看”应用层服务存在的必要性”有哪些理由:
统一权限校验:如上文所说,网关层只负责网络级的安全防护,业务层的权限校验则需要应用层来完成,试想一个没有应用层的微服务体系,就意味着每一个微服务都需要加上权限校验逻辑,这不仅编码上困难(可以用过滤器,AOP),而且对于成千上万个微服务(据了解,腾讯目前微服务数量已经超过2万,大众点评有将近千个微服务)来说,这会浪费大量时间,调用链越长,浪费的时间越多。换句话说,微服务体系有一个不突出但是很重要的特征—— 领域间环境安全,领域间的通信应当是可信的 ,否则分布式的缺点(多服务意味着多次通信)会被加剧。业务数据网关:举个例子,一个order-service提供了一个queryOrder的接口,输入起始日期查询对应的订单列表,其有2个消费者:C端网站应用服务 和 报表应用服务 ,C端网站应用服务 只需要知道订单的基本信息如下单时间、商品名称、金额就可以了,而报表应用服务是给管理者看的,需要的订单数据很全,除了C端网站应用服务需要的之外,还需要看平台与商家的结算金额。根据第4部分最后一点的思路,我们肯定不能为调用方写定制接口(写不完的,有的要这个数据,有的要那个数据,每次新增调用方,领域服务还得找人修改)。而如果我们统一使用的全量数据,并且没有应用层(同样的也没有应用层模型DTO了),那么很可能我们吐出去的数据包含了我们与商家的结算价,这会引发很多不必要的麻烦的。所以应用层还充当了业务数据网关的作用,应用层应用服务需要保证仅吐出调用方感兴趣的数据。资源控制和缓存:想象一下双十一高并发的情况,如果查询库存每次都查库是多么恐怖的一件事。所以一般仅在支付的时候做一次库存校验,而在商品展示时查缓存的库存即可。那么问题来了,如果没有应用层,缓存直接放在库存微服务上是否可行呢?首先这会入侵库存领域,库存微服务需要按照调用方的需求做特定时间的缓存,而不是自己想缓存多久就多久,我想库存微服务的开发者也会很不满的,他会提出,让你自己去做缓存。他的方案是科学的,因为还有一些其他服务可能需要实时的数据。这时候就需要有一层来做对其下方微服务返回的数据按照应用自身的需求进行必要的缓存,而不是把这些需求都推给资源提供方,想象一下一个资源提供方有多少需求者,每个需求方都有自己的定制需求,该多痛苦。当然这一点也不是说微服务自身不能做缓存,微服务自身的缓存一定是考虑自身域的合理性后的一个措施(比如订单查询服务会做一个500ms的缓存,因为不会有正常人500ms里点两次查询还必须要求两次都是最新的),而不是由调用方来决定的。资源聚合和加工:其实第2点也有加工的味道在里面,只是这里更多的是描述应用层应用根据自身需求来对下层返回的数据进行聚合和处理的过程。举个例子就能很好的说明这一点:任何APP都有首页,而首页的数据可能是五花八门的,可以有用户昵称、最近下的订单简要信息、最近支出曲线、积分信息等。这4个信息可以来自4个领域微服务,他们是:用户中心、订单中心、支付中心和积分中心。那么有读者会说,直接暴露微服务让前端分别调用4个接口再做聚合不是也行吗?显然这种粗暴的方式是极其不合理的,会额外增加广域网网络调用3次不说,还传输了很多不必要的信息。应用隔离和流控:如果将每个领域服务直接暴露到网关层对外提供服务,那么在多应用场景下,多个应用间是共享这些服务能力的,在服务降级的时候,如果需要按照应用进行降级(比如将优先级不高的应用进行限流),就很难实现。但如果每个应用对应了一个应用层服务,只需要对其暴露的网关接口进行统一限流就行了,或者在应用层做一个开关,将其流量阻止在应用层,而不是拖垮整个领域服务。举个例子,假如我们的平台不仅有自己的网站服务,还有第三方的对接服务,如果某个第三方被攻击而我们直接将领域服务暴露了出去,那么我们就需要在各个领域层服务里去编写对应的开关,这将侵入领域层服务,导致不必要的耦合。而有了应用层这些都不是问题,因为应用层充当了一个调度者的角色,调度者可以很轻松的决定是否调度下层的服务。 为了加深对应用层的理解,我们举个代码的例子,假如我们写一个很简单的首页应用:
Response getHomeData(Request request){ String nickName = userService.
非极大值抑制(Non-Maximum Suppression,NMS),顾名思义就是抑制不是极大值的元素。它在目标检测中起着非常关键的作用。
目标检测一般分为两个过程:训练过程+检测(推理)过程。
在训练过程中,目标检测算法会根据给定的ground truth调整深度学习网络参数来拟合数据集的目标特征,训练完成后,神经网络的参数固定,因而能够直接对新的图像进行目标预测。 然而,在实际的目标预测中,一般的目标检测算法(R-CNN,YOLO等等)都会产生非常多的目标框,其中有很多重复的框定位到同一个目标,NMS作为目标检测的最后一步,用来去除这些重复的框,获得真正的目标框。
在两阶段目标检测算法中,以Faster-RCNN为例,有两处使用NMS,第一处是在训练的时候,利用ProposalCreator来生成proposal的时候,因为只需要一部分proposal,所以利用NMS进行筛选。第二处使用是在预测的时候,当得到300个分类与坐标偏移结果的时候,需要对每个类别逐一进行非极大值抑制。那为什么对于每个类别不直接取置信度最高的那一个作为最终的预测呢?因为一张图中某个类别可能不止一个,例如一张图中有多个人,直接取最高置信度的只能预测其中的一个人,而通过NMS理想情况下可以使得每个人(每类中的每个个体)都会有且仅有一个bbox框。
在一阶段目标检测算法中,以YOLOv5为例,输入一张640*640的图像,NMS之前会产生(80*80+40*40+20*20)*3=25200个目标框,这些框都有相应的分类置信度,当置信度满足正样本条件时(比如100个框,这些框密集的分布在目标周围),被送入NMS,NMS后会产生个数位个目标框(比如7个),如下图所示。
NMS作为计算机视觉中检测的一个组成部分,已经有50年的历史[1]。它首先被应用于边缘检测技术,随后被应用于特征点检测、Face 检测和目标检测中。在边缘检测中,NMS进行边的细化移走伪响应。在特征点检测中,NMS在进行局部阈值以得到独一无二的特征点检测中非常有效。在Face检测中,NMS使用交叠标准划分边界框到不关联的子集。 近年来,NMS仍然被应用在现代目标检测器中。也有个别基于学习的方法被提出作为Greedy NMS的替代。在[2]中,首先计算每一对检测框的交叠,然后进行仿射推理聚类,为每个代表最终检测框的类选择一个样例。在[3]中,多类的版本被提出,这里每张图像上所有类的实例被同时评估。 本文介绍现代的NMS基本算法及改进算法,具体为:
传统的NMS
带权的NMS(soft NMS,Weighted NMS,Softer NMS )
考虑定位的NMS
Adaptive NMS
并行的NMS(Fast NMS, Cluster NMS)
1 NMS基本算法
NMS基本算法的具体步骤如下:
1)依据框的分数(即目标的概率)将所有预测框排序;
2)选择最大分数的检测框M,将其他与M框重叠度大(IoU超过阈值Nt)的框抑制;
3)迭代这一过程直到所有框被检测完成。
在NMS中,使用小的阈值Nt(如0.3),将会更多的抑制附近的检测框,从而增加了错失率;而使用大的Nt(如0.7)将会增加假正的情况。当目标个数远远小于RoI个数时,假正的增加将会大于真阳的增加,因此高的NMS不是最优的。
2. 带权的NMS
带权的NMS包含三种算法:Soft—NMS[4], Weighted NMS[5], Softer-NMS[6]
A. Soft-NMS
背景:根据传统NMS算法的设计,如果一个真实的目标框的分数满足阈值范围,它就会被抑制掉,如下图所示。
解决方案:提出Soft-NMS,降低检测的分数,构成与M重叠度的连续函数,而不是直接删除目标。
Soft-NMS分析:如果检测框M附近的一个检测框b真的包含一个没有被M覆盖的物体,它在低的检测阈值下不会导致错失这个物体;如果检测框b不包含任何物体,那么即使降低了它的分数,它仍然排在真实检测的前面,会产生一个假正的检测。因此NMS考虑了下面的条件。
邻域检测的评分应该降低到使假阳性率上升的可能性很小,但高于检测列表中明显的假阳性。
用一个低NMS阈值移走相邻的检测框将是次最佳的,它会在用高的重叠率评估时(如mAP@0.90)增加错失率;
当NMS阈值过高时,在重叠范围内测量的平均精度(mAP)将会降低。
Rescoring 函数
当邻居检测框b与当前框M有大的IoU时,它更应该被抑制,因此分数更低。而远处的框不受影响。
为什么可以这样呢?细想一下,如果gt之间存在overlap的情况,则我们的目的是抑制那些overlap更大的。比如有三个结果ABC,分数分别为0.9,0.8,0.7,其中AC是正确结果,A和B的overlap大于A和C的,这样经过一轮NMS后BC的分数可能就变了0.6,0.5,从而将B抑制了。
然而,上面的公式不是一个连续函数,在Nt处会有一个突然的惩罚。连续的高斯罚函数如下:
算法伪代码如下:
Soft-NMS是NMS的更通用形式,NMS是Soft-NMS的一种特例。
B. Weighted NMS
背景:NMS方法在一组候选框中选择分数最大的框作为最终的目标框。然而,非极大的检测结果保存了特征的最大值,因此忽略非极大的检测检测结果是不合适的。
解决方法:假定所有的Box来自相同的物体,通过考虑非极大结果充分考虑了目标的信息,提出了如下的非极大权重(Non-Maximum Weighting):
其中n为box的个数,Ci是boxi的置信度。
C. Softer-NMS
背景:在目标检测数据集中,一些Ground-truth框往往有模糊性(不准确的标注、遮挡、边框模糊)。
(a)两个候选框坐标都不准确 (b)高准确率的框左边界不准确
解决方法:边界框参数化(预测一个分布替代唯一的定位);训练时边框回归时采取KL损失;一个新的NMS方法提高定位准确性(下图)。
当选择了最大分数的b之后,它的新位置根据它自身和邻居框计算。受Soft-NMS启发,离得越近,不确定性越低,会分配更高的权重。新的坐标计算如下:
3. 考虑定位的NMS( IoU-guided NMS)[7]
使用文档查起来真是太不方便了,有时查下相关的内容,找其来难死了,不知大家有没有同感 接口wx.redirectto与wx.navigateTo的OBJECT 参数相同,如下表所示:
wx.navigateTo(Object object) 以 Promise 风格 调用:支持
需要页面权限:小程序不能在插件页面中调用该接口,插件也不能在小程序页面中调用该接口
小程序插件:支持,需要小程序基础库版本不低于 2.2.2
在小程序插件中使用时,只能在当前插件的页面中调用
微信 Windows 版:支持
微信 Mac 版:支持
保留当前页面,跳转到应用内的某个页面。但是不能跳到 tabbar 页面。使用 wx.navigateBack 可以返回到原页面。小程序中页面栈最多十层。
参数 Object object 属性类型默认值必填说明urlstring是需要跳转的应用内非 tabBar 的页面的路径 (代码包路径), 路径后可以带参数。参数与路径之间使用 ? 分隔,参数键与参数值用 = 相连,不同参数用 & 分隔;如 'path?key=value&key2=value2'eventsObject否页面间通信接口,用于监听被打开页面发送到当前页面的数据。基础库 2.7.3 开始支持。successfunction否接口调用成功的回调函数failfunction否接口调用失败的回调函数completefunction否接口调用结束的回调函数(调用成功、失败都会执行) object.success 回调函数
参数
Object res
属性类型说明eventChannelEventChannel和被打开页面进行通信 示例代码 wx.navigateTo({ url: 'test?id=1', events: { // 为指定事件添加一个监听器,获取被打开页面传送到当前页面的数据 acceptDataFromOpenedPage: function(data) { console.log(data) }, someEvent: function(data) { console.log(data) } ... }, success: function(res) { // 通过eventChannel向被打开页面传送数据 res.
1.基础知识
1.1.虚拟内存
虚拟内存到物理内存之间的映射
从上面的图中可以得出一些结论:
无论是物理内存还是虚拟内存的管理都是以页为单位来进行管理的,并且一般情况下二者的尺寸保持一致。操作系统为每个进程建立一张进程页表mmu,页表记录着虚拟内存页到物理内存页的映射关系以及相关的权限。并且页表是保存在物理内存页中的。因此所谓的虚拟内存分配其本质就是在页表中建立一个从虚拟内存页到物理内存页的映射关系而已。而所谓的remap就是将不同的虚拟页号映射到同一个物理页号而已。就如例子中进程1的第1页和第4页都是映射在同一个6号物理页中。不同进程之间的不同虚拟页号可以映射到相同的物理页号。操作系统还会维持一个全局物理页空闲信息表,用来记录当前未被分配的物理内存。这样一旦有进程需要分配虚拟内存空间时就从这个表中查找空闲的区域进行快速分配。 iOS的内核系统中有一层Mach子系统,Mach子系统是内核中的内核,它是一种微内核。Mach子系统中将进程(task)、线程、内存的管理都称之为一个对象,并且为每个对象都会分配一个被称之为port的端口号,所有对象之间的通信和功能调用都是通过port为标识的mach message来进行通信的。
1.2vm_remap 做到可以将动态分配出来的内存页具备可执行权限,就需要利用 vm_remap。 它的定义是这样的:
On Darwin, vm_remap() provides support for mapping an existing code page at new address, while retaining the existing page protections; using vm_remap(), we can create multiple copies of existing, executable code, placed at arbitrary addresses.
从定义中我们可以知道两点信息:
vm_remap 可以让内存页具备被 map 的页的特性,如果是可执行页被 map,那新创建的页自然而然页具备了这个权限。vm_remap 也不是肆无忌惮的创建任何可执行的页,通俗理解,它只是一个 copy 映射。 上述图片引用自Implementing imp_implementationWithBlock()
因此,我们可以通过在编写代码的过程中,精心构造、预留在程序二进制的代码页,在运行时不断“复制映射”,来完成特殊的使命。
tramplion实现步骤
1.struct定义dataPage和textPage数据结构 构造textPage页函数实现(汇编或者c实现等等),使用数据结构来进行相关操作
2.vm_remap构造页:vm_allocate分配两页虚拟内存 dataPage数据页 textPage代码页 初始都是读写权限 vm_remap使textPage指向构造好的函数(可以是c实现,汇编实现等等) 3.textPage汇编实现:
• 取出原方法的 IMP A,保存起来
opencv依赖包安装
sudo apt install libjasper1 libjasper-dev
报错:
使用《Xavier NX安装opencv3.4.7》里说的解决方法~不行:
新的解决方法:
1.sudo gedit /etc/apt/sources.list
2.在sources.list文件最后面 添加:
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial main multiverse restricted universe
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial-security main multiverse restricted universe
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial-updates main multiverse restricted universe
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial-backports main multiverse restricted universe
deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial main multiverse restricted universe
deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial-security main multiverse restricted universe
deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial-updates main multiverse restricted universe
deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial-backports main multiverse restricted universe
问题: 修改pipelines.yml和pipelines.yml对应的config文件时,logstash自动重新加载配置文件,并自定义检测配置文件是否有修改的间隔时间
解决方法: 使用 --config.reload.automatic和 --config.reload.interval参数
--config.reload.automatic 参数设置为自动检测和重新加载配置更改
--config.reload.interval 参数指定 Logstash 检查配置文件更改的间隔,默认为3秒检测一次,该参数的单位为秒,设置的参数单位s一定要写上
例子: 执行命令:bin/logstash --config.reload.automatic --config.reload.interval 60s
设置为自动重新加载配置文件,并每60秒检测一次配置文件是否有修改
参考文档:https://www.elastic.co/guide/en/logstash/current/reloading-config.html
IDEA给mapper.xml文件绑定数据库,解决SQL数据库字段爆红 一、IDEA设置数据库SQL字段依然爆红二、解决方案 一、IDEA设置数据库SQL字段依然爆红 填写数据库连接信息,并test测试连接,成功保存
配置完数据后我们的SQL语句里的字段依然爆红
二、解决方案 打开我们的IDEA的settings,配置如下信息
把SQL Dialect改成我们所用的数据库,把我们写SQL的mapper.xml文件夹路径配置在Path中,保存。
保存后发现,数据库的字段变成了紫色,不再爆红了。
并且我们输入数据库的表名,字段名都有提示出来了。
当使用pd.read_excel(file) 读取文件时,出现报错:struct.error: unpack requires a buffer of 2 bytes
原因:
读取的excel文件受保护
解决办法:
保存 信任该文件
Controller 用@RequestBody 和String 来接受数据
public ResultJson validateInventury(@RequestBody String jsonObject)
参数
{"inventoryDTOs":[{
"type": 0,
"saleQuantity": 188.0000,
"basePrice":1000.00,
"salePrice": 3000.00,
"inventoryId": 35
}, {
"type": 0,
"saleQuantity": 949.0000,
"basePrice":200.00,
"salePrice": 300.00,
"inventoryId": 28
},{
"type": 1,
"saleQuantity": 94.0000,
"basePrice":200.00,
"salePrice": 1000.00,
"inventoryId": 326 }
]}
转换:
JSONObject jsonObject = JSONObject.parseObject(jsonObject);
String versionInfoStr = jsonObject.getString("inventoryDTOs");
List<InventoryDTO> inventoryDTOs = JSON.parseArray(versionInfoStr, InventoryDTO.class);
特别注意,当时以为外面可以用JSONObject接收,省掉了转换第一步,结果在转换的时候回报错。
————————————————
版权声明:本文为CSDN博主「All In丶」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/superPojo/article/details/108793355
看很多图像处理的博文,都会说图像的主要成分是低频信息,它形成了图像的基本灰度等级,对图像结构的决定作用较小;中频信息决定了图像的基本结构,形成了图像的主要边缘结构;高频信息形成了图像的边缘和细节,是在中频信息上对图像内容的进一步强化。我有点纳闷,图像不就是一堆像素点构成的三维矩阵吗?跟频率有啥关系!
针对这个问题,又查了不少网文,解释的也都各不相同。例如,
“初学的时候,你可以把高中低这个度量和图像上相邻点的灰度差大小对等起来”。
“感性上来讲,图像越杂乱的部分,频率越高,例如边界;图像越柔和的部分,频率越低,比如一大片天空。换句话来讲就是梯度越大的地方,频率越高。”。
“频率指的是灰度值的变化频率。低频信息就是指灰度值变化频率慢的信息,那就是连续而相近的亮度,这些信息无疑是一张图片里的大色块,也就形成了图像的基本灰度等级。中频则灰度变化较快,而图像中物体的边缘处就是这种灰度变化较快的区域,所以是主要的边缘结构。高频的话灰度变化就很快,应当对应图像里的边缘、细节甚至噪点。”
“图像的频率:灰度值变化剧烈程度的指标,是灰度在平面空间上的梯度。
(1)什么是低频?
低频就是颜色缓慢地变化,也就是灰度缓慢地变化,就代表着那是连续渐变的一块区域,这部分就是低频. 对于一幅图像来说,除去高频的就是低频了,也就是边缘以内的内容为低频,而边缘内的内容就是图像的大部分信息,即图像的大致概貌和轮廓,是图像的近似信息。
(2)什么是高频?
反过来, 高频就是频率变化快.图像中什么时候灰度变化快?就是相邻区域之间灰度相差很大,这就是变化得快.图像中,一个影像与背景的边缘部位,通常会有明显的差别,也就是说变化那条边线那里,灰度变化很快,也即是变化频率高的部位.因此,图像边缘的灰度值变化快,就对应着频率高,即高频显示图像边缘。图像的细节处也是属于灰度值急剧变化的区域,正是因为灰度值的急剧变化,才会出现细节。
另外噪声(即噪点)也是这样,在一个像素所在的位置,之所以是噪点,就是因为它与正常的点颜色不一样了,也就是说该像素点灰度值明显不一样了,,也就是灰度有快速地变化了,所以是高频部分,因此有噪声在高频这么一说。
其实归根到底,是因为我们人眼识别物体就是这样的.假如你穿一个红衣服在红色背景布前拍照,你能很好地识别么?不能,因为衣服与背景融为一体了,没有变化,所以看不出来,除非有灯光从某解度照在人物身上,这样边缘处会出现高亮和阴影,这样我们就能看到一些轮廓线,这些线就是颜色(即灰度)很不一样的地方.
”
实际上,只有当进行傅里叶变换后才有低频和高频之分,低频一般是大范围大尺度的信息,也就是背景,而高频反映的是小范围细节信息。应用上对应高频滤波和低频滤波,如果你想得到局部信息,则相应要保留高频部分,滤掉低频部分,反之,若你想得到总体趋势变化,则相应要保留低频部分,滤掉高频部分。
从物理效果看,傅立叶变换是将图像从空间域转换到频率域,傅立叶变换的物理意义是将图像的灰度分布函数变换为图像的频率分布函数,傅立叶逆变换是将图像的频率分布函数变换为灰度分布函数。
对图像而言:
低频分量(低频信号):代表着图像中亮度或者灰度值变化缓慢的区域,也就是图像中大片平坦的区域,描述了图像的主要部分,是对整幅图像强度的综合度量。高频分量(高频信号):对应着图像变化剧烈的部分,也就是图像的边缘(轮廓)或者噪声以及细节部分。 主要是对图像边缘和轮廓的度量,而人眼对高频分量比较敏感。之所以说噪声也对应着高频分量,是因为图像噪声在大部分情况下都是高频的。 图像进行二维傅立叶变换得到频谱图,就是图像梯度的分布图,如下图所示,右图为左图的频谱图。
注意:频谱图上的各点与图像上各点并不存在一一对应的关系,即使在不移频的情况下也是没有。傅立叶频谱图上我们看到的明暗不一的亮点,实际是上图像上某一点与邻域点差异的强弱,即梯度的大小,也即该点的频率的大小(可以这么理解,图像中的低频部分指低梯度的点,高频部分相反)。
傅立叶变换以前,图像(未压缩的位图)是由对在连续空间(现实空间)上的采样得到一系列点的集合,我们习惯用一个二维矩阵表示空间上各点,则图像可由来表示。由于空间是三维的,图像是二维的,因此空间中物体在另一个维度上的关系就由梯度来表示,这样我们可以通过观察图像得知物体在三维空间中的对应关系。为什么要用梯度?因为实际上对图像进行二维傅立叶变换得到频谱图,就是图像梯度的分布图。
我的一点理解
我们在学习图像频率滤波,能看出来,频域上操作的效果比空间域上操作的效果好。但是很难理解为什么要这么做。其实可以这么理解,例如,有一幅含噪声的图像,若对图像进行空间域操作,使用均值滤波,那么噪声未知的像素点会被周围像素点求均值取代,可能效果会比原图好一点,但是这对原有图像的有些信息造成损失(像素取均值)。
那么将图像在频率域上进行操作,噪声在频率上反映是高频信息,通过设置相应的滤波器,滤除噪声。然后从频域还原回来。此时图像的有用的信息不会损失。
参考:
https://zhidao.baidu.com/question/230575188.html
https://blog.csdn.net/charlene_bo/article/details/70877999
https://blog.csdn.net/zaishuiyifangxym/article/details/89452123
IP 路由表
上次有写过一篇《20张图深度详解MAC地址表、ARP表、路由表 》的文章,里面有提到路由表,那么什么是IP路由、什么又是IP路由表呢?
路由:路由是网络中的基本概念,网络的基本功能就是使得处于网络中两个IP地址能够互相通信。
当路由器收到一个IP数据包时,路由器会解析出IP数据包中的目的IP地址,然后根据目的IP地址查找路由表,依据最长掩码匹配原则,找到对应的路由条目,根据路由条目中的下一跳或者出接口将报文转发出去,这就是路由。
路由表:简单点说路由表就是路由器用于指导数据包如何转发的表项,记录了去往目的IP的下一跳去哪里(如下图)。
路由表的作用类似于我们生活中的地图或者指示牌,指引我们去往一个目的地该如何走?
IP路由表包含了哪些要素
IP路由表中包含了目的网络/掩码,协议类型,优先级,开销,标志,下一跳,出接口这个七大要素。
下面我们来看下一个真实的路由表:
从这个路由器我们可以通过命令 display ip routing-table来查询该设备的路由表,我们可以看到这条设备一共有12条路由条目。
每个路由条目必须包括下面几个信息元素:
目的网络/掩码
目的网络/掩码:也被称为路由前缀,这是路由条目所关联的目的网络地址及网络掩码。
一条完整的路由前缀由:网络地址+前缀长度(或者网络掩码)构成,两者缺一不可,例如192.168.1.0/24与192.168.1.0/25,虽然网络地址相同,都是192.168.1.0,但是两者绝对是两条不同的路由,因为他们的前缀长度不相同。
当路由器收到一个IP数据包时,路由器会解析出IP数据包中的目的IP地址,然后根据目的IP地址查找路由表,依据最长掩码匹配原则,找到对应的路由条目。
最长掩码匹配原则匹配的就是目的网络/掩码。
比如:路由器收到一个目的IP地址为10.1.1.1的数据包,此时查找路由表,有两个路由条目,一个路由条目的A的目的网络/掩码是10.1.1.0/24,另一条路由条目B的目的网络/掩码是10.1.1.0/28,那么这个数据包匹配的是哪一个路由条目呢?
正确答案:是匹配路由条目B,因为B的掩码长。
协议类型
协议类型:指该路由条目是通过什么路由协议学些过来的。例如是直连的,或是静态的,或者是通过OSPF、IS-IS、EIGRP、BGP等动态路由学习到的。
1、直连路由:指和路由器的接口直接的地址生成的路由。
如下图中,协议类型是direct的就是直接直连地址生成的路由。
2、静态路由:静态路由是指通过静态路由协议生成的路由。
3、动态路由:动态路由协议主要有RIP、OSPF、ISIS、BGP。RIP和BGP是基于距离矢量的路由协议,OSPF和ISIS都是基于链路状态的路由协议。
优先级
路由表中去往同一目的地的路由可能通过多种路由协议生成。
举个例子:去往目的IP为192.168.2.1的通过静态路由生成了,也通过OSPF路由生成了。那么这个时候什么样的路由才会加入到路由表中呢?这个时候就和路由协议的优先级有关系了。
每种协议类型对应不同的优先级,优先级值越小则路由越优。
常用路由协议和优先级的关系表如下图。
那么当一台路由器同时从多种不同的路由协议学习到去往同一个目的地的路由时,它将优选路由协议优先级值最小的那条路由。
因此,本次例子中,正确的应该是通过OSPF学习到路由加入到路由表中(OSPF的路由优先级比静态路由优先级小)
开销
开销:路由的度量值,经常也使用metric来描述。
直连及静态路由的Cost为0。
通过动态路由协议学习到的Cost则根据实际情况而定。不同的路由协议计算Cost的方法不同。
例如上图中,R1去往PC2的路由条目通过OSPF路由协议学习到,开销为3。
标记
标志:路由标记,R表示该路由是迭代路由。D表示该路由下发到FIB(Forwarding Information Base)表。
迭代路由:路由必须有直连的下一跳才能够指导转发,但是路由生成时下一跳可能不是直连的,因此需要计算出一个直连的下一跳和对应的出接口,这个过程就叫做路由迭代。BGP路由、静态路由和UNR路由的下一跳都有可能不是直连的,都需要进行路由迭代。
例如,BGP路由的下一跳一般是非直连的对端loopback地址,不能指导转发,需要进行迭代。即根据以BGP学习到的下一跳为目的地址在IP路由表中查找,当找到一条具有直连的下一跳、出接口信息的路由后(一般为一条IGP路由),将其下一跳、出接口信息填入这条BGP路由的IP路由表中并生成对应的FIB表项。
下一跳
下一跳:去往目标网络的下一跳IP地址。
出接口
出接口:去往目标网络从本设备的哪个接口出去。
---END---
IO简介 IO就是Input和Output的简称,也就是输入输出。主要包括磁盘IO、网络IO、键盘输入,显示器输出、USB等操作。
输入是从IO设备输入到内存中,输出是从内存中输出到IO设备中。
IO控制器 CPU不会直接控制IO设备,而是通过IO控制器间接的控制IO设备。因为市面上有各种各样的IO设备,操作方式都不太一样,CPU无法直接控制IO设备。所以引入了IO控制器,也叫做设备控制器来间接控制IO设备。
IO控制器作为CPU和IO设备的中介,通过地址总线、控制总线与CPU相连。有以下作用:
1、数据缓冲
CPU和内存等速度都非常快,IO设备的速度比较慢,所以IO控制器设立缓冲区。
当输出的时候,CPU将数据放到IO控制器中的数据寄存器中,然后就可以去忙其他工作了,IO设备可以慢慢的从IO控制器中的数据寄存器中拿数据然后输出。
当输入的时候,IO设备先将输入的信息放到IO控制器中的数据寄存器中,等到攒到一定数量或者输入完成后,CPU一次性将数据拿走,提高了CPU的运行效率。
2、IO设别状态识别
IO控制器会识别IO设备的工作状态,将工作状态保存到状态寄存器中,供CPU查用。
3、控制IO设备
控制IO设备的读取和写入,定时等控制信号。
IO分类 IO主要分为以下4类:程序查询方式、中断方式、DMA、通道,这四类效率依次是变高的。
我们接下来挨个仔细分析一下。
1、程序查询方式
读取数据时,CPU从设备控制器的状态寄存器中查询设备是否可用,如果不可用就一直轮询查询,直到可用为止。如果可用就发送读取信号,然后轮询查询数据是否准备号,如果准备好就从数据寄存器中读取数据到CPU中,然后将数据从CPU转移到内存中。
写数据时,CPU也是轮询查看设备是否可用,如果可用就将数据从CPU写入到数据寄存器中。
缺点: 程序查询方式,CPU需要不断的查询,白白浪费了CPU资源,CPU利用率低。
2、中断驱动
中断驱动是对程序查询的改进,中断的意思就是CPU是可以被打断的,硬件可以向CPU发送中断命令,然后CPU会执行对应的中断程序。
当CPU请求IO时,就直接发送IO读取的相关命令。如果当前设备正被占用,就排队,然后IO设备器会对依次对队列中的进行处理,处理完成后就发出中断命令,打断CPU原本的操作,转而去执行中断程序,比如将数据从数据寄存器转到CPU,然后从CPU转到内存中。
优点: 在IO的时候,CPU可以处理其他线程的工作,CPU的利用效率提高了
缺点: 在IO完成后,还是需要CPU将数据转移到内存中,还是会占用一定的CPU。
3、DMA
DMA全称为Direct Memory Access,也叫做直接存储器访问。DMA可以直接与内存相连,也就是说IO设备可以直接与内存交换数据,不要CPU的中转了。
相较于中断驱动,DMA有了以下改进:
1、以块为单位进行传送
2、内存和IO设备可以直接传递,不需要CPU的中转。
3、CPU只需要在开始的时候发出CPU指令,在结束的时候DMA会发出中断,CPU执行相关的中断程序就行了。
优点: CPU只需要在开始的时候,指定从内存和IO设备中的哪些位置进行读写,进一步增加了CPU的利用率。
缺点: DMA可以一次性读取多个块,但是在内存和IO设备中必须是连续的。
如果牵扯到读写离散的块,CPU必须发出多个IO指令。
4、通道
通道是一种硬件,自己就可以执行IO命令,相当于一个削弱版的小CPU,执行的指令单一。
通道可以执行IO指令,CPU只需要将相关的IO指令发送给通道控制器就可以了,通道会执行IO指令,完成对应的传输。
相较于DMA,DMA实现固定的数据传送,而通道拥有着自己的指令和程序,具有更强的IO处理能力。
前言
Android R上分区存储的限制得到进一步加强,无论APP的targetsdkversion是多少,都将无法访问Android/data和Android/obb这二个应用私有目录。这无疑对会部分APP的业务场景及用户体验造成冲击,典型的如下
文件管理类软件:微信、QQ传输的文件无法展示给用户以便捷使用垃圾清理类软件:清理缓存功能受阻 “你有你的张良计,我有我的过墙梯”,现市面上文件管理类软件(如MT管理器)已解决上述系统限制,本文将浅析其实现方案,并主要分析以下2个问题:
SAF是通过何种方式访问文件系统的,MediaStore API ? File API ? Native Code ?SAF为何能访问Android/data目录 实现方案
其实现方案很简单,就是通过Intent ACTION_OPEN_DOCUMENT_TREE,启动SAF让用户授权访问Android/data目录,属于官方公开的方法。
前提是APP的targetsdkversion要小于30。
文档链接:
文档访问限制
授予对目录内容的访问权限
基本使用
通过Intent启动SAF授权界面,注意URI的百分号编解码(%3A和%2F),别随意替换,否则SAF无法导航到Android/data目录 @TargetApi(26) private void requestAccessAndroidData(Activity activity){ try { Uri uri = Uri.parse("content://com.android.externalstorage.documents/document/primary%3AAndroid%2Fdata"); Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, uri); //flag看实际业务需要可再补充 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); activity.startActivityForResult(intent, 6666); } catch (Exception e) { e.printStackTrace(); } } 授权申请
在用户同意授权后,持久化uri权限(否则关机重启或授权界面finish后,APP就无权限访问了),并只能通过DocumentFile进行业务操作,File API操作是无效的,此授权只是授权uri操作,并未授权文件系统,后续章节有说明。 implementation "androidx.documentfile:documentfile:1.0.1" @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.
The C Programming Language https://zedware.github.io/homepage https://zedware.github.io/homepage/code/tcpl/index.html
API文档自动生成工具调研 项目背景 项目的版本为python2.7,django1.11,采用的前后端交互方式是人工手写接口文档,交给前端,人工写postman接口测试,没有一套高效的自动接口文档生成和自动化测试的流程。
调研过程 //依赖库 djangorestframework==3.9.4 coreapi==2.3.3 django-rest-swagger==2.2.0 drf-yasg 实现方式:Django Rest Swagger生成api文档
实现思路:
修改settings配置,添加依赖库,添加swagger配置修改urls.py 添加swagger文档路由,添加视图路由添加视图类实现序列化类实现模型类 djangorestframework 这种模式在postman就能实现,没必要再自己再重新造一套。
这种接口文档的形式,可以满足需求,但还是没有swagger优雅简洁。
在实现这种模式的时候也遇到不少坑,最终没有呈现出这个效果。
django-rest-swagger:deprecated (2019-06-04) Django 1.8+Django REST framework 3.5.1+Python 2.7, 3.5, 3.6 核心库还是在coreapi,djangorestframework。
依赖于上述核心库的编码方式,换成了swagger的ui
非常容易实现。
drf-yasg Django Rest Framework: 3.10, 3.11, 3.12Django: 2.2, 3.0, 3.1Python: 3.6, 3.7, 3.8, 3.9 特征:
完全支持嵌套序列化器和模式响应模式和描述模型定义与代码生成工具兼容在规范生成过程中的所有点上的定制挂钩JSON和YAML格式的规范捆绑了最新版本的swagger-ui和redoc,用于查看生成的文档架构视图是可立即缓存的生成的Swagger模式可以通过Swagger -spec-validator自动验证通过URLPathVersioning和NamespaceVersioning支持Django REST框架API版本化;目前不支持其他DRF或自定义版本方案 swagger模式
redoc模式
总结:提供了更加强大,更多的功能,以及在可视化ui上做得更好,本质上还是使用了djangorestframework的编码风格
问题:由于python2.7的Django Rest Framework最高只支持3.9版本,所以不兼容3.10+的要求。故可预测该方案不可行。
优缺点: 优点:
更规范科学的开发模式,自动生成api文档,节约大量在文档上编辑以及测试耗费的时间。更直观的管理接口 缺点:
跟原项目的编码风格有较大出入,比如序列化过程,原项目并没有。而是直接从urls到service调用数据库。难以在项目开发规范中推广。需要踩大量坑,由于原项目的python跟django版本较低,在刚尝试使用这种模式就遇到大量坑,很难预测之后会遇到什么坑。增加今后潜在的开发成本需要配置session,目前还没有将项目内的接口跑通,测试成本还在增加。代码侵入性较强,没办法简便的对已开发的接口进行改造,只能考虑后续加入的接口。 总结: 对于当前项目的python和django版本,当前查到的资料都脱离不开Django Rest Framework的编码风格,对于之前已经开发的接口难以引入,同事想要的是一款尽量只改变路由就能实现文档生成,而不是进行大量代码的侵入,所以并不能采用上述的库,当前探索先告一段落。
踩坑记录: django1的urls.py中不支持path,改为url格式(django2才支持path)django修改settings或修改requirement没必要将整个docker-compose重启,项目自动重启失败可以手动重启,也可以直接进docker上直接执行pipyou cannot access body after reding from request’s data stream 一旦对序列化类修改post传入的字段时,就会出现这个问题,查阅资料是说django不能让中间件修改request的post,所以用request.
一、PON的经本概念和组成结构
(一)概念
PON是Passive Optical Network(无源光网络) 的简写,是一种基于P2MP拓扑结构的网络,是一种应用于接入网,局端设备和多个用户端设备之间通过无源的光缆、光分/合路器等组成的光分配网连接网络,是关于最后一英里的光纤传输技术。由三个部分组成,光线路终端(安装于中心控制站)、光网络单元(配套安装于用户场所)和无源分光器。
PON是一种非对称的、点到多点(P2MP)结构;OLT(光线路终端)和ONU(光网络终端)所扮演的角色不同:→OLT相当于MASTER的角色,→ONU相当于Slave的角色;光源是光分配网络没有任何有源电子设备。
(二)优点
A. 节约
节省大量光纤和收发器,较传统光纤接入方案成本低。
B. 可靠
信号在PON传输过程中不经过有源电子器件,可以减少潜在的故障点;使用无源设备可以简化网络层次结构,扁平化的网络结构更易于维护和管理。
C. 长距离
PIN的传输距离在10~20km,完全克服了以太网和xDSL接入方式在距离和带宽上的局限性,可以大大增加用户接入方案的灵活性。
D. 高带宽
PON和xDSL相比带宽更高,可以在相当长的时间内满足用户对带宽的需求
E. 灵活
PON组网模型不受限制,可以灵活组建树型、星型拓扑结构的网络;特别适合用户接入信息点分散的场合,实现一根主光纤满足所有用户接入信息点的接入。
F. 多用途
PON是天然的广播网络,采用单纤波分复用技术,可以在OLT端将有线电视信号叠加进入PON网络进行传输,最后在用户端通过分离器分离出来,既可以传输数据也可以传输有线电视信号。
二、EPON简介
(一)定义
EPON是PON技术中较新的一种,由IEEE802.3EFM(Ethernet for the First Mile)提出,是一种结合了Ethernet 和PON的宽带接入技术,采用点到多点网络结构、无源光纤传输方式、基于高速以太网平台和TDM时分MAC媒体访问控制方式从电气特性。机械特性、规程特性、功能特性等四大功能基本上采纳了Ethernet的标准,在数据链路层上,采用的是基于TDM思想的全新控制协议。简单来说,就是以太网和PON结合,产生了以太网无源光网络,EPON使用单光纤连接到OLT,然后OLT连接到ONU。
(二)EPON各主要结构的作用
EPON只是PON的具体应用,所以我们来具体说一下点到多点的过程中OLT和ONU的具体作用。
1.OLT
OLT是核心功能模块,在物理结构上一般以机架的形式呈现。机架式大型OLT采用插板式结构,由于功能复杂、容量大,比较难实现;盒式小型OLT由于功能简单、容量小,比较容易实现。
用于连接光线干线的终端设备,产生时间戳信息,用户系统参考时间;通过MPCP帧指配带宽;进行测距操作和控制ONU的注册。
2.ONU
ONU位于终端和ODN之间,提供用户接口和多业务接入,ONU上联口为光口,用户侧接口较多,像是以太网接口、E1接口、CATV接口等。
是光网络单元,通过下行控制帧的时间戳同步OLT;等待发现处理(测距、带宽等)和发现帧;等待授权,毕竟它只有在授权后(OLT)才能发送数据。
3.ODN
ODN位于ONU和OLT之间,为ONU和OLT提供光传输手段,完成光信号的传输和功率分配任务。ODN通常呈现树型分支结构,主要包括以下设备:局端配线设备、光分配点设备、光用户接入点设备、用户端设备等。
(三)EPON下行和上行传输方式
1.下行传输是广播方式
所有的ONU都能接受到相同的数据,但是通过LLID来区分不同的ONU数据,ONU只接收属于自己的数据,丢弃其他用户的数据。
2. 上行传输方式是TDMA方式
OLT统筹管理ONU发送上行信号的时刻,发出时隙分配帧。ONT根据时隙分配帧,在OLT分配给它的时隙中发送自己的上行信号。各ONU的发送时间和长度由OLT集中控制。
(四)EPON的信道复用技术
信道复用即频分复用(FDM,Frequency Division Multiplexing),就是将用于传输信道的总带宽划分成若干个子频带(或称子信道),每一个子信道传输1路信号。要求总频率宽度大于各个子信道频率之和,同时为了保证各子信道中所传输的信号互不干扰,应在各子信道之间设立隔离带,这样就保证了各路信号互不干扰(条件之一)。
EPON系统采用WDM技术,实现单纤双向传输,链路层的速率上行和下行都是1.25Gbs。
我们这次的分享就先这样,大致解释一下EPON,我们后续还会跟进几篇相关的文章,这篇文章整理的比较简陋,还希望小伙伴们见谅,毕竟内容太多,挑选的时候选的都是最基本的内容,想由浅入深地介绍。
我们高耐特13年专注工业通信设备工业交换机、光纤收发器、光端机等的研发、生产和销售,是世界500强企业的合作伙伴,还参与近百例的平安城市、智慧交通等大型建设项目。欢迎前来了解更多。
产生需求的原因: 最近都在使用python做一些小demo,尤其是经常会用python做一些关于数据处理的操作,于是就产生问题:怎么才能让处理数据时有更好的且直接能上手的案例呢?换言之就是python有什么“奇淫技巧”可以帮助我快速处理数据呢?于是搜集一些案例,结合自己实际工作需要,将这些案例做成连续更新篇,今天来讲一个,即去除列表中的空值元素
需求目标: 搜集针对python高效处理数据的核心代码,今天是实现去除列表中的空值元素
具体实操: 去除列表中的空值元素 关键词:for、while、remove、if
tmp_list = ['支出种类介绍', '三餐', '基金投资', '通信', '借款', '交通出行', '摄影文印', '人情', '', '', '', '', '', '', '', '', ''] # 方法一: while "" in tmp_list:# 判断是否有空值在列表中 tmp_list.remove("")# 如果有就直接通过remove删除 print(tmp_list) # 方法二: new_list = [i for i in tmp_list if i != ""]# 先循环遍历有空值的列表,并抽取非空的列表元素,存储到新的列表中 print(new_list) 结语:
这个是一个连续篇,如果有新的python教程实用案例,会持续更新
学习笔记
这里是官方关于在vue中如何使用的文档,下面的只是基于官方文档的基础用法,详细内容建议查看官方文档! 官方文档
以此为例
先看效果:
大致步骤如下:
一、安装video.js
npm install --save video.js 二、参考官方文档操作
这里定义一个名为VideoJs的组件,便于使用。
注意要引入下面的video-js.css和video.js!!,一个是样式,一个是播放器。
<template> <div> <video ref="videoPlayer" class="video-js"></video> </div> </template> <script> import 'video.js/dist/video-js.css'; import videojs from 'video.js'; export default { name: "VideoJs", props: { options: { type: Object, default() { return {}; } } }, data() { return { player: null } }, mounted() { this.player = videojs(this.$refs.videoPlayer, this.options, function onPlayerReady() { console.log('onPlayerReady', this); }) }, beforeDestroy() { if (this.
安装epel依赖源
$ sudo yum install epel-release;
卸载 ibus软件
$ rpm -e --nodeps ibus
安装fcitx相关
$ yum -y install fcitx fcitx-pinyin fcitx-configtool
在 /etc/profile.d 中增加一个配置脚本命名为 fcitx.sh
vi /etc/profile.d/fcitx.sh
内容如下:
export GTK_IM_MODULE=fcitx
export QT_IM_MODULE=fcitx
export XMODIFIERS=@im=fcitx
安装相应的依赖
yum -y install qtwebkit
yum -y install dpkg
yum -y install alien
安装搜狗输入法
https://pinyin.sogou.com/linux/
转换搜狗输入法的 deb包
alien -r --scripts sogoupinyin_2.2.0.0108_amd64.deb
rpm包安装
rpm -ivh --force sogoupinyin-2.2.0.0108-2.x86_64.rpm
搜狗拼音的库,创建软链接:
ln -s /usr/lib/x86_64-linux-gnu/fcitx/fcitx-sogoupinyin.so / usr/lib64/fcitx/fcitx-sogoupinyin.so
ln -s /usr/lib/x86_64-linux-gnu/fcitx/fcitx-punc-ng.so /usr/lib64/fcitx/fcitx-punc-ng.
Latex日常问题汇总:
LaTeX中使用hyperref包报错
LaTex ieee模版中表格caption大写出不来
需要对文本进行两端对齐时:
加入包\usepackage{ragged2e}在需要对齐的文字前面添加\justifying 参考:
Latex两端对齐
金融介绍 金融 金融就是对现有资源进行重新整合之后,实现价值和利润的等效流通。
金融工具 金融工具是在金融市场中可交易的金融资产。
主要分为股票、期货、黄金、外汇、基金、债券等。
期货 期货是以某种大众产品如棉花、大豆、石油等及金融资产如股票、债券等为标的标准化可交易合约。因此,这个标的物可以是某种商品(例如黄金、原油、农产品),也可以是金融工具。
黄金 外汇 外汇,是货币行政当局(中央银行、货币管理机构、外汇平准基金及财政部)以银行存款、财政部库券、长短期政府证券等形式保有的在国际收支逆差时可以使用的债权。
投资基金 投资基金是通过公开发售基金份额募集资本,然后投资于证券的机构。投资基金由基金管理人管理,基金托管人托管,以资产组合方式进行证券投资活动,为基金份额持有人的利益服务。
股票 股票是股份公司发给出资人的一种凭证,股票持有者就是股份公司的股东。
股票的作用
出资证明、证明股东身份、对公司经营发表意见公司分红、交易获利
上市/IPO:企业通过证券交易所公开向社会增发股票以筹募资金。 股票分类 收益分类 蓝筹股成长股ST股 上市地区分类 A股:中国大陆上市,人民币认购买卖B股:中国大陆上市,外币认购买卖H股:中国香港上市N股:美国纽约上市S股:新加坡上市 股票市场构成 上市公司证监会、证券业协会、交易所证券中介机构交易所 上海证券交易所:只有一个主板(沪指)深圳证券交易所: 主板:大型成熟企业(深成指)中小板:经营规模小(中小版指)创业板:尚处于成长期的创业企业(创业板指) 影响股票的因素 A股买卖 股票交易日:周一至周五(非法定节假日和交易所休市日)交易时间 9.15-9.25开盘集合竞价时间9.30-11.39前市,连续竞价时间13:00-15:00后市,连续竞价时间14:57-15:00深交所收盘集合竞价时间 金融分析 基本面分析 行业分析公司分析:财务数据、业绩报告等宏观经济面分析:国家的财政政策、货币政策等 技术面分析 K线MA(均线,moving avg)MACD(指数平滑移动平均线)KDJ(随机指标)RSIBOLL
http://data.10jqka.com.cn/market/lhbgg/code/000428/ 金融量化投资 为什么需要量化交易? 传统交易
量化交易
也就是说量化交易可以使交易效率提高百倍 量化交易 金融量化分析主要是指以先进的数学模型替代人为的主观判断,利用计算机技术从庞大的历史数据当中选出能够带来超额收益的多种“大概率”事件以此来指定策略。主要就是以下几步:
灵光乍现细化策略策略转程序检验策略结果 回测模拟交易 实盘交易 量化交易的价值 避免主观情绪、人性弱点和认知偏差,选择更加客观能同时包括多角度的观察和多层次的模型及时跟踪市场变化,不断发现新的统计模型,寻找交易机会在决定投资策略后,能通过回测验证其效果。
备份元数据 设置环境变量,重启 lotus-miner
export LOTUS_BACKUP_BASE_PATH=/root/lotus-backups 备份元数据
# lotus-miner backup /root/lotus-backups/backup.cbor Success 拷贝配置文件 config.toml config.toml
cd $LOTUS_MINER_PATH cp config.toml storage.json /root/lotus-backups/ # ls lotus-miner_backup/ backup.cbor config.toml storage.json 恢复 在另外一个节点恢复
拷贝到$LOTUS_MINER_PATH目录下
cp config.toml storage.json /nfstore/miner/ 拷贝扇区数据store
修改扇区路径
vim storage.json 修改IP地址
vim config.toml 开始恢复
lotus-miner init restore backup.cbor 运行旷工 nohup lotus-miner run >> /var/log/miner/miner.log 2>&1 &
文章目录 前言知识总览无结构文件有结构文件有结构文件的逻辑结构1、顺序文件2、索引文件3、索引顺序文件检索效率分析多级索引顺序文件 知识点回顾与重要考点 前言 此篇文章是我在B站学习时所做的笔记,部分为亲自动手演示过的,方便复习用。此篇文章仅供学习参考。
提示:以下是本篇文章正文内容,下面案例可供参考
知识总览 无结构文件 无结构文件:文件内部的数据就是一系列二进制流或字符流组成。又称“流式文件”。如:Windows操作系统中的.txt文件。
文件内部的数据其实就是一系列字符流,没有明显的结构特性,因此也不用探讨无结构文件的“逻辑结构”问题。 有结构文件 有结构文件:由一组相似的记录组成,又称“记录式文件”。每条记录又若干个数据项组成。如:数据库表文件。一般来说,每条记录有一个数据项可作为关键字(作为识别不同记录的ID)
根据各条记录的长度(占用的存储空间)是否相等,又可分为定长记录和可变长记录两种。
有结构文件的逻辑结构 1、顺序文件 顺序文件:文件中的记录一个接一个地顺序排列(逻辑上),记录可以是定长的或可变长的。各个记录在物理上可以顺序存储或链式存储。
注:一般来说,考试题目中所说的“顺序文件”指的是物理上顺序存储的顺序文件。
结论:定长记录的顺序文件,若物理上采用顺序存储,则可实现随机存取;若能再保证记录的顺序结构,则可实现快速检索
2、索引文件 对于可变长记录文件,要找到第i个记录,必须先顺序第查找前i-1个记录,但是很多应用场景中又必须使用可变长记录。如何解决这个问题?
索引表的各个表项是需要连续存放的,每个索引表的表项都是大小相等的,比如说,索引号、长度、指针这3个字段分别占4个字节,则表项需要占12个字节的长度,因此索引表本身也可以理解成是定长记录的顺序文件。定长记录的顺序文件是可以支持随机访问的,所以可以快速找到第i个记录对应的索引表项存放在什么地方。
3、索引顺序文件 索引顺序文件的索引表其实是一个定长记录的串结构的顺序文件
思考索引文件的缺点:每个记录对应一个索引表项,因此索引表可能会很大。比如:文件的每个记录平均只占8B,而每个索引表项占32个字节,那么索引表都要比文件内容本身大4倍,这样对存储空间的利用率就太低了。
检索效率分析 若一个顺序文件有10000个记录,则根据关键字检索文件,只能从头开始顺序查找(这里指的并不是定长记录、顺序结构的顺序文件),平均须查找5000个记录。若采用索引顺序文件结构,可把10000个记录分为√10000= 100组,每组100个记录。则需要先顺序查找索引表找到分组(共100个分组,因此索引表长度为100,平均需要查50次),找到分组后,再在分组中顺序查找记录(每个分组100个记录,因此平均需要查50次)。可见,采用索引顺序文件结构后,平均查找次数减少为50+50= 100次。同理,若文件共有106个记录,则可分为1000个分组,每个分组1000个记录。根据关键字检索一个记录平均需要查找500+500 = 1000次。这个查找次数依然很多,如何解决呢? 多级索引顺序文件 为了进一步提高检索效率,可以为顺序文件建立多级索引表。例如,对于一个含106个记录的文件,可先为该文件建立一张低级索引表,每100个记录为一组,故低级索引表中共有10000个表项(即10000个定长记录),再把这10000个定长记录分组,每组100个,为其建立顶级索引表,故顶级索引表中共有100个表项。
知识点回顾与重要考点
项目需要处理txt文件里边的数据,数据来自verilog 算法仿真。
这里举例数据如下:
11.txt
fscanf() 和 fprintf() 函数与前面使用的 scanf() 和 printf() 功能相似,都是格式化读写函数,两者的区别在于 fscanf() 和 fprintf() 的读写对象不是键盘和显示器,而是磁盘文件。
int fscanf ( FILE *fp, char * format, … );
int fprintf ( FILE *fp, char * format, … );
fp 为文件指针,format 为格式控制字符串,… 表示参数列表。
FILE *fp; int i, j; char *str, ch; fscanf(fp, "%d %s", &i, str); fprintf(fp,"%d %c", j, ch); 示例代码:
#include <stdio.h> #include <math.h> #include <stdlib.h> #include <string.h> #define MAX_LINE 1024 int main() { int buf[MAX_LINE]; int i; FILE *fp=fopen("
整体思路:
Android10系统根目录只有读的权限,没有写的权限,故不能像低版本Android系统那样直接将证书拷贝到系统证书所在目录;Android10需要通过Magisk中的Move Certificates模块将用户证书转化为系统证书。
实现步骤:
1、需要在TWRP官网刷入适配自己手机机型的TWRP(官网地址:https://twrp.me/),再使用TWRP刷入面具(面具下载地址:https://github.com/topjohnwu/Magisk/releases)
(上图选择自己手机型号的版本,查看自己的手机型号可以将手机连接到电脑上打开cmd终端使用adb devices -l 命令)
2、将需要安装的证书安装到用户证书下
3、在面具中找到Move Certificates模块下载,直接在面具下载需要手机配置全局科学上网,可以在github上下载(https://github.com/Magisk-Modules-Repo/movecert/releases)
4、模块下载完成后证书即从用户目录移动到系统目录,重启手机证书生效
PC版
<!DOCTYPE html> <html> <head> <!-- 国际统一字符编码集一定要写在最前面 --> <meta charset="UTF-8"> <!-- 解决浏览器兼容,以webkit内核解析,ie 以最高内核解析或以谷歌内核 --> <meta name="renderer" content="webkit"/> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/> <title>Text</title> <link rel="manifest" href="%PUBLIC_URL%/manifest.json"> <meta name="keywords" content="text"/> <meta name="description" content="text"/> <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico"/> </head> <body> <div id="root"></div> </body> </html> H5版
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="renderer" content="webkit"/> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/> <title>Text</title> <link rel="manifest" href="%PUBLIC_URL%/manifest.json"> <meta name="keywords" content="text"/> <meta name="description" content="text"/> <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico"/> <!-- m 站 start --> <meta name="
一、前言
众所周知,postgres是一种比较先进的对象关系型数据库DBMS,支持SQL标准的扩展,包括事务、外键、子查询等一系列关系型数据库的特性。
二、常见的Postgres操作
1、基本操作
登录
#注意:登录时,默认使用postgres用户和postgres数据库 #首先确保系统中存在postgres用户和对应的用户组 su - postgres psql 执行后,即可进入postgres界面
2、数据库操作
#1、列举数据库,相当于MySQL中的 show databases \l #2、切换数据库,相当于MySQL中的use db \c <dbname> #3、列出数据库中对应的表信息,相当于show tables; \dt #4、查看表结构,相当于 desc; \d tdlname #5、创建数据库 create database <database> #6、删除数据库 drop database <dbname> #7、创建表 create table ([字段名1] [类型1] ;,[字段名2] [类型2],......<,primary key (字段名m,字段名n,...)>;); #8、在表中插入数据 insert into 表名 ([字段名m],[字段名n],......) values ([列m的值],[列n的值],......); #9、备份数据库 pg_dump -U postgres -f /tmp/postgres.sql postgres #(导出postgres数据库保存为postgres.sql) pg_dump -U postgres -f /tmp/postgres.sql -t test01 postgres #(导出postgres数据库中表test01的数据) pg_dump -U postgres -F t -f /tmp/postgres.
二叉树及其表示 树 有根树 从图论的角度看,树等价于连通无环图。因此与一般的图相同,树也由一组项点〈vertex)以及联接与其间的若干条边〈edge) 组成。在计算机科学中,往往还会在此基础上,再指定某一特定顶点,并称之为根 root) 。在指定根节点之后,我们也称之为有根树《rooted tree) 。此时,从程序实现的角度,我们也更多地将项点称作节点 node) 。
深度与层次 由树的连通性,每一节点与根之间都有一条路径相联,而很据树的无环性,由根通往每个节点的路径必然叭一。因此如图5.1所示,沿每个节点v到根r的唯一通路所经过这的数目,称作v的深度 (depth) ,记作depth(v)。依据深度排序,可对所有节点做分层归类。特别地,约定根节点的深度depth(F) = 0,故属于第0层。
任一节点v在通往树根沿途所经过的每个节点都是其祖先 (ancestor) ,v是它们的后代 (descendant) 。特别地,v的祖先/后代包括其本身,而v本身以外的祖先/后代称作真祖先 (proper ancestor) /真后代 (properdescendant) 。节点v历代祖先的层次,自下而上以1为单位逐层递减; 在每一层次上,v的祖先至多一个。特别地,若节点u是v的祖先且怡好比v高出一层,则称u是v的父亲(parent) ,v是u的孩子(child) 。v的孩子总数,称作其度数或度〔degree) ,记作deg(v)。无孩子的节点称作叶节点《leaf) ,包括根在内的其余节点普为内部节点 〈internal node) 。
v所有的后代及其之间的联边称作子树 (subtree) ,记作subtree(v)。在不致歧义时,我们往往不再严格区分节点〈v) 及以之为根的子树〈subtree(v)) 。
高度 树T中所有节点深度的最大值称作该笃的高度 (height) ,记作height(T)。不难理解,树的高度总是由其中某一叶节点的深度确定的。特别地,本书约定,仅含单个节点的树高度为9,空树高度为-1。推而广之,任一节点v所对应子树subtree(v)的高度,亦称作该节点的高度,记作height(v)。特别地,全树的高度亦即其根节点r的高度,height(T) = height®。
如图5.2所示,二叉树 〈binary tree)中每个节点的度数均不超过2。因此在二叉树中,同一父节点的孩子都可以左、右相互区分一一此时,亦称作有序二又树 (ordered binary tree) 。本书所提到的二叉树,默认地都是有序的。特别地,不合一度节点的二叉树称作真二叉树 〈propenr binary tree) 习题[5-2]) 。
有序树
树中任意节点的 子结点之间有顺序关系,这种树称为有序树
无序树
树中任意节点的 子结点之间没有顺序关系,这种树称为无序树,也称为自由树
多叉树 一般地,树中各节点的孩子数目并不确定。每个节点的孩子均不超过k个的有根树,称作K叉树《〈k-ary tree) 。本节将就此类树结构的表示与实现方法答一简要介绍。
父节点 由如图5.
现象:
单一项目,通过代码层,对缓存进行存储和读取,正常。但通过可视化界面客户端或是终端链接界面 get key 查询不到。
原因及解决方案:
存储时,没有对存储的信息做序列化,一般在网上参考的时候都会复制,部分人会遗忘。加入如下代码即可
/** * redis配置类 * */ @SuppressWarnings("unchecked") @Configuration @Slf4j public class RedisConfiguration { /** * springboot2.x 使用LettuceConnectionFactory 代替 RedisConnectionFactory * application.yml配置基本信息后,springboot2.x RedisAutoConfiguration能够自动装配 * LettuceConnectionFactory 和 RedisConnectionFactory 及其 RedisTemplate * * @param redisConnectionFactory * @return */ @Bean public RedisTemplate redisTemplate(LettuceConnectionFactory redisConnectionFactory) { RedisTemplate redisTemplate = new RedisTemplate(); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new StringRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setHashValueSerializer(new StringRedisSerializer()); redisTemplate.setConnectionFactory(redisConnectionFactory); log.info("redisTemplate:" + redisTemplate.toString()); return redisTemplate; } } 该现象在单一项目下,不使用可视化客户端或是终端界面操作时,无任何感知。
程序压测时访问量达到一定数量时程序崩溃
报错如下:
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@284019b7]
Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@284019b7]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@284019b7]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@284019b7]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@360e82bd] was not registered for synchronization because synchronization is not active
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@328cc4d4] was not registered for synchronization because synchronization is not active
从问题来看是在mybatis层出现的问题翻了下代码发现是@Transactional注解加错了位置 ,实现类上加了事务注解导致的问题
目录
环境
1、实现效果图
2、实现方式
2.1 框选实现
2.1.1 初始化
2.1.2 框选实现
2.2 提取椭圆 ROI 区域
2.2.1 初始化界面
2.2.2 ROI 提取
环境 QT 5.14.2
VTK 8.2.0 (vs2019编译,64位 release 版本)
构建方式:Qt5.14.2 MSVC2017 64bit Release
1、实现效果图 2、实现方式 大致流程:
1、使用 vtkEllipseArcSource 类在原图像上画出椭圆进行框选。
2、使用 vtkImageEllipsoidSource 类制作椭圆的二值图像
3、使用 vtkImageMask 类 提取 ROI 椭圆区域
2.1 框选实现 2.1.1 初始化 自定义类 MyVTKOpenGLWidget 继承自 QVTKOpenGLWidget,初始化做了3件事:
1、创建 vtkImageViewer2 对象,用来显示图像,图像数据由外部传入。vtkImageViewer2 使用的 交互样式是自定义样式 MyInteractorStyle,主要是用来在椭圆绘制过程中屏蔽 vtk 自带的移动事件。
2、创建 vtkPointPicker 对象,用来在图像上拾取点坐标。
3、创建 vtkEllipseArcSource 对象,用来画 椭圆。
VMware Fusion 12 Pro 在mac上使用vmware
mac版虚拟机软件叫 VMware Fusion
可在官网直接下载 Fusion 12 Pro 专业版
在vmware中安装centos系统 需要准备一个iso文件,在centos官网下载,这里下载centos8,有几个版本,boot(网络安装)和dvd
boot.iso看文件大小只有600多M,这个镜像只有基本启动引导等内容,安装期间的大部分内容需要从网上下载,因此不合适在安装期间没有网络环境的情形。
DVD.iso这个文件有8G多,是最大的一个文件,包含了centos 8的所有内容。如果你的U盘足够大,用这个镜像安装最方便。
mininal.iso,这个文件1G多,可以在安装期间无网络环境的条件下完成centos 8的最小安装。是上述两个ISO文件的折中。很适合安装时无网络环境、但又没有足够大的U盘或者只能用光盘启动的情况。
推荐安装dvd版本
将安装的iso拖入vmware软件,使用快捷安装,输入用户和密码;配置虚拟机,使用默认配置,网络连接默认使用NAT模式;开始启动虚拟机,安装centos 网络连接模式 桥接模式:直接连接到物理网络 优点:简单,只需要在系统内设置一个网段内的ip;共享主机网卡,跟一台真电脑一样,可以进行局域网通信
缺点:需要占用网段内一个ip
NAT模式:使用已共享的主机IP地址 使用VMnet8虚拟网卡
优点:不用占用ip
缺点:只能和主机通信,不能和其它局域网内电脑通信
和Host-Only区别在于,如果主机可以访问互联网,则虚拟机也可以访问互联网
Host-Only:与主机共享一个私有网络 使用VMnet1虚拟网卡
和NAT模式差别在于,只能和主机通信,不能访问互联网