zoukankan      html  css  js  c++  java
  • 浅谈ArrayList

    浅谈ArrayList

    废话不多说(事实是不会说),让我们直接进入正题
    首先讲一讲最基本的ArrayList的初始化,也就是我们常说的构造函数,ArrayList给我们提供了三种构造方式,我们逐个来查看

    Arraylist();

    无参的构造方法,这种方式的初始化,ArrayList内部会为我们声明一个长度为0的列表,但在我们调用add方法加入一个元素时,它内部会经历add->ensureCapacityInternal->calculateCapacity->ensureExplicitCapacity->grow->调用Arrays.copyOf方法返回新生成的列表(内部最终调用System.arraycopy完成元素的转移)->grow->ensureExplicitCapacity->ensureExplicitCapacity->add方法,该执行流程最终生成一个容量为十(默认值)的列表,并将元素加入到列表中。以下列出部分关键源码

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; 
    }
    
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        //如果为无参的构造
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { 
            //该处的DEFAULT_CAPCITY=10,minCapcity=1
            return Math.max(DEFAULT_CAPACITY, minCapacity); 
        }
        return minCapacity;
    }
    
    private void ensureExplicitCapacity(int minCapacity) {
        //列表结构发生改变的记录值,没发生一次结构改变(扩容、元素增加、元素删除),该值加一
        modCount++;
    
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    
    private void grow(int minCapacity) {
        ...
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    

    也许你会问,既然有初始默认容量值,为什么不在调用构造函数的时候就初始化一个默认长度的列表呢?
    个人意见:我认为在存在一种情况,就是仅声明一个ArrayList的列表对象,但是后面并未使用,在这种情况下,如果在声明是就初始化一个长度为10的列表,会造成空间的浪费,而且在真正使用列表时(即调用add方法),才初始化列表,有一种懒加载的思想在其中,避免了耗资源操作的集中,但也并非所有的构造方法中都不会初始化列表容量,在ArrayList(int capacity)构造方法中,只要输入的初始容量为正整数,那么就会在构造函数中就定义出指定大小的列表对象

    ArrayList(int capacity);

    该方式的初始化在执行构造函数的时候传入了一个初始容量值,不过要求这个初始容量值必须为正整数,而且如果为0的话等同于无参的构造方法,假定初始化容量为5,那么在调用add方法时,经历的方法依次为add->ensureCapacityInternal->calculateCapacity->ensureExplicitCapacity->add,下面我们来看看部分关键源码:

    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            //按指定容量初始化列表,此时elementData.length==initialCapacity
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            //传入值等于0,初始化一个容量为0的初始列表
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            //传入值为不符合要求,报错
            throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
        }
    }
    
    private static final Object[] EMPTY_ELEMENTDATA = {};
    
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        //不满足条件,不会进入该分支
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        //直接返回最小容量,即列表当前元素值+1(给新增元素的空间)
        return minCapacity;
    }
    

    ArrayList(Collection<? extends E> c);

    该方式的初始化需要传入Collection的子类对象,并根据该对象来初始化ArrayList

    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            //如果不是Object[]类型的列表,转化为Object[]类型
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
    

    到这里对ArrayList的三个构造函数做了简单的介绍

    在继续进行ArrayList中方法的介绍之前,需要注意ArrayList不是线程安全的,所以如果涉及并发操作,建议在初始化的时候就调用Collections.synchronizedList(list)来将线程不安全的列表对象转化为线程安全的对象,内部的实现原理:使用代理模式在原来的方法执行之前嵌套了一个同步机制,这里摘取其中的size()方法,源码如下:

    public int size() {
        synchronized (mutex) {  //同步代码块
            return c.size();    //c为被代理对象
        }
    }
    

    因为给所有的方法加上锁会降低代码的执行效率,而且有些方法是不需要加锁的,如果不想对所有的方法都加锁,可以在需要加锁的特定方法调用之前手动的做同步处理
    下面进入ArrayList中的部分方法介绍,首先介绍从List父接口中继承过来的方法,其中使用到的[]代表对应参数可以存在也可以不存在,不过存在与不存在构成了不同的重载方法:

    add([int index,] E element);

    该方法用于向列表中添加元素,存在单参方法和双参两个重载方法,单参直接往列表末尾添加新元素,双参方法则可以手动指定插入到列表的哪个索引位置。每当列表元素的个数(size)超过列表容量(elementData.length)时,会触发grow方法(扩容方法),每次在老容量的基础上增加一半的容量,然后通过Arrays.copyOf方法进行新列表的创建和拷贝原列表内容

    //在grow方法中关于新列表容量定义的关键代码
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    

    addAll([int index,] Collection<? extends E> c);

    该方法用于将另一个集合中的所有元素加入到当前列表中,该方法也存在单参和双参两个重载方法,单参方法把所有新元素添加到列表末尾,双参方法则在指定索引位置开始插入所有元素,扩容方法的触发和过程同add

    set(int index, E element);

    该方法用于替换列表中指定索引位置的值,内部会先进行索引范围检查,返回值为被替换下的老元素

    isEmpty();

    该方法用于判断列表中是否存在元素,返回值是一个布尔类型,源码的返回值为size == 0

    get(int index);

    该方法用于获取列表中指定位置的值并返回

    remove(int index);

    该方法用于删除列表中特定索引位置的值,返回值为被删除的老元素

    remove(Object o);

    该方法用于删除列表中第一个为特定元素的值,可以传入null,代表删除列表中第一个为null的元素,返回值为布尔类型

    clear();

    该方法用于清空列表中的所有元素,内部对elementData列表对象的所有元素置空,并记录modCount(列表结构记录变量)值,最后将size置0

    removeAll(Collection<?> c);

    该方法用于删除集合中存在于传入列表中的所有指定元素,要注意的是,该方法为全列表查找删除,不同于remove方法只删除第一次出现的位置

    retainAll(Collection<?> c);

    该方法用于跟removeAll的作用刚好互补,用于保留传入集合中包含的所有指定元素,即删除指定集合之外的所有元素

    size();

    该方法返回列表中元素的个数(size),但该值并不代表列表当前的容量(elementData.length)

    indexOf(Object o);

    该方法用于查询列表中特定元素出现的第一个索引位置,如果未找到则返回-1

    lastIndexOf(Object o);

    该方法用于查询列表中特定元素出现的最后一个下标位置,如果未找到则返回-1

    contains(Object o);

    该方法用于判断列表中是否存在指定的元素,返回值为布尔类型,源码的返回值为indexOf(o) >= 0

    sort(Comparator<? super E> c);

    该方法用于对列表元素按传入的指定排序规则进行排序,以下使用匿名内部类并结合Lambda表达式来作为示例构造一个二级排序规则,可根据需要增加更多层级的排序规则:

    list.sort((o1,o2) -> {
        int result = -(o1.getClick() - o2.getClick());// 数值类型的比较写法,点击量降序
        if (result == 0) {// 如果点击量相同,进入二级排序
            result = o1.getDate().compareTo(o2.getDate());// 字符串类型的比较写法,时间升序
        }
        return result;// 返回比较结果
    });
    

    subList(int fromIndex,int toIndex);

    该方法用于返回一个可操作性的子列表,不过要注意的是,子列表只不过是添加了偏移量的父列表,所以两个列表的对象是一致的,对于子列表中的操作,源码中都是在添加上对应的偏移量之后直接对父列表做对应修改,所以,对子列表的操作实际上就是对父列表的操作

    iterator();

    该方法用于返回当前列表的一个普通迭代器,提供了hasNext,next,remove和forEachRemaining方法,其中hasNext用于判断是否存在下一个迭代对象,next用于将cursor指向下一个待操作元素,并返回当前元素(由内部的lastRet索引值进行指定),remove用于删除当前元素(该方法不可连续调用多次,因为内部的lastRet索引在一次操作后会被置为-1,可以会在下一次next时重新指向当前元素),forEachRemaining用于对列表未迭代对象执行传入的钩子方法
    注意:如果在迭代器迭代对象时使用迭代器外部的remove或add方法改变了列表结构(新增或删除元素),会导致继续遍历列表时抛出ConcurrentModificationException异常,使用迭代器内部的remove方法改变列表结构不会抛出此异常

    listIterator([int index]);

    该方法用于返回一个加强版的迭代器对象,由于内部继承了iterator的类,所以可以提供iterator的所有功能,除此之外,还提供了自己独有的功能和特性,首先在初始化时就可以指定迭代初始索引,其次还提供了hasPrevious,nextIndex,previousIndex,previous,set和add方法,其中的previous方法可以实现列表的逆序遍历,set可以替换列表的当前迭代对象,add可以在当前迭代位置添加新的列表元素
    注意:如果在迭代器迭代对象时使用迭代器外部的remove或add方法改变了列表结构(新增或删除元素),会导致继续遍历列表时抛出ConcurrentModificationException异常,使用迭代器内部的remove和add方法改变列表结构不会抛出此异常

    spliterator();

    该方法用于切割列表元素,可用于并发编程时多线程操作列表,需要先进行切割,方法内部默认对半分,再对切割完成的列表进行并行处理,示例代码如下:

    ist list = new ArrayList();
    list.add(1);list.add(2);list.add(3);list.add(4);list.add(5);
    list.add(6);list.add(7);list.add(8);list.add(9);list.add(0);
    //对列表进行切割
    Spliterator<Integer> a = list.spliterator();//将列表转化为可拆分列表
    Spliterator<Integer> b = a.trySplit();//将可拆分的a列表对半分
    Spliterator<Integer> c = a.trySplit();//继续将可拆分的a列表对半分
    Spliterator<Integer> d = b.trySplit();//将对半分得到的b列表对半分
    //对切割好的列表进行操作
    a.forEachRemaining(x -> System.out.print(x + " "));
    System.out.println();
    b.forEachRemaining(x -> System.out.print(x + " "));
    System.out.println();
    c.forEachRemaining(x -> System.out.print(x + " "));
    System.out.println();
    d.forEachRemaining(x -> System.out.print(x + " "));
    

    打印结果为:

    8 9 0 
    3 4 5 
    6 7 
    1 2 
    

    toArray(T[] a);

    该方法用于将列表转化为数组,可以使用参数来指定生成数组的数据类型,返回值为指定类型的数组,以下为代码示例:

    String[] strs=list.toArray(new String[0]);// list.toArray(new Object[0]);等同于list.toArray();
    

    下面的方法是ArrayList中特有的方法,如果在你的代码中调用不到,请检查下声明该ArrayList对象的时候对象声明部分是否为ArrayList喔~

    clone();

    该方法继承自Cloneable接口父类,用于创建并返回一个浅克隆的列表对象

    ensureCapacity(int minCapacity);

    该方法用于检查列表容量是否已经达到瓶颈,继而判断是否需要进行扩容操作

    trimToSize();

    该方法用于将列表容量调整至列表的元素总数,可以对不需要继续添加新元素的列表使用该操作用于释放部分资源,使用了该方法的列表,在添加新元素时会进行扩容操作,扩容方法依旧是扩容原容量的一半

    forEach(Consumer<? super E> action);

    用于对列表中的所有元素执行对应的操作,该处使用匿名内部类结合Lambda表达式的方式进行简单演示:

    list.forEach(x -> System.out.println(x.getAge()+1));// 如果只是简单的打印,还可以使用list.forEach(System.out::println);
    

    removeIf(Predicate<? super E> filter);

    该方法用于移除列表中符合条件的所有元素,该处使用匿名内部类结合Lambda表达式的方式进行简单演示:

    list.removeIf(x -> x.getAge() < 18);
    

    如果对你有帮助,点个赞,或者打个赏吧,嘿嘿
    整理不易,请尊重博主的劳动成果

  • 相关阅读:
    Could A New Linux Base For Tablets/Smartphones Succeed In 2017?
    使用libhybris,glibc和bionic共存时的TLS冲突的问题
    6 Open Source Mobile OS Alternatives To Android in 2018
    Using MultiROM
    GPU drivers are written by the GPU IP vendors and they only provide Android drivers
    Jolla Brings Wayland Atop Android GPU Drivers
    How to Use Libhybris and Android GPU Libraries with Mer (Linux) on the Cubieboard
    闲聊Libhybris
    【ARM-Linux开发】wayland和weston的介绍
    Wayland and X.org problem : Why not following the Android Solution ?
  • 原文地址:https://www.cnblogs.com/Mango-Tree/p/12721185.html
Copyright © 2011-2022 走看看