1. 程式人生 > >收集的java常見問題與自己的理解

收集的java常見問題與自己的理解



j2se知識體系





------------------------------------------

equals() 與 hashcode()

------------------------------------------



參考網址




========================

作用是什麼?

   >>> hash table用來實現快速查詢的。那麼 hashcode用於快速定位元素,相對於線性查詢,效率比較高。 [KLY 需要把資料結構的內容再進行補充]


 這個又涉及到Map的實現。它從實現上是一個二維的陣列。類似 map[ hashcode() ] [ equals() ]

查詢的過程是,先通過hashcode找到第一維的位置,然後通過equals找到第二維的位置。從而實現元素的定位。可以通過HashMap的get方法,觀察這個過程:


-------------------------------------------------code---begin--------------------------------------------------------

public V get(Object key) {

       if (key == null)

           return getForNullKey();

       int hash = hash(key.hashCode());

       for (Entry<K,V> e = table[indexFor(hash, table.length)];  //定位第一維元素

            e != null;

            e = e.next) { //遍歷第二維的元素

           Object k;

           if (e.hash == hash && ((k = e.key) == key || key.equals(k)))                            

                   return e.value;

       }

       return null;

   }

----------------------------------------------------code---end------------------------------------------------------  




這種查詢的先後順序決定了equals()與hashcode()這兩個方法的關係(相當於實現中的約定):


1. If two objects are equal, then they must have the same hash code.


  如果兩個元素是equal的,那麼他們的必須hashcode相等。


2. If two objects have the same hashcode, they may or may not be equal.

  如果兩個元素的hashcode相等,那麼他們不一定equal。




一個比喻: 可以理解hash表通過一個個的hashcode來表示不同的”桶“,這些桶裡可以放不同的元素。在查詢物件的時候,使用hashcode來查詢到對應的”桶“,然後再通過equals來定位需要的物件。通過這種方式進行查詢,比“把所有物件線性的排列然後一個一個順序比較”的效率要高。




<<<<  常見問題 >>>>



何時需要重寫equals()


    當一個類有自己特有的“邏輯相等”概念(不同於物件身份的概念)。


如何覆寫equals()和hashcode()


覆寫equals方法

1  使用instanceof操作符檢查“實參是否為正確的型別”。

2  對於類中的每一個“關鍵域”,檢查實參中的域與當前物件中對應的域值。

3. 對於非float和double型別的原語型別域,使用==比較;

4  對於物件引用域,遞迴呼叫equals方法;

5  對於float域,使用Float.floatToIntBits(afloat)轉換為int,再使用==比較;

6  對於double域,使用Double.doubleToLongBits(adouble)轉換為int,再使用==比較;

7  對於陣列域,呼叫Arrays.equals方法。


覆寫hashcode


1. 把某個非零常數值,例如17,儲存在int變數result中;

2. 對於物件中每一個關鍵域f(指equals方法中考慮的每一個域):

3, boolean型,計算(f? 0 : 1);

4. byte,char,short型,計算(int);

5. long型,計算(int)(f ^ (f>>>32));

6. float型,計算Float.floatToIntBits(afloat);

7. double型,計算Double.doubleToLongBits(adouble)得到一個long,再執行[2.3];

8. 物件引用,遞迴呼叫它的hashCode方法;

9. 陣列域,對其中每個元素呼叫它的hashCode方法。

10. 將上面計算得到的雜湊碼儲存到int變數c,然後執行result=31*result+c;

11. 返回result。





>>>在儲存資料計算hash地址的時候,我們希望儘量減少有同樣的hash地址,所謂“衝突”。<<<


  31是個神奇的數字,因為任何數n * 31就可以被JVM優化為 (n << 5) -n,移位和減法的操作效率要比乘法的操作效率高的多,對左移現在很多虛擬機器裡面都有做相關優化,並且31只佔用5bits!


參考

[

]


比如 7 * 31 = ( 7 * 32 ) - 7 = (7 * 25) - 7


  2的5次方就相當於左移5位。



這裡String的hashcode使用素數來產生,是一個比較老的技術。基本思想就是素數與其他數相乘,出現重複的概率比較小。[也有其他方法來產生hashcode,而且效率也比較高]


研究人員通過研究發現31這個數,可以使得hash分佈比較均勻,而且衝突比較少。[KLY我覺得應該是通過統計的方式進行研究的]




----------------------

comparable介面與comparator

----------------------





--------------------------------

 java實現淺克隆(shallow clone)與深克隆(deep clone)

--------------------------------


淺克隆:從物件A中複製出物件B 時,只複製A中的物件引用C,導致當B操作對應的物件C時,A中的物件C也發生了改變。  


深克隆: 從物件A中複製出物件B時, A中的引用物件C也被對應的複製了出來D,當B操作物件D時,A中的物件C不會發生變化。



Deep Clone的幾種方法:

<1> 實現 Serializable 介面。


通過 序列化 和 反序列化 來完成深克隆。。



-------------------------------------------------code---begin--------------------------------------------------------

  ByteArrayOutputStream baos = new ByteArrayOutputStream();  

  ObjectOutputStream oos = new ObjectOutputStream(baos);  

   oos.writeObject(src);  

    oos.close();  

  ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());  

   ObjectInputStream ois = new ObjectInputStream(bais);  

   o = ois.readObject();  

   ois.close();  

----------------------------------------------------code---end------------------------------------------------------  



總結:

當克隆的物件只有基本型別,不含引用型別時,可以用淺克隆實現.     

當克隆的物件含有引用型別時,必須使用深克隆實現.




序列化的問題


Serializable

Externalizable



還有其他的實現方法


----------------------------------------------

談談final, finally, finalize的區別

----------------------------------------------


final


意味著它不能再派生出新的子類,不能作為父類被繼承。

因此一個類不能既被宣告為 abstract的,又被宣告為final的。

將變數或方法宣告為final,可以保證它們在使用中不被改變。

被宣告為final的變數必須在宣告時給定初值,而在以後的引用中只能讀取,不可修改。

被宣告為final的方法也同樣只能使用,不能過載.


finally

 異常處理函式中用來執行清除操作。

如果丟擲一個異常時,先會執行捕捉該異常的catch語句中的內容,最後會執行finally語句中的內容。

在實際應用中,主要用來清理執行過程中用到的變數,比如關閉輸入輸出流,關閉資料庫連線之類的。

在finally中巢狀try-catch-finally語句的問題。


try-catch-finally 的處理邏輯是[KLY應該是jvm編譯器編譯的邏輯]:

當exception (Ex1)從一個程式塊丟擲來,那麼就會到達程式塊的外部,這時候,如果外部的程式塊丟擲了一個新的exception(Ex2),那麼之前丟擲的Ex1就“被忽略”了,然後這個Ex2繼續拋到更外層的程式塊中。 當使用 try-catch-finally 來處理的時候,try程式塊中丟擲的異常,會在catch中進行捕捉,然後執行finally程式塊;假設catch中也丟擲了異常,這時,finally依然會執行。

---------------------------------code-begin----------------------------------------------

public class Main {

  public static void main(String args[]) throws Exception {

  try {

System.out.print(1);

          q();

      }

      catch ( Ex1 i ) {

 System.out.print(2);

          throw( new Ex2());

      }

      finally {

System.out.print(3);

          throw( new Ex1() );

      }


  }

  static void q() throws Exception {

      try {

System.out.print(4);

          throw( new Ex1());

      }

      catch( Exception y ) {

 System.out.print(5);

          throw( new Ex1());

      } finally {

 System.out.print(6);

          throw( new Ex2() );

      }

  }

}


class Ex1 extends Exception {}

class Ex2 extends Exception {}

---------------------------------code-end----------------------------------------------

輸出結果:    14563Exception in thread "main" me.exception.Ex1


---------------------------------------------------------------------------------------

開始執行時輸出1,然後就執行q(),try塊中輸入4,丟擲Ex1的異常,由於Ex1是Exception的子類,所以被catch塊捕捉,輸出5,

之後catch塊丟擲Ex1異常,但是沒有對應的try-catch來捕捉它,所以就拋到了外部,由於有finally模組,所以會執行finally塊,輸出6。這時finally塊會丟擲型別為Ex2的異常,那麼之前的catch中丟擲的Ex1異常就”被忽略”了,最終q()方法丟擲的異常是finally塊中丟擲的Ex2。這時,回到了main塊中,q執行完後,丟擲了Ex2型別的異常,但是catch塊只捕捉Ex1型別的異常,那麼catch塊就沒有執行,也就不輸出2,然後就到了main中finally塊中,輸出3,然後丟擲了Ex1型別的異常。







finalize


GC在檢查某個物件沒有任何引用指向它時,會執行的一個方法,但不一定是立即執行。

這個方法只會執行一次。

這個方法是由垃圾收集器在確定這個物件沒有被引用時對這個物件呼叫的。

它是在 Object 類中定義的,因此所有的類都繼承了它。

子類覆蓋 finalize() 方法以整理系統資源或者執行其他清理工作。[KLY 這個用法也不一定準確,因為執行finalize方法之後,GC不一定會立刻把物件清除,這個和具體的GC演算法有關係,這個方法不建議override進行使用]





-----------------------------------------------------

  Java物件的強、軟、弱和虛引用

-----------------------------------------------------


String abc=new String("abc");  //強

SoftReference<String> abcSoftRef=new SoftReference<String>(abc);  //強

WeakReference<String> abcWeakRef = new WeakReference<String>(abc); //強  

abc=null; //軟

abcSoftRef.clear();//弱



軟引用


軟引用是主要用於記憶體敏感的快取記憶體。在jvm報告記憶體不足之前會清除所有的軟引用,這樣以來gc就有可能收集軟可及的物件,可能解決記憶體吃緊問題,避 免記憶體溢位。

具體應用到“資源池”的設計中。


【一個例子】

  僱員資訊查詢系統,應用場景就是使用者有可能查詢之前已經查詢過的資料。就是過一段不確定的時間(從幾秒到幾天),會查詢之前查詢過的資料。

程式的實現方式:

1> 把查詢過的僱員資訊都放到記憶體裡。

2> 每次使用者查詢時,都重新從資料庫中讀取資訊,然後再生成對應的java物件,查詢完畢後,就刪除該物件。

第一種方法很顯然會浪費大量的記憶體,造成記憶體溢位。

第二種方法執行效能很低,從訪問檔案、訪問網路資源、查詢資料庫等操作,都是影響系統性能的重要因素。而且每次都會生成新的java物件,如果之前查詢的資料還在記憶體裡面,還沒有被GC回收,也用不了。

比較理想的場景是:

  使用者查詢資料時,程式先去看看記憶體裡有沒有,如果沒有再去其他地方(檔案、資料庫、網路資源等)拿。 而程式為了防止記憶體溢位,會定期的把記憶體裡當前沒有使用到的物件進行回收,保持系統的穩定性。


這個時候,就可以使用SoftReference來實現對應的需求。






-----------------------------------------------------------------------------------------------

在J2EE的bean設計時我們對有些bean需要實現Serializable,為什麼?

-----------------------------------------------------------------------------------------------


實現了Serializable介面的物件,可將它們轉換成一系列位元組,並可在以後完全恢復回原來的樣子。這一過程亦可通過網路進行。這意味著序列化機制能自動補償作業系統間的差異。換句話說,可以先在Windows機器上建立一個物件,對其序列化,然後通過網路發給一臺Unix機器,然後在那裡準確無誤地重新“裝配”。不必關心資料在不同機器上如何表示,也不必關心位元組的順序或者其他任何細節。