淺析 java ArrayList

淺析 java ArrayList

簡介

容器是java提供的一些列的數據結構,也能夠叫語法糖。容器就是用來裝在其餘類型數據的數據結構。java

ArrayList是數組列表因此他繼承了數組的優缺點。同時他也是泛型容器能夠自定義各類數據解構、對象容納在其中。數組

結構淺析

  • 父類安全

    AbstractList數據結構

  • 接口併發

    List dom

    Collection函數

    RandomAccess源碼分析

    Cloneablethis

    Serializable線程

基本用法

建立對象:

ArrayList<Integer> arrList = new ArrayList<Integer>();

表示建立一個支持整數類型的List集合。

添加元素

arrList.add(10);
arrList.add(5, 10);

第一個表示在arrList的尾部添加元素10;
第二個表示在arrList的index = 5的地方添加元素爲10,而且會把5以後的數據所有右移

讀取元素

arrList.get(int)

獲取指定位置的值

刪除元素

arrList.remove(int index)
arrList.remove(Object object)

第一種是刪除指定位置的元素,刪除後該位置以後的元素所有左移動
第二種是按指定對象刪除,一樣的刪除後再其末尾的數據要左移動

若是ArrayList 的類型爲Integer, 同時又想按對象刪除元素是須要把其轉換成對象: arrList.remove((Integer)10).

源碼分析

構造函數

ArrayList.class:

 public ArrayList(int paramInt)
 {
   if (paramInt < 0) {
     throw new IllegalArgumentException("Illegal Capacity: " + paramInt);
   }
   elementData = new Object[paramInt];
 }
 
 public ArrayList()
 {
   this(10);
 }
 
  public ArrayList(Collection<? extends E> paramCollection)
 {
   elementData = paramCollection.toArray();
   size = elementData.length;
   if (elementData.getClass() != Object[].class) {
     elementData = Arrays.copyOf(elementData, size, Object[].class);
   }
 }

如上能夠看出來,ArrayList是一個Object對象數組結構的,在沒有被指定數組長度的狀況下默認是爲10。Object是全部對象的父類,因此ArrayList支持全部的對象類型。

添加元素

ArrayList.class

public boolean add(E paramE)
  {
    ensureCapacityInternal(size + 1); // 確保本來的數組容量還能不能容納新元素, 會被直接按當前大小的1/2 擴大。
    elementData[(size++)] = paramE; // 在尾部添加元素,同時讓size + 1
    return true;
  }
  
  public void add(int paramInt, E paramE)
  {
    rangeCheckForAdd(paramInt); // 判斷paramInt會不會越界,必需要小於list的長度才能被插入,不然會報異常:IndexOutOfBoundsException(outOfBoundsMsg(paramInt))
    ensureCapacityInternal(size + 1); // 確保長度是足夠的。
    System.arraycopy(elementData, paramInt, elementData, paramInt + 1, size - paramInt);
    elementData[paramInt] = paramE;
    size += 1;
  }
  
  
  private void ensureCapacityInternal(int paramInt)
  {
    modCount += 1;
    // 新長度未超過最大list的size
    if (paramInt - elementData.length > 0) {
      grow(paramInt); // 對舊的list擴大size
    }
  }
  
  private void grow(int paramInt)
  {
    int i = elementData.length;
    int j = i + (i >> 1); // 等價於 (int)i+i/2, 用位運算效率更高。把長度擴充到原來的1.5倍
    if (j - paramInt < 0) { // 若是擴充的元素仍是小於新的list的長度,則直接去新的list的size進程擴充
      j = paramInt;
    }
    if (j - 2147483639 > 0) { // (其實這裏插入有可能失敗了,由於若是當j = Integer.MAX_VALUE時是沒有真正的擴充本來的list)
      j = hugeCapacity(paramInt);
    }
    // 跟系統從新分配一塊數組空間,並完成拷貝工做
    elementData = Arrays.copyOf(elementData, j);
  }
  
  private static int hugeCapacity(int paramInt)
  {
    if (paramInt < 0) {
      throw new OutOfMemoryError();
    }
    // Integer.MAX_VALUE = 2147483647, 2^31 -1
    return paramInt > 2147483639 ? Integer.MAX_VALUE : 2147483639;
  }

Array.class

public static <T> T[] copyOf(T[] paramArrayOfT, int paramInt)
  {
    return (Object[])copyOf(paramArrayOfT, paramInt, paramArrayOfT.getClass());
  }
  
  public static <T, U> T[] copyOf(U[] paramArrayOfU, int paramInt, Class<? extends T[]> paramClass)
  {
    Object[] arrayOfObject = paramClass == Object[].class ? (Object[])new Object[paramInt] : // 從新分配數組 (Object[])Array.newInstance(paramClass.getComponentType(), paramInt);
    System.arraycopy(paramArrayOfU, 0, arrayOfObject, 0, Math.min(paramArrayOfU.length, paramInt)); // 拷貝數據
    return arrayOfObject; // 新的數組地址
  }

從這塊的代碼分析過來,能夠發現有以下兩種行爲:

  1. 頻繁內存申請和數據拷貝:

    若是list頻繁的插入數據會讓這個list不斷的從新分配數組空間,並從新拷貝數據。這個時候就考慮給一個不錯的ArrayList 數組默認長度或者考慮其餘更好的更適合場景的數據容器。

  2. 線程安全、併發問題

    觀察add(Index, element) 發現ArrayList是非線程安全的,好比線程1 缸取Index的元素,而後作修改由於獲取到的是元素的應用,因此修改Index也就修改了ArrayList的值了。在這個期間若是有線程2去把Index的元素給替換了,那麼線程1的元素操做就被覆蓋了。

讀取元素

ArrayList.class

public E get(int paramInt)
  {
    rangeCheck(paramInt); // 檢查位置的合法性
    return (E)elementData(paramInt); // 取出對應值
  }
  
  private void rangeCheck(int paramInt)
  {
    if (paramInt >= size) {
      throw new IndexOutOfBoundsException(outOfBoundsMsg(paramInt));
    }
  }
  // 從這裏也能夠看出來,就是按數組的形式來操做的
  E elementData(int paramInt)
  {
    return (E)elementData[paramInt];
  }

這塊代碼淺顯易懂就不作論述

刪除元素

ArrayList.class

// 根據指定位置刪除元素
  public E remove(int paramInt)
  {
    rangeCheck(paramInt); // 檢查刪除的元素的位置釋放是合法的
    modCount += 1;
    Object localObject = elementData(paramInt); // 取出對應位置的元素
    int i = size - paramInt - 1;
    if (i > 0) {
      System.arraycopy(elementData, paramInt + 1, elementData, paramInt, i);
    }
    elementData[(--size)] = null; // 對應位置設置爲null值
    return (E)localObject;
  }
  // 根據對象主動刪除元素
  public boolean remove(Object paramObject)
  {
    int i;
    if (paramObject == null) {
      for (i = 0; i < size; i++) {
        if (elementData[i] == null)
        {
          fastRemove(i);
          return true;
        }
      }
    } else {
      for (i = 0; i < size; i++) {
        if (paramObject.equals(elementData[i]))
        {
          fastRemove(i);
          return true;
        }
      }
    }
    return false;
  }
  // 具體的刪除操做
  private void fastRemove(int paramInt)
  {
    modCount += 1;
    int i = size - paramInt - 1;
    if (i > 0) {
      System.arraycopy(elementData, paramInt + 1, elementData, paramInt, i);
    }
    elementData[(--size)] = null;
  }
  // 檢查元素合法性
  private void rangeCheck(int paramInt)
  {
    if (paramInt >= size) {
      throw new IndexOutOfBoundsException(outOfBoundsMsg(paramInt));
    }
  }

從代碼中能夠得出以下結論:

  1. null值

    ArrayList中容許直接存儲null值的

  2. 線程安全、併發問題

    由於他是直接對ArrayList對應位置置爲null,若是有多個線程訪問可能存在數據安全性問題。(跟上述說的問題是同樣的)

  3. 內存問題

    上面說到他的內存隨着數據不斷插入會不斷的去申請內存塊,可是從這裏的這塊代碼中能夠發現,若是內存已經被分配的狀況下是不會隨着元素的遞減(刪除)而收縮內存的。

  4. 按對象刪除元素

    若是是按對象從ArrayList中刪除元素,會從開始位置找到第一個跟刪除對象匹配的值並刪除,若是ArrayList中存在多個相同的對象時須要考慮清楚是不是刪除你想刪除的對象哦。

優缺點

優勢:讀取速度快
缺點:插入慢,非線程安全

總結

在使用ArrayList的時候,腦海裏必須清晰好本身的場景是否會涉及到併發問題。其次要清晰的瞭解到數組的優缺點。由於它就是數組的實現

相關文章
相關標籤/搜索