zoukankan      html  css  js  c++  java
  • ArrayList常用方法

    基于JDK 1.8.0.211
    个人观点,水平低下,谨慎阅读

    要阅读ArrayList源码,我这里打算先看一遍正常的使用流程,然后再去分析一下别的函数
    此外,先看一眼继承树

    首先是这句话

     ArrayList<Integer> list = new ArrayList<>();
    

    进入构造函数之后,你会发现构造函数出奇的简单

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    

    其中elementData 和 DEFAULTCAPACITY_EMPTY_ELEMENTDATA都是在ArrayList类中定义的

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    transient Object[] elementData;
    

    据我观察,ArrayList类中没有静态代码块,而且DEFAULTCAPACITY_EMPTY_ELEMENTDATA 已经被final修饰,所以这句话显然是说,将elementData赋值为一个空的Object数组
    根据参数的命名和这里的操作,我们可以猜测,elementData就是ArrayList中存储数据的地方

    看到这里,相信你一定和我一样,有两个疑问。
    1.为什么elementData使用transient 修饰
    众所周知,这个修饰符使得变量不能被序列化,也就是说难道ArrayList不能将数据序列化吗?
    2.为什么这两个成员变量都没有使用泛型?
    那我指定的类型信息在哪里被使用了呢?

    目前,我们只能继续向下阅读了
    让我们执行这句话

    list.add(45);
    

    add方法也很简洁

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    

    第一个函数是ensureCapacityInternal,字面意思是确认内部空间足够
    没有返回任何值,那么基本可以确认所谓的动态扩容就在这在函数内进行了
    其后,在elementData中加入了我们传入的参数
    这里参数用到了泛型,于是编译器便会检测传入的数据类型是否正确(难道这就是泛型的作用吗?)
    size可以猜到,就是在ArrayList的数据个数,而且可以猜到size()函数要怎么写了
    返回ture表示插入成功,从我现在的知识看,这个返回是没有意义的,于是猜测是版本遗留问题

    我们进入ensureCapacityInternal看一下先

    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    

    刚刚传入的参数是size+1,就是如果还要插入一个元素,需要的最小空间是多少
    这里有一个函数套函数,先看内层函数calculateCapacity
    看函数名意思是计算容量

    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
             return Math.max(DEFAULT_CAPACITY, minCapacity);
         }
         return minCapacity;
    }
    

    这里的意思是,如果是第一次添加元素(别忘了DEFAULTCAPACITY_EMPTY_ELEMENTDATA的声明哦),就返回传入参数与默认参数的最大值
    既然这里取了最大值,那就说明有可能一次添加多个元素哦!

    private static final int DEFAULT_CAPACITY = 10;
    

    现在可以回头来看外层函数ensureExplicitCapacity了,字面意思确认明确容量
    传入的参数是指需要的容量

    private void ensureExplicitCapacity(int minCapacity) {
            modCount++;
            // overflow-conscious code
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
    }
    

    首先,modCount是一个什么东西呢,我们可以发现,源码中有注释
    大致意思是说,这个变量用来记录数据被修改了多少次,如果多线程中有迭代器访问ArrayList的同时,有一个另外线程修改了数据,可以快速通过它抛出异常

    然后我们来看grow函数

    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
             newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
             newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    

    废话不多说,新的容量被设定为旧容量的1.5倍,要是还不够,那需要多少就给多少
    如果新的容量已经比MAX_ARRAY_SIZE (Integer.MAX_VALUE -8)还要大了,那就看设定为Integer.MAX_VALUE还是MAX_ARRAY_SIZE
    如果新容量是负数(溢出),则抛出异常

    到这里容量便扩展完成

    以上未免说得太仔细了,我没有时间这么干了,接下来我们速推,加上中文注释应该很好理解了
    方法:

    public void add(int index, E element) {
        //判断是否越界
        rangeCheckForAdd(index);
    
        //更新容量
        ensureCapacityInternal(size + 1);  // Increments modCount!!
    
    
        //本地方法,把所有index后面的数据往后挪一格
        System.arraycopy(elementData, index, elementData, index + 1,
                size - index);
        //赋值
        elementData[index] = element;
        size++;
    }
    
    //添加另外一个集合的所有元素
    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        //这里就是刚刚说的一次增加多个
        ensureCapacityInternal(size + numNew);  // Increments modCount
        //将a中的元素赋值给size
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }
    
    //添加另外一个集合的所有元素,不过这次不是插在最后,而是在index位置
    public boolean addAll(int index, Collection<? extends E> c) {
    
        //判断是否越界
        rangeCheckForAdd(index);
    
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
    
        int numMoved = size - index;
        if (numMoved > 0)
            //将原有index(包括)后面的元素挪动numMoved个单位
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);
    
        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        //为什么在这里返回呢,早一点返回不是更优吗
        //难道什么都不加,也要通知modCount吗?
        return numNew != 0;
    }
    
    public void clear() {
        modCount++;
    
        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;
    
        //注意,这里并没有改变elementData.length
        //所以并不会优化内存
        //看一眼ensureExplicitCapacity()方法,你就知道,在minCapacity>elementData.length时,grow()不会被执行
        size = 0;
    }
    

    注意,ArrayList实现了Cloneable接口

    public Object clone() {
        try {
            //为什么要用通配符呢?直接写上E不好吗
            ArrayList<?> v = (ArrayList<?>) super.clone();
            //复制数组
            //请注意这里的方法 Arrays.copyOf()也是一个浅拷贝方法
            //如果随后修改了其中的某个值,也会改变当前ArrayList的值
            //但是我们目前为止还没有遇到过修改ArrayList中值的方法
            //光是引用位置的变化(add等)不会影响原来ArrayList中的值
            v.elementData = Arrays.copyOf(elementData, size);
            //定义在父类中的modCount
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }
    
    //寻找o的下标
    //无脑遍历
    //而且可以看出来只返回第一个o的下标
    public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }
    
    //判断o是否存在,因为如果没找到,indexof会返回-1
    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }
    

    containsAll定义在父类AbstractCollection中,判断当前ArrayList中是否包含了参数中的所有对象
    注意AbstractCollection也定义了contains方法,与ArrayList重写的方法,唯一的不同就是,AbstractCollection使用了迭代器,而不是for

    //无脑遍历
    public boolean containsAll(Collection<?> c) {
        for (Object e : c)
            if (!contains(e))
                return false;
        return true;
    }
    

    get这个方法与clone方法结合在一起看非常危险
    试验了一下,在clone中的ArrayList中修改这个元素信息,果然影响了原来的ArrayList元素

    @SuppressWarnings("unchecked")
    E elementData(int index) {
        return (E) elementData[index];
    }
    public E get(int index) {
        rangeCheck(index);
        return elementData(index);
    }
    

    由此可见
    Arraylist的clone方法是比较危险的

    私有方法,没有返回值,没有检查边界

    private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }
    

    定义在接口迭代器里的默认方法,这里是重写的代码

    @Override
    //消费型函数试接口,或许这就是JDK8的新特性吧
    public void forEach(Consumer<? super E> action) {
        //要求action不等于null,否则抛出异常
        Objects.requireNonNull(action);
        //看呐,modCount开始发挥作用了
        final int expectedModCount = modCount;
        @SuppressWarnings("unchecked")
        final E[] elementData = (E[]) this.elementData;
        final int size = this.size;
        //在遍历途中观察有没有其他线程修改ArrayList,一旦有立刻跳出循环
        for (int i=0; modCount == expectedModCount && i < size; i++) {
            //accept是消费型函数式接口的唯一一个方法,不清楚的可以先了解一下函数式编程哦
            action.accept(elementData[i]);
        }
        //在这里抛出异常
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }
    

    一句话函数

    public boolean isEmpty() {
        return size == 0;
    }
    

    返回一个迭代器
    注意迭代器是内部类,实现了Iterator接口
    内部类我们稍后聊

    public Iterator<E> iterator() {
        return new Itr();
    }
    

    这个也一样,不过ListItr是Itr的子类

    public ListIterator<E> listIterator() {
        return new ListItr(0);
    }
    

    倒着走的indexof,节约篇幅,不贴代码了

    public int lastIndexOf(Object o) 
    

    移除某个位置的元素
    也就是删一个挪一堆
    注意,elementData.length并没有因此改变
    感觉不用贴代码了吧(越来越懒了)

    public E remove(int index);
    //只删第一个,其实就是先找到第一次出现的位置,在调用私有方法fastRemove(index)将其删除
    public boolean remove(Object o);
    //这个方法调用了下面的batchRemove那个方法
    public boolean removeAll(Collection<?> c) ;
    

    删除一部分

    private boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData;
        int r = 0, w = 0;
        boolean modified = false;
        try {
            for (; r < size; r++)
                //如果当前这个元素不在c中(RemoveAll传进来的是false,这里就此分析)
                //就将其直接复制到数组开头
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
            // c.contains()搞不好会抛出异常
            if (r != size) {
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }
            
            if (w != size) {
                // clear to let GC do its work
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                //如果modCount只是用在了判断有没有被修改过,那么++不就好了吗?
                modCount += size - w;
                size = w;
                modified = true;
            }
        }
        return modified;
    }
    

    果然,retainall就是刚好和removeall相反,调用batchRemove的时候传入了true

    将ArrayList变成数组。和clone一样,浅复制

    public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }
    

    这就是大部分常用函数了
    值得注意的是,ArrayList中有几个内部类,他们有的是迭代器,在这里不多说,还有一个是sublist,返回一个ArrayList的子list,其实就是ArrayList的一段映射,但是只实现了List接口内的相关方法

  • 相关阅读:
    【JAVA】【集合12】Java中的TreeMap
    【JAVA】【集合11】Java中的HashMap
    【JAVA】【集合10】Java中的Map接口
    main.c:24:22: fatal error: pk11func.h: No such file or directory
    C语言——随笔の想啥说啥(待续)
    大华摄像头报警接口中图片加密,python调用c++方式实现解密
    win10 下安卓源码同步小技巧
    repo sync error: .repo/manifests/: contains uncommitted changes
    Pycharm连接远程Linux服务器的虚拟环境
    利用 Django 动态展示 Pyecharts 图表数据的几种方法
  • 原文地址:https://www.cnblogs.com/ZGQblogs/p/12455956.html
Copyright © 2011-2022 走看看