夜光序言:
勝利是一切,不知何時已經成爲使命,勝利的一方永遠是對的,我不允許我的人生有敗北兩字。
正文:
以道御術 / 以術識道
2. 集合的安全性問題
// Collections.synchronizedCollection(c)
// Collections.synchronizedList(list)
// Collections.synchronizedMap(m)
// Collections.synchronizedSet(s)
3. ArrayList 內部用什麼實現的?
(回答這樣的問題,不要只回答個皮毛,可以再介紹一下 ArrayList 內部是如何實現數組的增加和刪除的,因爲數組在創建的時候長度是固定的,那麼就有個問題我們往 ArrayList 中不斷的添加對象,它是如何管理這些數組呢?)
一、構造函數
1)空參構造
/**
* Constructs a new {@code ArrayList} instance with zero initial capacity.
*/
public ArrayList() {
array = EmptyArray.OBJECT;
}
EmptyArray 僅僅是一個系統的類庫,該類源碼如下:
public final class EmptyArray {
private EmptyArray() {}
public static final boolean[] BOOLEAN = new boolean[0];
public static final byte[] BYTE = new byte[0];
public static final char[] CHAR = new char[0];
public static final double[] DOUBLE = new double[0];
public static final int[] INT = new int[0];
public static final Class<?>[] CLASS = new Class[0];
public static final Object[] OBJECT = new Object[0];
public static final String[] STRING = new String[0];
public static final Throwable[] THROWABLE = new Throwable[0];
public static final StackTraceElement[] STACK_TRACE_ELEMENT = new StackTraceElement[0];
}
2)帶參構造 1
/**
* Constructs a new instance of {@code ArrayList} with the specified
* initial capacity.
*
* @param capacity
* the initial capacity of this {@code ArrayList}.
*/
public ArrayList(int capacity) {
if (capacity < 0) {
throw new IllegalArgumentException("capacity < 0: " + capacity);
}
array = (capacity == 0 ? EmptyArray.OBJECT : new Object[capacity]);
}
3)帶參構造 2
/**
* Constructs a new instance of {@code ArrayList} containing the elements of
* the specified collection.
*
* @param collection
* the collection of elements to add.
*/
public ArrayList(Collection<? extends E> collection) {
if (collection == null) {
throw new NullPointerException("collection == null");
}
Object[] a = collection.toArray();
if (a.getClass() != Object[].class) {
Object[] newArray = new Object[a.length];
System.arraycopy(a, 0, newArray, 0, a.length);
a = newArray;
}
array = a;
size = a.length;
}
這裏講些題外話,其實在看 Java 源碼的時候,作者的很多意圖都很費人心思,我能知道他的目標是啥,但是不知道他爲何這樣寫。比如對於 ArrayList, array 是他的成員變量
但是每次在方法中使用該成員變量的時候作者都會重新在方法中開闢一個局部變量,然後給局部變量賦值爲 array,然後再使用,有人可能說這是爲了防止併發修改 array
二、add 方法
add 方法有兩個重載,這裏只研究最簡單的那個。
/**
* Adds the specified object at the end of this {@code ArrayList}.
*
* @param object
* the object to add.
* @return always true
*/
@Override public boolean add(E object) {
Object[] a = array;
int s = size;
if (s == a.length) {
Object[] newArray = new Object[s +
(s < (MIN_CAPACITY_INCREMENT / 2) ?
MIN_CAPACITY_INCREMENT : s >> 1)];
System.arraycopy(a, 0, newArray, 0, s);
array = a = newArray;
}
a[s] = object;
size = s + 1;
modCount++;
return true;
}
2、判斷集合的長度 s 是否等於數組的長度(如果集合的長度已經等於數組的長度了,說明數組已經滿了,該重新分配新數組了),重新分配數組的時候需要計算新分配內存的空間大小,如果當前的長度小於 MIN_CAPACITY_INCREMENT/2(這個常量值是 12,除以 2 就是 6,也就是如果當前集合長度小於 6)則分配 12 個長度,如果集合長度大於 6 則分配當前長度 s 的一半長度。
這裏面用到了三元運算符和位運算,s >> 1,意思就是將 s 往右移 1 位,相當於 s=s/2,只不過位運算是效率最高的運算。
三、remove 方法
remove 方法有兩個重載,我們只研究 remove(int index)方法。
/**
* Removes the object at the specified location from this list.
*
* @param index
* the index of the object to remove.
* @return the removed object.
* @throws IndexOutOfBoundsException
* when {@code location < 0 || location >= size()}
*/
@Override public E remove(int index) {
Object[] a = array;
int s = size;
if (index >= s) {
throwIndexOutOfBoundsException(index, s);
}
@SuppressWarnings("unchecked")
E result = (E) a[index];
System.arraycopy(a, index + 1, a, index, --s - index);
a[s] = null; // Prevent memory leak
size = s;
modCount++;
return result;
}
4、調用 System 的 arraycopy 函數,拷貝原理如下圖所示。
5、接下來就是很重要的一個工作,因爲刪除了一個元素,而且集合整體向前移動了一位,因此需要將集合最後一個元素設置爲 null,否則就可能內存泄露。
6、重新給成員變量 array 和 size 賦值
四、clear 方法
/**
* Removes all elements from this {@code ArrayList}, leaving it empty.
*
* @see #isEmpty
* @see #size
*/
@Override public void clear() {
if (size != 0) {
Arrays.fill(array, 0, size, null);
size = 0;
modCount++;
}
}
如果集合長度不等於 0,則將所有數組的值都設置爲 null,然後將成員變量 size 設置爲 0 即可,最後讓修改記錄加 1
4. 併發集合和普通集合如何區別?
在 java 中有普通集合、同步(線程安全)的集合、併發集合。
參考閱讀:
5. List 的三個子類的特點
6. List 和 Map、Set 的區別
6.1 結構特點
6.2 實現類
6.3 區別
7. HashMap 和 HashTable 有什麼區別?
HashTable 是 sychronize,多個線程訪問時不需要自己爲它的方法實現同步,而 HashMap 在被多個線程訪問的時候需要自己爲它的方法實現同步;
8. 數組和鏈表分別比較適合用於什麼場景,爲什麼?
8.1 數組和鏈表簡介
另外一種,不影響別人的數據存儲方式是把數據集中的數據分開離散地存儲到這些不連續空間中,
8.2 數組和鏈表的區別
還有就是鏈表中數據在內存中可以在任意的位置,通過應用來關聯數據(就是通過存在元素的指針來聯繫)
8.3 鏈表和數組使用場景
9. Java 中 ArrayList 和 Linkedlist 區別?
一個表項總是包含 3 個部分:元素內容,前驅表和後驅表,如圖所示:
表項 header 的後驅表項便是鏈表中第一個元素,表項 header 的前驅表項便是鏈表中最後一個元素
10. List a=new ArrayList()和 ArrayList a =new ArrayList()的區別?
實例代碼如下:
List list = new ArrayList();
ArrayList arrayList = new ArrayList();
list.trimToSize(); //錯誤,沒有該方法。
arrayList.trimToSize(); //ArrayList 裏有該方法。
11. 要對集合更新操作時,ArrayList 和 LinkedList 哪個更適合?
一.時間複雜度
1. ArrayList 消耗時間:15
2. LinkedList 消耗時間:2596
這個結果不是固定的,但是基本上 ArrayList 的時間要明顯小於 LinkedList 的時間。
因此在這種情況下不宜用LinkedList。二分查找法使用的隨機訪問(random access)策略,而 LinkedList 是不支持快速的隨機訪問的。
對一個LinkedList 做隨機訪問所消耗的時間與這個 list 的大小是成比例的。而相應的,在 ArrayList 中進行隨機訪問所消耗的時間是固定的。
package com.hy.集合;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class ListDemo {
static final int N = 50000;
static long timeList(List list) {
long start = System.currentTimeMillis();
Object o = new Object();
for (int i = 0; i < N; i++)
list.add(0, o);
return System.currentTimeMillis() - start;
}
public static void main(String[] args) {
System.out.println("ArrayList 耗時:" + timeList(new ArrayList()));
System.out.println("LinkedList 耗時:" + timeList(new LinkedList()));
}
}
二.空間複雜度
private static class Entry {
Object element;
Entry next;
Entry previous;
}
我們還可以通過 trimToSize 方法在 ArrayList 分配完畢之後去掉浪 費掉的空間。
三.總結
ArrayList 和 LinkedList 在性能上各有優缺點,都有各自所適用的地方,總的說來可以描述如下:
ArrayList 會提供比較好的性能;當你的操作是在一列數據的前面或中間添加或刪除數據,並且按照順序訪問其中的元素時,就應該使用 LinkedList 了。
12. 請用兩個隊列模擬堆棧結構
代碼如下:
package com.hy.集合;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
/**
* @Description: 請用兩個隊列模擬堆棧結構
* @Param:
* @return:
* @Author: Hy
* @Date: 2019/4/15
*/
public class Qu {
public static void main(String[] args) {
Queue<String> queue = new LinkedList<String>(); //a 隊列
Queue<String> queue2 = new LinkedList<String>(); //b 隊列
ArrayList<String> a = new ArrayList<String>(); //arrylist 集合是中間參數
//往 a 隊列添加元素
queue.offer("a");
queue.offer("b");
queue.offer("c");
queue.offer("d");
queue.offer("e");
System.out.print("進棧:");
//a 隊列依次加入 list 集合之中
for (String q : queue) {
a.add(q);
System.out.print(q);
}
//以倒序的方法取出(a 隊列依次加入 list 集合)之中的值,加入 b 對列
for (int i = a.size() - 1; i >= 0; i--) {
queue2.offer(a.get(i));
}
//打印出棧隊列
System.out.println("");
System.out.print("出棧:");
for (String q : queue2) {
System.out.print(q);
}
}
}
13. Collection 和 Map 的集成體系
Map:
14. Map 中的 key 和 value 可以爲 null 麼?
package com.hy.集合;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
/**
* @Description: 問: Map 中的 key 和 value 可以爲 null 麼?
* 答:
* HashMap 對象的 key、value 值均可爲 null。
* HashTable 對象的 key、value 值均不可爲 null。
* 且兩者的的 key 值均不能重複,若添加 key 相同的鍵值對,後面的 value 會自動覆蓋前面的 value,但不會報錯。
* @Param:
* @return:
* @Author: Hy
* @Date: 2019/3/2
*/
public class HashTest {
public static void main(String[] args) {
Map<String, String> map = new HashMap<String, String>();//HashMap 對象
Map<String, String> tableMap = new Hashtable<String, String>();//HashTable 對象
map.put(null, null);
System.out.println("hashMap 的[key]和[value]均可以爲 null:" + map.get(null));
try {
tableMap.put(null, "3");
System.out.println(tableMap.get(null));
} catch (Exception e) {
System.out.println("【ERROR】:hashTable 的[key]不能爲 null");
}
try {
tableMap.put("3", null);
System.out.println(tableMap.get("3"));
} catch (Exception e) {
System.out.println("【ERROR】:hashTable 的[value]不能爲 null");
}
}
}