Java ArrayList笔记

上大学时学过一次 现在深入了解一下

ArrayList

1、什么是ArrayList

ArrayList就是传说中的动态数组,用MSDN中的说法,就是Array的复杂版本,它提供了如下一些好处:

  • 动态的增加和减少元素
  • 实现了ICollection和IList接口
  • 灵活的设置数组的大小

大家知道,数组是静态的,数组被初始化之后,数组长度就不能再改变了。ArrayList是可以动态改变大小的。那么,什么时候使用Array(数组),什么时候使用ArrayList?
答案是:当我们不知道到底有多少个数据元素的时候,就可使用ArrayList;如果知道数据集合有多少个元素,就用数组。

2.ArrayList源码解析

基本参数

   /**默认初始容量*/ 
    private static final int DEFAULT_CAPACITY = 10;  


   /**用于空实例的共享空数组实例*/  
   private static final Object[] EMPTY_ELEMENTDATA = {};   


  /**共享的空数组实例,用于默认大小的空实例。我们将此与EMPTY_ELEMENTDATA区别开来,以了解添加第一个元素时需要添加多少*/  
   private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};   


  /**存储元素的数组,在添加第一个元素时将会扩充至10*/  
   transient Object[] elementData;   


  /**ArrayList内的元素个数*/ 
    private int size;

可以看到ArrayList内定义了两个空数组,和一个值为10的参数(注释表示初始容量,但其实是分情况的),数组长度不一定是等于size的,size是实际元素个数的数量。
1.构造函数
第一种:带初始容量的构造函数

	构造一个具有指定初始容量的空列表 
	initialCapacity  列表的初始容量     
   public ArrayList(int initialCapacity) {     
    if (initialCapacity > 0) {//如果初始容量大于0   
             this.elementData = new Object[initialCapacity];
             //elementData赋值为一个初始容量为initialCapacity的Object数组  
       } else if (initialCapacity == 0) {
       		//如果初始容量等于0         
    		this.elementData = EMPTY_ELEMENTDATA;//elementData赋值为空实例数组     
    } else {      
           throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);     
    }   
  }

第二种:不带参的构造函数

 public ArrayList() {    
     this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
     //赋值为已创建的空数组 以前老版本的JDK是一个大小为10的空数组的,现在修改为空数组
  }

第三种:带集合的构造函数

public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

这里可以看到,不管是哪一种构造函数,在没有设置初始容量的时候,或者设置的初始容量为0的时候,都给数据持有对象elementData赋值为了空数组。
2.add方法
ArrayList无参构造方法默认是一个空数组,其实ArrayList的容量是在调用add方法时初始化的。(以前版本是初始时默认为10,现在改为空数组)

add(E e) 方法
	public boolean add(E e) {
		// 判断是否需要扩容
		ensureCapacityInternal(size + 1);
		// 将新元素追加到相应的数组中
		elementData[size++] = e;
		return true;
	}

ensureCapacityInternal(int minCapacity)方法判断是否需要扩容

// minCapacity = size + 1
private void ensureCapacityInternal(int minCapacity) {
	ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

private static int calculateCapacity(Object[] elementData, int minCapacity) {
	// 如果当前数组为空,则所需容量就是默认容量
	if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
		return Math.max(DEFAULT_CAPACITY, minCapacity);
	}
	return minCapacity;
}

private void ensureExplicitCapacity(int minCapacity) {
	modCount++;

	// 增加元素所需空间不足,则需扩容
	if (minCapacity - elementData.length > 0)
		// 真正的扩容方法
		grow(minCapacity);
}

在grow()方法中,可以看出每次扩容后的长度是当前数组长度的1.5倍,其实质是将原有数组内容复制到一个长度为newCapacity的新数组中。

// 数组默认的最大长度
	private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

	private void grow(int minCapacity) {
		// 旧数组的长度
		int oldCapacity = elementData.length;
		// 扩容为原来的1.5倍
		int newCapacity = oldCapacity + (oldCapacity >> 1);
		// 判断新的数组容量是否达到需求
		if (newCapacity - minCapacity < 0)
			newCapacity = minCapacity;
		// 判断新容量是否溢出
		if (newCapacity - MAX_ARRAY_SIZE > 0)
			newCapacity = hugeCapacity(minCapacity);
		// 调用Arrays.copyOf()方法,将elementData数组复制到一个新数组
		elementData = Arrays.copyOf(elementData, newCapacity);
	}

	private static int hugeCapacity(int minCapacity) {
		if (minCapacity < 0)
			throw new OutOfMemoryError();
		return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
	}

3.add(int index, E element)方法

// 将元素添加到指定位置,扩容机制与add(E e)方法一致。
	public void add(int index, E element) {
		// 判断index是否越界
        rangeCheckForAdd(index);

        // 是否扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!
        /*
        * System.arraycopy()是比较重要的一个步骤,
        * 其实质是将elementData数组中index位置(包含index)以后的数据全部向后移动一个位置,
        * 从而用 elementData[index] = element 将index位置元素替换为待添加元素。
        */
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
	}

4.addAll()方法

// 将指定集合中的元素添加到集合中
    public boolean addAll(Collection<? extends E> c) {
    	// 转数组
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        // 将c中所有元素追加到集合尾部
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }
	
	// 将指定集合中的元素添加到集合中的指定位置
    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)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);

        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }

这里有一个问题 ,如果addall()时,size直接大于当前的容量,那么当前容量扩展,那么就进行扩容乘1.5倍!

在这里插入图片描述

add(E e)方法 添加元素到末尾,平均时间复杂度为O(1)。
add(int index, E element)方法 添加元素到指定位置,平均时间复杂度为O(n)。

get(int index)方法

public E get(int index) {
    // 检查是否越界
    rangeCheck(index);
    // 返回数组index位置的元素
    return elementData(index);
}

private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

E elementData(int index) {
    return (E) elementData[index];
}

检查索引是否越界,这里只检查是否越上界,如果越上界抛出IndexOutOfBoundsException异常,如果越下界抛出的是ArrayIndexOutOfBoundsException异常。

remove(int index)方法



public E remove(int index) {
    // 检查是否越界
    rangeCheck(index);

    modCount++;
    // 获取index位置的元素
    E oldValue = elementData(index);

    // 如果index不是最后一位,则将index之后的元素往前挪一位
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index, numMoved);

    // 将最后一个元素删除,帮助GC
    elementData[--size] = null; // clear to let GC do its work

    // 返回旧值
    return oldValue;
}

删除指定索引位置的元素,时间复杂度为O(n) 可以看到,ArrayList删除元素的时候并没有缩容。

remove(Object o)方法

public boolean remove(Object o) {
    if (o == null) {
        // 遍历整个数组,找到元素第一次出现的位置,并将其快速删除
        for (int index = 0; index < size; index++)
            // 如果要删除的元素为null,则以null进行比较,使用==
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        // 遍历整个数组,找到元素第一次出现的位置,并将其快速删除
        for (int index = 0; index < size; index++)
            // 如果要删除的元素不为null,则进行比较,使用equals()方法
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}

private void fastRemove(int index) {
    // 少了一个越界的检查
    modCount++;
    // 如果index不是最后一位,则将index之后的元素往前挪一位
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index, numMoved);
    // 将最后一个元素删除,帮助GC
    elementData[--size] = null; // clear to let GC do its work
}

删除指定元素值的元素,时间复杂度为O(n)。

(1)ArrayList内部使用数组存储元素,当数组长度不够时进行扩容,每次加一半的空间,ArrayList不 会进行缩容;

(2)ArrayList支持随机访问,通过索引访问元素极快,时间复杂度为O(1);

(3)ArrayList添加元素到尾部极快,平均时间复杂度为O(1);

(4)ArrayList添加元素到中间比较慢,因为要搬移元素,平均时间复杂度为O(n);

(5)ArrayList从尾部删除元素极快,时间复杂度为O(1);

(6)ArrayList从中间删除元素比较慢,因为要搬移元素,平均时间复杂度为O(n);

ArrayList缩容
ArrayList没有自动缩容机制。无论是remove方法还是clear方法,它们都不会改变现有数组elementData的长度。但是它们都会把相应位置的元素设置为null,以便垃圾收集器回收掉不使用的元素,节省内存。ArrayList的缩容,需要我们自己手动去调用trimToSize()方法,达到缩容的目的。
ArrayList的remove只是通过数组部分元素左移、最后一个元素置为null同时size自减来实现删除操作。实际上并未对elementData进行缩容。
可以使用trimToSize()方法对elementData进行缩容。

modCount作用
这个成员变量记录着集合的修改次数,也就每次add或者remove它的值都会加1。在对一个集合对象进行跌代操作的同时,并不限制对集合对象的元素进行操 作,这些操作包括一些可能引起跌代错误的add()或remove()等危险操作。在AbstractList中,使用了一个简单的机制来规避这些风险。 这就是modCount和expectedModCount的作用所在

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java基础知识笔记通常会涵盖以下几个核心主题: 1. Java概述:包括Java的历史、平台无关性、JVM(Java Virtual Machine)的作用和Java的核心API(如java.lang包)。 2. 数据类型和变量:基本数据类型(如int, float, char等)、引用类型(如String, Object),以及变量的声明、初始化和作用域。 3. 控制结构:条件语句(if-else, switch)、循环(for, while, do-while)、异常处理(try-catch-finally)。 4. 类和对象:封装、继承和多态的概念,类的定义、构造函数和析构函数,以及实例化对象和方法调用。 5. 面向对象编程:接口、抽象类和它们的区别,以及设计模式的基本概念。 6. 数组和集合:数组的定义、操作,以及ArrayList、LinkedList、HashMap等常用集合框架的使用。 7. 输入输出流:文件I/O、标准输入输出流(System.in/out)的处理。 8. 方法和函数:静态方法、重载方法、递归等。 9. 异常处理:异常的抛出、捕获和处理机制。 10. IO流和网络编程:Socket编程、HTTP请求等。 创建一个Markdown格式的笔记,可能会包括标题、列表项、代码示例和注释。例如: ```markdown # Java基础笔记 ## 1. Java简介 - Java语言特点: 平台无关性、垃圾回收机制 - JVM: 负责执行Java字节码 ## 2. 数据类型 - int, double, String等基本类型 - ```java // 声明并初始化一个整型变量 int num = 10; ``` ## 3. 控制结构 - if-else: 条件判断 - ```java if (num > 0) { System.out.println("Positive"); } ``` ... ### 附录:常用类库 - java.util: 集合框架 - java.io: I/O流处理 ``` FileInputStream fileIn = new FileInputStream("file.txt"); ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值