首页 > 技术文章 > ArrayList和数组的区别

jinlin-2018 2021-04-12 18:20 原文

ArrayList有自动扩容机制,数组没有

ArrayList是如何扩容的呢?

jdk1.2jdk1.6中的ArrayList的源码中,在构造方法上的确是创建了一个初始容量为10的容器。

jdk_1.7中的源码是这样写的

 

 

 调用构造方法时,如下

 

 

 说明从jdk_1.7开始,当你进行new ArrayList();创建的是一个空数组初始容量就不是10了,而是一个空数组

 

jdk_1.7中的ArrayList定义了一个常量这个值就是10

 

 

 这个常量在进行扩容的时候,会和当前容器的最小容量进行比较,取最大的作为新容器的容量
例如当你第一次调用add进行添加元素的时候,会触发扩容

 

 

 进入ensureCapacityInternal(size + 1);,一开始是一个空容器所以size=0传入的minCapacity=1

 

 

 DEFAULT_CAPACITY=10,传入的最小容量minCapacity=1,二者取大,故minCapacity变成10。

会发现minCapacity被重新赋值为10 (DEFAULT_CAPACITY=10
传入ensureExplicitCapacity(minCapacity);这时minCapacity=10
下面是方法体:

 

 

 10-0>0

modCount++是在父类AbstarctList中,
后又调用了扩容grow(10)

 

 

 第一次扩容size,即elementData.length=0,故newCapacity=0+0/2=0,故进入第4行的if中,使得newCapacity=minCapacity=10。

java中有三种移位运算符

<<      :     左移运算符,num << 1,相当于num乘以2

>>      :     右移运算符,num >> 1,相当于num除以2

>>>    :     无符号右移,忽略符号位,空位都以0补齐

 

这是jdk1.7中的扩容机制代码
其中int newCapacity = oldCapacity + (oldCapacity >> 1);就是面试过程中经常提问到的,1.5倍原来数组的长度

当第二次扩容时,size,即elementData.length=10,则minCapacity=size+1=11

 

minCapacity=minCapacity=11。

进入grow(11)

这时oldCapacity=10,则newCapacity=10+10/2=15,即扩容1.5倍

下一次oldCapacity=15,则newCapacity=15+15/2=22.5,即扩容1.5倍 

引自 https://blog.csdn.net/qq_41813208/article/details/107777539

 

Arrays.copyOf函数

copyOf函数:把原数组中的数据复制到新数组中,属于深层拷贝。

所以也是创建一个新数组,逐个拷贝过去。故扩容是费时的。

 

 

 

add(E e)如果不需要扩容,只是把数据加到队尾,不需要挪位置,那么是很快的。

add(int index, E element)若要插到队中的话,就要用到arraycopy函数,挪空塞数据,那会很慢。

每次用带索引的add和remove函数时都需要调用arraycopy函数,是费时的。

 

了解一下copyOf函数

在 Java 编程中经常会遇到数组拷贝操作,一般会有如下四种方式对数组进行拷贝。 
* for遍历,遍历源数组并将每个元素赋给目标数组。 
* clone方法,原数组调用clone方法克隆新对象赋给目标数组,更深入的克隆可以看之前的文章《从JDK角度看对象克隆》。 
* System.arraycopy,JVM 提供的数组拷贝实现。 
* Arrays.copyof,实际也是调用System.arraycopy。

 

 

 

 

 

 

 

 

关于@HotSpotIntrinsicCandidate

这个注解是 HotSpot VM 标准的注解,被它标记的方法表明它为 HotSpot VM 的固有方法, HotSpot VM 会对其做一些增强处理以提高它的执行性能,比如可能手工编写汇编或手工编写编译器中间语言来替换该方法的实现。虽然这里被声明为 native 方法,但是它跟 JDK 中其他的本地方法实现地方不同,固有方法会在 JVM 内部实现,而其他的会在 JDK 库中实现。在调用方面,由于直接调用 JVM 内部实现,不走常规 JNI lookup,所以也省了开销。 

System.arraycopy为 JVM 内部固有方法,它通过手工编写汇编或其他优化方法来进行 Java 数组拷贝,这种方式比起直接在 Java 上进行 for 循环或 clone 是更加高效的。数组越大体现地越明显。

引自 https://blog.csdn.net/weixin_34308389/article/details/92348857

 

System.arraycopy是一个native函数,需要看native层的代码

找到对应的openjdk6-src/hotspot/src/share/vm/prims/jvm.cpp,这里有JVM_ArrayCopy的入口

引自博客园某BLOG,两方证明是hotspot虚拟机JVM底层的东西。挖个坑以后看。

 

为什么用右移运算符呢?运算规则是怎样的呢?

移位运算是二进制的运算,计算机运算速度快。

右移
运算方式:数值的补码向右移X位,符号位不变(左边补上符号位)

10换算成二进制等于1010

2^3  2^2  2^1  2^0

  8     4      2      1         8+2=10

  1     0      1      0

正数的补码与原码相同,即原码=补码=1010

32位计算机x86,即 0000 0000 0000 0000 0000 0000 0000 1010

右移一位指整体往右挪,不是小数点右移,即变成0000 0000 0000 0000 0000 0000 0000 0101

64位计算机x64同理。

101转换为十进制为5

2^2  2^1  2^0

 4      2      1         4+1=5

 1      0      1

引自 https://blog.csdn.net/onezg/article/details/53103559

 

不需要扩容的时候

第二次add

此时还没完全add完,故当前的size还是1

minCapacity是假使add完成,add一个后的size,即当前minCapacity=2

 

EMPTY_ELEMENTDATA={}

这时ArrayList中已经有了一个数据,故elementData不等于{},故直接进入ensureExplictCapacity(2)

 

 

 此时minCapacity=2,elementData.length是当前最大可存放容量,即上一次扩容的10,故elementData.length=10

故2-10不大于0,则不会进入grow函数,也就不会执行扩容了。

sqList

推荐阅读