美文网首页程序员JAVA
JDK 容器篇(一)之ArrayList原理分析+面试题

JDK 容器篇(一)之ArrayList原理分析+面试题

作者: 猫挺欠 | 来源:发表于2018-03-31 22:00 被阅读0次

    面试中常常会被问到JDK自带的容器,许多时候面试官会深挖实现以及常用类,这里简单做一部分的整理,文章基于jdk1.8版本。由于本人才疏学浅,如有错误之处还望斧正,技术应认真对待,请您发现错误及时反馈,感谢🙏
    -->转载请注明出处 简书 geek之路


    Connection

    首先看一下Connection接口


    Connection实现类以及接口

    可以发现常用的List 接口 Set接口与Queue接口

    再依次看下List接口中有哪些常用类(并发包留给后续的并发篇去写)


    List接口下常用类

    ArrayList、LinkedList、Vector 映入眼帘

    同理Set 发现有HashSet 与LinkedHashSet

    Queue接口发现基本都是concurrent下的东西留在并发篇去写


    Queue实现

    ArrayList实现原理
    核心参数

     // 默认容器大小
     private static final int DEFAULT_CAPACITY = 10;
    //空的对象数组
    private static final Object[] EMPTY_ELEMENTDATA = {};
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    //数组结构存储目标对象
    transient Object[] elementData;
    //ArrayList 的 size;
    private int size;
    

    初始化的三个构造器

       public ArrayList() {
            // 初始化一个空数组
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }
      //传入指定大小
      public ArrayList(int initialCapacity) {
            if (initialCapacity > 0) {
               //初始化指定大小的数据
                this.elementData = new Object[initialCapacity];
            } else if (initialCapacity == 0) {
                //初始化空数组
                this.elementData = EMPTY_ELEMENTDATA;
            } else {
                //传入指定大小小于0 抛出异常
                throw new IllegalArgumentException("Illegal Capacity: "+
                                                   initialCapacity);
            }
        }
    
    //将c元素的集合放入ArrayList
    public ArrayList(Collection<? extends E> c) {
            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;
            }
        }
    

    add()

    初始化好后我们常常向其中添加元素执行add()源码如下

      public boolean add(E e) {
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            elementData[size++] = e;
            return true;
        }
     private void ensureCapacityInternal(int minCapacity) {
            ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
        }
    
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { //如果是个空的数组
                return Math.max(DEFAULT_CAPACITY, minCapacity);//取10和参数间大的
           }
          return minCapacity;
         }
    
    private void ensureExplicitCapacity(int minCapacity) {
            modCount++;      //父类AbstractList  结构性修改+1
    
            // 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); //copy 扩容数据
        }
    

    可以发现关键点在于对容器大小进行了扩容

    remove ()

    有添加就要有删除

      public E remove(int index) {
            rangeCheck(index); //检查参数合法性
    
            modCount++; //  结构修改+1
            E oldValue = elementData(index) ; //获得删除object
    
            int numMoved = size - index - 1;  //计算移动的个数
            if (numMoved > 0)
                //移动
                System.arraycopy(elementData, index+1, elementData, index,
                                 numMoved);
            //有利于GC  也就是数组不再持有该对象,使其不可达
            elementData[--size] = null; // clear to let GC do its work
    
            return oldValue;
        }
    

    get() 获取对象

      public E get(int index) {
            // 检验索引是否合法
            rangeCheck(index);
            return elementData(index);
        }
    

    set() 指定位置设置值

        public E set(int index, E element) {
            // 校验下标是否合法
            rangeCheck(index);
            // 旧值
            E oldValue = elementData(index);
            // 将新值放在指定下标
            elementData[index] = element;
            // 返回旧值
            return oldValue;
        }
    

    indexOf() 查找某对象

        public int indexOf(Object o) {
            if (o == null) { // 查找的对象为null
                for (int i = 0; i < size; i++) // 遍历数组
                    if (elementData[i]==null)//找到第一个为null的对象
                        return i;//返回数组下标
            } else { // 查找的对象不为null
                for (int i = 0; i < size; i++) // 遍历数组
                    if (o.equals(elementData[i]))//找到第一个和指定对象相等的对象
                        return i;//返回下标
            } 
            // 没有找到,返回-1
            return -1;
        }
    

    由此可以看出,如果查找的对象不为null,那么该对象判断条件的关键在于是否重写equals() ,并且查找为顺序遍历

    小结

    ArrayList 默认容量为10,扩容系数为原长度的1.5倍,采用的是数组的结构。也就是特殊的链表结构,在物理内存中是有序的,所以我们查询某元素效率很快,增加删除元素由于需要改变其顺序,效率略低。同时,我们需要注意,在使用的时候对可预见的长度,创建时尽量指定大小,避免多次扩容,提高效率。

    ps:发现写博客一篇基础知识也需要大约四十分中的书写,贵在坚持吧!

    相关文章

      网友评论

        本文标题:JDK 容器篇(一)之ArrayList原理分析+面试题

        本文链接:https://www.haomeiwen.com/subject/zilncftx.html