天天看點

Java 面試知識點解析(一)——基礎知識篇

  • 前言:

在遨遊了一番 Java Web 的世界之後,發現了自己的一些缺失,是以就着一篇深度好文:知名網際網路公司校招 Java 開發崗面試知識點解析 ,來好好的對 Java 知識點進行複習和學習一番,大部分内容參照自這一篇文章,有一些自己補充的,也算是重新學習一下 Java 吧。

(一)Java 基礎知識點

1)面向對象的特性有哪些?

答:封裝、繼承和多态(應要多算一個那就是抽象)

  • 封裝是指将對象的實作細節隐藏起來,然後通過公共的方法來向外暴露出該對象的功能。

    但封裝不僅僅是 private + getter/setter ,使用封裝可以對 setter 進行更深層次的定制,例如你可以對執行方法的對象做規定,也可以對資料做一定的要求,還可以做類型轉換等等。使用封裝不僅僅安全,更可以簡化操作。(封裝擴充閱讀:oc面向對象三大特性之一 <封裝>)

  • 繼承是面向對象實作軟體複用的重要手段,當子類繼承父類後,子類是一種特殊的父類,能直接或間接獲得父類裡的成員。

    繼承的缺點:1)繼承是一種強耦合關系,父類變子類也必須變;2)繼承破壞了封裝,對于父類而言,它的實作細節對子類來說都是透明的。

  • 多态簡而言之就是同一個行為具有多個不同表現形式或形态的能力。

    比如說,有一杯水,我不知道它是溫的、冰的還是燙的,但是我一摸我就知道了,我摸水杯的這個動作,對于不同溫度的水,就會得到不同的結果,這就是多态。

    多态的條件:1)繼承;2)重寫;3)向上轉型。

    多态的好處:當把不同的子類對象都當作父類類型來看,可以屏蔽不同子類對象之間的實作差異,進而寫出通用的代碼達到通用程式設計,以适應需求的不斷變化。(多态擴充閱讀:重新認識java(五) ---- 面向對象之多态(向上轉型與向下轉型))

  • 抽象是指從特定的角度出發,從已經存在的一些事物中抽取我們所關注的特性、行為,進而形成一個新的事物的思維過程,是一種從複雜到簡潔的思維方式。

2)面向對象和面向過程的差別?

答:面向過程是一種站在過程的角度思考問題的思想,強調的是功能行為,功能的執行過程,即先幹啥,後幹啥。

面向過程的設計:最小的程式單元是函數,每個函數負責完成某一個功能,用以接受輸入資料,函數對輸入資料進行處理,然後輸出結果資料。整個軟體系統由一個個的函數組成,其中作為程式入口的函數稱之為主函數,主函數依次調用其他函數,普通函數之間可以互相調用,進而實作整個系統功能。

  • 面向過程的缺陷:

    向過程的設計,是采用置頂而下的設計方式,在設計階段就需要考慮每一個子產品應該分解成哪些子子產品,每一個子子產品有細分為更小的子子產品,如此類推,直到将子產品細化為一個個函數。

  • 問題:1)設計不夠直覺,與人類的習慣思維不一緻;2)系統軟體适應性差,可擴充性差,維護性低。

面向過程最大的問題在于随着系統的膨脹,面向過程将無法應付,最終導緻系統的崩潰。為了解決這一種軟體危機,我們提出面向對象思想。

面向對象是一種基于面向過程的新的程式設計思想,是一種站在對象的角度思考問題的思想,我們把多個功能合理的放到不同對象裡,強調的是具備某些功能的對象。

  • 面向對象更加符合我們正常的思維方式,穩定性好,可重用性強,易于開發大型軟體産品,有良好的可維護性。在軟體工程上,面向對象可以使工程更加子產品化,實作更低的耦合和更高的内聚。
  • 注意: 不要粗淺的認為面向對象一定就優于面向過程的設計

看到知乎上有一句有意思的話:

你的程式要完成一個任務,相當于講一個故事。

面向過程:編年體;

面向對象:紀傳體。

而對于複雜的程式/宏大的故事,事實都證明了,面向對象/紀傳是更合理的表述方法。

擴充閱讀:面向過程 VS 面向對象

3)JDK 和 JRE 的差別是什麼?

解析:這是考察一些基本的概念

答:Java 運作時環境(JRE-Java Runtime Environment),它包括 Java 虛拟機、Java 核心類庫和支援檔案,但并不包含開發工具(JDK-Java Development Kit)——編譯器、調試器和其他工具。

Java 開發工具包(JDK)是完整的 Java 軟體開發包,包含了 JRE,編譯器和其他的工具(比如 JavaDoc, Java 調試器),可以讓開發者開發、編譯、執行 Java 應用程式。

  • 還有其他的一些名詞也可以再看一下:

4)Java 中覆寫和重載是什麼意思?

解析:覆寫和重載是比較重要的基礎知識點,并且容易混淆,是以面試中常見。

答:覆寫(Override)是指子類對父類方法的一種重寫,隻能比父類抛出更少的異常,通路權限不能比父類的小,被覆寫的方法不能是 private 的,否則隻是在子類中重新定義了一個新方法。

重載(Overload)表示同一個類中可以有多個名稱相同的方法,但這些方法的參數清單各不相同。

面試官: 那麼構成重載的條件有哪些?

答:參數類型不同、參數個數不同、參數順序不同。

面試官: 函數的傳回值不同可以構成重載嗎?為什麼?

答:不可以,因為 Java 中調用函數并不需要強制指派。舉例如下:

如下兩個方法:

void f(){}
int f(){ return 1; }
           

隻要編譯器可以根據語境明确判斷出語義,比如在

int x = f();

中,那麼的确可以據此區分重載方法。不過, 有時你并不關心方法的傳回值,你想要的是方法調用的其他效果 (這常被稱為 “為了副作用而調用” ),這時你可能會調用方法而忽略其傳回值,是以如果像下面的調用:

f();
           

此時 Java 如何才能判斷調用的是哪一個

f()

呢?别人如何了解這種代碼呢?是以,根據方法傳回值來區分重載方法是行不通的。

5)抽象類和接口的差別有哪些?

答:

  1. 抽象類中可以沒有抽象方法;接口中的方法必須是抽象方法;
  2. 抽象類中可以有普通的成員變量;接口中的變量必須是 static final 類型的,必須被初始化,接口中隻有常量,沒有變量。
  3. 抽象類隻能單繼承,接口可以繼承多個父接口;
  4. Java 8 中接口中會有 default 方法,即方法可以被實作。

面試官:抽象類和接口如何選擇?

  1. 如果要建立不帶任何方法定義和成員變量的基類,那麼就應該選擇接口而不是抽象類。
  2. 如果知道某個類應該是基類,那麼第一個選擇的應該是讓它成為一個接口,隻有在必須要有方法定義和成員變量的時候,才應該選擇抽象類。因為抽象類中允許存在一個或多個被具體實作的方法,隻要方法沒有被全部實作該類就仍是抽象類。

6)Java 和 C++ 的差別:

解析:雖然我們不太懂C++,但是就是會這麼問,尤其是三面(總監級别)面試中。

  1. 都是面向對象的語言,都支援封裝、繼承和多态
  2. 指針:Java不提供指針來直接通路記憶體,程式更加安全
  3. 繼承: Java的類是單繼承的,C++支援多重繼承;Java通過一個類實作多個接口來實作C++中的多重繼承; Java中類不可以多繼承,但是!!!接口可以多繼承
  4. 記憶體: Java有自動記憶體管理機制,不需要程式員手動釋放無用記憶體

7)“static” 關鍵字是什麼意思?

答:“static” 關鍵字表明一個成員變量或者是成員方法可以在沒有所屬的類的執行個體變量的情況下被通路。

面試官:Java中是否可以覆寫(override)一個 private 或者是 static 的方法?

答:Java 中 static 方法不能被覆寫,因為方法覆寫是基于運作時動态綁定的,而 static 方法是編譯時靜态綁定的。static 方法跟類的任何執行個體都不相關,是以概念上不适用。

Java 中也不可以覆寫 private 的方法,因為 private 修飾的變量和方法隻能在目前類中使用,如果是其他的類繼承目前類是不能通路到 private 變量或方法的,當然也不能覆寫。

擴充閱讀:重新認識java(六) ---- java中的另類:static關鍵字(附代碼塊知識)

8)Java 是值傳遞還是引用傳遞?

解析:這類題目,面試官會手寫一個例子,讓你說出函數執行結果。

答:值傳遞是對基本型變量而言的,傳遞的是該變量的一個副本,改變副本不影響原變量。引用傳遞一般是對于對象型變量而言的,傳遞的是該對象位址的一個副本, 并不是原對象本身 。

一般認為,Java 内的傳遞都是值傳遞.,Java 中執行個體對象的傳遞是引用傳遞,Java 是值傳遞的!

  • 我們先來看一個例子:

這是一個很經典的例子,我們希望在調用了 swap() 方法之後交換 arg1 和 arg2 的值,但事實上并沒有,為什麼會這樣?

這就是因為 Java 是值傳遞的,也就是說,我們在調用一個需要傳遞參數的函數時,傳遞給函數的參數并不是我們傳遞進去的參數本身,而是它的一個副本,我們改變了資料其實隻是改變了副本的資料而已,并不會對原來的參數有任何的改變。

  • 再來看一個例子:

我們自己定義了一個内部類 Person ,該類隻有一個 int 類型的 age 屬性,然後有 getter/setter ,我們希望通過 changeAge() 函數來改變 Person 對象的 age 屬性,為什麼這次成功了呢?

你依然可以了解為,主函數将 person 複制了一份到 changeAge 函數中去,最終還是隻改變了 changeAge 中複制的那一份參數的值,而原本的參數并沒有改變,但 changeAge 中的那一份和原本的參數指向了同一個記憶體區域!

9)JDK 中常用的包有哪些?

答:java.lang、java.util、java.io、java.net、java.sql。

10)JDK,JRE 和 JVM 的聯系和差別?

答:JDK 是 Java 開發工具包,是 Java 開發環境的核心元件,并提供編譯、調試和運作一個 Java 程式所需要的所有工具,可執行檔案和二進制檔案,是一個平台特定的軟體。

JRE 是 Java 運作時環境,是 JVM 的實施實作,提供了運作 Java 程式的平台。JRE 包含了 JVM,但是不包含 Java 編譯器 / 調試器之類的開發工具。

JVM 是 Java 虛拟機,當我們運作一個程式時,JVM 負責将位元組碼轉換為特定機器代碼,JVM 提供了記憶體管理 / 垃圾回收和安全機制等。

這種獨立于硬體和作業系統,正是 Java 程式可以一次編寫多處執行的原因。

差別:

  1. JDK 用于開發,JRE 用于運作 Java 程式;

  2. JDK 和 JRE 中都包含 JVM;

  3. JVM 是 Java 程式設計語言的核心并且具有平台獨立性。

11)Integer 的緩存機制

解析:考察的是對源碼的熟悉程度

  • 看一個例子:

第一個傳回true很好了解,就像上面講的,a和b指向相同的位址。

第二個傳回false是為什麼呢?這是因為 Integer 有緩存機制,在 JVM 啟動初期就緩存了 -128 到 127 這個區間内的所有數字。

第三個傳回false是因為用了new關鍵字來開辟了新的空間,i和j兩個對象分别指向堆區中的兩塊記憶體空間。

我們可以跟蹤一下Integer的源碼,看看到底怎麼回事。在IDEA中,你隻需要按住Ctrl然後點選Integer,就會自動進入jar包中對應的類檔案。

跟蹤到檔案的700多行,你會看到這麼一段,感興趣可以仔細讀一下,不用去讀也沒有關系,因為你隻需要知道這是 Java 的一個緩存機制。Integer 類的内部類緩存了 -128 到 127 的所有數字。(事實上,Integer類的緩存上限是可以通過修改系統來更改的,了解就行了,不必去深究。)

12)下述兩種方法分别建立了幾個 Sring 對象?

// 第一種:直接賦一個字面量
String str1 = "ABCD";
// 第二種:通過構造器建立
String str2 = new String("ABCD");
           

解析:考察的是對 String 對象和 JVM 記憶體劃分的知識。

String str1 = "ABCD";

最多建立一個String對象,最少不建立String對象.如果常量池中,存在”ABCD”,那麼str1直接引用,此時不建立String對象.否則,先在常量池先建立”ABCD”記憶體空間,再引用.

String str2 = new String("ABCD");

最多建立兩個String對象,至少建立一個String對象。new關鍵字絕對會在堆空間建立一塊新的記憶體區域,是以至少建立一個String對象。

我們來看圖了解一下:
  • 當執行第一句話的時候,會在常量池中添加一個新的ABCD字元,str1指向常量池的ABCD
  • 當執行第二句話的時候,因為有new操作符,是以會在堆空間新開辟一塊空間用來存儲新的String對象,因為此時常量池中已經有了ABCD字元,是以堆中的String對象指向常量池中的ABCD,而str2則指向堆空間中的String對象。

String 對象是一個特殊的存在,需要注意的知識點也比較多,這裡給一個之前寫的 String 詳解的文章連結:傳送門 其中包含的問題大概有:1)“+” 怎麼連接配接字元串;2)字元串的比較;3)StringBuilder/StringBuffer/String 的差別;

13)i++ 與 ++i 到底有什麼不同?

解析:對于這兩個的差別,熟悉的表述是:前置++是先将變量的值加 1,然後使用加 1 後的值參與運算,而後置++則是先使用該值參與運算,然後再将該值加 1 .但事實上,前置++和後置++一樣,在參與運算之前都會将變量的值加 1

答:實際上,不管是前置 ++,還是後置 ++,都是先将變量的值加 1,然後才繼續計算的。二者之間真正的差別是:前置 ++ 是将變量的值加 1 後,使用增值後的變量進行運算的,而後置 ++ 是首先将變量指派給一個臨時變量,接下來對變量的值加 1,然後使用那個臨時變量進行運算。

14)交換變量的三種方式

  • 第一種:通過第三個變量
public class Test{
    public static void main(String[] args) {
        int x = 5;
        int y = 10;
        swap(x,y);
        System.out.println(x);
        System.out.println(y);

        Value v = new Value(5,10);
        swap(v);
        System.out.println(v.x);
        System.out.println(v.y);
    }

    // 無效的交換:形參的改變無法反作用于實參
    public static void swap(int x,int y) {
        int temp = x;
        x = y;
        y = temp;
    }

    // 有效的交換:通過引用(變量指向一個對象)來修改成員變量
    public static void swap(Value value) {
        int temp = value.x;
        value.x = value.y;
        value.y = temp;
    }
}

class Value{
    int x;
    int y;

    public Value(int x,int y) {
        this.x = x;
        this.y = y;
    }
}
           

輸出的結果:

5

10

這有點類似于C/C++語言中的指針,不過相對來說更加安全。

事實上,其實如果把基礎類型int改成對應的包裝類的話其實可以更加簡單的完成這個操作,不過需要付出更多的記憶體代價。

第二種:通過通過相加的方式(相同的 Value 類不再重複展示)
public class Test{
    public static void main(String[] args) {
        Value v1 = new Value(5,10);
        swap(v1);
        System.out.println("v1交換之後的結果為:");
        System.out.println(v1.x);
        System.out.println(v1.y);
    }

    public static void swap(Value v) {
        v.x = v.x + v.y;
        v.y = v.x - v.y;
        v.x = v.x - v.y;
    }
}
           
v1的交換結果:

核心的算法就是swap方法:

v.x = v.x + v.y;    // 把v.x與v.y的和存儲在v.x中
v.y = v.x - v.y;    // v.x減掉v.y本來的值即為v.x
v.x = v.x - v.y;    // v.x減掉v.y的值也就是以前x.y的值
           

這樣就可以不通過臨時變量,來達到交換兩個變量的目的,如果覺得上面的方法不太容易了解,我們也可以用另一個參數z來表示上述過程:

int z = v.x + v.y;    // 把v.x與v.y的和存儲在z中
v.y = z - v.y;        // z減掉以前的v.y就等于v.x
v.x = z - v.y;        // z減掉現在的v.y即以前的v.x,即為v.y
           

但并不推薦這種做法,原因在于當數值很大的時候,16進制的求和運算可能造成資料的溢出,雖然最後的結果依然會是我們所期望的那樣,但仍然不是十分可取。

  • 第三種:通過異或的方式:

位異或運算符(^)有這樣的一個性質,就是兩個整型的資料x與y,有:

(x ^ y ^ y) == x

這說明,如果一個變量x異或另外一個變量y兩次,結果為x。通過這一點,可以實作交換兩個變量的值:

public class Test{
    public static void main(String[] args) {
        Value v1 = new Value(5,10);
        swap(v1);
        System.out.println("v1交換之後的結果為:");
        System.out.println(v1.x);
        System.out.println(v1.y);
    }

    public static void swap(Value v) {
        v.x = v.x ^ v.y;
        v.y = v.x ^ v.y;
        v.x = v.x ^ v.y;
    }
}
           
v1交換之後的結果為:

跟上面相加的方式過程幾乎類似,隻不過運算的方式不同而已。異或的方法比相加更加可取的地方在于,異或不存在資料溢出。

15)Java 對象初始化順序?

答:不考慮靜态成員的初始化,調用一個對象的構造函數時,程式先調用父類的構造函數(可以通過super關鍵字指定父類的構造函數,否則預設調用無參的構造函數,并且需要在子類的構造函數的第一行調用),之後靜态成員變量的初始化函數和靜态初始化塊則按照在代碼當中的順序執行,成員變量如果沒有指定值的話則賦予預設值,即基本資料類型為0或false等,對象則為null;最後調用自身構造函數。

  • 我們可以寫一段程式來對初始化順序進行一個簡單的驗證:
public class Derive extends Base
{
    private Member m1 = new Member("Member 1");
    {
        System.out.println("Initial Block()");
    }

    public Derive() {
        System.out.println("Derive()");
    }

    private Member m2 = new Member("Member 2");
    private int i = getInt();

    private int getInt()
    {
        System.out.println("getInt()");
        return 2;
    }

    public static void main(String[] args)
    {
        new Derive();
    }
}

class Base
{
    public Base()
    {
        System.out.println("Base()");
    }
}

class Member
{
    public Member(String m)
    {
        System.out.println("Member() "+m);
    }
}
           

程式的輸出結果是:

Base()

Member() Member 1

Initial Block()

Member() Member 2

getInt()

Derive()

16)true、false 與 null 是關鍵字嗎?

答:不是。true、false 是布爾類型的字面常量,null 是引用類型的字面常量。

面試官:那 goto 與 const 呢?

答:是。goto 與 const 均是 Java 語言保留的關鍵字,即沒有任何文法應用。

17)exception 和 error 有什麼差別?

答:exception 和 error都是 Throwable 的子類。exception 用于使用者程式可以捕獲的異常情況;error 定義了不期望被使用者程式捕獲的異常。

exception 表示一種設計或設計的問題,也就是說隻要程式正常運作,從不會發生的情況;而 error 表示回複不是不可能但是很困難的情況下的一種嚴重問題,比如記憶體溢出,不可能指望程式處理這樣的情況。

18)throw 和 throws 有什麼差別?

答:throw 關鍵字用來在程式中明确的抛出異常,相反,throws 語句用來表明方法不能處理的異常。每一個方法都必須要指定哪些異常不能處理,是以方法的調用者才能夠確定處理可能發生的異常,多個異常是用逗号分隔的。

小結:本節主要闡述了 Java 基礎知識,并沒有涉及到一些進階的特性,這些問題一般難度不大,适當複習下,應該沒問題。

(二)Java 中常見集合

集合這方面的考察相當多,這部分是面試中必考的知識點。

1)說說常見的集合有哪些吧?

答:Map接口和Collection接口是所有集合架構的父接口:

  1. Collection接口的子接口包括:Set接口和List接口
  2. Map接口的實作類主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等
  3. Set接口的實作類主要有:HashSet、TreeSet、LinkedHashSet等
  4. List接口的實作類主要有:ArrayList、LinkedList、Stack以及Vector等

2)HashMap和Hashtable的差別有哪些?(必問)

  1. HashMap沒有考慮同步,是線程不安全的;Hashtable使用了synchronized關鍵字,是線程安全的;
  2. 前者允許null作為Key;後者不允許null作為Key

3)HashMap的底層實作你知道嗎?

答:在Java8之前,其底層實作是數組+連結清單實作,Java8使用了數組+連結清單+紅黑樹實作。此時你可以簡單的在紙上畫圖分析:

4)ConcurrentHashMap 和 Hashtable 的差別?(必問)

答:ConcurrentHashMap 結合了 HashMap 和 HashTable 二者的優勢。HashMap 沒有考慮同步,HashTable 考慮了同步的問題。但是 HashTable 在每次同步執行時都要鎖住整個結構。 ConcurrentHashMap 鎖的方式是稍微細粒度的。 ConcurrentHashMap 将 hash 表分為 16 個桶(預設值),諸如get,put,remove 等常用操作隻鎖目前需要用到的桶。

面試官:ConcurrentHashMap的具體實作知道嗎?

1. 該類包含兩個靜态内部類 HashEntry 和 Segment ;前者用來封裝映射表的鍵值對,後者用來充當鎖的角色;

2. Segment 是一種可重入的鎖 ReentrantLock,每個 Segment 守護一個HashEntry 數組裡得元素,當對 HashEntry 數組的資料進行修改時,必須首先獲得對應的 Segment 鎖。

5)HashMap 的長度為什麼是2的幂次方?

1. 通過将 Key 的 hash 值與 length - 1 進行 & 運算,實作了目前 Key 的定位,2 的幂次方可以減少沖突(碰撞)的次數,提高 HashMap 查詢效率

2. 如果 length 為 2 的次幂 則 length-1 轉化為二進制必定是 11111……的形式,在于 h 的二進制與操作效率會非常的快,而且空間不浪費;如果 length 不是 2 的次幂,比如 length 為 15,則 length - 1 為 14,對應的二進制為 1110,在于 h 與操作,最後一位都為 0 ,而 0001,0011,0101,1001,1011,0111,1101 這幾個位置永遠都不能存放元素了,空間浪費相當大,更糟的是這種情況中,數組可以使用的位置比數組長度小了很多,這意味着進一步增加了碰撞的幾率,減慢了查詢的效率!這樣就會造成空間的浪費。

6)List和Set的差別是啥?

答:List元素是有序的,可以重複;Set元素是無序的,不可以重複。

7)List、Set和Map的初始容量和加載因子:

1. List

  • ArrayList的初始容量是10;加載因子為0.5; 擴容增量:原容量的 0.5倍+1;一次擴容後長度為15。
  • Vector初始容量為10,加載因子是1。擴容增量:原容量的 1倍,如 Vector的容量為10,一次擴容後是容量為20。

2. Set

HashSet,初始容量為16,加載因子為0.75; 擴容增量:原容量的 1 倍; 如 HashSet的容量為16,一次擴容後容量為32

3. Map

HashMap,初始容量16,加載因子為0.75; 擴容增量:原容量的 1 倍; 如 HashMap的容量為16,一次擴容後容量為32

8)Comparable接口和Comparator接口有什麼差別?

1. 前者簡單,但是如果需要重新定義比較類型時,需要修改源代碼。

2. 後者不需要修改源代碼,自定義一個比較器,實作自定義的比較方法。 具體解析參考部落格:Java集合架構—Set

9)Java集合的快速失敗機制 “fail-fast”

是java集合的一種錯誤檢測機制,當多個線程對集合進行結構上的改變的操作時,有可能會産生 fail-fast 機制。

例如:假設存在兩個線程(線程1、線程2),線程1通過Iterator在周遊集合A中的元素,在某個時候線程2修改了集合A的結構(是結構上面的修改,而不是簡單的修改集合元素的内容),那麼這個時候程式就會抛出 ConcurrentModificationException 異常,進而産生fail-fast機制。

原因:疊代器在周遊時直接通路集合中的内容,并且在周遊過程中使用一個 modCount 變量。集合在被周遊期間如果内容發生變化,就會改變modCount的值。每當疊代器使用hashNext()/next()周遊下一個元素之前,都會檢測modCount變量是否為expectedmodCount值,是的話就傳回周遊;否則抛出異常,終止周遊。

解決辦法:

1. 在周遊過程中,所有涉及到改變modCount值得地方全部加上synchronized。

2. 使用CopyOnWriteArrayList來替換ArrayList

10)ArrayList 和 Vector 的差別

這兩個類都實作了 List 接口(List 接口繼承了 Collection 接口),他們都是有序集合,即存儲在這兩個集合中的元素位置都是有順序的,相當于一種動态的數組,我們以後可以按位置索引來取出某個元素,并且其中的資料是允許重複的,這是與 HashSet 之類的集合的最大不同處,HashSet 之類的集合不可以按索引号去檢索其中的元素,也不允許有重複的元素。

ArrayList 與 Vector 的差別主要包括兩個方面:

  1. 同步性:

    Vector 是線程安全的,也就是說它的方法之間是線程同步(加了synchronized 關鍵字)的,而 ArrayList 是線程不安全的,它的方法之間是線程不同步的。如果隻有一個線程會通路到集合,那最好是使用 ArrayList,因為它不考慮線程安全的問題,是以效率會高一些;如果有多個線程會通路到集合,那最好是使用 Vector,因為不需要我們自己再去考慮和編寫線程安全的代碼。

  2. 資料增長:

    ArrayList 與 Vector 都有一個初始的容量大小,當存儲進它們裡面的元素的個人超過了容量時,就需要增加 ArrayList 和 Vector 的存儲空間,每次要增加存儲空間時,不是隻增加一個存儲單元,而是增加多個存儲單元,每次增加的存儲單元的個數在記憶體空間利用與程式效率之間要去的一定的平衡。Vector 在資料滿時(加載因子1)增長為原來的兩倍(擴容增量:原容量的 1 倍),而 ArrayList 在資料量達到容量的一半時(加載因子 0.5)增長為原容量的 0.5 倍 + 1 個空間。

面試官:那 ArrayList 和 LinkedList 的差別呢?

  1. LinkedList 實作了 List 和 Deque 接口,一般稱為雙向連結清單;
  2. LinkedList 在插入和删除資料時效率更高,ArrayList 在查找某個 index 的資料時效率更高;
  3. LinkedList 比 ArrayList 需要更多的記憶體;

面試官:Array 和 ArrayList 有什麼差別?什麼時候該應 Array 而不是 ArrayList 呢?

答:它們的差別是:

  1. Array 可以包含基本類型和對象類型,ArrayList 隻能包含對象類型。
  2. Array 大小是固定的,ArrayList 的大小是動态變化的。
  3. ArrayList 提供了更多的方法和特性,比如:addAll(),removeAll(),iterator() 等等。

對于基本類型資料,集合使用自動裝箱來減少編碼工作量。但是,當處理固定大小的基本資料類型的時候,這種方式相對比較慢。

11)如何去掉一個 Vector 集合中重複的元素?

Vector newVector = new Vector();
for (int i = 0; i < vector.size(); i++) {
	Object obj = vector.get(i);
	if (!newVector.contains(obj)) {
		newVector.add(obj);
	}
}
           

還有一種簡單的方式,利用了 Set 不允許重複元素的特性:

HashSet set = new HashSet(vector);
           
小結:本小節是 Java 中關于集合的考察,是 Java 崗位面試中必考的知識點,除了應該掌握以上的問題,包括各個集合的底層實作也建議各位同學閱讀,加深了解。

12)如何權衡是使用無序的數組還是有序的數組?

答:有序數組最大的好處在于查找的時間複雜度是O(log n),而無序數組是O(n)。有序數組的缺點是插入操作的時間複雜度是O(n),因為值大的元素需要往後移動來給新元素騰位置。相反,無序數組的插入時間複雜度是常量O(1)。

總結

oh.....複習下來還真是酸爽....前路漫漫啊....

歡迎轉載,轉載請注明出處!

簡書ID:@我沒有三顆心髒

github:wmyskxz

歡迎關注公衆微信号:wmyskxz_javaweb

分享自己的Java Web學習之路以及各種Java學習資料