[題庫]Java 面試知識點



(一)Java 基礎知識點

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

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

  • 封裝是指將對象的實現細節隱藏起來,然後通過公共的方法來向外暴露出該對象的功能。
  • 但封裝不僅僅是 private + getter/setter ,使用封裝可以對 setter 進行更深層次的定製,例如你可以對執行方法的對象做規定,也可以對數據做一定的要求,還可以做類型轉換等等。使用封裝不僅僅安全,更可以簡化操作。
  • 繼承是面向對象實現軟件複用的重要手段,當子類繼承父類後,子類是一種特殊的父類,能直接或間接獲得父類裡的成員。
  • 繼承的缺點:1)繼承是一種強耦合關係,父類變子類也必須變;
    2)繼承破壞了封裝,對於父類而言,它的實現細節對子類來說都是透明的。
  • 多態簡而言之就是同一個行為具有多個不同表現形式或形態的能力。
  • 比如說,有一杯水,我不知道它是溫的、冰的還是燙的,但是我一摸我就知道了,我摸水杯的這個動作,對於不同溫度的水,就會得到不同的結果,這就是多態。
  • 多態的條件1)繼承;2)重寫;3)向上轉型。
  • 多態的好處:當把不同的子類對象都當作父類類型來看,可以屏蔽不同子類對象之間的實現差異,從而寫出通用的代碼達到通用編程,以適應需求的不斷變化。(多態擴展閱讀:重新認識java(五) ---- 面向對象之多態(向上轉型與向下轉型))
  • 抽象是指從特定的角度出發,從已經存在的一些事物中抽取我們所關注的特性、行為,從而形成一個新的事物的思維過程,是一種從複雜到簡潔的思維方式。

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

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

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

[題庫]Java 面試知識點


  • 面向過程的缺陷:
  • 向過程的設計,是採用置頂而下的設計方式,在設計階段就需要考慮每一個模塊應該分解成哪些子模塊,每一個子模塊有細分為更小的子模塊,如此類推,直到將模塊細化為一個個函數。
  • 問題:1)設計不夠直觀,與人類的習慣思維不一致;2)系統軟件適應性差,可擴展性差,維護性低。

面向過程最大的問題在於隨著系統的膨脹,面向過程將無法應付,最終導致系統的崩潰。為了解決這一種軟件危機,我們提出面向對象思想。

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

[題庫]Java 面試知識點


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

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

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

面向過程:編年體;

面向對象:紀傳體。

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

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

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

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

答:Java 運行時環境(JRE-Java Runtime Environment),它包括 Java 虛擬機、Java 核心類庫和支持文件,但並不包含開發工具(JDK-Java Development Kit)——編譯器、調試器和其他工具。

Java 開發工具包(JDK)是完整的 Java 軟件開發包,包含了 JRE,編譯器和其他的工具(比如 JavaDoc, Java 調試器),可以讓開發者開發、編譯、執行 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 方法,即方法可以被實現。


[題庫]Java 面試知識點


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

答:

  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 是值傳遞的!

  • 我們先來看一個例子:


[題庫]Java 面試知識點


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

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

  • 再來看一個例子:


[題庫]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 的緩存機制

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

  • 看一個例子:


[題庫]Java 面試知識點


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

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

第三個返回false是因為用了new關鍵字來開闢了新的空間,i和j兩個對象分別指向堆區中的兩塊內存空間。

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

[題庫]Java 面試知識點


跟蹤到文件的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對象。

我們來看圖理解一下:

[題庫]Java 面試知識點


  • 當執行第一句話的時候,會在常量池中添加一個新的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,然後使用那個臨時變量進行運算。

[題庫]Java 面試知識點


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

10

5

這有點類似於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的交換結果:

10

5

核心的算法就是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交換之後的結果為:

10

5

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

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使用了數組+鏈表+紅黑樹實現。此時你可以簡單的在紙上畫圖分析:

[題庫]Java 面試知識點


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. 同步性:
  2. Vector 是線程安全的,也就是說它的方法之間是線程同步(加了synchronized 關鍵字)的,而 ArrayList 是線程不安全的,它的方法之間是線程不同步的。如果只有一個線程會訪問到集合,那最好是使用 ArrayList,因為它不考慮線程安全的問題,所以效率會高一些;如果有多個線程會訪問到集合,那最好是使用 Vector,因為不需要我們自己再去考慮和編寫線程安全的代碼。
  3. 數據增長:
  4. 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)。



分享到:


相關文章: