讨论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?;ヂ摼WJavaEE其實不是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好太多了?,F在的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不能把自己的引用直接傳回給自己,而是需要用一個已知的容器來傳遞。
總結
以上是生活随笔為你收集整理的讨论JAVA和QT之争的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python爬取斗鱼图片
- 下一篇: 禁用 WebDAV