当前位置: 首页 > 知识库问答 >
问题:

为什么使用不同的ArrayList构造函数会导致内部数组的增长率不同?

童宏富
2023-03-14

我似乎在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 = {};

我们将它与EMPTY_ELEMENTDATA区分开来,以了解添加第一个元素时要膨胀多少

这是有道理的,但为什么一个膨胀到10(比我要求的多得多),而另一个膨胀到1(和我要求的完全一样)。

即使您使用list 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

但它的增长速度小于默认构造html" target="_blank">函数的情况。

这让我想起了hashmap实现,其中的桶数几乎总是比您要求的多;但这是因为需要“两个”桶的功率,而不是这里的情况。

所以问题来了--有人能给我解释一下这个区别吗?

共有1个答案

雷锋
2023-03-14

即使是在实现方式不同的旧版本中,您也可以精确地得到您所要求的内容,所指定的内容也是不同的:

构造初始容量为10的空列表。

构造具有指定初始容量的空列表。

即使将初始容量指定为零时也适用。

Java ;8增加了一个优化,即ten elements数组的创建被推迟到添加第一个元素之后。这是针对arraylist实例(使用默认容量创建)在很长一段时间内甚至整个生存期内保持为空的常见情况。此外,当第一个实际操作是addall时,它可能会跳过第一个数组大小调整操作。这不会影响具有显式初始容量的列表,因为这些列表通常是谨慎选择的。

正如本答复所述:

 类似资料:
  • 我正在尝试编写一个Python实用程序,将Oracle的RAW字节字符串(作为字符串)转换为Guid,反之亦然。我试图重用我在C#中构建的同一个实用程序中的算法,但是从同一个字节数组构造一个和一个会产生不同的Guid/UUID。它们是一样的,不是吗?我读过UUID只是一个更好的术语。 在C#中,我有一个字节数组,< code>byte_array如下所示: 在 Python 中,我有一个字节数组,

  • 问题内容: 在C ++中,从构造函数内部调用虚拟函数时,它的行为不像虚拟函数。 我认为第一次遇到这种行为的每个人都会感到惊讶,但第二次认为这是有道理的: 只要派生的构造函数没有被执行的对象是 不是 又一个 衍生 实例。 那么如何调用派生函数呢?前提条件还没有建立的机会。例: Java和.NET完全相同,但是他们选择了另一种方式,这可能是 产生最少惊讶原则 的唯一原因吗? 您认为哪个是正确的选择?

  • 我注意到我正在开发的应用程序中有一个奇怪的bug。在修改一个类时,我将一组属性移动到构造函数中进行自动连接,而不是使用字段注入,然而,由于循环依赖关系,这导致我在启动时出错。下面是导致错误的依赖关系的分解: 我正在尝试在TargetClass的内部使用ServiceA 服务A通过其构造函数注入了服务B 通过现场注入,服务B已注入服务C 通过现场注入,ServiceC已注入TargetClass 我

  • 允许您执行以下操作: 但不是这个: 可能是因为返回类型不是函数签名的一部分。但是是一个类类型,它被赋予一个返回类型,并且知道构造它的函数对象的返回类型。所以这里可能有编译器错误。 为什么没有编译器错误?

  • 我有一个抽象的超类,它有一个形式的构造函数 并希望创建该抽象类的一个子类,该子类不是以字符串作为其第一个参数,而是采用一个表示给定字符串名称的整数值,例如,0代表某个字符串,1代表另一个字符串,依此类推。 当我尝试编写窗体子类(int number,int amount)的构造函数时,我得到一个格式为“Implicit super constructor is undefined.必须显式调用另一

  • 问题内容: 为什么不能和这两个一起在构造函数中使用? 合并这样的东西的原因是什么? 问题答案: 将在同一类中调用另一个构造函数,而将调用超级构造函数。如果构造函数中没有,则编译器将隐式添加一个。 因此,如果两者都允许,您最终可能会两次调用构造函数。 示例(不要在参数中寻找含义): 现在,如果调用,将调用以下构造函数: 更新 : 如果您能够使用并且可能会得到如下结果: ( 注意 :这是为了说明 如果