讨论JAVA和QT之争

不想你离开。 提交于 2019-12-04 06:54:06

这是两种以跨平台为特色的开发方式。Qt更多被认为是一种框架,但是Qt中有新增一些C++所没有的语法,所以也可以认为是一种编程语言。Java被认为是一种编程语言,但是很多人并不知道JAVA的编程语言其实是Java SE,而他们所知的Java EE其实不是编程语言,反而是一种框架。

Qt和Java到底怎么选?没有任何明确需求的前提下,我认为尽可能使用Java,因为Java能够解决Qt不能解决的问题,而Java不能解决的问题Qt却基本没办法。Java和Qt都依赖C/C++,都是作为C/C++的延伸,讨论Java和Qt的时候,认为C++是两者共有的部分,只不过Qt更直接一点。

然后是一些相关需求下的对比。需要指出,现在是2019年10月,Java与Qt都有很大的发展。很多不了解Java的人妄言Java是用来做互联网的。这是一种误解。Java是全能型的编程语言,本身主要面向单台设备的开发,然后在单台设备之上才建立起各种脚本语言用以支持互联网。其中的Java语言也就是JavaSE是与互联网没什么关系的,它的竞争对手是纯C/C++编程以及Qt。互联网JavaEE其实不是Java语言,而是Java和C/C++共同的产品。JavaEE是一种产品类型,它的竞争也是产品级的竞争,也是JavaEE的内部竞争,主要是各种网页服务器软件,这里不提。我们现在所说的Java是指Java语言也就是JavaSE。

Java和Qt的性能怎么样?

对于单线程的控制台程序,显然Qt要比Java快20%~50%。这是因为Qt的产出是机器码,而Java需要通过Jvm来进行解释,当任务密度很高的时候,Qt是比Java快的。

对于多线程相互联锁的程序,也就是一个任务会分别由多个线程接力执行的情况下,总体Qt比Java快20%。Qt的线程交换时间大部分比Java短,但是不稳定,最长的时间是Java线程切换最长时间的2倍。Java最长一次交换需要13ms,QT需要26ms。也就是Qt平均更快,但是偶尔实时性不如Java。这个测试其实还是对Java不利,因为我用的Java框架已经做到非常庞大了,重载函数是重载了好几级父类的方法,各种函数调用次数也更多。而当时的Qt版本才刚开始,只重载了3层,函数调用也更少。不过总体上看,这些影响并不是非常重要。Qt还是会比Java快20%,因为Qt的线程切换大部分更快1ms。

在多线程领域,Qt的支持一直不够。Qt没有并发容器,这是很致命的。在高并发的情况下,Java的并发容器可以达到很好的性能,而Qt则必须为线程锁承担更大的开销。这一项很难比较,因为到底多少个线程算多?这个很难决定。但是我的控制层用的是高并发的结构,Qt显然慢到不能用。这个是我的锅,我的框架一开始就是为Java高并发的特性设计的,所以没有办法移植到Qt上。但是高并发真的是太方便了,每一个单元都可以有自己的线程,每一个控制程序都可以从中间启停,不再需要像PLC程序那样完整地从头到尾执行一遍。单元控制线程可以自由地在同步、异步、嵌入式三种状态切换,极大地提高了代码的灵活性。

关于Java和Qt的界面。

对于窗体控件,Qt还是比swing更快的。但是谁还会去用swing呢,swing的慢是肉眼可见的,现在我们有了JavaFX。JavaFX有着更美观的原生界面,更丰富的控件和更好和性能。而Qt原生的界面还是Win98那个时代的。JavaFX有自带的runLater可以处理非窗体线程操作窗体的操作(比如在按钮上做倒计时,或者触发后台大任务后再反馈到界面上,就需要一个让后台线程反馈的接口),Qt的runLater是我自己用定时器做的,效果也很好,就是需要自己在构造函数里先进行初始化。其他Qt开发人员可能更依赖信号槽。事实上我的runLater也是信号槽的包装。信号槽是让程序变混乱的好办法。传统的Win32API编程顶多让一个任务分别写到两个case里面,Qt直接通过信号槽绑定让任何意想不到的地方都能发生关系,你甚至不知道他们是怎么搞到一起的。这简直糟透了,除非你打算自己一个人搞定全部,不然不要把一个任务放到两个地方。runLater的好处就是可以把前后端代码写到连续的几行里面,虽然它们并不是按书面顺序执行的,但是带来的好处是调试的时候不需要在多个地方找任务的碎片。把信号槽封装成runLater很费劲,要防止程序出错,还要合理地安排Lambda对象的传递,但是功在当代利在千秋,这是很值得的。

另外Qt的界面不会自由地变化大小,而JavaFX会。如果需要控件的位置和大小严格控制,那么JavaFX需要设置最大尺寸和最小尺寸,而Qt就只有一个不会动的尺寸。JavaFX有些控件的尺寸和位置接口会比较匪夷所思,控件的变化也不是实时的,可以尽情地操作。Jvm会自动跳过不需要的操作,而Qt不会,一个菜鸟可以很轻松让Qt死机。

Qt和Java的IDE。

Qt的IDE以QtCreater为主,其它IDE简直不能用。QtCreater不是很稳定,会出现一些奇怪的现象,甚至会出现同样的代码过一夜就坏了的情况。如果可以的话,我宁愿用Codeblocks,但是对Qt的配置实在麻烦。Java的Eclipse至少比Visual Studio好用,虽然最近添加了一些代码生成的功能让我很不爽。Eclipse还是非常稳定的,Java也不会出现隔夜问题。

IO操作。

Qt的串口操作确实比Java好太多了。现在的Java串口还是我自己用Win32 dll搞的。因为以前用的RXTX不支持Win10,Java没有自带的串口组件,只能用JNI自己开发。Qt的Socket和串口有统一的父类,而Java的TCP就有好几种,UCP又是另一种开发方式。最后,我搞了好几个月才实现了Java的统一通信端口。但是Qt也有不足,Qt的串口不支持多线程,解决方法是我按Java上面做过的统一接口的结构,又重新包装了一遍Qt的各种通信。把Qt的通信对象放到一个后台线程,通过线程信号的方法间接调用Qt的通信类对象,这样就能保证Qt通信对象只在一个线程中被调用,而它的功能可以分配给其它任意线程。所以最终我对Qt和Java都做了一次二次封装。Qt本来很好了,就是不支持多线程读写串口坑死人。

文件IO方面,Qt直接实现了我早先在Java上做了很久的文件内存映射封装,只需要一个mapTo函数。巧的是我的Java封装也是叫mapTo。Qt可以通过模板成员函数直接对返回的指针进行转型,而Java则需要用各种getter和putter,没法直接把映射的内存当成对象来用。Qt可以直接关闭映射,而Java则需要调用gc才能关闭映射。

文件流方面,Qt文件流我还不知道怎么用,事实上C++的文件流都很复杂,导致我一直在用fopen那一套。Java的文件流显然要简单很多。

Qt没有文件锁,Win32文件锁不支持阻塞,Java却支持阻塞(实际上可能是用100ms的睡眠查询实现的)。

HttpClient

Qt的HttpClient必须是窗体工程才能使用,Java没有这种要求。

SQL

Qt比Java少一个获取数据类型的函数,所以Qt查询的结果不包含数据类型信息。QODBC和JDBC的使用方法很像,就是url不一样。

线程

Qt的互斥量要么是不可重入的,要么是不支持等待的。所以我特意用不可重入的互斥量制作了一个可重入又支持等待的可重入锁。Java的可重入锁和同步块是全功能的,不需要设置。

Java的线程池可以用Lambda导入,Qt不支持直接使用Lambda,所以我对Qt又做了一次封装。Qt的开发者可能不太熟悉C++,他们提供了一大堆看起来很落伍的Concurrent方法来用函数指针启动线程。也可能是这种东西太多了,导致我没找到我想要的。其实做一个使用Lambda的函数很简单,只需要用Function<void (*)(void)> run做形参就可以了。可以这么声明void execute(Function<void (*)(void)> const & run);但是我没找到与这个类似的东西。

Qt一启动就有一个默认的线程池,默认线程数为CPU核心数,这个线程池不可以随便关闭,否则会导致未知的错误。Java没有默认线程池,也没有默认的创建线程池的参数。除了不支持Lambda和多了一个默认线程池,Qt和Java在多线程的编程上非常相似。

Qt把一个对象传给另一个线程需要程序员自己分析什么时候传引用什么时候传值,这是因为Qt的一些对象是栈中的对象,栈指针不能跨线程使用。Java只传引用,因为Java对象都是堆中的,堆指针可以给多个线程使用。对于多线程使用同一个对象,new一个对象要比在栈中传递更高效,Qt需要智能指针的帮助,否则很难确定由哪个线程来delete对象。智能指针是一个装饰者,它可以保证对象及时释放,而Java不需要这个装饰者,代码更简洁,java对象一般不会在不用的时候立即释放。

Qt不光没有并发容器,它的对象不是线程安全的,甚至是线程危险的。在不同线程使用Qt对象,或在非Qt线程使用Qt对象都可能出现未知故障。而Java对象如果不是线程安全的,也允许在不同线程使用。

架构能力

Qt本身是一种架构,要想在架构上再来一重架构是很麻烦的,但比从Win32开始要好得多。Java提供的是一系列便于使用的API,并没有很强的关联性,几乎所有功能都可以自由放置,相比Qt,Java更容易设计自己的架构。Java可以轻易设计独立的工具组件。Qt就比较麻烦,因为有时候不得不使用信号槽,有些功能是依赖窗体程序的,无法用于控制台程序。

Java可以作为一个子进程来使用,虽然比C++控制台程序麻烦一点。Java和标准C++的管道有着相似的特性,就是可以立即输出。这一点C的管道就做不到。Qt的管道比较奇葩,可能是建立在C的管道上的,无法立即输出,导致Qt无法使用管道与父进程通信。

而作为一个动态库,Qt的dll和Java的class一样不适合给其它编程语言使用,虽然Qt那个也叫dll。

动态加载

Qt可以直接动态加载标准C接口的动态库,就像变通函数一样。Java加载标准C动态库相对麻烦,需要JNA,而且不能直接使用指针。

Java支持反射,class文件也不需要依赖硬盘,能够用反射来设计解释器。Qt没有反射,如果要用动态库来代替反射,调试会非常麻烦,功能也不完整。Qt开发中为了调试方便,只能使用查表法,也就是对每一个字符串都映射一个函数指针,这将导致解释器难以自由扩展,也难以维护。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!