java集合系列01--ArrayList

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_36952611/article/details/74130124

以下几篇文章都是对java集合的一个介绍,这些文章并非都是我的原创,主要是集合了各种途径获取的一个总结。对于集合,我们主要从以下四点关注:
1.是否允许为空;
2.是否允许重复数据;
3.是否有序,有序是指读取顺序与存放顺序是否一致;
4.是否线程安全。

ArrayList

(1)ArrayList 是一个数组队列,相当于动态数组。与Java中的数组相比,它的容量能动态增长。它继承于AbstractList,实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口。
(2)ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
(3)ArrayList 实现了RandmoAccess接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在ArrayList中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。稍后,我们会比较List的“快速随机访问”和“通过Iterator迭代器访问”的效率。
(4)ArrayList 实现了Cloneable接口,即覆盖了函数clone(),能被克隆。
(5)ArrayList 实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。
(6)和Vector不同,ArrayList中的操作不是线程安全的!所以,建议在单线程中才使用ArrayList,而在多线程中可以选择Vector或者CopyOnWriteArrayList。
ArrayList是一个以数组形式实现的集合,首先我们来看一下ArrayList的基本元素。

ArrayList与Collection的关系如图:
这里写图片描述
ArrayList包含了两个重要的对象:elementData和Size。
(1)elementData是“Object[]类型的数组”,它保存了添加到ArrayList中的的元素。实际上,elementData是一个动态数组,我们可以通过构造函数ArrayList(int initialCapacity)来执行它的初始化容量;如果通过不含参数的构造函数ArrayList()来创建,则它的默认容量是10。
(2)size是动态数组的实际大小。

添加元素

public class Test {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("00");
        list.add("11");
    }
}

我们来看一下添加操作的底层源码实现:

    public boolean add(E e) {
    //这个方法是为了实现扩容的作用
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

底层在调用add方法的时候只是给elementData的某个位置添加了一个数据而已,下图为例这里写图片描述
为了方便理解才这样画的,elementData中存储的应该是堆内存中元素的引用,而不是实际的元素,这样画会让人误以为elementData中存放了实际元素,在这里提醒一下。
*扩容*
数组扩容的时候我们会先把数组乘以3,再除2后加1.为什么这样呢?
1、如果一次性扩容太大,必然会造成空间的浪费。
2、如果一次扩容不够,那么下一次扩容操作会很快发生,这回降低程序的运行效率,要知道扩容还是比较耗费性能的;
所以扩容多少,是jdk开发人员在时间和空间上的一个权衡。最后调用的是Arrays的copyOf方法,将原数组里的内容全部复制到新的数组里面。

删除元素

ArrayList支持两种删除方式:1.按照下标删除;2.按照元素删除,这会删除与指定元素相同的第一个元素。

//jdk源码
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; // Let gc do its work

        return oldValue;
    }

两种删除方式都是调用这段代码。都是做了两件事:
1、将指定元素后面位置的所有元素,利用System.arraycopy方法整体向前移动一位。
2、最后一个位置的元素指定外围null,这样gc就会去回收它。

插入元素

插入元素用的也是add方法,其方法与删除操作类似。

ArrayList的遍历方式

(1)通过迭代器遍历

//迭代器遍历
        String value = null;
        Iterator it = list.iterator();
        while(it.hasNext()) {
            value = (String) it.next();
            System.out.println(value);
        }
        System.out.println("--------分割线-----------");
        //随机访问,通过索引遍历
        String value2 = null;
        for(int i = 0; i < list.size(); i++) {
            value = list.get(i);
        }   
        String value3 = null;
        //for循环遍历
        for(String str : list) {
            value3 = str;
        }

那么我们来比较一下它们的运行效率如何:
运行结果:

iteratorThroughRandomAccess:3 ms
iteratorThroughIterator:8 ms
iteratorThroughFor2:5 ms

由此可见,遍历ArrayList时,使用随机访问(即,通过索引序号访问)效率最高,而使用迭代器的效率最低!

ArrayList的优缺点

优点:
1、ArrayList底层是以数组实现,是一种随机访问模式,再加上实现了RandomAccess接口,因此查找也就是get非常快。
2、ArrayList顺序添加非常方便。
缺点:
无论是删除或插入元素(除非是最后一个元素)都涉及到一次元素复制,如果比较多则是一个耗费性能的过程。
因此ArrayList比较适合顺序添加、随机访问的场景。

ArrayList和Vector的区别

ArrayList是线程非安全的,这很明显,因为ArrayList中所有方法都不是同步的,在并发的情况下一定会出现线程安全问题。那么解决方法有两种:
1、使用Collections.synchronizedList方法把ArrayList变成一个线程安全的List。

List<String> synchronizedList = Collections.synchronizedList(list);
        synchronizedList.add("44");

2、另一个方法就是使用vector,它是ArrayList线程安全版本,大部分实现完全一样,区别在于:
1、vector是线程安全的,ArrayList是非线程安全;
2、vector可以指定增长因子,如果该增长因子指定了,那么扩容的时候每次新数组大小就会在原基础上加上增长因子;如果不指定那么就是原数组*2。

为什么ArrayList的elementData是用transient修饰

ArrayList中数组的定义:

private transient Object[] elementData;

ArrayList实现了serializable接口,这意味着ArrayList可以被序列化,用transient修饰elementData意味着我们不希望elementData数组序列化。这时为什么?因为序列化ArrayList的时候,ArrayList数组未必是满的,比如说数组大小是10,现在只有3和数组,那么是否有必要序列化整个elementData呢?显然是没有必要的,因此ArrayList重写了writeObject方法,每次序列化都调用它,先调用defaultWriteObject() 方法序列化ArrayList中非transient元素,然后遍历elementData,只序列化那些有的元素,这样:
1、加快了序列化的速度;
2、减小了序列化之后文件的大小。

猜你喜欢

转载自blog.csdn.net/qq_36952611/article/details/74130124