如何在Java中获取ArrayList的容量?

27

众所周知,Java ArrayList 使用数组实现,并以容量为10进行初始化,每次增加50%。如何获取当前 ArrayList 的容量而不是大小。

谢谢。


3
你为什么需要这个信息? - Bart Kiers
3
汪……没有具体的意思……我在一次面试中听到的。 - JavaUser
1
谷歌把我带到了这里。我需要这些信息的原因是,我有一个ArrayBlockingQueue,我正在通过它进行take()操作,处理并将结果存储在另一个集合中。我认为使用与ABQ容量匹配的初始容量来存储结果集合会很好。这不是一个奇怪的用例,对吧?对于我来说还有其他方法可以解决这个问题(例如将容量存储为静态常量并引用它),但并非所有情况都适用。 - ericsoco
请查看 https://dev59.com/FXA65IYBdhLWcg3w6DNO#3564928 - Mohammad Kholghi
11个回答

20

我认为这是不可能的。你有什么使用场景?我相信C# ArrayLists 有一个 .capacity 属性,但Java ArrayList类没有公开这个信息。

你可以使用带有初始容量参数的构造函数,也可以使用 ensureCapacity() 方法来减少增量重新分配的次数。

如果您真的担心内存使用情况,您还可以使用 trimToSize() 方法。


1
关于向量的情况呢? - JavaUser
4
Vector类有一个capacity()方法,返回当前容量。您有时间查看API吗?http://java.sun.com/j2se/1.4.2/docs/api/java/util/Vector.html - Mark B
2
没有问题,我只是在查看你的回答。谢谢。 - JavaUser
2
如果你不知道容量,如何决定何时调用trimToSize()?每次调用trimToSize()都不高效。不公开容量是一个错误。 - jack

17

你可以通过反射来获取它:

public abstract class ArrayListHelper {

    static final Field field;
    static {
        try {
            field = ArrayList.class.getDeclaredField("elementData");
            field.setAccessible(true);
        } catch (Exception e) {
            throw new ExceptionInInitializerError(e);
        }
    }

    @SuppressWarnings("unchecked")
    public static <E> int getArrayListCapacity(ArrayList<E> arrayList) {
        try {
            final E[] elementData = (E[]) field.get(arrayList);
            return elementData.length;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }
}

7
如果字段不叫做elementData,这将会失败得非常惨。而且因为API没有指定这个名字,所以任何实现(以及任何版本!)都可以将其命名为完全不同的名称。 - Joachim Sauer
4
@JoachimSauer仍然称之为elementData,我不明白为什么他们要改变这一点,但如果/当他们这样做时,你仍然可以轻松修改代码! - BaSsGaz
2021年仍然称为“elementData”...这种解决方法是有效的,也不错。 - dieter
这个解决方法已经失效,自JDK 17+以后,非法反射访问(使私有字段可访问)将会失败。9版本之后和16版本之前会产生警告,16版本需要使用JVM参数才能使其工作,而这个参数在17版本中完全被删除。不要这样做。 - predi

7

您可以使用反射在Java中获取ArrayList的当前容量。以下是一个示例:

package examples1;

import java.util.ArrayList;
import java.util.List;
import java.lang.reflect.Field;

public class Numbers {

    public static void main(String[] args) throws Exception {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(1);
        System.out.println(getCapacity(numbers));
    }

    static int getCapacity(List al) throws Exception {
        Field field = ArrayList.class.getDeclaredField("elementData");
        field.setAccessible(true);
        return ((Object[]) field.get(al)).length;
    }
}

这将输出:10 注释:
  1. getCapacity() method modified from the original at http://javaonlineguide.net/2015/08/find-capacity-of-an-arraylist-in-java-size-vs-capacity-in-java-list-example.html
  2. Note that the default capacity of 10 is granted after the first add to the list. If you try this before adding, you will get an output of 0
  3. To force a capacity without adding, pass it in the constructor like so:

    List<Integer> numbers = new ArrayList<>(20);
    

2
哦,非常感谢你,伙计。你救了我的一天! 附注:如果你遇到“没有字段elementData”的异常,我不知道是什么,但我从Android Studio切换到NetBeans后,异常消失了。 - Dyno Cris

1

查看ArrayList的规范,我没有看到提供此信息的方法。

话虽如此,ensureCapacity方法似乎是朝着正确方向迈出的一步(注意:它不能保证正确答案):当调用它时,它确保容量至少为指定参数。因此,如果ArrayList实现使用此方法来确保容量(而不是调用某些私有方法/直接操作相关字段),则可以通过覆盖此方法来获取当前容量。您还需要以类似的方式覆盖trimToSize()

当然,这种解决方案在可移植性方面并不是很好,因为不同供应商的JVM上的ArrayList实现可能会有所不同。

以下是代码示例:

public class CapacityTrackingArrayList<T> extends ArrayList<T> {

   // declare a constructor for each ArrayList constructor ...


   // Now, capacity tracking stuff:
   private int currentCapacity = 10;

   public int getCapacity() { return currentCapacity; }

   public void ensureCapacity(int arg) {
     currentCapacity = arg;
     super.ensureCapacity(arg);
   }

   public void trimToSize() { currentCapacity = size(); super.trimToSize(); }

}

1
ensureCapacity() 只是确保它至少有那么多的空间。如果它有更多的空间,它将不会执行任何操作。如果稍后您继续添加元素以使其需要更多空间,它将增长。您的示例代码将无法处理这些情况。您的所有代码都只能让您想起之前传递给 ensureCapacity() 的值,这可能与以后的真实容量没有实际关系。 - Mark B
毫无疑问,我并不认为这是一个完美的解决方案,也不是一个可移植的解决方案。它只是朝着正确的方向迈出的一步。 - Itay Maman

1

ArrayList的默认容量为10。一旦达到最大大小,新的容量将会是:

新容量=(当前容量*3/2)+1。


1
使用ArrayList的主要目的是动态添加新元素,因此没有特定的方法来获取ArrayList的容量。
每次动态添加一个元素都会导致重新分配内存空间,由于重新分配内存空间需要耗费时间,因此防止重新分配可以提高性能。你可以通过调用ensureCapacity()手动增加ArrayList的容量,但是你无法找出ArrayList的容量。

1
你可以使用 Vector 替代 ArrayList。Vector 支持 capacity() 方法。

6
楼主显然想要一个ArrayList的容量,而不是一个Vector - Manuel
1
Vector可以实现与ArrayList相同的功能。Java没有暴露容量,这是Java的问题。如果您不知道容量,如何决定何时调用trimtosize?每次调用trimtosize都不高效。在这种情况下,改用Vector是一个很好的选择。 - jack

0

不记得是否有,但你可以通过查看ArrayList的源代码自己完成。Java开发人员应该利用与SDK捆绑的源代码。


不确定这对帮助他在运行时找到当前容量有多大用处。除非他拿起源代码并添加一个公开容量的方法,然后在所有地方使用他的自定义版本的ArrayList。 - Mark B
1
哈哈,先有人点赞然后又有人踩了他 :)) 我给了他一根鱼竿,但你却给了他一条鱼... 现在怎么办,他会要求 Vector、LinkedList 吗? :)) - instcode
@mbaird “你可以自己做” 可能意味着“你可以查看源代码以找出获取容量的方法”,而不是“你可以更改源代码”。 - bjornars

0
我刚刚查看了关于ArrayList类的Sun文档,我所看到与容量相关的唯一方法是ensureCapacity(int minCapacity),这并不完全符合您的需求。祝你好运!

0
Java中Array List的默认容量为2。我无法找到之前阅读的模糊文档。但是在初始化时,大小将为零。 一旦我们添加第三个元素,它将在另一个内存位置创建一个容量加倍的数组。引用将相应地移动,并且以前的数组将被垃圾回收。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接