一、ArrayList 線程不安全
1.數據結構(數組 transient Object[] elemetData;)
ArrayList的底層數據結構就是一個數組,數組元素的類型為Object類型,對ArrayList的所有操作底層都是基於數組的。
2.擴容(1.5倍,在add時初始化默認為10)
ArrayList的擴容主要發生在向ArrayList集合中添加元素的時候
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 判斷元素數組是否為空數組
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); // 取較大值
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
// 結構性修改加1
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
int oldCapacity = elementData.length; // 舊容量
int newCapacity = oldCapacity + (oldCapacity >> 1); // 新容量為舊容量的1.5倍
if (newCapacity - minCapacity < 0) // 新容量小於參數指定容量,修改新容量
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0) // 新容量大於最大容量
newCapacity = hugeCapacity(minCapacity); // 指定新容量
// 拷貝擴容
elementData = Arrays.copyOf(elementData, newCapacity);
}
2.線程不安全
添加操作:在object[size]上存放元素,然后size++
原因:兩個線程,A將數據存放在0的位置,A暫停,B存放數據,由於A未將size++,所以B也將數據存放在0上,然后
A和B都同時運行,size=2,但是只有位置0上有數據,所以說線程不安全
解決辦法: 使用synchronized關鍵字;或用Collections類中的靜態方法synchronizedList();對ArrayList進行調用即可。
3.類變量
private static final int default_capacity = 10;
當ArrayList的構造方法中沒有顯示指出ArrayList的數組長度時,類內部使用默認缺省時對象數組的容量大小,為10。
private static final Object[] empty_elementdata = {};
當ArrayList的構造方法中顯示指出ArrayList的數組長度為0時,類內部將EMPTY_ELEMENTDATA 這個空對象數組賦給elemetData數組。
private static final Object[] defaultcapacity_empty_elementdata = {};
當ArrayList的構造方法中沒有顯示指出ArrayList的數組長度時,類內部使用默認缺省時對象數組為DEFAULTCAPACITY_EMPTY_ELEMENTDATA。
transient Object[] elemetData;
ArrayList的底層數據結構,只是一個對象數組,用於存放實際元素,並且被標記為transient,也就意味着在序列化的時候此字段是不會被序列化的。
private int size;
實際ArrayList中存放的元素的個數,默認時為0個元素。
private static final int max_array_size = integer.max_value – 8;
ArrayList中的對象數組的最大數組容量為Integer.max_value – 8
4.構造方法
public ArrayList() {
// 無參構造函數,設置元素數組為空
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) { // 初始容量大於0
this.elementData = new Object[initialCapacity]; // 初始化元素數組
} else if (initialCapacity == 0) { // 初始容量為0
this.elementData = EMPTY_ELEMENTDATA; // 為空對象數組
} else { // 初始容量小於0,拋出異常
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
}
}
public ArrayList(Collection<? extends E> c) { // 集合參數構造函數
elementData = c.toArray(); // 轉化為數組
if ((size = elementData.length) != 0) { // 參數為非空集合
if (elementData.getClass() != Object[].class) // 是否成功轉化為Object類型數組
elementData = Arrays.copyOf(elementData, size, Object[].class); // 不為Object數組的話就進行復制
} else { // 集合大小為空,則設置元素數組為空
this.elementData = EMPTY_ELEMENTDATA;
}
}
6.其他方法
add
主要完成三件事:判斷是否需要擴容;將元素添加到elementData數組的指定位置;將集合中實際的元素個數加1。
set
首先判斷傳遞的元素數組下標參數是否合法,然后將原來的值取出,設置為新的值,將舊值作為返回值返回。
indexOf(參數可為null),lastIndexOf(從末尾開始)
// 從首開始查找數組里面是否存在指定元素
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;
}
get
檢查索引值是否合法(只檢查是否大於size,而沒有檢查是否小於0),如果所引致合法,則調用elementData(int index)方法獲取值。在elementData(int index)方法中返回元素數組中指定下標的元素,並且對其進行了向下轉型。
return (E) elementData[index]
remove
刪除指定下標的元素 ,將指定下標后面一位到數組末尾的全部元素向前移動一個單位,並且把數組最后一個元素設置為null( 有利於進行GC)
7.優缺點
ArrayList的優點
ArrayList底層以數組實現,是一種隨機訪問模式,再加上它實現了RandomAccess接口,因此查找也就是get的時候非常快。
ArrayList在順序添加一個元素的時候非常方便,只是往數組里面添加了一個元素而已。
根據下標遍歷元素,效率高。
根據下標訪問元素,效率高。
可以自動擴容,默認為每次擴容為原來的1.5倍。
ArrayList的缺點
插入和刪除元素的效率不高。
根據元素下標查找元素需要遍歷整個元素數組,效率不高。
線程不安全
二、LinkedList 線程不安全
1.數據結構(Node)
Node 類是LinkedList中的私有內部類,LinkedList中就是通過Node來存儲集合中的元素。
E :節點的值。
Node next:當前節點的后一個節點的引用(可以理解為指向當前節點的后一個節點的指針)
Node prev:當前節點的前一個節點的引用(可以理解為指向當前節點的前一個節點的指針)
private static class Node<E> {
E item;
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.線程安全性
不安全
2.變量
transient int size = 0;
用來記錄LinkedList的大小
transient Node<E> first;
用來表示LinkedList的頭節點。
transient Node<E> last;
用來表示LinkedList的尾節點。
3.方法
addFirst(提供給用戶的) 調用linkFirst
linkFirst (E e) 參數中的元素作為鏈表的第一個元素
//因為我們需要把該元素設置為頭節點,所以需要新建一個變量把頭節點存儲起來。
//然后新建一個節點,把next指向f,然后自身設置為頭結點。
//再判斷一下f是否為空,如果為空的話,說明原來的LinkedList為空,所以同時也需要把新節點設置為尾節點。
//否則就把f的prev設置為newNode。
//size和modCount自增。
private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
addLast(提供給用戶的) 調用linkLast
linkLast(E e) 參數中的元素作為鏈表的最后一個元素,與上同理
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
linkBefore 在非空節點succ之前插入元素e。
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
//首先我們需要新建一個變量指向succ節點的前一個節點,因為我們要在succ前面插入一個節點。
//接着新建一個節點,它的prev設置為我們剛才新建的變量,后置節點設置為succ。
//然后修改succ的prev為新節點。
//接着判斷一個succ的前一個節點是否為空,如果為空的話,需要把新節點設置為為頭結點。
//如果不為空,則把succ的前一個節點的next設置為新節點。
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++;
}
removeFirst(提供給用戶的)調用unlinkFirst 調用之前會檢驗參數first是否為空
unlinkFirst 刪除LinkedList中第一個節點。(該節點不為空)
(並且返回刪除的節點的值)它是私有方法,我們也沒有權限使用
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
//定義一個變量element指向待刪除節點的值,接着定義一個變量next指向待刪除節點的
//下一個節點。(因為我們需要設置f節點的下一個節點為頭結點,而且需要把f節點的值設置為空)
//接着把f的值和它的next設置為空,把它的下一個節點設置為頭節點。
//接着判斷一個它的下一個節點是否為空,如果為空的話,則需要把last設置為空。否則
//的話,需要把next的prev設置為空,因為next現在指代頭節點。
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;
size--;
modCount++;
return element;
}
removeLast(提供給用戶的)調用unlinkLast,調用之前會檢驗參數last是否為空
刪除LinkedList的最后一個節點。(該節點不為空)(並且返回刪除節點對應的值)
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;
size--;
modCount++;
return element;
}
unlink刪除一個節點(該節點不為空)
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;
}
get(int index)調用node(), return node(index).item;
計算指定索引上的節點(返回Node) LinkedList還對整個做了優化,不是盲目地直接從頭進行遍歷,而是先比較一下index更靠近鏈表(LinkedList)的頭節點還是尾節點。然后進行遍歷,獲取相應的節點。
Node<E> node(int index) {
// 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;
}
}
indexof()和contains()
public boolean contains(Object 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;
}
remove從LinkedList中刪除指定元素。(且只刪除第一次出現的指定的元素)
public boolean remove(Object o) {
if (o == 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;
}
addAll(int index, Collection<? extends E> c)
// 首先調用一下空的構造器。
//然后調用addAll(c)方法。
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
//通過調用addAll(int index, Collection<? extends E> c) 完成集合的添加。
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
//重點來了。(還是建議大家自己手動寫一次源碼實現。)
//幾乎所有的涉及到在指定位置添加或者刪除或修改操作都需要判斷傳進來的參數是否合法。
// checkPositionIndex(index)方法就起這個作用。該方法后面會介紹。
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index);
//先把集合轉化為數組,然后為該數組添加一個新的引用(Objext[] a)。
Object[] a = c.toArray();
//新建一個變量存儲數組的長度。
int numNew = a.length;
//如果待添加的集合為空,直接返回,無需進行后面的步驟。后面都是用來把集合中的元素添加到
//LinkedList中。
if (numNew == 0)
return false;
//這里定義了兩個節點:(讀這里的程序的時候,強烈建議自己畫一個圖,輔助你理解這個過程)。
//Node<E> succ:指代待添加節點的位置。
//Node<E> pred:指代待添加節點的前一個節點。
//下面的代碼是依據新添加的元素的位置分為兩個分支:
//①新添加的元素的位置位於LinkedList最后一個元素的后面。
//新添加的元素的位置位於LinkedList中。
//如果index==size;說明此時需要添加LinkedList中的集合中的每一個元素都是在LinkedList
//最后面。所以把succ設置為空,pred指向尾節點。
//否則的話succ指向插入待插入位置的節點。這里用到了node(int index)方法,這個方法
//后面會詳細分析,這里只需要知道該方法返回對應索引位置上的Node(節點)。pred指向succ節點的前一個節點。
//上面分析的這幾步如果看不懂的話,可以自己畫個圖,清晰明了^_^。
Node<E> pred, succ;
if (index == size) {
succ = null;
pred = last;
} else {
succ = node(index);
pred = succ.prev;
}
//接着遍歷數組中的每個元素。在每次遍歷的時候,都新建一個節點,該節點的值存儲數組a中遍歷
//的值,該節點的prev用來存儲pred節點,next設置為空。接着判斷一下該節點的前一個節點是否為
//空,如果為空的話,則把當前節點設置為頭節點。否則的話就把當前節點的前一個節點的next值
//設置為當前節點。最后把pred指向當前節點,以便后續新節點的添加。
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null)
first = newNode;
else
pred.next = newNode;
pred = newNode;
}
//這里仍然和上面一樣,分兩種情況對待:
//①當succ==null(也就是新添加的節點位於LinkedList集合的最后一個元素的后面),
//通過遍歷上面的a的所有元素,此時pred指向的是LinkedList中的最后一個元素,所以把
//last指向pred指向的節點。
//當不為空的時候,表明在LinkedList集合中添加的元素,需要把pred的next指向succ上,
//succ的prev指向pred。
//最后把集合的大小設置為新的大小。
//modCount(修改的次數)自增。
if (succ == null) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
size += numNew;
modCount++;
return true; }
4.優缺點
優點
LinkedList的優點在於刪除和添加數據所消耗的資源較少,且比ArrayList效率高。
缺點
線程不安全,查找消耗的資源大,效率低,不能隨機訪問。
三、Vector 線程安全
1.數據結構
Vector可實現自動增長的對象數組
創建了一個向量類的對象后,可以往其中隨意插入不同類的對象,即不需顧及類型也不需預先選定向量的容量,並可以方便地進行查找。
對於預先不知或者不願預先定義數組大小,並且需要頻繁地進行查找,插入,刪除工作的情況,可以考慮使用向量類。
2.擴容(翻倍或添加指定大小,在new時初始化默認為10
)
public vector(int initialcapacity,int capacityIncrement)
參數capacityincrement給定了每次擴充的擴充值。當capacityincrement為0的時候,則每次擴充一倍,利用這個功能可以優化存儲。
2.線程安全性
它支持線程的同步,即某一時刻只有一個線程能夠寫Vector,避免多線程同時寫而引起的不一致性,但實現同步需要很高的花費
2.構造方法
public vector()
public vector(int initialcapacity,int capacityIncrement)
public vector(int initialcapacity)
使用第一種方法系統會自動對向量進行管理,若使用后兩種方法,則系統將根據參數,initialcapacity設定向量對象的容量(即向量對象可存儲數據的大小),當真正存放的數據個數超過容量時。系統會擴充向量對象存儲容量。
參數capacityincrement給定了每次擴充的擴充值。當capacityincrement為0的時候,則每次擴充一倍,利用這個功能可以優化存儲。
3.其他方法
插入功能:
(1)public final synchronized void adddElement(Object obj)
將obj插入向量的尾部。obj可以是任何類型的對象。對同一個向量對象,亦可以在其中插入不同類的對象。但插入的應是對象而不是數值,所以插入數值時要注意將數組轉換成相應的對象。
例如:要插入整數1時,不要直接調用v1.addElement(1),正確的方法為:
Vector v1 = new Vector();
Integer integer1 = new Integer(1);
v1.addElement(integer1);
(2) public final synchronized void setElementAt(Object obj,int index)
將index處的對象設置成obj,原來的對象將被覆蓋。
(3) public final synchronized void insertElementAt(Object obj,int index)
在index指定的位置插入obj,原來對象以及此后的對象依次往后順延。
刪除功能:
(1) public final synchronized void removeElement(Object obj)
從向量中刪除obj,若有多個存在,則從向量頭開始試,刪除找到的第一個與obj相同的向量成員。
(2) public final synchronized void removeAllElement();
刪除向量所有的對象
(3) public fianl synchronized void removeElementAt(int index)
刪除index所指的地方的對象
查詢搜索功能:
(1)public final int indexOf(Object obj)
從向量頭開始搜索obj,返回所遇到的第一個obj對應的下標,若不存在此obj,返回-1.
(2)public final synchronized int indexOf(Object obj,int index)
從index所表示的下標處開始搜索obj.
(3)public final int lastindexOf(Object obj)
從向量尾部開始逆向搜索obj.
(4)public final synchornized int lastIndex(Object obj,int index)
從index所表示的下標處由尾至頭逆向搜索obj.
(5)public final synchornized firstElement()
獲取向量對象中的首個obj
(6)public final synchornized Object lastElement()
獲取向量對象的最后一個obj
其他功能:
(1) public final int size();
此方法用於獲取向量元素的個數。它們返回值是向量中實際存在的元素個數,而非向量容量。可以調用方法capacity()來獲取容量值。
(2) public final synchronized void setSize(int newsize);
此方法用來定義向量的大小,若向量對象現有成員個數已經超過了newsize的值,則超過部分的多余元素會丟失。
程序中定義Enumeration類的一個對象Enumeration是java.util中的一個接口類,
(3) public final synchronized Enumeration elements();
此方法將向量對象對應到一個枚舉類型。java.util包中的其他類中也都有這類方法,以便於用戶獲取對應的枚舉類型。
在Enumeration中封裝了有關枚舉數據集合的方法。
方法 hasMoreElement() 來判斷集合中是否還有其他元素。
方法 nextElement() 來獲取下一個元素