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. 链表:
    • 优点
      • 插入删除速度快
      • 内存利用率高,不会浪费内存
      • 大小没有固定,拓展很灵活
    • 缺点
      • 不能随机查找,必须从第一个开始遍历,查找效率低

返回顶部

posted @ 2020-03-27 10:09  平淡454  阅读(179)  评论(0编辑  收藏  举报