zoukankan      html  css  js  c++  java
  • Java容器源码分析-ArrayList

     概览

    因为 ArrayList 是基于数组实现的,所以支持快速随机访问。RandomAccess 接口标识着该类支持快速随机访问。

    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable

    数组的默认大小为 10。

    private static final int DEFAULT_CAPACITY = 10;

    扩容

    添加元素时使用 ensureCapacityInternal() 方法来保证容量足够,如果不够时,需要使用 grow() 方法进行扩容,新容量的大小为 ArraysSupport.newLength(oldCapacity,minCapacity - oldCapacity, oldCapacity >> 1); ,即newLength = Math.max(minCapacity - oldCapacity, oldCapacity >> 1) + oldCapacity; 。扩容操作需要调用 Arrays.copyOf() 把原数组整个复制到新数组中,这个操作代价很高,因此最好在创建 ArrayList 对象时就指定大概的容量大小,减少扩容操作的次数。

    public boolean add(E e) {  // 直接添加
            modCount++;
            add(e, elementData, size);
            return true;
        }
    
    public void add(int index, E element) {  // 根据索引添加
            rangeCheckForAdd(index);
            modCount++;
            final int s;
            Object[] elementData;
            if ((s = size) == (elementData = this.elementData).length)
                elementData = grow();
            System.arraycopy(elementData, index,
                             elementData, index + 1,
                             s - index);
            elementData[index] = element;
            size = s + 1;
        }
    
    private void add(E e, Object[] elementData, int s) {
            if (s == elementData.length)
                elementData = grow();
            elementData[s] = e;
            size = s + 1;
        }
    
    private Object[] grow() {
            return grow(size + 1);
        }
    
    private Object[] grow(int minCapacity) {
            int oldCapacity = elementData.length;
            if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                int newCapacity = ArraysSupport.newLength(oldCapacity,
                        minCapacity - oldCapacity, /* minimum growth */
                        oldCapacity >> 1           /* preferred growth */);
                return elementData = Arrays.copyOf(elementData, newCapacity);
            } else {
                return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
            }
        }

    删除元素

    需要调用 System.arraycopy() 将 index+1 后面的元素都复制到 index 位置上,该操作的时间复杂度为 O(N),可以看到 ArrayList 删除元素的代价是非常高的。

     public E remove(int index) {   //根据索引删除
            Objects.checkIndex(index, size);
            final Object[] es = elementData;
    
            @SuppressWarnings("unchecked") E oldValue = (E) es[index];
            fastRemove(es, index);
    
            return oldValue;
        }
    
    public boolean remove(Object o) {  // 根据对象删除
            final Object[] es = elementData;
            final int size = this.size;
            int i = 0;
            found: {
                if (o == null) {
                    for (; i < size; i++)
                        if (es[i] == null)
                            break found;
                } else {
                    for (; i < size; i++)
                        if (o.equals(es[i]))  // 若存在等价的多个要删除的对象,只删除第一个
                            break found;
                }
                return false;
            }
            fastRemove(es, i);
            return true;
        }
    
    private void fastRemove(Object[] es, int i) {
            modCount++;
            final int newSize;
            if ((newSize = size - 1) > i)
                System.arraycopy(es, i + 1, es, i, newSize - i);
            es[size = newSize] = null;
        }

    set

    根据索引删除,返回被删除的值

    public E set(int index, E element) {
            Objects.checkIndex(index, size);
            E oldValue = elementData(index);
            elementData[index] = element;
            return oldValue;
        }

    序列化

    ArrayList 基于数组实现,并且具有动态扩容特性,因此保存元素的数组不一定都会被使用,那么就没必要全部进行序列化。

    保存元素的数组 elementData 使用 transient 修饰,该关键字声明数组默认不会被序列化

    transient Object[] elementData; // non-private to simplify nested class access

    ArrayList 实现了 writeObject() 和 readObject() 来控制只序列化数组中有元素填充那部分内容。

    @java.io.Serial
        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
    
            // Read in size, and any hidden stuff
            s.defaultReadObject();
    
            // Read in capacity
            s.readInt(); // ignored
    
            if (size > 0) {
                // like clone(), allocate array based upon size not capacity
                SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Object[].class, size);
                Object[] elements = new Object[size];
    
                // Read in all elements in the proper order.
                for (int i = 0; i < size; i++) {
                    elements[i] = s.readObject();
                }
    
                elementData = elements;
            } else if (size == 0) {
                elementData = EMPTY_ELEMENTDATA;
            } else {
                throw new java.io.InvalidObjectException("Invalid size: " + size);
            }
        }
    @java.io.Serial
        private void writeObject(java.io.ObjectOutputStream s)
            throws java.io.IOException {
            // Write out element count, and any hidden stuff
            int expectedModCount = modCount;
            s.defaultWriteObject();
    
            // Write out size as capacity for behavioral compatibility with clone()
            s.writeInt(size);
    
            // Write out all elements in the proper order.
            for (int i=0; i<size; i++) {
                s.writeObject(elementData[i]);
            }
    
            if (modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }
        }

    序列化时需要使用 ObjectOutputStream 的 writeObject() 将对象转换为字节流并输出。而 writeObject() 方法在传入的对象存在 writeObject() 的时候会去反射调用该对象的 writeObject() 来实现序列化。反序列化使用的是 ObjectInputStream 的 readObject() 方法,原理类似。

    Fail-Fast

    modCount 用来记录 ArrayList 结构发生变化的次数。结构发生变化是指添加或者删除至少一个元素的所有操作,或者是调整内部数组的大小,仅仅只是设置元素的值不算结构发生变化。

    在进行序列化或者迭代等操作时,需要比较操作前后 modCount 是否改变,如果改变了需要抛出 ConcurrentModificationException。代码参考上节序列化中的 writeObject() 方法。

    对ArrayList进行操作的测试:

    public class AnalyseArrayList {
        public static void main(String[] args) {
            ArrayList<String> strings = new ArrayList();
            strings.add("aaa");
            strings.add("bbb");
            strings.add("ccc");
            strings.add("aaa");
            // strings.add(1);
    
            System.out.println("-------------直接添加后---------------");
            for (String str:strings){
                System.out.println(str);
            }
    
            strings.add(1,"zzz");
            System.out.println("-------------根据索引添加后---------------");
            for (String str:strings){
                System.out.println(str);
            }
    
            strings.remove(2);
            System.out.println("-------------根据索引删除后---------------");
            for (String str:strings){
                System.out.println(str);
            }
    
            strings.remove("aaa");
            System.out.println("-------------根据对象删除后---------------");
            for (String str:strings){
                System.out.println(str);
            }
            strings.set(0,"ooo");
            System.out.println("-------------进行set操作后---------------");
            for (String str:strings){
                System.out.println(str);
            }
        }
    }

  • 相关阅读:
    深入理解Spring Redis的使用 (三)、使用RedisTemplate的操作类访问Redis
    深入理解Spring Redis的使用 (二)、RedisTemplate事务支持、序列化
    Elasticsearch 评分score计算中的Boost 和 queryNorm
    Docker 镜像构建的时候,应该小心的坑
    怎么给kibana加上权限?
    网站异常了,日志要怎么看?
    使用 Gradle 配置java项目
    Cassandra 类型转换限制
    Elasticsearch 排序插件的开发
    ElasticSearch 2.0以后的改动导致旧的资料和书籍需要订正的部分
  • 原文地址:https://www.cnblogs.com/dong973711/p/14602876.html
Copyright © 2011-2022 走看看