会场安排问题(贪心算法): 贪心算法:在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。 2.相关代码:
#include<stdio.h> #define M 11 #define MAX 2000 void select(int num,int s[],int f[]){ int preStart=0; int preFinal=MAX;//保证是无限大即可 int i; int temp; int OK=1; int sel[M];//用来储存相容的活动编号 int selNum=0; while(OK){ OK=0; for(i=0;i<M;i++){ if(f[i]<preFinal&&s[i]>=preStart){//寻找开始时间合适地情况下结束时间最早者 preFinal=f[i]; temp=i; OK=1; printf("%d-------------->%d\n",s[i],f[i]); } } if(preFinal!=MAX){ //变量的重新赋值 sel[selNum++]=temp; preStart=f[temp]; preFinal=MAX; } } printf("%d",selNum);//相容数量 } int main(){ int s[]={1,3,0,5,3,5,6,8,8,2,12}; int f[]={4,5,6,7,8,9,10,11,12,13,14}; select(M,s,f); } 3.问题解决思路:
把会场要安排的所有活动作为一个集合W,初始开始时间标准为preStart,初始结束时间标准为preFinal.每次放入W的活动要在满足,开始时间s[i]>preStart的前提下f[i]最小。然后把f[i]赋值给preStart. 依次加入,直到加不进去为止。从而把问题解决。
4.为什么贪心算法可以得到最优解?
贪心算法并不是针对任何一个问题都存在最优解,但是针对会场安排问题可以得到最优解。我们可以从贪心算法得到的结果集仅进行倒推。首先去掉结果集中的第一个活动A,那么在剩下的活动中,结束时间最早的活动B的结束时间一定不早于A,那么A活动在最优解中一定合理。再用同样的方式判断活动B,依次类推,则结果集就是最优解。
自从有了Docker再也不用烦恼环境问题了,发布的时候使用Docker固然方便快捷,开发的时候需要使用自己的addons,Docker可以将自己的Addons映射到容器里Odoo的指定addons目录下,这样开发Odoo顿时也很简便了。映射很简单,docker run 的时候,启用 ‘-v’ 命令来指定映射目录,具体格式同 '-p',如: -v 主机工作目录:容器内目录。
1. 启动Postgres容器
sudo docker run -d -v /home/odoo/odoo-dev/docker_odoo/pgdata/:/usr/lib/postgresql/data -e POSTGRES_USER=odoo -e POSTGRES_PASSWORD=odoo --name db postgres:9.4 2. 启动Odoo
sudo docker run -v /home/odoo/odoo-dev/docker_odoo/my_addons/:/mnt/extra-addons -p 8069:8069 --name odoo --link db:db -t odoo --db-filter=ODOO.* 这样就可以在本机的/home/odoo/odoo-dev/docker_odoo/my_addons/目录下写自己的模块了。
看hub.docker.com里的教程说可以自定义Odoo配置,大致方式跟映射工作目录是一样的,使用'-v'来映射,但实践发现同时映射配置和工作目录不工作,所以还是直接指定内联的odoo参数来使用自定义的配置,如以上启动Odoo命令里最后的 '--db-filter=ODOO.*',意思就是数据实例名只接受'ODOO'开头的。
补充:之前对于启动Odoo的时候既映射工作目录又要指定自定义配置不知道如何写命令的问题有了解决方法,使用环境变量ODOO_CONF, 使用'-e'来指定环境变量,命令如下:
sudo docker run -e ODOO_CONF=/home/odoo/odoo-dev/ODOO.conf -v /home/odoo/odoo-dev/docker_odoo/my_addons/:/mnt/extra-addons -p 8069:8069 --name odoo --link db:db -t odoo 转载于:https://www.cnblogs.com/lnkDel/p/8990405.html
概述: 以太坊虚拟机EVM是智能合约的运行环境,它不仅是沙盒封装的,而且是完全隔离的,也就是说在EVM中运行代码是无法访问网络,文件系统和其他进程的。甚至智能合约之间的访问也是受限的。 账户:
以太坊中有两类账户,它们共用一个地址空间:外部账户由公钥-私钥对控制,也就是由人控制;合约账户由与账户一起存储的代码控制。 外部账户地址由公钥决定,而合约账户地址是在创建合约时确定,该地址通过合约创建者的地址和从该地址发出过的交易数量(也就是所谓的nonce值)计算得到的。 无论账户是否存储代码,对EVM来说这两类账户都是一样的。 每个账户都有一个键值对形式的持久化存储,它的key和value长度都是256位,这里称之为存储storage。 此外,每个账户都有一个以太币余额balance,单位为Wei,这个余额会因发送包含以太币的交易而改变。 交易:
交易是从一个账户发送到另一个账户的消息,它能包含二进制数据和以太币。这里的账户,可能是相同或特殊的零账户。二进制数据为合约负载。 若目标账户包含代码,则此代码会被执行,同时以payload作为入参。 若目标账户是零账户,也就是账户地址为0,则此交易将创建一个新合约。如前面所说,合约的地址不是令地址,而是通过合约创建者的地址和从该地址发出过的交易数量(也就是nonce值)计算得到的。这个用来创建合约的交易的payload会被转换为EVM字节码并执行。执行的输出将作为合约代码被永久存储。这意味着若要创建一个合约,不必向合约发送真正的合约代码,而只发送能够产生合约代码的代码即可。 注意:当合约正在创建时,它的代码仍然是空的。鉴于此,不应该在构造函数还未完全执行成功就去回调合约。 gas: 一旦创建交易,每笔交易都要收取一定数量的gas,其目的是限制执行交易所需要的工作量和未交易支付手续费,当EVM执行交易时,gas会按照特定规则逐渐耗尽。 gas price是由交易发送者设置的一个值,发送者账户需要预付手续费等于gas_price*gas。若交易执行后还有剩余,则gas会原路返回。 无论执行到什么位置,一旦gas耗尽,比如降为负值了,就会触发一个out-of-gas异常。当前调用帧current call fram所做的所有状态修改都将被回滚。 调用帧表示EVM运行栈(stack)中当前操作所需要的若干元素。 存储storage,内存memory和栈stack: 每个账户有一块持久化内存区称为存储。存储是将256位字映射到256位字的键值存储区。在合约中枚举存储是不可能的,且读存储相对开销很高,修改存储的开销甚至更高,合约只能读写存储区内属于自己的部分。 第二个内存区域称为内存,合约会为每一次消息调用获取一块全新的被擦除过的内存实例。内存是线性的,可按字节寻址,但是读长度限制为256位宽,而写长度可为8位也可为256位宽。当读写之前从未读写过的内存字(word)时,比如偏移到该字内的任何位置时,内存都将按字进行扩展,这里每个字是256位。扩容也将消耗一定量的gas,随着内存使用量的增长,其费用也会增高,以平方级。 EVM是基于栈的,而不是基于寄存器,因此所有的计算都是在一个被称为栈stack的区域执行的。栈的最大尺寸为1024个元素,每个包含256个位。栈访问只限制访问栈顶端,限制方式为:允许拷贝最顶端16个元素之一到栈顶或者交换栈顶元素和下面16个元素之一。所有其他操作都只能取得最顶端的两个元素,或一个或更多,这取决于具体操作,运算后,把结果压入栈顶。这里当然可以把栈上的元素放到存储或内存中,但无法只访问栈上指定深度的那个元素,除非先从栈顶移除其他元素。 指令集: EVM的指令集要保证最小,以避免引起共识问题的错误实现。所有指令操作都是针对256位字这个基本数据类型来进行的,还具备常用的算术,位,逻辑和比较操作,也由条件和无条件跳转操作。此外合约还可以访问当前区块的相关属性,比如区块号和时间戳。 消息调用: 合约可通过消息调用的方式来调用其他合约或发送以太币到非合约账户,消息调用与交易很相似,它们都有一个源,目标,数据,以太币,gas和返回值。实际上每个交易都由一个顶层消息调用构成,这个消息调用反过来又可创建更多的消息调用。 合约可决定在其内部消息调用中,对其剩余的gas,应该发送多少和保留多少。若在内部消息调用时发生了out-of-gas异常或其他任何异常,这将会被一个压入栈顶的错误值所指示。这种情况下,只消耗掉与该内部消息调用一起发送的gas。在Solidity中,在这种情况下,发起调用的合约会默认引起一个人工异常,以便异常在调用栈中冒泡。 若前面所说,被调用的合约,也可以和调用者时同一个合约,会获得一块全新的擦除过的内存实例,并可访问调用的payload,这个payload数据是由一个称为calldata的独立区域提供的。调用执行结束后,返回数据将被存放在调用方预先分配好的一块内存中。调用深度被限制为1024,也就是意味着对于更加复杂的操作,应该使用循环而不是递归调用。 委托调用delegatecall/代码调用callcode和库: 有一种特殊类型的消息调用,被称为委托调用,它与一般消息调用的区别在于,目标地址的代码将在发起掉用的合约的上下文中执行,同时msg.sender和msg.value的值不会改变。 这意味着合约在运行时可以从另一个地址动态地加载代码。存储,当前地址和余额都仍然指向发起调用的合约,只有代码是从被调用地址获取的。 这使得在solidity内可实现库:可复用的库代码可放在合约的存储上,比如,用于实现一个复杂数据结构。 日志: 在一个特殊可索引的数据结构内存储数据,该结构可一路向上被映射到区块级别。这个特性在solidity内为实现事件被使用到,也被称为日志。合约在创建后就无法再访问log数据,但是可以在区块外高效地访问这些数据。由于部分日志数据是被保存在布隆过滤器中的,因此可以高效且加密安全地搜索这些日志,所以即使网络节点没有下载整个区块链(轻客户端)也仍然能够找到这些日志。 创建: 合约甚至可以通过使用一个特殊指令来创建其他合约,并不是简单的调用零地址。这些创建调用create calls和一般的消息调用唯一的不同点就是: payload数据被执行及其结果以合约代码形式存储,同时调用者或创建者在栈上获得新合约的地址。 自毁: 从区块链上删除合约代码的唯一方式就是在合约在合约地址上执行selfdestruct自毁操作,该合约地址上剩余的以太币会被发送到指定目标地址,随即其存储和代码从状态中删除。 注意:
1.即使合约的代码不包含对selfdestruct的调用,它仍然可以通过delegatecall或者callcode执行自毁操作。
2.旧合约的删减有可能或不可能由以太坊客户端实现,另外,归档节点可能选择无限期地保存合约存储和代码。
3.当前的外部账户不可能从状态中删除。 什么是布隆过滤器? 布隆过滤器是1970年由布隆提出的,实际上是一个很长的二进制向量和一系列随机映射函数,布隆过滤器可用于检索一个元素是否在一个集合中,它的优点是空间效率和查询时间都远远 超过一般的算法,缺点是有一定的误识别率和删除困难。
参考网址
===========================================>20180504
go-ethereum v1.8.7
每个交易都带有两部分内容需要执行:
1.转账,由转出方地址向转入方地址转一笔以太币
2.携带[]byte类型成员变量payload,其每一个byte都对应一个单独虚拟机指令,这些内容都是由EVM对象来完成的,EVM结构体是以太坊虚拟机机制的核心,它与协同类的UML关系图如下所示:
1.Context结构体包含交易Transaction的信息GasPrice,GasLimit;区块Block的信息BlockNumber,Time,Difficulty以及转账函数等,这些信息都是提供给EVM的。
2.StateDB接口是针对state.StateDB结构体设计的本地行为接口,可以为EVM提供statedb的相关操作。
3.Interpreter结构体为解释器,用来解释执行EVM中合约的指令。
对EVM结构中定义的成员变量Context,仅声明变量而无类型,而变量名又是其类型名时,在Go语言中,这里意味着主结构体EVM可直接调用成员变量Context的所有方法和成员变量,比如EVM直接调用Context中的Transfer()
完成转账:
交易转账操作由Context对象中的TransferFunc类型函数实现,类似函数类型还有CanTransferFunc和GetHashFunc:
go-ethereum\core\vm:
type ( CanTransferFunc func(StateDB, common.Address, *big.
·1.用户空间 参考:https://blog.csdn.net/crazycoder8848/article/details/38958075 #define page_map_file "/proc/self/pagemap" typedef unsigned long long uint64_t; #define PFN_MASK ((((uint64_t)1)<<55)-1) #define PFN_PRESENT_FLAG (((uint64_t)1)<<63) int mem_addr_vir2phy(unsigned long vir, unsigned long *phy) { int fd; int page_size = getpagesize(); unsigned long vir_page_idx = vir/page_size; unsigned long pfn_item_offset = vir_page_idx*sizeof(uint64_t); uint64_t pfn_item; fd = open(page_map_file, O_RDONLY); if (fd<0) { printf("open %s failed", page_map_file); goto mem_addr_vir2phy_error; } if ((off_t)-1 == lseek(fd, pfn_item_offset, SEEK_SET)) { printf("lseek %s failed", page_map_file); goto mem_addr_vir2phy_error; } if (sizeof(uint64_t) !
jqwidgets: //日期插件(
1.导入jaxDateTimeInput.js 和 jaxCalendar.js 2.改为24小时制的:需把 hh:mm:ss 改为 HH:mm:ss (将小写‘h’改为大写‘H’就OK了。) 3. 改成中文版的还需
globalize.js和 globalize.culture.zh-CN.js(需将该js里取值改变成:names:一、二、三、、、 4.设置参数 culture:'zh-CN'
)
$("#datetime").jqxDateTimeInput({theme:'darkblue',showTimeButton: true,formatString: 'yyyy/MM/dd HH:mm:ss', width: '202px', height: '25px',culture:'zh-CN' }); bootstrap: dateTimePicker1.Format = DateTimePickerFormat.Custom; dateTimePicker1.CustomFormat = "yyyy-MM-dd HH:mm:ss";//HH是24小时,hh是12小时
简介:
本文主要介绍在内核中简单使用DMA实现内存数据传递。由于本篇文章中没有介绍与框架相关的程序,只是使用字符设备来操作DMA,同时也没有抽象的层次,因此本文中代码分析部分就相对简单。但我还是会将文章分为两部分,第一部分我将介绍与DMA相关的知识。而第二部分讲解在内核中如何通过代码实现DMA的数据传递。
Linux内核:linux-2.6.22.6
所用开发板:JZ2440 V3(S3C2440A)
声明:
本文是看完韦东山老师关于DMA的视频后,所写的学习总结。所以文章中可能会用到老师在课上所讲解的内容。不过我也在文章中加了一些我自己的东西。所以如果你觉得我的文章对你有帮助那是我的荣幸。同时我将S3C2440A中DMA这一章节翻译为中文并放在我的文章:S3C2440A 第八章:DMA 中,如果你想了解可以看看这篇翻译。而我会在下面介绍DMA中一些重要的知识点。
第一部分:DMA相关知识
在介绍DMA之前我想问大家:我们为什么要引入DMA,DMA对我们有什么好处那?
答:计算机系统中各种常用的数据输入/输出方法有查询方式(包括无条件及条件传送方式)和中断方式,这些方式适用于CPU与慢速及中速外设之间的数据交换。但当高速外设要与系统内存或者要在系统内存的不同区域之间进行大量数据的快速传送时,就在一定程度上限制了数据传送的速率。直接存储器存取(DMA)就是为解决这个问题提出的,采用DMA方式,在一定时间段内,由DMA控制器取代CPU,获得总线控制权,来实现内存与外设或者内存的不同区域之间大量数据的快速传送。同时很重要的一点是当DMA传输数据时,并不占用CPU资源,在这个时候CPU可以空出手来做其他的事情。这样我们既可以做大量数据的高速传输又可以让CPU有时间和资源去做其他的事情。
上面介绍了什么是DMA,也介绍了DMA的重要性。那么我们就要看看我们芯片中的DMA了。在S3C2440A中,我们集成了DMA模块,可以用来传递高速传输数据。既然是数据传输那么我又要问了,我们知道数据传输三要素:源,目的,长度。这是我们数据传输时要知道的。那么在S3C2440A中,源,目的,长度是怎么表示的那?
在2440中我们的源与目的的选择有四种情况
1. 源和目的都在系统总线
2. 源在系统总线,而目的在外部总线
3. 源在外部总线,而目的在系统总线
4. 源和目的都在外部总线
2440中源与目的就是通信的双方,而这双方是通过请求DMA传递信息的,所以我们将上面这些向DMA发送请求的(不管是源还是目的)称为请求源。他们请求DMA来传输数据。而在2440中有四条通道来设置不同的请求源,他们为:
介绍完源与目的,我们本来是要介绍数据传输长度的。但是我想还是先介绍数据传输方式比较好。介绍完数据传输方式我们了解了数据是以什么样的方式传输的。这样我们自然就知道要传输的数据长度了。
DMA模式介绍:
DMA service mode:single service&Whole service。前一模式下,一次DMA请求完成一项原子操作,并且transfer count的值减1。后一模式下,一次DMA请求完成一批原子操作,直到transfer count等于0表示完成一次整体服务。具体对应DCON[27]。
DMA DREQ/DACK PROTOCOL:DMA请求和应答的协议有两种,Demond mode 和 Handshake mode。两者对Request和Ack的时序定义有所不同:
在Demond模式下,如果DMA完成一次请求后如果Request仍然有效,那么DMA就认为这是下一次DMA请求,并立即开始下一次的传输;
在Handshake模式下,DMA完成一次请求后等待Request信号无效,如果Request无效,DMA会无效ACK两个时钟周期,再等待下一次Request。
下面我们来介绍DMA中数据传输的格式,注意,这里说的是传输格式,而不是传输的大小。在DMA中有两种传输格式,单元传输和burst4传输,相对于单元传输的每次读写一个单元,burst4 可以一次完成四个单元的读写。而这里的单元就是我们说的数据的大小有:字节,半字,字。
有了上面的知识我们就可以介绍总的数据的传输大小了:DSZ x TSZ x TC,其中DSZ就是上面说的数据的大小,TSZ是传输的格式,而TC是传输的次数。他们的乘积就是整个数据的大小了。
介绍了数据传输的三要素,我想大家又要问了:既然有了数据传输的三要素,那么我们的数据是如何传输的?
在我们的S3C2440中他是使用三态的有限状态机(FSM)来实现数据的传输的。虽然是使用的有限状态机,但2440 中还是有两种传输模式:单服务模式和全服务模式。我下面用一幅图来展示三态的有限状态机:
在状态1时,DMA等待DMA请求,此时DMA ACK 和INT REQ 为 0 。当DMA收到DMA请求时,他跳转到状态2 。
在状态2时,DMA ACK为1,INT REQ还为0,此时CURR_TC从DCON[19:0]从加载计数值。在他们完成后他跳到状态3 。
在状态3时,引入子状态机用来处理一次原子操作,即完成一次数据从源读出然后写到目的中。而在这是我们就要分单服务模式和全服务模式来讨论了。在单服务模式中,子有限状态机完成一次原子操作后CURR_TC-1,主有限状态机将DMA ACK设为0,然后调回到状态1,然后等待下一次的DMA请求。而在全服务模式时,子有限状态机将一直运行直到CURR_TC为0,然后他再将INT REQ设为1而将DMA ACK设为0,然后调回到状态1,然后等待下一次的DMA请求。
上面我们介绍了DMA是如何传输数据的,同时也讲了数据传输的三要素。那么我们下面就要讲一下在2440中DMA的配置步骤和要点了(主要针对寄存器):
1.数据从哪里来,到哪里去?
一、排序算法说明 排序的定义:对一个无序的序列进行排序的过程。 输入:n个数:a1,a2,a3,…,an。 输出:n个数的排列:a1,a2,a3,…,an,使得a1<=a2<=a3<=…<=an。排序的稳定性:相同值的节点相对位置是否会发生改变。 稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。 不稳定:如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面。排序的时间复杂度:一个算法执行所耗费的时间,一般在三种情况下考虑:最好情况、最坏情况、平均情况。空间复杂度:运行完一个程序所需内存的大小。 二、各种排序算法性能分析 1. 插入排序 1.1 直接插入排序 直接插入排序的原理是将未排好序的序列一个个地插入到已排好序的序列中,插入时,需要与已排好序的序列进行多次比较,直到找到合适的位置插入,而原来已排好序的部分节点可能需要进行后移操作,这个过程中需要一个额外的空间保存一个值用于交换节点,所以空间复杂度为O(1)。
时间复杂度 最坏情况:当待排序序列正好为逆序状态,首先遍历整个序列,之后一个个地将待插入元素放在已排序的序列最前面,之后的所有元素都需要向后移动一位,所以比较和移动的时间复杂度都是O(n),再加上遍历整个序列的复杂度,总复杂度为O(n^2)。 最好情况:当待排序序列正好为正序状态,则遍历完整个序列,当插入元素时,只比较一次就够了,所以时间复杂度为O(n)。 平均情况:当被插入的元素放在已排序的序列中间位置时,为平均情况,比较和移动的时间复杂度为O(n/2),所以总的时间复杂度依然为O(n^2)。
稳定性 插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。当然,刚开始这个有序的小序列只有1个元素,就是第一个元素。比较是从有序序列的末尾开始,也就是想要插入的元素和已经有序的最大者开始比起,如果比它大则直接插入在其后面,否则一直往前找直到找到它该插入的位置。如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。
1.2 希尔排序 希尔排序是对直接插入排序的优化,它的原理是加大插入排序中元素的间隔,并在这些有间隔的元素中进行插入排序,从而使数据进行大幅度的移动,当进行过依次排序后,再减小间隔再一次进行插入排序,直到间隔缩小为1。这样做的目的可以使得最后排序时整个序列基本有序,而无需再进行过多的元素比较和移动次数,在这个过程中也只需要一个额外的空间保存一个值用于交换节点,所以空间复杂度为O(1)。
时间复杂度与增量的选取有关,计算起来较为复杂,不再细述。
稳定性 希尔排序是进行多次直接插入排序的算法,由于多次插入排序,虽然每一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以希尔排序是不稳定的。
2. 选择排序 2.1 直接选择排序 直接选择排序的原理是在待排序的序列中选取最小(大)的值放在序列的第一个位置。遍历整个序列,首先选取第一位置的值分别与之后所有的值比较,如果后边值更小则与之交换,直到第一轮遍历结束时,序列第一个位置的值就是最小的,接下来继续从第二个、第三个做同样的操作,此过程需要一个额外的空间保存最小值用于交换,所以空间复杂度为O(1)。 时间复杂度 序列无论是正序还是逆序状态,每一轮的最小值需要比较到最后才能确定,所以最坏情况和最好情况下都需要 比较n次,再加上遍历整个序列的O(n),总的复杂度为O(n^2),平均情况的复杂度也是O(n^2)。
稳定性 直接选择排序是给每个位置选择当前元素最小的,比如给第一个位置选择最小的,在剩余元素里面给第二个元素选择第二小的,依次类推,直到第n-1个元素,第n个元素不用选择了,因为只剩下它一个最大的元素了。那么,在一趟选择,如果当前元素比一个元素小,而该小的元素又出现在一个和当前元素相等的元素后面,那么交换后稳定性就被破坏了。举个例子,序列5 8 5 2 9, 第一遍选择时第1个元素5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了,所以选择排序不是一个稳定的排序算法。
2.2 堆排序 堆排序是对直接选择排序的改进算法,选择排序的特点在于每次选取最小或最大的值,而选取最大值时的比较次数为复杂度的关键,堆排序采用二叉树的方法存储元素,每个节点都满足父节点的值大于等于子节点的特点,与直接选择排序类似,堆排序需要两个个值的空间来存储临时变量,用于交换节点,一次用于存储子树最大节点用于交换子节点,一次用于存储堆顶的值用于交换最后的节点,所以空间复杂度为O(1)。
采用堆的方式寻找最大值是降低时间复杂度的关键,假设有n个数据,需要n-1次建堆的过程,每次建堆的时间复杂度为log2n,但是无论序列的开始状态如何,都需要对堆进行遍历寻找最大值,所以在最好情况、最坏情况和平均情况下的时间复杂度都是O(nlog2n)。
稳定性 堆排序是利用堆的特点,堆的结构是节点i的孩子为2*i和2*i+1节点,大顶堆要求父节点大于等于其2个子节点,小顶堆要求父节点小于等于其2个子节点。在一个长为n 的序列,堆排序的过程是从第n/2开始和其子节点共3个值选择最大(大顶堆)或者最小(小顶堆),这3个元素之间的选择当然不会破坏稳定性。但当为n /2-1, n/2-2, …1这些个父节点选择元素时,就会破坏稳定性。有可能第n/2个父节点交换把后面一个元素交换过去了,而第n/2-1个父节点把后面一个相同的元素没有交换,那么这2个相同的元素之间的稳定性就被破坏了。所以,堆排序不是一个稳定的排序算法。
3. 交换排序 3.1 冒泡排序 冒泡排序是交换类排序算法的典型实现,它的原理是遍历整个序列,比较前后相邻两个值的大小,如果前边比后边大,则交换它们,直到序列的最后的两个值进行比较,这样最后的值就是最大的,之后再进行第二轮、第三轮遍历,直到剩下序列的最前的值。从实现原理上可以知道,冒泡排序只需要一个值的空间用于交换节点,所以空间复杂度为O(1)。
时间复杂度 最坏情况:序列为逆序状态,则每一轮遍历都需要n次交换位置,所以时间复杂度为O(n^2)。 最好情况:序列为正序状态,每一轮遍历不需要交换位置,所以时间复杂度为O(n)。 平均情况:每一轮遍历需要n/2次交换位置,所以时间复杂度依然为O(n^2)。
稳定性 冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,如果两个元素相等,不会发生交换。如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,也不会发生交换,所以相同元素的前后顺序并没有改 变,所以冒泡排序是一种稳定排序算法。
3.2 快速排序 快速排序是另一种交换类排序方法,它的原理是选择一个基准元素,通常选择第一个元素或者最后一个元素,通过一趟扫描,将待排序列分成两部分,一部分比基准元素小,一部分大于等于基准元素,此时基准元素在其排好序后的正确位置,然后再用同样的方法递归地排序划分的两部分。首先就地快速排序使用的空间是O(1)的,而真正消耗空间的就是递归调用了,因为每次递归就要保持一些数据,每一次都平分数组的情况下空间复杂度为O(logn) ,最差的情况下空间复杂度为O(n)。
时间复杂度 最坏情况:每一次选取的基准元素都是最大或最小的,复杂度为O(n^2) 最好情况:每一次选取的基准元素都能平分整个序列,由于快排涉及到递归调用,所以时间复杂度为O(nlog2n)。 平均情况:平均情况下复杂度也是O(nlog2n)。 稳定性 快速排序有两个方向,左边的i下标一直往右走,当a[i]<=a[center_index],其中center_index是中枢元素的数组下标,一般取为数组第0个元素。而右边的j下标一直往左走,当a[j]>a[center_index]。如果i和j都走不动了,i<=j, 交换a[i]和a[j],重复上面的过程,直到i>j。 交换a[j]和a[center_index],完成一趟快速排序。在中枢元素和a[j]交换的时候,很有可能把前面的元素的稳定性打乱,比如序列为 5 3 3 4 3 8 9 10 11,现在中枢元素5和3(第5个元素,下标从1开始计)交换就会把元素3的稳定性打乱,所以快速排序是一个不稳定的排序算法,不稳定发生在中枢元素和a[j] 交换的时刻。 4.
一、排序算法的分类 选择排序(直接选择排序,堆排序)交换排序(冒泡排序,快速排序)插入排序(直接插入排序,希尔排序) 归并排序桶式排序 基数排序 二、归并排序的原理 归并排序利用的是分治的思想实现的,对于给定的一组数据,利用递归与分治技术将数据序列划分成为越来越小的子序列,之后对子序列排序,最后再用递归方法将排好序的子序列合并成为有序序列。合并两个子序列时,需要申请两个子序列加起来长度的内存,临时存储新的生成序列,再将新生成的序列赋值到原数组相应的位置。
三、归并排序的实现 public class MergeSort { public static void merSort(int[] arr,int left,int right){ if(left<right){ int mid = (left+right)/2; merSort(arr,left,mid);//左边归并排序,使得左子序列有序 merSort(arr,mid+1,right);//右边归并排序,使得右子序列有序 merge(arr,left,mid,right);//合并两个子序列 } } private static void merge(int[] arr, int left, int mid, int right) { int[] temp = new int[right - left + 1];//ps:也可以从开始就申请一个与原数组大小相同的数组,因为重复new数组会频繁申请内存 int i = left; int j = mid+1; int k = 0; while(i<=mid&&j<=right){ if (arr[i] < arr[j]) { temp[k++] = arr[i++]; } else { temp[k++] = arr[j++]; } } while(i<=mid){//将左边剩余元素填充进temp中 temp[k++] = arr[i++]; } while(j<=right){//将右序列剩余元素填充进temp中 temp[k++] = arr[j++]; } //将temp中的元素全部拷贝到原数组中 for (int k2 = 0; k2 < temp.
1. 概述 information_schema 数据库跟 performance_schema 一样,都是 MySQL 自带的信息数据库。其中 performance_schema 用于性能分析,而 information_schema 用于存储数据库元数据(关于数据的数据),例如数据库名、表名、列的数据类型、访问权限等。
information_schema 中的表实际上是视图,而不是基本表,因此,文件系统上没有与之相关的文件。
mysql> use information_schema; Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Database changed mysql> show tables; +---------------------------------------+ | Tables_in_information_schema | +---------------------------------------+ | CHARACTER_SETS | | COLLATIONS | | COLLATION_CHARACTER_SET_APPLICABILITY | | COLUMNS | | COLUMN_PRIVILEGES | | ENGINES | | EVENTS | | FILES | | GLOBAL_STATUS | | GLOBAL_VARIABLES | | KEY_COLUMN_USAGE | | OPTIMIZER_TRACE | | PARAMETERS | | PARTITIONS | | PLUGINS | | PROCESSLIST | | PROFILING | | REFERENTIAL_CONSTRAINTS | | ROUTINES | | SCHEMATA | | SCHEMA_PRIVILEGES | | SESSION_STATUS | | SESSION_VARIABLES | | STATISTICS | | TABLES | | TABLESPACES | | TABLE_CONSTRAINTS | | TABLE_PRIVILEGES | | TRIGGERS | | USER_PRIVILEGES | | VIEWS | | INNODB_LOCKS | | INNODB_TRX | | INNODB_SYS_DATAFILES | | INNODB_FT_CONFIG | | INNODB_SYS_VIRTUAL | | INNODB_CMP | | INNODB_FT_BEING_DELETED | | INNODB_CMP_RESET | | INNODB_CMP_PER_INDEX | | INNODB_CMPMEM_RESET | | INNODB_FT_DELETED | | INNODB_BUFFER_PAGE_LRU | | INNODB_LOCK_WAITS | | INNODB_TEMP_TABLE_INFO | | INNODB_SYS_INDEXES | | INNODB_SYS_TABLES | | INNODB_SYS_FIELDS | | INNODB_CMP_PER_INDEX_RESET | | INNODB_BUFFER_PAGE | | INNODB_FT_DEFAULT_STOPWORD | | INNODB_FT_INDEX_TABLE | | INNODB_FT_INDEX_CACHE | | INNODB_SYS_TABLESPACES | | INNODB_METRICS | | INNODB_SYS_FOREIGN_COLS | | INNODB_CMPMEM | | INNODB_BUFFER_POOL_STATS | | INNODB_SYS_COLUMNS | | INNODB_SYS_FOREIGN | | INNODB_SYS_TABLESTATS | +---------------------------------------+ 61 rows in set (0.
应用场景: GR适用功能场景:核心交换机S86或者S12K双主控引擎,或是两台S86或者S12K构建VSU的场景下,双链路与邻居设备(比如聚合AP口与汇聚5750E设备相连)互联,同时启用动态路由协议(常见的OSPF,BGP)时与邻居设备交互路由,强烈建议开启GR功能,这样当双引擎中的主引擎故障的时候切换到备引擎工作,或者是VSU组中的主设备故障时候切换到备设备工作的时候,能够保证邻居以及自身的OSPF,BGP路由条目保持不删除,只是邻居关系重新收敛建立,这样保证数据不断流转发(根据每个测试场景实际情况的不同有时候不丢包,有时候丢1个包)。
功能简介: GR技术产生的背景:
1、分布式架构并支持不间断转发技术的设备,要求控制平面和数据平面分离。
2、控制平面负责路由计算、表项下发等;数据平面根据控制平面下发的转发表项进行数据转发。
3、在主备引擎切换时,备引擎上有数据平面信息,可以快速接替主引擎完成数据转发;但备引擎无控制面信息(比如动态路由数据库、邻居关系等),那么将导致其邻接设备检测到本设备动态协议中断,邻接的设备动态路由重新收敛,导致整网路由黑洞或路由旁路。
4、动态路由的收敛时间将是分钟级别的,无法满足不间断转发需求。
GR的原理:
Graceful Restart(优雅重启),主要是为了实现在协议的重新启动过程中数据转发不间断。在管理板主备切换过程中,GR功能使动态路由邻居的路由转发表项保持住,待新的邻居协商收敛完成后再进行表项刷新,使得网络拓扑保持稳定,维持转发表;保障业务不中断。
GR的两个角色:
Restarter:要执行优雅重启的设备
Helper :辅助Restarter完成优雅重启,是Restarter的邻接设备
GR配置:
1、RIP-GR配置:需在本端配置Restarter,邻接设备无需配置(RIP天然支持Helper 角色)
Ruijie(config)#router rip
Ruijie(config-router)#graceful-restart
2、OSPF-GR配置:需在本端配置Restarter,邻接设备配置helper(锐捷设备默认开启helper功能,无需配置;友商设备行为需要确认,多数也为默认,建议参阅其配置手册确认)
Ruijie(config)#router ospf 1
Ruijie(config-router)#graceful-restart 3、BGP-GR配置:两端都需配置Restarter
Ruijie(config)#router bgp 1
Ruijie(config-router)#bgp graceful-restart 4、LDP-GR配置:需在本端配置Restarter,邻接设备配置helper(锐捷设备默认开启helper功能,无需配置;友商设备行为需要确认,多数也为默认,建议参阅其配置手册确认)
Ruijie(config)#mpls router ldp
Ruijie(config-mpls-router)#graceful-restart
转载于:https://blog.51cto.com/12855977/2106732
vim是进入配置文件并修改
修改完按Esc进入控制模式,再:w保存 :wq是保存并退出
转载于:https://www.cnblogs.com/qy-blogs/p/8899451.html
B树的概念是为了解决一些现实问题而提出的,当数据量太大时,而内存中又无法存储这么多的数据,那么就需要将数据存储在磁盘上,如果继续采用平衡树的方法就会带来一些问题,平衡树每个节点都会分为两个节点,那么当数据太大的时候,树的高度也会不断增减,IO操作的次数也随之增加,所以需要降低树的高度,所以才有了一个节点存储多个数据的B树的方案。
B-树 B-树其实就是B树,B树是一种多路平衡搜索树(非二叉),若其是M路,则:
任意非叶子节点最多可以有M个子女,且M>2;根节点的子女数为[2,M];除了根节点以外的非叶子节点的子女数目为M/2(取上整)个到M个;每个节点存放至少M/2-1(取上整)和至多M-1个键值(至少两个);非叶子节点的关键字个数=指向子女的指针个数-1;非叶子节点的关键字K[1],K[2],…,K[M-1]且有K[i] < K[i+1];非叶子节点的指针P[1],P[2],…,P[M];其中P[1]指向关键字小于K[1]的子树,P[M]指向关键字大于K[M-1]的子树,其他P[i]指向关键字属于(K[i-1],K[i])的子树;所有叶子节点都位于同一层。 B树与二叉搜索树的最大区别在于其每个节点可以存不止一个键值,并且其子女不止两个,不过还是需要满足键值数=子女数-1。因此,对于相同数量的键值,B树比二叉搜索树要更加矮一些,特别是当M较大时,树高会更低。
一张图看懂一切 B-树的插入 B树的插入首先查找插入所在的节点,若该节点未满,插入即可,若该节点以及满了,则需要将该节点分裂,并将该节点的中间的元素移动到父节点上,若父节点未满,则结束,若父节点也满了,则需要继续分裂父节点,如此不断向上,直到根节点,如果根节点也满了,则分裂根节点,从而树的高度+1。
B-树的删除 B树的删除首先要找到删除的节点,并删除节点中的元素,如果删除的元素有左右孩子,则上移左孩子最右节点或右孩子最左节点到父节点,若没有左右孩子,则直接删除。删除后,若某节点中元素数目不符合B树要求(小于M/2-1取上整),则需要看起相邻的兄弟节点是否有多余的元素,若有,则可以向父节点借一个元素,然后将最丰满的相邻兄弟结点中上移最后或最前一个元素到父节点中(有点类似于左旋)。若其相邻兄弟节点没有多余的元素,则与其兄弟节点合并成一个节点,此时也需要将父节点中的一个元素一起合并。
B+树 B+树主要是应文件系统所需而产生的。文件系统中,文件的目录是一级一级索引,只有最底层的叶子节点(文件)保存数据。非叶子节点只保存索引,不保存实际的数据,数据都保存在叶子节点中,所有的非叶子节点都可以看成是索引部分。
B+树是B树的一个变种,其也是一种多路平衡搜索树,其与B树的主要区别是:
非叶子节点的指针数量与关键字数量相等;非叶子节点的子树指针P[i],指向关键字值属于[K[i],K[i+1])的子树(B树是开区间,B+树是左闭右开,也就是说B树不允许关键字重复,而B+树允许);所有关键字都在叶子节点出现,所有的叶子节点增加了一个链指针(稠密索引,且链表中的关键字切好是有序的);非叶子节点相当于是叶子节点的索引(稀疏索引),叶子节点相当于是存储数据的数据层。 一张图看懂一切 B+树的插入 B+树的插入与B树类似,如果节点中有多余的空间放入元素,则直接插入即可。如果节点本来就已经满了,则将其分裂为两个节点,并将其中间元素的索引放入到父节点中,在这里如果是叶子节点的话,是拷贝中间元素的索引到父节点中(因为叶子节点需要包含所有的元素),而如果是非叶子节点,则是上移节点的中间元素到父节点中。
B+树的删除 在叶节点中删除元素,如果节点还满足B+树的要求,则okay。如果元素个数过少,并且其邻近兄弟节点有多余的元素,则从邻近兄弟节点中借一个元素,并修改父节点中的索引使其满足新的划分。如果其邻近兄弟节点也没有多余的元素,则将其和邻近兄弟节点合并,并且我们需要修改其父节点的索引以满足新的划分。并且如果父节点的索引元素太少不满足要求,则需要继续看起兄弟节点是否多余,如果没有多余则还需要与兄弟节点合并,如此不断向上,直到根节点。如果根节点中元素也被删除,则把根节点删除,并由合并来的节点作为新的根节点,树的高度减1。
B-树和B+树的区别 B+树的非叶子节点并没有指向关键字具体信息的指针,因此其内部节点相对B树更小,如果把所有同一内部节点的关键字存放在同一盘块中,盘块所能容纳的关键字数量也越多,具有更好的空间局部性,一次性读入内存的需要查找的关键字也越多,相对的IO读写次数也就降低了。
另外对于B+树来说,因为非叶子节点只是叶子节点中关键字的索引,所以任何关键字的查找都必须走一条从根节点到叶子节点的路,所有关键字查询的路径长度相同。而若经常访问的元素离根节点很近,则B树访问更迅速,因为其不一定要到叶子节点。
数据库索引采用B+树的主要原因是B树在提高了IO性能的同时并没有解决元素遍历效率低下的问题,而也正是为了解决该问题,B+树应运而生。因为叶子节点中增加了一个链指针,B+树只需要取遍历叶子节点可以实现整棵树的遍历。而且数据库中基于范围的查询是非常频繁的,B树对基于范围的查询效率太低。
在[B,A]=butter(n,wn)中,n是滤波器的阶数,Wn是截止频率,Wc = 截止频率*2/采样频率
Wc=2*50/Fs; %截止频率 50Hz
[b,a]=butter(4,Wc,‘low’); %低通滤波器 主要去除高频信号,可消除白噪声等
Signal_Filter=filter(b,a,Mix_Signal_1);
[b,a]=butter(4,Wc,‘high’); %高通滤波器 主要去除低频信号,可消除基线漂移等
[b,a]=butter(4,Wc,‘stop’); %带阻滤波器 [b,a]=butter(4,Wc); %带通滤波器 Wc=[W1 W2] W1<W2
仍有问题可 help butter :
The cutoff frequency Wn must be 0.0 < Wn < 1.0, with 1.0 corresponding to half the sample rate.
If Wn is a two-element vector, Wn = [W1 W2], butter returns an order 2N bandpass filter with passband W1 < W < W2.
[B,A] = butter(N,Wn,'high') designs a highpass filter.
腾讯云服务器相关配置 最近租了腾讯云ubuntu16.04搭建网站练习,将其中的相关配置记录下来,方便以后查找。
root账号登录 先登录ubuntu账号,执行sudo passwd root输入密码执行sudo vi /etc/ssh/sshd_config找到PermitRootLogin without-password一行,将后面的without-password改为yes,保存执行sudo service ssh restart 本地主机向云服务器传文件 windows下有一款好用的软件winscpputty下载解压后有一个可执行程序pscp.exe,可以用于文件传输 pscp a.txt ubuntu@182.92.82.82:/home/ubuntu 备份apt-get源 cp /etc/apt/sources.list /etc/apt/sources.list.backup.1 安装jdk 利用apt-get安装openjdk
sudo apt-get install openjdk-8-jdk 安装maven 利用apt-get安装maven
sudo apt-get install maven 更换国内源 安装mysql apt-get install mysql-server 设置远程连接(传送门:https://blog.csdn.net/Eternally123/article/details/79477537)解决中文编码问题 安装tomcat 下载官网上的tar.gz core包解压配置环境变量 修改$HOME/.profile 修改端口号注意:ubuntu中非root用户默认是无法启动1024以下端口的,所以想改成80端口启动必须root用户或者其他方法
下面创建方法不需要手动再重新调用subscribe中的方法:
1.just()
创建发送指定值的Observeble,just只是简单的发射原样的值,将数组或iterable当做单个数据.如果传递的值为null,则发送的observeble的值为null.item最多为10个.
public static <T> Observable<T> just(T item1, T item2, T item3, T item4, T item5, T item6, T item7, T item8, T item9, T item10) { ObjectHelper.requireNonNull(item1, "The first item is null"); ObjectHelper.requireNonNull(item2, "The second item is null"); ObjectHelper.requireNonNull(item3, "The third item is null"); ObjectHelper.requireNonNull(item4, "The fourth item is null"); ObjectHelper.requireNonNull(item5, "The fifth item is null"); ObjectHelper.requireNonNull(item6, "The sixth item is null"); ObjectHelper.requireNonNull(item7, "The seventh item is null"
get提交 //window.location=href; /* 使用post方式提交 */ var form = $("<form>"); //定义一个form表单 form.attr('method','post'); form.attr('action',href); $('body').append(form); //将表单放置在web中 form.submit(); //表单提交
目录
1、自定义view
2、adjustViewBounds
3、百分比布局
4、ConstraintLayout
我们在android开发过程中可能会遇到一种情况,一个组件需要保持固定的宽高比,但是组件本身大小却不定。尤其在android屏幕碎片化的情况下,很多时候我们需要让一个组件宽度与屏幕宽度一致,这样就无法确定宽度。那么如何让控件保持固定宽高比?有几种方法供大家选择。
1、自定义view 自定义view,重写onMeasure或onLayout等相关方法,通过预定的比例计算宽高。
下面是简单示例:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); if (mRatio != 0) { float height = width / mRatio; heightMeasureSpec = MeasureSpec.makeMeasureSpec((int) height, MeasureSpec.EXACTLY); } super.onMeasure(widthMeasureSpec, heightMeasureSpec); } 这种方法是很多早期开发者喜欢的方式,但是缺点是需要自己重新自定义一个view。
2、adjustViewBounds 为ImageView设置adjustViewBounds,如下:
android:adjustViewBounds="true" 这样ImageView就会以图片的宽高比显示。
但是这个方法的缺点是只能用于ImageView。
3、百分比布局 Android提供了Android-percent-support这个库,支持百分比布局,包括PercentRelativeLayout和PercentFrameLayout。
使用PercentFrameLayout也可以实现一个组件的固定比例显示,代码如下:
<android.support.percent.PercentFrameLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:layout_width="0dp" android:layout_height="0dp" android:scaleType="fitXY" app:layout_widthPercent="100%" app:layout_aspectRatio="@fraction/circle_article_aspectRatio" /> </android.support.percent.PercentFrameLayout> 需要在res/values下新建一个fraction.xml,代码如下:
<?xml version="1.0" encoding="utf-8"?> <resources> <item name="circle_article_aspectRatio" type="fraction">133%</item> </resources> 这样就实现了宽高4:3的比例。
今天,简单讲讲android的TextView 的setTextSize方法的使用。
之前,我看代码时发现了这个函数,于是在网上查询了这个函数的用法,发现之前自己了解的不够全面,所以这里记录一下。
看了看TextView的源码:
public void setTextSize(float size) {
setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
}
我们平时使用setTextSize()的时候都是只用了一个参数,那TypedValue.COMPLEX_UNIT_SP又是个什么鬼?别急来看看下面的代码:
public void setTextSize(int unit, float size) {
Context c = getContext();
Resources r;
if (c == null)
r = Resources.getSystem();
else
r = c.getResources();
setRawTextSize(TypedValue.applyDimension(
unit, size, r.getDisplayMetrics()));
}
这是我们在使用setTextView()的时候系统去帮我们做的事,第一个参数是系统默认使用的单位,第二个就是我们设置的文字大小值。那么TypedValue.COMPLEX_UNIT_SP到底是什么呢?
public static final int COMPLEX_UNIT_PX = 0;
/** {@link #TYPE_DIMENSION} complex unit: Value is Device Independent
* Pixels. */
public static final int COMPLEX_UNIT_DIP = 1;
一、什么是FastDFS?
DFS(distributed file system):分布式文件系统。
FastDFS是一种开源的轻量级分布式服务器,用来解决大容量存储的问题,并充分考虑了冗余备份,负载均衡,线性扩容等机制,注重高可用、高性能等指标。特别适合中小文件,对以文件为载体的在线服务提供了非常好的解决方案。
主要功能:文件存储,文件同步,文件访问(文件上传、文件下载)等。
二、FastDFS由什么组成?
FastDFS由跟踪服务器(TrackerServer)、存储服务器(Storage Server)和客户机(Client)构成。
跟踪服务器 TrackerServer:
主要起到调度工作,起到均衡作用,负责管理所有的Storage Server 和 Group ,每个Storage 在启动之后会自动连接Tracker ,告知自己所署的Group的信息,并保持周期性心跳,Tranker 会根据Storger的心跳信息,建立Group -> Storage Serverlist 【因为一个Group 可以由多个StorageServer构成】,由于Storage会保持周期性的心跳,所以Tracker 需要管理的元信息就很少,会全部存储在内存中,本身并不需要持久化任何数据,这样的话,就使得Tracker非常容易扩展,仅仅增加Tracker 即可扩展为 Tracker Cluster 集群服务。Cluster里的每个Tracker 之间是完全对等的,所有的Tracker都接收Storage的心跳信息,生成元数据信息来提供读写服务。
存储服务器 StorageServer:
主要提供容量和备份服务,以Group为单位,每个Group 内有多个Storage Server,数据互相备份,当一个Storage Server损坏时,可以通过其他的Storage Server进行恢复。
以Group 为单位组织存储能方便的进行应用隔离、负载均衡、副本数定制【副本数即为Storage Server 的数量】,比如将不同的应用数据存储到不同的Group就能实现简单的应用数据隔离,同时可以根据应用不同的访问特性来分配不同的Group,做到负载均衡。
客户端 Client:
此时,这个客户端并不是用户所在的客户端,而是部署了我们项目的服务器,每个客户端(搭载项目的服务器)都需要安装Nginx,客户端(搭载项目的服务器)和文件存储服务器之间的数据交流也是服务器之间的交流。
三、FastDFS工作流程
Ⅰ、上传
0:Storage Server会定时向Tracker Server 发送自己的Group以及文件夹信息。使得Tracker Server 会保留最近的元数据。
1:普通用户向Client(服务器)发送请求。
2:Client(服务器)会向TrackerServer发送请求存储。
3:TrackerServer 会向Client反馈一个不是太忙的Storage Server 信息。
4:Client(服务器)收到消息,请求StorageServer。
5:Storage Server存储数据,并反馈给Client一个id,这个id也就是存储文件的路径。
(路径分为组名、文件夹、文件名和后缀,也就是后面用来访问此文件的方式)
模拟对话:
StorageServer:Tracker老哥,我还活着,你要记得我啊!
用户1:我要上传个电脑啊,服务器!(哇,有点过分了啊!)
Client(服务器):好勒,东西给我,我帮你把东西存起来。
Client(服务器):TrackerServer,我要存一台电脑,给我找一个仓库。
TrackerServer:老铁等一下,我给你找一个地大一点的仓库,要不就去A小区B栋302仓库吧。
Client(服务器):嘿,StorageServer,呐,这个是我的东西。
介绍:Rapidjson
Rapidjson库是C++对象序列化到Json字符串的非常好的工具,以效率著称,腾讯的人写的。
官方网站:点击打开链接
本文全部资源百度云
这个库的缺点(个人拙见):
1 暴露的细节相对较多:容器,迭代器,类型,成员函数,序列化,反序列化,都有非常细致的操作。这个给使用者带来记忆负担较重。至少需要同时暴露Value类型和Document类型才能完整的实现Object内部包含Object类型。但这个包装起来很麻烦。
2 使用移动语义和自动内存回收:这个是内存占用小的保障,但是光是移动语义就给使用者造成很多不便。移动语义当然提高了效率,但是和使用者的程序代码风格不能很好的融合。移动语义使得Document的管理的内存数据在Document释放的时候就被析构,Object内包含Object的时候不好像C++基础类型语义那样去赋值操作(赋值就是副本,不担心原对象释放)。为了既能够使用移动语义和Document自动管理内存,又能够使用子对象的数据赋值,本文实现的CJsonObject对象返回和设置子对象均使用std::shared_ptr<CJsonObject>来实现。
3 序列化的思路好,但是没有和反序列化很好的结合:
官网上的序列化实现的思路是仿照STL的思路,使用类似输出操作符重载的方式给类增加序列化的函数(而不是输出操作符重载),从而使得复杂的类型(包括类类型成员的)可以借助成员类型的序列化来实现自己的序列化。
这种方式好处是明显的,程序结构清晰,Rapidjson对现有代码的冲击最小,想序列化哪一个就实现个序列化的成员函数就行了。但是却没有发现反序列化的例子。反序列化就是将json字符串通过Parse来创建一个C++内存对象,从而使用这个内存对象。
其实和序列化的场景一样,大多数人使用json都是想让原有类型支持序列化和反序列化,而不是抛弃原有类型(原有类型存在于大量的已有代码中,这样冲击太大)
还有一种思路是:将Rapidjson视为反序列化的工具,需要反序列化的时候临时创建Document对象来实现。但是数据在Document中,要想让现有C++类拿到这些数据,还是要一个一个的GetInt,GetBool,GetDouble等取出来再赋值给现有C++对象。这个是无法忍受的,只能再实现一套帮助类来做转换。这样增加了一批帮助类其实也是负担,而且程序代码分散而不集中。
本文的思路:
1 借鉴Rapidjson序列化的思路,让需要序列化的类自己实现统一的(通过实现基类CJsonBase来做到)序列化和反序列化成员函数。这样对现有代码的冲击最小,需要新写的代码量最小,最大程度的保护的原有的C++业务类
2 为序列化和反序列化实现一个执行类:CJsonObject,该类型提供序列化和反序列化时的所有操作实现。包括:CreateFromJson,ToJson,Get/SetInt GetSetBool 等。
3 优点:对现有代码的冲击最小(只需要包含添加CJsonObject类即可);只针对对现有代码的序列化和反序列化场景(比如C++对象存储到Redis;公开接口API形式跟外部通信,等等)
本文示例代码:借鉴了官网的serialize.cpp实现
#include "stdafx.h" #include "JsonObject.h" #include "Person.h" #include <iostream> #include <list> using namespace std; int _tmain(int argc, _TCHAR* argv[]) { std::list<Employee> employees; std::list<Employee> employeesCopyFromJson; employees.push_back(Employee("Milo YIP", 34, true)); employees.back().AddDependent(Dependent("Lua YIP", 3, new Education("Happy Kindergarten", 3.5))); employees.back().AddDependent(Dependent("Mio YIP", 1)); employees.push_back(Employee("Percy TSE", 30, false)); cout<<"C++对象->Json字符串:"<<endl; for (auto itr = employees.