《java集合》--ArrayList

ArrayList

参考:第二章 ArrayList源码解析
Collections Framework - ArrayList

掌握知识

  • 数据结构
  • ArrayList的基本属性
  • ArrayList的构造器
  • 添加元素 add、addAll、set
  • 删除元素 remove、clear
  • ArrayList的数组扩容,resize
  • 获取元素 get
  • 遍历ArrayList
  • 判断元素是否存在 contains
  • ArrayList的元素排序

数据结构

image

1、ArrayList的底层是一个固定长度的(默认10)数组,当向容器中添加元素时,如果容量不足,容器会自动增大底层数组的大小

2、实现了List接口,是顺序容器,继承AbstractList的迭代器

3、允许NULL值

4、非线程安全

5、重写了克隆、系列化、反系列化

ArrayList的基本属性

  • elementData:底层Object[]
  • size:ArrayList中元素的实际个数
  • modCount:记录ArrayList的修改次数
private transient Object[] elementData;

ArrayList底层数组为Object[],用于存放元素,==修饰为 transient 表示系列化时不系列化该属性==,ArrayList重写了系列化和反系列化方法


//系列化
private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{

    int expectedModCount = modCount;
    系列化ArrayList中的其他属性
    s.defaultWriteObject();
    //系列化数组中的元素
    for (int i=0; i<size; i++)
            s.writeObject(elementData[i]);
    //系列化过程中不允许对ArrayList进行修改操作
    if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }

//反系列化
private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    //反系列化ArrayList中的其他属性
    s.defaultReadObject();

    // 根据系列化中的长度创建一个Object数组
    int arrayLength = s.readInt();
    Object[] a = elementData = new Object[arrayLength];

    //读取数组中的元素
    for (int i=0; i<size; i++)
            a[i] = s.readObject();
    }
private int size;

ArrayList中元素的实际个数

protected transient int modCount = 0;

modCount变量用于在遍历集合(iterator())时,检测是否发生了add、remove操作,在系列化过程中判断ArrayList是否有变动,如果变动,抛出ConcurrentModificationException

final void checkForComodification() {
        if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}
}

ArrayList的构造器

在使用ArrayList时如果知道长度,最好指定长度创建ArrayList,这样既可以避免空间的浪费也可以避免数组扩容时元素拷贝到新的数组而引发的性能下降。

public ArrayList() {
//默认长度为10
    this(10);
}
public ArrayList(int initialCapacity) {
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
    //创建一个指定长度的 Object[] 数组
    this.elementData = new Object[initialCapacity];
}
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    size = elementData.length;
    //如果转换后的数组不是Object[],创建一个新的Object[]并拷贝元素
    if (elementData.getClass() != Object[].class)
        elementData = Arrays.copyOf(elementData, size, Object[].class);
}
//根据长度创建一个新的数组并拷贝元素
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
    T[] copy = ((Object)newType == (Object)Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}

添加元素 add、addAll、set

在向ArrayList中添加元素时,每次都需要验证数组的长度是否满足,如果长度不够,创建一个新的数组(长度为原长度*1.5+1),然后将原数组中的元素拷贝到新的数组中。

向指定位置插入元素的时候,涉及到数组中的元素的移动,


//添加一个元素
public boolean add(E e) {
    //添加一个元素,保证数组的长度满足 size+1,否则新建数组拷贝元素
    ensureCapacity(size + 1);  // Increments modCount!!
    //数组指针执行添加的元素
    elementData[size++] = e;
    return true;
}
public boolean addAll(Collection<? extends E> c) {
    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacity(size + numNew);  // Increments modCount
    System.arraycopy(a, 0, elementData, size, numNew);
    size += numNew;
    return numNew != 0;
}
//指定位置插入元素,涉及元素的移动
public void add(int index, E element) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(
        "Index: "+index+", Size: "+size);

    ensureCapacity(size+1);  // Increments modCount!!
    //不创建新的数组,移动index位置及之后的元素
    System.arraycopy(elementData, index, elementData, index + 1,
             size - index);
    elementData[index] = element;
    size++;
}
public boolean addAll(int index, Collection<? extends E> c) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(
        "Index: " + index + ", Size: " + size);
    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacity(size + numNew);  // Increments modCount
    //判断是否需要移动
    int numMoved = size - index;
    if (numMoved > 0)
        System.arraycopy(elementData, index, elementData, index + numNew,
                 numMoved);
    //如果不需要移动直接插到最后
        System.arraycopy(a, 0, elementData, index, numNew);
    size += numNew;
    return numNew != 0;
}
//set方法不会引起数组的结构变化,不需要更新 modCount
public E set(int index, E element) {
    //检查下标是否越界
    RangeCheck(index);
    E oldValue = (E) elementData[index];
    elementData[index] = element;
    return oldValue;
}
//确保数组长度满足,如果不满足新建一个新的满足长度(1.5倍+1)的数组拷贝元素
public void ensureCapacity(int minCapacity) {
    modCount++;
    int oldCapacity = elementData.length;
    if (minCapacity > oldCapacity) {
        Object oldData[] = elementData;
        //1.5倍+1
        int newCapacity = (oldCapacity * 3)/2 + 1;
            if (newCapacity < minCapacity)
        newCapacity = minCapacity;
            // minCapacity is usually close to size, so this is a win:
            elementData = Arrays.copyOf(elementData, newCapacity);
    }
}
//检查数组下标是否越界
private void RangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(
        "Index: "+index+", Size: "+size);
}

删除元素 remove、clear

删除的时候需要遍历数组查找待删除的对象,然后移动数组中的元素,重新指向

将数组移动后最后空出的位置的指针指向NULL,让GC回收

//移除一个元素
public boolean remove(Object o) {
    if (o == null) {
        //遍历查找,然后移除
        for (int index = 0; index < size; index++)
        if (elementData[index] == null) {
            fastRemove(index);
            return true;
        }
    } else {
        for (int index = 0; index < size; index++)
        if (o.equals(elementData[index])) {
            fastRemove(index);
            return true;
        }
        }
    return false;
}
//不进行边界验证直接移除,所以叫fastRemove
private void fastRemove(int index) {
    //modCount+1
    modCount++;
    int numMoved = size - index - 1;
    //判断是否是从末尾删除,如果是就不要进行元素移动,直接将最后的指定指向NULL
    if (numMoved > 0)
    System.arraycopy(elementData, index+1, elementData, index,
                     numMoved);
    //将数组位置的指针指向NULL,让GC回收
    elementData[--size] = null; // Let gc do its work
}
//清空
public void clear() {
    modCount++;
    // Let gc do its work
    for (int i = 0; i < size; i++)
        elementData[i] = null;
    //长度重置为0
    size = 0;
}

ArrayList的数组扩容,resize

在向ArrayList中添加元素时,每次都需要验证数组的长度是否满足,如果长度不够,创建一个新的数组(长度为原长度*1.5+1),然后将原数组中的元素拷贝到新的数组中。

获取元素 get

public E get(int index) {
    RangeCheck(index);
    return (E) elementData[index];
}

遍历ArrayList

继承自AbstractList中的iterator,遍历删除集合中元素时使用iterator,避免出现问题

public Iterator<E> iterator() {
    return new Itr();//返回一个内部类对象
}
private class Itr implements Iterator<E> {
        //记录下一个,用来next
        int cursor = 0;
        //记录上一个,删除时使用
        int lastRet = -1;
        int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor != size();
        }

        public E next() {
            //检查保证遍历的时候,不允许修改
            checkForComodification();
            try {
                //调用ArrayList(外部对象)获取数组指定位置元素
                E next = get(cursor);
                //指针向后+1
                lastRet = cursor++;
                return next;
            } catch (IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }
        public void remove() {
            if (lastRet == -1)
                throw new IllegalStateException();
            //检查保证遍历的时候,不允许修改
            checkForComodification();
            try {
                //直接操作数组,不会发生指针溢出
                AbstractList.this.remove(lastRet);
                //指针cursor向前移动一位,保证执行next的时候是下一个
                if (lastRet < cursor)
                    cursor--;
                //重置lastRet
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException e) {
                throw new ConcurrentModificationException();
            }
        }
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
}

判断元素是否存在 contains

判断是否包含某元素,调用indexof,遍历数组查找元素

public boolean contains(Object o) {
    return indexOf(o) >= 0;
}
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

ArrayList的元素排序

RandomAccess

实现RandomAccess接口的集合有:ArrayList, AttributeList, CopyOnWriteArrayList, RoleList, RoleUnresolvedList, Stack, Vector等。
在RandomAccess接口的注释中有这么一段话:

for (int i=0, n=list.size(); i < n; i++) {     
    list.get(i);
}
runs faster than this loop:
for (Iterator i=list.iterator(); i.hasNext(); ) { 
   i.next();
}

说明实现了RandomAccess接口的集合,在数据量很大的情况下,采用迭代器遍历比较慢

总结

  • ArrayList基于数组方式实现,无容量的限制(会扩容)
  • 添加元素时可能要扩容(所以最好预判一下),删除元素时不会减少容量(若希望减少容量,trimToSize()),删除元素时,将删除掉的位置元素置为null,下次gc就会回收这些元素所占的内存空间。
  • add(int index, E element):添加元素到数组中指定位置的时候,需要将该位置及其后边所有的元素都整块向后复制一位
  • get(int index):获取指定位置上的元素时,可以通过索引直接获取(O(1))
  • remove(Object o)需要遍历数组
  • remove(int index)不需要遍历数组,只需判断index是否符合条件即可,效率比remove(Object o)高
  • contains(E)需要遍历数组
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值