java中ArrayList怎么用

java中ArrayList怎么用

小编给大家分享一下java中ArrayList怎么用,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!

ArrayList构造器简介

在java中,一切皆对象,一切操作又离不开对象,ArrayList也是如此,所以想要研究ArrayList是如何工作的就要从ArrayList的构造开始。

ArrayList提供了三个构造,分别是不带任何参数的构造器、接受一个int类型值的构造器和接受一个带泛型的Collection的构造器,首先我们从最简单的没有任何参数、也是大多数人最常用的这个构造器开始。

无参数构造器

打开源码可以看到无参数构造器非常的简单,只有一行代码,代码如下:

java中ArrayList怎么用

其中DEFAULTCAPACITY_EMPTY_ELEMENTDATA是ArrayList中定义的一个静态的不可变的空数组,而elementData则是ArrayList保存实际数据使用的数组引用,也就是说如果用默认无参数构造器构造ArrayList的话是没有初始数组的(为空,所有实例共享)。

接收一个int类型值的构造器

打开源码,可以看到如下代码:

java中ArrayList怎么用

可以看到,当传入参数为0时与不传参数的逻辑是一致的,当传入参数大于0时ArrayList会构建一个与传入参数为大小的Object数组,而当传入参数小于0时,就会抛出一个非法参数异常(IllegalArgumentException)了。

接收一个Collection参数的构造器

仍然是打开源码查看,可以看到以下代码:

java中ArrayList怎么用

首先是将传入参数转换为数组,然后将这个数组引用赋值给ArrayList内部维护的数组引用,如果这个数组的长度是0时你会发现这时的行为又与空参数构造器的行为一致了,而当这个数组长度不是0时,ArrayList做了一些小动作,对于这些小动作,注释也写的很清楚了,c.toArray might (incorrectly) not return Object[],也就是说这个数组有可能不是Object类型的数组(在不正确的情况下,这个是由JDK本身的一个BUG造成的,具体的可以去JDK的BUG库http://bugs.java.com/bugdatabase/中查找BUG ID为6260652的BUG,有兴趣的同学可以自行查找阅读,本文不去详细介绍),所以为了保证代码的健壮性,就加了这么一个类型检查,而为什么要检查这个数组是不是Object类型的呢?这个很简单,就是因为如果此处引用的是一个String类型的数组,后续如果往里边放入一个非String类型的数据就会报错,而这个错误通常是到运行时才会发现的。

ArrayList的构造器介绍到此就结束了,下面开始介绍ArrayList的方法,由于篇幅有限,所以本文仅挑选几个ArrayList初级使用者最常用的方法介绍。

public boolean add(E e)

这个方法是最常用的一个方法,其作用就是将一个新元素加入到当前list的尾部,源码如下:

java中ArrayList怎么用

可以看到这个方法非常简洁,调用了另外一个方法ensureCapacityInternal,然后将数据填充进数组,更新size,整个方法就结束了,固定返回的true,也就是该方法只要不抛出异常肯定会返回true,至于ensureCapacityInternal方法会在后面讲解,ArrayList中也多次调用到了该方法,算是核心方法之一。可以很容易看出,该方法的渐进时间复杂度为O(1)(不扩容的情况下)。

public void add(int index, E element)

这个方法的作用是将一个元素插入到list的指定位置,源码如下:

java中ArrayList怎么用

首先是第一行,第一行很简单,看方法名就知道是范围检测,具体的行为就是检测当前传入的index参数是否大于当前size或者小于0,如果大于当前size或者小于0的话就抛出一个索引越界异常(IndexOutOfBoundsException),然后该方法也调用了ensureCapacityInternal方法,然后就是将参数index位置和index位置之后的数据给全部后移一位,也就是说该方法插入的位置后边的数据越多,那么要移位的数据也就越多,效率也就越低。该方法的最坏时间复杂度为O(n)(不扩容的情况下),其中n等于ArrayList的size。

public E remove(int index)

该方法是将指定索引处的数据删除,源码如下:

java中ArrayList怎么用

该方法的第一行仍然是范围检查,不同的是只要index不是大于等于ArrayList的size就不会抛出异常,但是这一行不抛出异常不代表后边就不会抛出异常,该处没有显示检查是否小于0是因为后边第三行有从数组中取数据这个操作,而对于该操作,如果index小于0的话是一定会抛出异常的。然后第四行计算了一下需要移动位置的元素,也就是index位置之后还有多少元素需要前移,当该值大于0时(注:只会大于0或者等于0,不可能小于0),说明有需要移动的元素,将这numMoved个元素统统前移一位,然后将原数组的最后一位设置为null以便于GC可以清除该处引用(注:因为使用的System.arraycopy方法,所以并不会删除数据,只会复制,而原来数组的最后一位虽然已经复制前移,而且size也已经更新,获取不到该引用的数据,但是该引用仍然存在,并没有被清空,所以下次GC的时候如果该处没有填充新的值覆盖,那么这个引用将不会被回收并且也获取不到该处的值,需要详细了解的可以看一下java的GC原理或者加我Q1213812243询问^_^)。最后,将第三行获取到的该位置的数据返回,至此,该方法结束。由于该方法也需要原数组数据的移位,所以很容易可以得出该方法的最坏时间复杂度也是O(n),其中n等于ArrayList的size。

public boolean remove(Object o)

该方法是从ArrayList中删除指定元素,更准确的说是从ArrayList中删除第一个与指定元素相同的元素,话不多说上源码:

java中ArrayList怎么用

由该方法的实现可以看出,如果传入的元素为null时,该方法将删除ArrayList中的第一个null元素(不是全部的null元素)并返回true,如果传入元素不为null时,该方法将调用传入元素的equals方法与ArrayList中的元素一一对比,直到发现第一个相同的元素,然后删除并返回true,如果遍历整个list发现都没有与传入对象相同的对象,那么该方法将返回false表示删除失败。这里要注意一个小细节,该方法实际删除元素操作用的是fastRemove而不是上一个提到的public E remove(int index),而根据命名可以猜测出fastRemove方法删除是比较快的,但是具体是哪儿快了呢?首先查看源码:

java中ArrayList怎么用

与上一个remove方法对比,很容易可以看出这个方法由于是内部使用的,也不需要返回值,所以并没有去检查参数index的范围合法性,也没有把要删除的数据取出,省了这两个开销,虽然省的并不多,但是如果在一个大型系统中,这样的小开销累计起来也是很大的。同样的,很容易可以得出该方法的时间渐进复杂度为O(n),其中n等于ArrayList的size。

public E get(int index)

该方法是从ArrayList中取数据,数据存起来的目的就是为了后续的使用,所以该方法是最常用也是最重要的一个方法,源码如下:

java中ArrayList怎么用

可以看出,该方法很简单,仅仅检查了一下参数的范围,然后就直接取数据返回了,而elementData方法也只有短短的一行

java中ArrayList怎么用

很容易可以得出该方法的时间渐进复杂度为O(1)。

private void ensureCapacityInternal(int minCapacity)

这个方法是前面提及的但是并没有具体介绍的一个方法,把这个方法放在最后是因为这个方法算是ArrayList的一个核心方法,该方法的作用就是检查内部数组是否够用,如果不够用的话自动扩充,源码如下:

java中ArrayList怎么用

该方法又调用了ensureExplicitCapacity方法,而ensureExplicitCapacity方法的源码如下:

java中ArrayList怎么用

该方法判断了一下参数是否大于当前数组的最大长度,如果大于那么调用grow方法扩容,如果不大于那么不做任何操作。grow方法源码如下:

java中ArrayList怎么用

该方法也很简单,只有7行代码,在第一行首先计算出原数组的长度,然后第二行计算出原数组长度扩充1.5倍后的新的长度(注:右移一位等价于除以2),第三行判断了扩充1.5倍后的长度是否大于参数(实际需要的长度),如果小于该长度的话将扩充后的新长度更新为参数值(minCapacity),然后在第5行判断了扩充后的长度是否大于MAX_ARRAY_SIZE这个静态变量,如果大于该长度的话使用hugeCapacity方法确定是否溢出(即数组长度是否大于Integer.MAX_VALUE),该方法进入后正常情况是肯定会返回Integer.MAX_VALUE,因为外部已经判断过minCapacity是否大于MAX_ARRAY_SIZE了,只有大于该值才能进来,所以此方法必定返回Integer.MAX_VALUE,ArrayList内部维护的数组长度也达到了最大值Integer.MAX_VALUE,而此时当list再次填充满需要扩容的时候,传到此处的参数为(size+1)将是一个负数(Integer溢出),然后调用hugeCapacity方法时会发现传进来的参数是一个负数,说明已经溢出了,将会抛出一个异常。(注:如果对此处的溢出不理解可以加QQ1213812243询问或者自行百度)

该方法的最后一行会将数据从老数组迁移至新数组,而老数组由于没有了引用所以会在后续的GC中被清除。很容易可以得出该方法的时间渐进复杂度为O(n)(需要扩容的情况下,不需要扩容为O(1)),其中n等于当前ArrayList的size。

以上是“java中ArrayList怎么用”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注行业资讯频道!