博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java集合(一) —— ArrayList
阅读量:7128 次
发布时间:2019-06-28

本文共 18631 字,大约阅读时间需要 62 分钟。

ArrayList

ArrayList介绍

我们先看一下ArrayList的继承关系图:

①.从图中我们可以看出来ArrayList实现了Serializable接口表示支持序列化,实现了RandomAccess接口表示支持随机访问,实现了Cloneable接口,表示能被克隆.
②.ArrayList允许所有元素,包括null元素可重复.往ArrayList不断添加元素,其容量也自动增长,可以使用ensureCapacity()方法在添加大量元素前来增加ArrayList实例的容量,这样可以减少增量重新分配的数量.
③.ArrayList线程不安全,可以用 Collections.synchronizedList()使ArrayList线程安全,或者使用结构类似的CopyOnWriteArrayList(若读的场景多用CopyOnWriteArrayList,写场景多用Collections.synchronizedList,vector不建议用).
④.当某一个线程通过iterator遍历ArrayList的过程中,若ArrayList中元素被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件.
⑤.ArrayList删除和插入指定索引方法的最坏估计时间为O(n),更确切地说是O(n-index),即在索引为0操作时所需时间最长,在末尾位置操作所需时间最少.
不介绍函数式编程

源码解析

ArrayList字段

/**     * 序列号     */    private static final long serialVersionUID = 8683452581122892189L;    /**     * 默认容量     */    private static final int DEFAULT_CAPACITY = 10;    /**     * 空数组,定义容量为0的有参构造,返回此空数组public ArrayList(0)     * 或者定义长度为0集合的有参构造,也会返回     */    private static final Object[] EMPTY_ELEMENTDATA = {};    /**     * 默认的空数组,无参构造返回此空数组     */    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};    /**     * 保存添加到ArrayList中的元素,不可序列化,可以看出ArrayList基于数组实现     */    transient Object[] elementData;    /**     * ArrayList元素数量(非容量)     */    private int size;          /**     * 最大容量,若还是不够用可以扩容最大Integer.MAX_VALUE     * 因为索引是int型,所以最大容量为Integer.MAX_VALUE     * 为什么减8,不是减其他数字?     *[有点偏而且我看得也很懵。。](https://stackoverflow.com/questions/35756277/why-the-maximum-array-size-of-arraylist-is-integer-max-value-8),     */    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;复制代码

构造方法

/**     * 若知道数组大小,可以直接用此构造     *  可能抛出java.lang.OutOfMemoryError: Requested array size exceeds VM limit     * @param 初始容量     * @throws IllegalArgumentException 若容量为负则抛异常     */    public ArrayList(int initialCapacity) {        // 容量大于0时         if (initialCapacity > 0) {            // 创建一个此大小的Object数组赋给elementData            this.elementData = new Object[initialCapacity];                // 容量为0时        } else if (initialCapacity == 0) {            // 将空数组赋给elementData            this.elementData = EMPTY_ELEMENTDATA;        // 容量小于0时抛出IllegalArgumentException异常            } else {            throw new IllegalArgumentException("Illegal Capacity: " +                    initialCapacity);        }    }         /**     * 无参构造,在新增操作时当elementData为空数组时会初始化其容量设置成10     */    public ArrayList() {        // 将空数组赋给elementData        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;    }        /**     * 参数Collection的构造方法     *     * @param c 元素被放入此ArrayList中的集合c     * @throws NullPointerException 若集合c为空则抛异常     */    public ArrayList(Collection c) {        //集合转Object[]数组        elementData = c.toArray();        if ((size = elementData.length) != 0) {            // c.toarray可能不返回Object[](见JAVA BUG编号6260652)            if (elementData.getClass() != Object[].class)                elementData = Arrays.copyOf(elementData, size, Object[].class);        } else {            // 空数组赋给elementData            this.elementData = EMPTY_ELEMENTDATA;        }    }复制代码

JAVA BUG编号6260652

/** List的toArray()方法   预期返回Object[],可能向上转型返回子类型,如下面的返回      *  String[]数组,这也就意味着我们只能往此数组里放入String类型元素      */     public static void main(String[] args) {        List l = Arrays.asList(args);        System.out.println(l.toArray().getClass()); //返回String[]    }复制代码

CRUD

Create

ArrayList新增(public)有如下几个方法:

public boolean add(E e) //末尾插入某元素
public void add(int index, E element) //指定位置插入某元素
public boolean addAll(Collection<? extends E> c)//末尾插入集合(顺序不变)
public boolean addAll(int index, Collection<? extends E> c)//指定位置插入集合

ArrayList是一个动态数组,其容量会自动增长,那它如何实现的?我们来看一下add()方法,在其给当前末尾元素后一个位置添加元素时,先执行了ensureCapacityInternal方法:

public boolean add(E e) {        //确定ArrayList容量大小        ensureCapacityInternal(size + 1);        //ArrayList长度加一,在size+1位置上添加元素        elementData[size++] = e;        return true;    } 复制代码

在ensureCapacityInternal方法里会先调calculateCapacity计算所需容量,若elmentData为空数组时容量会设置成10,若不是返回操作所需容量.

private void ensureCapacityInternal(int minCapacity) {        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));     }          private static int calculateCapacity(Object[] elementData, intminCapacity){        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {            return Math.max(DEFAULT_CAPACITY, minCapacity);        }        return minCapacity;    }复制代码

最后调用ensureExplicitCapacity方法确认是否扩容.方法里先是将modCount加1(modCount记录ArrayList修改次数,用来实现fail-fast机制的).再判断所需容量是否超过当前容量,若大于则扩容

private void ensureExplicitCapacity(int minCapacity) {        modCount++;        //         if (minCapacity - elementData.length > 0)            grow(minCapacity);    }复制代码

扩容方法:

private void grow(int minCapacity) {        //获取ArrayList中elementData数组的内存空间长度        int oldCapacity = elementData.length;        //扩容至原容量的1.5倍        int newCapacity = oldCapacity + (oldCapacity >> 1);        //若新容量小于所需容量,就将容量直接设置成所需容量        if (newCapacity - minCapacity < 0)            newCapacity = minCapacity;        //若新容量大于定义的最大值检查是否溢出            if (newCapacity - MAX_ARRAY_SIZE > 0)            newCapacity = hugeCapacity(minCapacity);        //Arrays.copy进行数组拷贝(方法里面会会调用System.arraycopy属于浅拷贝)           elementData = Arrays.copyOf(elementData, newCapacity);    }         /**     * 检查是否溢出,若没有溢出,返回int最大值或定义的最大值     */    private static int hugeCapacity(int minCapacity) {        //内存溢出        if (minCapacity < 0)             throw new OutOfMemoryError();        //大于定义的最大值返回int最大值,小于返回定义的最大值            return (minCapacity > MAX_ARRAY_SIZE) ?            Integer.MAX_VALUE :            MAX_ARRAY_SIZE;    }复制代码

其余新增方法类似,稍微略带下:

/**     * 指定位置添加元素     */    public void add(int index, E element) {        //判断index是否越界        rangeCheckForAdd(index);        //判断是否需要扩容        ensureCapacityInternal(size + 1);        //public static native void arraycopy(Object src,  int  srcPos,        // Object dest, int destPos, int length); 本地方法        // src:源数组,srcPos:源数组要复制的起始位置,dest:目标数组        // destPos:目标数组放置的起始位置,length:复制长度        // index位置及其后面的元素往后移一位        System.arraycopy(elementData, index, elementData, index + 1,                         size - index);        //指定位置赋值        elementData[index] = element;        //ArrayList长度加一        size++;    }         /**     * 末尾依次添加集合所有元素,add(Collection)与addAll(Collection)区别在于add * 是添加集合,addAll是添加集合所有元素     */    public boolean addAll(Collection c) {        //参数集合转为数组        Object[] a = c.toArray();        //获取数组长度        int numNew = a.length;        //判断是否扩容        ensureCapacityInternal(size + numNew);  // Increments modCount        //将a的第0位开始拷贝至elementData的size位开始,拷贝长度为numNew        System.arraycopy(a, 0, elementData, size, numNew);        // size增加numNew        size += numNew;        //空集合返回false,不为空返回true        return numNew != 0;    }        /**     * 指定位置插入集合所有元素     */    public boolean addAll(int index, Collection c) {        //判断index是否越界        rangeCheckForAdd(index);        //参数集合转为数组        Object[] a = c.toArray();        //获取数组长度        int numNew = a.length;        //判断是否扩容        ensureCapacityInternal(size + numNew);          int numMoved = size - index;        //若是末尾插入直接插入集合,若不是先移动原来集合中元素再插入以免被覆盖        if (numMoved > 0)            System.arraycopy(elementData, index, elementData, index + numNew,                             numMoved);        System.arraycopy(a, 0, elementData, index, numNew);        // size增加numNew        size += numNew;        //空集合返回false,不为空返回true        return numNew != 0;    }复制代码

Retrieve

ArrayList查询(public)有如下几个方法:

public boolean contains(Object o)//查询是否包含某元素
public int indexOf(Object o)//查询某元素首次出现在ArrayList的索引

/**     * 内部调用了indexOf,若-1则表示没有,不小于0表示有     */    public boolean contains(Object o) {        return indexOf(o) >= 0;    }         /**     * for循环从索引为0往后开始遍历ArrayList,返回元素首次出现位置,若不存在返回 -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;    } 复制代码

public E get(int index)//查询此索引位置的元素

public E get(int index) {        // 检查是否越界        rangeCheck(index);        // 返回ArrayList的elementData数组index位置的元素        return elementData(index);    }        返回指定位置的值    E elementData(int index) {        return (E) elementData[index];    } 复制代码

public boolean isEmpty() //查询ArrayList大小是否为0

/**     * ArrayList大小为0返回true,否则返回false     */    public boolean isEmpty() {        return size == 0;    } 复制代码

public int lastIndexOf(Object o) //查询某元素最后一次出现在ArrayList的索引

/**     * for循环从索引为size-1往前开始遍历ArrayList,返回元素首次出现位置,若不存在返回 -1     */    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;    } 复制代码

public int size() //返回ArrayList的长度

/**     * 直接返回size     */    public int size() {        return size;    } 复制代码

public List subList(int fromIndex, int toIndex)

/**     * 返回指定[fromIndex, toIndex)区间的集合视图,fromIndex == toIndex时返回空集合     * 此子集合具有集合所有操作     */    public List subList(int fromIndex, int toIndex) {        //越界检查,非法参数检查        subListRangeCheck(fromIndex, toIndex, size);        //返回subList有参构造        return new SubList(this, 0, fromIndex, toIndex);    }         /**     * 内部类,实现了RandomAccess提供快速访问     */    private class SubList extends AbstractList implements RandomAccess {        private final AbstractList parent;        private final int parentOffset;        private final int offset;             int size;        /**           * 从这里可以看出,subList用的是父集合引用,即对subList操作的同时会影父集合         */        SubList(AbstractList parent,                int offset, int fromIndex, int toIndex) {            this.parent = parent;            this.parentOffset = fromIndex;            this.offset = offset + fromIndex;            this.size = toIndex - fromIndex;            this.modCount = ArrayList.this.modCount;        }    }复制代码

Update

ArrayList更新(public)有如下几个方法:

public void ensureCapacity(int minCapacity)

/**     * 此方法用于若添加大量元素,可以先调用此方法分配多点内存,从而避免多次重新分配内存     */    public void ensureCapacity(int minCapacity) {        // 最小扩充容量,默认是 10          int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)            ? 0            : DEFAULT_CAPACITY;        if (minCapacity > minExpand) {            ensureExplicitCapacity(minCapacity);        }    } 复制代码

public E set(int index, E element)

/**     * 设置index位置元素的值,返回被替换元素     */    public E set(int index, E element) {        //校验是否越界        rangeCheck(index);        //取出旧值        E oldValue = elementData(index);        //替换元素        elementData[index] = element;        //返回被替换元素        return oldValue;    }复制代码

public void trimToSize()

/**     * 将容量修改成ArrayList实际大小,用来减少内存空间资源浪费     */    public void trimToSize() {        modCount++;        if (size < elementData.length) {            elementData = (size == 0)              ? EMPTY_ELEMENTDATA              : Arrays.copyOf(elementData, size);        }    } 复制代码

Delete

ArrayList删除(public)有如下几个方法:

public E remove(int index)

/**     * 删除指定索引位置元素     */    public E remove(int index) {        //校验是否越界        rangeCheck(index);        modCount++;        //获取当前指定索引位置元素        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;    } 复制代码

public boolean remove(Object o) //删除某元素,只会删除第一次出现的

/**     * for循环遍历ArrayList,若集合存在此元素调用fastRemove快速移除     */    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;    }         /**     * 与remove(int index)区别,其没有校验是否越界,也不用返回被替换元素     */    private void fastRemove(int index) {        modCount++;        int numMoved = size - index - 1;        if (numMoved > 0)            System.arraycopy(elementData, index+1, elementData, index,                             numMoved);        elementData[--size] = null;     }复制代码

public boolean removeAll(Collection<?> c) //删除ArrayList与c中的相同元素

public boolean removeAll(Collection c) {        //当c为null抛空指针异常        Objects.requireNonNull(c);        return batchRemove(c, false);    }         /**     * 批量移除     * complement为true时保留ArrayList与集合c的相同元素     * false时移除相同元素       */    private boolean batchRemove(Collection c, boolean complement) {        final Object[] elementData = this.elementData;        int r = 0, w = 0;        boolean modified = false;        try {            for (; r < size; r++)                //false记录不同值,true记录相同值                if (c.contains(elementData[r]) == complement)                    elementData[w++] = elementData[r];        } finally {            // 防止抛出异常没有遍历完,保留未遍历的元素            if (r != size) {                System.arraycopy(elementData, r,                                 elementData, w,                                 size - r);                w += size - r;            }            // 若不是全被替换,将w及其位置后面的元素置为null            if (w != size) {                for (int i = w; i < size; i++)                    elementData[i] = null;                //记录改变次数                modCount += size - w;                //设置大小                size = w;                //修改成功                modified = true;            }        }        return modified;    }复制代码

public boolean retainAll(Collection<?> c) //保留ArrayList和集合c中的相同元素`

public boolean retainAll(Collection c) {        Objects.requireNonNull(c);        return batchRemove(c, true);    } 复制代码

public void clear()

/**     * 清空所有元素     */    public void clear() {          modCount++;          for (int i = 0; i < size; i++)              elementData[i] = null;          size = 0;      }   复制代码

转换数组

/**     * 用Arrays.copyOf()转为数组     */    public Object[] toArray() {        return Arrays.copyOf(elementData, size);    }        /**     * @param 数组a     * @return 将ArrayList里元素赋值到数组中     * @throws NullPointerException a为null     * @throws ArrayStoreException a的运行时类型不是ArrayList中每个元素的运行时类型的超类型     */    public  T[] toArray(T[] a) {        if (a.length < size)            // 创建一个新的a的运行时类型数组,内容不变            return (T[]) Arrays.copyOf(elementData, size, a.getClass());        System.arraycopy(elementData, 0, a, 0, size);        if (a.length > size)            a[size] = null;        return a;    }复制代码

序列化

我们知道ArrayList实现了Serializable,其elementData是一个缓存数组,其容量少数情况会满,只需要序列化实际存储的元素即可,所以用transient修饰标记不可序列化.ArrayList通过独特方式完成序列化,在序列化时会调用定义的writeObject,将size和element写入对象输出流中.

/**     * 序列化     */    private void writeObject(java.io.ObjectOutputStream s)        throws java.io.IOException{        int expectedModCount = modCount;        //默认序列化方法,将当前类的非静态和非transient字段写入流中        s.defaultWriteObject();        // 写入大小        s.writeInt(size);        // for循环写入ArrayList中所有元素        for (int i=0; i < size; i++) {            s.writeObject(elementData[i]);        }        if (modCount != expectedModCount) {            throw new ConcurrentModificationException();        }    }    /**     * 反序列化     */    private void readObject(java.io.ObjectInputStream s)        throws java.io.IOException, ClassNotFoundException {        elementData = EMPTY_ELEMENTDATA;        // 默认反序列化方法        s.defaultReadObject();        // 读取长度        s.readInt(); // ignored        if (size > 0) {            // 像clone()方法 ,根据大小而不是容量分配数组            int capacity = calculateCapacity(elementData, size);            SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);            ensureCapacityInternal(size);            Object[] a = elementData;            //读取所有元素            for (int i=0; i < size; i++) {                a[i] = s.readObject();            }        }    } 复制代码

遍历

在简述中提到了ArrayList实现了RandomAccess接口,其有一段英文注释

As a rule of thumb, a List implementation should      implement this interface if, for typical instances of the class, this loop:     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();复制代码

for循环遍历效率高过foreach,在Collections工具类中个方法:

private static final int FILL_THRESHOLD           =   25;    public static  void fill(List list, T obj) {        int size = list.size();        if (size < FILL_THRESHOLD || list instanceof RandomAccess) {            for (int i=0; i itr = list.listIterator();            for (int i=0; i < size; i++) {                itr.next();                itr.set(obj);            }        }    }复制代码

我们可以从这个方法可以看出若是实现了RandomAccess用了for循环遍历.

ArrayList的iterator与listIterator区别

iterator与listIterator都是迭代器,不同的是listIterator可以指定位置开始,拥有hasPrevious()和previous()方法能逆向遍历,可以对集合本身进行新增,修改不过必须要next()过且不能连续操作,还可以返回当前遍历位置nextIndex(),previousIndex()当前遍历位置前一个.

手撕简单实现

public class ArrayList {    private Object[] elementData;    private int size;    public ArrayList(int capacity){        this.elementData = new Object[capacity];    }    public ArrayList(){        this.elementData = new Object[10];    }    public void add(Object obj){        isExpanse(size + 1);        elementData[size++] = obj;    }    private void isExpanse(int minCapacity){        int oldCapacity = elementData.length;        if(minCapacity > oldCapacity){            int newCapacity = oldCapacity + oldCapacity >> 1;            elementData = Arrays.copyOf(elementData, newCapacity);        }    }    public Object get(int index){        checkLength(index);        return elementData[index];    }    public void set(int index, Object obj){        checkLength(index);        elementData[index] = obj;    }    private void checkLength(int index){        if(index < 0 || index >= size) {            throw new IndexOutOfBoundsException("越界");        }    }    public void remove(int index){        checkLength(index);        int moveNum = size - index - 1;        if(moveNum > 0 ){            System.arraycopy(elementData, index + 1, elementData, index, moveNum);        }        elementData[size--] = null;    }}复制代码

转载地址:http://rxael.baihongyu.com/

你可能感兴趣的文章
C语言 时间函数的学习
查看>>
你真的懂Redis事务吗?
查看>>
收藏 | 12个ggplot2拓展程序助你强化R可视化
查看>>
1-Linux C语言编程基本原理与实践-学习笔记
查看>>
WRF-DA代码编译与安装(二)——WRF-DA模块的编译与安装
查看>>
2018年美团Android校招
查看>>
Spring消息之WebSocket
查看>>
Java 文件流操作.
查看>>
《11招玩转网络安全》之第三招:Web暴力破解-Low级别
查看>>
Eclipse快捷键大全
查看>>
Android实现TextView字符串波浪式跳动
查看>>
NumPy—random随机数生成函数总结
查看>>
第10章节-Python3.5-Django路由分发
查看>>
排序三 直接插入排序
查看>>
输入输出流体系图
查看>>
玩转报表排名
查看>>
《函数响应式领域建模》读后感
查看>>
一入前端深似海,从此红尘是路人系列第四弹之未来前端路该何去何从
查看>>
java笔记--笔试中极容易出错的表达式的陷阱
查看>>
第140天:前端开发中浏览器兼容性问题总结(一)
查看>>