JDK List一点笔记

1. List
    常用List为ArrayList、LinkedList、Vector、CopyOnWriteArrayList,List类图结构如下

Iterable接口是迭代接口,方法iterator、forEach、spliterator三个方法,实现类可以使用迭代方法iterator()了。
List是一个接口,它继承于Collection的接口,代表着有序的队列。
AbstractList 是一个抽象类,它继承于AbstractCollection。AbstractList实现List接口中除size()、get(int location)之外的函数。
AbstractSequentialList 是一个抽象类,它继承于AbstractList。AbstractSequentialList 实现了“ 链表中,根据index索引值操作链表的全部函数”。

2. ArrayList
    ArrayList是常用的List,以对象数组存储数据,随机访问效率高,随机插入、删除效率低。
    在操作数据过程中大量的使用了System.arraycopy(Object src,  int  srcPos, Object dest, int destPos, int length)。 此方法是native方法,
          src:原数组
          srcPos:原数组其实索引
          dest:目标数组
          destPos:目标数组起始位置
          length:复制的数组长度
   
     public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable

    继承AbstractList,实现了List、RandomAccess、Cloneable、Serializable,
    ArrayList 实现了RandmoAccess接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在ArrayList中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。List的“通过Iterator迭代器访问”的效率要比 “快速随机访问”和“for循环遍历”效率高。
    ArrayList 实现了Cloneable接口,即覆盖了函数clone(),能被克隆,使用Arrays.copy来进行clone。
    ArrayList 实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。

    ArrayList默认容量为10,每次递增算法是每次增加50%的容量,如果增加50%比所需的最小容量还小,则默认使用参数中的最小容量;最大Integer.MAX_VALU
    注意: ArrayList和Vector在扩容过程中,都会采用Arrays.copyOf将旧数组复制到新数组中去,会有性能和空间损耗。
   
   
    private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }  
    ArrayList是非线程安全的。
    适用场景:非线程安全下的读多写少的情况。

    ArrayList支持3中遍历方式:
    1)迭代器遍历
           Integer value = null;
          Iterator iter = list.iterator();
          while (iter.hasNext()) {
                value = (Integer)iter.next();
          }
    2)随机访问,通过索引值去遍历。
         Integer value = null;
          int size = list.size();
          for (int i=0; i<size; i++) {
               value = (Integer)list.get(i);        
          }  
    3)for循环遍历
           Integer value = null;
          for (Integer integ:list) {
               value = integ;
          }

    ArrayList的插入、删除操作,思想为:
    ①检查容量ensureCapacityInternal(size+1),容量不足则自动扩容;
    ②使用System.arraycopy( elementData, index, elementData, index + 1,  size - index )来移动数组;
    ③变更size

    排序使用Arrays.sort

3. LinkedList
     LinkedList是用双向链表结构存储数据,它可以被当做堆栈、队列或双端队列进行操作。LinkedList随机访问效率低,但随机插入、随机删除效率低。

    public class LinkedList<E>  extends AbstractSequentialList<E>  implements List<E>, Deque<E>, Cloneable, java.io.Serializable
     AbstractSequentialList 实现了get(int index)、set(int index, E element)、add(int index, E element) 和 remove(int index)这些函数。这些接口都是随机访问List的,LinkedList是双向链表;既然它继承于AbstractSequentialList,就相当于已经实现了“get(int index)这些接口”。 我们若需要通过AbstractSequentialList自己实现一个列表,只需要扩展此类,并提供 listIterator() 和 size() 方法的实现即可。若要实现不可修改的列表,则需要实现列表迭代器的 hasNext、next、hasPrevious、previous 和 index 方法即可。
    
    链表查找node时,一个小小的优化,折半查找
   
   
   Node<E> node(int index) {
        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    } 
 
    LinkList通过设置两个成员变量Node<E> first, Node<E> last,来标识双项链表的头尾节点。
    LinkList不存在容量不足,需要扩容的问题。
    LinkedList的克隆函数,即是将全部元素克隆到一个新的LinkedList对象中。  
    LinkedList实现java.io.Serializable。当写入到输出流时,先写入“容量”,再依次写入“每一个节点保护的值”;当读出输入流时,先读取“容量”,再依次读取“每一个元素”。

4. Vector
    Vector 是矢量队列,和ArrayList一样,它也是一个动态数组,由数组实现。但是ArrayList是非线程安全的,而Vector是线程安全的,方法通过synchronized进行同步。
    
    public class Vector<E>  extends AbstractList<E>  implements List<E>, RandomAccess, Cloneable, java.io.Serializable
     继承AbstractList,实现了List、RandomAccess、Cloneable、Serializable,
    Vector实现了RandmoAccess接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在ArrayList中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。
     Vector 实现了Cloneable接口,即覆盖了函数clone(),能被克隆。
     Vector 实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。

    Vector 默认容量为10,每次增加capacityIncrement的大小,但如果capacityIncrement为0,则默认乘以2,如果乘以2仍然比所需的最小容量还小,则使用参数中的最小容量。注意:ArrayList和Vector在扩容时的实现是,使用Arrays.copyOf,生成一个新数组,将旧数组的数据复制到新数组中。
   
   
private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
         
     排序使用Arrays.sort

5. CopyOnWriteArrayList
    CopyOnWriteArraySet和CopyOnWriteArrayList是JDK1.5后提供的两个并发容器。

    Copy On Write,是一种用于程序设计中的优化策略。其基本思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容Copy出去形成一个新的内容然后再改( 即每次写,都会生成一个新的数组),这是一种延时懒惰策略。
    CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

    public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    实现了List接口。
    实现了RandmoAccess接口,即提供了随机访问功能。
    实现了Cloneable接口,即覆盖了函数clone(),能被克隆,使用Arrays.copy来进行clone。
    实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。

     CopyOnWriteArrayList用对象数组存储数据, 读不需要加锁,而写 使用 ReentrantLock可重入锁作为锁。
         public  boolean add ( E e )   {
  
  
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }  
   
   
    public E remove(int index) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            E oldValue = get(elements, index);
            // index后面需要移动的元素数量
            int numMoved = len - index - 1;
            if (numMoved == 0)
                // numMoved == 0,表示移除最后一个
                setArray(Arrays.copyOf(elements, len - 1));
            else {
                Object[] newElements = new Object[len - 1];
                // 非最后一个,需要两次复制
                // 将0~(index-1)共index个元素复制到新数组中,即newElements[0]~newElements[index-1]
                System.arraycopy(elements, 0, newElements, 0, index);
                // 将旧数组中的(index+1)及其以后的元素复制到新数组中,即newElements[index]~newElements[len-1]
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);
                // 将引用指向新的数组
                setArray(newElements);
            }
            return oldValue;
        } finally {
            lock.unlock();
        }
    }  
    
   
   
    public boolean addAll(int index, Collection<? extends E> c) {
        Object[] cs = c.toArray();
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            if (index > len || index < 0)
                throw new IndexOutOfBoundsException("Index: "+index+
                                                    ", Size: "+len);
            if (cs.length == 0)
                return false;
            // 注意这里不是len-index-1
            int numMoved = len - index;
            Object[] newElements;
            if (numMoved == 0)
                // 加载末尾
                newElements = Arrays.copyOf(elements, len + cs.length);
            else {
                // 加在中间
                newElements = new Object[len + cs.length];
                // 将原数组分别复制到新数组中0~(index-1)和(index+cs.length)~(len+cs.length)位置,留下的空间放置要添加的数组
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index,
                                 newElements, index + cs.length,
                                 numMoved);
            }
            System.arraycopy(cs, 0, newElements, index, cs.length);
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }  

    CopyOnWrite并发容器用于读多写少的并发场景。比如白名单,黑名单,商品类目的访问和更新场景,假如我们有一个搜索网站,用户在这个网站的搜索框中,输入关键字搜索内容,但是某些关键字不允许被搜索。这些不能被搜索的关键字会被放在一个黑名单当中,黑名单每天晚上更新一次。当用户搜索时,会检查当前关键字在不在黑名单当中。
    使用CopyOnWriteMap需要注意两件事情:
    1. 减少扩容开销。根据实际需要,初始化CopyOnWriteMap的大小,避免写时CopyOnWriteMap扩容的开销。
    2.  使用批量添加。因为每次添加,容器每次都会进行复制,所以减少添加次数,可以减少容器的复制次数。如使用上面代码里的addBlackList方法。

    CopyOnWrite的缺点:
    CopyOnWrite容器有很多优点,但是同时也存在两个问题,即内存占用问题和数据一致性问题。
    1.  内存占用问题。因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象(注意:在复制的时候只是复制容器里的引用,只是在写的时候会创建新对象添加到新容器里,而旧容器的对象还在使用,所以有两份对象内存)。如果这些对象占用的内存比较大,比如说200M左右,那么再写入100M数据进去,内存就会占用300M,那么这个时候很有可能造成频繁的Yong GC和Full GC。之前我们系统中使用了一个服务由于每晚使用CopyOnWrite机制更新大对象,造成了每晚15秒的Full GC,应用响应时间也随之变长。
     针对内存占用问题,可以通过压缩容器中的元素的方法来减少大对象的内存消耗,比如,如果元素全是10进制的数字,可以考虑把它压缩成36进制或64进制。或者不使用CopyOnWrite容器,而使用其他的并发容器,如ConcurrentHashMap。
    2.  数据一致性问题。CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值