热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

labview面向对象编程_LabVIEW面向对象编程_初窥门径(9):操作者框架ActorFramework之开发...

在前一篇文章中,我们从传统的队列消息处理器(QMH)设计模式逐步演化出操作者框架(ActorFramework)
ae44268886f19a247d287b3508a11d0c.png

在前一篇文章中,我们从传统的队列消息处理器(QMH)设计模式逐步演化出操作者框架(Actor Framework),并通过面向对象技术的封装、继承和多态技术抽象出来了操作者类(Actor.lvclass)、消息类(Message.lvclass)以及负责底层消息传输的消息队列类(Message Queues.lvclass)。

除此之外,NI公司的开发者们还为我们提供了框架必需的配套VI节点函数以及配套消息生成器脚本工具软件,但是正如前篇文章所述,由于框架的使用主要是通过面向对象技术的继承技术的合理应用与掌握,如操作者类的新建,功能的扩展与多循环的辅助装饰功能,消息的制作等等编程工作。本篇博客将主要讲解操作者框架(Actor Framework)在实际开发过程中需要了解与掌握的相关知识点:

主要有消息的继承开发应用,操作者的继承开发应用,及重要的操作者本身动态运行流程(启动、运行、停止、错误),还有在实际开发过程中必不可少的在Actor Core.vi中与消息处理循环并行处理的辅助功能循环(Helper Loops)的开发原则。

最后,简单的概述一下实际开发操作者框架应用程序的开发流程。

特别需要提醒的一点就是,由于该系列文章篇幅的限制,本文中并不讲述具体的某个AF实际应用的例程开发,因为基于操作者框架的开发例程在NI开发者论坛中比比皆是,包括众多的简单的小型DEMO演示示范样例,实践动手练习样例手册(Hand-on exercise mannual ),随LabVIEW系统安装附带的反馈式蒸发冷却器样例,以及开发经理Elijah Kerry开发的Measurement Utility大型例程,因此文章还是聚焦于操作者的核心概念及内部运转机制,只有理解和掌握了这些知识点才能更好的看懂实际样例代码以及开发出自己的实际操作者应用程序。

我们以LabVIEW2012版本为例(这也是首个将操作者框架4.0作为内嵌模板设计模式的版本,2010及2011版本都需要自行安装安装才可使用),看看操作者框架为我们提供了哪些基本结构内容,首先是位于National InstrumentsLabVIEW 2012vi.libActorFramework目录中的操作者框架库,其中的主要类成员Actor、Message和Message Queue在前篇文章中已进行过详细的介绍。

在物理文档视图中有三个类结构并未包含在AF框架的逻辑视图概念中,这三个类分别为Batch Msg、Reply Msg、Self-Addressed Msg和Time_Delayed Send Message,都是属于消息类Messages的子类型,用于批量、同步通信、自动寻址消息以及时间延迟发送消息的场景下的操作者相互通信。根据原厂AF框架的白皮书介绍,由于这些场景属于特定场合的特殊应用,并不是广泛应用的范畴,所以框架本身库(Actor Framework.lvlibp)中并没有包含这些特殊消息类。

dcdcbf5820dd609f23e1b885d22d7c74.png

在LabVIEW编程环境程序框图“函数选板”中的“数据通信”选板中的“操作者框架”面板中有相关的功能函数操作节点,这些功能函数节点均是操作者类库中的对应类的公共访问函数,用以方便开发者使用。

d60104e2db19e84097263c5d30d8d577.png

而真正要进行面向操作者设计(Actor-Oriented Design)的应用程序开发时,必须要对面向对象技术中的继承有着良好的技能掌握,由于程序设计时要对功能分解封装成一个个相对独立的操作者(Actor),操作者间嵌套组成层级树状模型,并通过消息类(Message)进行相互访问通信,实际代码开发时首当其冲的就是对操作者父类(Actor.lvclass)进行继承与扩展功能,在使用一一对应功能的消息子类完成对功能的调用。

f637a6c9fc71ea43d21fa3de701a86f8.png

继承分为抽象接口继承(也成为规范继承)和实现继承,从程序设计的角度是鼓励规范继承,使得继承父类的子类可以完成相应的动态替换(多态),由于是对抽象概念的代码应用,这种替换并不会影响客户调用代码的使用;在我们的操作者开发框架的开发应用中,对消息的继承就是属于规范继承。

68178e753cd51f22318485feaab61c2b.png

前一篇文章中我们讲到消息类的动态分配调用就是设计模式中命令模式的具体应用,所有的消息都要从消息类(Message.lvclass)继承而来,AF框架本身自定义了部分所有操作者都需要使用的内置消息子类(Stop、Lask ask等等),和某些特定场合应用的消息子类。

每个自定义的消息子类都需要根据被调用操作者的具体功能函数进行消息类Do.vi包装覆写,同时对应编写Send task Message.vi,进行消息本身的产生以及传输至操作者(Actor)待入消息队列中,以便Actor中的Actor Core.vi中的消息处理循环调用响应的功能函数。

消息类中的簇属性(.ctl)可以用来携带传输至需要发送给操作者功能函数的输入参数数据变量。对消息类中的Do.vi的覆写就是标准的规范继承的使用方式。祖先类只定义了输入输出参数,并没有实现任何具体的代码实现,而消息子类们需要覆写(Override)Do.vi,用具体代码实现自己的逻辑功能调用。

在应用时,所有的操作者代码(即客户代码)调用消息类的发送任务及消息处理全部都统一一致,依赖继承与多态(动态分配)技术确保了框架应用的开闭特性,既有使用上的一致性,又有相应的具体细节差异性,充分体现了面向对象的继承、多态与封装的强大威力。

规范继承(抽象概念接口继承)强调继承扩展的代码被上层的概念层调用,易于扩展,没有实现继承的认知学习负担,开发应用便捷有效,但是在设计阶段需要精心合理地安排逻辑功能及任务分解,定义好统一输入输出参数接口,因此需要花费较多的设计时间,但是应用操作者框架的好处之一就是该框架由NI公司的资深工程师主导设计开发,并在多个实践工程中反复验证与测试,因此大幅度降低自己开发类似框架测试与维护的时间精力,提高整体代码质量和缩短开发时间。

e8e3b23b8a0b7b8e125445932bd7f32a.png

操作者框架的核心类—Actor.lvclass中的Actor Core.vi继承扩展应用是进行基于操作者框架开发流程中最为核心关键的步骤,它的继承方式即为实现继承,子操作者类在继承操作者父类(Actor.lvclass)后,必须要覆写该访问范围为保护的核心方法,并且在方法中还要使用“调用父类方法”的节点完成对消息处理循环的通用代码的复用,在子类的Actor Core.vi函数中往往还有启动嵌套其他操作者、辅助功能循环(Helper Loops)以及程序界面等功能任务。

从代码实现上来看,Actor.lvclass中的Actor Core.vi继承是典型的实现继承,代码复用,由于每个操作者要求都从操作者父类继承,并且要有维护自己内部消息处理循环,框架的设计者在顶层父类实现了具体的消息处理循环代码,每个具体的子操作者通过继承来对该片段代码的复用,并根据自己的职责能力添加相应的功能代码。

但是从更为宏观的语义概念上来看,顶层父类Actor只提供了基础消息处理循环运转,以及启动、停止等框架性模板代码并预留了扩展点(在下面详细讲解),并没有任何的逻辑概念上的映射,而子类通过继承,复用了上层的基础代码和框架流程,并通过Actor Core.vi的核心覆写来实现完成一个具体的实际逻辑概念意义的操作者逻辑单元,从这个层面上讲:是父类和子类共同实现的操作者语义概念,缺一不可。

操作者框架是作为原先消息队列处理QMH的多线程并发程序开发的面向对象技术的替代品,因此多线程开发中某个新线程的启动、运行、停止、错误处理等工作内容必备可少,在Actor Framework中将这些工作流程固化成标准模板流程,是设计模式中模板方法的具体实践应用,并通过继承扩展点函数来提供对具体差异性的封装,进行操作者开发工程师们如果对操作者的运行生命周期不能够熟知掌握的话,就很难搭建出良好的应用程序。但是要想详细了解该流程就不可避免的要学习了解一下程序源代码,因为不管多么精准的流程图和清晰的文字解释说明,都不如直接查看框架程序代码源框图来的直接有效,虽然说源代码比较复杂,但是通过操作者框架相关配套技术资料(白皮书、PPT演讲和NI开发者社区论坛)的支持,还是能够顺利掌握的,有了对内部原理机制的理解,相应地就可以很好的支持对操作者框架的扩展应用。

ed1d0998a0a278c5a8cc28f811f964f6.png

启动操作者及运转消息处理循环,以及关停的功能函数均封装在操作者类库中的操作者类(Actor.lvclass)中,所有的操作者均遵循一致的生命周期运行,其中的启动触发点就是该类中的唯一公开函数节点Lanch Actor.vi,在该函数内部将异步调用Actor.vi,Lanch Actor.vi函数同时创建一个消息队列的队列(Queue of Queues)用以将欲启动起来的操作者的消息待入队列传递出来供主调用者使用。另外还创建了一个队列用以将在Actor.vi中调用的Pre-Lanuch Init.vi函数产生的错误传递出来便于启动Lanch Actor.vi函数做出相应的处理。而被异步调用Actor.vi的函数节点内部将首先创建一个全新的消息队列作为将要启动的操作者消息处理循环的消息待入队列,并通过Actor.vi的输入参数(Queue of Queues)将给队列传输至Lanch Actor.vi函数当中去,并将通过作为Actor.vi输入参数主调用者的消息待入队列和新产生的消息队列作为预启动操作者的内部属性构建完成。

2cdc71203260cc80e290f128b42c1573.png

然后Actor.vi函数节点依次顺序调用预先配置函数Pre Launch Init.vi,以及核心消息处理循环函数Actor Core.vi,从而完成操作者的启动与正常的消息处理While循环运转过程,主调用者可以通过使用Lanch Actor.vi输出参数外传出的消息待入队列,来对该启动运转的操作者发送消息调用调用功能,操作者的Actor Core.vi函数节点中的消息处理循环完成命令模式的消息Do.vi的动态调用,如果在此运转过程中有错误产生,将调用处理错误Handle Error.vi函数节点完成处理,该函数默认的处理方式为关闭消息处理的循环。

操作者子类能够覆写Actor Core.vi,但是覆写的VI必须保留调用父节点功能函数(Call Parent Method)。这因为调用父节点功能函数要运行顶层父类的消息处理循环MHL,并且该循环与操作者子类的新增功能代码是并行运行而非顺序执行的。覆写的VI中还可以包括用户界面,调用嵌套的其他操作者,或者增加状态机的其它控制逻辑代码以及功能辅助循环(Helper Loops)。

d33ca812c50949161e38b988e4922a6f.png

一个操作者的生命运行周期有启动,运转,就必然有停止结束,停止结束的过程也是通过发送给该操作者停止消息(Stop Msg)来触发完成,停止消息有两种不同发送方式——一种为正常优先级的发送(错误代码为43),另外一种为紧急优先级的发送(错误代码为1608),这也是框架内唯一的使用的以紧急优先级发送的消息。停止消息的Do.vi函数运行将通过产生预先规定好的错误簇触发Handle Error.vi函数来直接停止操作者消息处理循环(MHL),停止消息从功能上讲非常类似一颗毒丸,将毒丸消息即人为产生的特定错误消息簇发送给正常运转的消息处理单元从而关闭掉While循环,Actor Core.vi内部在结束消息处理循环后还将继续调用Stop.vi函数节点,而Stop.vi是保护访问范围函数Stop Core.vi的包装函数,操作者需要清理的资源可以在此完成清理关闭工作。如果一个操作者覆写Actor Core.vi来启动多个内嵌的操作者,相对应的也必须对Stop Core.vi进行覆写,以便继续传递停止消息给这些嵌套的操作者。

另外重要的一点是,在Actor Core.vi结束关闭后,将返回到Actor.vi函数,该函数将继续完成后继的两个步骤任务工作。

646db30f28d36355e99c5f64dc368d0a.png

第一: 关闭产生的接收消息队列

在Actor Core.vi的消息处理While循环关闭以后,执行完成Stop.vi(内部实际是包装调用Stop Core.vi)后关闭退出后,将执行线程返还给Actor.vi函数,该函数首先关闭早先创建的消息队列,这样的设计将确保每个操作者都负责维护自己的消息队列的创建与销毁,于是消息队列有着和接收者(receiver)组件相同的生命周期,而不是依赖于消息的发送者(Sender),这也就意味着不管是任何组件角色——调用者(Caller)或者是被调用者(Callee)都能够发送停止消息关闭自己,而无需担心消息没有被发送出去。

14ebd485ad35c6e6b4485d32b02d5d49.png

第二: 发送Actor关闭后的状态给调用者(主程序或者是主调用Actor操作者)

在一个操作者完成关闭后将返回最终的终止状态给主调用者,该操作是通过发送给主调用者的“Last Ack”消息来完成的,Lask Ack.lvclass消息类携带着被调用者的最终错误(如果有的话)和该操作者的最终值,以便给主调用者使用。如在嵌套操作者停止后,如果还想要执行一些后继的操作,需要覆写接收到“Last Ack”消息操作者的Handle Last Ack Core.vi函数节点。

至此,我们详细地完成了消息类(Message.lvclass)和操作者类(Actor.lvclass)的主要接口函数的功能讨论,以及操作者类本身的启动、运转和停止的细节过程,可见,要想灵活应用操作者Actor Framework,就需要对消息类和操作者类的结构性继承,以及运转流程的核心扩展点有着清晰的认识及熟练的应用。

在LabVIEW的随机帮助中也提供了重写扩展点的帮助说明,下面我们表格的形式予以汇总说明。

fc0dcbf54b459252457f5d2c533e7000.png
80d8c54e8fe94394a6efa8be3a39bc01.png
c6c05eee31ebc3be16fdc5264d4cf3c4.png
675679e081fdb377d832c6e407d26035.png
349d6bfb045d13842db694d0bceb119b.png
2e3d9dfff4fc94dd7758b6960d470987.png
5b956070ee882b734e5a25863f033ef7.png
e9bc7a52c643f1bd0ee1e3629ed77095.png

除了以上的核心消息类和操作者类的核心扩展点外,在操作者框架中还有多个关键的包装调用映射关系,即静态的框架应用调用VI与被包装的动态分配的核心(Core)扩展点的一一对应关系。从框架应用层面上讲,框架应用调用的是静态的VI,在通过静态的VI再去调用包装核心VI,核心VI根据具体的子类要求进行必要的覆写(Override),从而实现具体细节的差异化代码编写。采取这样代码实现的目的主要为了可以非常精细化的进行代码复用及访问范围的控制,也就是面向对象技术中的关键技术——封装的具体体现。

让我们以最为核心的一对映射来分析:操作者类(Actor.lvclass)中的Actor.vi和Actor Core.vi,Actor.vi的访问范围为私有(private),也就是只有唯一的操作者类(Actor.lvclass)本身才能访问,并在该VI函数中完成操作者本身接收队列的新建、启动前的预处理工作、运行Actor Core.vi消息处理循环函数,以及循环关闭后的发送“最后一次确认”消息,并在最后调用Drop Message.vi的包装静态函数等等任务工作,以上该Actor.vi中函数的种种功能是完成操作者启动运转的必备关键工作,是每个单独的操作者启动的必备流程,而且这部分工作流程在概念上也是高度统一,但是框架的设计者可能在未来的版本迭代演化中,修改其内部的代码实现,所以该流程代码细节不便于直接暴露给外界或者被子类继承修改,由此决定了该函数封装特性为内部私有的限定访问范围,从而保护该部分实现代码发生变化时是不会波及和影响到客户代码,但是在该函数内部,根据扩展需要相应地开放了两个保护访问范围(protect)的继承扩展点,分别是Pre Launch Init.vi函数和Actor Core.vi,用来便于继承子类差异化实现各自的具体实现。这种形式实际上体现了代码隔离,逻辑继承的理念,其他的操作者类中扩展点函数如Handle Error.vi中并没有要对子孙类封闭的具体代码,因此直接将该方法定义为访问范围为保护(protect)的动态分配函数,可直接由子类进行覆写(Override)。

另外还有一种形式就是为了保证上层框架的调用的稳定性,将要扩展点核心(Core)方法包装在静态方法函数中,Handle Last Ack.vi与Handle Last Ack Core.vi就是该类型的具体体现。

另外还有一个小提示,Actor.Vi 与Actor Core.vi的图标基本是一致的,除了在Actor Core.vi图标的下部中多出来一个矩形的图形框。其他的映射对函数与此类似。

bc0e61096ff76b309f369aee2f5f7170.png
60b276bd732f9b764ffaaad121066043.png
423a8d0cb27ec0e72f7830a6cc8f1bdb.png
ac91b0c63fc5619cf0ccec16870c0667.png

至此,我们将操作者框架的启动流程、继承扩展点以及封装委托调用等细节内容进行了详尽的讲述与分析,虽然单单从数量上来看操作者框架库内只有个30个文档文件VIs,如下表所示。

6b4e0e1e55754c89e0254e244ebbeebf.png

但是由于项目中的VI使用了委托包装调用,异步调用和动态分配调用等等非直接的方法调用模式,另外特别是队列(Queue)的各种复杂应用以及面向对象技术的重复使用,这些因素综合叠加在一起导致了初学者理解该框架代码较为困难,结合源代码以及NI公司提供的详尽的讲解资料,理解Actor Model概念及异步通信的模式,加强对面向对象技术的深入学习,还是能够顺利的完成操作者框架的掌握,只有在较为充分的理解了其内部原理和源代码后才能完成应用系统的面向操作者的开发过程,将任务分解为相应的层次嵌套调用的操作者来组合完成整体目标任务。

在进行以操作者为任务单元基础的应用程序设计开发时,我们还需要继续掌握另外的两个知识点:操作者框架中的人机用户界面(UI)以及辅助功能循环(Helper Loops)的创建,我们先学习和了解一下辅助功能循环(Helper Loops)的知识点,人机界面中基于自定义用户事件(User Event)的前面板更新模式广义上来讲也是属于辅助功能循环(Helper Loops)的一种。

除非某个操作者角色任务就是消息转发和路由的任务功能,否则你还是需要在覆写操作者核心(Actor Core.vi)方法,添加自己辅助功能循环(Helper)实现自己附加的逻辑控制循环,并且需要明确的是,新添加的循环是与父类的Actor Core.vi即消息处理循环并行运行的。

99768d237d5c5a9f7a37e8d323705138.png

辅助功能循环是用来要完成操作者大多数实际工作任务的,例如:操作者需要一个用户操作界面?构建一个用户事件处理在辅助功能循环里(如上图所示),想要从数据采集卡(DAQ)设备中读取数据?可以将该读取任务放在辅助功能循环中,并且该辅助功能循环也许需要使用“Read Self Enqueuer”方法(获取自身队列引用)来向该操作者发送消息调用,并且该操作者核心(Actor Core)也需要与辅助功能循环内建互通消息机制,因此在应用程序中往往需要由你来决定采用哪种(内建)通信机制是较为适宜的整体解决方案。

另外在设计辅助功能循环还需要注意的是,由你开启的循环就一定要由你来负责关闭,操作者框架本身完全有责任停止消息处理循环(该功能位于调用父类Actor core函数节点里),与此同时你需要停止掉你所负责的所有功能辅助循环,如果违反该原则,例如你在停止掉一个在后台运行的操作者消息处理循环,却没有停掉的应用任务的辅助功能循环时,上层调用程序就再也不能与它正常的通信。

操作者核心函数方法(Actor Core.vi)中封装的消息处理循环一次只能处理一条消息,它按消息入列的顺序(有优先权的例外)相应地出列消息,每条消息将调用操作者对象的一个方法。如果你打算将某段执行很长时间的代码放置到消息中,你将会延迟处理下一条消息的工作直至该段缓慢代码执行完毕。这也就意味着任何其它的消息将不会被及时的去处理(即使是最高优先权的消息)。

响应消息的一般原则是我们希望消息处理的越快越好,所以为了保证消息处理的时效性,你应该把所有需要长时间运行的代码委托放置到功能辅助循环当中去,操作者核心(Actor Core)消息处理循环将通知你创建功能辅助循环要做些具体任务(可以通过所有的LabVIEW中可用的内部进程间通信工具,队列、通告和事件等等),然后它将及时返回继续处理后继消息(或者处于等待状态)。你的功能辅助循环这时将花费长时间去进行真正的操作工作。该设计方式将允许消息处理方式优雅和可控,并同时解放你的操作者代码去做更多的工作。

操作者框架的最主要目标之一就是创建与代码的其余部分松散耦合(或理想情况下零耦合)的代码模块。可以分离出消息处理循环的功能代码越多,调试相关代码也就会越容易。这意味着每个操作者将是功能独立自洽单元,只有一个小范围的公共接口通过消息来间接让其它的代码可以调用。每个操作者应该与其它操作者保持着松散耦合。

但是,这种关系并不适用于你的功能辅助循环与消息处理循环之间(备注:内外要有别,对内要强内聚,对外要松散耦合)。功能辅助循环应该紧密地与你的消息处理程序循环强耦合。应该在两个循环之间共享某单一引用,两种循环之间的紧密耦合使你能够非常迅速地完成更多的工作任务,因为辅助程序循环和消息处理程序循环是操作者整体一件事情的各自一部分,它们之间缺一不可。

因此合理设计覆写(Override)操作者核心方法,创建自己的功能辅助循环是设计一个操作者关键的任务之一。

前面提到过操作者的人机界面也可以使用功能辅助循环(Helper Loop)来完成,通过创建一组用户事件(User Event)来管理前面板更新,在Actor Core.vi 中创建这些更新事件,并在调用父节点(Call Parent node)函数之前完成将事件绑定注册工作。然后可以创建一个循环来动态处理事件,在Actor Core.vi 中,对于调用父节点(Call Parent node)即消息处理循环而言该事件处理循环是并行运行的,相当于一个辅助功能循环。当操作者收到消息,该消息的处理功能函数可以生成适当的事件并伴随传递相关数据,不光有事件通讯方式,另外也可以有其他的选择,如队列、通告、或者其他的通讯机制完成并行循环。但是无论采用哪种机制,操作者框架的设计者们都建议在应用程序中所有的操作者都应该只采用一种通讯传输机制,并且在做为整体系统应用中将此类内建通信机制的总数保持在最低限度,另外还要注意最后要确保该机制能够提供停止掉并行循环能力,以及在该操作者类的子孙类中覆写Stop Core.vi 中也能够触发停止该机制。

除使用用户事件机制的辅助功能循环来创建人机界面外,还有另外一种方式可用:即使用界面控件的参考引用来直接操控界面更新,但是要注意在调用父节点函数(Call Parent node)之前绑定这些引用到操作者之中,此后操作者就能够在内部的任何方法中访问这些引用了。该模式允许内部方法控制Actor Core.vi中的用户交互界面。虽然该模式并不是最为有效的,但是如果界面仅是显示信息而无需响应用户操作动作时还是十分简洁有效的。

最后,在简要概述一下操作者框架的开发流程:每个操作者都是一个LabVIEW类。和其他LabVIEW类一样,创建之前必须明白操作者的含义及其作用。为了明确待创建的操作者的类型及其功能,需考虑应用程序中独立运行的任务(任务分解)以及每个任务能够执行的动作。明确操作者需执行的动作后,还需确定操作者运行时哪些动作需要连续发生,哪些动作仅在从另一个操作者收到消息(内部消息或外部消息)后发生。连续发生的动作应添加在操作者的“操作者核心”方法中。响应消息而发生的动作应表示为操作者类的一个方法。使用该信息定义操作者行为。然后,创建消息,命令操作者调用每个方法。

由于篇幅有限,关于操作者框架的具体应用实例就不在此展开讲解了,掌握了Actor Model的核心概念思想和了解源代码后,特别是扩展点的应用及辅助功能循环的应用特点,结合实例学习并在实际工程中模仿应用,一定能熟练的掌握操作者框架,开发出良好的多线程LabVIEW应用程序。

参考文件:

《Actor Framework Whitepaper》, National Instruments Corporation,2014年。

《Introduction to the Actor Framework》,National Instruments Corporation,2011年。

《Actor Framework Basics》,http://www.mooregoodideas.com,2015年。


如果感觉文章对你有帮助,点个赞吧!
为以后便于查找知识,再点个收藏吧!


如果这篇文章能够让你收获价值,就请我喝一听北冰洋汽水吧!

88cf68bcde4b61fd15a4d9998f9bd14e.png
请使用赞赏功能支付4元,感谢!



推荐阅读
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • vue使用
    关键词: ... [详细]
  • 本文介绍了brain的意思、读音、翻译、用法、发音、词组、同反义词等内容,以及脑新东方在线英语词典的相关信息。还包括了brain的词汇搭配、形容词和名词的用法,以及与brain相关的短语和词组。此外,还介绍了与brain相关的医学术语和智囊团等相关内容。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • Linux重启网络命令实例及关机和重启示例教程
    本文介绍了Linux系统中重启网络命令的实例,以及使用不同方式关机和重启系统的示例教程。包括使用图形界面和控制台访问系统的方法,以及使用shutdown命令进行系统关机和重启的句法和用法。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 解决Cydia数据库错误:could not open file /var/lib/dpkg/status 的方法
    本文介绍了解决iOS系统中Cydia数据库错误的方法。通过使用苹果电脑上的Impactor工具和NewTerm软件,以及ifunbox工具和终端命令,可以解决该问题。具体步骤包括下载所需工具、连接手机到电脑、安装NewTerm、下载ifunbox并注册Dropbox账号、下载并解压lib.zip文件、将lib文件夹拖入Books文件夹中,并将lib文件夹拷贝到/var/目录下。以上方法适用于已经越狱且出现Cydia数据库错误的iPhone手机。 ... [详细]
  • 拥抱Android Design Support Library新变化(导航视图、悬浮ActionBar)
    转载请注明明桑AndroidAndroid5.0Loollipop作为Android最重要的版本之一,为我们带来了全新的界面风格和设计语言。看起来很受欢迎࿰ ... [详细]
  • Google在I/O开发者大会详细介绍Android N系统的更新和安全性提升
    Google在2016年的I/O开发者大会上详细介绍了Android N系统的更新和安全性提升。Android N系统在安全方面支持无缝升级更新和修补漏洞,引入了基于文件的数据加密系统和移动版本的Chrome浏览器可以识别恶意网站等新的安全机制。在性能方面,Android N内置了先进的图形处理系统Vulkan,加入了JIT编译器以提高安装效率和减少应用程序的占用空间。此外,Android N还具有自动关闭长时间未使用的后台应用程序来释放系统资源的机制。 ... [详细]
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
author-avatar
momosu1028_738_636
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有