Коллекции Java, серия 1, ArrayList

Java задняя часть исходный код
Коллекции Java, серия 1, ArrayList

1. Обзор ArrayList

ArrayListБазовая структура данныхдинамический массив, поэтому мы можем назвать это очередью массива. Зависимости ArrayList:

public class ArrayList<E> extends AbstractList<E>
    	implements List<E>, RandomAccess, Cloneable, java.io.Serializable

Как видно из зависимостей, ArrayList — это во-первых список, а во-вторых, у него есть родственные функции списка, поддерживающие быстрое (фиксированное время) позиционирование ресурсов. Можно выполнять операции копирования, поддерживается сериализация. Здесь нам нужно сосредоточиться на AbstractLit и RandomAccess. Этот класс предназначен для определения основных свойств списка, а также для определения общих действий в нашем списке. RandomAccess в основном обеспечивает функцию быстрого определения местонахождения ресурса.

2. Переменная-член ArrayList

  /**
     * Default initial capacity.数组默认大小
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     空队列
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
        如果使用默认构造方法,则默认对象内容是该值
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
        用于存储数据
     */
    transient Object[] elementData; 

     // 当前队列有效数据长度
      private int size;

     // 数组最大值
     private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

В исходном коде ArrayList в основном присутствуют указанные выше переменные-члены:

  • elementData : динамический массив, который является основным массивом, в котором мы храним данные.
  • DEFAULT_CAPACITY: длина массива по умолчанию, которая будет введена при вызове конструктора по умолчанию.
  • size: запишите эффективную длину данных, метод size() возвращает это значение напрямую
  • MAX_ARRAY_SIZE: максимальная длина массива, если расширение превышает это значение, установите длину в Integer.MAX_VALUE

Расширьте свое мышление:EMPTY_ELEMENTDATAиDEFAULTCAPACITY_EMPTY_ELEMENTDATAОба являются двумя пустыми объектами массива, в чем разница между ними? Мы проведем подробное сравнение, когда объясним метод построения в следующем разделе.

3. Метод строительства

ArrayListТри метода построения предусмотрены в:

  • ArrayList()
  • ArrayList(int initialCapacity)
  • ArrayList (Коллекция c)

В зависимости от конструктора способ строительства будет разным. В нашей обычной разработке ArrayList (целая емкость) может вызываться внутри конструктора по умолчанию, но в ArrayList дляРазные конструкторы имеют разные внутренние реализации, в основном связанные с упомянутыми выше переменными-членами.

3.1 СписокСписков()

Это описано в комментариях к исходному коду следующим образом: Построить пустой список с начальной емкостью десять

    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

Как видно из исходного кода, он просто конвертируетelementDataуказал наDEFAULTCAPACITY_EMPTY_ELEMENTDATAадрес хранения иDEFAULTCAPACITY_EMPTY_ELEMENTDATAНа самом деле это пустой объект массива, так почему же он говорит создать список с размером по умолчанию 10?

Или давайте подумаем об этом с другой стороны, а что, если нам нужно добавить элементы в этот пустой массив?

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  //确认内部容量
        elementData[size++] = e;
        return true;
    }
    
    private void ensureCapacityInternal(int minCapacity) {
        // 如果elementData 指向的是 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的地址
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            设置默认大小 为DEFAULT_CAPACITY
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        //确定实际容量
        ensureExplicitCapacity(minCapacity);
    }
    
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // 如果超出了容量,进行扩展
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    
    private void grow(int minCapacity) {
        // overflow-conscious code
        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. add(E e): чтобы добавить элемент, сначала определите длину массива elementData, а затем установите значение

2. sureCapacityInternal (int minCapacity): определить, является ли элемент пустым, если да, установить длину массива по умолчанию

3. sureExplicitCapacity (int minCapacity): определите, превышает ли длина массива ожидаемого роста текущую емкость, и если да, вызовите функциюrow().

4. Grow (int minCapacity): расширить массив

回到刚才的问题:为什么说创建一个默认大小为10 的列表呢?或许你已经找到答案了~

3.2 ArrayList(int initialCapacity)

Инициализировать размер массива в ArrayList в соответствии с указанным размером, если значение по умолчанию больше 0, инициализировать в соответствии с параметром, если он равен 0, указать адрес памяти EMPTY_ELEMENTDATA (аналогично использованию конструктора по умолчанию выше) . Если меньше 0, генерируется исключение IllegalArgumentException.

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

Расширение мышления: почему здесь используетсяEMPTY_ELEMENTDATAвместо использования конструктора по умолчаниюDEFAULTCAPACITY_EMPTY_ELEMENTDATA? Заинтересованная детская обувь может подумать сама, а знания после размышлений ваши~

3.3 ArrayList (коллекция c)

Преобразуйте данные, хранящиеся в Collection c, в форму массива (метод toArray()), а затем оцените, равна ли текущая длина массива 0, если она равна 0, только массив по умолчанию (EMPTY_ELEMENTDATA); в противном случае скопируйте данные .

    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

3.4 Резюме

Как видно из приведенных выше трех методов построения, на самом деле то, что делает каждый конструктор, отличается, особенно прямая разница между конструктором по умолчанию и ArrayList(int initialCapacity), нам нужно сделать некоторые различия.

  • ArrayList(): указывает наDEFAULTCAPACITY_EMPTY_ELEMENTDATA, когда список используется, он будет инициализирован, а размер массива по умолчанию будет установлен путем определения того, является ли он объектом DEFAULTCAPACITY_EMPTY_ELEMENTDATA.
  • ArrayList(int initialCapacity): когда initialCapacity > 0, установите длину. если initialCapacity = 0, указывает наEMPTY_ELEMENTDATAПри использовании длина массива по умолчанию не устанавливается.

Следовательно, существенное различие между DEFAULTCAPACITY_EMPTY_ELEMENTDATA и EMPTY_ELEMENTDATA заключается в том, будет ли установлена ​​длина массива по умолчанию.

4. Добавить метод (Добавить)

ArrayList добавляет четыре метода добавления:

  • add(E element)
  • add(int i , E element)
  • addAll(Collection<? extends E> c)
  • addAll(int index, Collection<? extends E> c)

4.1 add(E element)

Сначала посмотрите на исходный код add(T t):

  public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // 元素个数加一,并且确认数组长度是否足够 
        elementData[size++] = e;		//在列表最后一个元素后添加数据。
        return true;
    }

В сочетании с конструктором по умолчанию или другими конструкторами, если массив по умолчанию пуст, массив будет инициализирован при вызове метода sureCapacityInternal(). Поэтому при вызове конструктора по умолчанию мы создаем пустой массив, но в комментариях он описан как массив длины 10.

4.2 добавить (int i , T t)

   public void add(int index, E element) {
    // 判断index 是否有效
        rangeCheckForAdd(index);
    // 计数+1,并确认当前数组长度是否足够
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index); //将index 后面的数据都往后移一位
        elementData[index] = element; //设置目标数据
        size++;
    }

Этот метод на самом деле похож на добавление выше. Этот метод может вставлять элементы в соответствии с позицией элемента и указанной позицией. Конкретная логика выполнения выглядит следующим образом:

1) Убедитесь, что позиция, в которую вставляется число, меньше или равна текущей длине массива и не меньше 0, иначе выдается исключение

2) Убедитесь, что массив использовал длину (размер) плюс 1 достаточно для хранения следующих данных

3) Количество модификаций (modCount) автоматически увеличивается на 1. Если текущая длина массива (размер) плюс 1 больше, чем текущая длина массива, вызывается метод Grow для увеличения массива

4) Метод Grow изменит длину текущего массива в 1,5 раза по сравнению с исходной емкостью.

5) Убедившись, что емкости достаточно, используйте System.arraycopy для перемещения всех элементов после позиции (индекса), которую необходимо вставить, на одно место.

6) Сохраните новое содержимое данных в указанной позиции (индексе) массива.

4.3 addAll(Collection<? extends E> c)

    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

Метод addAll() завершает добавление всех данных коллекции путем преобразования данных в коллекции в Array[] и добавления их в массив elementData. В начале ничего особенного в целом, сборка здесь может вызывать исключение управления NullPointerException и на это нужно обратить внимание.

4.4 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;
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);

        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }

По сравнению с описанным выше методом, здесь в основном есть еще два шага, чтобы определить, находится ли позиция добавления данных в конце, если она находится в середине, вам нужно сначала переместить данные назад на позицию длины коллекции.

5. Удалить метод (Удалить)

Существует пять способов удаления данных в ArrayList:

  • удалить (интервал я)
  • удалить (элемент E)
  • removeRange (начало int, конец int)
  • чистый()
  • удалить все (коллекция c)

5.1. удалить (целое я):

Удаление данных не меняет длину массива, оно только удаляет перестановку данных.Если у цели нет других действительных ссылок, она будет переработана во время GC.

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; // 让指针最后指向空,进行垃圾回收
        return oldValue;
    }

5.2, удалить (элемент E):

Таким образом, массив будет внутренне пройден методом AccessRandom.Когда совпадающие данные равны объекту, для его удаления будет вызвана функция fastRemove().

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

быстроудалить(): Операция fastRemove фактически согласуется с вышеупомянутым удалением на основе индексов.

   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
    }

5.3, removeRange (int fromIndex, int toIndex)

Этот метод в основном удаляет данные в диапазоне и может перезаписать всю часть данных через System.arraycopy.

    protected void removeRange(int fromIndex, int toIndex) {
        modCount++;
        int numMoved = size - toIndex;
        System.arraycopy(elementData, toIndex, elementData, fromIndex,
                         numMoved);

        // clear to let GC do its work
        int newSize = size - (toIndex-fromIndex);
        for (int i = newSize; i < size; i++) {
            elementData[i] = null;
        }
        size = newSize;
    }

5.4. очистить()

Непосредственно установите для всего массива значение null , которое здесь подробно описываться не будет.

5.5. removeAll (Коллекция c)

В основном по телефону:

    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 进行判断删除或留下
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
            // 进行数据整理
            if (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;
    }

Также есть вызовы в continueAll (Collection c), основные функции — удалить элементы, содержащиеся в этой коллекции, и оставить элементы, содержащиеся в этой коллекции.

расширить мышление

Узнав метод удаления ArrayList, а затем объединив наши часто используемые методы удаления, мы подумаем, какие шаги вызовут проблемы.Обычно мы выбираем список переменных, и если он совпадает, удаляем его. Проходим следующими путями:

  • foreach(): ConcurrentModificationException в основном происходит
  • for(int i;**;i++): те же данные пропускаются, см.: https://blog.csdn.net/sun_flower77/article/details/78008491
  • Обход итератора: в основном появляетсяConcurrentModificationExceptionСсылка: https://www.cnblogs.com/dolphin0520/p/3933551.html

Эффективный способ избежать исключения ConcurrentModificationException — использовать CopyOnWriteArrayList в пакете Concurrent, который будет подробно проанализирован позже.

6. в массив()

ArrayList предоставляет 2 функции toArray():

  • Object[] toArray()
  • T[] toArray(T[] contents)

Вызов toArray() вызовет исключение "java.lang.ClassCastException", но вызов toArray(T[] content) обычно возвращает T[].

toArray() генерирует исключение, потому что toArray() возвращает массив Object[] и преобразование Object[] в другие типы (например, преобразование Object[] в Integer[]) вызовет исключение «java. lang.ClassCastException», поскольку Java не поддерживает понижение.

исходный код toArray():

    public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }
    

7. подсписок()

Если нам нужно получить определенную часть данных в коллекции для работы в процессе разработки, мы можем получить ее с помощью метода SubList(), который создаст внутренний класс SubList() для ArrayList.

SubList наследует AbstractList и реализует большинство действий AbstractList.

Следует отметить, что определенная часть данных в коллекции, возвращаемой SubList, будет связана с исходной коллекцией. То есть, когда мы работаем с Sublist, это все равно повлияет на исходную коллекцию. Давайте посмотрим на метод добавления в Sublist:

  	public void add(int index, E e) {
        rangeCheckForAdd(index);
            checkForComodification();
            parent.add(parentOffset + index, e);
            this.modCount = parent.modCount;
            this.size++;
        }

Видно, что операция сложения в подсписке на самом деле вызывает операцию сложения в родительском (то есть исходном наборе). Поэтому при использовании метода subList вы должны четко подумать, нужно ли вам изменять элементы вложенной коллекции, не затрагивая исходную коллекцию списка.

Суммировать

ArrayList в целом относительно прост, но ArrayList имеет следующие характеристики:

  • ArrayList реализует свои собственные методы сериализации и десериализации, поскольку он сам реализует методы private void writeObject(java.io.ObjectOutputStream s) и private void readObject(java.io.ObjectInputStream s).

  • ArrayList реализован на основе метода массива, без ограничения емкости (он будет расширяться)

  • Возможно, потребуется расширить вместимость при добавлении элементов (так что лучше заранее рассудить), а вместимость не будет уменьшаться при удалении элементов (если хотите уменьшить вместимость, trimToSize()), при удалении элементов, установите для удаленного элемента position значение null, и следующий gc. Пространство памяти, занимаемое этими элементами, будет восстановлено.

  • поток небезопасен

  • add(int index, E element): При добавлении элемента в указанную позицию в массиве необходимо скопировать позицию и все элементы за ней на один бит назад.

  • get(int index): при получении элемента в указанной позиции вы можете получить его непосредственно через индекс (O(1))

  • remove(Object o) должен пройти через массив

  • remove(int index) не нужно обходить массив, просто оцените, соответствует ли индекс условиям, эффективность выше, чем remove(Object o)

  • contains(E) должен пройти через массив

  • Обход с помощью итератора может вызвать исключение многопоточности

расширить мышление

  • Расширенное мышление 1. Как интерфейс RandomAccess обеспечивает быстрое обнаружение ресурсов?
  • Расширенное мышление 2. Какова роль EMPTY_ELEMENTDATA и DEFAULTCAPACITY_EMPTY_ELEMENTDATA?
  • Расширение мышления 3. Яма метода удаления?
  • Расширенное мышление 4: почему ArrayList не является потокобезопасным?

использованная литература

http://www.cnblogs.com/skywang12345/p/3308556.html https://blog.csdn.net/daye5465/article/details/77971530 https://blog.csdn.net/daye5465/article/details/77971530