• Java集合-ArrayList


      一直要总结java集合中的知识,不知道应该如何下笔。觉得集合太多东西了,写细了太难了,写粗了又感觉写不好。不管如何觉得还是要坚持的写一写基础这一类的东西,为了提高自己的编程基础。本来觉的自己对这些已经很熟悉,最近见过一些大神后发现差距太大了,瞬间懵了,只能在加强学习了。

    一、ArrayList是什么?

      ArrayList是实现List接口的动态数组,所谓动态是指它的大小是可变的。实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。

      既然是数组,肯定就有容量。每个ArrayList对象都有一个容量,该容量是用来表示可以存放多少个数据在里面,即是数组的大小(默认是10)。当然,动态的肯定就会自动增加,每次我们往里面添加数据的时候,它都会进行扩容检查,检查完扩容会扩大为原来的1.5倍,扩容操作带来数据向新数组的重新拷贝,影响性能,所以如果我们知道具体业务数据量,在构造ArrayList时可以给ArrayList指定一个初始容量,这样就会减少扩容时数据的拷贝问题。当然在添加大量元素前,应用程序也可以使用ensureCapacity操作来增加ArrayList实例的容量,这可以减少递增式再分配的数量。

      ArrayList的底层实现是不同步,多线程操作会出现问题,这一点大家要注意。可以插入重复数据,可以插入Null。

    二、ArrayList源码分析

      2.1、ArrayList定义

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

        ArrayList的定义,继承AbstractList,实现List<E>, RandomAccess, Cloneable, java.io.Serializable,其中AbstractList实现了 List 的一些位置相关操作(比如 get,set,add,remove),是第一个实现随机访问方法的集合类,但不支持添加和替换。RandomAccess代表该类是否支持 随机访问,Cloneable类支持克隆,Serializable支持序列化。

      2.2、底层使用数组

        private static final long serialVersionUID = 8683452581122892189L;//serialVersionUID作用是序列化时保持版本的兼容性,即在版本升级时反序列化仍保持对象的唯一性。

        private transient Object[] elementData;//Object数组,transient关键字不知道的同学自己查资料去,带transient关键字的变量不会序列化。ArrayList容器,基本操作都是基于该数组进行操作的。

        private int size;//数组的大小。

        private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;//要分配的数组的最大大小。

      2.3、构造函数   

        ArrayList有三个构造函数:

        ArrayList():默认构造函数,提供初始容量为10的空列表。

        ArrayList(int initialCapacity):构造一个具有指定初始容量的空列表。

        ArrayList(Collection<? extends E> c):构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 的迭代器返回它们的顺序排列的。

      /**
         * 构造具有指定初始容量的空列表。.
         *
         * @param  初始容量列表的初始容量
         * @throws IllegalArgumentException 如果指定的初始容量为负会抛出非法异常。
         */
        public ArrayList(int initialCapacity) {
            super();
            if (initialCapacity < 0)
                throw new IllegalArgumentException("Illegal Capacity: "+
                                                   initialCapacity);
            this.elementData = new Object[initialCapacity];
        }
    
        /**
         * 构建一个初始容量为十的空列表.
         */
        public ArrayList() {
            this(10);
        }
    
        /**
         * 构造包含指定元素的列表。
         * 集合,按集合返回的顺序
         * 迭代器
         *
         * @param c的集合,其元素将放在这个列表中。
         * @throws NullPointerException 如果指定的集合为null为抛出这个空指针异常。
         */
        public ArrayList(Collection<? extends E> c) {
            elementData = c.toArray();//如果为空,这里会抛异常
            size = elementData.length;
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        }

      2.4、add方法  

        ArrayList提供了add(E e)、add(int index, E element)、addAll(Collection<? extends E> c)、addAll(int index, Collection<? extends E> c)、set(int index, E element)这个五个方法来实现ArrayList增加。

    我就拿一个来讲了,懂的一个,其他应该都懂了。  

      public boolean add(E e) {
            ensureCapacityInternal(size + 1);  //增加操作次数
            elementData[size++] = e;//把值放到最后一位
            return true;
      }
      private void ensureCapacityInternal(int minCapacity) {
            modCount++;
            // overflow-conscious code
            if (minCapacity - elementData.length > 0)//如果数组真实存储大于数组容量就增加
                grow(minCapacity);
      }
      private void grow(int minCapacity) {
            // overflow-conscious code
            int oldCapacity = elementData.length;//原来的容量
            int newCapacity = oldCapacity + (oldCapacity >> 1);//新的容量为旧的1.5倍
            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);//复制数组
     }
     private static int hugeCapacity(int minCapacity) {//注意是个静态方法
            if (minCapacity < 0) // overflow   小于0,抛异常
                throw new OutOfMemoryError();
            return (minCapacity > MAX_ARRAY_SIZE) ?//大于MAX_ARRAY_SIZE就设置为MAX_VALUE,否则就MAX_ARRAY_SIZE
                Integer.MAX_VALUE :
                MAX_ARRAY_SIZE;
    }

      2.5、remove方法

        ArrayList提供了remove(int index)、remove(Object o)、removeRange(int fromIndex, int toIndex)、removeAll()四个方法进行元素的删除。

        remove(int index):移除此列表中指定位置上的元素。

      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; // 让GC工作
            return oldValue;//返回旧值
      }
      private void rangeCheck(int index) {//检查越界
            if (index >= size)
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
      }
      private String outOfBoundsMsg(int index) {//是不是感觉经常空间这个,哈哈
            return "Index: "+index+", Size: "+size;
      }
      @SuppressWarnings("unchecked")
      E elementData(int index) {
            return (E) elementData[index];
      }

        remove(Object o):如果存在移除此列表中首次出现的指定元素。

      public boolean remove(Object o) {
            if (o == null) {//如果为null
                for (int index = 0; index < size; index++)
                    if (elementData[index] == null) {//==来比较
                        fastRemove(index);
                        return true;//存在并删除返回true
                    }
            } else {//如果不为null
                for (int index = 0; index < size; index++)
                    if (o.equals(elementData[index])) {//equals来比较
                        fastRemove(index);//存在并删除返回true
                        return true;
                    }
            }
            return false;//不存在返回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; // Let gc do its work
      }

      2.6、get方法

       public E get(int index) {
            rangeCheck(index);//检查是否越界
            return elementData(index);//取值
      }

      2.7、注意subList方法

      public static void main(String[] args) {
            List<Integer> list1 = new ArrayList<Integer>();
            list1.add(1);
            list1.add(2);
            // 通过构造函数新建一个包含list1的列表 list2
            List<Integer> list2 = new ArrayList<Integer>(list1);
            // 通过subList生成一个与list1一样的列表 list3
            List<Integer> list3 = list1.subList(0, list1.size());
            // 修改list3
            list3.add(3);
            System.out.println(list1.size());
            System.out.println("list1 == list2:" + list1.equals(list2));
            System.out.println("list1 == list3:" + list1.equals(list3));
    
      }

        上面一段代码我感觉大部人都会认为结果是2个false,list2是新构造的肯定与list1不一样,list3是截取的肯定也不一样。所以会认为都是false,我们深入subList去看看。

    public List<E> subList(int fromIndex, int toIndex) {
            subListRangeCheck(fromIndex, toIndex, size);//检查下标和大小
            return new SubList(this, 0, fromIndex, toIndex);//新创建一个SubList对象,注意传入的是this,代表了要截取的对象
    }
    private class SubList extends AbstractList<E> implements RandomAccess {//我擦,跟list差不多的感觉
    private final AbstractList<E> parent;
    private final int parentOffset;
    private final int offset;
    int size;//我擦,熟悉的感觉
    SubList(AbstractList<E> parent,int offset, int fromIndex, int toIndex) {//刚刚的构造
                this.parent = parent;//this.parent = parent;而parent就是在前面传递过来的list,也就是说this.parent就是原始list的引用。
                this.parentOffset = fromIndex;
                this.offset = offset + fromIndex;
                this.size = toIndex - fromIndex;
                this.modCount = ArrayList.this.modCount;//操作数也是跟原来的一样
    }

      原来sublist里面操作的是原来的list,并没有生成新的list,导致2个其实是一样的。

      所以上面正确的结果是:

      list1 == list2:false
      list1 == list3:true

    三、ArrayList总结

      因为它是基于数组实现的,主要有如下特点:

      1、插入、删除比较慢,因为插入、删除需要移动数据位置。

      2、可以重复插入数据、可以插入null。

      3、查找比较快,可以直接使用下标。

  • 相关阅读:
    【缓存】缓存穿透、缓存并发、热点缓存解决方案
    【缓存】redis缓存设计
    【AOP】Spring AOP 和 AspectJ
    disruptor
    Spring Boot application starters
    【日志】log4j2 yml
    PHP中间件--ICE
    docker 简单入门(一)
    redis、memcache和mongodb各自的优缺点是什么
    MYSQL三大范式
  • 原文地址:https://www.cnblogs.com/liaoweipeng/p/5751800.html
Copyright © 2020-2023  润新知