摘要:用戶自己指定容量創建大小的數組創建空數組默認構造函數,其默認初始容量為構造一個包含指定集合的元素的列表,按照它們由集合的迭代器返回的順序。以正確的順序返回該列表中的元素的迭代器。此方法充當基于陣列和基于集合的之間的橋梁。
目錄:
0-0-1. 前言
0-0-2. 集合框架知識回顧
0-0-3. ArrayList簡介
0-0-4. ArrayList核心源碼
0-0-5. ArrayList源碼剖析
0-0-6. ArrayList經典Demo
前言:這篇文章,其實幾天前就已經在圖書館寫出來了,不過手一抖幾個刪除鍵就都沒有了,所以一直拖到了現在。這篇文章在分析ArrayList的時候對ArrayList源碼中用到的比較好的語法也會作以陳述。希望通過這篇文章可以讓你從本質上認識ArrayList,筆者愚笨,如若遇到錯誤敬請告知。
集合框架知識回顧:總體知識脈絡
ArrayList 的底層是數組隊列,相當于動態數組。與Java中的數組相比,它的容量能動態增長。在添加大量元素前,應用程序可以使用ensureCapacity 操作來增加 ArrayList 實例的容量。這可以減少遞增式再分配的數量。它繼承于AbstractList,實現了List, RandomAccess, Cloneable, java.io.Serializable這些接口。
在我們學數據結構的時候就知道了線性表的順序存儲,插入刪除元素的時間復雜度為O(n),求表長以及增加元素,取第 i 元素的時間復雜度為O(1)
ArrayList 繼承了AbstractList,實現了List。它是一個數組隊列,提供了相關的添加、刪除、修改、遍歷等功能。
ArrayList 實現了RandmoAccess接口,即提供了隨機訪問功能。RandmoAccess是java中用來被List實現,為List提供快速訪問功能的。在ArrayList中,我們即可以通過元素的序號快速獲取元素對象,這就是快速隨機訪問。
ArrayList 實現了Cloneable接口,即覆蓋了函數clone(),能被克隆。
ArrayList 實現java.io.Serializable接口,這意味著ArrayList支持序列化,能通過序列化去傳輸。
和Vector不同,ArrayList中的操作不是線程安全的!所以,建議在單線程中才使用ArrayList,而在多線程中可以選擇Vector或者CopyOnWriteArrayList。
package java.util; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.function.UnaryOperator; public class ArrayListArrayList源碼分析:extends AbstractList implements List , RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8683452581122892189L; /** * 默認初始容量大小 */ private static final int DEFAULT_CAPACITY = 10; /** * 空數組(用于空實例)。 */ private static final Object[] EMPTY_ELEMENTDATA = {}; //用于默認大小空實例的共享空數組實例。 //我們把它從EMPTY_ELEMENTDATA數組中區分出來,以知道在添加第一個元素時容量需要增加多少。 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** * 保存ArrayList數據的數組 */ transient Object[] elementData; // non-private to simplify nested class access /** * ArrayList 所包含的元素個數 */ private int size; /** * 帶初始容量參數的構造函數。(用戶自己指定容量) */ public ArrayList(int initialCapacity) { if (initialCapacity > 0) { //創建initialCapacity大小的數組 this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { //創建空數組 this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } /** *默認構造函數,其默認初始容量為10 */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } /** * 構造一個包含指定集合的元素的列表,按照它們由集合的迭代器返回的順序。 */ public ArrayList(Collection extends E> c) { // elementData = c.toArray(); //如果指定集合元素個數不為0 if ((size = elementData.length) != 0) { // c.toArray 可能返回的不是Object類型的數組所以加上下面的語句用于判斷, //這里用到了反射里面的getClass()方法 if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // 用空數組代替 this.elementData = EMPTY_ELEMENTDATA; } } /** * 修改這個ArrayList實例的容量是列表的當前大小。 應用程序可以使用此操作來最小化ArrayList實例的存儲。 */ public void trimToSize() { modCount++; if (size < elementData.length) { elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size); } } //下面是ArrayList的擴容機制 //ArrayList的擴容機制提高了性能,如果每次只擴充一個, //那么頻繁的插入會導致頻繁的拷貝,降低性能,而ArrayList的擴容機制避免了這種情況。 /** * 如有必要,增加此ArrayList實例的容量,以確保它至少能容納元素的數量 * @param minCapacity 所需的最小容量 */ public void ensureCapacity(int minCapacity) { int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) // any size if not default element table ? 0 // larger than default for default empty table. It"s already // supposed to be at default size. : DEFAULT_CAPACITY; if (minCapacity > minExpand) { ensureExplicitCapacity(minCapacity); } } //得到最小擴容量 private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 獲取默認的容量和傳入參數的較大值 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } //判斷是否需要擴容 private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) //調用grow方法進行擴容,調用此方法代表已經開始擴容了 grow(minCapacity); } /** * 要分配的最大數組大小 */ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; /** * ArrayList擴容的核心方法。 */ private void grow(int minCapacity) { // oldCapacity為舊容量,newCapacity為新容量 int oldCapacity = elementData.length; //將oldCapacity 右移一位,其效果相當于oldCapacity /2, //我們知道位運算的速度遠遠快于整除運算,整句運算式的結果就是將新容量更新為舊容量的1.5倍, int newCapacity = oldCapacity + (oldCapacity >> 1); //然后檢查新容量是否大于最小需要容量,若還是小于最小需要容量,那么就把最小需要容量當作數組的新容量, if (newCapacity - minCapacity < 0) newCapacity = minCapacity; //再檢查新容量是否超出了ArrayList所定義的最大容量, //若超出了,則調用hugeCapacity()來比較minCapacity和 MAX_ARRAY_SIZE, //如果minCapacity大于最大容量,則新容量則為ArrayList定義的最大容量,否則,新容量大小則為 minCapacity。 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); } //比較minCapacity和 MAX_ARRAY_SIZE private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; } /** *返回此列表中的元素數。 */ public int size() { return size; } /** * 如果此列表不包含元素,則返回 true 。 */ public boolean isEmpty() { //注意=和==的區別 return size == 0; } /** * 如果此列表包含指定的元素,則返回true 。 */ public boolean contains(Object o) { //indexOf()方法:返回此列表中指定元素的首次出現的索引,如果此列表不包含此元素,則為-1 return indexOf(o) >= 0; } /** *返回此列表中指定元素的首次出現的索引,如果此列表不包含此元素,則為-1 */ 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++) //equals()方法比較 if (o.equals(elementData[i])) return i; } return -1; } /** * 返回此列表中指定元素的最后一次出現的索引,如果此列表不包含元素,則返回-1。. */ public int lastIndexOf(Object o) { if (o == null) { for (int i = size-1; i >= 0; i--) if (elementData[i]==null) return i; } else { for (int i = size-1; i >= 0; i--) if (o.equals(elementData[i])) return i; } return -1; } /** * 返回此ArrayList實例的淺拷貝。 (元素本身不被復制。) */ public Object clone() { try { ArrayList> v = (ArrayList>) super.clone(); //Arrays.copyOf功能是實現數組的復制,返回復制后的數組。參數是被復制的數組和復制的長度 v.elementData = Arrays.copyOf(elementData, size); v.modCount = 0; return v; } catch (CloneNotSupportedException e) { // 這不應該發生,因為我們是可以克隆的 throw new InternalError(e); } } /** *以正確的順序(從第一個到最后一個元素)返回一個包含此列表中所有元素的數組。 *返回的數組將是“安全的”,因為該列表不保留對它的引用。 (換句話說,這個方法必須分配一個新的數組)。 *因此,調用者可以自由地修改返回的數組。 此方法充當基于陣列和基于集合的API之間的橋梁。 */ public Object[] toArray() { return Arrays.copyOf(elementData, size); } /** * 以正確的順序返回一個包含此列表中所有元素的數組(從第一個到最后一個元素); *返回的數組的運行時類型是指定數組的運行時類型。 如果列表適合指定的數組,則返回其中。 *否則,將為指定數組的運行時類型和此列表的大小分配一個新數組。 *如果列表適用于指定的數組,其余空間(即數組的列表數量多于此元素),則緊跟在集合結束后的數組中的元素設置為null 。 *(這僅在調用者知道列表不包含任何空元素的情況下才能確定列表的長度。) */ @SuppressWarnings("unchecked") public T[] toArray(T[] a) { if (a.length < size) // 新建一個運行時類型的數組,但是ArrayList數組的內容 return (T[]) Arrays.copyOf(elementData, size, a.getClass()); //調用System提供的arraycopy()方法實現數組之間的復制 System.arraycopy(elementData, 0, a, 0, size); if (a.length > size) a[size] = null; return a; } // Positional Access Operations @SuppressWarnings("unchecked") E elementData(int index) { return (E) elementData[index]; } /** * 返回此列表中指定位置的元素。 */ public E get(int index) { rangeCheck(index); return elementData(index); } /** * 用指定的元素替換此列表中指定位置的元素。 */ public E set(int index, E element) { //對index進行界限檢查 rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; //返回原來在這個位置的元素 return oldValue; } /** * 將指定的元素追加到此列表的末尾。 */ public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! //這里看到ArrayList添加元素的實質就相當于為數組賦值 elementData[size++] = e; return true; } /** * 在此列表中的指定位置插入指定的元素。 *先調用 rangeCheckForAdd 對index進行界限檢查;然后調用 ensureCapacityInternal 方法保證capacity足夠大; *再將從index開始之后的所有成員后移一個位置;將element插入index位置;最后size加1。 */ public void add(int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // Increments modCount!! //arraycopy()這個實現數組之間復制的方法一定要看一下,下面就用到了arraycopy()方法實現數組自己復制自己 System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; } /** * 刪除該列表中指定位置的元素。 將任何后續元素移動到左側(從其索引中減去一個元素)。 */ public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work //從列表中刪除的元素 return oldValue; } /** * 從列表中刪除指定元素的第一個出現(如果存在)。 如果列表不包含該元素,則它不會更改。 *返回true,如果此列表包含指定的元素 */ public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; } /* * Private remove method that skips bounds checking and does not * return the value removed. */ private void fastRemove(int index) { modCount++; int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work } /** * 從列表中刪除所有元素。 */ public void clear() { modCount++; // 把數組中所有的元素的值設為null for (int i = 0; i < size; i++) elementData[i] = null; size = 0; } /** * 按指定集合的Iterator返回的順序將指定集合中的所有元素追加到此列表的末尾。 */ public boolean addAll(Collection extends E> c) { Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); // Increments modCount System.arraycopy(a, 0, elementData, size, numNew); size += numNew; return numNew != 0; } /** * 將指定集合中的所有元素插入到此列表中,從指定的位置開始。 */ public boolean addAll(int index, Collection extends E> c) { rangeCheckForAdd(index); Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); // Increments modCount int numMoved = size - index; if (numMoved > 0) System.arraycopy(elementData, index, elementData, index + numNew, numMoved); System.arraycopy(a, 0, elementData, index, numNew); size += numNew; return numNew != 0; } /** * 從此列表中刪除所有索引為fromIndex (含)和toIndex之間的元素。 *將任何后續元素移動到左側(減少其索引)。 */ protected void removeRange(int fromIndex, int toIndex) { modCount++; int numMoved = size - toIndex; System.arraycopy(elementData, toIndex, elementData, fromIndex, numMoved); // clear to let GC do its work int newSize = size - (toIndex-fromIndex); for (int i = newSize; i < size; i++) { elementData[i] = null; } size = newSize; } /** * 檢查給定的索引是否在范圍內。 */ private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } /** * add和addAll使用的rangeCheck的一個版本 */ private void rangeCheckForAdd(int index) { if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } /** * 返回IndexOutOfBoundsException細節信息 */ private String outOfBoundsMsg(int index) { return "Index: "+index+", Size: "+size; } /** * 從此列表中刪除指定集合中包含的所有元素。 */ public boolean removeAll(Collection> c) { Objects.requireNonNull(c); //如果此列表被修改則返回true return batchRemove(c, false); } /** * 僅保留此列表中包含在指定集合中的元素。 *換句話說,從此列表中刪除其中不包含在指定集合中的所有元素。 */ public boolean retainAll(Collection> c) { Objects.requireNonNull(c); return batchRemove(c, true); } /** * 從列表中的指定位置開始,返回列表中的元素(按正確順序)的列表迭代器。 *指定的索引表示初始調用將返回的第一個元素為next 。 初始調用previous將返回指定索引減1的元素。 *返回的列表迭代器是fail-fast 。 */ public ListIterator listIterator(int index) { if (index < 0 || index > size) throw new IndexOutOfBoundsException("Index: "+index); return new ListItr(index); } /** *返回列表中的列表迭代器(按適當的順序)。 *返回的列表迭代器是fail-fast 。 */ public ListIterator listIterator() { return new ListItr(0); } /** *以正確的順序返回該列表中的元素的迭代器。 *返回的迭代器是fail-fast 。 */ public Iterator iterator() { return new Itr(); }
通過上面源碼我們發現這兩個實現數組復制的方法被廣泛使用而且很多地方都特別巧妙。比如下面add(int index, E element)方法就很巧妙的用到了arraycopy()方法讓數組自己復制自己實現讓index開始之后的所有成員后移一個位置:
/** * 在此列表中的指定位置插入指定的元素。 *先調用 rangeCheckForAdd 對index進行界限檢查;然后調用 ensureCapacityInternal 方法保證capacity足夠大; *再將從index開始之后的所有成員后移一個位置;將element插入index位置;最后size加1。 */ public void add(int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // Increments modCount!! //arraycopy()方法實現數組自己復制自己 //elementData:源數組;index:源數組中的起始位置;elementData:目標數組;index + 1:目標數組中的起始位置; size - index:要復制的數組元素的數量; System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }
又如toArray()方法中用到了copyOf()方法
/** *以正確的順序(從第一個到最后一個元素)返回一個包含此列表中所有元素的數組。 *返回的數組將是“安全的”,因為該列表不保留對它的引用。 (換句話說,這個方法必須分配一個新的數組)。 *因此,調用者可以自由地修改返回的數組。 此方法充當基于陣列和基于集合的API之間的橋梁。 */ public Object[] toArray() { //elementData:要復制的數組;size:要復制的長度 return Arrays.copyOf(elementData, size); }
聯系:
看兩者源代碼可以發現copyOf()內部調用了System.arraycopy()方法
區別:
1,arraycopy()需要目標數組,將原數組拷貝到你自己定義的數組里,而且可以選擇拷貝的起點和長度以及放入新數組中的位置
2,copyOf()是系統自動在內部新建一個數組,并返回該數組。
/** * ArrayList擴容的核心方法。 */ private void grow(int minCapacity) { // oldCapacity為舊容量,newCapacity為新容量 int oldCapacity = elementData.length; //將oldCapacity 右移一位,其效果相當于oldCapacity /2, //我們知道位運算的速度遠遠快于整除運算,整句運算式的結果就是將新容量更新為舊容量的1.5倍, int newCapacity = oldCapacity + (oldCapacity >> 1); //然后檢查新容量是否大于最小需要容量,若還是小于最小需要容量,那么就把最小需要容量當作數組的新容量, if (newCapacity - minCapacity < 0) newCapacity = minCapacity; //再檢查新容量是否超出了ArrayList所定義的最大容量, //若超出了,則調用hugeCapacity()來比較minCapacity和 MAX_ARRAY_SIZE, //如果minCapacity大于最大容量,則新容量則為ArrayList定義的最大容量,否則,新容量大小則為 minCapacity。 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); }
擴容機制代碼已經做了詳細的解釋。另外值得注意的是大家很容易忽略的一個運算符:移位運算符
簡介:移位運算符就是在二進制的基礎上對數字進行平移。按照平移的方向和填充數字的規則分為三種:<<(左移)、>>(帶符號右移)和>>>(無符號右移)。
作用:對于大數據的2進制運算,位移運算符比那些普通運算符的運算要快很多,因為程序僅僅移動一下而已,不去計算,這樣提高了效率,節省了資源
比如這里:int newCapacity = oldCapacity + (oldCapacity >> 1);
右移一位相當于除2,右移n位相當于除以2的n次方。這里oldCapacity 明顯右移了1位所以相當于oldCapacity /2。
(1)private class Itr implements Iterator(2)private class ListItr extends Itr implements ListIterator (3)private class SubList extends AbstractList implements RandomAccess (4)static final class ArrayListSpliterator implements Spliterator
ArrayList有四個內部類,其中的Itr是實現了Iterator接口,同時重寫了里面的hasNext(),next(),remove()等方法;其中的ListItr繼承Itr,實現了ListIterator接口,同時重寫了hasPrevious(),nextIndex(),previousIndex(),previous(),set(E e),add(E e)等方法,所以這也可以看出了Iterator和ListIterator的區別:ListIterator在Iterator的基礎上增加了添加對象,修改對象,逆向遍歷等方法,這些是Iterator不能實現的。具體可以參考http://blog.csdn.net/a5979266...。其中的SubList繼承AbstractList,實現了RandmAccess接口,類內部實現了對子序列的增刪改查等方法,但它同時也充分利用了內部類的優點,就是共享ArrayList的全局變量,例如檢查器變量modCount,數組elementData等,所以SubList進行的增刪改查操作都是對ArrayList的數組進行的,并沒有創建新的數組。(內部類這里參考了這位老兄的博客http://blog.csdn.net/ljcitwor...
ArrayList經典Demo:package list; import java.util.ArrayList; import java.util.Iterator; public class ArrayListDemo { public static void main(String[] srgs){ ArrayListarrayList = new ArrayList (); System.out.printf("Before add:arrayList.size() = %d ",arrayList.size()); arrayList.add(1); arrayList.add(3); arrayList.add(5); arrayList.add(7); arrayList.add(9); System.out.printf("After add:arrayList.size() = %d ",arrayList.size()); System.out.println("Printing elements of arrayList"); // 三種遍歷方式打印元素 // 第一種:通過迭代器遍歷 System.out.print("通過迭代器遍歷:"); Iterator it = arrayList.iterator(); while(it.hasNext()){ System.out.print(it.next() + " "); } System.out.println(); // 第二種:通過索引值遍歷 System.out.print("通過索引值遍歷:"); for(int i = 0; i < arrayList.size(); i++){ System.out.print(arrayList.get(i) + " "); } System.out.println(); // 第三種:for循環遍歷 System.out.print("for循環遍歷:"); for(Integer number : arrayList){ System.out.print(number + " "); } // toArray用法 // 第一種方式(最常用) Integer[] integer = arrayList.toArray(new Integer[0]); // 第二種方式(容易理解) Integer[] integer1 = new Integer[arrayList.size()]; arrayList.toArray(integer1); // 拋出異常,java不支持向下轉型 //Integer[] integer2 = new Integer[arrayList.size()]; //integer2 = arrayList.toArray(); System.out.println(); // 在指定位置添加元素 arrayList.add(2,2); // 刪除指定位置上的元素 arrayList.remove(2); // 刪除指定元素 arrayList.remove((Object)3); // 判斷arrayList是否包含5 System.out.println("ArrayList contains 5 is: " + arrayList.contains(5)); // 清空ArrayList arrayList.clear(); // 判斷ArrayList是否為空 System.out.println("ArrayList is empty: " + arrayList.isEmpty()); } }
歡迎關注我的微信公眾號(分享各種Java學習資源,面試題,以及企業級Java實戰項目回復關鍵字免費領取):
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/68955.html
摘要:是棧,它繼承于。滿二叉樹除了葉結點外每一個結點都有左右子葉且葉子結點都處在最底層的二叉樹。沒有鍵值相等的節點。這是數據庫選用樹的最主要原因。 在我們學習Java的時候,很多人會面臨我不知道繼續學什么或者面試會問什么的尷尬情況(我本人之前就很迷茫)。所以,我決定通過這個開源平臺來幫助一些有需要的人,通過下面的內容,你會掌握系統的Java學習以及面試的相關知識。本來是想通過Gitbook的...
摘要:它們會在鏈表為空時,拋出獲取尾節點數據方法兩者區別方法在鏈表為空時,會拋出,而則不會,只是會返回。 目錄: 0-1. 簡介 0-2. 內部結構分析 0-3. LinkedList源碼分析 0-3-1. 構造方法 0-3-2. 添加add方法 0-3-3. 根據位置取數據的方法 0-3-4. 根據對象得到索引的方法 0-3-5. 檢查鏈表是否包含某對象的方法 ...
摘要:所謂拉鏈法就是將鏈表和數組相結合。若遇到哈希沖突,則將沖突的值加到鏈表中即可。在編寫程序中,要盡量避免。 目錄: 0-1. 簡介 0-2. 內部結構分析 0-2-1. JDK18之前 0-2-2. JDK18之后 0-3. LinkedList源碼分析 0-3-1. 構造方法 0-3-2. put方法 0-3-3. get方法 0-3-4. resize方法 ...
摘要:底層的數據結構就是數組鏈表紅黑樹,紅黑樹是在中加進來的。負載因子哈希表中的填滿程度。 前言 把 Java 容器的學習筆記放到 github 里了,還在更新~其他的目前不打算抽出來作為文章寫,感覺挖的還不夠深,等對某些東西理解的更深了再寫文章吧Java 容器目錄如下: Java 容器 一、概述 二、源碼學習 1. Map 1.1 HashMap 1.2 LinkedHashM...
摘要:下面隆重介紹簡介是一個解析的第三方庫,它提供了一套非常方便的,可使用,以及類的操作方法來取出和操作數據。一個文檔的對象模型文檔由多個和組成其繼承結構如下繼承繼承繼承一個包含一個子節點集合,并擁有一個父。 前言 使用python寫爬蟲的人,應該都聽過beautifulsoup4這個包,用來它來解析網頁甚是方便。那么在java里有沒有類似的包呢?當然有啦!而且也非常好用。下面隆重介紹jso...
閱讀 2418·2023-04-26 00:46
閱讀 581·2023-04-25 21:36
閱讀 729·2021-11-24 10:19
閱讀 2266·2021-11-23 09:51
閱讀 1015·2021-10-21 09:39
閱讀 830·2021-09-22 10:02
閱讀 1664·2021-09-03 10:29
閱讀 2677·2019-08-30 15:53