ArrayList函数及相关解释

Posted 天堂1223

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ArrayList函数及相关解释相关的知识,希望对你有一定的参考价值。

Java中ArrayList是使用对象数组实现的,默认的数组容量为10。在ArrayList中,默认创建了两个空的对象数组,一个用户该对象的空实例,一个用于当第一个元素被添加的时候,该数组要被填充多少。

同时,该类中还有一个transient属性的对象数组,transient描述的对象属性,表示在序列化的时候,不用去序列化该属性。

1、ArrayList构造函数

首先我们看一下ArrayList的构造函数,该类有三个构造函数,分别是:

  1. ArrayList(int initialCapacity)
  2. ArrayList()
  3. ArrayList(Collection<? extends E> c)

第一个构造函数,指定了ArrayList的初始化大小。我们看一下该函数的函数体:

 public ArrayList(int initialCapacity) 
        if (initialCapacity > 0) 
            this.elementData = new Object[initialCapacity];
         else if (initialCapacity == 0) 
            this.elementData = EMPTY_ELEMENTDATA;
         else 
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        
    

在该构造函数中,判断初始化大小的值,如果大于零,则直接新建一个数组长度为initialCapacity的对象数组,并赋值给elementData。如果等于0,则elementData直接被赋值给默认创建的空对象数组。如果小于0,则抛出异常。

第二个构造函数为空参数,该函数中直接创建一个空的对象数组,并且其初始化大小为默认的大小。

public ArrayList() 
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    

第三个构造函数的参数为一个集合,也就是说构造一个包含指定集合元素的列表。

public ArrayList(Collection<? extends E> c) 
        elementData = c.toArray();
        if ((size = elementData.length) != 0) 
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
         else 
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        
    

首先,调用集合的toArray()方法,将集合转换为对象数组,并赋值给elementData,接着给ArrayList中描述列表长度的属性size赋值为数组的长度,如果数组长度不为0,在这里,因为toArray()函数可能不会返回指定对象类型的数组,所以需要调用Arrays.copyOf()函数,该函数可以指定赋值的对象的类型;如果数组长度为0,则将elementData直接赋值为初始创建的空数组。

2、ArrayList中的功能函数

2.1 trimToSize()函数

该函数是将ArrayList实例的容量修建为列表实际的大小。

public void trimToSize() 
    modCount++;
    if (size < elementData.length) 
        elementData = (size == 0)
          ? EMPTY_ELEMENTDATA
          : Arrays.copyOf(elementData, size);
    

所有在ArrayLisy函数操作的地方,都会增加modCount的值,这样是为了在后续的函数中判断是否出现ConcurrentModificationException的异常。

只有在当前列表实际的长度小于数组的长度的时候,才会执行该操作。所以在该函数中进行了判断。如果实际大小为0,则将elementData直接赋值为空数组,否则,使用Arrays.copyOf()函数,对数组的实际长度进行复制。

2.2 ensureCapacity(int minCapacity)函数

通过参数指定的最小容器来确保列表的容量,以使得列表能够容纳至少指定容量大小的元素。

public void ensureCapacity(int minCapacity) 
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            // any size if not default element table
            ? 0
            // larger than default for default empty table. It's already
            // supposed to be at default size.
            : DEFAULT_CAPACITY;

        if (minCapacity > minExpand) 
            ensureExplicitCapacity(minCapacity);
        
    

这里比较有意思的是判断elementData是否是默认元素表,如果不是默认元素表,则最小扩展值设置为0;如果是默认元素表,则最少扩展值为默认容量。这样对参数中指定的最小容量与当前扩展值做对比,如果最小容量较大,则进行扩容处理。便会调用ensureExplicitCapacity(int minCapacity)函数。

2.3 ensureExplicitCapacity(int minCapacity)函数

该函数就不会去进行刚刚的判断,很明确就是要把列表扩展到参数指定的最小容量。

private void ensureExplicitCapacity(int minCapacity) 
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    

在这里做判断目的是显而易见的,最小容量至少不能少于当前列表的实际的大小。如果最小容量比列表实际的长度大,则进行扩容操作,调用grow(int minCapacity)函数。

2.4 grow(int minCapacity)函数

该函数用于增加列表的容量以使得容器至少能够包含指定参数的容量大小。

private void grow(int minCapacity) 
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    

在这里,首先获取数组的实际的长度,然后将长度扩展未原来的1.5倍,如果扩展后的大小小于指定的大小,则新数据的长度为指定的长度;否则为扩展后的长度。同样,程序会判断指定的数组容量是否溢出,如果溢出,再使用hugeCapacity()函数指定数组大小,最后通过copyOf()函数创建新扩展容量的新数组,并把原数组中的数据转到新的数组中。

2.5 hugeCapacity(minCapacity)函数

该函数相当于临界值判断的函数。

private static int hugeCapacity(int minCapacity) 
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    

很明显,数组的长度有两个临界值,一个是0,一个是数组的最大长度。0很好理解,但是对于数组的最大长度,在java的ArrayList的实现中,定义了一个数组的最大长度的静态值,我们来看一下。

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

该值为int的最大值减去8。我们看一下官方解释,所谓的减去8的原因是,有一些虚拟机会在数组中保留一些头部信息,所以保留8作为虚拟机保留的头部信息。

2.6 size()函数

返回该列表真实的保存的数据的长度。

public int size() 
        return size;
    

该函数体现了java的封装性,在ArrayList中,有一个保存列表长度属性的值size,但是该值并不暴露出去,而是封装一个代表相关意义的函数,来获取该值,这样对该size值也是一种保护,用户不能随便修改该值。

2.7 isEmpty()函数

该函数用于判断列表是否为空,其实现方式很简单,直接判断列表长度是否为0即可。

public boolean isEmpty() 
        return size == 0;
    

2.8 contains(Object o)函数

该函数用于判断该列表中是否包含指定的元素。其实现如下:

public boolean contains(Object o) 
        return indexOf(o) >= 0;
    

该函数调用indexOf()函数直接判断其返回值是否大于等于0。下面,我们看一下indexOf函数的实现。

2.9 indexOf(Object o)函数

该函数返回列表中包含元素o的第一个位置,如果元素不存在,返回-1。

public int indexOf(Object o) 
        if (o == null) 
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
         else 
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        
        return -1;
    

我们很明显的看到,在这里程序将对象o进行了判断,是否为null。因为其判等的方式是不同的,如果为null,直接可以使用等号进行判断;如果不为null,则需要使用equals进行判断。

这里就引出了另外一个问题,equals为什么可以进行是否相等的判断,而不是使用等号。

这个问题的解答可以看我的另一篇文章《java中equals和等号进行对象相等判断的不同》。

2.10 lastIndexOf(Object o)函数

该函数与上述函数正好相反,返回列表中包含对象o的最后一个位置。其实现逻辑与indexOf()函数也类似,不过就是倒序遍历既可以。

 public int lastIndexOf(Object o) 
        if (o == null) 
            for (int i = size-1; i >= 0; i--)
                if (elementData[i]==null)
                    return i;
         else 
            for (int i = size-1; i >= 0; i--)
                if (o.equals(elementData[i]))
                    return i;
        
        return -1;
    

2.11 clone()函数

该函数实现列表的浅拷贝。在这里,我们学习一下java中的浅拷贝和深拷贝的区别。对于java中对象的浅拷贝和深拷贝可以通过这一篇博客进行学习,该博客将的很透彻Java 浅拷贝和深拷贝的理解和实现方式

我们看一下ArrayList的浅拷贝实现方式:

public Object clone() 
        try 
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
         catch (CloneNotSupportedException e) 
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        
    

同样,其调用父类的clone()函数,然后拷贝数组列表,并将modCount改为0,生成的列表便是一个新的列表。

2.12 toArray()函数

在这里,着重说一下ArrayList列表的toArray()函数,其意思就是将列表转换为对象数组。而在ArrayList中,实现了两个toArray()函数,我们看一下他们两个的返回值。

public Object[] toArray() 
        return Arrays.copyOf(elementData, size);
    
    
public <T> T[] toArray(T[] a) 
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    

很明显,我们可以看到,无参数的toArray()函数返回的是对象列表,而有参数的toArray()函数返回的是指定类型的对象列表。所以,如果我们想要通过toArray()函数获取指定类型的数组,则需要使用带有参数的函数。下面是该函数使用的方式的例子:

    ArrayList<String> list = new ArrayList<>();
    list.add("ssss");
    list.add("ddddd");
    
    // 使用方式一
    String strs[] = new String[list.size()];
    System.out.println("第一种方式-----------------");
    list.toArray(strs);
    
    for(int i = 0;i < strs.length;i++)
        System.out.print(strs[i]);
    
    
    System.out.println();
    
    // 使用方式二
    String strs2[] = list.toArray(new String[0]);
    for(int i = 0;i < strs2.length;i++)
        System.out.print(strs2[i]);
    
    
    System.out.println();

2.13 elementData(int index)函数

该函数返回指定位置的元素。

@SuppressWarnings("unchecked")
E elementData(int index) 
        return (E) elementData[index];
    

在该函数中,我们看到有一个unchecked属性,表明该函数并未对index的范围做相关的验证,其是一个内部函数,为其他函数提供基本支持。

2.14 get(int index)函数

该函数是用户可调用的,用于返回指定的位置的元素。

    public E get(int index) 
        rangeCheck(index);

        return elementData(index);
    

函数实现中,首先对传入的index参数做一个范围判断,如果校验成功,则返回指定位置的元素信息。

2.15 rangeCheck(int index)函数

该函数用于对用户传入的指定位置信息进行范围判定,如果超出列表的实际长度或小于0,则抛出IndexOutOfBoundsException。

private void rangeCheckForAdd(int index) 
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    

以上是关于ArrayList函数及相关解释的主要内容,如果未能解决你的问题,请参考以下文章

java基础-ArrayDeque学习

添加新元素时,一个 ArrayList 的所有元素都会改变吗? [复制]

makefile中的多个冒号和等号(需要解释)

Java中Vector与ArrayList的差别具体解释

初学opencv c++学习笔记图像的相关操作及属性

二叉搜索树的建立查找删除等相关操作实现及对于删除操作的详细解释