java基础学习之集合-List

来源:互联网 发布:联通4g网络覆盖查询 编辑:程序博客网 时间:2024/06/07 16:05
java中,集合是常见的保存数据的一种方式,尤其是数据量比较大的时候,我们可以在集合利用泛型来保存某种对象,泛型的好处是增强程序的可读性和稳定性。java的集合主要是Collection接口和Map接口,Collection下有List,Set,Queue, Deque等子接口。
  1. List接口是有序的Collection,集合中的元素可以重复,访问集合中的元素可以根据元素的索引来访问。
  2. Set接口是无序的Collection,集合中的元素不可以重复,访问集合中的元素只能根据元素本身来访问(也是不能集合里元素不允许重复的原因)。
  3. Map集合中保存Key-value对形式的元素,访问时只能根据每项元素的key来访问其value。

    List的LinkedList

    ArrayList应该是大家非常熟悉的List了,它是基于动态数组实现的集合,即大小是可变的,允许重复,也允许为空,不过线程不安全,可以通过Collections.synchronizedList转换为同步的。

    List list = Collections.synchronizedList(new ArrayList(...)); 

    我们可以看到其底层使用了数组:

    private transient Object[] elementData;

    这儿很有趣的是用了transient关键字,它在修饰了Object[] elementData是希望数组不被序列化,但是我们看到ArrayList是实现了Serializable接口的,即是希望其可以被序列化,这里是因为序列化的时候,elementData数组里面的元素不一定是满的,就没有必要全部都序列化类,因此ArrayList重写了writeObject方法:

 private void writeObject(java.io.ObjectOutputStream s)        throws java.io.IOException{// Write out element count, and any hidden stuffint expectedModCount = modCount;s.defaultWriteObject();        // Write out array length       s.writeInt(elementData.length);    // Write out all elements in the proper order.for (int i=0; i<size; i++)           s.writeObject(elementData[i]);    if (modCount != expectedModCount) {           throw new ConcurrentModificationException();    }}

当序列化的时候调用这个方法,先调用defaultWriteObject()方法序列化ArrayList中的非transient元素,但是elementData没有序列化,然后遍历elementData,只序列化那些有的元素,这样可以加快序列化的速度。
ArrayList在添加时 add(E e) 元素的时候,将元素放到列表尾部

    public boolean add(E e) {    //扩容检测    ensureCapacity(size + 1);  // Increments modCount!!    //插入指定位置    elementData[size++] = e;    return true;    }

ensureCapacity()方法是对ArrayList集合进行扩容操作,elementData(size++) = e,将列表末尾元素指向e。
add(int index, E element):将指定的元素插入此列表中的指定位置。

  public void add(int index, E element) {        //判断索引位置是否正确        if (index > size || index < 0)            throw new IndexOutOfBoundsException(            "Index: "+index+", Size: "+size);        //扩容检测        ensureCapacity(size+1);          /*         * 对源数组进行复制处理(位移),从index + 1到size-index。         * 主要目的就是空出index位置供数据插入,         * 即向右移动当前位于该位置的元素以及所有后续元素。          */        System.arraycopy(elementData, index, elementData, index + 1,                 size - index);        //在指定位置赋值        elementData[index] = element;        size++;        }

这儿使用了System.arraycopy()方法,该方法的根本目的就是将index位置空出来以供新数据插入,这里需要进行数组数据的右移,这是非常麻烦和耗时的,这就是ArrayList插入删除效率低下的原因。
ArrayList 删除元素 :
1.按照下标删除 remove(int index):移除此列表中指定位置上的元素。

  public E remove(int index) {        //位置验证        RangeCheck(index);        modCount++;        //需要删除的元素        E oldValue = (E) elementData[index];           //向左移的位数        int numMoved = size - index - 1;        //若需要移动,则想左移动numMoved位        if (numMoved > 0)            System.arraycopy(elementData, index + 1, elementData, index,                    numMoved);        //置空最后一个元素        elementData[--size] = null; // Let gc do its work        return oldValue;    }

2、按照元素删除 remove(Object o),这会删除ArrayList中与指定要删除的元素匹配的第一个元素

public boolean remove(Object o) {        //因为ArrayList中允许存在null,所以需要进行null判断        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;    }

可以看出两种删除方法都差不多,都使用System.arraycopy( )方法来移动删除元素位置的后面所有元素向前移动一个位置。

总结一下ArrayList的优缺点:
1、ArrayList底层以数组实现,是一种随机访问模式,再加上它实现了RandomAccess接口,因此查找也就是get的时候非常快。

2、ArrayList在顺序添加一个元素的时候非常方便,只是往数组里面添加了一个元素而已。

不过ArrayList的缺点也十分明显:

1、插入元素的时候,涉及到一次元素复制,如果要复制的元素很多,那么就会比较耗费性能

2、删除元素的时候,涉及到一次元素复制,如果要复制的元素很多,那么就会比较耗费性能

因此,ArrayList比较适合顺序添加、随机访问的场景。

List的LinkedList

LinkedList是底层基于双向链表实现的List,这使得LinkedList在插入和删除时更优于ArrayList,而随机访问则比ArrayList逊色些。线程也不安全。双向链表就是链表中任意一个存储单元都可以通过向前或者向后寻址的方式获取到其前一个存储单元和其后一个存储单元。
可以看到LinkedList里的属性header

//header是链表的起点,Entry是节点private transient Entry<E> header = new Entry<E>(null, null, null);//节点,里面的一个内部类private static class Entry<E> {        E element;        //元素节点        Entry<E> next;    //下一个元素        Entry<E> previous;  //上一个元素        Entry(E element, Entry<E> next, Entry<E> previous) {            this.element = element;            this.next = next;            this.previous = previous;        }    }
Entry里定义了存储的元素和前一个元素和后一个元素,其实存储的是元素的引用地址。

1.添加元素 add(E e): 将指定元素添加到此链表的结尾

public boolean add(E e) {    addBefore(e, header);        return true;    }//addBefore()private Entry<E> addBefore(E e, Entry<E> entry) {        //利用Entry构造函数构建一个新节点 newEntry,        Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);        //修改newEntry的前后节点的引用,确保其链表的引用关系是正确的        newEntry.previous.next = newEntry;        newEntry.next.previous = newEntry;        //容量+1        size++;        //修改次数+1        modCount++;        return newEntry;    }

其实就是先新增了一个节点,并改变了其前后节点的引用,所以增删元素的时候会比ArrayList慢一些。
2.删除元素 ,跟ArrayList一样有按元素删除和按下标删除,前者会删除从头开始匹配的第一个元素从此列表中移除首次出现的指定元素,后者会找到元素位置,再删除。
3.查找元素

public E get(int index) {    return entry(index).element;}private Entry<E> entry(int index) {    //检查是否越界    if (index < 0 || index >= size)        throw new IndexOutOfBoundsException("Index: "+index+                                            ", Size: "+size);    Entry<E> e = header;    //位于前半段,往后查找    if (index < (size >> 1)) {         //遍历查找        for (int i = 0; i <= index; i++)            e = e.next;    } else {        //位于后半段,往前查找        for (int i = size; i > index; i--)            e = e.previous;    }    return e;}

由于LinkedList是双向链表,支持向前查找和向后查找,这里先进行了一个判断来提高效率。

List的Vector

Vector是底层用数组实现的List,由于实现了Serializable,所以线程是安全的,在需要线程安全时可以使用。

LinkedList和ArrayList的对比
1、插入速度ArrayList会比较快,因为ArrayList是基于数组实现的,只要往指定位置塞一个数据就好了;LinkedList每次插入的时候LinkedList将先new一个对象出来,所以插入LinkedList慢于ArrayList

2、同样,由于LinkedList里面有插入的元素,还要改变Entry的前置Entry和后继Entry,如果一个LinkedList中的Entry非常多,那么LinkedList将比ArrayList更耗费一些内存

3.LinkedList不支持高效的随机元素访问,ArrayList支持,它实现了RandomAccess ,两种更优的遍历方式区别,ArrayList使用最普通的for循环遍历,LinkedList使用foreach循环比较快,使用不当的遍历方法会降低效率

ArrayList和Vector的对比
1.同步性,Vector是线程安全的,是同步的,而ArrayList是线程不安全的,就是不同步
2.数据扩容,扩容时,Vector默认是扩容到2倍,而ArrayList是1.5倍,同时需要注意扩容是很耗时间和内存的。

1 0