ArrayList의 소스 코드 분석 (팽창기구 jdk8)

ArrayList의 개요

(1) ArrayList고정 길이의 배열에 기초하여 가변 길이 컬렉션 클래스이다.

(2) ArrayList기본 어레이 소자의 용량이 다수로한다 ArrayList를, 추가 널 및 반복 요소, 수 팽창 큰 배열기구를 재생성한다.

(3) 이후 ArrayList기본 어레이 기반 구현 그것 수 있도록 O(1)임의의 복잡도 완전한 룩업 동작.

(4) ArrayList비 - 스레드 안전 동시 환경의 ArrayList를 동작하는 복수의 스레드는 예외 또는 예측 에러를 던진다.

ArrayList의 구성원 속성

ArrayList의에 대한 다양한 방법의 도입하기 전에 회원의 기본 속성을보십시오. 어느 DEFAULTCAPACITY_EMPTY_ELEMENTDATA与EMPTY_ELEMENTDATA的区别是:当我们向数组中添加第一个元素时,DEFAULTCAPACITY_EMPTY_ELEMENTDATA将会知道数组该扩充多少.

//默认初始化容量
private static final int DEFAULT_CAPACITY = 10;

//默认的空的数组,这个主要是在构造方法初始化一个空数组的时候使用
private static final Object[] EMPTY_ELEMENTDATA = {};

//使用默认size大小的空数组实例,和EMPTY_ELEMENTDATA区分开来,
//这样可以知道当第一个元素添加的时候进行扩容至多少
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

//ArrayList底层存储数据就是通过数组的形式,ArrayList长度就是数组的长度。
//一个空的实例elementData为上面的DEFAULTCAPACITY_EMPTY_ELEMENTDATA,当添加第一个元素的时候
//会进行扩容,扩容大小就是上面的默认容量DEFAULT_CAPACITY
transient Object[] elementData; // non-private to simplify nested class access

//arrayList的大小
private int size;
复制代码

정적 변형 EMPTY_ELEMENTDATADEFAULTCAPACITY_EMPTY_ELEMENTDATA

ArrayList의 생성자

(1) 생성자 초기 용량

  • 배열 변수가 0보다 큰 경우에는으로부터 elementData 크기 파라미터 : initialCapacity 초기화
  • 매개 변수는 0으로부터 elementData 처음에 빈 배열 미만
  • 매개 변수는 예외가 발생, 0보다 작
//参数为初始化容量
public ArrayList(int initialCapacity) {
    //判断容量的合法性
    if (initialCapacity > 0) {
        //elementData才是实际存放元素的数组
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        //如果传递的长度为0,就是直接使用自己已经定义的成员变量(一个空数组)
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}
复制代码

인수 (2) 생성자

  • 생성자에서 하늘의 배열 DEFAULTCAPACITY_EMPTY_ELEMENTDATA으로부터 elementData을 초기화
  • 첫 번째 요소를 추가하기 위해 추가 메소드를 호출하면 확대됩니다
  • DEFAULT_CAPACITY의 크기로 확장 = 10
//无参构造,使用默认的size为10的空数组,在构造方法中没有对数组长度进行设置,会在后续调用add方法的时候进行扩容
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
复制代码

(3) 컬렉션의 매개 변수 유형 생성자

//将一个参数为Collection的集合转变为ArrayList(实际上就是将集合中的元素换为了数组的形式)。如果
//传入的集合为null会抛出空指针异常(调用c.toArray()方法的时候)
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        //c.toArray()可能不会正确地返回一个 Object[]数组,那么使用Arrays.copyOf()方法
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        //如果集合转换为数组之后数组长度为0,就直接使用自己的空成员变量初始化elementData
        this.elementData = EMPTY_ELEMENTDATA;
    }
}
复制代码

이 공법 위의 두 생성자가 할주의 전에 목표는 이해하기 비교적 간단 기본 배열으로부터 elementData을 (this.elementData = XXX) 초기화 . 차이점은 无参构造方法会将 elementData 初始化一个空数组,插入元素时,扩容将会按默认值重新初始化数组. 그리고 有参的构造方法则会将 elementData 初始化为参数值大小(>= 0)的数组. 정상적인 상황에서, 우리는 기본 생성자를 사용할 수 있습니다. 생성자는 케이스의 ArrayList에 삽입하는 방법을 많은 요소 알에서 매개 변수가있는 경우 사용할 수있다.

이 인수없이 위의 생성자의 사용에 관해서, 당신이 호출 할 때 추가 방법은 확장 될, 그래서 방법의 세부 사항에 대해 살펴 보겠습니다 및 확장을 추가 할 때

ArrayList에 추가 방법

일반 과정의 방법은 추가

//将指定元素添加到list的末尾
public boolean add(E e) {
    //因为要添加元素,所以添加之后可能导致容量不够,所以需要在添加之前进行判断(扩容)
    ensureCapacityInternal(size + 1);  // Increments modCount!!(待会会介绍到fast-fail)
    elementData[size++] = e;
    return true;
}
复制代码

우리는 요소를 추가 추가하기 전에 접근 방법을 참조 먼저 크기의 크기를 결정한다, 그래서 우리는 방법의 세부 사항을보고 ensureCapacityInternal

ensureCapacityInternal 분석법

private void ensureCapacityInternal(int minCapacity) {
    //这里就是判断elementData数组是不是为空数组
    //(使用的无参构造的时候,elementData=DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
    //如果是,那么比较size+1(第一次调用add的时候size+1=1)和DEFAULT_CAPACITY,
    //那么显然容量为10
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);
}
复制代码

* 첫 번째 요소에 추가 할 때,이 minCapacity가 (크기 + 1 = 0 + 1 =) 비교 1 Math.max () 메소드 minCapacity가 10이다. ** 즉시 ensureExplicitCapacity 업데이트 된 값의 modCount를 호출하고 추가 용량이 필요한지 여부를 결정

ensureExplicitCapacity 분석법

private void ensureExplicitCapacity(int minCapacity) {
    modCount++; //这里就是add方法中注释的Increments modCount
    //溢出
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);//这里就是执行扩容的方法
}
复制代码

확장의 주요 방법에서 다음보기는 성장한다.

분석하는 성장

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
    // oldCapacity为旧数组的容量
    int oldCapacity = elementData.length;
    // newCapacity为新数组的容量(oldCap+oldCap/2:即更新为旧容量的1.5倍)
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 检查新容量的大小是否小于最小需要容量,如果小于那旧将最小容量最为数组的新容量
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    //如果新容量大于MAX_ARRAY_SIZE,使用hugeCapacity比较二者
    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);
}
复制代码

hugeCapacity 방법

여기에 방법에서 간단히 살펴 hugeCapacity

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    //对minCapacity和MAX_ARRAY_SIZE进行比较
    //若minCapacity大,将Integer.MAX_VALUE作为新数组的大小
    //若MAX_ARRAY_SIZE大,将MAX_ARRAY_SIZE作为新数组的大小
    //MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
复制代码

요약 실행 흐름 방법을 추가

우리는 인수가없는 생성자를 사용하는 경우, 첫 번째 호출 후 실행 과정이 방법을 추가 할 때 무엇을 정렬하는 간단한 맵을 사용

이것은 처음으로 증설 후에 값이 10 인 경우, 추가 메소드를 호출하는 프로세스,

  • 두 번째 요소 계속 추가 (제 호출 매개 변수 전달 방법은주의를 ensureCapacityInternal 크기 + 1 = 1 + 1 = 2)

  • ensureCapacityInternal 방법에서는으로부터 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 확립 때문에 직접 실행 방법 ensureExplicitCapacity하지

  • 제 2 결정 (2-10 = -11)가 설정되지 않은 경우, 즉 newCapacity MAX_ARRAY_SIZE보다 크고가 입력되지 않도록하는 방법은 단지 ensureExplicitCapacity minCapacity가 2 전달 grow방법. 어레이 용량 (10)는, 추가 방법은 크기가 1 증가, true를 돌려줍니다.

  • 상기 3,4- 첨가한다고 가정 ...... 소자 (10) (프로세스와 유사하지만 팽창 성장 방법을 수행하지 않는 경우)

  • 제 판정이 충족되지 않으면 newCapacity 15 계산할 때 11 매의 추가 시간, 성장 촉진 수단을 입력 할 때, (10 + 1 = 11)의 비율 minCapacity가 될 것은 크다. 새로운 용량 hugeCapacity 수단을 입력하지 않을 어레이의 최대 크기보다 크다. 어레이 (15)의 용량을 확장 방법 true를 돌려 추가 크기는 11으로 증가.

추가 (INT 지수, E 요소) 방법

//在元素序列 index 位置处插入
public void add(int index, E element) {
    rangeCheckForAdd(index); //校验传递的index参数是不是合法
    // 1. 检测是否需要扩容
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 2. 将 index 及其之后的所有元素都向后移一位
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    // 3. 将新元素插入至 index 处
    elementData[index] = element;
    size++;
}
private void rangeCheckForAdd(int index) {
    if (index > size || index < 0) //这里判断的index>size(保证数组的连续性),index小于0
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
复制代码

추가 (INT 지수 E 원소 () 메소드 요소의 시퀀스 (지정 위치는 假设该位置合理다음 절차에 대한 것이다)를 삽입)

  1. (여기 상기 구현 됨) 충분한 공간 배열이 존재 하는지를 검출
  2. 인덱스의 모든 요소가 다시 이동 후
  3. 새로운 요소는 인덱스에 삽입됩니다.

새로운 요소가 순서 지정된 위치에 삽입, 당신은 요소의 위치를 필요로하고 다시 이동 후이며, 새로운 요소를위한 공간을 만들기. 이 동작시 복잡도되는 O(N)자주 소자 효율 문제, 설정 시간의 요소, 특히 다수 발생할 수 움직인다. 필요하지 않은 경우 매일 개발에서, 우리는 제 2 삽입 방법은 큰 컬렉션을 통해 호출을 피하려고한다.

ArrayList를 제거하는 방법

ArrayList의 요소를 제거하는 두 가지 방법을 지원합니다

첨자에 따라 삭제 한 제거 (INT 인덱스)

public E remove(int index) {
    rangeCheck(index); //校验下标是否合法(如果index>size,旧抛出IndexOutOfBoundsException异常)
    modCount++;//修改list结构,就需要更新这个值
    E oldValue = elementData(index); //直接在数组中查找这个值

    int numMoved = size - index - 1;//这里计算所需要移动的数目
    //如果这个值大于0 说明后续有元素需要左移(size=index+1)
    //如果是0说明被移除的对象就是最后一位元素(不需要移动别的元素)
    if (numMoved > 0)
        //索引index只有的所有元素左移一位  覆盖掉index位置上的元素
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    //移动之后,原数组中size位置null
    elementData[--size] = null; // clear to let GC do its work
    //返回旧值
    return oldValue;
}
//src:源数组   
//srcPos:从源数组的srcPos位置处开始移动
//dest:目标数组
//desPos:源数组的srcPos位置处开始移动的元素,这些元素从目标数组的desPos处开始填充
//length:移动源数组的长度
public static native void arraycopy(Object src,  int  srcPos,
                                    Object dest, int destPos,
                                    int length);
复制代码

아래와 같이 제거 공정

도 2는, 매칭 파라미터 및 삭제의 첫 번째 요소를 요소를 제거하는 항 (객체 O)을 제거

public boolean remove(Object o) {
    //如果元素是null 遍历数组移除第一个null
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                //遍历找到第一个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
}
复制代码

ArrayList의 다른 방법

ensureCapacity 방법

사용하기 전에 가장 좋은 방법은 새로운 할당 단위의 수를 줄이기 위해 요소의 ensureCapacity 많은 수의 추가

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);
    }
}
复制代码

ArrayList를 요약

(1) ArrayList10 (1.7 초기화 즉이 상기 제 1 부가 요소를 추가 메소드가 호출 지연 후 기본 생성자를 사용하여 고정 길이의 배열에 기초하여 가변 길이 컬렉션 클래스는, 용량 부족으로부터 elementData 때 것이다 초기화한다 있습니다 용량) (10)로 초기화된다.

(2) ArrayList기본 어레이 소자의 용량이 다수로한다 ArrayList를, 추가 널 및 반복 요소, 수 팽창 큰 배열기구를 재생성한다. ArrayList확장 길이는 1.5 배 원래 길이

(3) 이후 ArrayList기본 어레이 기반 구현 그것 수 있도록 O(1)임의의 복잡도 완전한 룩업 동작.

(4) ArrayList비 - 스레드 안전 동시 환경의 ArrayList를 동작하는 복수의 스레드는 예외 또는 예측 에러를 던진다.

(5) 첨가 순서 편리

(6) 삭제 및 삽입 Copy 어레이 성능 저하 (사용 LinkindList)

(7)에 Integer.MAX_VALUE - 8 : 주로 다른 JVM의 계정에, JVM 일부는 확장 후 용량이 MAX_ARRAY_SIZE보다 큰 경우, 우리는, 최소 용량 및 MAX_ARRAY_SIZE 비교를 비교해야 첫번째 일부 데이터를 추가 할 것입니다 그것은 경우에 비해 대형 단지는 Integer.MAX_VALUE를 취할 수, 그렇지 않은 경우는 Integer.MAX_VALUE -8. 이 jdk1.7에서 시작에 불과

메커니즘을 빠르게 실패

설명 빠른 실패 :

시스템 설계에서는 르파 시스템은 즉시 인터페이스에 장애를 나타낼 가능성이있는 상태를보고 하나입니다. 르파 시스템은 일반적으로 정상 작동을 중지보다는 아마도 결함이 과정을 계속 시도하도록 설계되었습니다. 이러한 설계는 종종 작업의 여러 지점에서 시스템의 상태를 확인하므로, 어떠한 장애는 초기에 검출 될 수있다. 페일 빠른 모듈의 책임은 다음 시스템의 다음-가장 높은 수준을 처리시키는 오류를 감지한다.

아마 의미 : 시스템 설계, 시스템은 신속하게 바로 실패 나타낼 수있는 시스템의 고장 사례를보고 할 수 있습니다. 시스템의 실패는 일반적으로 빠른 정지가 정상적으로 작동보다는 잠재적 결함의 과정을 계속하는 것을 시도하도록 설계되었습니다. 이러한 설계는 일반적으로 검사 장치의 동작 상태에서, 복수의 포인트이며, 따라서, 오작동 조기 검출 할 수있다. 신속한 장애 감지 모듈 의무 잘못, 시스템을 처리 오류의 다음으로 높은 수준을 수 있습니다.

사실, 시스템 설계 시간을 일을하는 것은 특별한 상황을 고려 예외가 발생하면, 같은 다음의 간단한 예로서, 중지 및 직접보고

//这里的代码是一个对两个整数做除法的方法,在fast_fail_method方法中,我们对被除数做了个简单的检查,如果其值为0,那么就直接抛出一个异常,并明确提示异常原因。这其实就是fail-fast理念的实际应用。
public int fast_fail_method(int arg1,int arg2){
    if(arg2 == 0){
        throw new RuntimeException("can't be zero");
    }
    return arg1/arg2;
}
复制代码

부적절하게 사용 된 경우 설계하는 데 사용되는 메커니즘의 많은 부분에서 자바 컬렉션 클래스에서 코드가 실패 빠른 예상치 못한 설계 트리거 메커니즘이 발생합니다. 르파 우리가 일반적으로 자바에서 말하는 메커니즘을 기본 자바 오류 감지 메커니즘의 집합을 의미한다 . 복수의 thread가 구조적 변화의 컬렉션의 일부에서 작동 할 때, 예외 동시 변경 ** 던질 것이다 메커니즘을 트리거 할 가능성이 ConcurrentModificationException**입니다. 물론, 경우 멀티 스레드 환경에서의 foreach는 탐색하는 경우 추가 기능을 사용할 때 / 방법도 발생 될 수 있습니다 제거합니다. 참조 메커니즘을 빨리 실패 간단한 요약으로 여기에,

之所以会抛出ConcurrentModificationException异常,是因为我们的代码中使用了增强for循环,而在增强for循环中,集合遍历是通过iterator进行的,但是元素的add/remove却是直接使用的集合类自己的方法。这就导致iterator在遍历的时候,会发现有一个元素在自己不知不觉的情况下就被删除/添加了,就会抛出一个异常,用来提示可能发生了并发修改!所以,在使用Java的集合类的时候,如果发生ConcurrentModificationException,优先考虑fail-fast有关的情况,实际上这可能并没有真的发生并发,只是Iterator使用了fail-fast的保护机制,只要他发现有某一次修改是未经过自己进行的,那么就会抛出异常。

추천

출처juejin.im/post/5d42ab5e5188255d691bc8d6