2014年9月30日 星期二

【Java】for(;;)和for(:)的效能差異

【我測效能的方式一向是土法煉鋼。】



宣告一個長度在三千以上的矩陣,然後用兩種不同的for迴圈來讀取、操作內容,效能會差距多少?

注意!矩陣長度一定至少要在三千以上,一萬是比較保險的。(對矩陣個別元素的操作或運算內容也別太複雜。)

注意!還可以在for(:)的迴圈內容加個長整數,然後讓長整數一起跟著遞增,讓兩個迴圈的差異變得比較小。

答案是........




一千倍!

for(:)迴圈的速度是for(;;)迴圈的一千倍以上!




但了解技術內容的人都知道for(:)迴圈其實是將矩陣轉換成Iterator,然後用這個物件操作內容。

而且這個內容只是矩陣元素取值出來操作而已!它只是宣告一個變數,然後這個變數的值會是當下正在操作的矩陣元素的值。不論對這個變數做怎樣的操作,矩陣元素的值都不會有變化。

理論上來說,for(:)反而應該要比較慢才對,因為多了個轉換手續。

所以矩陣的長度要夠長,才會看得出差異。如果太短,這個轉換手續造成的效能差異會讓for(:)迴圈看起來比較慢。




如果有在for(:)迴圈內加長整數遞增,效能依然是一千倍!

顯然。遞增運算絕對不是效能差異的關鍵。(這很理所當然。)兩個迴圈的差異,完全在於Iterator的效能。

但差在哪裡呢?操作記憶體的效率?........傳統的for(;;)迴圈操作矩陣可能沒有位址紀錄器,要重覆的讀取迴圈內設置的長整數,還要判斷長整數的執行範圍........不過這是我隨便猜想啦!

如果在換個方法來測試這兩個迴圈的差異,例如也在傳統的for(;;)迴圈中加上一個區域變數,然後用這個區域變數取值後再對這個區域變數做運算操作,效能會差多少呢?




根本沒差!

或許關鍵根本不再Iterator,而是「變化矩陣元素內容」就是會這麼慢。






一般來說長度會到這個等級的矩陣......都是Stream吐出來的基本型別資料串。(自己寫的物件搞到三千多個?...超級資料庫?)

2014年9月17日 星期三

【Android/Java】ListView中使用ArrayAdapter搭配自己設計的物件

一般官方範例只會說到如何使用ArrayAdapter呈現String資料串。

感覺上大家會以為「ArrayAdapter只是把程式中寫在泛型資料格式指定的資料物件塞到XML Layout中的TextView上」。

但其實ArrayAdapter呼叫的是資料物件的「toString()」這個函數。


也就是說:設計師只要把「自己設定的物件」的「toString()」重新複寫就可以丟進ArrayAdapter了!

但剩下的資料呢?自己設定的物件中一定還有其他資料要顯示在ListView中,否則就不需要自定義物件了......

問這個問題的人去跪算盤!

複寫getView(int position, View convertView, ViewParent parent)這個函式就好啦!

講精準一點來說,ArrayAdapter在產生實體(new)的時候,會需要指令XML Layout跟這個Layout中的一個TextView的id。(如果不指定會預設為android.R.id.text1。)複寫的getView中如果寫了super.getView,就不用另外執行這個toString動作。(記得把super.getView傳出的View設為回傳值...記得getView這個函數要設回傳值。)




這個方法可以用在幾乎所有會用到Adapter的地方。

而且資料動態變更(執行notify函數)時,絕對會成功,因為用的是ArrayAdapter的方法。(我實作BaseAdapter產生的物件幾乎沒有成功過。)



話說......Java物件預設的函式到底還有那些?又有哪些功能?......

複寫他們會有些什麼副作用嗎?一般來說「toString()」是回傳物件的class資訊,就這樣複寫掉會有什麼影響呢?.......

有機會再補充啦。

2014年8月27日 星期三

【Java】interface內的static參數(型別為物件,而不是基本數值型別)

挺奇妙的...

在interface內的參數會被強制宣告為final型態,因此boolean/byte/short/int/char/long/float/double/String這些基本型態的參數一旦寫在裡面,就會被「鎖死」不能再進行修改。

但如果這個參數直接是個類別(A)的實作物件(A),雖然不能修改物件(A)、不能重新宣告或指定新的物件(A),但物件(A)內的參數是可以修改的。

最明顯的例子就是就是宣告ArrayList<T>。

實作這個介面的物件依舊可以動態的、彈性的增減ArrayList<T>的內容。



更奇妙的是可以將這個ArrayList宣告為static屬性。

在多個不同(幾乎沒有相似性)的類別中實作帶ArrayList的介面後,修改、增加ArrayList的內容,然後看看內容組成,(只要看數量就夠了,)會發現ArrayList確實只被產生一次。(它是個綁在介面下的static參數,而不是綁在類別中。)



Android中,Service和Activity的關係一直困擾著我的地方是:Activity要如何對Service內的參數做出修正?或引用Service的屬性和函數?

直接掛上ServiceConnection當然是必要動作,但如果是Activity下所屬的Dialog呢?或是設計者將功能獨立成一個物件,為了能夠在多個Activity中使用,所以傳入參數只使用的基本Context?(好爛的例子......但大家應該懂我真正想表達的,其實就是一個問題:「萬一真的不行,萬一真的有必要另闢途徑,萬一我懶到不想這麼做、專案已經複雜到新增一個傳遞參數大家就會因為要修改舊程式碼而爆炸........有沒有別的選擇?」)

將參數打包成一個物件,然後用interface(I)包裝起來再丟給Service去實作/擴充...這樣一來凡是實作/擴充這個interface(I)的類別都可以享有Service下的參數讀取、甚至修改的權限。



我馬上想到的第一個想法就是設計一個ArrayList<Runnable>,任何被丟進去的Runnable都會被Service執行......當然要做基本的數量控制!......這樣就有簡單的「任務控制器」了。.......這概念大家都稱為「任務控制器」吧?

2014年8月22日 星期五

Android的LayoutInflater真是博大精深啊!!!

public View inflate(int resource, ViewGroup root)

這個Inflater裡的函數非常有趣!

「ViewGroup root」帶Null或帶參數...對「int resource」解析的結果會完全不同!



剛剛同事花了半天才發現如果帶「ViewGroup root」,其實回傳的是這個「ViewGroup root」。

一般情況下這沒什麼,但如果「int resource」最外層的ViewGroup是個設計者自行設計的Layout類別,結果Inflater會忽略這個類別,然後只把「int resource」內的ChildView解析出來、加入「ViewGroup root」中。



會不會有點繞口?



詳細一點說明,如果設計者自行設計了一個LinearLayoutA,並且將它用在XML中,而且是包在檔案最外層。

如果帶了個「ViewGroup root」給Inflater,則Inflater會跳過這個LinearLayoutA的XML不去解析。



如果真是這樣,可以做個實驗.......

XML檔最外層是個FrameLayout,但「ViewGroup root」卻是個LinearLayout。

則用一個「FrameLayout frameLayout」參數去接收Inflater的回傳時,肯定會出錯............



等等做實驗!



(十五分鐘後........)

沒錯!實驗結果完全如此!

2014年8月18日 星期一

【Android】決定Dialog的大小和位置

今天的工作內容是「設計一個Dialog,它彈出的位置會隨著呼叫它的Button位置不同,而跟著不同,例如按鈕如果在整個螢幕偏右側,則視窗要在左側,反之則是在右側。」

問題最後算是初步解決了。以下是心得。



簡單來說,先獲得Dialog下的Window物件,然後再用Window物件產生WindowManager。

([Dialog物件].getWindow()可以獲得Window物件。[Window物件].getWindowManager可以獲得.......別問我怎麼獲得Dialog物件!拒絕回答!)

有了WindowManager,就可以進一步獲得這個Dialog的LayoutParams。

(這個LayoutParams可以說是Dialog的LayoutParams嗎?...這個Params的物件路徑明明白白是歸屬在WindowManager下,所以它到底是.........一點都不重要!)



一般的View元件在有LayoutParams後,決定View的大小位置,就不是什麼難事了!

但.........

不管設了什麼值給這個Params,一旦Dialog執行了show()之後,什麼都有可能亂掉!

例如我碰到的情況是「系統會將寬度強制設回-2」。

(系統會強制用預設的-1/-2覆蓋掉我們給予的值!──似乎不是所有人都會有這個困擾!為何只有某些人會碰到、或基本的程序會節外生枝,........抱歉!還在研究中。)

解決方法在於不使用LayoutParams設定位置與大小,而是使用WindowManager的setLayout(int wid, int height)。

(能不能用同樣的方法指定位置?......晚點試驗一下。)


2014年6月28日 星期六

Stream、Port、Ping、Socket

不知道有沒有人試過在瀏覽器的網址輸入欄中輸入「file:///」後面接上某檔案的路徑,然後按下「Enter」,看看會發生什麼事?........

很多檔案其實都可以用這種方式開啟。



因為這就是Stream與Port的概念。



在電腦中,中央對外溝通是藉由Port。

主機板要跟網路溝通?用Port。

主機板要跟列表機溝通?用Port。

主機板要跟硬碟要資料?用Port。

幾乎每個Port都有特定的工作,(或只能執行特定的工作,或是先佔先贏,程式大多會衝突,很多時候就是因為搶Port、或是對Port的設定沒有彈性。)

打入網址,瀏覽器會從Port編號:8080去跟外界網路溝通;如果打入檔案路徑,可能也是從Port編號:8080,但也可能不是,(我不想探究!Java耶!細問這麼多?)

重點是對電腦的中央來說,這都只是資料藉由Stream傳入或傳出而已,數據機跟網路怎麼處理從中央傳入的資料?中央才不管!(或許可以想像哪天真的把網址傳給硬碟,會發生怎樣有趣的事情呢?硬碟會變成變型金剛?)



但真正的問題是:Stream本身只管檢查自己是否可以傳出或傳入資料,能否建立Stream,這完全不是Stream自己可以控管的。

所以有File類別來檢查「檔案路徑是否存在」,所以有各種硬體管理類別來檢查「特定硬體是否存在」。

Socket就是用來檢查「指定的連線IP是否存在、是否可以進行連線」的類別。

網路連線的第一步就是「檢查IP是否正常、正確、可以連線?我在CMD模式下打入[Ping]+[ IP],會收到怎樣的結果?」,但Stream本身都不具備這些功能,這就是我們需要Socket的原因。

所以要清楚意識到Socket並不是用來進行資料傳輸溝通的主體!Stream才是!

雖然沒有Socket就沒有Stream,沒有Stream、Socket只是好看的工具,但這兩者並沒有絕對的依存關係。(.........講是這樣講,但我也不知道怎麼樣不靠Socket建立網路連線。)



學習使用Java Socket,大部分的新手都是死在這個認知上。

能夠清楚、易懂傳達這個認知的文章.......對不起!沒看過。(我也是別人明明白白講給我聽、甚至試範給我看後,才搞懂這一切。)

三張圖,讓人看懂Java Socket真正的用法

Socket的結構、特性......不難查到相關資料跟說明,例如怎樣設定它的Port、怎樣跟特定IP建立聯結、怎樣檢查連線是否成功........

但是實際上會用Socket建立TCP/IP或UDP連線的人很少,要做網路功能,URL和J2EE幾乎把這塊領域吃光光........

怎麼回事?



因為Oracle官方的教學文件本身就是塊爛帳!

要看清楚這塊爛帳,除非老師上課有講清楚、教學文寫者有意識到,不然讀的人最後都會撞死在「為什麼連線建好了卻不能用」、「為什麼連線的效率差到根本無法運作」、「為什麼最後會徹底失去建立Socket和Stream的能力」......上。

把「網際網路通訊原理」丟一邊,Java設計師/工程師其實需要理解到的本來就只有「這樣就可以跟遠端裝置進行通訊」這個概念就好,(而且這不正是「Socket的精神」嗎?)但是Oracle的官方範例僅止於「一條龍式的示範可以丟入怎樣的參數來建立Socekt,建立之後可以優先使用哪些參數或得哪些回傳物件........」但是對於真正重要的Stream使用就幾乎支字未提。

而且我可以保證同樣都是Stream,在網路跟單純的檔案管理上頭,這兩種Sream的使用與操作經驗完全不同。(我現在會這樣說,十年後還是這樣說。「這是一樣的東西啊!」那完全是老鳥的嘴砲與傲慢。)

不管是Oracle,或是網路上、課堂上無數的範例與教學,根本沒想過要讓人搞懂「Java怎麼建立連線環境」的全貌,都是不停丟出片段的知識而已。

(不知道多少初入門的Java設計師死在這上頭,最後只能一把鼻涕一把眼淚的強迫自己接受J2EE的強暴、放棄自己滿腦子本來可以用J2SE實現的應用與設計。真是殺死人的絕境、與欲哭無淚的哀愁啊!)



第一步,建立Socket。
不管是用直接指定IP的方式,或用ServerSocket。
總之,想去「取得」一個Socket。 


 從第一步中獲得的Socket產生兩個InputStream和OutputStream。
可能是Thread中產生兩個全域變數Stream;
或是直接產生兩個Thread裡面帶有Stream變數。

將兩個Stream丟給不同的Thread(不是負責生成Socket的同一個Thread),來進行資料的讀寫操作。

這裡頭牽涉到一些細節,例如負責生成Socket的Thread要怎麼管理已經生成的Socket?

提示:絕對不是馬上關掉它。Stream一樣也不用馬上關閉,除非在測試中發現機器沒辦法同時負荷那麼多Stream(原理...待查中...)否則Stream做好,除非要結束連線......建議:就放著吧。(不然連線的效能拉不起來。)



為何要獨立一個Thread來管理Socket生成?

當機器要能夠具備跟多個對象溝通的能力時,Socket絕對不會只有一組,可能最後要建立多組,特別是使用ServerSocket時,但問題是ServerSocket的avaliable()指令會霸佔住整個Thread,所以對於已經建立好的Socket,如果要保有繼續操作它們的能力,就要用另外獨立的Thread來操作,另外Socket要有建立流程、自然也要有關閉流程,一個Thread中怎麼同時管理多組Socket、操作多組InputStream/OutputStream?(神人會說「有何難?不就幾百行程式碼而已!」........想當那種神人,我就不會開這個部落格、寫這些文章分享這些經驗了。)

領悟力好的人會理解這點,根本不需要我這三張圖,.......但問題是領悟力好的人有多少呢?況且說這些人領悟力好...這些人又有多少有能力自己從底層去寫自己的Socket物件來管理網路連線?........還不提我看到一家規模中上等級的公司開出「Java工程師,懂Socket使用,會管理TCP/IP連線......」的徵人啟事,薪水45K(起跳),結果過了一個月還沒人應徵.......