• Java集合(四)--基于JDK1.8的ArrayList源码解读


    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    {
        private static final long serialVersionUID = 8683452581122892189L;
    
        //默认初始容量
        private static final int DEFAULT_CAPACITY = 10;
    
        //空数组
        private static final Object[] EMPTY_ELEMENTDATA = {};
    
        //默认空数组,用来和EMPTY_ELEMENTDATA区分什么时候扩容多少
        private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    
        //存储ArrayList元素的数组缓冲区,ArrayList的容量是此数组缓冲区的长度,添加第一个元素时,*将扩展为DEFAULT_CAPACITY
        transient Object[] elementData;
    
        //ArrayList的大小,也就是包含的元素数
        private int size;
    
        public ArrayList(int initialCapacity) {
            if (initialCapacity > 0) {	//初始容量大于0
                this.elementData = new Object[initialCapacity];	//生成一个新的存储数组
            } else if (initialCapacity == 0) {	//初始容量等于0
                this.elementData = EMPTY_ELEMENTDATA;	//设置为空数组
            } else {
                throw new IllegalArgumentException("Illegal Capacity: "+
                                                   initialCapacity);
            }
        }
    
        public ArrayList() {
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }
    
        /**
         * 构造一个包含指定元素的列表
         */
        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;
            }
        }
    }

    从上述源码中,我们可以得到以下信息:源码基于JDK1.8

    1、implements List<E>, RandomAccess(可以随机访问), Cloneable, java.io.Serializable

    2、初始容量10,有看过大佬博客说,默认创建ArrayList的时候,调用默认构造器this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA,此时初始容量为0,而不是10

    我的理解:

    默认构造器注解:

      Constructs an empty list with an initial capacity of ten

      构造一个初始容量为10的空集合

    elementData的注解如下:

      The capacity of the ArrayList is the length of this array buffer. Any empty ArrayList with elementData ==

    DEFAULTCAPACITY_EMPTY_ELEMENTDATA will be expanded to DEFAULT_CAPACITY when the first element is added

      第一次添加元素,容量会变成DEFAULT_CAPACITY,也就是初始容量

      所以,只是容量为0,初始容量DEFAULT_CAPACITY是final类型,永远为10,和elementData具体等于什么没关系,容量Capacity才会变

    3、并且通过transient Object[] elementData保存数据

    add()添加数据

    public boolean add(E e) {
    	ensureCapacityInternal(size + 1);  //确保容量
    	elementData[size++] = e; //在数组尾部添加数据
    	return true;
    }
    
    private void ensureCapacityInternal(int minCapacity) {
    	if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    		minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    	}
    
    	ensureExplicitCapacity(minCapacity);
    }
    
    private void ensureExplicitCapacity(int minCapacity) {
    	modCount++; //修改次数+1
    	if (minCapacity - elementData.length > 0)  //如果最小容量大于数组的长度,进行扩容
    		grow(minCapacity);
    }

    grow()扩容

    private void grow(int minCapacity) {
    	int oldCapacity = elementData.length;  //oldCapacity当前数组的长度
    	int newCapacity = oldCapacity + (oldCapacity >> 1);  //新容量=oldCapacity*1.5
    	if (newCapacity - minCapacity < 0)  //newCapacity < 最小容量,赋值
    		newCapacity = minCapacity;
    	if (newCapacity - MAX_ARRAY_SIZE > 0)  //大于最大容量,newCapacity赋值为Integer最大值0x7fffffff
    		newCapacity = hugeCapacity(minCapacity);
    	elementData = Arrays.copyOf(elementData, newCapacity);  //通过Arrays重新生成一个新容量的数组,包含之前数组的数据,赋值原数组
    }
    

    remove()删除数据

    1、按照数组下标删除

    public E remove(int index) {
    	rangeCheck(index);  //检查数组是否下标越界
    
    	modCount++;
    	E oldValue = elementData(index);  //取出index位置的数据
    
    	int numMoved = size - index - 1;  //
    	if (numMoved > 0)
         //将index后面的所有元素,都前移一位 System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; //最后一位置为null return oldValue;  //返回原来的老数据 }

    2、按照元素删除

    public boolean remove(Object o) {
    	if (o == null) {
    		for (int index = 0; index < size; index++)
    			if (elementData[index] == null) {  //删除第一个null
    				fastRemove(index);
    				return true;
    			}
    	} else {
    		for (int index = 0; index < size; index++)
    			if (o.equals(elementData[index])) {  //删除第一个和object相同的数据
    				fastRemove(index);
    				return true;
    			}
    	}
    	return false;
    }
    

    都是找个第一个和参数匹配的数据,然后执行的代码和第一种方式相同 

    复制方法:public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)

    Object src : 原数组

    int srcPos : 从元数据的起始位置开始

    Object dest : 目标数组

    int destPos : 目标数组的开始起始位置

    int length : 要copy的数组的长度

    set():

    public E set(int index, E element) {
    	rangeCheck(index);  //检查是否下标越界
    
    	E oldValue = elementData(index);  //获取原值
    	elementData[index] = element;  //覆盖原值
    	return oldValue;  //返回原值
    }
    

    get():

    public E get(int index) {
    	rangeCheck(index);
    
    	return elementData(index);
    }
    

    indexOf():

    public int indexOf(Object o) {  //和contain()一样,整体就是遍历是否包含该元素,然后返回下标,如果不包含,返回-1
    	if (o == null) {
    		for (int i = 0; i < size; i++)
    			if (elementData[i]==null)
    				return i;
    	} else {
    		for (int i = 0; i < size; i++)
    			if (o.equals(elementData[i]))
    				return i;
    	}
    	return -1;
    }
    

    size():和elementData的长度没有关系,指的是实际包含元素的个数

    public int size() {
    	return size;
    }
    

    isEmpty():是否为空

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

    trimToSize():缩减集合的容量为size,也就是剔除空节点

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

    ArrayList的for循环: 

      for、foreach、Iterator,for效率最好,Iterator效率最差

      只有for循环中调用add()和remove(),才不会发生java.util.ConcurrentModificationException异常,其他两种都会,set()不会出现异常。

      因为add() remove()都会执行modCount++

      ConcurrentModificationException主要为了防止线程并发修改的,因为ArrayList是线程不安全的,做了实现fast-fail机制

    public static void main(String[] args) throws IOException {
    	ArrayList<String> list = new ArrayList<>();
    	list.add("abc");
    	list.add("def");
    	list.forEach(s -> {
    		list.add(s);
    		list.remove(s);
    	});
    	Iterator iterator = list.iterator();
    	while (iterator.hasNext()) {
    		String s = (String)iterator.next();
    		list.remove(iterator.next());
    	}
    }
    
    输出结果:java.util.ConcurrentModificationException

    Itr:

    public Iterator<E> iterator() {
    	return new Itr();
    }
    

    ArrayList实现了自定义的迭代器

    private class Itr implements Iterator<E> {
    	int cursor;       //游标,下个元素的索引
    	int lastRet = -1; //最后一个元素的索引,如果没有返回-1
    	int expectedModCount = modCount;//期望修改的次数
    
    	public boolean hasNext() {  //是否有下个元素
    		return cursor != size;
    	}
    
    	@SuppressWarnings("unchecked")
    	public E next() {  //得到下个元素
    		checkForComodification();
    		int i = cursor;
    		if (i >= size)
    			throw new NoSuchElementException();
    		Object[] elementData = ArrayList.this.elementData;
    		if (i >= elementData.length)
    			throw new ConcurrentModificationException();
    		cursor = i + 1;  //后移一位
    		return (E) elementData[lastRet = i];  
    	}
    
    	public void remove() {
    		if (lastRet < 0)
    			throw new IllegalStateException();
    		checkForComodification();
    
    		try {
    			ArrayList.this.remove(lastRet);  //删除当前元素
    			cursor = lastRet;  //cursor指的是下个元素的索引,由于删除一个元素,所以还是等于当前位置
    			lastRet = -1;  
    			expectedModCount = modCount;
    		} catch (IndexOutOfBoundsException ex) {
    			throw new ConcurrentModificationException();
    		}
    	}
    
    	@Override
    	@SuppressWarnings("unchecked")
    	public void forEachRemaining(Consumer<? super E> consumer) {
    		Objects.requireNonNull(consumer);
    		final int size = ArrayList.this.size;
    		int i = cursor;
    		if (i >= size) {
    			return;
    		}
    		final Object[] elementData = ArrayList.this.elementData;
    		if (i >= elementData.length) {
    			throw new ConcurrentModificationException();
    		}
    		while (i != size && modCount == expectedModCount) {
    			consumer.accept((E) elementData[i++]);
    		}
    		// update once at end of iteration to reduce heap write traffic
    		cursor = i;
    		lastRet = i - 1;
    		checkForComodification();
    	}
    
    	final void checkForComodification() {  //检查是否发生过并发修改
    		if (modCount != expectedModCount)
    			throw new ConcurrentModificationException();
    	}
    }
    

    ListIterator:

    在之前对比Iterator和Iterable有讲过,支持双向遍历,既可以向前,又可以向后

    序列化:

      ArrayList本身实现了序列化,transient Object[] elementData,用来保存数据的数组确实用transient修饰的,意味着不能该字段不能实现

    序列化,那ArrayList到底如何实现的序列化的呢?

      通过重写writeObject()和readObject(),因为数组中容量Capacity一般都是大于size的,意味着有很多空节点,ArrayList为了节省网络资源

    ,自定义了序列化,只序列化非空的部分

      如果对序列化不够了解的话,可以参考:Java基础(十一)--Serializable和Externalizable接口实现序列化

    到目前为止,我们对ArrayList有了基本的了解:

    1、通过数组保存数据,可以随机访问数据,无论是get(i)、add(Object)还是set(int index, E element)效率都是比较高的

    2、remove()和add(int index, Object object),都会发生System.arraycopy(),如果index后面的元素比较多的情况下,效率降低很多

    3、线程不安全,如果需要保证安全性的话,建议使用CopyOnWriteArrayList

    4、初始容量10,发生扩容为原来的1.5倍

    总结:ArrayList适合顺序插入,按下标访问/修改,不适合删除,按下标插入

  • 相关阅读:
    基于学习的超分辨率技术
    图像缩放技术
    cifar-10 图片可视化
    python图像插值
    替换空格
    配置Windows Server 2008/2012/2016允许多个用户同时远程桌面
    安装XPS文件查看器的方法
    win10外接显示器时有些应用和里面的字体显示比较模糊
    关于中行长城跨境通卡的网上支付常见问题&支付实例
    分布式中Redis实现Session(将Session保存到Redis)
  • 原文地址:https://www.cnblogs.com/huigelaile/p/11052997.html
Copyright © 2020-2023  润新知