Кажется, я наткнулся на что-то интересное в реализации ArrayList
которое я не могу обернуть вокруг. Вот код, который показывает, что я имею в виду:
public class Sandbox {
private static final VarHandle VAR_HANDLE_ARRAY_LIST;
static {
try {
Lookup lookupArrayList = MethodHandles.privateLookupIn(ArrayList.class, MethodHandles.lookup());
VAR_HANDLE_ARRAY_LIST = lookupArrayList.findVarHandle(ArrayList.class, "elementData", Object[].class);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException();
}
}
public static void main(String[] args) {
List<String> defaultConstructorList = new ArrayList<>();
defaultConstructorList.add("one");
Object[] elementData = (Object[]) VAR_HANDLE_ARRAY_LIST.get(defaultConstructorList);
System.out.println(elementData.length);
List<String> zeroConstructorList = new ArrayList<>(0);
zeroConstructorList.add("one");
elementData = (Object[]) VAR_HANDLE_ARRAY_LIST.get(zeroConstructorList);
System.out.println(elementData.length);
}
}
Идея в том, что если вы создадите ArrayList
следующим образом:
List<String> defaultConstructorList = new ArrayList<>();
defaultConstructorList.add("one");
И посмотрите, что elementData
(Object[]
где хранятся все элементы) будет сообщать 10
. Таким образом, вы добавляете один элемент - вы получаете 9 дополнительных слотов, которые не используются.
Если, с другой стороны, вы делаете:
List<String> zeroConstructorList = new ArrayList<>(0);
zeroConstructorList.add("one");
Вы добавляете один элемент, место зарезервировано только для этого элемента, не более того.
Внутренне это достигается через два поля:
/**
* Shared empty array instance used for empty instances.
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
Когда вы создаете ArrayList
через new ArrayList(0)
- будет использоваться EMPTY_ELEMENTDATA
.
Когда вы создаете ArrayList
помощью new Arraylist()
- используется DEFAULTCAPACITY_EMPTY_ELEMENTDATA
.
Интуитивная часть изнутри меня - просто кричит "удалить DEFAULTCAPACITY_EMPTY_ELEMENTDATA
" и позволяет обрабатывать все случаи с помощью EMPTY_ELEMENTDATA
; конечно код комментария:
Мы отличаем это от EMPTY_ELEMENTDATA, чтобы знать, сколько раздувать при добавлении первого элемента
имеет смысл, но почему один раздувает до 10
(намного больше, чем я просил), а другой до 1
(ровно столько, сколько я просил).
Даже если вы используете List<String> zeroConstructorList = new ArrayList<>(0)
и продолжаете добавлять элементы, в конечном итоге вы попадете в точку, где elementData
больше, чем запрошенный:
List<String> zeroConstructorList = new ArrayList<>(0);
zeroConstructorList.add("one");
zeroConstructorList.add("two");
zeroConstructorList.add("three");
zeroConstructorList.add("four");
zeroConstructorList.add("five"); // elementData will report 6, though there are 5 elements only
Но скорость его роста меньше, чем в случае конструктора по умолчанию.
Это напоминает мне о реализации HashMap
, где количество сегментов почти всегда больше, чем вы просили; но там это делается из-за необходимости в "силе двух" ведер, хотя здесь дело не в этом.
Итак, вопрос в том, может ли кто-нибудь объяснить мне эту разницу?