zoukankan      html  css  js  c++  java
  • ArrayList:源码

    构造方法

    本文基于jdk1.8

    三个构造方法

    public ArrayList();//无参
    public ArrayList(int initialCapacity);//指定初始化容量
    public ArrayList(Collection<? extends E> c);//构造包含指定集合的元素的列表
    

    无参构造源码

    	//默认一个空容量的数组,长度为零
        private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; 
        //真正存储数据的容器
        transient Object[] elementData;
    	public ArrayList() {
            //赋值
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }
    

    有参构造源码(1)

    如果指定参数大于0,List容器大小为指定该参数

    如果指定参数为0,则创建空容量数组。

        public ArrayList(int initialCapacity) {
            if (initialCapacity > 0) {
                this.elementData = new Object[initialCapacity];
            } else if (initialCapacity == 0) {
                this.elementData = EMPTY_ELEMENTDATA;
            } else {
                throw new IllegalArgumentException("Illegal Capacity: "+
                                                   initialCapacity);
            }
        }
    

    有参构造源码(2)

        public ArrayList(Collection<? extends E> c) {
            //将c转为数组
            elementData = c.toArray();
            if ((size = elementData.length) != 0) {
                // c.toArray might (incorrectly) not return Object[] (see 6260652)
                //这里为确保是Object[].class,进一步处理
                if (elementData.getClass() != Object[].class)
                    elementData = Arrays.copyOf(elementData, size, Object[].class);
            } else {
                // replace with empty array.
                this.elementData = EMPTY_ELEMENTDATA;
            }
        }
    

    添加方法

    add(E e)

        public boolean add(E e) {
            //保证容器容量足够
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            //添加指定元素到数组中
            elementData[size++] = e;
            return true;
        }
    

    ensureCapacityInternal代码

    image-20210117182426233

    grow(int minCapacity) 源码:

    
        private void grow(int minCapacity) {
            // overflow-conscious code
            int oldCapacity = elementData.length;
            //右移几位就相当于除以2的几次幂
            //左移几位就相当于乘以2的几次幂
            //这里右移一位,并加上原来大小,算出来大小差不多相当于乘以1.5   
            //newCapacity = 1.5*oldCapacity
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            //如果算出来的newCapacity比当前所需容量要小,那么直接让newCapacity = minCapacity
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
            //如果算出来的newCapacity比数组的最大容量大,那么让newCapacity重新计算出一个大容量的数
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
            //将原来的数组copy到新的大小的数组中去,新数组大小为计算出的newCapacity
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
    
        //这里的方法,可能有人看不太懂,需要结合MAX_ARRAY_SIZE的注释,
       //数组的理论长度是Integer.MAX_VALUE
        //但是部分虚拟机在数组分配长度大于Integer.MAX_VALUE-8时就会发生OOM异常
        //所以java中先限制MAX_ARRAY_SIZE = Integer.MAX_VALUE-8,这样会避免部分虚拟机报错
        //但是如果minCapacity大于Integer.MAX_VALUE-8,会直接扩容到Integer.MAX_VALUE
        //如果Integer.MAX_VALUE还放不下,那兄弟你耗子尾汁!
        private static int hugeCapacity(int minCapacity) {
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return (minCapacity > MAX_ARRAY_SIZE) ?
                Integer.MAX_VALUE :
                MAX_ARRAY_SIZE;
        }
    

    add(int index, E element)

    注意:只有容量不够的时候,才会扩容,并不是所有时候都会扩容

    image-20210117190648907

    其中:System.arraycopy这一行含义是 将elementData数组从索引index开始往后size-index个元素,复制到elementData,所有元素从index+1依次排开

    image-20210117190513021

    addAll(Collection<? extends E> c)

    image-20210117191317897

    addAll(int index, Collection<? extends E> c)

    image-20210117192216392

    上面四个add方法还是比较容易理解的,需要注意的点还是ArrayList的扩容情况。

    并发修改异常(迭代器)

    next方法

            ArrayList<String> list = new ArrayList<>();
            list.add("20");
            list.add("30");
            list.add("40");
            for (String o : list) {
                if(o.equals("20")){
                    list.remove(o);
                }
            }
    

    image-20210117200219054

    增强for循环在java底层用的是迭代器遍历实现的。

    而使用迭代器遍历会调用iterator的next方法:

            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];
            }
    		//检查修改
            final void checkForComodification() {
                if (modCount != expectedModCount)
                    throw new ConcurrentModificationException();
            }
    

    而上述代码使用的并不是迭代器的remove方法(这个方法会维护迭代器中expectedModCount这个值),使用的是ArrayList的remove方法,而这个方法不会维护expectedModCount和modCount的一致性,所以在checkForComodification方法中抛出了异常。

    如果想要避免抛出异常可以改成

            Iterator<String> it = list.iterator();
            while (it.hasNext()){
                String str = it.next();
                if(str.equals("20")){
                    it.remove();
                }
            }
    

    或者使用jdk8的新增api:removeIf方法

    remove方法

    再看一下迭代器中remove方法:

    image-20210117202757163

    特殊情况

    一种特殊情况:

            ArrayList<String> list = new ArrayList<>();
            list.add("30");
            list.add("20");
            list.add("40");
            for (String o : list) {
                System.out.println(o);
                if(o.equals("20")){
                    list.remove(o);
                }
            }
    

    上面这种情况,要remove的元素在list的倒数第二个位置

    我们看hasNext的源码:

    		int cursor; 
    		public boolean hasNext() {
                return cursor != size;
            }
    

    当要删除元素在list的倒数第二个位置时,这时候删除后,size=size-1,而cursor这时候正好等于size-1,返回了false,就不会调用执行while中内容,也就不会执行next方法。(此时for循环执行了size-1次就结束了)

    面试题

    频繁扩容

    频繁扩容导致性能急剧下降,怎么处理?

    例如向ArrayList中添加一百万条数据,如果直接new一个空的ArrayList,会频繁扩容,那么为何不 直接new ArrayList<>(1000000)呢?

    线程安全

    线程不安全。以下代码直接抛出ConcurrentModificationException异常

        public static void main(String[] args)  {
            List<Integer> list = new ArrayList<>();
            list.add(1);
            list.add(2);
            list.add(3);
            list.add(4);
            list.add(5);
            Thread thread1 = new Thread(){
                public void run() {
                    for (Integer integer : list) {
                        System.out.println(integer);
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                };
            };
            Thread thread2 = new Thread(){
                public void run() {
                    list.removeIf(integer -> integer == 2);
                };
            };
            thread1.start();
            thread2.start();
        }
    

    上述代码换成Vector也是直接报错的。虽然Vector的方法采用了synchronized进行了同步,但是实际上通过Iterator访问的情况下,每个线程里面返回的是不同的iterator,也即是说expectedModCount是每个线程私有。
    假若此时有2个线程,线程1在进行遍历,线程2在进行修改,那么很有可能导致线程2修改后导致Vector中的modCount自增了,线程2的expectedModCount也自增了,但是线程1的expectedModCount没有自增,此时线程1遍历时就会出现expectedModCount不等于modCount的情况了。

    上述代码换成CopyOnWriteArrayList就不会报错了。

  • 相关阅读:
    信息的封装和隐藏
    力扣 20. 有效的括号
    servlet执行原理
    当请求一个Servlet时,后台如何运作?
    req.getAttribute 和 req.getParameter
    Servlet 实现登录页面,并在条件下跳转
    request.getRequestDispatcher(a.jsp).forward(request,response)和response.sendRedirect的差别
    通过端口 1433 连接到主机 localhost 的 TCP/IP 连接失败。错误:“Connection refused: connect。请验证连接属性,并检查 SQL Server 的实例正在
    Cocos2d-x 3.0 精灵帧缓存(SpriteFrameCache)
    lua 中处理cocos2dx 的button 事件
  • 原文地址:https://www.cnblogs.com/wwjj4811/p/14290165.html
Copyright © 2011-2022 走看看