讨论JAVA和QT之争

这是两种以跨平台为特色的开发方式。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的性能怎么样?

最新的情况是我用MinGW64和Java1.8做了一个纯计算题,一个很神奇的现象是C++纯数学运算只比Java快5%。测试用的代码是几乎一样的,因为测试总时长为30秒左右,JVM启动的时间和动态编译时间可以忽略。这个测试是很公平的,如果程序总时长不到1秒,Java仍然会很慢。如果用MinGW32会比Java1.8的64位版本慢30%~40%。这个现象建议我们别再用32位编译器去编译C++了,因为32位编译器的C++程序已经明显不如Java了。这个测试结果使我决定将程序尽可能用Java实现,现在再去争那5%的优势已经没有意义了。

对于多线程相互联锁的程序,也就是一个任务会分别由多个线程接力执行的情况下,总体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有些控件的尺寸和位置接口会比较匪夷所思,控件的变化也不是实时的,可以尽情地操作。

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就有好几种,UDP又是另一种开发方式。最后,我搞了好几个月才实现了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那十几个G的平台资源里面包含了内置的QODBC,Java的自带资源很少,需要专门到数据库的官网上下载配套的JDBC。但是JDBC的配置也很简单。

线程

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的管道上的,我一开始还以为无法立即输出,其实需要调用一下fflush(stdout)。

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

动态加载

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

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

Lambda

Qt中与Qt无关的C++的部分对Lambda的支持是非常好的,但Qt本身并不使用Lambda,这可能是为了便于在嵌入式环境使用,嵌入式环境不支持太高版本的C++。Qt里很多适合传入Lambda的接口都用了奇怪的方法实现。C++的Lambda可以在中括号里填写想要在Lambda中共享的变量的副本或引用,其实中括号是代表成员变量的,写到中括号里的东西都会变成成员变量,被保存到新建的Lambda对象中。C++的Lambda递归语法很简单,只要把Lambda保存到一个变量再传一个引用就可以递归了。这是因为C++的局部变量会先一步分配内存,然后再初始化。初始化之前,Lambda自身的地址和内存空间已经确定了,在初始化时就可以把这个空间的引用复制到Lambda的成员变量。

Java的Lambda是从JDK8开始的,作为匿名内部类的一种改进语法,它比匿名内部类更适合在反射中使用。由于匿名内部类是保存在独立文件中的,当有名类是由自定义类加载器加载时,匿名内部类中的非public的所有标识符都不能被调用。当然,可以考虑通过完善自己的自定义类加载器来解决,不过很麻烦。Lambda是保存在同一个文件中的嵌入式代码,它与有名类是同时加载的,所以即使类加载器有点问题,Lambda也能正常调用。Java的Lambda可以传入准常量引用,包括声明为final的引用和没有声明为final但只赋值一次的引用。Java的Lambda递归比较麻烦,需要先new一个带有Lambda引用的容器(数组、Collection、Map、乃至包装者类都可以)或者用函数来声明Lambda(其实就是把Lambda变成了函数递归),然后在下一步把Lambda对象传入容器,这样就能在Lambda中通过容器引用来递归。这是因为Java是先初始化对象再分配ID号的。初始化时这个Lambda引用的ID号还无法确定,Java又不能传指针,所以Java的Lambda不能把自己的引用直接传回给自己,而是需要用一个已知的容器来传递。

  • 19
    点赞
  • 59
    收藏
    觉得还不错? 一键收藏
  • 13
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值