Java 为什么要启动具有初始容量的ArrayList?
Java 为什么要启动具有初始容量的ArrayList?,java,data-structures,arraylist,capacity,Java,Data Structures,Arraylist,Capacity,ArrayList的常用构造函数是: ArrayList<?> list = new ArrayList<>(); ArrayList list=new ArrayList(); 但也有一个重载构造函数,其初始容量有一个参数: ArrayList<?> list = new ArrayList<>(20); ArrayList list=新的ArrayList(20); 当我们可以随心所欲地附加到ArrayList时,为什么创建具有初始容量
ArrayList
的常用构造函数是:
ArrayList<?> list = new ArrayList<>();
ArrayList list=new ArrayList();
但也有一个重载构造函数,其初始容量有一个参数:
ArrayList<?> list = new ArrayList<>(20);
ArrayList list=新的ArrayList(20);
当我们可以随心所欲地附加到
ArrayList
时,为什么创建具有初始容量的ArrayList
很有用?如果您事先知道ArrayList
的大小,指定初始容量会更有效。如果不这样做,随着列表的增长,内部数组将不得不重复重新分配
最终列表越大,通过避免重新分配节省的时间就越多
也就是说,即使没有预先分配,在
ArrayList
后面插入n
元素也会保证占用总O(n)
时间。换句话说,追加一个元素是一个摊销的固定时间操作。这是通过让每次重新分配都以指数方式增加数组的大小来实现的,通常是以1.5
的系数增加。使用这种方法,操作总数。将ArrayList的初始大小设置为ArrayList(100)
,可以减少内部内存重新分配的次数
示例:
ArrayList example = new ArrayList<Integer>(3);
example.add(1); // size() == 1
example.add(2); // size() == 2,
example.add(2); // size() == 3, example has been 'filled'
example.add(3); // size() == 4, example has been 'expanded' so that the fourth element can be added.
ArrayList示例=新的ArrayList(3);
示例。添加(1);//大小()==1
示例。添加(2);//大小()==2,
示例。添加(2);//size()==3,示例已“填充”
示例。添加(3);//size()==4,示例已“展开”,以便可以添加第四个元素。
正如您在上面的示例中所看到的,如果需要,可以扩展ArrayList
。这并没有告诉您Arraylist的大小通常是原来的两倍(不过请注意,新的大小取决于您的实现)。以下引述自:
“每个ArrayList实例都有一个容量。容量是
用于存储列表中元素的数组。它始终位于
至少与列表大小相同。当元素添加到
ArrayList,其容量会自动增长。增长的详细信息
策略的指定不超过添加元素
固定摊销时间成本。”
显然,如果您不知道将保持什么样的范围,设置大小可能不是一个好主意-但是,如果您心中有一个特定的范围,设置初始容量将提高内存效率 Arraylist的默认大小为10
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this(10);
}
因此,如果要添加100条或更多记录,可以看到内存重新分配的开销
ArrayList<?> list = new ArrayList<>();
// same as new ArrayList<>(10);
ArrayList list=new ArrayList();
//与新ArrayList(10)相同;
因此,如果您对存储在Arraylist中的元素数量有任何想法,那么最好创建具有该大小的Arraylist,而不是从10开始,然后继续增加它 我认为每个ArrayList的初始容量值为“10”。所以不管怎样,如果创建ArrayList时未在构造函数中设置容量,则将使用默认值创建该ArrayList。ArrayList可以包含许多值,并且在执行大型初始插入时,可以告诉ArrayList首先分配更大的存储空间,以免在尝试为下一项分配更多空间时浪费CPU周期。因此,在开始时分配一些空间更有效。我认为这是一种优化。没有初始容量的ArrayList将有约10个空行,并在执行添加时展开
要获得一个包含您需要调用的项数的列表,请执行以下操作:,因为
ArrayList
是一个数据结构,这意味着它被实现为一个具有初始(默认)固定大小的数组。当这个数组被填满时,数组将扩展为一个两倍大小的数组。此操作成本很高,因此您需要尽可能少的操作
因此,如果您知道上限是20个项目,那么创建初始长度为20的数组要比使用默认值(例如15)然后将其调整为15*2=30
,并且只使用20,同时浪费扩展周期要好
另一方面,正如AmitG所说,扩展因子是特定于实现的(在本例中为
(oldCapacity*3)/2+1
)这是为了避免为每个对象重新分配资源
int newCapacity = (oldCapacity * 3)/2 + 1;
内部创建了新对象[]
。在arraylist中添加元素时,JVM需要努力创建
新对象[]
。如果您没有上述代码(您认为的任何算法)进行重新分配,那么每次调用arraylist.add()
时,都必须创建新对象[]
,这是毫无意义的,我们正在浪费时间为每个要添加的对象将大小增加1。因此,最好使用以下公式增大对象[]
的大小。(JSL使用了下面给出的预测公式来动态增长arraylist,而不是每次增长1。因为增长需要JVM的努力) 事实上,我在两个月前写了一篇关于这个话题的文章。这篇文章是为C#的
List
写的,但是Java的ArrayList
有一个非常类似的实现。由于ArrayList
是使用动态数组实现的,因此它会根据需要增加大小。因此,容量构造器用于优化目的
当其中一个调整大小操作发生时,ArrayList会将数组的内容复制到一个新数组中,该新数组的容量是旧数组的两倍。此操作在O(n)时间内运行
实例
以下是ArrayList
将如何增加大小的示例:
10
16
25
38
58
... 17 resizes ...
198578
297868
446803
670205
1005308
因此,列表的容量从10
开始,当添加第11项时,它将增加50%+1
到16
。在第17项中,ArrayList
再次增加到25
a
10
16
25
38
58
... 17 resizes ...
198578
297868
446803
670205
1005308
list1Sttop-list1Start = 14
list2Sttop-list2Start = 10
list1Stop-list1Start = 40
list2Stop-list2Start = 66
public static final int LOOP_NUMBER = 100000;
public static void main(String[] args) {
long list1Start = System.currentTimeMillis();
List<Integer> list1 = new ArrayList();
for (int i = 0; i < LOOP_NUMBER; i++) {
list1.add(i);
}
long list1Stop = System.currentTimeMillis();
System.out.println("list1Stop-list1Start = " + String.valueOf(list1Stop - list1Start));
long list2Start = System.currentTimeMillis();
List<Integer> list2 = new ArrayList(LOOP_NUMBER);
for (int i = 0; i < LOOP_NUMBER; i++) {
list2.add(i);
}
long list2Stop = System.currentTimeMillis();
System.out.println("list2Stop-list2Start = " + String.valueOf(list2Stop - list2Start));
}