учиться сегодняArrayList
Исходный код исходного кода отличается от блогов, написанных другими людьми, многие комментарии в исходном коде переведены, а затем непосредственно вставлены в статью. Редактор намерен изменить стиль написания.Возможно, просмотр исходного кода с проблемами может быть более полезным.В этой статье будут обсуждаться следующие вопросы.
1. Проблемы возникают
-
1. Почему
ArrayList
Контейнер, в котором хранятся элементы коллекции, объявляется какtransient Object[] elementData;
? -
2. Так как
ArrayList
Его можно автоматически расширять, так как же реализован его механизм расширения? -
3. Звонок
ArrayList
изiterator()
Итератор что вернул? -
4. Принять
ArrayList
Когда итератор пересекает коллекцию, почему он выбрасывает при выполнении связанных операций модификации в коллекции?ConcurrentModificationException
, как мы можем избежать этого? -
5. Когда коллекция расширяется или клонируется, копирование коллекции неизбежно, а затем
ArrayList
Как реализовано копирование массива? -
6.
ArrayList
Механизм сериализации в
Сяобянь правоArrayList
После грубого просмотра исходного кода резюмирую вышеперечисленные проблемы.С этими проблемами давайте откроем исходники и решим их вместе!
2. Ответы на вопросы
1. ПочемуArrayList
Контейнер, в котором хранятся элементы коллекции, объявляется какtransient Object[] elementData;
?
ArrayList
Это контейнер для сбора. Так как это контейнер, то должно быть некоторые вещи. Поскольку вам нужно хранить некоторые вещи, вы должны иметь место для хранения! Это как сказать, что вам нужно установить тонну воды, вы должны иметь бассейн, чтобы дать вам! Или вы хотите установить десятки воды Millilita, всегда получайте эту бутылку или сумку! Разница в том, что вода, которую нам нужна, не совпадает с водой, размер контейнера нам нужен!
теперь, когдаArrayList
Дженерики уже поддерживаются, так почемуArrayList
Почему определение контейнера исходного кода должно быть определено следующим образомObject[]
Что насчет типа?
transient Object[] elementData;
На самом деле, используете ли выtransient E[] elementData;
способ объявить или использоватьtransient Object[] elementData;
Утверждения разрешены, разница состоит в том, что первая требует от нас в создании экземпляраelementData
Нужно сделать преобразование типов, и это преобразование типов требует от наших программистов, чтобы гарантировать, что это преобразование не вызовет никаких ошибок. Чтобы предупредить программистов о возможных исключениях преобразования типов, компилятор выдаетType safety: Unchecked cast from String[] to E[]
Предупреждение, я не знаю, будет ли это очень запутанно, следующий код говорит вам:
public class MyList<E> {
// 声明数组,类型为E[]
E[] DATAS;
// 初始化数组,必须做一次类型转换
public MyList(int initialCapacity) {
DATAS = (E[]) new Object[initialCapacity];
}
public E getDATAS(int index) {
return DATAS[index];
}
public void setDATAS(E[] dATAS) {
DATAS = dATAS;
}
}
Приведенный выше код находится в1
где мы объявилиE[]
Массив, точный тип зависит от того, что вы передаетеE
фактический тип, но имейте в виду, что когда выDATAS
При выполнении инициализации вы не можете инициализировать так:
E[] DATAS = new E[10]; // 这句代码将报错
Это,Общие массивы не могут быть реифицированы, то есть не может пройтиnew 泛型[size];
Другими словами, сама JVM не поддерживает дженерики, а только компилирует стирание типов, так как же это решить? Есть два способа:
-
1. Выполните преобразование, как указано выше, но это не рекомендуется.
Как показано в коде выше, мы можем инициализировать как
Object[]
введите, а затем преобразуйте вE[]
, но предпосылкой является то, что вы должны убедиться, что в этом преобразовании не будет ошибок, обычно мы не рекомендуем писать таким образом! -
2. Прямо объявить как
Object[]
Этот способ также
ArrayList
Определение исходного кода, тогда давайте посмотримArrayList
Как это инициализируется:
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
// 此处直接new Object[],不会出现任何错误
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
Но есть одно замечание, но ты звонишьArrayList
изtoArray
Когда метод преобразует коллекцию в массив объектов, могут возникнуть неожиданные результаты.Подробности см. в другом сообщении блога редактора.
[На самом деле у ArrayList есть близнецы, но разница довольно большая! ]
Суммировать:В общем, надо знать, что универсальные массивы нельзя бетонировать, и как их решать! Вам может быть интересно, почему я неtransient
Этот редактор введен в следующую сериализацию и десериализацию.
2. Так какArrayList
Его можно автоматически расширять, так как же реализован его механизм расширения?
Иногда мы должны убедиться, что при добавлении воды исходный контейнер также может быть заполнен новой водой без перелива, то естьArrayList
автоматический механизм расширения. Мы можем представить, что если размер списка равен 10, он может содержать только 10 элементов при нормальных обстоятельствах, нам любопытно вызвать после этогоadd()
метод делает что-то волшебное под капотом, так что давайте посмотримadd()
Как реализован метод:
// 增加一个元素
public boolean add(E e) {
// 确保内部容量大小,size指的是当前列表的实际元素个数
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
Из приведенного выше метода видно, что сначала определяют, достаточна ли внутренняя емкостьsize + 1
Элементы, если возможно, напрямуюelementData[size++] = e;
, иначе его нужно расширить, так как расширить?ensureCapacityInternal()
метод посмотрите, здесь очень важный момент, пожалуйста, помните следующие параметры:
-
minCapacity
Всегда представляет фактическое общее количество элементов после увеличения -
newCapacity
Всегда означает, что список может удовлетворить объем хранилищаminCapacity
Размер списка элементов, которые необходимо расширить
// 校验内部容量大小
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
// 这个方法只有首次调用时会用到,不然默认返回 minCapacity
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 这里如果成立,表示该ArrayList是刚刚初始化,还没有add进任何元素
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
// 扩容判断
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 判断是否需要扩容,elementData.length表示列表的空间总大小,不是列表的实际元素个数,size才是列表的实际元素个数
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
Вышеизложенное будет судить о том, инициализирована ли коллекция, то есть нет ли вызова.add()
Метод, если установлен, будет набор по умолчанию расширение на 10,DEFAULT_CAPACITY
равно 10, в зависимости от того, что больше. последний методgrow()
Условие - элемент контейнера больше 10 и нет свободного места, то есть его нужно расширить, давайте посмотримgrow()
метод:
private void grow(int minCapacity) {
// 获取旧的列表大小
int oldCapacity = elementData.length;
// 扩容之后的新的容器大小,默认增加一半 ..............................1
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果扩容一半之后还不足,则新的容器大小等于minCapacity.............................2
if (newCapacity - minCapacity < 0) newCapacity = minCapacity;
// 如果新的容器大小比MAX_ARRAY_SIZE还大,
if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity);
// 数组拷贝操作
elementData = Arrays.copyOf(elementData, newCapacity);
}
// 最大不能超过Integer.MAX_VALUE
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
выше1
где>>
Представляет сдвиг вправо, что эквивалентно делению на 2 и уменьшению его пополам.2
Может позвонитьaddAll()
метод установлен.
Ниже мы перечислим несколько случаев:
ID | Описание ситуации | вызвать добавить()? | Вызвать addAll(size)? + size size | Результаты |
---|---|---|---|---|
1 | Список только что инициализирован | да | нет | Инициализировать список длиной 10, то есть контейнер расширяется до 10 единиц |
2 | Фактическое количество элементов в списке равно 10, и фактический размер также равен 10. В это время вызывается операция добавления | да | нет | К расширению судна 15, количество элементов контейнеров 11, то есть, есть четыре работоспособности |
3 | Фактическое количество элементов 10, 10 списка 10 - это также длина списка, затем Addall addall | нет | да + 5 | Контейнер расширился до 15, и места не осталось |
4 | Фактическое количество элементов в списке равно 5, а длина списка равна 10. В это время вызывается операция addAll(). | нет | да + 10 | Контейнер расширился до 15, и места не осталось |
Суммировать:
Механизм расширения следующий:
- 1. Сначала установите размер списка по умолчанию
newCapacity
Увеличьте исходную половину, то есть, если исходный размер равен 10, новый размер равен 15; - 2. Если новый размер
newCapacity
все еще не удовлетворенadd
Общее количество входящих элементовminCapacity
, затем измените размер списка на иminCapacity
того же размера, то есть если увеличить вдвоеnewCapacity
15, ноadd
Общее количество входящих элементовminCapacity
равно 20, то 15, очевидно, не может хранить 20 элементов, тогдаnewCapacity
Размер увеличен до 20, что достаточно для хранения 20 элементов; - 3. Если размер развернутого списка больше
2147483639
, то есть больше, чемInteger.MAX_VALUE - 8
, в это время требуется дополнительная обработка, поскольку фактический общий размер элемента может быть больше, чемInteger.MAX_VALUE
даже больше, когда фактический общий размер элементаminCapacity
значение больше, чемInteger.MAX_VALUE
, что больше, чем2147483647
когда, в это времяminCapacity
Значение станет отрицательным, поскольку int имеет знак, и становится отрицательным, когда превышает максимальное значение.
Редактор считает, что третий пункт выше также отражает своего рода мудрость, то есть, когда что-то может пойти не так, мы должны решить это заранее, вместо того, чтобы ждать, когда это будет устранено, когда произойдет ошибка. Это цель нашей эксплуатации и технического обслуживания для мониторинга.
3. ЗвонокArrayList
изiterator()
Как выглядит возвращаемый итератор?
Мы все знаем, что все наборыCollection
Класс реализации интерфейса, и посколькуCollection
наследоватьIterable
интерфейс, поэтому все коллекции повторяемы. Мы часто используем итераторы коллекций для обхода элементов коллекции, как в следующем коде:
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
// 获取集合的迭代器对象
Iterator<String> iter = list.iterator();
while (iter.hasNext()) {
String item = iter.next();
System.err.println(item);
}
Мы можем назвать коллекциюiterator()
метод для получения объекта итератора коллекции, затем вArrayList
середина,iterator()
Как реализуется метод?
public Iterator<E> iterator() {
return new Itr();
}
Супер просто, оказалось, что это новый называетсяItr
объект, то этоItr
Что это? Открываем исходный код и находимItr
класс на самом деле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;......................... 1
Itr() {}
public boolean hasNext() {...}// 具体实现被我删除了
public E next() {...}
public void remove() {...}
public void forEachRemaining(Consumer<? super E> consumer) {...}
final void checkForComodification() {...}
}
Этот итератор реализуетIterator
Интерфейс и реализует связанные методы, предоставляя нам возможность перемещаться по коллекции. Суммировать:ArrayList
Итератор по умолчанию использует свою внутреннюю реализацию класса, и для реализации пользовательского итератора нужно только реализоватьIterator
интерфейс и реализовать связанные методы. реализоватьIterable
Интерфейс указывает, что реализующий класс имеет что-то вродеfor-each loop
Возможность итеративного обхода.
4. ПринятьArrayList
Когда итератор пересекает коллекцию, почему он выбрасывает при выполнении связанных операций модификации в коллекции?ConcurrentModificationException
, как мы можем избежать этого?
В подразделе 3 выше мы рассмотрелиArrayList
Исходный код итератора, мы все знаем, что если во время итерации будет выполнен внутренний вызов не итератораremove
илиclear
метод выдастConcurrentModificationException
Необычно, почему? Давайте посмотрим вместе. Прежде всего, здесь разработаны две очень важные переменные, одна из которыхexpectedModCount
, другойmodCount
,expectedModCount
Определяется во внутреннем итераторе коллекции, как и исходный код в третьем разделе выше.1
показано здесь,modCount
существуетAbstractList
определено в. как третий бар1
Там, где они видны, они равны по умолчанию, т.е.expectedModCount = modCount
, выдает исключение, только если не хочет ждать. Вы действительно хотите создать исключение, если не хотите ждать? Заглянем внутрь итератораnext
метод:
public E next() {
// 在迭代前会对两个变量进行检查
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
// 具体检查
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
Видно, что сообщается об ошибке, когда они не хотят ждать между двумя.Вопрос в том, когда они не захотят ждать? Не торопитесь, давайте посмотримArrayList
изremove
метод:
public E remove(int index) {
rangeCheck(index);
// 这里会修改modCount的值
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()
Когда метод действительно модифицированmodCount
Значение, приводящее к ошибке. Тогда как мы можем сделать, чтобы не хотелось добавлять или удалять ошибку в итерации данных? Ответ заключается в использовании внутреннего итератораremove()
метод.
Суммировать:
Когда итератор выполняет итерацию по коллекции, он не может изменить итерируемую коллекцию, потому чтоmodCount
иexpectedModCount
Два значения переменных не хотят ждать!
5. Когда коллекция расширяется или клонируется, копирование коллекции неизбежно, а затемArrayList
Как реализовано копирование массива?
существуетArrayList
Копия коллекции делается по телефонуArrays
изcopyOf
Метод реализуется следующим образом:
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());.................2
}
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
// 在创建新数组对象之前会先对传入的数据类型进行判定
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
Наконец позвонилSystem
изarraycopy
метод.
6.ArrayList
Механизм сериализации в
В первом разделе мы знаемArrayList
Сохраненные данные определяются как:
transient Object[] elementData;
Нам показалось бы очень странным, что это ядро коллекции, хранящей элементы, но объявленное какtransient
, Означает ли это, что он не будет сериализован? Это не научно! На самом деле данные, хранящиеся в коллекции, все равно будут сериализованы, давайте посмотрим подробнее.ArrayList
серединаwriteObject
метод:
writeObject
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// 这个地方做一个序列化操作
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
Из приведенного выше кода мы видим, чтоArrayList
На самом деле даelementData
сериализуется, но причина этого в том, чтоelementData
В нем может быть много нулевых элементов, чтобы не сериализовать нулевые элементы, так настроитьwriteObject
иreadObject
метод.
Спасибо за чтение, приветствуются комментарии, обсуждаем вместе ~