zoukankan      html  css  js  c++  java
  • JDK 1.8源码阅读 ArrayList

    一,前言

      ArrayList是Java开发中使用比较频繁的一个类,通过对源码的解读,可以了解ArrayList的内部结构以及实现方法,清楚它的优缺点,以便我们在编程时灵活运用。

    二,ArrayList结构

      

      如上图所示:ArrayList是一种线性数据结构,它的底层是用数组实现的,相当于动态数组。与Java中的数组相比,它的容量能动态增长。类似于C语言中的动态申请内存,动态增长内存。
      当创建一个数组的时候,就必须确定它的大小,系统会在内存中开辟一块连续的空间,用来保存数组,因此数组容量固定且无法动态改变。ArrayList在保留数组可以快速查找的优势的基础上,弥补了数组在创建后,要往数组添加元素的弊端。实现的基本方法如下:
      1. 快速查找:在物理内存上采用顺序存储结构,因此可根据索引快速的查找元素。
      2. 容量动态增长: 当数组容量不够用时,创建一个比原数组容量大的新数组,将数组中的元素“搬”到新数组,再将新的元素也放入新数组,最后将新数组赋给原数组即可。
      3. ArrayList插入:。插入一个元素的时候,是耗时是一个常量时间O(1),在插入n个元素的时候,需要的时间就是O(n)。其他的操作中,运行的时间也是一个线性的增长(与数组中的元素个数有关)。

    三,ArrayList源码阅读

      3.1 ArrayList的继承关系

         

       在这提一下RandomAccess接口:

       实现所使用的标记接口,用来表明其支持快速(通常是固定时间)随机访问。此接口的主要目的是允许一般的算法更改其行为,从而在将其应用到随机或连续访问列表时能提供良好的性能。

         对于顺序访问的list,比如LinkedList,使用Iterator访问会比使用for-i来遍历list更快。这一点其实很好理解,当对于LinkedList使用get(i)的时候,由于是链表结构,所以每次都会从表头开始向下搜索,耗时肯定会多。
       对于实现RandomAccess这个接口的类,如ArrayList,我们在遍历的时候,使用for(int i = 0; i < size; i++)来遍历,其速度比使用Iterator快(接口上是这么写的)。但是笔者看源码的时候,Iterator里使用的也是i++,这种遍历,无非是增加了fail-fast判断,估计就是这个导致了性能的差距,但是没有LinkedList这么大。笔者循环了 1000 * 100 次,贴出比较结果,仅供参考.

    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.LinkedList;
    import java.util.ListIterator;
    
    public class Main {
    
        public static void main(String[] args) {
    
            ArrayList al = new ArrayList<Integer>();
            for (int i = 1; i < 1000 * 100; i++) {
                al.add(i);
            }
            long tmp_time1 = System.currentTimeMillis();
            for (int j = 1; j < al.size(); j++) {
                Object ol = al.get(j);
            }
            long tmp_time2 = System.currentTimeMillis();
            System.out.println(tmp_time2-tmp_time1); // 1
    
            Iterator iterator = al.iterator();
            for(al.iterator(); iterator.hasNext();){
                Object next = iterator.next();
            }
            long tmp_time3 = System.currentTimeMillis();
            System.out.println(tmp_time3-tmp_time2); // 4
    
            LinkedList bl = new LinkedList<Integer>();
            for (int i = 1; i < 1000 * 100; i++) {
                bl.add(i);
            }
            long tmp_time4 = System.currentTimeMillis();
            for (int j = 1; j < bl.size(); j++) {
                Object ol = bl.get(j);
            }
            long tmp_time5 = System.currentTimeMillis();
            System.out.println(tmp_time5-tmp_time4); //7561
    
            Iterator iterator2 = bl.iterator();
            for(bl.iterator(); iterator2.hasNext();){
                Object next = iterator2.next();
            }
            long tmp_time6 = System.currentTimeMillis();
            System.out.println(tmp_time6-tmp_time5); // 4
        }
    }
    
    // 分别是1,4,7561,4

        不对比不知道,一对比吓一跳,当然也有可能是笔者电脑原因。所以在选择遍历方式时需要谨慎使用。

      3.2 ArrayList的成员变量

       直接贴出源码,如下所示:

    //初始化默认容量
    private static final int DEFAULT_CAPACITY = 10;
    // 空对象数组
    private static final Object[] EMPTY_ELEMENTDATA = {};
    // 默认容量的空对象数组
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};// 存储的数量
    private int size;
    // 数组能申请的最大数量
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

          在上面我们说过,ArrayList是一种可变的数组,其实他是有一个初始值的,当超出这个初始值时就会进行扩容,而这个初始值就为10,也就是上面的

        DEFAULT_CAPACITY显示的值。

      3.3 ArrayList的构造方法

        public ArrayList(int initialCapacity) {}  // 初始对于大小的ArrayList
    
        public ArrayList() {}   // 无参调用
    
        public ArrayList(Collection<? extends E> c) {}   //  数组拷贝形式

      3.4 ArrayList的常用方法

         在这只列举一些常用的方法,若需要更加详细的内容,可以查看源码。

    public int size() {}  // 返回ArrayList的长度
    public boolean isEmpty() {} // 判断是否为空 public boolean contains(Object o) {} // 查看是否包含某个元素 public int indexOf(Object o) {} // 返回元素索引 public int lastIndexOf(Object o) {} // 返回某个元素最后一次出现的索引 public Object clone() {} // 克隆一个ArrayList public Object[] toArray() {} // 将ArrayList变成数组 public <T> T[] toArray(T[] a) {} // 指定数组的类型 public E get(int index) {} // 返回对应索引的元素 public E set(int index, E element) {} // 将对于索引位置元素替换为指定的元素 public boolean add(E e) {} // 在末尾添加元素 public void add(int index, E element) {} // 在指定位置添加元素 public E remove(int index) {} // 移除对应索引位置的元素 public boolean remove(Object o) {} // 直接移除某个元素, 若没有该元素返回falsepublic void clear() {} // 清空ArrayList public boolean addAll(Collection<? extends E> c) {} // 在某位添加一个集合到ArrayList public boolean addAll(int index, Collection<? extends E> c) {} // 在指定位置添加一个集合 protected void removeRange(int fromIndex, int toIndex) {} // 删除一段索引区间的元素public boolean removeAll(Collection<?> c) {} // 删除一个集合 public boolean retainAll(Collection<?> c) {}public ListIterator<E> listIterator(int index) {} // 转换成一个迭代器对象 public ListIterator<E> listIterator() {} //转换成一个List迭代器对象 public Iterator<E> iterator() {} // 转换成一个迭代器对象 public List<E> subList(int fromIndex, int toIndex) {} // 窃取一段ArrayList @Override public void forEach(Consumer<? super E> action) {} // 增强for可以使用 @Override public boolean removeIf(Predicate<? super E> filter) {} @Override @SuppressWarnings("unchecked") public void replaceAll(UnaryOperator<E> operator) {} @Override @SuppressWarnings("unchecked") public void sort(Comparator<? super E> c) {} // 排序 参数是排序方法

    四,总结

      ArrayList在随机访问的时候,数组的结构导致访问效率比较高,但是在指定位置插入,以及删除的时候,需要移动大量的元素,导致效率低下,在使用的时候要根据场景特点来选择,另外注意循环访问的方式选择。

  • 相关阅读:
    前端兼容性问题总结
    javascript中Array类型常用方法
    TCP/IP基础知识
    Ajax
    angular 过滤器(日期转换,时间转换,数据转换等)
    angular学习笔记
    TCP协议三次握手过程分析
    TCP/IP基础知识
    Halcon形态学处理
    Halcon软件介绍与图像基本知识
  • 原文地址:https://www.cnblogs.com/tashanzhishi/p/10603002.html
Copyright © 2011-2022 走看看