【Java面試題】搞定 BAT 的 Java 技術面試,集合詳解篇(上)

先來看看集合的繼承關係圖,如下圖所示:

搞定 BAT 的 Java 技術面試,集合詳解篇(上)

 

其中:

  • 外框爲虛線的表示接口,邊框爲實線的表示類;
  • 箭頭爲虛線的表示實現了接口,箭頭爲實線的表示繼承了類。

爲了方便理解,我隱藏了一些與本文內容無關的信息,隱藏的這些內容會在後面的章節中進行詳細地介紹。

從圖中可以看出,集合的根節點是 Collection,而 Collection 下又提供了兩大常用集合,分別是:

  • List:使用最多的有序集合,提供方便的新增、修改、刪除的操作;
  • Set:集合不允許有重複的元素,在許多需要保證元素唯一性的場景中使用。

下面我們分別對集合類進行詳細地介紹。

集合使用

1)Vector

Vector 是 Java 早期提供的線程安全的有序集合,如果不需要線程安全,不建議使用此集合,畢竟同步是有線程開銷的。

使用示例代碼:

Vector vector = new Vector();
vector.add("dog");
vector.add("cat");
vector.remove("cat");
System.out.println(vector);

程序執行結果:[dog]

2)ArrayList

ArrayList 是最常見的非線程安全的有序集合,因爲內部是數組存儲的,所以隨機訪問效率很高,但非尾部的插入和刪除性能較低,如果在中間插入元素,之後的所有元素都要後移。ArrayList 的使用與 Vector 類似。

3)LinkedList

LinkedList 是使用雙向鏈表數據結構實現的,因此增加和刪除效率比較高,而隨機訪問效率較差。

LinkedList 除了包含以上兩個類的操作方法之外,還新增了幾個操作方法,如 offer() 、peek() 等,具體詳情,請參考以下代碼:

LinkedList linkedList = new LinkedList();
// 添加元素
linkedList.offer("bird");
linkedList.push("cat");
linkedList.push("dog");
// 獲取第一個元素
System.out.println(linkedList.peek());
// 獲取第一個元素,並刪除此元素
System.out.println(linkedList.poll());
System.out.println(linkedList);

程序的執行結果:

dog
dog
[cat, bird]

4)HashSet

HashSet 是一個沒有重複元素的集合。雖然它是 Set 集合的子類,實際卻爲 HashMap 的實例,相關源碼如下:

public HashSet() {
    map = new HashMap‹›();
}

因此 HashSet 是無序集合,沒有辦法保證元素的順序性。

HashSet 默認容量爲 16,每次擴充 0.75 倍,相關源碼如下:

public HashSet(Collection‹? extends E› c) {
    map = new HashMap‹›(Math.max((int) (c.size()/.75f) + 1, 16));
    addAll(c);
}

HashSet 的使用與 Vector 類似。

5)TreeSet

TreeSet 集合實現了自動排序,也就是說 TreeSet 會把你插入數據進行自動排序。

示例代碼如下:

TreeSet treeSet = new TreeSet();
treeSet.add("dog");
treeSet.add("camel");
treeSet.add("cat");
treeSet.add("ant");
System.out.println(treeSet);

程序執行結果:[ant, camel, cat, dog]

可以看出,TreeSet 的使用與 Vector 類似,只是實現了自動排序。

6)LinkedHashSet

LinkedHashSet 是按照元素的 hashCode 值來決定元素的存儲位置,但同時又使用鏈表來維護元素的次序,這樣使得它看起來像是按照插入順序保存的。

LinkedHashSet 的使用與 Vector 類似。

集合與數組

集合和數組的轉換可使用 toArray() 和 Arrays.asList() 來實現,請參考以下代碼示例:

List‹String› list = new ArrayList();
list.add("cat");
list.add("dog");
// 集合轉數組
String[] arr = list.toArray(new String[list.size()]);
// 數組轉集合
List‹String› list2 = Arrays.asList(arr);

集合與數組的區別,可以參考第 1-7 課的內容。

集合排序

在 Java 語言中排序提供了兩種方式:Comparable 和 Comparator,它們的區別也是常見的面試題之一。下面我們徹底地來了解一下 Comparable 和 Comparator 的使用與區別。

1)Comparable

Comparable 位於 java.lang 包下,是一個排序接口,也就是說如果一個類實現了 Comparable 接口,就意味着該類有了排序功能。

Comparable 接口只包含了一個函數,定義如下:

package java.lang;
import java.util.*;
public interface Comparable {
  public int compareTo(T o);
}

Comparable 使用示例,請參考以下代碼:

class ComparableTest {
    public static void main(String[] args) {
        Dog[] dogs = new Dog[]{
                new Dog("老旺財", 10),
                new Dog("小旺財", 3),
                new Dog("二旺財", 5),
        };
        // Comparable 排序
        Arrays.sort(dogs);
        for (Dog d : dogs) {
            System.out.println(d.getName() + ":" + d.getAge());
        }
    }
}
class Dog implements Comparable‹Dog› {
    private String name;
    private int age;
    @Override
    public int compareTo(Dog o) {
        return age - o.age;
    }
    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
}

程序執行結果:

小旺財:3
二旺財:5
老旺財:10

如果 Dog 類未實現 Comparable 執行代碼會報程序異常的信息,錯誤信息爲:

Exception in thread "main" java.lang.ClassCastException: xxx cannot be cast to java.lang.Comparable

compareTo() 返回值有三種:

  • e1.compareTo(e2) › 0 即 e1 › e2;
  • e1.compareTo(e2) = 0 即 e1 = e2;
  • e1.compareTo(e2) ‹ 0 即 e1 ‹ e2。

2)Comparator

Comparator 是一個外部比較器,位於 java.util 包下,之所以說 Comparator 是一個外部比較器,是因爲它無需在比較類中實現 Comparator 接口,而是要新創建一個比較器類來進行比較和排序。

Comparator 接口包含的主要方法爲 compare(),定義如下:

public interface Comparator‹T› {
  int compare(T o1, T o2);
}

Comparator 使用示例,請參考以下代碼:

class ComparatorTest {
    public static void main(String[] args) {
        Dog[] dogs = new Dog[]{
                new Dog("老旺財", 10),
                new Dog("小旺財", 3),
                new Dog("二旺財", 5),
        };
        // Comparator 排序
        Arrays.sort(dogs,new DogComparator());
        for (Dog d : dogs) {
            System.out.println(d.getName() + ":" + d.getAge());
        }
    }
}
class DogComparator implements Comparator‹Dog› {
    @Override
    public int compare(Dog o1, Dog o2) {
        return o1.getAge() - o2.getAge();
    }
}
class Dog {
    private String name;
    private int age;
    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
}

程序執行結果:

小旺財:3
二旺財:5
老旺財:10

相關面試題

1.List 和 Set 有什麼區別?

答:區別分爲以下幾個方面:

  • List 允許有多個 null 值,Set 只允許有一個 null 值;
  • List 允許有重複元素,Set 不允許有重複元素;
  • List 可以保證每個元素的存儲順序,Set 無法保證元素的存儲順序。

2.哪種集合可以實現自動排序?

答:TreeSet 集合實現了元素的自動排序,也就是說無需任何操作,即可實現元素的自動排序功能。

3.Vector 和 ArrayList 初始化大小和容量擴充有什麼區別?

答:Vector 和 ArrayList 的默認容量都爲 10,源碼如下。

Vector 默認容量源碼:

public Vector() {
    this(10);
}

ArrayList 默認容量源碼:

private static final int DEFAULT_CAPACITY = 10;

Vector 容量擴充默認增加 1 倍,源碼如下:

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + ((capacityIncrement › 0) ?
                                     capacityIncrement : oldCapacity);
    if (newCapacity - minCapacity ‹ 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE › 0)
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}

其中 capacityIncrement 爲初始化 Vector 指定的,默認情況爲 0。

ArrayList 容量擴充默認增加大概 0.5 倍(oldCapacity + (oldCapacity ›› 1)),源碼如下(JDK 8):

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity ›› 1);
    if (newCapacity - minCapacity ‹ 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE › 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}

4.Vector、ArrayList、LinkedList 有什麼區別?

答:這三者都是 List 的子類,因此功能比較相似,比如增加和刪除操作、查找元素等,但在性能、線程安全等方面表現卻又不相同,差異如下:

  • Vector 是 Java 早期提供的動態數組,它使用 synchronized 來保證線程安全,如果非線程安全需要不建議使用,畢竟線程同步是有性能開銷的;
  • ArrayList 是最常用的動態數組,本身並不是線程安全的,因此性能要好很多,與 Vector 類似,它也是動態調整容量的,只不過 Vector 擴容時會增加 1 倍,而 ArrayList 會增加 50%;
  • LinkedList 是雙向鏈表集合,因此它不需要像上面兩種那樣調整容量,它也是非線程安全的集合。

5.Vector、ArrayList、LinkedList 使用場景有什麼區別?

答:Vector 和 ArrayList 的內部結構是以數組形式存儲的,因此非常適合隨機訪問,但非尾部的刪除或新增性能較差,比如我們在中間插入一個元素,就需要把後續的所有元素都進行移動。

LinkedList 插入和刪除元素效率比較高,但隨機訪問性能會比以上兩個動態數組慢。

6.Collection 和 Collections 有什麼區別?

答:Collection 和 Collections 的區別如下:

  • Collection 是集合類的上級接口,繼承它的主要有 List 和 Set;
  • Collections 是針對集合類的一個幫助類,它提供了一些列的靜態方法實現,如 Collections.sort() 排序、Collections.reverse() 逆序等。

7.以下選項沒有繼承 Collection 接口的是?

A:ListB:SetC:MapD:HashSet

答:C

8.LinkedHashSet 如何保證有序和唯一性?

答:LinkedHashSet 底層數據結構由哈希表和鏈表組成,鏈表保證了元素的有序即存儲和取出一致,哈希表保證了元素的唯一性。

9.HashSet 是如何保證數據不可重複的?

答:HashSet 的底層其實就是 HashMap,只不過 HashSet 實現了 Set 接口並且把數據作爲 K 值,而 V 值一直使用一個相同的虛值來保存,我們可以看到源碼:

public boolean add(E e) {
    return map.put(e, PRESENT)==null;// 調用 HashMap 的 put 方法,PRESENT 是一個至始至終都相同的虛值
}

由於 HashMap 的 K 值本身就不允許重複,並且在 HashMap 中如果 K/V 相同時,會用新的 V 覆蓋掉舊的 V,然後返回舊的 V,那麼在 HashSet 中執行這一句話始終會返回一個 false,導致插入失敗,這樣就保證了數據的不可重複性。

10.執行以下程序會輸出什麼結果?爲什麼?

Integer num = 10;
Integer num2 = 5;
System.out.println(num.compareTo(num2));

答:程序輸出的結果是 1,因爲 Integer 默認實現了 compareTo 方法,定義了自然排序規則,所以當 num 比 num2 大時會返回 1,Integer 相關源碼如下:

public int compareTo(Integer anotherInteger) {
    return compare(this.value, anotherInteger.value);
}
public static int compare(int x, int y) {
    return (x ‹ y) ? -1 : ((x == y) ? 0 : 1);
}

11.如何用程序實現後進先出的棧結構?

答:可以使用集合中的 Stack 實現,Stack 是標準的後進先出的棧結構,使用 Stack 中的 pop() 方法返回棧頂元素並刪除該元素,示例代碼如下。

Stack stack = new Stack();
stack.push("a");
stack.push("b");
stack.push("c");
for (int i = 0; i ‹ 3; i++) {
    // 移除並返回棧頂元素
    System.out.print(stack.pop() + " ");
}

程序執行結果:c b a

12.LinkedList 中的 peek() 和 poll() 有什麼區別?

答:peek() 方法返回第一個元素,但不刪除當前元素,當元素不存在時返回 null;poll() 方法返回第一個元素並刪除此元素,當元素不存在時返回 null。

13.Comparable 和 Comparator 有哪些區別?

答:Comparable 和 Comparator 的主要區別如下:

  • Comparable 位於 java.lang 包下,而 Comparator 位於 java.util 包下;
  • Comparable 在排序類的內部實現,而 Comparator 在排序類的外部實現;
  • Comparable 需要重寫 CompareTo() 方法,而 Comparator 需要重寫 Compare() 方法;
  • Comparator 在類的外部實現,更加靈活和方便。

總結

本文介紹的集合都實現自 Collection,因此它們都有同樣的操作方法,如 add()、addAll()、remove() 等,Collection 接口的方法列表如下圖:

搞定 BAT 的 Java 技術面試,集合詳解篇(上)

 

當然部分集合也在原有方法上擴充了自己特有的方法,如 LinkedList 的 offer()、push() 等方法。本文也提供了數組和集合互轉方法,List.toArray() 把集合轉換爲數組,Arrays.asList(array) 把數組轉換爲集合。最後介紹了 Comparable 和 Comparator 的使用和區別,Comparable 和 Comparator 是 Java 語言排序提供的兩種排序方式,Comparable 位於 java.lang 包下,如果一個類實現了 Comparable 接口,就意味着該類有了排序功能;而 Comparator 位於 java.util 包下,是一個外部比較器,它無需在比較類中實現 Comparator 接口,而是要新創建一個比較器類來進行比較和排序。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章