ArrayList 기능 및 소스 코드 읽기

ArrayList는 매일 개발할 때 가장 자주 사용되는 컬렉션이며 빠른 액세스로 인해 선호됩니다.

하나, 시공 방법

일반적으로 사용되는 구성 방법에는 두 가지가 있습니다. 하나는 기본 매개 변수없는 구성 방법이고 다른 하나는 매개 변수를 포함하는 구성 방법입니다.

매개 변수없는 기본 구성

//new ArrayList(0) 会创建一个空实例的共享空数组实例
private static final Object[] EMPTY_ELEMENTDATA = {
    
    };

//new ArrayList()默认构造会创建一个提供默认大小的实例的共享空数组实例
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {
    
    };

public ArrayList() {
    
    
     this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

그렇다면 EMPTY_ELEMENTDATA 및 DEFAULTCAPACITY_EMPTY_ELEMENTDATA의 두 인스턴스가있는 이유는 무엇입니까? 주요 용도는 확장 할 때 현재 ArrayList가 생성되는 생성 방법 식별하는 것입니다.

매개 변수 구조

/**
 * @param  initialCapacity  初始容量
 * 如果initialCapacity<0 就抛出异常
 */
public ArrayList(int initialCapacity) {
    
    
    if (initialCapacity > 0) {
    
    
        this.elementData =
                new Object[initialCapacity];
    } else if (initialCapacity == 0) {
    
    
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
    
    
        throw new IllegalArgumentException(
                "Illegal Capacity: "+ initialCapacity);
    }
}

일반적으로 ArrayList의 세 가지 구조는 다음과 같습니다.

  • new ArrList () : 使用 DEFAULTCAPACITY_EMPTY_ELEMENTDATA (첫 번째 추가 요소를 확장해야합니다.), 기본 길이는 10입니다.
  • new ArrList (0) : 使用 EMPTY_ELEMENTDATA , (첫 번째 추가 요소를 확장해야합니다.), 그러나 첫 번째 확장의 결과는 위와 다릅니다.
  • new ArrList (21) : 길이가 21 인 배열 생성
  • 기타 상황 : 예외 발생

두, 멤버 변수

특정 작업을보기 전에 관련 멤버 변수를 살펴보십시오.

//new ArrayList() 第一次扩容后的大小
private static final int DEFAULT_CAPACITY = 10;

//elementData.lentgh是当前ArrayList的容量大小,用于扩容判断
transient Object[] elementData;

//当前ArrayList中的元素个数
private int size;

셋, 추가 () 연산

add ()는 null을 포함한 모든 요소를 ​​허용합니다.

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

//确定是否需要扩容
private void ensureCapacityInternal(int minCapacity) {
    
    
     ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}    

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    
    
	 //new ArrayList()在第一次add会return max(10,1)
     if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    
    
        return Math.max(DEFAULT_CAPACITY, minCapacity);
     }
     //new ArrayList(0) 在第一次add时会走这一步 return 1
     return minCapacity;
}

/*
    new ArrayList()会在grow()内扩容成 10
    new ArrayList(0)会在grow()内扩容成 1 
*/
private void ensureExplicitCapacity(int minCapacity) {
    
    
     modCount++;

     // new ArrayList()和new ArrayList(0)的length均为0,因为一开始创建的都是空的数组实例
     if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

        추가 프로세스는 먼저 확장 여부를 결정한 다음 새 요소를 배열 끝에 넣는 것입니다.
        new ArrayList ()와 new ArrayList (0)는 확장 결과가 다릅니다. 처음 추가 할 때 calculateCapacity(Object[] elementData, int minCapacity)메서드에서 new ArrayList ()는 10을 반환하고 new ArrayList (0)는 1을 반환합니다. . 반환 결과는 grow ()의 첫 번째 확장 길이를 결정합니다.

private void grow(int minCapacity) {
    
    
        // new ArrayList()和new ArrayList(0)的length均为0,因为一开始创建的都是空的数组实例
        int oldCapacity = elementData.length;
        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.5 배 원래 길이 및 용도는 (oldCapacity >> 1)비트 단위 작업을 고효율로. 이면 newCapacity >Integer.MAX_VALUE - 8확장 된 용량을 Integer.MAX_VALUE와 동일하게 두면 일부 사람들은 배열 요소 수가 Integer.MAX_VALUE-8 보다 크거나 같아서 최대 값으로 확장하는 이유를 궁금해 할 수 있습니다 .이 값이 더 작 으면 거기에 많은 어레이가 될 것입니다. " "더 쉽게 "최대로 확장하고 메모리를 낭비합니다. 새 어레이
        의 용량을 결정한 후 Arrays.copyOf(elementData, newCapacity)원래 참조가 새 어레이를 가리 키도록 새 어레이가 생성됩니다.
구체적인 프로세스는 다음과 같습니다.
여기에 사진 설명 삽입

add (int index, E element) : 특정 위치에 삽입

public void add(int index, E element) {
    
    
	//检查插入的位置是否存在数值越界
    rangeCheckForAdd(index);
    //确定是否需要扩容
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1, size - index);
    elementData[index] = element;
    size++;
}

        ArrayList가 삽입 및 수정에 적합하지 않은 이유는 무엇입니까 ( 면접 테스트 사이트 )? 그 이유는 삽입 System.arraycopy(elementData, index, elementData, index + 1, size - index);하면 요소가 복사 된 후 위치 색인에 삽입되고 시작 색인 + 1부터 순서대로 배치되고 요소가 하나씩 뒤로 이동 하고 마지막으로 지정된 인덱스 할당. 이 프로세스는 여전히 시간이 많이 걸리며 확장되면 훨씬 더 번거로울 것입니다.

addAll () : 컬렉션의 내용을 ArrayList에 추가합니다.

//把集合的内容加到末尾
public boolean addAll(Collection<? extends E> c) {
    
    
    Object[] a = c.toArray();
    int numNew = a.length;
    //判断是否需要扩容
    ensureCapacityInternal(size + numNew); 
    //其中的参数size就是说把 来源数组a copy到目标数组elementData的最后一个位置
    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)
    	//先把目标数组elementData的部分元素后移numNew个单位
        System.arraycopy(elementData, index, elementData, index + numNew, numMoved);
	
	//再把 来源数组a copy到目标数组elementData下标为[index,index+numNew]的位置
    System.arraycopy(a, 0, elementData, index, numNew);
    size += numNew;
    return numNew != 0;
}

모두 addAll ()이지만 在指定位置插入 addAll(int index, Collection<? extends E> c)로직은 조금 더 복잡하며 두 단계가 있습니다.

  1. 대상 배열의 요소 (인덱스 위치에서 시작하는 요소) 를 numNew 단위만큼 뒤로 이동합니다 . 여기서 numNew는 소스 배열의 길이입니다.
  2. 소스 배열 a의 모든 요소를 ​​복사하여 대상 배열 elementData의 [index, index + numNew] 위치에 배치합니다.

넷, remove ()

remove ()는 삭제할 요소 뒤에있는 모든 요소 하나씩 앞으로 이동 하여 삭제 작업을 완료하는 것입니다.

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(int index)작업은 다음과 같습니다.

  1. 인덱스가 범위를 벗어 났는지 확인
  2. 인덱스가 배열의 끝에 있는지 확인하고, 배열의 끝에 있으면 마지막 요소를 Null로 둡니다.elementData[--size] = null
  3. index가 배열의 마지막 요소가 아닌 경우 elementData [index + 1] 및 이후 요소를 복사하고 아래 첨자 index 에서 시작하여 순서대로 배치 하고 마지막으로 배열의 끝 요소를 null로 만듭니다.

remove (Object o) : 지정된 요소 삭제

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 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
}

지정된 요소를 삭제하는 remove(Object o)것은 배열을 순회하고 하나씩 비교해야하기 때문에 좀 더 번거로운 작업입니다. 비교가 같으면 호출 fastRemove(int index) 本质还是remove(int index)이 다음 요소를 하나씩 앞으로 이동합니다.

다섯 가지 get () : 지정된 위치에서 요소를 가져옵니다.

public E get(int index) {
    
    
    rangeCheck(index);
    return elementData(index);
}

ArrayList의 맨 아래 계층은 배열이며 배열은 임의 액세스를 지원합니다.메모리 연속성또한 어레이 순회를 효율적으로 만듭니다. 이중 연결 목록을 기반으로하는 LinkedList는 하나씩 탐색해야합니다.기억은 연속적이지 않다그 이유는 검색에 적합하지 않습니다.

여섯, 반복자

반복기는 ArrayList의 내부 클래스로, 동일한 ArrayList 인스턴스에서 작동하는 여러 스레드로 인한 데이터 일관성 문제 (더티 읽기)를 방지하기 위해 순회 중에 동시에 삭제하거나 추가 할 수 없습니다.

private class Itr implements Iterator<E> {
    
    
    int cursor;       // index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such
    int expectedModCount = modCount;

    public boolean hasNext() {
    
    
        return cursor != size;
    }

    @SuppressWarnings("unchecked")
    public E next() {
    
    
        checkForComodification();
        int i = cursor;
        //数组为空会抛异常
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        //new ArrayList(0)后调用next()会抛异常
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }

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

    final void checkForComodification() {
    
    
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

3 개의 멤버 변수가 있습니다.

  1. 커서 : 방문 할 다음 요소
  2. lastRet : 배열이 비어 있는지 확인하는 데 사용할 수있는 -1로 정의 된 마지막 액세스 된 요소의 인덱스입니다.
  3. expectedModCount : ArrayList의 수정 시간에 대한 예상 값입니다. 예상 값과 modCount의 실제 값이 일치하지 않으면 순회 프로세스 중에 반복기가 제거 및 추가와 함께 예외를 throw합니다 ( 제거 및 추가 할 때마다). ArrayList의 원래 구조를 변경하면 modCount + 1 ).

이터레이터에는 hasNext (), next (), remove ()의 3 가지 메소드가 있습니다.

  1. 다음 요소가 있는지 확인하려면 public boolean hasNext(): ( 위첨자 를 방문 할 다음 요소에 커서 놓을 때를 나타냄 ) 동일한 크기로, 현재가 배열의 마지막 요소에 도달 했음을 나타냅니다.
  2. 다음 요소 반환 public E next(): 배열이 비어 있으면 NoSuchElementException이 발생하고, 배열이 new ArrayList (0)에 의해 생성 된 경우 next ()를 직접 호출하면 ConcurrentModificationException이 발생합니다. 일반적으로 방문다음 요소 가 반환됩니다.
  3. 요소 제거 remove(): elementData [lastRet] 삭제 다음 요소가 한 자리 앞으로 이동 한 후 커서도 한 자리 앞으로 이동해야하는데 이는에 반영됩니다 cursor = lastRet.

일곱, 스레드는 안전하지 않습니다

        ArrayList는 스레드로부터 안전하지 않습니다. 여러 스레드가 동시에 동일한 ArrayList를 트래버스, 추가 및 삭제하면 더티 읽기, 반복 된 확장 및 낭비되는 공간과 같은 문제가 발생한다고 상상해보십시오.
        회피 위험하기 위해, ArrayList를 대신 동시 변경을 위험의 르파 메커니즘이 있습니다. 점에서 구현되는 if (modCount != expectedModCount) throw new ConcurrentModificationException();
        스레드 안전이 필요한 경우, 벡터 수 Collections.synchronizedList를 사용 하고, ArrayList에가 도 될 수있다 ArrayList를 포장하는 데 사용됩니다. 두 가지가 있습니다 보장 에 의해 동기화 키워드 수정 방법. 안전 스레드.

추천

출처blog.csdn.net/qq_44384533/article/details/108498376