zl程序教程

您现在的位置是:首页 >  Java

当前栏目

Java集合框架(一)-ArrayList

2023-03-31 11:05:09 时间

大佬理解->Java集合之LinkedList

1、ArrayList的特点

存放的元素有序
元素不唯一(可以重复)
随机访问
插入删除元素
非线程安全

2、底层实现

底层初始化,使用一个Object类型的空对象数组,初始长度为0;

源码

//Object类型对象数组引用
transient Object[] elementData;
//默认空的Object数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//实例化时,将Object类型对象数组引用 指向 默认空的Object数组
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

首次添加元素,自动进行扩容,默认扩充容量是10(数组的长度,也就是集合存放元素的个数);

源码

//如果是第一次添加元素
public boolean add(E e) {
    //private int size; //size = 0;
    //调用ensureCapacityInternal(int minCapacity)方法
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

//minCapacity = 1;
private void ensureCapacityInternal(int minCapacity) {
    //调用calculateCapacity(Object[] elementData, int minCapacity)方法
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    //判断是不是默认空的Object数组
    //如果是进入选择一个数组容量
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        //private static final int DEFAULT_CAPACITY = 10;
        //minCapacity = 1;
        //所以第一次添加元素时,自动进行扩容,默认扩充容量是10
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

3、扩容

  • 当前一次扩容的数组容量不足时(放满10个元素,再想添加一个元素,容量不足),开始进行动态扩容;
  • 每次扩容,是之前一次扩容后的数组容量的1.5倍(即:每次都在前一次数组容量的基础上,增加一半-右移1位);
  • 最大容量Integer.MAX_VALUE - 8,即2^31-8;
//扩容方法
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) //如果新数组的容量大于最大值,将数组的容量设置为Integer.MAX_VALUE - 8
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}

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

4、ArrayList初始化

基于多态创建ArrayList集合对象

List<Object> list = new ArrayList<>(); // 推荐
Collection collection = new ArrayList();
ArrayList arrayList = new ArrayList();
List<Integer> intList = new ArrayList<>(); //可以使用泛型,指定存放数据的类型

5、常用方法

方法 说明
add(Object obj) 添加元素
add(int index, E element) 指定下标添加元素
remove(int index) 移除指定 下标
remove(Object o) 移除指定 元素
get(int index)) 获取元素
size() 集合元素个数
contains(Object o) 是否包含某元素
isEmpty() 集合是否为空

5.1 add(Object obj)

//添加元素方法:add(Object obj),每次添加元素都是自动添加到数组的末尾,元素下标值从0开始,跟数组一致;
//可以添加重复值;
//可以添加null值;

5.2 add(int index, E element)

//指定下标添加元素和删除元素,执行效率比较低;

5.3 remove(int index)

// 先检查该索引是否合法,不合法会抛出索引越界异常;
//再把要删除的元素拿出来作为返回值,期间会计算要删除的元素后面还有多少个元素,再调用System.arraycopy(elementData, index+1, elementData, index, numMoved);移动数组元素,这个方法是一个本地方法,将一个数组从指定位置复制到另外一个数组的指定位置。
//然后让最后一个元素变成null,将数组大小减一,不置为null会存在引用,无法被GC回收,造成内存泄漏。

remove方法参口原博客

源码分析

//删除指定索引位置元素
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);
    //让最后一个元素变成null
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}

5.4 remove(Object o)

//删除指定元素和上一个方法类似,主要是这个需要先找到指定元素的索引位置,找不到直接返回false,找到再调用内部的删除方法fastRemove(int index)来删除;

源码分析

//删除指定元素
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;
}

//内部的删除方法
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
}

注意:使用remove(Object o)移除元素的时候,如果存在多个元素值一样,只会移除一个;

特殊案例1,使用for循环遍历删除元素:

@Test
public  void testRemoveObject(){
    List nums = new ArrayList();
    nums.add(1);
    nums.add(1);
    nums.add(1);

     for (int i = 0; i < nums.size(); i++) {
         if(nums.get(i).equals(1)){
             nums.remove(i);
             System.out.println("删除第"+i+"个1");
             //第一个1被删除
             //第二个1,由于第一个1删除后,往前移动了,所以第二次遍历的是第三个1
         }
     }
    System.out.println("删除后的集合:"+nums);
   /*
   	删除第0个1
    删除第1个1
    删除后的集合:[1]  删除还有遗漏
   */
}

特殊案例2使用 遍历删除元素:

 @Test
public  void testRemoveObject2(){
    List nums = new ArrayList();
    nums.add(1);
    nums.add(1);
    nums.add(1);

    Iterator iterator = nums.iterator();
    int i = 0;
    while (iterator.hasNext()){
        if(iterator.next().equals(new Integer(1))){
            iterator.remove();
            System.out.println("删除第"+i+"个1"); i++;
        }
    }
    System.out.println("删除后的集合:"+nums);
    /*
        删除第0个1
        删除第1个1
        删除第2个1
        删除后的集合:[]  删除成功
    */
}

5.5get(int index))

// 获取元素方法:get(下标值),只能通过下标取值;
//当访问下标值超出了集合元素的最大下标值,报下标越界异常:java.lang.IndexOutOfBoundsException
// 可用的下标值的范围:最小值是0,最大值是集合元素的个数 - 1

5.6 size()

// 获取集合中元素个数方法:size();

5.7 contains(Object o)

// 判断list集合中,是否包含某个元素方法:contains(查找元素值),返回true,代表存在,返回false,代表不存在;

5.8 isEmpty()

// 判断list集合是否为空方法:isEmpty(),返回true代表没有元素,空的,返回false,代表有元素,不是空的
// 底层就是通过集合中元素个数size == 0 判断,所以也可以使用size() == 0判断集合非空

源码

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

5.9 clear()

//清空list集合方法:clear(),清除集合中的所有元素

源码

ublic void clear() {
    modCount++;

    // clear to let GC do its work
    for (int i = 0; i < size; i++) //一次将数组赋值为null;
    elementData[i] = null;

    size = 0; //设置数组长度为0;
}

5.10 toArray()

// list集合一步转换为数组方法:toArray(),返回的是Object类型数组

6、数组转换成集合

Arrays.asList(目标数组)

String[] strArrays = {"奥迪", "奔驰", "宝马"};
List<String> strList1 = Arrays.asList(strArrays);
System.out.println(strList1); //[奥迪, 奔驰, 宝马]

7、遍历

List<String> strList = new ArrayList<>();
strList.add("Audi");
strList.add("Benz");
strList.add("Bmw");
strList.add("Audi");

//for循环
for (int i = 0; i < strList.size(); i++) {
    System.out.println("汽车品牌:" + strList.get(i));
}

//迭代器
//Iterator迭代器,只能通过集合获取,不可以重复使用,迭代结束,迭代器就失效,如果想再次使用,需要重新获取
Iterator<String> iterator = strList.iterator();
// 迭代器遍历,使用while,不知道其中元素个数
while(iterator.hasNext()){
    System.out.println("汽车品牌:" + iterator.next());
}

运行结果:

汽车品牌:Audi
汽车品牌:Benz
汽车品牌:Bmw
汽车品牌:Audi

8、Vector(线程安全)

  • Vector,底层数据结构是和ArrayList一致的,都是对象数组,但是它的操作是线程安全的,每个方法都带有synchronized同步;
  • 默认初始容量是10,可以自定义,但是不能小于0,默认每次扩容是前一次容量的一倍,扩容的数量也是可以指定的,如果指定,每次都是在前一次基础上扩容指定的数量