Java Collection框架 - ArrayList

Java Collection框架 - ArrayList

基于jdk1.8

簡介

ArrayList是基于數組實現的動態數組,不是線程安全

時間復雜度:

  • 查找和修改 ==> O(1)
  • 刪除和添加(除了向末尾添加) ==> O(n)

數據結構

先看下ArrayList的數據結構:

ArrayList數據結構

ArrayList的數據結構很簡單,就是一個數組跟一個size屬性作為尾指針

主要屬性與方法列表

//代表ArrayList默認容量 10
DEFAULT_CAPACITY : int
//內部數組
elementData : Object[]
//容量
size : int
//最大容量 Integer.MAX_VALUE - 8
MAX_ARRAY_SIZE : int

trimToSize() : void
ensureCapacity(int) : void
size() : int
isEmpty() : boolean
contains(Object) : boolean
indexOf(Object) : int
lastIndexOf(Object) : int
clone() : Object
toArray() : Object[]
toArray(T[]) : T[]
get(int) : E
set(int, E) : E
add(E) : boolean
add(int, E) : void
remove(int) : E
remove(Object) : boolean
clear() : void
addAll(Collection<? extends E>) : boolean
addAll(int, Collection<? extends E>) : boolean
removeAll(Collection<?>) : boolean
retainAll(Collection<?>) : boolean
listIterator(int) : ListIterator<E>
listIterator() : ListIterator<E>
subList(int, int) : List<E>
sort(Comparator<? super E>) : void

主要代碼分析

  1. 查找

ArrayList提供了get(int index)用于讀取數組中的元素。由于ArrayList使用數組實現,所以可以直接使用下標來獲取元素

  public E get(int index) {
      rangeCheck(index);

      return elementData(index);
  }

  E elementData(int index) {
      return (E) elementData[index];
  }
  1. 增加

ArrayList提供了add(E e)add(int index, E element)addAll(Collection<? extends E> c)addAll(int index, Collection<? extends E> c)四個方法來添加元素

  • add(E e):將元素添加到ArrayList末尾

      public boolean add(E e) {
          ensureCapacityInternal(size + 1);  // Increments modCount!!
          elementData[size++] = e;
          return true;
      }
    

    這里的ensureCapacityInternal()方法的作用是嘗試對數組進行擴容

  • add(int index, E element):將元素添加至指定位置,并將之后的元素向后移一位

      public void add(int index, E element) {
          //判斷索引位置是否合法
          rangeCheckForAdd(index);
    
          ensureCapacityInternal(size + 1);  // Increments modCount!!
          //將原數組從index之后的元素,全部向后移動一位
          //其目的是將index位置空出來
          System.arraycopy(elementData, index, elementData, index + 1,
                           size - index);
          elementData[index] = element;
          size++;
      }
    

    這里的rangeCheckForAdd(index)方法的作用是檢查index的值是否在0~size之間。ensureCapacityInternal()方法的作用與上面一樣,都是嘗試對數組進行擴容。System.arraycopy()方法用于將elementDataindex開始的元素復制到elementDataindex+1位置,一共復制size-index個元素,目的是將index位置空出來,方便后來的重新賦值

  • addAll(Collection<? extends E> c):將集合內的所有元素依次添加到ArrayList末尾

      public boolean addAll(Collection<? extends E> c) {
          Object[] a = c.toArray();
          int numNew = a.length;
          ensureCapacityInternal(size + numNew);  // Increments modCount
          //將a數組內的元素添加到內部數組中
          System.arraycopy(a, 0, elementData, size, numNew);
          size += numNew;
          return numNew != 0;
      }
    

    這里的System.arraycopy()方法用于將a數組從0開始的所有元素復制到elementDatasize位置,一共復制numNew個,使用System.arraycopy()方法相比于一個個復制速度更快

  • addAll(int index, Collection<? extends E> c):將集合內所有元素依次添加到指定位置

      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;
          //如果大于0,就將指定元素后移
          if (numMoved > 0)
              System.arraycopy(elementData, index, elementData, index + numNew,
                               numMoved);
    
          System.arraycopy(a, 0, elementData, index, numNew);
          size += numNew;
          return numNew != 0;
      }
    

    在這里System.arraycopy()的作用是先將指定元素后移numNew位(如果有必要),然后將a數組中的元素復制到指定位置

  1. 修改

ArrayList提供了set(int index, E element)方法來修改指定位置的元素

  • set(int index, E element):將index位置的元素設置為element

      public E set(int index, E element) {
          rangeCheck(index);
    
          E oldValue = elementData(index);
          elementData[index] = element;
          return oldValue;
      }
    

    add(int index, E element)方法類似,只不過不需要后移元素了

  1. 刪除

ArrayList提供了remove(int index)remove(Object o)removeAll(Collection<?> c)retainAll(Collection<?> c)四個方法來刪除元素

  • remove(int index):刪除指定位置的元素

      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;
      }
    
  • remove(Object o):刪除指定元素

      public boolean remove(Object o) {
          if (o == null) { //刪除元素為null的情況
              for (int index = 0; index < size; index++)
                  if (elementData[index] == null) {
                      fastRemove(index);
                      return true;
                  }
          } else { //不為null
              for (int index = 0; index < size; index++)
                  if (o.equals(elementData[index])) {
                      fastRemove(index);
                      return true;
                  }
          }
          return false;
      }
    
      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
      }
    
  • removeAll(Collection<?> c):刪除所有集合中包含的元素

      public boolean removeAll(Collection<?> c) {
          Objects.requireNonNull(c);
          return batchRemove(c, false);
      }
    
      private boolean batchRemove(Collection<?> c, boolean complement) {
          final Object[] elementData = this.elementData;
          int r = 0, w = 0;
          boolean modified = false;
          try {
              for (; r < size; r++)
                  //保留元素策略取決于complement參數
                  //用于實現removeAll跟retainAll方法
                  if (c.contains(elementData[r]) == complement)
                      //將保留元素移動到列表頭部
                      elementData[w++] = elementData[r];
          } finally {
              // Preserve behavioral compatibility with AbstractCollection,
              // even if c.contains() throws.
    
              //當contains方法拋出異常時,r不會等于size
              if (r != size) {
                  //0~w:需要保留
                  //w~r:需要刪除
                  //r~size:不能確定
                  //只刪除必定需要刪除的元素
                  System.arraycopy(elementData, r,
                                   elementData, w,
                                   size - r);
                  w += size - r;
              }
              if (w != size) {
                  // clear to let GC do its work
                  for (int i = w; i < size; i++)
                      elementData[i] = null;
                  modCount += size - w;
                  size = w;
                  modified = true;
              }
          }
          return modified;
      }
    

    removeAll()方法依賴于batchRemove()方法,通過complement屬性指定需要刪除的元素為c集合包含的元素

  • retainAll(Collection<?> c):刪除所有集合中不包含的元素

      public boolean retainAll(Collection<?> c) {
          Objects.requireNonNull(c);
          return batchRemove(c, true);
      }
    

    removeAll()方法類似,通過complement屬性指定需要刪除的元素為c集合不包含的元素

  1. 擴增

ArrayList提供了ensureCapacity(int minCapacity)ensureCapacityInternal(int minCapacity)兩個方法供其他方法調用,用于擴增容量。這兩個方法都依賴ensureExplicitCapacity(int minCapacity)方法,ensureExplicitCapacity(int minCapacity)方法又依賴grow(int 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(minCapacity);
  }

  private void grow(int minCapacity) {
      // overflow-conscious code
      int oldCapacity = elementData.length;
      //計算新的數組容量,以1.5倍擴容
      //1.5倍是通過測試得到的一個最佳值
      //以1.5倍擴容。既不會消耗太多性能,也不會消耗太多內存
      int newCapacity = oldCapacity + (oldCapacity >> 1);
      if (newCapacity - minCapacity < 0)
          newCapacity = 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);
  }
  1. 迭代器

ArrayList實現了ItrListItr兩個迭代器。ArrayList的迭代器能在遍歷的同時添加或刪除元素,是由于在這兩個方法中修改了迭代器的expectedModCount記錄

  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();
      }
  }

  public void add(E e) {
      checkForComodification();

      try {
          int i = cursor;
          ArrayList.this.add(i, e);
          cursor = i + 1;
          lastRet = -1;
          //修改記錄
          expectedModCount = modCount;
      } catch (IndexOutOfBoundsException ex) {
          throw new ConcurrentModificationException();
      }
  }

總結

  1. ArrayList的默認初始容量為10
  2. ArrayList以1.5倍擴容
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,179評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,628評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,444評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,948評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,185評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,717評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,794評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,414評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,750評論 2 370

推薦閱讀更多精彩內容