Java集合框架:ArrayList

简介: ArrayList定义package java.util;public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, java.

ArrayList定义

package java.util;
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
    private static final int DEFAULT_CAPACITY = 10;
    private static final Object[] EMPTY_ELEMENTDATA = {};
    private transient Object[] elementData;
    private int size;
//其余省略
}

ArrayList概述

  ArrayList以数组实现,允许重复。超出限制时会增加50%的容量(grow()方法中实现,如下所示),每次扩容都底层采用System.arrayCopy()复制到新的数组,因此最好能给出数组大小的预估值。默认第一次插入元素时创建数组的大小为10.

    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);
    }

  按数组下标访问元素—get(i)/set(i,e) 的性能很高,这是数组的基本优势。

    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }
    public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

  直接在数组末尾加入元素—add(e)的性能也高,但如果按下标插入、删除元素—add(i,e), remove(i), remove(e),则要用System.arraycopy()来移动部分受影响的元素,性能就变差了,这是基本劣势。

     public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    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++;
    }
    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;
    }

   ArrayList中有一个方法trimToSize()用来缩小elementData数组的大小,这样可以节约内存:

     public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = Arrays.copyOf(elementData, size);
        }
    }

  考虑这样一种情形,当某个应用需要,一个ArrayList扩容到比如size=10000,之后经过一系列remove操作size=15,在后面的很长一段时间内这个ArrayList的size一直保持在<100以内,那么就造成了很大的空间浪费,这时候建议显式调用一下trimToSize()这个方法,以优化一下内存空间。
  或者在一个ArrayList中的容量已经固定,但是由于之前每次扩容都扩充50%,所以有一定的空间浪费,可以调用trimToSize()消除这些空间上的浪费。
  非线程安全,可以调用Collections.synchronizedList(new ArrayList<>());实现。


ArrayList的使用

  举个简单一点的例子:

        List<Integer> list = new ArrayList<>();
        list.add(4);
        list.add(2);
        list.add(3);
        list.add(5);
        for(int i:list)
        {
            System.out.println(i);
        }
        System.out.println(list);

  运行结果:

4
2
3
5
[4, 2, 3, 5]

  可以发现ArrayList是按插入顺序存储的,这也不奇怪,每次插入是在elementData[size++]处插入。


subList的使用

        List<Integer> list = new ArrayList<>();
        list.add(4);
        list.add(2);
        list.add(3);
        list.add(5);
        list.add(7);
        list.add(5);
        list.add(11);
        list.add(14);
        list.add(10);
        list.add(9);
        System.out.println(list);
        List<Integer> list2 = list.subList(3, 6);
        System.out.println(list2);
        list2.set(2, 50);

        System.out.println("============");
        System.out.println(list);
        System.out.println(list2);

  运行结果:

[4, 2, 3, 5, 7, 5, 11, 14, 10, 9]
[5, 7, 5]
============
[4, 2, 3, 5, 7, 50, 11, 14, 10, 9]
[5, 7, 50]

  调用ArrayList中的subList方法生成的新的list,内部引用的还是原来的数组elementData,如果改变subList中的值,主list中的值也会跟着改变。


RandomAccess?!

  但是,上面的程序有不合理之处!你们可能感到费解,请听我一一道来。
  上面一段程序可以通过反编译看到foreach语法糖经过编译器处理成了Iterator的遍历,有关foreach语法糖的细节可以参考《Java语法糖之foreach》。由于上面程序反编译出来有100+行,太多了就不罗列了。只要知道一个事实就可以:foreach对于集合框架,编译解析成的是Iterator的遍历。
  那么这又有什么不妥?集合框架印象中不就是用迭代器遍历的嚒?
  注意ArrayList的特殊之处在于它implements了RandomAccess这个接口,在JDK中RandomAccess明确说明:

    It is recognized that the distinction between random and sequential access is often fuzzy. For example, some List implementations provide asymptotically linear access times if they get huge, but constant access times in practice. Such a List implementation should generally implement this interface. As a rule of thumb, a List implementation should implement this interface if, for typical instances of the class, this loop:
     for (int i=0, n=list.size(); i < n; i++)
         list.get(i);

runs faster than this loop:
     for (Iterator i=list.iterator(); i.hasNext(); )
         i.next();

  这段英文主要说明的是实现了RandomAccess接口的集合框架,采用迭代器遍历比较慢,不推荐。
  实现RandomAccess接口的集合有:ArrayList, AttributeList, CopyOnWriteArrayList, RoleList, RoleUnresolvedList, Stack, Vector等。
  所以上面的例子中的遍历应该这么写:

    int length = list.size();
        for(int i=0;i<length;i++)
        {
            System.out.println(list.get(i));
        }

  其实也可以加个判断,让程序通用起来:

     if (list instanceof RandomAccess)
        {
            for (int i = 0; i < list.size(); i++)
            {
            }
        }
        else
        {
            Iterator<?> iterator = list.iterator();
            while (iterator.hasNext())
            {
                iterator.next();
            }
        }

  因此,博主个人觉得JDK(jdk7)中有这么一段不妥之处(希望大神不要喷我):ArrayList的toString()方法是在AbstractCollection中实现的:

    public String toString() {
        Iterator<E> it = iterator();
        if (! it.hasNext())
            return "[]";

        StringBuilder sb = new StringBuilder();
        sb.append('[');
        for (;;) {
            E e = it.next();
            sb.append(e == this ? "(this Collection)" : e);
            if (! it.hasNext())
                return sb.append(']').toString();
            sb.append(',').append(' ');
        }
    }

  这里没有判断RandomAccess,直接采用的是迭代器的遍历,影响了一些性能。


ArrayList和LinkedList的区别

  1. ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
  2. 对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
  3. 对于新增和删除操作add和remove,LinkedList比较占优势,因为ArrayList要移动数据。

ArrayList和Vector的区别

  1. Vector和ArrayList几乎是完全相同的,唯一的区别在于Vector是同步类(synchronized),属于强同步类。因此开销就比ArrayList要大,访问要慢。正常情况下,大多数的Java程序员使用ArrayList而不是Vector,因为同步完全可以由程序员自己来控制。
  2. Vector每次扩容请求其大小的2倍空间,而ArrayList是1.5倍。
  3. Vector还有一个子类Stack.

参考资料:
1. 《Java语法糖之foreach

目录
相关文章
|
3月前
|
Java 大数据 API
Java Stream API:现代集合处理与函数式编程
Java Stream API:现代集合处理与函数式编程
255 100
|
3月前
|
Java API 数据处理
Java Stream API:现代集合处理新方式
Java Stream API:现代集合处理新方式
286 101
|
2月前
|
安全 前端开发 Java
《深入理解Spring》:现代Java开发的核心框架
Spring自2003年诞生以来,已成为Java企业级开发的基石,凭借IoC、AOP、声明式编程等核心特性,极大简化了开发复杂度。本系列将深入解析Spring框架核心原理及Spring Boot、Cloud、Security等生态组件,助力开发者构建高效、可扩展的应用体系。(238字)
|
3月前
|
人工智能 Java 开发者
阿里出手!Java 开发者狂喜!开源 AI Agent 框架 JManus 来了,初次见面就心动~
JManus是阿里开源的Java版OpenManus,基于Spring AI Alibaba框架,助力Java开发者便捷应用AI技术。支持多Agent框架、网页配置、MCP协议及PLAN-ACT模式,可集成多模型,适配阿里云百炼平台与本地ollama。提供Docker与源码部署方式,具备无限上下文处理能力,适用于复杂AI场景。当前仍在完善模型配置等功能,欢迎参与开源共建。
1554 58
阿里出手!Java 开发者狂喜!开源 AI Agent 框架 JManus 来了,初次见面就心动~
|
3月前
|
算法 Java
50道java集合面试题
50道 java 集合面试题
|
2月前
|
存储 Java 索引
用Java语言实现一个自定义的ArrayList类
自定义MyArrayList类模拟Java ArrayList核心功能,支持泛型、动态扩容(1.5倍)、增删改查及越界检查,底层用Object数组实现,适合学习动态数组原理。
110 4
|
2月前
|
存储 安全 Java
《数据之美》:Java集合框架全景解析
Java集合框架是数据管理的核心工具,涵盖List、Set、Map等体系,提供丰富接口与实现类,支持高效的数据操作与算法处理。
|
2月前
|
消息中间件 缓存 Java
Spring框架优化:提高Java应用的性能与适应性
以上方法均旨在综合考虑Java Spring 应该程序设计原则, 数据库交互, 编码实践和系统架构布局等多角度因素, 旨在达到高效稳定运转目标同时也易于未来扩展.
135 8
|
2月前
|
存储 算法 安全
Java集合框架:理解类型多样性与限制
总之,在 Java 题材中正确地应对多样化与约束条件要求开发人员深入理解面向对象原则、范式编程思想以及JVM工作机理等核心知识点。通过精心设计与周密规划能够有效地利用 Java 高级特征打造出既健壮又灵活易维护系统软件产品。
99 7
|
3月前
|
存储 Java Go
对比Java学习Go——函数、集合和OOP
Go语言的函数支持声明与调用,具备多返回值、命名返回值等特性,结合`func`关键字与类型后置语法,使函数定义简洁直观。函数可作为一等公民传递、赋值或作为参数,支持匿名函数与闭包。Go通过组合与接口实现面向对象编程,结构体定义数据,方法定义行为,接口实现多态,体现了Go语言的简洁与高效设计。