Java 集合基礎詳細介紹

一.Java集合框架概述

集合、數組都是對多個數據進行存儲操作的結構,簡稱Java容器。此時的存儲,主要指的是內存層面的存儲,不涉及到持久化的存儲(.txt, .jpg, .avi,數據庫中)。Java 集合就像一種容器,可以動態地把多個對象的引用放入容器中。

1.數組在內存存儲方面的特點:

  • 數組初始化以後,長度就確定了
  • 數組聲明的類型,就決定了進行元素初始化時的類型

2.數組在存儲數據方面的弊端:

  • 數組初始化以後,長度就不可變了,不便於擴展

  • 數組中提供的屬性和方法少,不便於進行添加、刪除、插入等操作,且效率不高。同時無法直接獲取數組中實際元素的個數

  • 數組存儲的數據是有序的、可以重複的。對於無序、不可重複的需求,不能滿足。

3.Java集合分爲Collection和Map兩種關係:

  • Collection接口:單列集合,用來存儲一個一個的對象
    • List:存儲有序的、可重複的數據。 --> “動態”數組
      • ArrayList、LinkedList、Vector
    • Set:存儲無序的、不可重複的數據 --> 高中講的“集合”
      • HashSet、LinkedHashSet、TreeSet
  • Map接口:雙列集合,用來存儲一對(key - value)一對的數據 --> 高中函數:y = f(x)
    • HashMap、LinkedHashMap、TreeMap、Hashtable、Properties

二.Collection接口

Collection 接口是 List、Set 和 Queue 接口的父接口,該接口裏定義的方法。既可用於操作 Set 集合,也可用於操作 List 和 Queue 集合。

1.常用的API

在這裏插入圖片描述

2.抽象方法

刪除、包含相關方法底層都是調用元素類型對象**重寫的equals方法(比較內容)**來判斷集合中是否有要刪除或包含的元素。向集合中添加obj數據時,要求obj所在類要重寫equals()。

  • 增:
    • boolean add(Object o)添加元素到集合
    • boolean addAll(Collection c):將指定集合中的所有元素添加到此集合
  • 刪:
    • boolean remove(Object o)刪除找到的第一個元素(equals)
    • boolean removeAll(Collection c):從當前集合中刪除公共元素(equals)(差集)
    • boolean retainAll(Collection c):從當前集合中刪除非公共元素(equals)(交集)
    • void clear():刪除所有元素
  • 查:
    • iterator()返回迭代器對象,用於遍歷
    • for(集合元素的類型 局部變量 : 集合對象)增強for循環,用於遍歷
    • int size()查詢有效元素個數
    • hashCode()查詢當前對象的哈希值
  • 判斷:
    • boolean contains(Object o)是否包含某個元素(equals)
    • boolean containsAll(Collection c):是否包含某個集合的所有元素(equals)
    • boolean isEmpty()判斷集合size==0
    • boolean equals(Object o)集合是否相等(比較集合是否相等:元素及順序)
  • 轉換:
    • Object[] toArray()集合 --> 數組
    • T[] toArray(T[] a) : 集合 --> 數組
    • (Arrays)public static List<T> asList(T... a)數組 --> 集合

3.注意

獲取長度的區分:

  • java 中的length屬性是針對數組說的,比如說你聲明瞭一個數組,想知道這個數組的長度則用到了 length 這個屬性.
  • java 中的length()方法是針對字符串String說的,如果想看這個字符串的長度則用到 length()這個方法.
  • java 中的size()方法是針對泛型集合說的,如果想看這個泛型有多少個元素,就調用此方法來查看!

向Collection接口的實現類的對象中添加obj數據時,要求obj所在類要重寫equals():

  • 在判斷時會調用obj對象所在類的equals()方法:equals方法默認比較地址,需要重寫來比較內容。String、File、Date、包裝類默認重寫了equals方法
  • equals(Object obj):要想返回true,需要當前集合和形參集合的元素都相同(包括元素的順序)
  • 調用Arrays類的靜態方法asList():基本數據類型數組會被當做一個對象,需要使用包裝類對象數組當形參
Collection coll = new ArrayList();
coll.add(123);
coll.add(new Person("Jerry",20));
coll.add(new String("Tom"));
coll.add(false);

// 在判斷時會調用obj對象所在類的equals()方法:equals方法默認比較地址,需要重寫來比較內容
// 1.contains(Object obj):判斷當前集合中是否包含obj
boolean contains = coll.contains(123);
System.out.println(contains);	// true
// String、File、Date、包裝類重寫了equals方法
System.out.println(coll.contains(new String("Tom")));	// true
// Person沒有重寫equals方法時:false;重寫了equals方法:true
System.out.println(coll.contains(new Person("Jerry",20)));

// coll2集合中:元素內容與coll集合相同,但順序不同
Collection coll2 = new ArrayList();
coll2.add(new Person("Jerry",20));
coll2.add(new String("Tom"));
coll2.add(123);
coll2.add(false);
// 2.equals(Object obj):要想返回true,需要當前集合和形參集合的元素都相同(包括元素的順序)。
System.out.println(coll.equals(coll1));  // false

// 3.集合 --> 數組:toArray()
Object[] arr = coll.toArray();
for(int i = 0;i < arr.length;i++){
    System.out.println(arr[i]);
}
// 若集合中都是相同類型的元素,若都爲Integer類型
// 第一種方式(最常用)
Integer[] integer = arrayList.toArray(new Integer[0]);
// 第二種方式(容易理解)
Integer[] integer1 = new Integer[arrayList.size()];
arrayList.toArray(integer1);
// 拋出異常,java不支持向下轉型,講Object數組轉爲Integer數組
//Integer[] integer2 = new Integer[arrayList.size()];
//integer2 = arrayList.toArray();
System.out.println();

// 4.數組 --> 集合:調用Arrays類的靜態方法asList()
List<String> list = Arrays.asList(new String[]{"AA", "BB", "CC"});
System.out.println(list);	// [AA, BB, CC]
// 當是int[]時,會當成一個int數組對象
List arr1 = Arrays.asList(new int[]{123, 456});
System.out.println(arr1.size());	// 1
// 包裝類數組時,會當成多個元素
List arr2 = Arrays.asList(new Integer[]{123, 456});
System.out.println(arr2.size());	// 2

Person類:重寫equals()方法

// Person對象重寫equals方法
public class Person {
    String name;
    int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age &&
                Objects.equals(name, person.name);
    }
}

三.集合遍歷

1.Iterator接口

Iterator對象稱爲迭代器(設計模式的一種),主要用於遍歷 Collection 集合中的元素

Collection接口繼承了java.lang.Iterable接口,該接口有一個iterator()方法,那麼所有實現了Collection接口的集合類都有一個iterator()方法,用以返回一個實現了Iterator接口的對象

  • 內部的方法:hasNext()判斷是否還有下一個元素next()指針下移並將指向的元素返回
  • 集合對象每次調用iterator()方法都得到一個全新的迭代器對象,默認遊標都在集合的第一個元素之前
  • 內部定義了remove(),可以在遍歷的時候,刪除集合中的元素。此方法不同於集合直接調用remove()

示例:

Collection coll = new ArrayList();
coll.add(123);
coll.add(new Person("Jerry",20));
coll.add(new String("Tom"));

Iterator iterator = coll.iterator();
//hasNext():判斷是否還有下一個元素
while(iterator.hasNext()){
    //next():1.指針下移 2.將下移以後集合位置上的元素返回
    System.out.print(iterator.next()+" ");	//123 Person{name='Jerry', age=20} Tom
}

//錯誤方式:
//集合對象每次調用iterator()方法都得到一個全新的迭代器對象,默認遊標都在集合的第一個元素之前。
while (coll.iterator().hasNext()){	// 死循環
    System.out.print(coll.iterator().next());	// 123 123 123 ...
}

//遍歷過程中刪除集合中"Tom"元素
Iterator iterator = coll.iterator();
while (iterator.hasNext()){
    //iterator.remove();	//指針沒有下移就remove()會報IllegalStateException
    Object obj = iterator.next();
    if("Tom".equals(obj)){
        iterator.remove();
    }
}
//刪除"Tom"之後再遍歷集合
iterator = coll.iterator();
while (iterator.hasNext()){
    System.out.println(iterator.next());	//123 Person{name='Jerry', age=20}
}

2.增強for循環

遍歷集合:for(集合元素的類型 局部變量 : 集合對象)

Collection coll = new ArrayList();
coll.add(123);
coll.add(new Person("Jerry",20));
coll.add(new String("Tom"));
coll.add(false);

//for(集合元素的類型 局部變量 : 集合對象)
//內部仍然調用了迭代器。
for(Object obj : coll){
    System.out.println(obj);
}

遍歷數組:for(數組元素的類型 局部變量 : 數組對象)

int[] arr = new int[]{1,2,3,4,5,6};
//for(數組元素的類型 局部變量 : 數組對象)
for(int i : arr){
    System.out.println(i);
}

注意:數組賦值時不能使用增強for循環

String[] arr = new String[]{"MM","MM","MM"};

//    //方式一:普通for賦值
//    for(int i = 0;i < arr.length;i++){
//        arr[i] = "GG";
//    }

//方式二:增強for循環
for(String s : arr){
    s = "GG";	// s是局部變量,存儲在棧中,並不是原數組的引用
}

for(int i = 0;i < arr.length;i++){
    System.out.print(arr[i]+" ");	// MM MM MM
}

四.List接口

List集合類中 元素有序、且可重複,集合中的每個元素都有其對應的順序索引

JDK API中List接口的實現類常用的有:ArrayList、LinkedList和 Vector

LIst除了從Collection集合繼承的方法外,List集合裏添加了一些根據索引來操作集合元素的方法.

插:

  • void add(int index, Object ele) : 在 index 位置插入ele 元素
  • boolean addAll(int index, Collection eles) : 從index 位置開始將eles 中的所有元素添加進來

刪:

  • Object remove(int index) : 移除指定index 位置的元素,並返回此元素

改:

  • Object set(int index, Object ele) : 設置指定index 位置的元素爲ele

查:

  • Object get(int index) : 獲取指定index 位置的元素
  • int indexOf(Object obj): 返回obj 在集合中首次出現的位置,如果不存在,返回-1.
  • int lastIndexOf(Object obj) : 返回obj 在當前集合中末次出現的位置,如果不存在,返回-1.
  • List subList(int fromIndex, int toIndex) : 返回從fromIndex 到toIndex位置的左閉右開區間的子集合

1.ArrayList

ArrayList 是 List 接口的主要實現類,本質上,ArrayList是對象引用的一個"變長"數組,容量能動態增長.

ArrayList 的JDK1.8 之前與之後的實現區別?

  • JDK1.7:ArrayList像餓漢式,直接創建一個初始容量爲10的數組
  • JDK1.8:ArrayList像懶漢式,一開始創建一個長度爲0的數組,當添加第一個元素時再創建一個初始容量爲10的數組

區分List中remove(int index)和remove(Object obj) ?

List list = new ArrayList();
list.add(1);	// 自動裝箱
list.add(2);
list.add(3);
updateList(list);
System.out.println(list);	// [1, 3]
// 判斷list是否包含3,contains沒有索引的形參
System.out.println(list.contains(3));	// true

private void updateList(List list) {
  // list.remove(2);	// 2代表索引,先根據索引
  list.remove(new Integer(2));	// 2代表對象
}

更多內容詳看 : 源碼學習 – > ArrayList

2.LinkedList

雙向鏈表內部沒有聲明數組,而是定義了Node類型的first和last,用於記錄首末元素。定義內部類Node,作爲LinkedList中保存數據的基本結構。

Node除了保存數據,還定義了兩個變量:prev變量記錄前一個元素的位置; next變量記錄下一個元素的位置.

在這裏插入圖片描述

	// 雙向鏈表
    private static class Node<E> {    // 內部類:只有當前類需要使用,外面類不需要使用時
        E item;        // 泛型,obj對象數據
        Node<E> next;    // 下一個元素節點地址:指向下一個元素的指針
        Node<E> prev;    // 上一個元素節點地址:指向上一個元素的指針

        // 元素節點分爲三部分:上節點地址,obj對象數據,下節點地址
        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

對於 頻繁的插入或刪除元素的操作,建議使用LinkedList類,效率較高.

新增方法:

  • void addFirst(Object obj)
  • void addLast(Object obj)
  • Object getFirst()
  • Object getLast()
  • Object removeFirst()
  • Object removeLast()

更多內容詳看 : 源碼學習 – > LinkedList

3.Vector

Vector 是一個古老的集合,JDK1.0就有了。大多數操作與ArrayList相同,區別之處在於Vector是線程安全的.

在各種list中,最好把ArrayList作爲默認選擇。當插入、刪除頻繁時,使用LinkedList;Vector總是比ArrayList慢,所以儘量避免使用。

更多內容詳看 : 源碼學習 --> ArrayList --> Vector

4.三者聯繫與區別

聯繫:

  • 都實現了List接口, 存儲有序的、可重複的數據
  • add自定義類時,該類需要重寫equals方法(contains,remove等方法以equals爲標準判斷的)

區別:

  • ArrayList: 底層使用動態數組結構; 線程不安全,效率高; 默認創建空列表,添加第一個元素時,分配10個大小的空間,每次擴容爲原來的1.5倍;對於隨機訪問get和set操作效率高於LinkedList
  • LinkedList: 底層使用雙向鏈表結構; 線程不安全; 對於頻繁的插入, 刪除操作效率比ArrayList高
  • Vector: 底層使用動態數組結構; 線程安全,效率低; 默認創建10個大小的數組,每次擴容爲原來的2倍

五.Set接口

Set接口是Collection的子接口,set接口沒有提供額外的方法,使用的都是Collection中聲明過的方法。存儲無序的、不可重複的數據,類似於高中數學中的"集合"。

以HashSet爲例:

  • 無序性:不等於隨機性。存儲的數據在底層數組中並非按照數組索引的順序添加,而是根據數據的哈希值決定的
  • 不可重複性:保證添加的元素按照equals()判斷時,不能返回true.即:相同的元素只能添加一個

向Set(主要指:HashSet、LinkedHashSet)中添加的數據,其所在的類一定要重寫hashCode()和equals(),重寫的hashCode()和equals()儘可能保持一致性:相等的對象必須具有相等的散列碼。 Set 判斷兩個對象是否相同不是使用 == 運算符,而是根據 equals() 方法

HashSet和TreeSet是Set接口的實現類,LinkedHashSet是HashSet的子類。

  • HashSet:作爲Set接口的主要實現類;線程不安全的;可以存儲null值
  • LinkedHashSet:作爲HashSet的子類;遍歷其內部數據時,可以按照添加的順序遍歷;對於頻繁的遍歷操作,LinkedHashSet效率高於HashSet
  • TreeSet:可以按照添加對象的指定屬性,進行排序

1.HashSet

1.1 概述

HashSet 是 Set接口的主要實現,HashSet 按Hash算法來存儲集合中的元素,因此具有很好的存取、查找、刪除性能。底層是 數組+鏈表結構。數組初始容量爲16,當如果使用率超過0.75,(16 * 0.75=12)就會擴大容量爲原來的2倍。(16擴容爲32,依次爲64,128…等)。

特點:不能保證元素的排列順序、不是線程安全的、集合元素可以是null。

判斷兩個元素相等的標準:兩個對象通過 hashCode() 方法比較相等,並且兩個對象的 equals() 方法返回值也相等。

對於存放在Set容器中的對象, 對應的類一定要重寫equals() 和hashCode(Object obj) 方法,以實現對象相等規則 。即: “相等的對象必須具有相等的散列碼” 。

1.2 添加元素過程

在這裏插入圖片描述

向HashSet中添加元素的過程(重要):

首先調用元素a所在類的hashCode()方法,計算元素a的哈希值,此哈希值接着通過某種算法計算出在HashSet底層數組中的存放位置(即爲:索引位置),判斷數組此位置上是否已經有元素:

  • 如果此位置上沒有其他元素,則元素a添加成功;
  • 如果此位置上有其他元素b(或以鏈表形式存在的多個元素),則比較元素a與元素b的hash值
    • 如果hash值不相同,則元素a添加成功;
    • 如果hash值相同,進而需要調用元素a所在類的equals()方法
      • equals()返回true,元素a添加失敗;
      • equals()返回false,則元素a添加成功。

元素a 與已經存在指定索引位置上數據以鏈表的方式存儲。可以用"七上八下"來形容。

  • JDK7:元素a放到數組中,指向原來的元素
  • JDK8:原來的元素在數組中,指向元素a

如果兩個元素的 equals() 方法返回 true,但它們的 hashCode() 返回值不相等,hashSet 將會把它們存儲在不同的位置,但依然可以添加成功。

示例:

//其中Person 類中重寫了hashCode() 和equal() 方法
HashSet set = new HashSet();
Person p1 = new Person(1001,"AA");
Person p2 = new Person(1002,"BB");

set.add(p1);
set.add(p2);
// [Person{id=1002, name='BB'}, Person{id=1001, name='AA'}]
System.out.println(set);

p1.name = "CC";
set.remove(p1);	// 由於p1的name變化導致hash值變化了,去查找對應的存儲位置是null的
// [Person{id=1002, name='BB'}, Person{id=1001, name='CC'}]
System.out.println(set);    

set.add(new Person(1001,"CC"));	// p1原先的hash值是由"AA"算出來的
// [Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, 
// Person{id=1001, name='CC'}]
System.out.println(set);

set.add(new Person(1001,"AA")); // hash值與p1相同,但equals時是false的
// [Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, 
// Person{id=1001, name='CC'}, Person{id=1001, name='AA'}]
System.out.println(set);

圖示:

在這裏插入圖片描述

1.3 重寫方法原則

一般用idea自動生成的重寫方法hashCode和equals方法就可以了,複寫的hashCode方法有31這個數字。

重寫hashCode方法:

  • 同一個對象多次調用 hashCode() 方法應該返回相同的值
  • 當兩個對象的 equals() 方法比較返回 true 時,這兩個對象的 hashCode()方法的返回值也應相等
  • 對象中用作 equals() 方法比較的 Field,都應該用來計算 hashCode 值

重寫equals方法:

  • 當改寫equals方法時,總要改寫hashCode方法;
  • 相等的對象必須具有相等的散列碼
  • 參與計算hashCode 的對象的屬性也應該參與到equals() 中進行計算

1.4 示例

Set set = new HashSet();
set.add(456);
set.add(123);
set.add(123);
set.add("AA");
set.add("CC");
set.add(new User("Tom",12));
set.add(new User("Tom",12));
set.add(129);

// 若不重寫hashCode方法,則用父類Object的hashCode方法,hash值是隨機生成的
// 先判斷hash值,hash值不同,兩個user對象都會添加到集合中
Iterator iterator = set.iterator();
while(iterator.hasNext()){
    // AA	CC	129	456	123	User{name='Tom', age=12}
    System.out.print(iterator.next()+"\t");
}

Person類:

public class User implements Comparable{
    private String name;
    private int age;

    public User() {
    }
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        System.out.println("User equals()....");
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        User user = (User) o;

        if (age != user.age) return false;
        return name != null ? name.equals(user.name) : user.name == null;
    }
    @Override
    public int hashCode() { //return name.hashCode() + age;
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        return result;
    }

    //按照姓名從大到小排列,年齡從小到大排列
    @Override
    public int compareTo(Object o) {
        if(o instanceof User){
            User user = (User)o;
//            return -this.name.compareTo(user.name);
            int compare = -this.name.compareTo(user.name);
            if(compare != 0){
                return compare;
            }else{
                return Integer.compare(this.age,user.age);
            }
        }else{
            throw new RuntimeException("輸入的類型不匹配");
        }

    }
}

使用HashSet去除List重複數字值:

public List duplicateList(List list) {
    HashSet set = new HashSet();
    set.addAll(list);
    return new ArrayList(set);
}

2.LinkedHashSet

LinkedHashSet 是 HashSet 的子類。根據元素的 hashCode 值來決定元素的存儲位置,在添加數據的同時,每個數據還維護了兩個引用,記錄此數據前一個數據和後一個數據。對於頻繁的遍歷操作,效率高於HashSet

底層結構:

在這裏插入圖片描述

示例:

Set set = new LinkedHashSet();
set.add(456);
set.add(123);
set.add(123);
set.add("AA");
set.add("CC");
set.add(new User("Tom",12));
set.add(new User("Tom",12));
set.add(129);

// 打印輸出看似有序,是由於每個數據還維護了兩個引用
// 存儲位置是由hash值決定的
Iterator iterator = set.iterator();
while(iterator.hasNext()){
    //456	123	AA	CC	User{name='Tom', age=12}	129	
    System.out.print(iterator.next()+"\t");
}

3.TreeSet

TreeSet 是 SortedSet 接口的實現類,TreeSet 可以確保集合元素處於排序狀態。TreeSet底層使用 紅黑樹結構存儲數據。有序,查詢速度比List快。向TreeSet中添加的數據,要求是相同類的對象

紅黑樹介紹:平衡查找樹之紅黑樹

結構:

在這裏插入圖片描述

TreeSet 兩種排序方法: 自然排序和 定製排序。默認情況下,TreeSet 採用自然排序。

  • 自然排序中,比較兩個對象是否相同的標準爲:compareTo()返回0.不再是equals().
  • 定製排序中,比較兩個對象是否相同的標準爲:compare()返回0.不再是equals().

3.1 自然排序

TreeSet 會調用集合元素的 compareTo(Object obj) 方法來比較元素之間的大小關係,然後將集合元素按升序(默認情況)排列。

如果試圖把一個對象添加到 TreeSet 時,則該對象的類必須實現 Comparable接口。實現 Comparable 的類必須實現 compareTo(Object obj) 方法,兩個對象即通過compareTo(Object obj) 方法的返回值來比較大小。

示例:

TreeSet set = new TreeSet();

set.add(new User("Tom",12));
set.add(new User("Jerry",32));
set.add(new User("Jim",2));
set.add(new User("Mike",65));
set.add(new User("Jack",33));
set.add(new User("Jack",56));

Iterator iterator = set.iterator();
while(iterator.hasNext()){
    System.out.println(iterator.next());
}
//        User{name='Tom', age=12}
//        User{name='Mike', age=65}
//        User{name='Jim', age=2}
//        User{name='Jerry', age=32}
//        User{name='Jack', age=33}
//        User{name='Jack', age=56}

// User類
public class User implements Comparable {
    //按照姓名從大到小排列,年齡從小到大排列
    @Override
    public int compareTo(User u) {
        int compare = -this.name.compareTo(u.name);
        if(compare != 0){
            return compare;
        }else {
            return Integer.compare(this.age,u.age);
        }
    }
}

3.2 定製排序

定製排序,通過Comparator接口來實現。需要重寫compare(T o1,T o2)方法。將實現Comparator接口的實例作爲形參傳遞給TreeSet的構造器。向TreeSet中只能添加類型相同的對象。否則發生ClassCastException異常。

示例:

Comparator com = new Comparator() {
    //按照年齡從小到大排列
    @Override
    public int compare(User u1, User u2) {
        return Integer.compare(u1.getAge(),u2.getAge());
    }
};

TreeSet set = new TreeSet(com);
set.add(new User("Tom",12));
set.add(new User("Jerry",32));
set.add(new User("Jim",2));
set.add(new User("Mike",65));
set.add(new User("Mary",33));
set.add(new User("Jack",33));
set.add(new User("Jack",56));

Iterator iterator = set.iterator();
while(iterator.hasNext()){
    System.out.println(iterator.next());
}

六.Map接口

在這裏插入圖片描述

1.Map與Collection並列存在。用於保存具有映射關係的數據:key-value。key和value可以是任何引用類型的數據。 Map接口的常用實現類:HashMap、TreeMap、LinkedHashMap和
Properties

  • HashMap:作爲Map的主要實現類;線程不安全的,效率高;可以存儲null的key和value
  • LinkedHashMap:保證在遍歷map元素時,可以按照添加的順序實現遍歷。原因:在原有的HashMap底層結構基礎上,添加了一對指針,指向前一個和後一個元素。對於頻繁的遍歷操作,此類執行效率高於HashMap
  • TreeMap:保證按照添加的key-value對進行排序,實現排序遍歷。此時考慮key的自然排序或定製排序。底層使用紅黑樹
  • Hashtable:作爲古老的實現類;線程安全的,效率低;不能存儲null的key或value
  • Properties:常用來處理配置文件key和value都是String類型

2.Map常用方法:

  • 添加:
    • Object put(Object key,Object value)將指定key-value添加到(或修改)當前map對象中
    • void putAll(Map m):將m中的所有key-value對存放到當前map中
  • 刪除:
    • Object remove(Object key)移除指定key的key-value對,並返回value
    • void clear():清空當前map中的所有數據
  • 修改:
    • Object put(Object key,Object value):將指定key-value添加到(或修改)當前map對象中
  • 查詢:
    • Object get(Object key)獲取指定key對應的value
    • int size()返回map中key-value對的個數
  • 判斷:
    • boolean containsKey(Object key)是否包含指定的key
    • boolean containsValue(Object value):是否包含指定的value
    • boolean isEmpty()判斷當前map是否爲空
    • boolean equals(Object obj)判斷當前map和參數對象obj是否相等
  • 遍歷:
    • Set keySet()返回所有key構成的Set集合
    • Collection values()返回所有value構成的Collection集合
    • Set entrySet()返回所有key-value對構成的Set集合

1.HashMap

HashMap是Map接口的主要實現類。允許使用null鍵和null值。

1.1 結構理解

  • key無序的、不可重複的,使用Set存儲所有的key --> key所在的類要重寫equals()和hashCode()
  • value無序的、可重複的,使用Collection存儲所有的value --> value所在的類要重寫equals()
  • 鍵值對:key-value構成了一個Entry對象
  • entry無序的、不可重複的,使用Set存儲所有的entry

即判斷兩個key相等的標準:hashCode相等且equals相等;判斷value相等的標準:equals相等。

1.2 重要常量

  • DEFAULT_INITIAL_CAPACITY : 默認容量:16
  • DEFAULT_LOAD_FACTOR:默認加載因子:0.75
  • threshold:擴容的臨界值(容量*填充因子):16 * 0.75 => 12
  • TREEIFY_THRESHOLD:Bucket(桶)中鏈表長度大於該默認值,轉化爲紅黑樹:8
  • MIN_TREEIFY_CAPACITY:桶中的Node被樹化時最小的hash表容量:64

1.3 底層實現

JDK7:

  • new HashMap():創建了一個長度爲16的Entry[] table數組
  • 數組+鏈表(形成鏈表時:新的元素指向舊的元素)

JDK8:

  • new HashMap()沒有創建一個長度爲16的數組,首次調用put()方法,底層創建成都爲16的 Node[] 數組
  • 數組+鏈表+紅黑樹(形成鏈表時:舊的元素指向新的元素)
  • 當數組的某一個索引位置上的元素以鏈表形式存在的數據個數(大於8) 且當前數組的長度(大於64)時,此索引位置上的所有數據改爲使用紅黑樹存儲

1.4 添加元素的過程(JDK7)

向HashMap中添加entry1(key,value),需要首先計算entry1中key的哈希值(根據key所在類的hashCode()計算得到),此哈希值經過處理以後,得到在底層Entry[]數組中要存儲的位置i

  • 如果位置i上沒有元素,則entry1直接添加成功。
  • 如果位置i上已經存在entry2(或還有鏈表存在的entry3,entry4),則需要通過循環的方法,依次比較entry1中key和其他的entry。
    • 如果彼此hash值不同,則直接添加成功。
    • 如果hash值相同,繼續比較二者是否equals
      • 如果返回值爲true,則使用entry1的value去替換equals爲true的entry的value。
      • 如果遍歷一遍以後,發現所有的equals返回都爲false,則entry1仍可添加成功。entry1指向原有的entry元素。

1.5 數組擴容

JDK7:

在不斷的添加過程中,會涉及到擴容問題,**當超出臨界值12(且要存放的位置非空)**時,擴容。默認的擴容方式:擴容爲原來容量的2倍,並將原有的數據複製過來

HashMap數組擴容後,原數組中的數據必須重新計算其在新數組中的位置,並放進去,這就是resize方法。比較消耗性能。

JDK8:

擴容:當HashMap中元素個數超過16 * 0.75=12(這個值就是代碼中的threshold值,也叫做臨界值)的時候,就把數組的大小擴展爲 2*16=32,即擴大一倍,然後重新計算每個元素在數組中的位置,並放進去。

樹形化:當HashMap中的其中一個鏈的對象個數如果達到了8個,此時如果數組capacity沒有達到64,那麼HashMap會先擴容解決,如果已經達到了64,那麼這個鏈會變成樹,結點類型由Node變成TreeNode類型。

1.6 負載因子值

  • 負載因子的大小決定了HashMap的數據密度
  • 負載因子越大密度越大,發生碰撞的機率越高,數組中的鏈表越容易長,造成查詢或插入時的比較次數增多,性能會下降
  • 負載因子越小,就越容易觸發擴容,數據密度也越小,意味着發生碰撞的機率越小,數組中的鏈表也就越短,查詢和插入時比較的次數也越小,性能會更高。但是會浪費一定的內存空間。而且經常擴容也會影響性能
  • 按照語言參考及研究經驗,會將負載因子設置爲 0.7~0.75,此時平均檢索長度接近於常數

1.7 示例

Map map = new HashMap();
map.put("AA",123);
map.put(45,123);
map.put("BB",56);
map.put(null,76);

System.out.println(map.get(45));	// 123

Object value = map.remove("AA");
System.out.println(value);	// 123 

boolean isExist = map.containsKey("BB");
System.out.println(isExist);	// true
isExist = map.containsValue(123);
System.out.println(isExist);	// true

map.clear();
System.out.println(map.isEmpty());	// true

//遍歷時:通常使用增強for循環
//遍歷所有的key集:keySet()
Set set = map.keySet();
Iterator iterator = set.iterator();
while(iterator.hasNext()){
    System.out.println(iterator.next());
}
//遍歷所有的value集:values()
Collection values = map.values();
for(Object obj : values){
    System.out.println(obj);
}
//遍歷所有的key-value
//方式一:entrySet()
Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
while (iterator1.hasNext()){
    Object obj = iterator1.next();
    //entrySet集合中的元素都是entry
    Map.Entry entry = (Map.Entry) obj;
    System.out.println(entry.getKey() + "---->" + entry.getValue());
}
//方式二:
Set keySet = map.keySet();
Iterator iterator2 = keySet.iterator();
while(iterator2.hasNext()){
    Object key = iterator2.next();
    Object value = map.get(key);
    System.out.println(key + "=====" + value);
}

更多內容詳看 : 源碼學習 – > HashMap

2.LinkedHashMap

LinkedHashMap 是 HashMap 的子類。

在HashMap存儲結構的基礎上,使用了一對雙向鏈表來記錄添加元素的順序。迭代遍歷時:順序與添加順序一致

// HashMap內部類:Node
static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;
}

// LinkedHashMap內部類:Entry 繼承 Node
static class Entry<K,V> extends HashMap.Node<K,V> {
    Entry<K,V> before, after;	// 前驅節點和後繼節點:記錄添加元素的順序
    Entry(int hash, K key, V value, Node<K,V> next) {
        super(hash, key, value, next);
    }
}

3.TreeMap

TreeMap底層使用紅黑樹結構存儲數據。可以保證所有的 Key-Value 對處於有序狀態。向TreeMap中添加key-value,要求key必須是由同一個類創建的對象

按照key進行排序:

  • 自然排序:所有key必須實現Comparable接口,所有key應是同一個類的對象,否則拋出ClassCastException
  • 定製排序:TreeMap構造器中傳入一個Comparator對象,該對象負責對所有key進行排序,此時不需要Key所在類實現Comparable接口

判斷兩個key相等的標準:兩個key通過compareTo()方法或者compare()方法返回0

自然排序示例:

TreeMap map = new TreeMap();
User u1 = new User("Tom",23);
User u2 = new User("Jerry",32);
User u3 = new User("Jack",20);
User u4 = new User("Rose",18);

map.put(u1,98);
map.put(u2,89);
map.put(u3,76);
map.put(u4,100);

// 遍歷打印
Set<User> keys = map.keySet();
for (User key : keys) {
    System.out.print(key + ":" + map.get(key)+"   ");

}

// User類:實現 Comparable接口
public class User implements Comparable{
	private String name;
    private int age;
    
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    //按照姓名從大到小排列,年齡從小到大排列
    @Override
    public int compareTo(User u) {
        if(this.name.equals(u.name)){
            return Integer.compare(this.age,U.age);
        }else{
            return -this.name.compareTo(u.name);
        }
    }
}    

定製排序示例:

TreeMap map = new TreeMap(new Comparator() {
    @Override
    public int compare(User u1, User u2) {
        return Integer.compare(u1.getAge(), u2.getAge());
    }
});

User u1 = new User("Tom",23);
User u2 = new User("Jerry",32);
User u3 = new User("Jack",20);
User u4 = new User("Rose",18);

map.put(u1,98);
map.put(u2,89);
map.put(u3,76);
map.put(u4,100);

// 遍歷打印
Set<User> keys = map.keySet();
for (User key : keys) {
    System.out.print(key + ":" + map.get(key)+"   ");
}

4.Hashtable與Properties

1.Hashtable比較古老了,JDK1.0就提供了。Hashtable實現原理和HashMap相同,功能相同。判斷兩個key或value值相等的標準也一致。

HashMap與Hashtable區別:

  • HashMap線程不安全,Hashtable線程安全
  • HashMap允許使用null作爲key或value,Hashtable不允許

2.Properties 類是 Hashtable 的子類,該對象用於處理配置文件。由於屬性文件裏的 key、value 都是字符串類型,所以 Properties 裏的 key和 value 都是字符串類型

存取數據時,建議使用setProperty(String key,String value)方法和getProperty(String key)方法:

Properties pros = new Properties();

FileInputStream fis = new FileInputStream("jdbc.properties");
pros.load(fis);//加載流對應的文件

String name = pros.getProperty("name");
System.out.println("name = " + name);

七.Collections工具類

Collections 是一個操作 Collection 和 Map 等集合的工具類。Collections 中提供了一系列靜態的方法對集合元素進行排序、查詢和替換等操作,還提供了對集合對象設置不可變、對集合對象實現同步控制等方法。

1.排序操作

方法 描述
reverse(List) 反轉 List 中元素的順序
shuffle(List) 對 List 集合元素進行隨機排序
sort(List) 根據元素的自然順序對指定 List 集合元素按升序排序
sort(List, Comparator) 根據指定的 Comparator 產生的順序對 List 集合元素進行排序
swap(List, int, int) 將指定 list 集合中的 i 處元素和 j 處元素進行交換

示例:

List list = new ArrayList();
list.add(56);
list.add(12);
list.add(34);
list.add(89);
list.add(-53);
list.add(0);
list.add(34);
// 反轉
Collections.reverse(list);
System.out.println(list);
// 隨機排序
Collections.shuffle(list);
System.out.println(list);
// 兩個索引位置的元素進行交換
Collections.swap(list,1,2);
System.out.println(list);

// Collections自然排序和定製排序方法底層都還是調用的 Arrays.sort(a, (Comparator) c)方法
// 自然排序
Collections.sort(list);
System.out.println(list);

// List接口中sort默認方法部分源碼(JDK8中接口新特性)
default void sort(Comparator<? super E> c) {
    Object[] a = this.toArray();	// 將集合轉爲數組
    Arrays.sort(a, (Comparator) c);	// 調用Arrays的sort方法
    
    ...
}

2.查找、替換操作

方法 描述
Object max(Collection) 根據元素的自然順序,返回給定集合中的最大元素
Object max(Collection,Comparator) 根據 Comparator 指定的順序,返回給定集合中的最大元素
Object min(Collection)
Object min(Collection,Comparator)
int frequency(Collection,Object) 返回指定集合中指定元素的出現次數
void copy(List dest,List src) 將src中的內容複製到dest中
boolean replaceAll(List list,Object oldVal,Object newVal) 使用新值替換List 對象的所有舊值

示例:

// 元素出現次數
int frequency = Collections.frequency(list, 34);
System.out.println(frequency);

// 複製
//報異常:IndexOutOfBoundsException("Source does not fit in dest")
// List dest = new ArrayList();
// Collections.copy(dest,list);
//正確的:目標集合size大小 大於等於 原集合size大小
List dest = Arrays.asList(new Object[list.size()]);
System.out.println(dest.size());//list.size();
Collections.copy(dest,list);

// Collections中void copy(List dest,List src)方法源碼
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
    int srcSize = src.size();
    // 若原集合size大小 大於 目標集合size大小(並不是集合初始化容量大小),則拋異常
    if (srcSize > dest.size())	
        throw new IndexOutOfBoundsException("Source does not fit in dest");

    ...
}

3.同步控制操作

Collections 類中提供了多個 synchronizedXxx() 方法,該方法可使將指定集合包裝成線程同步的集合,從而可以解決多線程併發訪問集合時的線程安全問題。

List list = new ArrayList();
list.add(56);
list.add(12);
//返回的list2即爲線程安全的List
List list2 = Collections.synchronizedList(list);

八.源碼學習

1.ArrayList

ArrayList 底層是數組隊列,相當於動態數組.與 Java 中的數組相比,它的容量能動態增長.線性表的順序存儲,插入刪除元素的時間複雜度爲O(n),求表長以及增加元素,取第 i 元素的時間複雜度爲O(1) .

它繼承於 AbstractList,實現了 List, RandomAccess, Cloneable, java.io.Serializable 這些接口。

  • ArrayList 繼承了AbstractList,實現了List。它是一個數組隊列,提供了相關的添加、刪除、修改、遍歷等功能。
  • ArrayList 實現了RandomAccess 接口, RandomAccess 是一個標誌接口,表明實現這個這個接口的 List 集合是支持快速隨機訪問的。在 ArrayList 中,我們即可以通過元素的序號快速獲取元素對象,這就是快速隨機訪問。
  • ArrayList 實現了Cloneable 接口,即覆蓋了函數 clone(),能被克隆
  • ArrayList 實現java.io.Serializable 接口,這意味着ArrayList支持序列化能通過序列化去傳輸
  • 和 Vector 不同,ArrayList 中的操作不是線程安全的!所以,建議在單線程中才使用 ArrayList,而在多線程中可以選擇 Collections中的靜態方法 List<T> synchronizedList(List<T> list) 將其轉爲線程安全的。

1.1 JDK7

ArrayList是List接口的可變數組的實現。實現了所有可選列表操作,並允許包括 null 在內的所有元素。除了實現 List 接口外,此類還提供一些方法來操作內部用來存儲列表的數組的大小。

1.1.1 核心源碼
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
		
	/**
	* 1.屬性
	*/
	
	// 底層使用數組實現	
	private transient Object[] elementData;	// 聲明對象數組變量

	/**
	* 2.構造方法
	*/

	// 構造一個默認初始容量爲 10 的空列表
	public ArrayList() {
        this(10);
    }
	// 構造一個指定初始容量的空列表
	public ArrayList(int initialCapacity) {	// 10
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];	// 分配空間10
    }
    // 構造一個包含指定 collection 的元素的列表
    // 這些元素按照該 collection 的迭代器返回它們的順序排列的
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        size = elementData.length;
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    }
    
    /**
    * 3.存儲
    */
    
    // 用指定的元素替代此列表中指定位置上的元素,並返回以前位於該位置上的元素。
    public E set(int index, E element) {
    	RangeCheck(index);

    	E oldValue = (E) elementData[index];
    	elementData[index] = element;
    	return oldValue;
    }
    // 將指定的元素添加到此列表的尾部。
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
	// 將指定的元素插入此列表中的指定位置。
	// 如果當前位置有元素,則向右移動當前位於該位置的元素以及所有後續元素(將其索引加 1)。
    public void add(int index, E element) {
        rangeCheckForAdd(index);
		// 如果數組長度不足,將進行擴容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
         // 將 elementData 中從 Index 位置開始、長度爲 size-index 的元素,
		// 拷貝到從下標爲 index+1 位置開始的新的 elementData 數組中。
		// 即將當前位於該位置的元素以及所有後續元素右移一個位置。
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }
    // 從指定的位置開始,將指定 collection 中的所有元素插入到此列表中。
    public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount

        int numMoved = size - index;
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);

        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }
    
    /**
    * 4.讀取
    */
    
    // 返回此列表中指定位置上的元素。
    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }
    
    /**
    * 5.刪除
    * ArrayList 提供了根據下標或者指定對象兩種方式的刪除功能
    */
	
	// 移除此列表中指定位置上的元素。
	public E remove(int index) {
        rangeCheck(index);
		// 快速失敗的機制,通過記錄 modCount 參數來實現
        modCount++;	
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // Let gc do its work

        return oldValue;
    }
    // 移除此列表中首次出現的指定元素(如果存在)。這是應爲ArrayList中允許存放重複的元素。
    public boolean remove(Object o) {
    	// 由於 ArrayList 中允許存放 null,因此下面通過兩種情況來分別處理。
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                	// 類似 remove(int index),移除列表中指定位置上的元素。
                    fastRemove(index);	
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }
	
}
1.1.2 底層擴容
	// 添加元素舉例
	public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // 有效長度+1
        elementData[size++] = e;
        return true;
    }
	private void ensureCapacityInternal(int minCapacity) {	// 確認實際容量
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)	// 當實際容量大於10時,進行擴容
            grow(minCapacity);
    }
	
	private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
	
	private void grow(int minCapacity) {	// 11
        // overflow-conscious code
        int oldCapacity = elementData.length;	// 10
        int newCapacity = oldCapacity + (oldCapacity >> 1);	// 10 + 10/2 相當於原先默認的1.5倍
        if (newCapacity - minCapacity < 0)	// 若擴容後比實際容量還小
            newCapacity = minCapacity;	// 賦值爲實際容量
        if (newCapacity - MAX_ARRAY_SIZE > 0)	// 若擴容後比(Integer.MAX_VALUE-8)還大
            newCapacity = hugeCapacity(minCapacity);	// 賦值爲Integer.MAX_VALUE
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);	// 賦值爲新的數組,數組長度爲擴容後的長度
    }
	
	private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

數組擴容通過一個公開的方法 ensureCapacity(int minCapacity) 來實現。會將老數組中的元素重新拷貝一份到新的 數組中,每次數組容量的增長大約是其原容量的 1.5 倍。在實際中我們可以在構造 ArrayList 實例時,就指定其容量,以避免數組擴容的發生。或者根據實際需求,通過調用 ensureCapacity 方法來手動增加 ArrayList 實例的容量, 以減少遞增式再分配的數量。

ArrayList 還給我們提供了將底層數組的容量調整爲當前列表保存的實際元素的大小的功能。它可以通過trimToSize 方法來實現。

	public void trimToSize() {
        modCount++;
        int oldCapacity = elementData.length;
        if (size < oldCapacity) {
            elementData = Arrays.copyOf(elementData, size);
        }
    }

1.2 JDK8

ArrayList 是一個動態數組,實現了 List 接口以及 list 相關的所有方法,它允許所有元素的插入,包括 null。另外,ArrayList 和 Vector 除了線程不同步之外,大致相等。

JDK8 : 默認構造空實例,大小是 0;只有在使用到時,纔會通過grow方法創建一個大小爲 10 的數組

JDK7 : 默認構造初始容量爲 10 的空列表

在擴容上代碼優點區別,但原理都是相同的,都是原容量的1.5倍.

1.2.1 核心源碼
// @since   1.2
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    
    /**
    * 1.屬性
    * ArrayList所有的方法都是建立在 elementData 屬性之上。
    */
    
	// 默認容量的大小
    private static final int DEFAULT_CAPACITY = 10;
    // 空數組常量:用於空實例
    private static final Object[] EMPTY_ELEMENTDATA = {};
    // 默認的空數組常量:用於默認大小空實例的共享空數組實例。
    // 把它從EMPTY_ELEMENTDATA數組中區分出來,以知道在添加第一個元素時容量需要增加多少。
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    // 存放元素的數組,從這可以發現 ArrayList 的底層實現就是一個 Object數組
    transient Object[] elementData;
    // 數組中包含的元素個數
    private int size;
    // 數組的最大上限
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;


	/**
	* 2.構造方法
	* 默認情況下,elementData 是一個大小爲 0 的空數組;
	* 當我們指定了初始大小的時候 elementData 的初始大小就變成了我們所指定的初始大小了
	*/
   
	// 實例化:按照指定大小分配空間
	public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];	// 分配指定長度的數組空間
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;	// 空實例
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
	// 實例化:空實例,大小是 0;只有在使用到時,纔會通過grow方法創建一個大小爲 10 的數組
	public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;	// 空數組實例
    }
    // 形參爲Collection實現類對象:可將set對象轉爲list對象
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
   
   
   /**
   * 3.get(int index)方法
   * 根據索引獲取ArrayList中的元素
   */
   
   // 根據索引獲取集合中元素
   public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }
    // 判斷索引是否越界
    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    // 直接通過數組下表獲取元素
    @SuppressWarnings("unchecked")
    E elementData(int index) {
        return (E) elementData[index];
    }
   

	/**
    * 4.add方法
    * 在插入元素之前,先檢查是否需要擴容,後把元素添加到數組中最後一個元素的後面
    */
	
	// 在數組末尾添加元素,複雜度爲O(1)
	public boolean add(E e) {
        ensureCapacityInternal(size + 1);
        elementData[size++] = e;
        return true;
    }
    // 在索引爲index處插入元素,複雜度爲O(n)
    public void add(int index, E element) {
        rangeCheckForAdd(index);	// 判斷索引是否越界或是否小於0

        ensureCapacityInternal(size + 1);
        // 調用一個 native 的複製方法,把 index 位置開始的元素都往後挪一位
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }
	// 確認是否是空數組
	private void ensureCapacityInternal(int minCapacity) {
		// 當 elementData 爲空數組時,它會使用默認的大小(10)去擴容
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }
	// 確認是否需要擴容
	private void ensureExplicitCapacity(int minCapacity) {
		// 快速失敗的機制,通過記錄 modCount 參數來實現
        modCount++;

        // 實際容量比默認長度大時,進行擴容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    // ArrayList 每次擴容都是擴 1.5 倍
    // 然後調用 Arrays 類的 copyOf 方法,把元素重新拷貝到一個新的數組中去
    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);
        // 初始化,賦值爲新的數組,長度爲10
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    // 比較minCapacity和 MAX_ARRAY_SIZE
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }


	/**
	* 5.set方法
	*/

	// 把下標爲index的元素替換爲element,複雜度爲O(1)
	public E set(int index, E element) {
        rangeCheck(index);	// 判斷索引是否越界

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }
    
    
    /**
    * 6.remove方法
    */
    
    // 刪除索引爲index處的元素,複雜度爲O(n)
    public E remove(int index) {
        rangeCheck(index);	// 判斷索引是否越界

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)	// 調用一個 native 的複製方法,把 index 位置開始的元素都往前挪一位
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }
    
    
    /**
    * 7.size方法
    */
    
    // 返回數組中元素的個數,時間複雜度爲 O(1),返回的並不是數組的實際大小
    public int size() {
        return size;
    }
    
    
    /**
    * 8.indexOf方法和lastIndexOf方法
    */
    
    // 返回從前往後遍歷查找第一個等於給定元素的值的下標,時間複雜度爲O(n)
    public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {	// 從前往後遍歷
            for (int i = 0; i < size; i++)	// 通過遍歷比較數組中每個元素的值來查找的
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }
    // 返回從後往前遍歷查找第一個等於給定元素的值的下標,時間複雜度爲O(n)
    public int lastIndexOf(Object o) {
        if (o == null) {
            for (int i = size-1; i >= 0; i--)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = size-1; i >= 0; i--)	// 從後往前遍歷
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }
    
    
    /**
     * 從列表中刪除所有元素。 
     */
    public void clear() {
        modCount++;

        // 把數組中所有的元素的值設爲null
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }
    
    /**
     *以正確的順序(從第一個到最後一個元素)返回一個包含此列表中所有元素的數組。 
     *返回的數組將是“安全的”,因爲該列表不保留對它的引用。 (換句話說,這個方法必須分配一個新的數
     組)。
     *因此,調用者可以自由地修改返回的數組。 此方法充當基於陣列和基於集合的API之間的橋樑。
     */
    public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }
    
    /**
     * 返回此ArrayList實例的淺拷貝。 (元素本身不被複制。) 
     */
    public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            //Arrays.copyOf功能是實現數組的複製,返回複製後的數組。參數是被複制的數組和複製的長度
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // 這不應該發生,因爲我們是可以克隆的
            throw new InternalError(e);
        }
    }

}
1.2.2 System.arraycopy()和Arrays.copyOf()

System.arraycopty():

 	/**
     * 在此列表中的指定位置插入指定的元素。 
     *先調用 rangeCheckForAdd 對index進行界限檢查;
     *然後調用 ensureCapacityInternal 方法保證capacity足夠大;
     *再將從index開始之後的所有成員後移一個位置;將element插入index位置;最後size加1。
     */
    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //arraycopy()方法實現數組自己複製自己
        //elementData:源數組;index:源數組中的起始位置;
        //elementData:目標數組;index + 1:目標數組中的起始位置; size - index:要複製的數組元素的數量
        System.arraycopy(elementData, index, elementData, index + 1, size - index);
        elementData[index] = element;
        size++;
    }

Arrays.copyOf():

	/**
     *以正確的順序(從第一個到最後一個元素)返回一個包含此列表中所有元素的數組。 
     *返回的數組將是“安全的”,因爲該列表不保留對它的引用。(換句話說,這個方法必須分配一個新的數組)
     *因此,調用者可以自由地修改返回的數組。 此方法充當基於陣列和基於集合的API之間的橋樑。
     */
    public Object[] toArray() {
    //elementData:要複製的數組;size:要複製的長度
        return Arrays.copyOf(elementData, size);
    }

兩個方法的源碼:

// Arrays類
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
  @SuppressWarnings("unchecked")
  T[] copy = ((Object)newType == (Object)Object[].class)	// 新建數組
    ? (T[]) new Object[newLength]
    : (T[]) Array.newInstance(newType.getComponentType(), newLength);
  System.arraycopy(original, 0, copy, 0,	// 拷貝
                   Math.min(original.length, newLength));
  return copy;
}

// System類
public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

聯繫: 看兩者源代碼可以發現copyOf()內部調用了System.arraycopy()

區別:

  • arraycopy()需要目標數組,將原數組拷貝到你自己定義的數組裏,而且可以選擇拷貝的起點和長度以及放入新數組中的位置
  • copyOf()是系統自動在內部新建一個數組,並返回該數組。
1.2.3 內部類
	(1)private class Itr implements Iterator<E>  
    (2)private class ListItr extends Itr implements ListIterator<E>  
    (3)private class SubList extends AbstractList<E> implements RandomAccess  
    (4)static final class ArrayListSpliterator<E> implements Spliterator<E>  

ArrayList有四個內部類,

  • 其中的Itr是實現了Iterator接口,同時重寫了裏面的hasNext()next()remove() 等方法;
  • 其中的ListItr 繼承 Itr,實現了ListIterator接口,同時重寫了hasPrevious()nextIndex()previousIndex()previous()set(E e)add(E e) 等方法;
  • Iterator和ListIterator的區別: ListIterator在Iterator的基礎上增加了添加對象,修改對象,逆向遍歷等方法,這些是Iterator不能實現的。

1.3 Vector

Vector很多方法都跟 ArrayList 一樣,只是多加了個 synchronized 來保證線程安全.部分源碼:

// @since   JDK1.0
public class Vector<E>
    extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable{

	// 每次擴容只擴 capacityIncrement 個空間
	protected int capacityIncrement;
	
	//  Vector 的默認大小也是 10,而且它在初始化的時候就已經創建了數組了
	public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
        this.capacityIncrement = capacityIncrement;
    }
    public Vector(int initialCapacity) {
        this(initialCapacity, 0);
    }
    public Vector() {
        this(10);
    }
    
    // newCapacity 默認情況下是兩倍的 oldCapacity;
    // 當指定了 capacityIncrement 的值之後,newCapacity 變成了oldCapacity+capacityIncrement
    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);
    }

}

1.4 總結

JDK8 ArrayList 與 Vector 比較 :

  • ArrayList 創建時的大小爲 0;當加入第一個元素時,進行第一次擴容時,默認容量大小爲 10。
  • ArrayList 每次擴容都以當前數組大小的 1.5 倍去擴容。
  • Vector 創建時的默認大小爲 10。
  • Vector 每次擴容都以當前數組大小的 2 倍去擴容。當指定了 capacityIncrement 之後,每次擴容僅在原先基礎上增加 capacityIncrement 個單位空間。
  • ArrayList 和 Vector 的 add、get、size 方法的複雜度都爲 O(1),remove 方法的複雜度爲 O(n)。
  • ArrayList 是非線程安全的,Vector 是線程安全的。

參考鏈接: ArrayList 源碼學習

2.LinkedList

LinkedList 是通過一個雙向鏈表來實現的,它允許插入所有元素,包括 null,同時,它是線程不同步的。LinkedList底層的鏈表結構使它支持高效的插入和刪除操作.如果想使LinkedList變成線程安全的,可以調用Collections中的靜態方法 List<T> synchronizedList(List<T> list) 將其轉爲線程安全.

2.1 內部結構

在這裏插入圖片描述

雙向鏈表每個結點除了數據域之外,還有一個前指針和後指針,分別指向前驅結點和後繼結點(如果有前驅/後繼的話)。另外,雙向鏈表還有一個 first 指針,指向頭節點,和 last 指針,指向尾節點。

LinkedList類中的一個內部私有類Node:

	// 雙向鏈表節點: 每個節點的結構
	private static class Node<E> {	// 內部類:只有當前類需要使用,外面類不需要使用時
        E item;		// 泛型,obj對象數據
        Node<E> next;	// 後繼節點;下一個元素節點地址:指向下一個元素的指針
        Node<E> prev;	// 前驅節點;上一個元素節點地址:指向上一個元素的指針

		// 元素節點分爲三部分:前驅節點,本節點的值,後繼結點
        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

2.2 屬性和構造器

// @since 1.2
public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable{
    
    //鏈表的節點個數//指向頭節點的指針
    transient int size = 0;
    //指向頭節點的指針
    transient Node<E> first;
    //指向尾節點的指針
    transient Node<E> last;
    
    // 空構造方法
    public LinkedList() {
    }
    // 用已有的集合創建鏈表的構造方法
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }
}

2.3 添加節點

在表頭或表尾進行插入元素只需要 O(1) 的時間,而在指定位置插入元素則需要先遍歷一下鏈表,所以複雜度爲 O(n)。

2.3.1 表頭添加元素

在這裏插入圖片描述

addFirst(E e), push(E e), offerFirst(E e)

	public void addFirst(E e) {	// 在鏈表頭部添加元素節點
        linkFirst(e);
    }
	public void push(E e) {	// 在鏈表頭部添加元素節點
        addFirst(e);
    }
	public boolean offerFirst(E e) {
        addFirst(e);
        return true;
    }

	private void linkFirst(E e) {
        final Node<E> f = first;
        //當前節點的前驅指向 null,後繼指針原來的頭節點
        final Node<E> newNode = new Node<>(null, e, f);
        first = newNode;	//頭指針指向新的頭節點
        if (f == null)	//如果鏈表爲空,last節點也指向該節點
            last = newNode;
        else	//否則,將頭節點的前驅指針指向新節點,也就是指向前一個元素
            f.prev = newNode;
        size++;
        modCount++;
    }
2.3.2 表尾添加元素

在這裏插入圖片描述

add(E e), addLast(E e), offer(E e), offerLast(E e)

	public boolean add(E e) {	// 在鏈表結尾添加元素節點
        linkLast(e);
        return true;
    }	
	public void addLast(E e) {	// 在鏈表結尾添加元素節點
        linkLast(e);
    }
	public boolean offer(E e) {
        return add(e);
    }
	public boolean offerLast(E e) {
        addLast(e);
        return true;
    }

	void linkLast(E e) {	// 加爲最後節點
        final Node<E> l = last;	// 聲明臨時節點用來存儲last節點
        final Node<E> newNode = new Node<>(l, e, null);	// 新節點指向原先last節點
        last = newNode;		// 新節點作爲最後一個節點
        if (l == null)
            first = newNode;
        else
            l.next = newNode;	// 原先last節點指向新節點(last節點)
        size++;
        modCount++;
    }
2.3.3 指定位置添加元素

在這裏插入圖片描述

add(int index, E element)

	public void add(int index, E element) {	//在指定位置添加元素節點
        checkPositionIndex(index);	//檢查索引是否處於[0-size]之間

        if (index == size)	//添加在鏈表尾部
            linkLast(element);
        else	//添加在鏈表中間
            linkBefore(element, node(index));	//一個插入節點的值,一個指定的node
    }

	void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;	//指定節點的前驅
        //當前節點的前驅爲指點節點的前驅,後繼爲指定的節點
        final Node<E> newNode = new Node<>(pred, e, succ);
        //更新指定節點的前驅爲當前節點
        succ.prev = newNode;
        //更新前驅節點的後繼
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

2.4 刪除節點

2.4.1 刪除頭節點

remove() ,removeFirst(), pop()

	public E pop() {
        return removeFirst();
    }
	public E remove() {
        return removeFirst();
    }
	public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }

	//刪除表頭節點,返回表頭元素的值
	private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        final E element = f.item;
        final Node<E> next = f.next;
        f.item = null;
        f.next = null; // help GC
        first = next;	//頭指針指向後一個節點
        if (next == null)
            last = null;
        else
            next.prev = null;	//新頭節點的前驅爲 null
        size--;
        modCount++;
        return element;
    }
2.4.2 刪除尾節點

removeLast(), pollLast()

區別: removeLast()在鏈表爲空時將拋出NoSuchElementException,而pollLast()方法返回null。

	public E removeLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(l);
    }
	public E pollLast() {
        final Node<E> l = last;
        return (l == null) ? null : unlinkLast(l);
    }

	//刪除表尾節點,返回表尾元素的值
	private E unlinkLast(Node<E> l) {
        // assert l == last && l != null;
        final E element = l.item;
        final Node<E> prev = l.prev;
        l.item = null;
        l.prev = null; // help GC
        last = prev;	//尾指針指向前一個節點
        if (prev == null)
            first = null;
        else
            prev.next = null;	//新尾節點的後繼爲 null
        size--;
        modCount++;
        return element;
    }
2.4.3 刪除指定節點

在這裏插入圖片描述

remove(Object o), remove(int index)

	public boolean remove(Object o) { 	// 刪除指定元素:只會刪除一個匹配的對象
        if (o == null) {	//如果刪除對象爲null 
            for (Node<E> x = first; x != null; x = x.next) {	//從頭開始遍歷 
                if (x.item == null) {	//找到元素  
                    unlink(x);	//從鏈表中移除找到的元素
                    return true;
                }
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {//從頭開始遍歷
                if (o.equals(x.item)) { //找到元素  
                    unlink(x);//從鏈表中移除找到的元素
                    return true;
                }
            }
        }
        return false;
    }
	public E remove(int index) {	//刪除指定位置的元素
        //檢查index範圍
        checkElementIndex(index);
        //將節點刪除
        return unlink(node(index));
    }

	//刪除指定節點,返回指定元素的值
	E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item;
        final Node<E> next = x.next;// 當前節點的後繼
        final Node<E> prev = x.prev;// 當前節點的前驅
		//刪除前驅指針
        if (prev == null) {
            first = next;	//如果刪除的節點是頭節點,令頭節點指向該節點的後繼節點
        } else {
            prev.next = next;	//更新前驅節點的後繼爲當前節點的後繼
            x.prev = null;
        }
		//刪除後繼指針
        if (next == null) {
            last = prev;	//如果刪除的節點是尾節點,令尾節點指向該節點的前驅節點
        } else {
            next.prev = prev;	//更新後繼節點的前驅爲當前節點的前驅
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
    }

2.5 獲取節點

2.5.1 獲取頭節點(index=0)

getFirst(), element(), peek(), peekFirst()

區別: 在於對鏈表爲空時的處理,是拋出異常還是返回null,其中getFirst()element() 方法將會在鏈表爲空時,拋出NoSuchElementException異常

	public E getFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
    }
	public E element() {
        return getFirst();
    }
	public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }
	public E peekFirst() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
     }
2.5.2 獲取尾節點(index=-1)

getLast(), peekLast()

區別: getLast() 方法在鏈表爲空時,會拋出NoSuchElementException,而peekLast() 則不會.

	public E getLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return l.item;
    }
 	public E peekLast() {
        final Node<E> l = last;
        return (l == null) ? null : l.item;
    }
2.5.3 獲取指定節點

get(int index)

	public E get(int index) {
        //檢查index範圍是否在size之內
        checkElementIndex(index);
        //調用Node(index)去找到index對應的node然後返回它的值
        return node(index).item;
    }

	//獲取指定下標的元素
	Node<E> node(int index) {	//查找index對應的node
        // assert isElementIndex(index);
      //根據下標是否超過鏈表長度的一半,來選擇從頭部開始遍歷還是從尾部開始遍歷
        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

2.6 其它方法

set(int index, E element), size(), contains(Object o), indexOf(Object o), lastIndexOf(Object o)

	public E set(int index, E element) {	//將此列表中指定位置的元素替換爲指定的元素
        checkElementIndex(index);
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }
	public int size() {	//返回此列表的元素個數
        return size;
    }
	public boolean contains(Object o) {	//檢查對象o是否存在於鏈表中
        return indexOf(o) != -1;
    }
	public int indexOf(Object o) {	//從頭遍歷找該對象的索引
        int index = 0;
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null)
                    return index;
                index++;
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item))
                    return index;
                index++;
            }
        }
        return -1;
    }
	public int lastIndexOf(Object o) {	//從尾遍歷找該對象的索引
        int index = size;
        if (o == null) {
            for (Node<E> x = last; x != null; x = x.prev) {
                index--;
                if (x.item == null)
                    return index;
            }
        } else {
            for (Node<E> x = last; x != null; x = x.prev) {
                index--;
                if (o.equals(x.item))
                    return index;
            }
        }
        return -1;
    }

參考鏈接: LinkedList 源碼學習

3.HashMap

轉載鏈接:HashMap(JDK1.8)源碼學習

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