Java集合类之ArrayList

概述

在这里插入图片描述
图片来自菜鸟教程
由图可知,Java集合框架主要由两部分组成,一个是集合Collectin,另一个是图Map,Collection接口有三个子接口,这三个子接口下面是一些实现的抽象类,再往下就是一些实现,之所以定义这么多的接口就是为了适应不同类型的操作。

1、Collection接口

查看Collection接口源码,其中定义了常用的add(),get(),remove(),retainAll()(取交集)等方法。

  • Collection接口定义了sort()排序函数,其实现是调用Arrays.sort()实现的。
  • Collection的indexOf和lastIndexOf查找第一次和最后一次出现指定元素的下标,从0开始。
2、深拷贝与浅拷贝

RandomAccess接口: 是一个标记接口,根据是否实现了这个接口采用不同的处理方式,当一个List拥有快速访问功能时,其遍历方法采用for循环最快速。而没有快速访问功能的List,遍历的时候采用Iterator迭代器最快速。

实现了Cloneable接口也是一个标记接口,在类中重写Object中的clone方法,然后通过类调用clone方法才能克隆成功,如果不实现这个接口,则会抛出CloneNotSupportedException(克隆不被支持)异常。

深拷贝与浅拷贝

  • ①、浅拷贝
    浅拷贝简单实现如下:
public class Person implements Cloneable{
    public String name;
    public Integer age;

    public Address address;

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
        this.address = new Address();
    }

    public Person() {
    }

    public void setAddress(String province, String city) {
        address.setAddress(province,city);
    }


    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    public String print() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", address=" + address +
                '}';
    }
}

public class Address {
    public String province;
    public String city;


    public void setAddress(String province,String city) {
        this.province = province;
        this.city = city;
    }

    @Override
    public String toString() {
        return "Address{" +
                "province='" + province + '\'' +
                ", city='" + city + '\'' +
                '}';
    }
}
public class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person p1 = new Person("admin",21);
        p1.setAddress("江苏省","苏州市");

        System.out.println("p1: " + p1 + " " + p1.print());

        Person p2 = (Person) p1.clone();
        System.out.println("p2: " + p2 + " " + p2.print());

        p2.setAddress("江苏省","南京市");
        System.out.println("p1: " + p1 + " " + p1.print());
        System.out.println("p2: " + p2 + " " + p2.print());
    }
}

/**
p1: com.penghui.collections.copy.Person@4554617c Person{name='admin', age=21, address=Address{province='江苏省', city='苏州市'}}
p2: com.penghui.collections.copy.Person@74a14482 Person{name='admin', age=21, address=Address{province='江苏省', city='苏州市'}}
p1: com.penghui.collections.copy.Person@4554617c Person{name='admin', age=21, address=Address{province='江苏省', city='南京市'}}
p2: com.penghui.collections.copy.Person@74a14482 Person{name='admin', age=21, address=Address{province='江苏省', city='南京市'}}
*/

在这里插入图片描述
根据结果可以发现,Person中的address经过clone之后只是复制了address的引用,内存地址还是指向之前的内存空间,当其中一个修改address值,另一个都会跟着变化。

创建一个新对象,然后将当前对象的非静态字段复制到该新对象,如果字段是值类型的,那么对该字段执行复制;如果该字段是引用类型的话,则复制引用但不复制引用的对象。因此,原始对象及其副本引用同一个对象。

  • ②、深拷贝
    深拷贝实现在Person中将clone实现修改为如下:
	@Override
    protected Object clone() throws CloneNotSupportedException {
        Person p = (Person) super.clone();
        p.address = (Address) address.clone();
        return p;
    }

并让Address类实现Cloneable接口,重写clone方法
同样的测试结果如下:

p1: com.penghui.collections.copy.Person@4554617c Person{name='admin', age=21, address=Address{province='江苏省', city='苏州市'}}
p2: com.penghui.collections.copy.Person@74a14482 Person{name='admin', age=21, address=Address{province='江苏省', city='苏州市'}}
p1: com.penghui.collections.copy.Person@4554617c Person{name='admin', age=21, address=Address{province='江苏省', city='苏州市'}}
p2: com.penghui.collections.copy.Person@74a14482 Person{name='admin', age=21, address=Address{province='江苏省', city='南京市'}}

在这里插入图片描述
也就是说所有属性都会独立拷贝一份,当修改其中一个对象时另一个不会受到影响。

另一种实现深拷贝的方式时是使用序列化,将Person和Address类都实现serializable接口,在Person添加如下方法:

public Object deepClone() throws Exception{
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);

        oos.writeObject(this);

        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ooi = new ObjectInputStream(bis);

        return ooi.readObject();
    }

采用Person p2 = (Person) p1.deepClone();调用即可。
参考文章:
Java的深拷贝和浅拷贝

3、ArrayList
3.1、底层数据结构

源码中有这样一个成员变量:

transient Object[] elementData;

这就是ArrayList的底层数据结构,至于为什么要使用transient修饰,原因在于序列化和反序列化。
transient用来表示一个域不是该对象序行化的一部分,当一个对象被序行化的时候,transient修饰的变量的值是不包括在序行化的表示中的。
对于ArrayList来说,序列化的时候会调用自身重写的writeObject()方法,将size和element写入ObjectOutputStream,反序列化的时候调用readObject()方法,从ObjectInputStream获取size和element,再恢复到elementData中。
不直接将elementData序列化的原因在于elementData是一个缓存数组,它通常会预留一些容量,等容量不足时再扩充容量,那么有些空间可能就没有实际存储元素,采用上诉的方式来实现序列化时,就可以保证只序列化实际存储的那些元素,而不是整个数组,从而节省空间和时间。

3.2、构造函数

构造函数有三个,无参、指定容量、包含指定集合这三个。无参构造函数在初始化的时候长度为0,只有在真正存数据的时候才会将长度置为10。

3.3、核心方法

①、add方法

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

具体流程:

  • 首先检查长度是否够用调用ensureCapacityInternal()方法,因为添加元素长度要增长,所以+1;
  • ensureCapacityInternal()实现如下:
    private void ensureCapacityInternal(int minCapacity) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
            }
            ensureExplicitCapacity(minCapacity);
        }
    
    第一次存值会将minCapacity置为10,之后会调用ensureExplicitCapacity()方法;
  • ensureExplicitCapacity()方法
    private void ensureExplicitCapacity(int minCapacity) {
            modCount++;
    
            // overflow-conscious code
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }
    
    如果minCapacity长度大于elementData长度,则调用grow()函数扩容;
  • 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倍

②、add(int index,E element) 在指定位置添加元素

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

这个操作可能会涉及到原数组整体或部分元素的移动,效率不高,数组移动调用的是System.arraycopy()方法,

//src为原数组,srcPos从原数组复制的起始位置,dest目标数组,destPos复制到目标数组起始位置,length要复制的原数组中元素的数量
public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

③、batchRemove(Collection<?> c, boolean complement),用于removeAll()和retainAll()方法

//删除或者保留指定集合中的元素
//用于两个方法,一个removeAll():它只清除指定集合中的元素,retainAll()用来测试两个集合是否有交集。 
private boolean batchRemove(Collection<?> c, boolean complement) {
    //将原集合,记名为A
    final Object[] elementData = this.elementData;
    //r用来控制循环,w是记录有多少个交集
    int r = 0, w = 0;
    boolean modified = false;
    try {
        //遍历 ArrayList 集合
        for (; r < size; r++)
            //参数中的集合c一次检测集合A中的元素是否有
            if (c.contains(elementData[r]) == complement)
                //有的话,就给集合A
                elementData[w++] = elementData[r];
    } finally {
        //发生了异常,直接把 r 后面的复制到 w 后面
        if (r != size) {
            //将剩下的元素都赋值给集合A
            System.arraycopy(elementData, r,
                             elementData, w,
                             size - r);
            w += size - r;
        }
        if (w != size) {
            //这里有两个用途,在removeAll()时,w一直为0,就直接跟clear一样,全是为null。
            //retainAll():没有一个交集返回true,有交集但不全交也返回true,而两个集合相等的时候,返回false,所以不能根据返回值来确认两个集合是否有交集,而是通过原集合的大小是否发生改变来判断,如果原集合中还有元素,则代表有交集,而元集合没有元素了,说明两个集合没有交集。
            // 清除多余的元素,clear to let GC do its work
            for (int i = w; i < size; i++)
                elementData[i] = null;
            modCount += size - w;
            size = w;
            modified = true;
        }
    }
    return modified;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值