国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

圖解集合1:ArrayList

2019-11-14 14:58:41
字體:
來源:轉載
供稿:網友

前言

這個分類中,將會寫寫java中的集合。集合是Java中非常重要而且基礎的內容,因為任何數據必不可少的就是該數據是如何存儲的,集合的作用就是以一定的方式組織、存儲數據。這里寫的集合,一部分是比較常見的、一部分是不常用但是我個人平時見到過的,一些比較相似的集合(比如HashMap和Hashtable)就只講一個,突出它們之間的區別即可。

最后,要指出一點,對于集合,我認為關注的點主要有四點:

1、是否允許空

2、是否允許重復數據

3、是否有序,有序的意思是讀取數據的順序和存放數據的順序是否一致

4、是否線程安全

 

ArrayList

ArrayList是最常見以及每個Java開發者最熟悉的集合類了,顧名思義,ArrayList就是一個以數組形式實現的集合,以一張表格來看一下ArrayList里面有哪些基本的元素:

元    素作    用
PRivate transient Object[] elementData;ArrayList是基于數組的一個實現,elementData就是底層的數組
private int size;ArrayList里面元素的個數,這里要注意一下,size是按照調用add、remove方法的次數進行自增或者自減的,所以add了一個null進入ArrayList,size也會加1 

 

四個關注點在ArrayList上的答案

以后每篇文章在講解代碼前,都會先對于一個集合關注的四個點以表格形式做一個解答:

關  注  點結      論
ArrayList是否允許空允許
ArrayList是否允許重復數據允許
ArrayList是否有序有序
ArrayList是否線程安全非線程安全

 

添加元素

有這么一段代碼:

public static void main(String[] args){    List<String> list = new ArrayList<String>();    list.add("000");    list.add("111");}

看下底層會做什么,進入add方法的源碼來看一下:

1 public boolean add(E e) {2 ensureCapacity(size + 1);  // Increments modCount!!3 elementData[size++] = e;4 return true;5 }

先不去管第2行的ensureCapacity方法,這個方法是擴容用的,底層實際上在調用add方法的時候只是給elementData的某個位置添加了一個數據而已,用一張圖表示的話是這樣的:

多說一句,我這么畫圖有一定的誤導性。elementData中存儲的應該是堆內存中元素的引用,而不是實際的元素,這么畫給人一種感覺就是說elementData數組里面存放的就是實際的元素,這是不太嚴謹的。不過這么畫主要是為了方便起見,只要知道這個問題就好了。

 

擴容

我們看一下,構造ArrayList的時候,默認的底層數組大小是10:

public ArrayList() {this(10);}

那么有一個問題來了,底層數組的大小不夠了怎么辦?答案就是擴容,這也就是為什么一直說ArrayList的底層是基于動態數組實現的原因,動態數組的意思就是指底層的數組大小并不是固定的,而是根據添加的元素大小進行一個判斷,不夠的話就動態擴容,擴容的代碼就在ensureCapacity里面:

public void ensureCapacity(int minCapacity) {modCount++;int oldCapacity = elementData.length;if (minCapacity > oldCapacity) {    Object oldData[] = elementData;    int newCapacity = (oldCapacity * 3)/2 + 1;        if (newCapacity < minCapacity)    newCapacity = minCapacity;           // minCapacity is usually close to size, so this is a win:           elementData = Arrays.copyOf(elementData, newCapacity);}}

看到擴容的時候把元素組大小先乘以3,再除以2,最后加1。可能有些人要問為什么?我們可以想:

1、如果一次性擴容擴得太大,必然造成內存空間的浪費

2、如果一次性擴容擴得不夠,那么下一次擴容的操作必然比較快地會到來,這會降低程序運行效率,要知道擴容還是比價耗費性能的一個操作

所以擴容擴多少,是JDK開發人員在時間、空間上做的一個權衡,提供出來的一個比較合理的數值。最后調用到的是Arrays的copyOf方法,將元素組里面的內容復制到新的數組里面去:

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {       T[] copy = ((Object)newType == (Object)Object[].class)           ? (T[]) new Object[newLength]           : (T[]) Array.newInstance(newType.getComponentType(), newLength);       System.arraycopy(original, 0, copy, 0,                        Math.min(original.length, newLength));       return copy;}

用一張圖來表示就是這樣的:

刪除元素

接著我們看一下刪除的操作。ArrayList支持兩種刪除方式:

1、按照下標刪除

2、按照元素刪除,這會刪除ArrayList中與指定要刪除的元素匹配的第一個元素

對于ArrayList來說,這兩種刪除的方法差不多,都是調用的下面一段代碼:

int numMoved = size - index - 1;if (numMoved > 0)    System.arraycopy(elementData, index+1, elementData, index,             numMoved);elementData[--size] = null; // Let gc do its work

其實做的事情就是兩件:

1、把指定元素后面位置的所有元素,利用System.arraycopy方法整體向前移動一個位置

2、最后一個位置的元素指定為null,這樣讓gc可以去回收它

比方說有這么一段代碼:

public static void main(String[] args){    List<String> list = new ArrayList<String>();    list.add("111");    list.add("222");    list.add("333");    list.add("444");    list.add("555");    list.add("666");    list.add("777");    list.add("888");    list.remove("333");}

用圖表示是這樣的:

插入元素

看一下ArrayList的插入操作,插入操作調用的也是add方法,比如:

 1 public static void main(String[] args) 2 { 3     List<String> list = new ArrayList<String>(); 4     list.add("111"); 5     list.add("222"); 6     list.add("333"); 7     list.add("444"); 8     list.add("555"); 9     list.add("666");10     list.add("777");11     list.add("888");12     list.add(2, "000");13     System.out.println(list);14 }

有一個地方不要搞錯了,第12行的add方法的意思是,往第幾個元素后面插入一個元素,像第12行就是往第二個元素后面插入一個000。看一下運行結果也證明了這一點:

[111, 222, 000, 333, 444, 555, 666, 777, 888]

還是看一下插入的時候做了什么:

 1 public void add(int index, E element) { 2 if (index > size || index < 0) 3     throw new IndexOutOfBoundsException( 4     "Index: "+index+", Size: "+size); 5     ensureCapacity(size+1);  // Increments modCount!! 6 System.arraycopy(elementData, index, elementData, index + 1, 7          size - index); 8 elementData[index] = element; 9 size++;10 }

看到插入的時候,按照指定位置,把從指定位置開始的所有元素利用System,arraycopy方法做一個整體的復制,向后移動一個位置(當然先要用ensureCapacity方法進行判斷,加了一個元素之后數組會不會不夠大),然后指定位置的元素設置為需要插入的元素,完成了一次插入的操作。用圖表示這個過程是這樣的:

 

ArrayList的優缺點

從上面的幾個過程總結一下ArrayList的優缺點。ArrayList的優點如下:

1、ArrayList底層以數組實現,是一種隨機訪問模式,再加上它實現了Randomaccess接口,因此查找也就是get的時候非常快

2、ArrayList在順序添加一個元素的時候非常方便,只是往數組里面添加了一個元素而已

不過ArrayList的缺點也十分明顯:

1、刪除元素的時候,涉及到一次元素復制,如果要復制的元素很多,那么就會比較耗費性能

2、插入元素的時候,涉及到一次元素復制,如果要復制的元素很多,那么就會比較耗費性能

因此,ArrayList比較適合順序添加、隨機訪問的場景

 

ArrayList和Vector的區別

ArrayList是線程非安全的,這很明顯,因為ArrayList中所有的方法都不是同步的,在并發下一定會出現線程安全問題。那么我們想要使用ArrayList并且讓它線程安全怎么辦?一個方法是用Collections.synchronizedList方法把你的ArrayList變成一個線程安全的List,比如:

List<String> synchronizedList = Collections.synchronizedList(list);synchronizedList.add("aaa");synchronizedList.add("bbb");for (int i = 0; i < synchronizedList.size(); i++){    System.out.println(synchronizedList.get(i));}

另一個方法就是Vector,它是ArrayList的線程安全版本,其實現90%和ArrayList都完全一樣,區別在于:

1、Vector是線程安全的,ArrayList是線程非安全的

2、Vector可以指定增長因子,如果該增長因子指定了,那么擴容的時候會每次新的數組大小會在原數組的大小基礎上加上增長因子;如果不指定增長因子,那么就給原數組大小*2,源代碼是這樣的:

int newCapacity = oldCapacity + ((capacityIncrement > 0) ?                                 capacityIncrement : oldCapacity);

 

為什么ArrayList的elementData是用transient修飾的?

最后一個問題,我們看一下ArrayList中的數組,是這么定義的:

private transient Object[] elementData;

不知道大家有沒有想過,為什么elementData是使用transient修飾的呢?關于這個問題,說說我的看法。我們看一下ArrayList的定義:

public class ArrayList<E> extends AbstractList<E>        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

看到ArrayList實現了Serializable接口,這意味著ArrayList是可以被序列化的,用transient修飾elementData意味著我不希望elementData數組被序列化。這是為什么?因為序列化ArrayList的時候,ArrayList里面的elementData未必是滿的,比方說elementData有10的大小,但是我只用了其中的3個,那么是否有必要序列化整個elementData呢?顯然沒有這個必要,因此ArrayList中重寫了writeObject方法:

private void writeObject(java.io.ObjectOutputStream s)        throws java.io.IOException{// Write out element count, and any hidden stuffint expectedModCount = modCount;s.defaultWriteObject();        // Write out array length       s.writeInt(elementData.length);    // Write out all elements in the proper order.for (int i=0; i<size; i++)           s.writeObject(elementData[i]);    if (modCount != expectedModCount) {           throw new ConcurrentModificationException();    }}

每次序列化的時候調用這個方法,先調用defaultWriteObject()方法序列化ArrayList中的非transient元素,elementData不去序列化它,然后遍歷elementData,只序列化那些有的元素,這樣:

1、加快了序列化的速度

2、減小了序列化之后的文件大小

不失為一種聰明的做法,如果以后開發過程中有遇到這種情況,也是值得學習、借鑒的一種思路。

 


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 大庆市| 钟山县| 梁河县| 武冈市| 文山县| 会同县| 东乌| 绥宁县| 安岳县| 德清县| 南丰县| 夏津县| 平阳县| 海安县| 四平市| 瓦房店市| 洪泽县| 稻城县| 绥中县| 河北区| 广饶县| 金秀| 奎屯市| 嵊泗县| 临朐县| 靖西县| 区。| 弋阳县| 隆安县| 黄平县| 安泽县| SHOW| 桓仁| 梅州市| 高青县| 筠连县| 桃园市| 苏尼特右旗| 铁岭县| 商丘市| 江永县|