Java 7之集合类型 - ArrayList

本文详细解析了Java ArrayList的实现原理,包括如何创建、添加元素、迭代元素、克隆对象以及如何避免数组扩容带来的性能损耗。同时,文章还介绍了ArrayList的迭代器使用方法,以及如何通过trimToSize方法优化内存使用。

转载请注明出处:http://blog.youkuaiyun.com/mazhimazh/article/details/19543911

首先来看一道面试题目:
ArrayList list = new ArrayList(20);中的list扩充几次(A)
A 0 B 1 C 2 D 3
解释:默认ArrayList的长度是10个,所以如果你要往list里添加20个元素肯定要扩充一次(扩充为原来的1.5倍),但是这里显示指明了需要多少空间,所以就一次性为你分配这么多空间,也就是不需要扩充了。

看一下ArrayList类的定义如下:

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable  

1、ArrayList 继承了AbstractList抽象类并且实现了List接口,所以提供了数组相关的添加、删除、修改、遍历等功能
2、ArrayList 实现了RandmoAccess接口,即提供了随机访问功能。也就是说,可以通过索引来快速获取元素对象
3、ArrayList 实现了Cloneable接口,即覆盖了函数clone(),能被克隆
在ArrayList中,主要是通过Object[]数组来完成实现的,与数组相比,它的容量能动态增长,这个由它自己进行管理。

1、创建ArrayList对象

来看一下构造函数:

// 数组缓冲区,用来存储元素  
private transient Object[] elementData;// 当进行串行化时,这个属性并不会被写入  
private int size;                      // 数组中存储的元素个数                     

public ArrayList(int initialCapacity) {  
    super();       // 显式调用父类的无参数构造函数   
    if (initialCapacity < 0)  
        throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);  
    this.elementData = new Object[initialCapacity];  
}  
public ArrayList() {// 默认分配的大小为10  
    this(10);  
}  
// in the order they are returned by the collection's iterator.  
public ArrayList(Collection<? extends E> c) {  
    elementData = c.toArray();//   
    size = elementData.length;  
    if (elementData.getClass() != Object[].class)  
        elementData = Arrays.copyOf(elementData, size, Object[].class);  
}  

初始时可以指定大小,如果不指定则默认分配的大小为10。还提供了一个有Collection参数的接口,这就为List和其他的集合之间相互转换提供了便利。

2、添加元素

ArrayList中添加元素方法的实现源代码如下:

public boolean add(E e) {  
     ensureCapacityInternal(size + 1);   // 保证elementData数组有足够的大小  
     elementData[size++] = e;            // 向数组中添加新元素  
     return true;  
 }  
 public void add(int index, E element) {  
     rangeCheckForAdd(index);  
     ensureCapacityInternal(size + 1);   // 保证数组的大小  
     // 将index索引处后的所有元素向后移动一个位置  
     System.arraycopy(elementData, index, elementData, index + 1,size - index);  
     elementData[index] = element;       // 将新的元素添加到指定的索引处  
     size++;                             // 元素个数加1  
 }  

在添加新元素时,会改变数组的内容。所以在每次调用ensureCapacityInternal()方法进行扩容时,还会对modCount加1,这个方法及相关方法的源代码如下:

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

private void ensureCapacityInternal(int minCapacity) {  
    modCount++;                                         // 对modCount加1,表示数组内容发生了变化  
    if (minCapacity - elementData.length > 0)           // 当(数组中实际元素个数+1)>数组的长度时,需要进行扩容操作  
        grow(minCapacity);  
}  

private void grow(int minCapacity) {  
    int oldCapacity = elementData.length;  
    int newCapacity = oldCapacity + (oldCapacity >> 1);  // 计算新的数组容量大小  
    /* 
     *  扩容后仍然小于要求的最小容量时,新数组容量大于就为最小容量大小 
     *  扩容后新容量大于MAX_ARRAY_SIZE值时,调用hugeCapacity()进行处理 
     */  
    if (newCapacity - minCapacity < 0)                     
        newCapacity = minCapacity;  
    if (newCapacity - MAX_ARRAY_SIZE > 0)  
        newCapacity = hugeCapacity(minCapacity);  
    elementData = Arrays.copyOf(elementData, newCapacity); // 将元素复制到新的数组中  
}  
private static int hugeCapacity(int minCapacity) {  
    if (minCapacity < 0) // overflow  
        throw new OutOfMemoryError();  
    return (minCapacity > MAX_ARRAY_SIZE) ?Integer.MAX_VALUE : MAX_ARRAY_SIZE;  
} 

如上就是具体的扩容方法。 ArrayList还给我们提供了将底层数组的容量调整为当前列表保存的实际元素的大小的功能。它可以通过trimToSize方法来实现。代码如下:

public void trimToSize() {    
    modCount++;    
    int oldCapacity = elementData.length;    
    if (size < oldCapacity) {    
        elementData = Arrays.copyOf(elementData, size);    
    }    
}  

但是应该尽量避免扩容和缩小等操作,因为这都会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量的增长大约是其原容量的1.5倍。这种操作的代价是很 高的,因此在实际使用时,我们应该尽量避免数组容量的扩张。当我们可预知要保存的元素的多少时,要在构造ArrayList实例时,就指定其容量,以避免 数组扩容的发生。或者根据实际需求,通过调用ensureCapacity方法来手动增加ArrayList实例的容量。

3、迭代元素

关于集合元素的迭代,在前面也说过不少了。如果不清楚,可以进行查看:
传送门: http://blog.youkuaiyun.com/mazhimazh/article/details/17759579
下面来看一个具体的例子:

ArrayList<String>  aList=new ArrayList<String>();  
        aList.add("a");  
        aList.add("b");  
        aList.add("d");  
        ListIterator<String> it=aList.iterator();// 下面要获取到Iterator对象后添加元素,所以生成的必须是ListIterator对象  
        // aList.add("x");      // 抛出异常ConcurrentModificationException  
        // aList.remove(2);       // 抛出异常ConcurrentModificationException  
        it.add("x");           // 调用自身的方法去修改正常  
        while(it.hasNext()){  
            System.out.print(it.next()+" ");  
        }

程序输出的结果如下:
a b c // 注意输出结果中不含有x
可以看到,在获取了iterator实例后,aList就不可改变。当ArrayList使用了iterator()方法产生自身对应的Iterator后,只能使用Iterator自身的remove()和add()方法来修改ArrayList的结构,因为这些方法会对expectedModCount和modCount变量会自动同步,如ListItr中的add(E e)方法,如下:

public void add(E e) {  
      checkForComodification();  
      try {  
          int i = cursor;  
          ArrayList.this.add(i, e);      // 调用ArrayList对象的add()方法进行元素的添加  
          cursor = i + 1;  
          lastRet = -1;  
          expectedModCount = modCount;   // 重新设置expectedModCount的值为修改后的modCount值  
      } catch (IndexOutOfBoundsException ex) {  
          throw new ConcurrentModificationException();  
      }  
  }  

还有Itr中的remove()方法,如下:

public void remove() {  
      if (lastRet < 0)  
          throw new IllegalStateException();  
      checkForComodification();  
      try {  
          ArrayList.this.remove(lastRet);  
          cursor = lastRet;  
          lastRet = -1;  
          expectedModCount = modCount;   // 重新设置expectedModCount值,所以在获取Itr实例时,只能通过调用这个实例中的方法进行数组内容的修改  
      } catch (IndexOutOfBoundsException ex) {  
          throw new ConcurrentModificationException();  
      }  
  }

调用ArrayList中本身定义的添加和删除方法都会引起ConcurrentModificationException异常,因为他们并不会重新设置expectedModCount值,如下:

public boolean add(E e) {  
     ensureCapacityInternal(size + 1);  // 会对modCount的值做加1操作  
     elementData[size++] = e;  
     return true;  
 }  
 public void add(int index, E element) {  
     rangeCheckForAdd(index);  
     ensureCapacityInternal(size + 1);  // 会对modCount值做加1操作  
     System.arraycopy(elementData, index, elementData, index + 1,size - index);  
     elementData[index] = element;  
     size++;  
 }  

 public E remove(int index) {  
     rangeCheck(index);  
     modCount++;                        // 对modCount值做加1操作  
     E oldValue = elementData(index);  
     int numMoved = size - index - 1;  
     if (numMoved > 0)  
         System.arraycopy(elementData, index+1, elementData, index,numMoved);  
     elementData[--size] = null;       // 有利于垃圾回收器进行回收  
     return oldValue;  
 }  

ArrayList使用AbstractList.modCount(初始的默认值为0)作为数组内容改变的标识。在ArrayList中,凡是会引起ArrayList结构变化的方法,都会修改modCount(modCount++),以区别是否修改了ArrayList中存储的内容。

如果是对ArrayList的Iterator做修改,在Iterator中会重置expectedModCount=modCount,如上面ListIterator类中的remove()方法。这样就可以保证在生成Iterator后,只能由Iterator来修改对应的ArrayList的内容。

4、克隆ArrayList对象

//  克隆出ArrayList的复本,是一个深克隆(继承了Cloneable接口)  
public Object clone() {   
    try {  
        ArrayList<E> v = (ArrayList<E>) super.clone();      
        v.elementData = Arrays.copyOf(elementData, size); // 拷贝数组中的内容到新数组中  
        v.modCount = 0;  
        return v;                                         // 返回新的数组的引用  
    } catch (CloneNotSupportedException e) {  
        throw new InternalError();  
    }  
}  

同样如果是引用类型,不能进行深度克隆,如下:

public class test01 {  

    public static void main(String args[]) {  
        ArrayList<TestBean> bb = new ArrayList<TestBean>(5);  
        bb.add(new TestBean("mazhi"));  
        bb.add(new TestBean("xxx"));  
        ArrayList<TestBean> test = (ArrayList<TestBean>) bb.clone(); // 浅克隆  
        test.get(0).setName("test");  
        System.out.println(bb.get(0).getName()); // test  
    }  

}  

class TestBean implements Cloneable {  

    private String name;  

    public TestBean(String name) {  
        this.name = name;  
    }  

    public String getName() {  
        return name;  
    }  

    public void setName(String name) {  
        this.name = name;  
    }  

    protected Object clone() throws CloneNotSupportedException {  
        TestBean newBtl = (TestBean) super.clone();  
        return newBtl;  
    }  

}  

修改副本会影响到原来List中的值

### Java ArrayList 入门与基本语法 #### 什么是ArrayList? `ArrayList` 是一种动态数组结构,属于 `java.util` 包的一部分。它允许存储可变数量的对象集合,并提供了灵活的方法来操作这些对象[^1]。 #### 如何导入和声明 ArrayList? 要使用 `ArrayList` 类型的数据结构,首先需要通过以下语句将其引入到程序中: ```java import java.util.ArrayList; ``` 接着可以按照如下方式创建并初始化一个 `ArrayList` 对象: ```java ArrayList<E> objectName = new ArrayList<>(); ``` 其中 `<E>` 表示泛型参数,用于指定该列表所存储的具体数据类型。例如,如果希望保存字符串类型的元素,则定义形式应为: ```java ArrayList<String> strList = new ArrayList<>(); ``` #### 添加元素至 ArrayList 中 向已有的 `ArrayList` 实例增加新成员可通过调用其内置方法 `.add()` 来实现。下面展示如何往上述名为 `strList` 的列表里追加若干项: ```java strList.add("First Item"); strList.add("Second Item"); // 继续添加更多项目... ``` #### 获取特定位置上的值 访问某个索引处的内容需要用到 `.get(index)` 方法。需要注意的是,在计算机科学领域内通常采用零基计数法表示序列的位置关系;也就是说第一个项目的下标编号为0而非1。 ```java String firstItem = strList.get(0); System.out.println(firstItem); // 输出:"First Item" ``` #### 删除指定位置的元素 当不再需要某些条目时,可以通过 `.remove(int index)` 函数移除它们。此动作会自动调整剩余部分之间的相对顺序以填补空白区域。 ```java strList.remove(0); // 移除首个元素 ("First Item") ``` #### 遍历整个 ArrayList 并打印所有成分 最后介绍怎样完整列举出容器内的全部物件。这里给出两种常见的做法——利用增强版for循环或者直接借助toString()转换成字符串表达式输出。 ```java // 方式一:增强型 For 循环 for (String item : strList) { System.out.println(item); } // 或者更简洁的方式二: System.out.println(strList.toString()); ``` 以上就是关于 Java 编程语言当中 ArrayList 数据结构的一些基础知识概览及其典型应用场景举例说明][^[^23]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值