前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java容器 ArrayList

Java容器 ArrayList

原创
作者头像
zeody
修改2019-05-16 09:46:39
5860
修改2019-05-16 09:46:39
举报
文章被收录于专栏:Java杂货店Java杂货店

ArrayList 数组容器类,实现了List,RandomAccess,Cloneable,Serializable四个接口,继承自AbstractList,具有动态扩容,随机访问,可复制,可序列化四个主要功能。

源码分析

核心

之前文章分析过这个接口所需要实现的方法,ArrayList 实现的核心是两个属性

代码语言:txt
复制
transient Object[] elementData; // 元素载体
private int size; //容器存放的元素大小

elementData 数组用于存放容器,transient 表明容器内的元素不能被序列化。这时候有些同学就表示疑问了,类声明的时候明明是实现了Serializable接口的,表明ArrayList是能够序列化的,此处为什么又要使用transient关键字呢?ArrayList到底能不能被序列化呢?

这里先说结论 ArrayList 是能被序列化的,有兴趣的同学可以做个实验,后面在回顾基础的时候会专门对序列化进行分析。

扩容

ArrayList 有三个构造函数

代码语言:txt
复制
ArrayList(int initialCapacity)  //指明容器大小
ArrayList() // 默认容器初始化大小
ArrayList(Collection<? extends E> c) // 通过Collection构建容器

这里讲一下第二个构造函数,这个函数只做了一件事,赋空数组。有很多资料说ArrayList 构造函数如果不指定大小,默认是10,这种说法是不严谨的。默认初始化后的大小其实是0,第一次扩容大小为10。

扩容主要发生在Add操作时,每次add 都会检查容量是否够放入新的元素(ensureCapacityInternal方法),如果不够,比较得出一个逻辑最小预期容量(calculateCapacity方法),然后将容量扩至原来的1.5倍(grow方法),如果还是不能满足需求,则将容量扩充至预期容量。

代码语言:txt
复制
    //入口方法,每次add时先检查 size+1
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            //如果是默认空数组,返回默认容量和minCapacity中较大的值
            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) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //尝试扩容至原来的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            //如果1.5倍还不够,则扩容至预期容量
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            //如果预期容量比最大数组容量还大
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

增加的方法有很多,这里重点讲一下增加方法中带有下标的方法,比如add(int index, E element)

代码语言:txt
复制
    public void add(int index, E element) {
        rangeCheckForAdd(index);
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }
    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

其中要注意的是rangeCheckForAdd(index)检查,插入的下标不能大于数组已有元素大小,否则会报数组越界异常。

删除有两种主要的删除方式,remove(int index)根据下标删除,remove(Object o) 根据元素删除。

代码语言:txt
复制
    public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        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

        return oldValue;
    }
    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

删除主要是通过遍历找到元素,然后通过平移覆盖元素,最后将尾部元素置为null,等待垃圾回收。这其中有一点需要注意,如果你的元素是int类型,在删除的时候一定要小心。很多人只知道remove(Object o)一种删除,想当然的以为remove(12) 会删除元素为12的元素,其实真正执行的是删除index为12的元素,这是就会参数数组越界问题。

List提供了get(index)方法让我们更快的查询数组中元素,我们平时用的最多的是这样的语句:

代码语言:txt
复制
for(String s: arrays){
}

这是1.5之后提供的foreach语法,他其实是一种语法糖,通过idea工具反编译class文件(或者javap -verbose命令)

代码语言:txt
复制
        List<Integer> list = Arrays.asList(Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3), Integer.valueOf(4), Integer.valueOf(55));
        Iterator var2 = list.iterator();

        while(var2.hasNext()) {
            Integer a = (Integer)var2.next();
            System.out.println(a);
        }

可以看到它经过编译器翻译后,底层使用的是iterator迭代器原理。既然使用的是迭代器原理,首先想到便是Fast-Fail机制。也就是说你不能在循环中删除元素,否则会报ConcurrentModificationException异常。

iterator

arrayList内部类Itr 实现了Iterator接口,这个接口主要有三个方法

  • boolean hasNext() //是否还有下一个元素
  • E next() // 获取下一个元素
  • E remove() //删除当前元素
代码语言:txt
复制
private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            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();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

可以看到Iterator通过modCount 和 expectedModCount 来实现 FastFail机制,每次对arraylist的操作都会修改modCount,如果在迭代的过程中ArrayList被修改就会触发快速失败。如果单线程对ArrayList进行删除,可以使用Iterator.remove() 方法,一般不建议在循环的时候删除元素。

ListIterator

Iterator的拓展接口,提供了双向遍历的能力。一般的Iterator是从前向后遍历,ListIterator功能更加全面一点。

subList

ArrayList中有一个方法

代码语言:txt
复制
    public List<E> subList(int fromIndex, int toIndex) {
        subListRangeCheck(fromIndex, toIndex, size);
        return new SubList(this, 0, fromIndex, toIndex);
    }

返回的是指定区间的子列表,SubList是一个内部类,举一个简单的内部方法

代码语言:txt
复制
        public E set(int index, E e) {
            rangeCheck(index);
            checkForComodification();
            E oldValue = ArrayList.this.elementData(offset + index);
            ArrayList.this.elementData[offset + index] = e;
            return oldValue;
        }

虽然他是new出来的对象,但可以看到他操作的是原列表的值,相当于原列表的一个视图,所以使用的时候要慎重。

使用规范

阿里的Java操作手册中有如下几点强制规范(这里引用一下):

  • 【强制】ArrayList的subList结果不可强转成ArrayList。
  • 【强制】在 subList 场景中,高度注意对原集合元素的增加或删除,均会导致子列表的遍历、 增加、删除产生 ConcurrentModificationException 异常。
  • 【强制】使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全 一样的数组,大小就是 list.size()。
  • 【强制】使用工具类 Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法。
  • 【推荐】集合初始化时,指定集合初始值大小。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 源码分析
    • 核心
      • 扩容
              • iterator
                • ListIterator
                  • subList
                  • 使用规范
                  相关产品与服务
                  文件存储
                  文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档