Я, кажется, наткнулся на что-то интересное в реализации 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
, где количество сегментов почти всегда больше, чем вы просили;но там это делается из-за необходимости в «силе двух» ведер, но здесь дело не в этом.
Итак, вопрос в том, может ли кто-нибудь объяснить мне эту разницу?