網傳此書不錯,最近用了幾天的時間,將其讀完,發現其中有不少錯誤,在此做一總結,希望給后續的讀者做一參考。
關于GET和POST
在書中的第2章,第2.1.1小節,P19中有如下描述:
此處描述欠準確,GET和POST的本質區別是冪等性不同,關于冪等性的定義,請自行google,除此之外,HTTP協議還規定了HEAD,PUT,DELETE,OPTIONS等一些其他的方法。
關于時間復雜度
在書中的第2章,第2.1.3小節,P26中有如下描述:
作者認為,HashMap的查找操作的時間復雜度為o(1)。此處小o符號的使用是錯誤的。在算法導論中,大O與小o符號的含義是嚴格定義的。
- 如果一個算法的時間復雜度為O(n),意味著該算法在最壞情況下,其運行時間最多是輸入規模的線性函數,而不會是二次或者更高次的函數。
- 如果一個算法的時間復雜度為o(n),意味著該算法在最壞情況下,其運行時間嚴格小于輸入規模的線性函數(也就是常數級的時間復雜度,即O(1))。
因此,可以把大O符號看做“<=”,把小o符號看做“<”。從理論上講,我們可以認為HashMap的查找操作的時間復雜度為O(1)(實際上會大于O(1),因為要加上解決沖突花費的時間),即常數級的時間復雜度,而不是o(1),因為小于常數級時間復雜度的算法并不存在。
在后續的章節中,還有一處這樣的錯誤,在此不重復提及。
當然,如果作者在此并不使用算法導論中對上述符號的定義,那么應該在提及該符號時闡明其含義。
關于HTTP和TCP
在書中的第3章,第3.2.1小節,P58中有如下描述:
上述描述中,將TCP與HTTP相互比較,好像在用HTTP的時候就可以不用TCP,關于訪問速度的對比,真實原因也沒闡明。
實際上,HTTP屬于應用層協議,而TCP屬于傳輸層協議,所有HTTP請求都會通過TCP實現,HTTP請求慢的原因在于,對于每一個請求都會重建一個TCP連接,而建TCP連接是一個相對耗時的操作,當然也可以使用一個TCP連接進行多次HTTP請求(即所謂的長連接),在實際的項目中,基本上也都是這樣做的。
關于Java序列化
在書中的第3章,第3.5.3小節,為了解決全局變量在App重啟之后,其成員變量為空的問題,作者提出了采用Java序列化把全局變量序列化到本地的方法。其中有如下描述:
作者關于Java序列化與實例控制(Instance Control)的關系描述是正確的,除了上圖中畫紅線部分。為了保持單例的性質,需要在readResolve方法中返回已有的實例,但是Instance Control并不需要readObject方法的參與,更不需要實現Cloneable接口。
在其全局變量的實現中也有問題,請看以下代碼段:
- 首先,看UserBean user這個成員變量,UserBean在后文中提到,實現了Serializable接口。作者依靠輔助方法Utils.saveObject將整個單例全局變量序列化到本地,并在反序列化時初始化成員變量user。然而此處存在一個安全漏洞,如果可以修改序列化的文件,黑客可以輕而易舉的創建一個新的全局變量(單例模式)的實例(具體如何攻擊,請參考《Effective Java》 Item 77)。如果將user變量聲明為transient,雖然不會再有安全問題,但是user也不會被實例化到本地。
實際上,Java 1.5之后,對于做實例控制的序列化單例而言,并沒有一個好的方法,如果真要這樣做,請使用枚舉類型來實現單例,其反序列化過程的實例控制以及安全性都是由JVM來保證的,不會有任何問題。(關于單例的話題,請參考我之前寫過的一篇文章:如何以正確的姿勢寫單例) - 其次,在readResolve中,作者調用了this.clone方法,其實并不需要調這個方法,因為實例控制不依賴于Cloneable接口,也沒有任何關系。
關于比較器
在第6章,6.1.8小節中,關于比較器,作者有如下描述:
排序算法按照是否是通過元素之間的比較來劃分,可以分為兩類排序算法,一類是比較排序算法,包括歸并排序,插入排序,冒泡排序,快速排序,堆排序等等;另一類是非比較排序算法,包括計數排序,桶排序等。任意一個比較排序算法在最壞情況下,其時間復雜度的下界為Ω(nlgn)(Ω符號請參考算法導論)。
比較器是比較排序算法中極其重要的一個工具,函數或者算子。有了比較器,比較排序算法就可以對輸入的元素集合進行排序。
那Java中為什么會定義Comparator這個接口,并要求開發者在實現該接口時,其返回值必須符合文檔給出的規范約束?因為,對于自定義的類,只有開發者才知道如何定義該類兩個實例之間的順序關系,返回值也只有符合文檔規范,比較排序算法才能正常工作。所以,作者說Comparator是插入排序與歸并排序相結合的產物是錯誤的(作者參考了一篇錯誤的文檔,得出了一個錯誤的結論)。
再說說時間復雜度,插入排序的時間復雜度為O(n2) ,歸并排序的時間復雜度為O(nlgn),冒泡排序的時間復雜度為O(n2)。在JDK 1.7的排序算法的實現是TimSort,TimSort在輸入規模較大的時候選擇歸并排序算法進行,而在輸入規模較小的時候選擇插入排序算法進行。因為在輸入規模較小時,插入排序的時間復雜度雖然為O(n2),由于其系數較小,運行時間反而比歸并排序小。
關于Context和startActivity
在第6章,6.2.4小節中,作者說使用除Activity之外的Context實例進行startActivity時,需要增加Intent.FLAG_ACTIVITY_NEW_TASK才能不報錯,但是沒說明具體原因。
當使用Activity的實例來啟動一個新的Activity時,系統知道新的Activity將加入到哪個Task當中去,因此是不需要指定一個NEW_TASK的標志位的。但是當使用Application或者Service的實例來啟動一個新的Activity時,由于Application和Service的實例并不存在與一個特定的系統Task中,因此系統并不知道如何將新啟動的Activity加入到哪個Task當中,因此要求開發者強制增加一個NEW_TASK的標志位。
一些書寫錯誤
在第6章,6.1.1小節中,P108,空指針異常應該是NullPointerException,而不是NullPointException。
6.9.1小節中,P163,OOM異常類名為OutOfMemoryError,而不是OutOfMemoryException。
此外,本書中存在很多筆誤,錯誤單詞,還有一些說法不恰當或者問題不太大的地方,本文就不一一列舉了。總體而言,此書對于新手,還是有些營養價值的,但是要注意辨別。。。