#java #c#
#java #c#
Вопрос:
Может быть, немного философский вопрос.
Глядя на реализацию Java в ArrayList, я заметил, что при создании нового экземпляра внутренний массив «elementData» (который содержит элементы) создается как новый пустой массив:
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
Однако HashSet (который основан на HashMap) создается с таблицей, а entreySet просто остается null;
transient Node<K,V>[] table;
transient Set<Map.Entry<K,V>> entrySet;
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
Это заставило меня задуматься, поэтому я пошел и посмотрел список C # и HashSet:
https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,61f6a8d9f0c40f6e
https://referencesource.microsoft.com/#System.Core/System/Collections/Generic/HashSet.cs,2d265edc718b158b
Список:
static readonly T[] _emptyArray = new T[0];
public List() {
_items = _emptyArray;
}
HashSet:
private int[] m_buckets;
public HashSet()
: this(EqualityComparer<T>.Default) { }
public HashSet(IEqualityComparer<T> comparer) {
if (comparer == null) {
comparer = EqualityComparer<T>.Defau<
}
this.m_comparer = comparer;
m_lastIndex = 0;
m_count = 0;
m_freeList = -1;
m_version = 0;
}
Итак, есть веская причина, по которой оба языка выбрали empty для list и null для set / map?
Они оба использовали «единый экземпляр» для трюка с пустым массивом, что неплохо, но почему бы просто не иметь нулевой массив?
Ответ №1:
Отвечаю с точки зрения C #.
Для пустой ArrayList
вы обнаружите, что вся логика (получение, добавление, рост, …) работает «как есть», если у вас есть пустой массив в качестве резервного хранилища. Нет необходимости в дополнительном коде для обработки неинициализированного регистра, это делает всю реализацию более аккуратной. И поскольку пустой массив кэшируется, это не приводит к дополнительному выделению кучи, поэтому вы получаете более чистый код без дополнительных затрат.
Для HashSet
это невозможно, поскольку доступ к корзине осуществляется через формулу hashCode % m_buckets.Length
. Попытка вычисления % 0
рассматривается как деление на 0 и, следовательно, недопустима. Это означает, что вам нужно конкретно обработать случай «не инициализированный», поэтому вы ничего не получите от предварительного присвоения полю пустого массива.
Ответ №2:
Инициализация elementData
пустого массива в ArrayList
позволяет избежать null
проверки в grow(int minCapacity)
методе, который вызывает:
elementData = Arrays.copyOf(elementData, newCapacity);
для увеличения емкости резервного массива. При первом вызове этого метода этот оператор «скопирует» пустой массив в начало нового массива (на самом деле он ничего не скопирует).
В HashMap
подобная стратегия не была бы полезной, поскольку при изменении размера массива сегментов вы не копируете исходный массив в начало нового массива, вам приходится перебирать все записи и находить новую ячейку для каждой записи. Поэтому инициализация массива buckets с пустым массивом вместо сохранения его нулевым значением потребует от вас проверки, равна ли длина массива == 0, вместо проверки, равно ли оно нулю. Замена одного условия другим не была бы полезной.