• List


    Vector:线程安全,本质和ArrayList一样是动态数组,方法都是通过sychronize实现安全的,不建议使用,可以用CopyOnWriteArrayList。
    Stack:Vector子类,本质也是动态数组,它实现的是先进后出的栈,效率低下可以用ArrayDeque来替代。

    ArrayList

    1、object数组用于存储元素。所以底层是用数组实现。
    2、size:记录长度

    List<String> strList = new ArrayList<String>();
    List<String> strList2 = new ArrayList<String>(2);
    

    一个无参,一个有参。无参构造器没有指定数组长度,默认为10,有参可以自己指定长度。

        public ArrayList() {
            this(10);//给定默认值10,然后调用有参构造器
        }
    
        public ArrayList(int initialCapacity) {
            super();
            if (initialCapacity < 0)
                throw new IllegalArgumentException("Illegal Capacity:" + initialCapacity);
            this.elementData = new Object[initialCapacity];//创建一个长度为指定的数组,元素值默认为null
        }
    

    添加元素

        public boolean add(E e) {
            ensureCapacity(size + 1);//确保对象数组elementData有足够的容量,可以将新加入的元素e加进去
            elementData[size++] = e;//加入新元素e,size加1
            return true;
        }
    

    每次数组做增加操作之前都要保证数组的大小够用,所以都会先调用ensureCapacity(size + 1)方法:

     public void ensureCapacity(int minCapacity) {
           modCount++;
           int oldCapacity = elementData.length;//获取数组大小(即数组的容量)
           //当数组满了,又有新元素加入的时候,执行扩容逻辑
           if (minCapacity > oldCapacity) {
               Object oldData[] = elementData;
               int newCapacity = (oldCapacity * 3) / 2 + 1;//新容量为旧容量的1.5倍+1
               if (newCapacity < minCapacity)//如果扩容后的新容量还是没有传入的所需的最小容量大或等于(主要发生在addAll(Collection<? extends E> c)中)
                   newCapacity = minCapacity;//新容量设为最小容量
               elementData = Arrays.copyOf(elementData, newCapacity);//复制新容量
           }
       }
    

    fail-fast机制
    ArrayList不是线程安全的集合,当多线程环境下会出现ConcurrentModifyException,这个就命名为fail-fast机制。

    代码实现在ArrayList的父类AbstractList中:

        public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
    
        ...
    
            // AbstractList中唯一的属性
            // 用来记录List修改的次数:每修改一次(添加/删除等操作),将modCount+1
            protected transient int modCount = 0;
    
            // 返回List对应迭代器。实际上,是返回Itr对象。
            public Iterator<E> iterator() {
                return new Itr();
            }
    
            // Itr是Iterator(迭代器)的实现类
            private class Itr implements Iterator<E> {
                int cursor = 0;
    
                int lastRet = -1;
    
                // 修改数的记录值。
                // 每次新建Itr()对象时,都会保存新建该对象时对应的modCount;
                // 以后每次遍历List中的元素的时候,都会比较expectedModCount和modCount是否相等;
                // 若不相等,则抛出ConcurrentModificationException异常,产生fail-fast事件。
                int expectedModCount = modCount;
    
                public boolean hasNext() {
                    return cursor != size();
                }
    
                public E next() {
                    // 获取下一个元素之前,都会判断“新建Itr对象时保存的modCount”和“当前的modCount”是否相等;
                    // 若不相等,则抛出ConcurrentModificationException异常,产生fail-fast事件。
                    checkForComodification();
                    try {
                        E next = get(cursor);
                        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);
                        if (lastRet < cursor)
                            cursor--;
                        lastRet = -1;
                        expectedModCount = modCount;
                    } catch (IndexOutOfBoundsException e) {
                        throw new ConcurrentModificationException();
                    }
                }
    
                final void checkForComodification() {
                    if (modCount != expectedModCount)
                        throw new ConcurrentModificationException();
                }
            }
    
        ...
        }
    

    说明一下这个流程:
    1、有一个集合X,初始化的时候modCount的值为0.
    2、有个线程A要遍历X,某一时刻创建了迭代器,此迭代器会记录此刻该线程的modCount为0.然后开始遍历,每遍历一个元素之前都会比较一下该迭代器记录的modCount和集合的modCount是否相同,如果不同证明其他线程修改了集合元素,则抛出异常。
    3、假设上边迭代的过程中有个线程B同时去修改集合元素,每次修改都会更新集合的modCount的值。

    解决这个问题的方法就是采用concurrent包下的CopyOnWriteList替代ArrayList。

    LinkedList

    核心属性

        transient int size = 0;
        transient Node<E> first;//链表第一个元素
        transient Node<E> last;//链表最后一个元素
    
        private static class Node<E> {
            E item;
            Node<E> next;
            Node<E> prev;
        }
    

    添加元素

        public boolean add(E e) {
            linkLast(e);
            return true;
        }
    
     void linkLast(E e) {
            final Node<E> l = last;
            final Node<E> newNode = new Node<>(l, e, null);
            last = newNode;
            if (l == null)
                first = newNode;
            else
                l.next = newNode;
            size++;
            modCount++;
        }
    

    1、构造器为空实现,所以当执行完下面代码后,LinkedList也是个空的,first和last都是null

    LinkedList<String> a = new LinkedList();
    

    2、add()方法添加第一个元素
    创建一个node1,该node pre为null,next为null,item为元素的值。
    然后把first和last都设置为node1

    3、调用add()方法添加第二个元素
    创建一个node2,该node pre为node1,next为null,item为元素的值。
    然后把last都设置为node2

    和ArrayList比较,LinkedList是链表结构,数据存储不是连续的,每当删除元素的时候不需要重新排序老数据,所以更新效率高。

    ArrayList和LinkedList按照索引访问数组如下区别

            List a = new ArrayList();
            a.add(2);
            a.add(5);
            a.add(1);
            a.add(9);
            System.out.println(a.get(3));//底层直接按照索引访问数组 this.elementData[var1]
    
            List b = new LinkedList();
            b.add(2);
            b.add(5);
            b.add(1);
            b.add(9);
            System.out.println(b.get(3));//底层 先比较链表长度的1/2 和3谁大 ,  如果3大从后遍历链表,否则从前遍历链表
    

    链表和数组的区别

    1. 数组:
      • 优点
        • 随机访问性强
        • 查找速度快
      • 缺点
        • 插入和删除效率低
        • 可能浪费内存
        • 内存空间要求高,必须有足够的连续内存空间。
        • 数组大小固定,不能动态拓展
    2. 链表:
      • 优点
        • 插入删除速度快
        • 内存利用率高,不会浪费内存
        • 大小没有固定,拓展很灵活
      • 缺点
        • 不能随机查找,必须从第一个开始遍历,查找效率低

    返回顶部

  • 相关阅读:
    【prufer编码】BZOJ1430 小猴打架
    【费马小定理】BZOJ3260 跳
    【欧拉函数】BZOJ2705: [SDOI2012]Longge的问题
    【卡特兰数】BZOJ1485: [HNOI2009]有趣的数列
    【缩点+拓扑判链】POJ2762 Going from u to v or from v to u?
    【Floyd】BZOJ1491: [NOI2007]社交网络
    【转】对信息学竞赛中调试方法的建议
    【建图+拓扑判环】BZOJ3953: [WF2013]Self-Assembly
    【dfs判负环】BZOJ1489: [HNOI2009]最小圈
    【二分+最小树形图】UVA11865 比赛网络
  • 原文地址:https://www.cnblogs.com/yanhui007/p/12579563.html
Copyright © 2020-2023  润新知