在ArrayList
的实现中,我似乎偶然发现了一些有趣的东西,但是我无法理解。以下是一些代码,展示了我的意思:
Translated:
我似乎在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 = {};
当您通过new ArrayList(0)
创建ArrayList
时,将使用EMPTY_ELEMENTDATA
;当您通过new ArrayList()
创建ArrayList
时,将使用DEFAULTCAPACITY_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
的实现,那里的存储桶数量几乎总是比您请求的多;但那是因为需要“2的幂”个存储桶,而不是本例中的情况。
所以问题是 - 有人能向我解释这种差异吗?
ArrayList
不再具有大小为10
的数组;该数组是惰性计算的——这完全不同。 - EugeneArrayList
中实现了两种不同的策略,而以前只有一种。为了决定使用哪种策略,引入了新的常量。 - JimmyBArrayList
没有任何条目时,它现在将由一个空数组支持,这可以通过EMPTY_ELEMENTDATA
实现,那么为什么还要保留两者呢? - Eugenenew ArrayList<>(0)
那样,你将不得不分配更多的后备数组,这可能会增加内存使用/垃圾回收使用率。 - Andy Turnernew ArrayList()
:1)要么不向其中添加任何内容(因此需要一个空数组)2)添加最多10个元素,但几乎总是接近10个。 - Eugene