ArrayList詳解未妹,自動擴容原理

\color{red}{最近開通了公眾號载城,希望喜歡本文章的小伙伴關(guān)注哈。}
\color{red}{最近開通了公眾號棵里,希望喜歡本文章的小伙伴關(guān)注哈。}
\color{red}{最近開通了公眾號姐呐,希望喜歡本文章的小伙伴關(guān)注哈殿怜。}

yunqing.jpg

首先:ArrayList的底層通過數(shù)組實現(xiàn)
image.png

ArrayList<E>:說明ArrayList支持泛型。
extends AbstractList<E> :繼承了AbstractList曙砂。AbstractList提供List接口的骨干實現(xiàn)头谜,以最大限度地減少“隨機訪問”數(shù)據(jù)存儲(如ArrayList)實現(xiàn)Llist所需的工作粥诫。
implements List<E>:實現(xiàn)了List根欧。實現(xiàn)了所有可選列表操作。
implements RandomAccess:表明ArrayList支持快速(通常是固定時間)隨機訪問。此接口的主要目的是允許一般的算法更改其行為奠伪,從而在將其應(yīng)用到隨機或連續(xù)訪問列表時能提供良好的性能片择。
implements Cloneable:表明其可以調(diào)用clone()方法來返回實例的field-for-field拷貝嚎卫。
implements java.io.Serializable:表明該類具有序列化功能勃救。有序列化功能
下面來看一些比較重要的參數(shù):

初始化的默認容量

image.png

指定該ArrayList數(shù)組容量為零時返回該對象數(shù)組

image.png

默認調(diào)用空構(gòu)造方法時返回該對象數(shù)組

image.png

ArrayList底層數(shù)組中存儲真正元素的數(shù)組

image.png

ArrayList的實際大小(數(shù)組包含真正的元素個數(shù))

image.png

下面是ArrayList的三種構(gòu)造方法:

第一種:

image.png

執(zhí)行完構(gòu)造方法時還是一個空數(shù)組甲脏,等到add方法執(zhí)行的時候則會初始化容量為10眶熬;

第二種:

image.png

自己穿入想要的容量參數(shù),對容量進行判斷如果容量小于零块请,則會拋出異常娜氏,否則會創(chuàng)建一個容量為initialCapacity的空ArrayList

第三種:

image.png

構(gòu)造一個包含指定 collection 的元素的列表,這些元素是按照該 collection 的迭代器返回它們的順序排列的墩新。

下面來看ArrayList的擴容機制()

先來看其中的add()方法:

image.png

add方法首先會調(diào)用ensureCapacityInternal(size+1)方法贸弥,

image.png

ensureCapacityInternal(size+1)方法是用來進行容量檢查,決定擴容的想要的最小容量海渊,舉個例子绵疲,第一次擴容的時候,size默認為0則minCapacity(即想要的最小容量)為1臣疑,執(zhí)行完Math.max語句minCapacity就變?yōu)?0盔憨,這個時候也就是進行空構(gòu)造第一次add的情況,當ensureCapacityInternal(size+1)傳入的參數(shù)小于默認參數(shù)的時候讯沈,就把默認參數(shù)當做想要的最小容量郁岩,如果大于默認參數(shù)就把你想要的參數(shù)當做想要的最小容量

image.png

這個方法是用來判斷是否擴容,如果你想要的最小容量大于數(shù)組長度則會調(diào)用grow方法進行擴容

image.png

通過grow方法可以看到新容量為當前數(shù)組容量的1.5倍缺狠,然后進行判斷问慎,如果理論擴容后新的容量小于小于你想要的最小容量(但我覺得這種情況不太可能會出現(xiàn),因為為了節(jié)省空間挤茄,假如當前容量為10如叼,只會傳入11來和擴容后的進行比較因此我自己認為不會出現(xiàn)if為真的情況)真正的實現(xiàn)擴容其實是Arrays.copy方法,就是復制數(shù)組實現(xiàn)擴容驮樊,


image.png

但是如果出現(xiàn)了if為真的情況薇正,從這個方法中可以看到數(shù)組的理論最大值為Integer的最大值減去8,但是如果你想要的最小容量已經(jīng)大于數(shù)組的理論最大值囚衔,則會進行大容量分配為Integer的最大值至此擴容結(jié)束,

下面粗略的談一下快速失敗機制(因為對線程還沒有一個好的認識)

public class FastFailTest {
 
 private static List<String> list = new ArrayList<String>();
 //private static List<String> list = new CopyOnWriteArrayList<String>();
 public static void main(String[] args) {
 
  // 同時啟動兩個線程對list進行操作雕沿!
  new ThreadOne().start();
  new ThreadTwo().start();
 }
 
 private static void printAll() {
  System.out.println("");
 
  String value = null;
  Iterator iter = list.iterator();
  while(iter.hasNext()) {
   value = (String)iter.next();
   System.out.print(value+", ");
  }
 }


 /**
  * 向list中依次添加0,1,2,3,4,5练湿,每添加一個數(shù)之后,就通過printAll()遍歷整個list
  */
 private static class ThreadOne extends Thread {
  public void run() {
   int i = 0;
   while (i<6) {
    list.add(String.valueOf(i));
    printAll();
    i++;
   }
  }
 }
 
 /**
  * 向list中依次添加10,11,12,13,14,15审轮,每添加一個數(shù)之后肥哎,就通過printAll()遍歷整個list
  */
 private static class ThreadTwo extends Thread {
  public void run() {
   int i = 10;
   while (i<16) {
    list.add(String.valueOf(i));
    printAll();
    i++;
   }
  }
 }
 
}

fail-fast是怎么產(chǎn)生的辽俗。步驟如下:

新建了一個ArrayList,名稱為arrayList篡诽。
向arrayList中添加內(nèi)容
新建一個“線程a”崖飘,并在“線程a”中通過Iterator反復的讀取arrayList的值。
新建一個“線程b”杈女,在“線程b”中刪除arrayList中的一個“節(jié)點A”朱浴。
這時,就會產(chǎn)生有趣的事件了达椰。
在某一時刻翰蠢,“線程a”創(chuàng)建了arrayList的Iterator。此時“節(jié)點A”仍然存在于arrayList中啰劲,創(chuàng)建arrayList時梁沧,expectedModCount = modCount(假設(shè)它們此時的值為N)。
在“線程a”在遍歷arrayList過程中的某一時刻蝇裤,“線程b”執(zhí)行了廷支,并且“線程b”刪除了arrayList中的“節(jié)點A”∷ü迹“線程b”執(zhí)行remove()進行刪除操作時酥泞,在remove()中執(zhí)行了“modCount++”,此時modCount變成了N+1啃憎!

“線程a”接著遍歷芝囤,當它執(zhí)行到next()函數(shù)時,調(diào)用checkForComodification()比較“expectedModCount”和“modCount”的大行疗肌悯姊;而“expectedModCount=N”,“modCount=N+1”,這樣贩毕,便拋出ConcurrentModificationException異常悯许,產(chǎn)生fail-fast事件。

至此辉阶,我們就完全了解了fail-fast是如何產(chǎn)生的先壕!
即,當多個線程對同一個集合進行操作的時候谆甜,某線程訪問集合的過程中垃僚,該集合的內(nèi)容被其他線程所改變(即其它線程通過add、remove规辱、clear等方法谆棺,改變了modCount的值);這時罕袋,就會拋出ConcurrentModificationException異常改淑,產(chǎn)生fail-fast事件碍岔。

方法1
在單線程的遍歷過程中,如果要進行remove操作朵夏,可以調(diào)用迭代器的remove方法而不是集合類的remove方法蔼啦。看看ArrayList中迭代器的remove方法的源碼:

public void remove() {  
    if (lastRet < 0)  
        throw new IllegalStateException();  
    checkForComodification();  
  
    try {  
        ArrayList.this.remove(lastRet);  
        cursor = lastRet;  
        lastRet = -1;  
        expectedModCount = modCount;  
    } catch (IndexOutOfBoundsException ex) {  
        throw new ConcurrentModificationException();  
    }  
}  

可以看到仰猖,該remove方法并不會修改modCount的值捏肢,并且不會對后面的遍歷造成影響,因為該方法remove不能指定元素亮元,只能remove當前遍歷過的那個元素猛计,所以調(diào)用該方法并不會發(fā)生fail-fast現(xiàn)象。該方法有局限性爆捞。
例子:

public static void main(String[] args) {  
      List<String> list = new ArrayList<>();  
      for (int i = 0 ; i < 10 ; i++ ) {  
           list.add(i + "");  
      }  
      Iterator<String> iterator = list.iterator();  
      int i = 0 ;  
      while(iterator.hasNext()) {  
           if (i == 3) {  
                iterator.remove(); //迭代器的remove()方法  
           }  
           System.out.println(iterator.next());  
           i ++;  
      }  
}  

方法2
使用java并發(fā)包(java.util.concurrent)中的類來代替ArrayList 和hashMap奉瘤。
比如使用 CopyOnWriterArrayList代替ArrayList,CopyOnWriterArrayList在是使用上跟ArrayList幾乎一樣煮甥,CopyOnWriter是寫時復制的容器(COW)盗温,在讀寫時是線程安全的。該容器在對add和remove等操作時成肘,并不是在原數(shù)組上進行修改卖局,而是將原數(shù)組拷貝一份,在新數(shù)組上進行修改双霍,待完成后砚偶,才將指向舊數(shù)組的引用指向新數(shù)組,所以對于CopyOnWriterArrayList在迭代過程并不會發(fā)生fail-fast現(xiàn)象洒闸。但 CopyOnWrite容器只能保證數(shù)據(jù)的最終一致性染坯,不能保證數(shù)據(jù)的實時一致性。
對于HashMap丘逸,可以使用ConcurrentHashMap单鹿,ConcurrentHashMap采用了鎖機制,是線程安全的深纲。在迭代方面仲锄,ConcurrentHashMap使用了一種不同的迭代方式。在這種迭代方式中湃鹊,當iterator被創(chuàng)建后集合再發(fā)生改變就不再是拋出ConcurrentModificationException儒喊,取而代之的是在改變時new新的數(shù)據(jù)從而不影響原有的數(shù)據(jù) ,iterator完成后再將頭指針替換為新的數(shù)據(jù) 涛舍,這樣iterator線程可以使用原來老的數(shù)據(jù)澄惊,而寫線程也可以并發(fā)的完成改變。即迭代不會發(fā)生fail-fast富雅,但不保證獲取的是最新的數(shù)據(jù)掸驱。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市没佑,隨后出現(xiàn)的幾起案子毕贼,更是在濱河造成了極大的恐慌,老刑警劉巖蛤奢,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鬼癣,死亡現(xiàn)場離奇詭異,居然都是意外死亡啤贩,警方通過查閱死者的電腦和手機待秃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來痹屹,“玉大人章郁,你說我怎么就攤上這事≈狙埽” “怎么了暖庄?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長楼肪。 經(jīng)常有香客問我培廓,道長,這世上最難降的妖魔是什么春叫? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任肩钠,我火速辦了婚禮,結(jié)果婚禮上暂殖,老公的妹妹穿的比我還像新娘价匠。我一直安慰自己,他們只是感情好央星,可當我...
    茶點故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布霞怀。 她就那樣靜靜地躺著,像睡著了一般莉给。 火紅的嫁衣襯著肌膚如雪毙石。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天颓遏,我揣著相機與錄音徐矩,去河邊找鬼。 笑死叁幢,一個胖子當著我的面吹牛滤灯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼鳞骤,長吁一口氣:“原來是場噩夢啊……” “哼窒百!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起豫尽,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤篙梢,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后美旧,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體渤滞,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年榴嗅,在試婚紗的時候發(fā)現(xiàn)自己被綠了妄呕。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡嗽测,死狀恐怖绪励,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情论咏,我是刑警寧澤优炬,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站厅贪,受9級特大地震影響蠢护,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜养涮,卻給世界環(huán)境...
    茶點故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一葵硕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧贯吓,春花似錦懈凹、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至爬舰,卻和暖如春们陆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背情屹。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工坪仇, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人垃你。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓椅文,卻偏偏與公主長得像喂很,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子皆刺,可洞房花燭夜當晚...
    茶點故事閱讀 45,691評論 2 361

推薦閱讀更多精彩內(nèi)容