1. 程式人生 > >Java筆試面試題整理第四波

Java筆試面試題整理第四波

本系列整理Java相關的筆試面試知識點,其他幾篇文章如下:

1、HashMap、HashTable、ConcurrentHashMap的區別

    (關於HashMap的分析,在第三篇總結《》中的hashCode有分析,同樣在這篇中有關於Java容器的介紹。HashMap和HashTable都屬於Map類集合。    HashMap和HashTable都實現了Map介面,裡面存放的元素不保證有序,並且不存在相同元素
區別(執行緒安全和儲存值是否為null方面):   (1) HashMap和HashTable在功能上基本相同,但HashMap是執行緒不安全的,HashTable是執行緒安全的
HashMap的put原始碼如下:
public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);    //說明key和value值都是可以為null
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }
(2)可以看到,HashMap的key和value都是可以為null的,當get()方法返回null值時,HashMap中可能存在某個key,只不過該key值對應的value為null,也有可能是HashM中不存在該key,所以不能使用get()==null來判斷是否存在某個key值,對於HashMap和HashTable,提供了containsKey()方法來判斷是否存在某個key。HashTable的put原始碼如下:
public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) {    //當value==null的時候,會丟擲異常
            throw new NullPointerException();
        }

        // Makes sure the key is not already in the hashtable.
        Entry tab[] = table;
        int hash = hash(key);
        int index = (hash & 0x7FFFFFFF) % tab.length;
        for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                V old = e.value;
                e.value = value;
                return old;
            }
        }

        modCount++;
        if (count >= threshold) {
            // Rehash the table if the threshold is exceeded
            rehash();

            tab = table;
            hash = hash(key);
            index = (hash & 0x7FFFFFFF) % tab.length;
        }

        // Creates the new entry.
        Entry<K,V> e = tab[index];
        tab[index] = new Entry<>(hash, key, value, e);
        count++;
        return null;
    }
(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表重新擴容操作,如:
 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)推薦方式:
Map<String, Integer> map = new HashMap<String, Integer>(20);
        for(Map.Entry<String, Integer> entry : map.entrySet()){    //直接遍歷出Entry
            System.out.println("key-->"+entry.getKey()+",value-->"+m.get(entry.getValue()));
        }
        這種方式相當於首先通過Set<Map.Entry<String,Integer>> set =  map.entrySet();方式拿到Set集合,而Set集合是可以通過foreach的方式遍歷的。(2) 普通方式:
Map<String, Integer> map = new HashMap<String, Integer>(20);
        Iterator<String> keySet = map.keySet().iterator();    //遍歷Hash表中的key值集合,通過key獲取value
        while(keySet .hasNext()){
            Object key = keySet .next();
            System.out.println("key-->"+key+",value-->"+m.get(key));
        }

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、靜態變數、普通變數),這也是內部類的主要優點(不用生成外部類物件而直接使用外部類變數)。如下例子:
public class OutClass {
    private String mName = "lly";
    static int mAge = 12;

    class InnerClass{
        String name;
        int age;
        private void getName(){
            name = mName;
            age = mAge;
            System.out.println("name="+name+",age="+age);
        }
    }
 
    public static void main(String[] args) {
        //第一種初始化內部類方法
        OutClass.InnerClass innerClass = new OutClass().new InnerClass();
        innerClass.getName();
        //第二種初始化內部類方法
        OutClass out = new OutClass();
        InnerClass in = out.new InnerClass();
        in.getName();
    }
}
輸出:name=lly,age=12可以看到,內部類裡面可以直接訪問外部類的靜態和非靜態變數,包括private變數。在內部類中,我們也可以通過外部類.this.變數名的方式訪問外部類變數,如:name = OutClass.this.mName; 內部類的初始化依賴於外部類,只有外部類初始化出來了,內部類才能夠初始化私有內部類(包括私有靜態內部類和私有非靜態內部類):如果一個內部類只希望被外部類中的方法操作,那隻要給該內部類加上private修飾,宣告為private 的內部類只能在外部類中使用,不能在別的類中new出來。如上private class InnerClass{...},此時只能在OutClass類中使用。靜態內部類:靜態內部類只能訪問外部類中的靜態變數。如下:
public class OutClass {
    private String mName = "lly";
    static int mAge = 12;

    static class StaticClass{
        String name = "lly2";
        int age;
        private void getName(){
//            name = mName;    //不能引用外部類的非靜態成員變數
            age = mAge;
            System.out.println("name="+name+",age="+age);
        }
    }
 
    public static void main(String[] args) {
        //第一種初始化靜態內部類方法
       OutClass.StaticClass staticClass = new OutClass.StaticClass();
        staticClass.getName();
        //或者直接使用靜態內部類初始化
        StaticClass staticClass2 = new StaticClass();
        staticClass2.getName();
    }
}
輸出:name=lly2,age=12可以看到,靜態內部類只能訪問外部類中的靜態變數,靜態內部類的初始化不依賴於外部類,由於是static,類似於方法使用,OutClass.StaticClass是一個整體。匿名內部類:匿名內部類主要是針對抽象類介面的具體實現。在Android的監聽事件中用的很多。如:
textView.setOnClickListener(new View.OnClickListener(){    //OnClickListener為一個介面interface
        public void onClick(View v){
            ...
        }
    });
對於抽象類:
public abstract class Animal {
    public abstract void getColor();
}
    
public class Dog{
    public static void main(String[] args) {
        Animal dog = new Animal() {
            @Override
            public void getColor() {
                System.out.println("黑色");
            }
        };
        dog.getColor();
    }
}
輸出:黑色對於介面類似,只需要把abstract class 改為interface即可。