JAVA面試4

1、HashMap、HashTable、ConcurrentHashMap的區別

    (關於HashMap的分析,在第三篇總結《Java筆試面試題整理第三波》中的hashCode有分析,同樣在這篇中有關於Java容器的介紹。HashMap和HashTable都屬於Map類集合。

    HashMap和HashTable都實現了Map接口,裏面存放的元素不保證有序,並且不存在相同元素

區別(線程安全和保存值是否爲null方面):
   (1) HashMap和HashTable在功能上基本相同,但HashMap是線程不安全的,HashTable是線程安全的

HashMap的put源碼如下:    
[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. public V put(K key, V value) {  
  2.         if (table == EMPTY_TABLE) {  
  3.             inflateTable(threshold);  
  4.         }  
  5.         if (key == null)  
  6.             return putForNullKey(value);    //說明key和value值都是可以爲null  
  7.         int hash = hash(key);  
  8.         int i = indexFor(hash, table.length);  
  9.         for (Entry<K,V> e = table[i]; e != null; e = e.next) {  
  10.             Object k;  
  11.             if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
  12.                 V oldValue = e.value;  
  13.                 e.value = value;  
  14.                 e.recordAccess(this);  
  15.                 return oldValue;  
  16.             }  
  17.         }  
  18.   
  19.         modCount++;  
  20.         addEntry(hash, key, value, i);  
  21.         return null;  
  22.     }  
(2)可以看到,HashMap的key和value都是可以爲null的,當get()方法返回null值時,HashMap中可能存在某個key,只不過該key值對應的value爲null,也有可能是HashM中不存在該key,所以不能使用get()==null來判斷是否存在某個key值,對於HashMap和HashTable,提供了containsKey()方法來判斷是否存在某個key。

HashTable的put源碼如下:    
[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. public synchronized V put(K key, V value) {  
  2.         // Make sure the value is not null  
  3.         if (value == null) {    //當value==null的時候,會拋出異常  
  4.             throw new NullPointerException();  
  5.         }  
  6.   
  7.         // Makes sure the key is not already in the hashtable.  
  8.         Entry tab[] = table;  
  9.         int hash = hash(key);  
  10.         int index = (hash & 0x7FFFFFFF) % tab.length;  
  11.         for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {  
  12.             if ((e.hash == hash) && e.key.equals(key)) {  
  13.                 V old = e.value;  
  14.                 e.value = value;  
  15.                 return old;  
  16.             }  
  17.         }  
  18.   
  19.         modCount++;  
  20.         if (count >= threshold) {  
  21.             // Rehash the table if the threshold is exceeded  
  22.             rehash();  
  23.   
  24.             tab = table;  
  25.             hash = hash(key);  
  26.             index = (hash & 0x7FFFFFFF) % tab.length;  
  27.         }  
  28.   
  29.         // Creates the new entry.  
  30.         Entry<K,V> e = tab[index];  
  31.         tab[index] = new Entry<>(hash, key, value, e);  
  32.         count++;  
  33.         return null;  
  34.     }  
  
(3)HashTable是不允許key和value爲null的。HashTable中的方法大部分是同步的,因此HashTable是線程安全的

拓展:
   (1) 影響HashMap(或HashTable)性能的兩個因素:初始容量和load factor;
        HashMap中有如下描述:        When the number of entries in the hash table exceeds the product of the load factor and the current capacity, 
the hash table is <i>rehashed</i> (that is, internal data structures are rebuilt) 
        當我們Hash表中數據記錄的大小超過當前容量,Hash表會進行rehash操作,其實就是自動擴容,這種操作一般會比較耗時。所以當我們能夠預估Hash表大小時,在初始化的時候就儘量指定初始容量,避免中途Hash表重新擴容操作,如:      
[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. HashMap<String, Integer> map = new HashMap<String, Integer>(20);  
        
(類似可以指定容量的還有ArrayList、Vector)

   (2)使用選擇上,當我們需要保證線程安全,HashTable優先選擇。當我們程序本身就是線程安全的,HashMap是優先選擇。
        其實HashTable也只是保證在數據結構層面上的同步,對於整個程序還是需要進行多線程併發控制;在JDK後期版本中,對於HashMap,可以通過Collections獲得同步的HashMap;如下:
        Map m = Collections.synchronizedMap(new HashMap(...));
        這種方式獲得了具有同步能力的HashMap。        

    (3)在JDK1.5以後,出現了ConcurrentHashMap,它可以很好地解決在併發程序中使用HashMap的問題,ConcurrentHashMap和HashTable功能很像,不允許爲null的key或value,但它不是通過給方法加synchronized方法進行併發控制的。
     ConcurrentHashMap中使用分段鎖技術Segment,將數據分成一段一段的存儲,然後給每一段數據配一把鎖,當一個線程佔用鎖訪問其中一個段數據的時候,其他段的數據也能被其他線程訪問,能夠實現真正的併發訪問。效率也比HashTable好的多。

關於ConcurrentHashMap具體可以參考

2、TreeMap、HashMap、LinkedHashMap的區別

    關於Map集合,前面幾篇都有講過,可以去回顧一下。而TreeMap、HashMap、LinkedHashMap都是Map的一些具體實現類,其關係圖如下:

其中,HashMap和HashTable主要區別在線程安全方面和存儲null值方面。HashMap前面討論的已經比較多了,下面說說LinkedHashMap和TreeMap。
(1)LinkedHashMap保存了數據的插入順序,底層是通過一個雙鏈表的數據結構來維持這個插入順序的。key和value都可以爲null
(2)TreeMap實現了SortMap接口,它保存的記錄是根據鍵值key排序,默認是按key升序排列。也可以指定排序的Comparator。

HashMap、LinkedHashMap和TreeMap都是線程不安全的,HashTable是線程安全的。提供兩種遍歷Map的方法如下:
(1)推薦方式:     
[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. Map<String, Integer> map = new HashMap<String, Integer>(20);  
  2.         for(Map.Entry<String, Integer> entry : map.entrySet()){    //直接遍歷出Entry  
  3.             System.out.println("key-->"+entry.getKey()+",value-->"+m.get(entry.getValue()));  
  4.         }  
        這種方式相當於首先通過Set<Map.Entry<String,Integer>> set =  map.entrySet();方式拿到Set集合,而Set集合是可以通過foreach的方式遍歷的。

(2) 普通方式:     
[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. Map<String, Integer> map = new HashMap<String, Integer>(20);  
  2.         Iterator<String> keySet = map.keySet().iterator();    //遍歷Hash表中的key值集合,通過key獲取value  
  3.         while(keySet .hasNext()){  
  4.             Object key = keySet .next();  
  5.             System.out.println("key-->"+key+",value-->"+m.get(key));  
  6.         }  

3、Collection包結構,與Collections的區別。

Collection的包結構如下:

Statck類爲Vector的子類。由於Collection類繼承Iterable類,所以,所有Collection的實現類都可以通過foreach的方式進行遍歷。

Collections是針對集合類的一個幫助類。提供了一系列靜態方法實現對各種集合的搜索、排序、線程完全化等操作。 
當於對Array進行類似操作的類——Arrays。 
如,Collections.max(Collection coll); 取coll中最大的元素。 
       Collections.sort(List list); 對list中元素排序 

4、OOM你遇到過哪些情況,SOF你遇到過哪些情況

    OOMOutOfMemoryError異常
    即內存溢出,是指程序在申請內存時,沒有足夠的空間供其使用,出現了Out Of Memory,也就是要求分配的內存超出了系統能給你的,系統不能滿足需求,於是產生溢出。
    內存溢出分爲上溢下溢比方說棧,棧滿時再做進棧必定產生空間溢出,叫上溢,棧空時再做退棧也產生空間溢出,稱爲下溢。
    
    有時候內存泄露會導致內存溢出,所謂內存泄露(memory leak),是指程序在申請內存後,無法釋放已申請的內存空間,一次內存泄露危害可以忽略,但內存泄露堆積後果很嚴重,無論多少內存,遲早會被佔光,舉個例子,就是說系統的籃子(內存)是有限的,而你申請了一個籃子,拿到之後沒有歸還(忘記還了或是丟了),於是造成一次內存泄漏。在你需要用籃子的時候,又去申請,如此反覆,最終系統的籃子無法滿足你的需求,最終會由內存泄漏造成內存溢出

    遇到的OOM:
    (1)Java Heap 溢出
    Java堆用於存儲對象實例,我們只要不斷的創建對象,而又沒有及時回收這些對象(即內存泄漏),就會在對象數量達到最大堆容量限制後產生內存溢出異常。
    (2)方法區溢出
   方法區用於存放Class的相關信息,如類名、訪問修飾符、常量池、字段描述、方法描述等。

異常信息:Java.lang.OutOfMemoryError:PermGen space

方法區溢出也是一種常見的內存溢出異常,一個類如果要被垃圾收集器回收,判定條件是很苛刻的。在經常動態生成大量Class的應用中,要特別注意這點。


SOF:StackOverflow(堆棧溢出)
    當應用程序遞歸太深而發生堆棧溢出時,拋出該錯誤。因爲棧一般默認爲1-2m,一旦出現死循環或者是大量的遞歸調用,在不斷的壓棧過程中,造成棧容量超過1m而導致溢出。
    棧溢出的原因:
    (1)遞歸調用
    (2)大量循環或死循環
    (3)全局變量是否過多
    (4)數組、List、Map數據過大

OOM在Android開發中出現比較多:
   場景有: 加載的圖片太多或圖片過大時、分配特大的數組、內存相應資源過多沒有來不及釋放等。

解決方法
    (1)在內存引用上做處理
        軟引用是主要用於內存敏感的高速緩存。在jvm報告內存不足之前會清除所有的軟引用,這樣以來gc就有可能收集軟可及的對象,可能解決內存吃緊問題,避免內存溢出。什麼時候會被收集取決於gc的算法和gc運行時可用內存的大小。
    (2)對圖片做邊界壓縮,配合軟引用使用
    (3)顯示的調用GC來回收內存,如:
        if(bitmapObject.isRecycled()==false//如果沒有回收  
       bitmapObject.recycle();
  (4)優化Dalvik虛擬機的堆內存分配
            》增強程序堆內存的處理效率    
        //在程序onCreate時就可以調用 即可
        privatefinalstaticfloat TARGET_HEAP_UTILIZATION = 0.75f; 
        VMRuntime.getRuntime().setTargetHeapUtilization(TARGET_HEAP_UTILIZATION);

            》設置堆內存的大小
            privatefinalstaticintCWJ_HEAP_SIZE = 610241024;
      //設置最小heap內存爲6MB大小
      VMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE);

    (5)用LruCache 和 AsyncTask<>解決
        從cache中去取Bitmap,如果取到Bitmap,就直接把這個Bitmap設置到ImageView上面。
  如果緩存中不存在,那麼啓動一個task去加載(可能從文件來,也可能從網絡)。

5、Java面向對象的三個特徵與含義,多態的實現方式

Java中兩個非常重要的概念:類和對象。類可以看做是一個模板,描述了一類對象的屬性和行爲;而對象是類的一個具體實現。Java面向對象的三大基本特徵:
(1)封裝
    屬性用來描述同一類事物的特徵,行爲用來描述同一類事物可做的一些操作。封裝就是把屬於同一類事物的共性(屬性和行爲)歸到一個類中,只保留有限的接口和方法與外部進行交互,避免了外界對對象內部屬性的破壞。Java中使用訪問控制符來保護對類、屬性、方法的訪問。
(2)繼承
    子類通過這種方式來接收父類所有的非private的屬性和方法(構造方法除外)。這裏的接收是直接擁有的意思,即可以直接使用父類字段和方法,因此,繼承相當於“擴展”,子類在擁有了父類的屬性和特徵後,可以專心實現自己特有的功能。
    (構造方法不能被繼承,因爲在創建子類時,會先去自動“調用”父類的構造方法,如果真的需要子類構造函數特殊的形式,子類直接修改或重載自己的構造函數就好了。
(3)多態
    多態是程序在運行的過程中,同一種類型在不同的條件下表現不同的結果。比如:
        Animal  a = new Dog();    // 子類對象當做父類對象來使用運行時,根據對象的實際類型去找子類覆蓋之後的方法

多態實現方式:
    (1)設計時多態,通過方法的重載實現多態;
    (2)運行時多態,通過重寫父類或接口的方法實現運行時多態;

6、interface與abstract類的區別

    abstract class 只能被繼承extends,體現的是一種繼承關係,而根據繼承的特徵,有繼承關係的子類和父類應該是一種“is-a”的關係,也即兩者在本質上應該是相同的(有共同的屬性特徵)。
    interface 是用來實現的implements,它並不要求實現者和interface之間在本質上相同,是一種“like-a”的關係,interface只是定義了一系列的約定而已(實現者表示願意遵守這些約定)。所以一個類可以去實現多個interface(即該類遵守了多種約定)。
    很多情況下interface和abstract都能滿足我們要求,在我們選擇用abstract火interface的時候,儘量符合上面的要求,即如果兩者間本質是一樣的,是一種“is-a”的關係,儘量用abstract,當兩者之間本質不同只是簡單的約定行爲的話,可以選擇interface。
特點:
(1)abstract類其實和普通類一樣,擁有有自己的數據成員和方法,只不過abstract類裏面可以定義抽象abstract的方法(聲明爲abstract的類也可以不定義abstract的方法,直接當做普通類使用,但是這樣就失去了抽象類的意義)。
(2)一個類中聲明瞭abstract的方法,該類必須聲明爲abstract類
(3)interface中只能定義常量和抽象方法。在接口中,我們定義的變量默認爲public static final類型,所以不可以在顯示類中修改interface中的變量;定義的方法默認爲public abstract,其中abstract可以不明確寫出。

7、static class 與non static class的區別

static class--靜態內部類,non static class--非靜態內部類,即普通內部類

普通內部類:
    內部類可以直接使用外部類的所有變量(包括private、靜態變量、普通變量),這也是內部類的主要優點(不用生成外部類對象而直接使用外部類變量)。如下例子:
[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. public class OutClass {  
  2.     private String mName = "lly";  
  3.     static int mAge = 12;  
  4.   
  5.     class InnerClass{  
  6.         String name;  
  7.         int age;  
  8.         private void getName(){  
  9.             name = mName;  
  10.             age = mAge;  
  11.             System.out.println("name="+name+",age="+age);  
  12.         }  
  13.     }  
  14.    
  15.     public static void main(String[] args) {  
  16.         //第一種初始化內部類方法  
  17.         OutClass.InnerClass innerClass = new OutClass().new InnerClass();  
  18.         innerClass.getName();  
  19.         //第二種初始化內部類方法  
  20.         OutClass out = new OutClass();  
  21.         InnerClass in = out.new InnerClass();  
  22.         in.getName();  
  23.     }  
  24. }  
輸出:name=lly,age=12

可以看到,內部類裏面可以直接訪問外部類的靜態和非靜態變量,包括private變量。在內部類中,我們也可以通過外部類.this.變量名的方式訪問外部類變量,如:name = OutClass.this.mName; 
內部類的初始化依賴於外部類,只有外部類初始化出來了,內部類才能夠初始化

私有內部類(包括私有靜態內部類和私有非靜態內部類):
如果一個內部類只希望被外部類中的方法操作,那隻要給該內部類加上private修飾,聲明爲private 的內部類只能在外部類中使用,不能在別的類中new出來。如上private class InnerClass{...},此時只能在OutClass類中使用。

靜態內部類:
靜態內部類只能訪問外部類中的靜態變量。如下:
[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. public class OutClass {  
  2.     private String mName = "lly";  
  3.     static int mAge = 12;  
  4.   
  5.     static class StaticClass{  
  6.         String name = "lly2";  
  7.         int age;  
  8.         private void getName(){  
  9. //            name = mName;    //不能引用外部類的非靜態成員變量  
  10.             age = mAge;  
  11.             System.out.println("name="+name+",age="+age);  
  12.         }  
  13.     }  
  14.    
  15.     public static void main(String[] args) {  
  16.         //第一種初始化靜態內部類方法  
  17.        OutClass.StaticClass staticClass = new OutClass.StaticClass();  
  18.         staticClass.getName();  
  19.         //或者直接使用靜態內部類初始化  
  20.         StaticClass staticClass2 = new StaticClass();  
  21.         staticClass2.getName();  
  22.     }  
  23. }  
輸出:name=lly2,age=12

可以看到,靜態內部類只能訪問外部類中的靜態變量,靜態內部類的初始化不依賴於外部類,由於是static,類似於方法使用,OutClass.StaticClass是一個整體。

匿名內部類:
匿名內部類主要是針對抽象類接口的具體實現。在Android的監聽事件中用的很多。如:    
[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. textView.setOnClickListener(new View.OnClickListener(){    //OnClickListener爲一個接口interface  
  2.         public void onClick(View v){  
  3.             ...  
  4.         }  
  5.     });  

對於抽象類:
[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. public abstract class Animal {  
  2.     public abstract void getColor();  
  3. }  
  4.       
  5. public class Dog{  
  6.     public static void main(String[] args) {  
  7.         Animal dog = new Animal() {  
  8.             @Override  
  9.             public void getColor() {  
  10.                 System.out.println("黑色");  
  11.             }  
  12.         };  
  13.         dog.getColor();  
  14.     }  
  15. }  
輸出:黑色

對於接口類似,只需要把abstract class 改爲interface即可。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章