摘要:快速失敗在用迭代器遍歷一個集合對象時,如果遍歷過程中對集合對象的內容進行了修改增加刪除修改,則會拋出。原理由于迭代時是對原集合的拷貝進行遍歷,所以在遍歷過程中對原集合所作的修改并不能被迭代器檢測到,所以不會觸發。
原文地址
LinkedList在Java.util包下
繼承自AbstractSequentialList
實現 List 接口,能對它進行隊列操作。
實現 Deque 接口,即能將LinkedList當作雙端隊列使用。
實現了Cloneable接口,即覆蓋了函數clone(),能克隆。
實現java.io.Serializable接口,這意味著LinkedList支持序列化,能通過序列化去傳輸。
允許包含null值
迭代器可以快速報錯
非線程安全的,如果在多線程中使用(修改),需要在外部作同步處理。
LinkedList是一種可以在任何位置進行高效地插入和移除操作的有序序列,它是基于雙向鏈表實現的。內部有三個變量,size表示鏈表中元素的個數, first指向鏈表頭部,last指向鏈表尾部。 結構圖如下圖所示
下面是LinkedList中Node節點的定義,Node類是LinkedList的靜態內部類。
private static class Node構造方法(Construction method){ E item; // 當前節點所存數據 Node next; // 當前節點的下一個節點 Node prev; // 當前節點的前一個節點 Node(Node prev, E element, Node next) { this.item = element; this.next = next; this.prev = prev; } }
LinkedList提供了兩種種方式的構造器,構造一個空列表、以及構造一個包含指定collection的元素的列表,
這些元素按照該collection的迭代器返回的順序排列的。
public LinkedList() { } public LinkedList(Collection extends E> c) { this(); addAll(c); // 調用addAll方法,構建一個包含指定集合c的列表 }添加元素
因為LinkedList即實現了List接口,又實現了Deque接口,所以LinkedList既可以添加將元素添加到尾部,也可以將元素添加到指定索引位置,還可以添加添加整個集合;另外既可以在頭部添加,又可以在尾部添加。
//添加元素作為第一個元素 public void addFirst(E e) { linkFirst(e); } //店家元素作為最后一個元素 public void addLast(E e) { linkLast(e); } //使用對應參數作為第一個節點,內部使用 private void linkFirst(E e) { final Nodef = first;//得到首節點 final Node newNode = new Node<>(null, e, f);//創建一個節點 first = newNode; //更新首節點 if (f == null) last = newNode; //如果之前首節點為空(size==0),那么尾節點就是首節點 else f.prev = newNode; //如果之前首節點不為空,之前的首節點的前一個節點為當前首節點 size++; //長度+1 modCount++; //修改次數+1 } //使用對應參數作為尾節點 void linkLast(E e) { final Node l = last; //得到尾節點 final Node newNode = new Node<>(l, e, null);//使用參數創建一個節點 last = newNode; //設置尾節點 if (l == null) first = newNode; //如果之前尾節點為空(size==0),首節點即尾節點 else l.next = newNode; //如果之前尾節點不為空,之前的尾節點的后一個就是當前的尾節點 size++; modCount++; } //在非空節點succ之前插入元素E。 void linkBefore(E e, Node succ) { final Node pred = succ.prev;//獲取前一個節點 final Node newNode = new Node<>(pred, e, succ);//使用參數創建新的節點 succ.prev = newNode;//當前節點指向新的節點 if (pred == null) first = newNode;//如果前一個節點為null,新的節點就是首節點 else pred.next = newNode;//如果存在前節點,那么前節點的向后指向新節點 size++; modCount++; } //添加指定集合的元素到列表,默認從最后開始添加 public boolean addAll(Collection extends E> c) { return addAll(size, c);//size表示最后一個位置 } /* 從指定位置(而不是下標!下標即索引從0開始,位置可以看做從1開始,其實也是0)后面添加指定集合的元素到列表中,只要有至少一次添加就會返回true index換成position應該會更好理解,所以也就是從索引為index(position)的元素的前面索引為index-1的后面添加! 當然位置可以為0啊,為0的時候就是從位置0(雖然它不存在)后面開始添加嘛,所以理所當然就是添加到第一個位置(位置1的前面)的前面 比如列表:0 1 2 3,如果此處index=4(實際索引為3),就是在元素3后面添加;如果index=3(實際索引為2),就在元素2后面添加。 */ public boolean addAll(int index, Collection extends E> c) { checkPositionIndex(index); //檢查索引是否正確(0<=index<=size) Object[] a = c.toArray(); //得到元素數組 int numNew = a.length; //得到元素個數 if (numNew == 0) //若沒有元素要添加,直接返回false return false; Node pred, succ; if (index == size) { //如果是在末尾開始添加,當前節點后一個節點初始化為null,前一個節點為尾節點 succ = null; //這里可以看做node(index),不過index=size了(index最大只能是size-1),所以這里的succ只能=null,也方便后面判斷 pred = last; } else { //如果不是從末尾開始添加,當前位置的節點為指定位置的節點,前一個節點為要添加的節點的前一個節點 succ = node(index); //添加好元素后(整個新加的)的后一個節點 pred = succ.prev; } //遍歷數組并添加到列表中 for (Object o : a) { @SuppressWarnings("unchecked") E e = (E) o; Node newNode = new Node<>(pred, e, null);//創建一個節點,向前指向上面得到的前節點 if (pred == null) first = newNode; //若當前節點為null,則新加的節點為首節點 else pred.next = newNode;//如果存在前節點,前節點會向后指向新加的節點 pred = newNode; //新加的節點成為前一個節點 } if (succ == null) { //pred.next = null //加上這句也可以更好的理解 last = pred; //如果是從最后開始添加的,則最后添加的節點成為尾節點 } else { pred.next = succ; //如果不是從最后開始添加的,則最后添加的節點向后指向之前得到的后續第一個節點 succ.prev = pred; //當前,后續的第一個節點也應改為向前指向最后一個添加的節點 } size += numNew; modCount++; return true; } //將指定的元素(E element)插入到列表的指定位置(index) public void add(int index, E element) { checkPositionIndex(index); //index >= 0 && index <= size if (index == size) linkLast(element); //尾插入 else linkBefore(element, node(index)); //中間插入 }
linkBefore的添加步驟:
創建newNode節點,將newNode的后繼指針指向succ,前驅指針指向pred
將succ的前驅指針指向newNode
根據pred是否為null,進行不同操作。
如果pred為null,說明該節點插入在頭節點之前,要重置first頭節點
如果pred不為null,那么直接將pred的后繼指針指向newNode即可
addAll的添加步驟:
檢查index索引范圍
得到集合數據
得到插入位置的前驅和后繼節點
遍歷數據,將數據插入到指定位置
刪除元素同樣的LinkedList也提供了很多方法來刪除元素
// 刪除首節點并返回刪除前首節點的值,內部使用 (f == first && f != null) private E unlinkFirst(Node序列化方法f) { final E element = f.item; // 獲取首節點的值 final Node next = f.next; // 獲取首節點的后一個節點 f.item = null; f.next = null; // help GC first = next; // 更新首節點 if (next == null) //如果不存在下一個節點,則首尾都為null last = null; else next.prev = null; //如果存在下一個節點,那它的前指針為null size--; modCount++; return element; } // 刪除尾節點,并返回尾節點的元素 (assert l == last && l != null) private E unlinkLast(Node l) { final E element = l.item;//獲取尾節點的值 final Node prev = l.prev;//獲取尾節點前一個節點 l.item = null; l.prev = null; // help GC last = prev; //前一個節點成為新的尾節點 if (prev == null) first = null; //如果前一個節點不存在,則首尾都為null else prev.next = null;//如果前一個節點存在,先后指向null size--; modCount++; return element; } // 刪除指定節點x并返回節點的值(x != null) E unlink(Node x) { //獲取當前值和前后節點 final E element = x.item; final Node next = x.next; final Node prev = x.prev; if (prev == null) { first = next; //如果前一個節點為空(如當前節點為首節點),后一個節點成為新的首節點 } else { prev.next = next;//如果前一個節點不為空,那么他先后指向當前的下一個節點 x.prev = null; //help GC } if (next == null) { last = prev; //如果后一個節點為空(如當前節點為尾節點),當前節點前一個成為新的尾節點 } else { next.prev = prev;//如果后一個節點不為空,后一個節點向前指向當前的前一個節點 x.next = null; //help GC } x.item = null; //help GC size--; modCount++; return element; } //刪除第一個元素并返回刪除的元素 public E removeFirst() { final Node f = first;//得到第一個節點 if (f == null) //如果為空,拋出異常 throw new NoSuchElementException(); return unlinkFirst(f); } //刪除最后一個元素并返回刪除的值 public E removeLast() { final Node l = last;//得到最后一個節點 if (l == null) //如果為空,拋出異常 throw new NoSuchElementException(); return unlinkLast(l); }
private static final long serialVersionUID = 876323262645176354L; //序列化:將linkedList的“大小,所有的元素值”都寫入到輸出流中 private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { s.defaultWriteObject(); s.writeInt(size); for (Node隊列操作x = first; x != null; x = x.next) s.writeObject(x.item); } //反序列化:先將LinkedList的“大小”讀出,然后將“所有的元素值”讀出 @SuppressWarnings("unchecked") private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); int size = s.readInt(); for (int i = 0; i < size; i++) linkLast((E)s.readObject()); //以尾插入的方式 }
//提供普通隊列和雙向隊列的功能,當然,也可以實現棧,FIFO,FILO //出隊(從前端),獲得第一個元素,不存在會返回null,不會刪除元素(節點) public E peek() { final Node迭代器f = first; return (f == null) ? null : f.item; } //出隊(從前端),不刪除元素,若為null會拋出異常而不是返回null public E element() { return getFirst(); } //出隊(從前端),如果不存在會返回null,存在的話會返回值并移除這個元素(節點) public E poll() { final Node f = first; return (f == null) ? null : unlinkFirst(f); } //出隊(從前端),如果不存在會拋出異常而不是返回null,存在的話會返回值并移除這個元素(節點) public E remove() { return removeFirst(); } //入隊(從后端),始終返回true public boolean offer(E e) { return add(e); } //入隊(從前端),始終返回true public boolean offerFirst(E e) { addFirst(e); return true; } //入隊(從后端),始終返回true public boolean offerLast(E e) { addLast(e);//linkLast(e) return true; } //出隊(從前端),獲得第一個元素,不存在會返回null,不會刪除元素(節點) public E peekFirst() { final Node f = first; return (f == null) ? null : f.item; } //出隊(從后端),獲得最后一個元素,不存在會返回null,不會刪除元素(節點) public E peekLast() { final Node l = last; return (l == null) ? null : l.item; } //出隊(從前端),獲得第一個元素,不存在會返回null,會刪除元素(節點) public E pollFirst() { final Node f = first; return (f == null) ? null : unlinkFirst(f); } //出隊(從后端),獲得最后一個元素,不存在會返回null,會刪除元素(節點) public E pollLast() { final Node l = last; return (l == null) ? null : unlinkLast(l); } //入棧,從前面添加 public void push(E e) { addFirst(e); } //出棧,返回棧頂元素,從前面移除(會刪除) public E pop() { return removeFirst(); }
//返回迭代器 public IteratordescendingIterator() { return new DescendingIterator(); } //迭代器 private class DescendingIterator implements Iterator { private final ListItr itr = new ListItr(size()); public boolean hasNext() { return itr.hasPrevious(); } public E next() { return itr.previous(); } public void remove() { itr.remove(); } } public ListIterator listIterator(int index) { checkPositionIndex(index); return new ListItr(index); } private class ListItr implements ListIterator { private Node lastReturned; private Node next; private int nextIndex; private int expectedModCount = modCount;//保存當前modCount,確保fail-fast機制 ListItr(int index) { next = (index == size) ? null : node(index);//得到當前索引指向的next節點 nextIndex = index; } public boolean hasNext() { // 判斷后面是否還有元素 return nextIndex < size; } public E next() { //獲取下一個節點 checkForComodification(); if (!hasNext()) throw new NoSuchElementException(); lastReturned = next; next = next.next; nextIndex++; return lastReturned.item; } public boolean hasPrevious() { return nextIndex > 0; } //獲取前一個節點,將next節點向前移 public E previous() { checkForComodification(); if (!hasPrevious()) throw new NoSuchElementException(); lastReturned = next = (next == null) ? last : next.prev; nextIndex--; return lastReturned.item; } public int nextIndex() { return nextIndex; } public int previousIndex() { return nextIndex - 1; } public void remove() { checkForComodification(); if (lastReturned == null) throw new IllegalStateException(); Node lastNext = lastReturned.next; unlink(lastReturned); if (next == lastReturned) next = lastNext; else nextIndex--; lastReturned = null; expectedModCount++; } public void set(E e) { if (lastReturned == null) throw new IllegalStateException(); checkForComodification(); lastReturned.item = e; } public void add(E e) { checkForComodification(); lastReturned = null; if (next == null) linkLast(e); else linkBefore(e, next); nextIndex++; expectedModCount++; } public void forEachRemaining(Consumer super E> action) { Objects.requireNonNull(action); while (modCount == expectedModCount && nextIndex < size) { action.accept(next.item); lastReturned = next; next = next.next; nextIndex++; } checkForComodification(); } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
在ListIterator的構造器中,得到了當前位置的節點,就是變量next。next()方法返回當前節點的值并將next指向其后繼節點,previous()方法返回當前節點的前一個節點的值并將next節點指向其前驅節點。
由于Node是一個雙向節點,所以這用了一個節點就可以實現從前向后迭代和從后向前迭代。另外在ListIterator初始時,exceptedModCount保存了當前的modCount,如果在迭代期間,有操作改變了鏈表的底層結構,那么再操作迭代器的方法時將會拋出ConcurrentModificationException。
fail-fastfail-fast 機制是java集合(Collection)中的一種錯誤機制。當多個線程對同一個集合的內容進行操作時,就可能會產生fail-fast事件。例如:當某一個線程A通過iterator去遍歷某集合的過程中,若該集合的內容被其他線程所改變了;那么線程A訪問集合時,就會拋出ConcurrentModificationException異常,產生fail-fast事件。
快速失敗(fail—fast)
在用迭代器遍歷一個集合對象時,如果遍歷過程中對集合對象的內容進行了修改(增加、刪除、修改),則會拋出Concurrent Modification Exception。
原理:迭代器在遍歷時直接訪問集合中的內容,并且在遍歷過程中使用一個 modCount 變量。集合在被遍歷期間如果內容發生變化,就會改變modCount的值。每當迭代器使用hashNext()/next()遍歷下一個元素之前,都會檢測modCount變量是否為expectedmodCount值,是的話就返回遍歷;否則拋出異常,終止遍歷。
注意:這里異常的拋出條件是檢測到 modCount!=expectedmodCount 這個條件。如果集合發生變化時修改modCount值剛好又設置為了expectedmodCount值,則異常不會拋出。因此,不能依賴于這個異常是否拋出而進行并發操作的編程,這個異常只建議用于檢測并發修改的bug。
場景:java.util包下的集合類都是快速失敗的,不能在多線程下發生并發修改(迭代過程中被修改)。
安全失敗(fail—safe)
采用安全失敗機制的集合容器,在遍歷時不是直接在集合內容上訪問的,而是先復制原有集合內容,在拷貝的集合上進行遍歷。
原理:由于迭代時是對原集合的拷貝進行遍歷,所以在遍歷過程中對原集合所作的修改并不能被迭代器檢測到,所以不會觸發Concurrent Modification Exception。
缺點:基于拷貝內容的優點是避免了Concurrent Modification Exception,但同樣地,迭代器并不能訪問到修改后的內容,即:迭代器遍歷的是開始遍歷那一刻拿到的集合拷貝,在遍歷期間原集合發生的修改迭代器是不知道的。
場景:java.util.concurrent包下的容器都是安全失敗,可以在多線程下并發使用,并發修改。
其他方法//獲取第一個元素 public E getFirst() { final Nodef = first;//得到首節點 if (f == null) //如果為空,拋出異常 throw new NoSuchElementException(); return f.item; } //獲取最后一個元素 public E getLast() { final Node l = last;//得到尾節點 if (l == null) //如果為空,拋出異常 throw new NoSuchElementException(); return l.item; } //檢查是否包含某個元素,返回bool public boolean contains(Object o) { return indexOf(o) != -1;//返回指定元素的索引位置,不存在就返回-1,然后比較返回bool值 } //返回列表長度 public int size() { return size; } //清空表 public void clear() { // help GC for (Node x = first; x != null; ) { Node next = x.next; x.item = null; x.next = null; x.prev = null; x = next; } first = last = null; size = 0; modCount++; } //獲取指定索引的節點的值 public E get(int index) { checkElementIndex(index); return node(index).item; } //修改指定索引的值并返回之前的值 public E set(int index, E element) { checkElementIndex(index); // 檢查下標是否合法 Node x = node(index); E oldVal = x.item; x.item = element; return oldVal; } //獲取指定位置的節點 Node node(int index) { if (index < (size >> 1)) {//如果位置索引小于列表長度的一半(或一半減一),從前面開始遍歷; Node x = first;//index==0時不會循環,直接返回first for (int i = 0; i < index; i++) x = x.next; return x; } else { // 否則,從后面開始遍歷 Node x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; } } //獲取指定元素從first開始的索引位置,不存在就返回-1 //這里不能按條件雙向找了,所以通常根據索引獲得元素的速度比通過元素獲得索引的速度快 public int indexOf(Object o) { int index = 0; if (o == null) { for (Node x = first; x != null; x = x.next) { if (x.item == null) return index; index++; } } else { for (Node x = first; x != null; x = x.next) { if (o.equals(x.item)) return index; index++; } } return -1; } //獲取指定元素從first開始最后出現的索引,不存在就返回-1 //但實際查找是從last開始的 public int lastIndexOf(Object o) { int index = size; if (o == null) { for (Node x = last; x != null; x = x.prev) { index--; if (x.item == null) return index; } } else { for (Node x = last; x != null; x = x.prev) { index--; if (o.equals(x.item)) return index; } } return -1; } //返回此 LinkedList實例的淺拷貝 public Object clone() { LinkedList clone = superClone(); clone.first = clone.last = null; clone.size = 0; clone.modCount = 0; for (Node x = first; x != null; x = x.next) clone.add(x.item); return clone; } //返回一個包含LinkedList中所有元素值的數組 public Object[] toArray() { Object[] result = new Object[size]; int i = 0; for (Node x = first; x != null; x = x.next) result[i++] = x.item; return result; } //如果給定的參數數組長度足夠,則將ArrayList中所有元素按序存放于參數數組中,并返回 //如果給定的參數數組長度小于LinkedList的長度,則返回一個新分配的、長度等于LinkedList長度的、包含LinkedList中所有元素的新數組 @SuppressWarnings("unchecked") public T[] toArray(T[] a) { if (a.length < size) a = (T[])java.lang.reflect.Array.newInstance( a.getClass().getComponentType(), size); int i = 0; Object[] result = a; for (Node x = first; x != null; x = x.next) result[i++] = x.item; if (a.length > size) a[size] = null; return a; }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/69570.html
摘要:我的是忙碌的一年,從年初備戰實習春招,年三十都在死磕源碼,三月份經歷了阿里五次面試,四月順利收到實習。因為我心理很清楚,我的目標是阿里。所以在收到阿里之后的那晚,我重新規劃了接下來的學習計劃,將我的短期目標更新成拿下阿里轉正。 我的2017是忙碌的一年,從年初備戰實習春招,年三十都在死磕JDK源碼,三月份經歷了阿里五次面試,四月順利收到實習offer。然后五月懷著忐忑的心情開始了螞蟻金...
摘要:線程不安全底層數據結構是鏈表。的默認初始化容量是,每次擴容時候增加原先容量的一半,也就是變為原來的倍刪除元素時不會減少容量,若希望減少容量則調用它不是線程安全的。 前言 聲明,本文用得是jdk1.8 前一篇已經講了Collection的總覽:Collection總覽,介紹了一些基礎知識。 現在這篇主要講List集合的三個子類: ArrayList 底層數據結構是數組。線程不安全 ...
摘要:集合中成員很豐富,常用的集合有,,等。實現接口的集合主要有。集合中不能包含重復的元素,每個元素必須是唯一的。而以作為實現的構造函數的訪問權限是默認訪問權限,即包內訪問權限。與接口不同,它是由一系列鍵值對組成的集合,提供了到的映射。 原文地址 Java集合 Java集合框架:是一種工具類,就像是一個容器可以存儲任意數量的具有共同屬性的對象。 Java集合中成員很豐富,常用的集合有Arra...
摘要:是現在廣泛流行的代從開始學習系列之向提交代碼掘金讀完本文大概需要分鐘。為了進行高效的垃圾回收,虛擬機把堆內存劃分成新生代老年代和永久代中無永久代,使用實現三塊區域。 React Native 開源項目 - 仿美團客戶端 (Android、iOS 雙適配) - Android - 掘金推薦 React Native 學習好項目,仿照美團客戶端... 極簡 GitHub 上手教程 - 工具...
閱讀 1426·2021-09-22 15:52
閱讀 1469·2019-08-30 15:44
閱讀 899·2019-08-30 14:24
閱讀 2712·2019-08-30 13:06
閱讀 2704·2019-08-26 13:45
閱讀 2787·2019-08-26 13:43
閱讀 1025·2019-08-26 12:01
閱讀 1444·2019-08-26 11:56