Java- ArrayList 和 LinkedList

396 阅读3分钟
  1. 底层实现:

    • ArrayList:
      • ArrayList 底层采用动态扩展的数组进行实现.
      • LinkedList 底层采用的是双向链表实现.
  2. 方法时间复杂度:

    • ArrayList:

      • get(int index) : O(1)
      • add(E element) : 在数组未满的情况下是O(1) ; 数组满的情况下是O(n) , 因为此时需要对数组进行扩容.
      • add(int index, E element) : O(n) (平均复杂度为 n/2), 因为需要移动数组的元素或对数组进行扩容.
      • remove(int index) : O(n) (平均复杂度为 n/2), 因为需要移动数组的元素.
      • Iterator.remove() : O(n) (平均复杂度为 n/2), 因为需要移动数组的元素.
      • ListIterator.add(E element) : O(n) (平均复杂度为 n/2), 因为需要移动数组的元素.
    • LinkedList:

      • get(int index) : O(n) (平均复杂度为 n/4). 因为内部采用双向链表实现, 内部对 index 进行了判断, 如果大于size/2则从尾部开始查找, 反之则从头部开始查找. 因此最坏的情况需要 n/2, 最好的情况 0, 所以平均复杂度为 n/4.

        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;
        }
        
      • add(E element): O(1)

      • add(int index, E element): O(n) (平均复杂度为 n/4), 理由同上.

      • remove(int index): O(n) (平均复杂度为 n/4), 理由同上`.

      • Iterator.remove(): O(1)

      • ListIterator.add(E element) O(1)

  3. 使用:

    • ArrayList:

      • 遍历元素:

        List<String> strs = new ArrayList<>();
        strs.add("test1");
        strs.add("test2");
        strs.add("test3");
        strs.add("test4");
        // for 循环方式
        for(int i = 0; i < strs.size(); i++) {
            System.out.println(strs.get(i));
        }
        // Iterator 方式
        Iterator<String> iterator = str.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
        
      • 正确删除元素:

        List<String> strs = new ArrayList<>();
        strs.add("test1");
        strs.add("test2");
        strs.add("test3");
        strs.add("test4");
        Iterator<String> iterator = str.iterator();
        String temp;
        while (iterator.hasNext()) {
            temp = iterator.next();
            if (temp.equals("test2")) {
                // 注意这里不能调用 strs.remove("test2") 进行删除, 这样会造成异常. 
                // 即使没有异常产生, 这样删除的效率也不高, 因为又必须去遍历整个列表去找到元素的位置
                iterator.remove();
            }
        }
        
      • Iterator 删除元素原理:

        ArrayList内部有一个modCount记录着内部元素size的变化次数, 任何引起元素size变化的操作都会更改这个值. 而在Iterator内部的方法执行前都会去检测这个值和自己当前保存的副本值是否一致, 如果不一致则会抛出异常. 在上述代码中, 如果在while循环中直接调用strs.remove("test2")方法删除元素就会导致modeCount值变化, 在下一次Iterator#next()方法调用时, 检测到这个值和自己保存的副本不一样, 就抛出异常了. 但是如果是调用Iterator#remove()方法, 本质上还是调用的ArrayList.remove()方法, 只不过Iterator#remove()方法在内部会更新它保存的modeCount副本, 这样就不会报错了. 在调用Iterator#remove()方法前一定要先调用Iterator#next()方法, 让它把游标移动到当前的元素.

        public boolean hasNext() {
            return cursor != size;
        }
        
        @SuppressWarnings("unchecked")
        public E next() {
            // 这里去判断 modeCount 副本的值和 modeCount 是否相同
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }
        
        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            // 这里去判断 modeCount 副本的值和 modeCount 是否相同
            checkForComodification();
        
            try {
                // 这里调用ArrayList#remove()方法删除元素
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                // 更新 modeCount 副本值
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
        
    • LinkedList:

      • 遍历元素:

        List<String> strs = new LinkedList<>();
        strs.add("test1");
        strs.add("test2");
        strs.add("test3");
        strs.add("test4");
        // Iterator 方式
        Iterator<String> iterator = str.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
        // 不要采用 for 循环方式
        for(int i = 0; i < strs.size(); i++) {
            // 因为调用 strs.get(i) 在内部又根据下标去循环查找元素, 相当于双重循环了
            System.out.println(strs.get(i));
        }
        
      • 正确删除元素:

        List<String> strs = new LinkedList<>();
        strs.add("test1");
        strs.add("test2");
        strs.add("test3");
        strs.add("test4");
        Iterator<String> iterator = str.iterator();
        String temp;
        while (iterator.hasNext()) {
            temp = iterator.next();
            if (temp.equals("test2")) {
                // 这里删除效率最高, 因为直接改变链表节点的指向即可
                iterator.remove();
            }
        }
        
      • Iterator 删除元素原理: 原理同上.

  4. 参考:

    [1] : Arraylist 与 LinkedList 区别?

    [2] : When to use LinkedList over ArrayList in Java?