java集合—— 链表(java中的所有链表都是双向链表)
【0】README
0.1) 本文描述轉自 core java volume 1, 源代碼 diy 的, 旨在理解 java集合—— 鏈表(java中的所有鏈表都是雙向鏈表) 的相關知識;
0.2) for full source code , please visit https://github.com/pacosonTang/core-java-volume/blob/master/chapter13/LinkedListTest.java
【1】鏈表(java中的所有鏈表都是雙向鏈表)
1.1)數組和數組列表有一個重大缺陷: 這就是從數組的中間位置刪除一個元素要付出很大的代價, 其原因是數組中處于被刪除元素之后的所有元素都要向數組的前段移動。(在數組中插入一個元素也是如此)
1.2)解決方法: java引入了鏈表來解決這個問題。在java的程序設計語言中, 所有鏈表實際上都是雙向鏈接的——即每個結點還存放著指向前驅節點的引用;
1.3)看個荔枝:(先添加3個元素,然后再將第2個元素刪除)
1.4)鏈表與泛型集合之間有一個重要區別
- 1.4.1)鏈表有一個有序集合,每個對象的位置都十分重要: LinkedList.add 方法將對象添加到鏈表的尾部, 但是,常常需要將元素添加到鏈表中間。 由于迭代器是描述集合中位置的,所以這種依賴于位置的 add 方法將由迭代器負責, 只有對自然有序的集合使用迭代器添加元素才有實際意義;
- 1.4.2)又如 集(Set)類型, 其中的元素完全是無序的。因此, 在 Iterator接口中就沒有add 方法。相反,集合類庫提供了子接口 ListIterator, 其中包含 add 方法(注意, Set是集類型 而 Collection是 集合類型,它們是不一樣的,再次提醒):
(現在你知道為什么要添加ListIterator接口 ? 因為Set是無序的,在Iterator中沒有 add方法,而LinkedList是有序的,又需要add方法,所以就有添加了一個接口 ListIterator用于給 LinkedList列表添加add 方法)
對以上代碼的分析(Analysis):
- A1)與 Collection.add 不同, 這個方法不返回boolean類型的值, 它假定添加操作總會改變鏈表;
A2)另外,ListIterator接口有兩個方法, 可以用來反向遍歷鏈表(Methods):
- M1)E previous(); : 與next方法一樣, previous 方法返回越過的對象;
- M2)boolean hasPrevious();
Attention)
- A1)順向遍歷: next();
- A2)逆向遍歷: previous();
1.5)LinkedList 類的 listIterator方法返回一個實現了 ListIterator接口的迭代器對象:
- 1.5.1)Add 方法在迭代器位置之前添加了一個新對象, 看個荔枝(下面的代碼將越過鏈表中的第一個元素, 并在第二個元素之前添加 “tang”):
- 1.5.2)如果多次調用add方法, 將按照提供的次序把元素添加到鏈表中。 它們被依次添加到 迭代器當前位置之前;
- 1.5.3)當用一個剛剛由 Iterator方法返回, 并且指向鏈表表頭的迭代器調用add 方法時, 新添加的元素將變成列表的新表頭;當迭代器越過鏈表的最后一個元素時(即hasNext 返回false), 添加的元素將變成列表的新表尾; (干貨)
- 1.5.4)如果鏈表有n個元素: 那么就會有n+1個位置可以添加新元素;
- 1.5.5)看個荔枝:如果鏈表包含3個元素, A、B、C,就有4個位置(標有 |)可以插入新元素:
Annotation) (我只覺得有好多干貨哦)
- A1)在用光標類比時要格外小心。remove操作與 BACKSPACE 鍵的工作方式不太一樣;在調用next方法之后, remove方法確實與 BACKSPACE 鍵一樣刪除了迭代器左側的元素;
- A2)但是,如果調用 previous就會將 右側的元素刪除掉,并且不能在同一行中調用兩次remove;
- A3) add 方法只依賴于迭代器的位置, 而 remove方法依賴于迭代器的狀態;
- A4)最后需要說明的是, set方法用一個新元素取代調用 next 或 previous 方法返回的上一個元素;
- A4.1)看個荔枝:(用一個新值取代鏈表中的第一個元素)
1.6)出現的問題:
- 1.6.1)可以想象,如果在某個迭代器修改集合時,另一個迭代器對其進行遍歷, 一定會出現混亂的狀況;
- 1.6.2)鏈表迭代器使其能夠檢測到這種修改。如果迭代器發現他的集合被另一個迭代器修改了, 或是被該集合自身的方法修改了, 就會拋出一個 Concurrent ModificationException 異常,
- 1.6.3)看個荔枝:
1.7)解決方法:
- 1.7.1)為了避免發生并發修改異常, 請遵循以下規則:可以根據需要給容器附加許多的迭代器,但是這些迭代器只能讀取列表。另外,再單獨附加一個既能讀也能寫的迭代器;
- 1.7.2)有一種簡單的方法可以檢測并發修改的問題:集合可以跟蹤改寫操作(添加或刪除元素)的次數。每個迭代器都維護一個獨立的計數值。在每個迭代器方法的開始處檢查自己修改操作的計數值是否與集合的改寫操作計數值一致。如果不一致, 拋出一個 ConcurrentModificationException異常;
Annotation) 對于并發修改列表的檢測有一個奇怪的例外。鏈表只負責跟蹤對列表的結構性修改, 例如, 添加元素,刪除元素。set操作不被視為結構性修改;
Conclusion)
- C1)可以使用 ListIterator類從前后兩個方向遍歷鏈表中的元素, 并可以添加和刪除元素;
- C2) Collection接口中聲明了許多用于對鏈表操作的有用方法。其中大部分方法都是在LinkedList 類的超類 AbstractCollection中實現的;
C3)在java類庫中, 還存在著一些爭議的方法。
- C3.1)鏈表不支持快速地隨機訪問。如果要查看鏈表中第n個元素, 就必須從頭開始, 越過 n-1個元素。 沒有捷徑可走。鑒于這個原因, 在程序需要采用整數索引訪問元素時, 程序員通常不選用鏈表;
- C3.2)盡管如此,LinkedList 還是提供了用來訪問特定元素的get方法:
(以上代碼效率低的一逼,不推薦使用;)
C4)迭代器還有一個方法, 可以告知當前位置的索引:
- C4.1)由于java 迭代器指向兩個元素間的位置, 所以可以同時產生兩個索引: nextIndex方法返回下一次調用next方法時 返回元素的整數索引; previousIndex 方法返回下一次調用 previous 方法時返回元素的整數索引;
- C4.2)以上兩個方法執行效率非常高,因為迭代器保持著當前位置的計數值;(執行效率高,那就是干貨)
- C4.3)需要說明的是: 如果有一個整數索引n, list.listIterator(n) 將返回一個迭代器, 這個迭代器指向索引為 n 的元素前面的位置。也就是說, 調用 next 與 調用 list.get(n) 會產生同一個元素, 只是獲得這個迭代器的效率比較低而已;
- C5)我們建議避免使用以整數索引表示鏈表中位置的所有方法。 如果需要對集合進行隨機訪問, 就使用數組 或 ArrayList, 而不要使用鏈表;(干貨建議)
- C6)要知道, 使用鏈表的唯一理由是盡可能減少在列表中間插入和刪除元素所付出的代價, 如果列表中只有少數幾個元素,就完全可以用 ArrayList(循環數組); (干貨建議)
API java.util.List<E> 1.2 ListIterator<E> listIterator():返回一個列表迭代器, 以便用來訪問列表中的元素; ListIterator<E> listIterator(int index):返回一個列表迭代器, 以便用來訪問列表中的元素; 這個元素是第一次調用next 返回的給定索引的元素; void add(int i, E element) void addAll(int i, Collection<? extends E> elements) E remove(int i) E get(i) E set(int i, E element) int indexOf(Object element) : 返回與指定元素相等 的元素在列表中第一次出現的位置, 如果沒有這樣的元素將返回-1; int lastIndexOf(Object element):返回與指定元素相等 的元素在列表中最后次出現的位置, 如果沒有這樣的元素將返回-1;API java.util.ListIterator<E> 1.2 void add(E element) void set(E element) boolean hasPrevious(): 當反向迭代列表時, 還有可供訪問的元素; E previous() :返回前一個對象。如果已經達到了 列表的頭部, 就拋出一個 NoSuchElementException int nextIndex():返回下一次調用 next 方法時將返回的元素索引; int previousIndex(): 返回下一次調用 previous方法時將返回的元素索引;API java.util.LinkedList<E> 1.2 LinkedList() 構造一個空鏈表;LinkedList(Collection<? extend E> elements) 構造一個鏈表, 并將集合中的所有元素添加到這個鏈表中;void addFirst(E element) void addLast(E ele) 將某個元素添加到頭部或尾部;E getFirst() E getLast() 返回頭部或尾部的元素;E removeFirst(); E removeLast(); 刪除并返回頭部或尾部的元素;
總結
以上是生活随笔為你收集整理的java集合—— 链表(java中的所有链表都是双向链表)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 真假Mesh路由器不会分?一招教你如何辨
- 下一篇: java中的native关键字有什么作用