ArryList深度剖析

ArryList深度剖析

一、底层数据结构

1、增删改查

增: 是先new一个新数组,然后将老的数组复制过去,在将新增内容填上

删:也要new一个新数组,然后将数组中要删除的元素的索引位置移除

改和查都不会new新数组

2、是List的可变数组的实现

二、继承实现关系

2.1、实现Serializable接口

可以序列化:将对象写到文件中

可以反序列化:将文件中的对象信息读取出来

2.2、实现Cloneable接口

如果要实现clone方法必须有两个条件

实现Cloneable的接口
重写clone()方法

源码实现

     *
     * @return a clone of this <tt>ArrayList</tt> instance
     */
    public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }

这里调用了super(最大父类object的克隆方法),然后将数组的数据传到新的ArrayList集合中的数组数据中,最后再返回新的ArrayList集合

A:浅拷贝

基于clone的浅拷贝
1、创建实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student implements  Cloneable {
    private  String name;
    private  int age;

    @Override
    public Student clone() throws CloneNotSupportedException {
        return (Student) super.clone();
    }
}

1、实现Cloneabe的接口
2、重写clone的方法,这里原本是返回Object对象,可以强转

		Student student1=new Student("zqs",24);
        Student student2 = student1.clone();
        System.out.println(student1==student2);
        System.out.println("原对象"+student1);
        System.out.println("浅拷贝的对象"+student2);

在这里插入图片描述
浅拷贝的局限性
基本类型可以完全被拷贝,引用地址则不能

因为浅拷贝拷贝的只是引用地址

B:深拷贝

主要是让引用的类也实现Cloneabe的接口

主对象

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student implements  Cloneable {
    private  String name;
    private  int age;
    private  Play play;//引用的对象
    @Override
    public Object clone() throws CloneNotSupportedException {
         Student student=(Student) super.clone();
         Play play1=(Play) play.clone();
         student.setPlay(play1);
         return student;
    }
}

引用对象

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Play implements  Cloneable {

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

深拷贝

	 public static void main(String[] args) throws CloneNotSupportedException {
   
        Play play=new Play("篮球");
        Student student1=new Student("zqs",24 ,play);
        Student student2 = (Student) student1.clone();
        
        System.out.println(student1==student2);
        System.out.println("原对象"+student1);
        System.out.println("浅拷贝的对象"+student2);

        System.out.println("----------修改引用对象属性------------");
        play.setPlay("乒乓");

        System.out.println("修改后的原对象"+student1);
        System.out.println("修改后的浅拷贝对象"+student2);
    }

结果
在这里插入图片描述

2.3、实现RandomAccess接口

使得随机访问的比顺序访问的时间要短一些,提高效率
在这里插入图片描述

这里对比LinkedList (都add一个字符串10000次)发现,LinkeLlist随机访问时间19932和顺序访问4
原因:LinkedList没有实现RandomAccess接口
所以对于LinkedList尽量使用迭代器方式add元素
在这里插入图片描述

实际开发运用
在这里插入图片描述
查询一个集合不知道类型时

判断是否实现了RandomAccess接口

2.4、继承AbstractSequentialList抽象类

主要是提供一些必要重写的抽象方法
在这里插入图片描述

三、源码分析

3.1、构造方法

A:无参构造

创建一个初始容量为10的空数组
在这里插入图片描述

 /**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;

在这里插入图片描述
B:有参构造(第一种)

自定义一个容量的容器
在这里插入图片描述

ArrayList<Student> arrayList=new ArrayList<>(6);

	public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
        	//给定容量为6,就创建一个容量为6的数组
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
    //initialCapacity == 0时
    private static final Object[] EMPTY_ELEMENTDATA = {};

C:有参构造(第二种)

传一个集合进去
在这里插入图片描述

		//自定义代码
		ArrayList<String> arrayList=new ArrayList<>();
        arrayList.add("篮球");
        ArrayList arrayList1=new ArrayList(arrayList);
    
    //源码
	public ArrayList(Collection<? extends E> c) {
		//1、先将原集合转为数组存到新集合的底层数组中
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
            	//数组的创建和拷贝
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

3.2、添加方法

A:add()

	public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
   
    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        //private static final int DEFAULT_CAPACITY = 10; minCapacity =10
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }
   
	private void ensureExplicitCapacity(int minCapacity) {
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    /***
    *扩容核心
    */
    private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        //扩容   新的容器长度=老的容器长度+老的……/2 (新=1.5*老)
        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倍 int newCapacity = oldCapacity + (oldCapacity >> 1);
默认为10

B:add(int index, E element)

	public void add(int index, E element) {
		//验证index是否合法(大于0小于集合长度)
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }
    //给默认容器
    private void ensureCapacityInternal(int minCapacity) {
    	//这里elementData 不等于空数组了,所以不走if里面
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);//minCapacity=2
    }
    
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // 这里的因为扩容过了elementData.length=0,所以也不走if
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

最后只需要将元素拷贝放到index位置,再将后面的元素复制到index之后
elementData[index] = element;‘

C:addAll(Collection<? extends E> c)

	public boolean addAll(Collection<? extends E> c) {
		//
        Object[] a = c.toArray();
        int numNew = a.length;
        //验证和扩容
        ensureCapacityInternal(size + numNew);  // Increments modCount
        //真正拷贝的方法
        System.arraycopy(a, 0, elementData, size, numNew);
        //修改长度
        size += numNew;
        return numNew != 0;
    }

3.3、修改方法

主要是将index出的元素索引出来,再覆盖值

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

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

3.4、获取方法

主要是通过index索引到对应的值直接返回

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

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

3.5、toString()方法 AbstractList()接口中的方法

底层主要是用迭代器判断有没有元素,没有直接返回[ ]
有就用StringBuilder拼接字符串

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

3.6、Iterator方法(迭代不能改变元素)

迭代删除代码(错误的)

package com.redis.test;
import java.util.ArrayList;
import java.util.Iterator;
/**
 * @ClassName
 * @Description TODO
 * @Author ZQS
 * @Date 2020/8/21 0019 22:27
 * @Version 1.0
 **/
public class TestArrayList  {

    public static void main(String[] args) throws CloneNotSupportedException {
        ArrayList<String> arrayList=new ArrayList<>();
        arrayList.add("篮球");
        arrayList.add("rap");

        Iterator<String> it =arrayList.iterator();
        while (it.hasNext()){
            String e=it.next();
            if(e=="rap"){
                arrayList.remove("rap");
            }
        }

    }
}

在这里插入图片描述

主要是在迭代的时候,是要判断 实际修改值和预期修改值判断,如果不一样就抛异常
再删除元素之后还会迭代,这时发现调用remove()后导致实际修改次数+1了(modCount++)


		final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

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

特殊点:

当删除的元素位于集合的倒数第二个元素的时候,将不会产生并发修改异常

如下图


public class TestArrayList  {

    public static void main(String[] args) throws CloneNotSupportedException {
        ArrayList<String> arrayList=new ArrayList<>();
        arrayList.add("乒乓");
        arrayList.add("rap");
        arrayList.add("篮球");

        Iterator<String> it =arrayList.iterator();
        while (it.hasNext()){
            String e=it.next();
            if(e=="rap"){
                arrayList.remove("rap");
            }
        }
        System.out.println(arrayList);

    }
}

在这里插入图片描述
这里主要是调用hashNext()方法的时候,光标的值和集合的长度一样,就会返回fase,就不会走next()方法获取元素,南无底就不会产生并发异常

删除两次后光标的值为
在这里插入图片描述

删除两次后size的值为
在这里插入图片描述

于是调用hashNext()方法
在这里插入图片描述
直接return了,就不走后面的next()方法,就不会验证(校验),所以就不会报异常

调用Iterator类的remove()方法就行

源码实现
1、调用ArryaList里面的remove(int index)方法,根据索引删除元素
2、当删除元素后又将实际修改值赋值给预期修改值
所以一直相等,就不会抛异常

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

3.7、清空集合方法

原理:
1、先将集合中的所有元素清空 设置为null
(主要是尽早的让垃圾回收器回收垃圾,释放空间)
2、再将集合的长度设置为0 (size=1)

源码

	 public void clear() {
        modCount++;
        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;
        size = 0;
    }

四、常见问题的处理(优化ArrayList)

4.1、扩容问题

在这里插入图片描述
尽量使用初始化容器的构造方法
这样避免了集合的多次扩容问题

eg:向一个容器中加1000000条数据,就会扩容很多次
这样就会使得效率降低很多

4.2、和LinkedList对比

add和remove方法不一定比LindedList慢

1、因为对于(根据索引删除)LinkedList底层涉及到循环,加节点的操作(删除解绑节点),效率也很低
2、Arraylist底层是由动态数组构成,因此不是每次操作都会创建新的数组

4.3、线程安全问题

1、ArrayList 本身是线程不安全的,因为底层没有加同步代码块和锁等
2、可以用Collections中的方法,原理就是加锁的方式
3、对于不需要线程安全的地方尽量不要加锁,因为加锁会导致效率降低
4、成员变量的集合中不需要线程安全,因为在一个方法中,每个调用都是一个单线程

当存在大量的写和读同时发生时可以用CopyOnWriteArrayList集合
在这里插入图片描述

创建线程,创建一个共有的CopyOnWriteArrayList集合

public class CollectionThread implements Runnable {
    private  static CopyOnWriteArrayList<String> list =new CopyOnWriteArrayList<>();
    static {
        list.add("zqs");
        list.add("gd");
        list.add("zxx");
    }
    @Override
    public void run() {
        for (String s : list) {
            System.out.println(s);
            list.add("星星");
        }
    }
}

开四个线程同时读和写

public class TestArrayList  {

    public static void main(String[] args) throws CloneNotSupportedException {
        CollectionThread myThread=new CollectionThread() ;
        for (int i = 0; i <4 ; i++) {
            new Thread(myThread).start();
        }

    }
}

结果如下
在这里插入图片描述
没有发生并发异常

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值