2.1 ArrayList 線程不安全,LinkedList 線程不安全,Vector 線程安全

1、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的缺點
插入和刪除元素的效率不高。
根據元素下標查找元素須要遍歷整個元素數組,效率不高。
線程不安全
 
 
2、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效率高。 
缺點
線程不安全,查找消耗的資源大,效率低,不能隨機訪問。
 
3、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() 來獲取下一個元素
相關文章
相關標籤/搜索