ArrayList和LinkedList区别(源码级JDK1.8)

ArrayList和LinkedList Add和GET方法差别

ArrayList add方法(为啥把构造也拿出来了?怕混乱)

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

无参的话就是直接空数组,数组名称:DEFAULTCAPACITY_EMPTY_ELEMENTDATA


开始

 public boolean add(E e) {
 		//每次添加之前判断大小是否扩容,所以传入是当前数组大小+1
        ensureCapacityInternal(size + 1);  
        elementData[size++] = e;
        return true;
    }

ensureCapacityInternal进入这个方法

  这时候 minCapacity最小容量为当期数组大小+1,elementData是内部维护的object数组
  private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

进入calculateCapacity(elementData, minCapacity),这里就是判断如果是无参创建的集合,小于默认大小,就给默认大小10,否则就直接返回传入的大小

private static int calculateCapacity(Object[] elementData, int minCapacity) {
		//DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个也是object空数组但是是无参构造的是创建,如果是有参初始值为0则为EMPTY_ELEMENTDATA
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        	//当前数组容量+1 和默认大小 10 取大的
        	//DEFAULT_CAPACITY默认大小10,如果无参就是创建的最小就是10
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        //如果通过有参构造创建直接return 当前数组容量+1
        return minCapacity;
    }

然后将方回值传入ensureExplicitCapacity方法

 private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // 是否需要扩容? 当前数组容量+1如果 大于当前数组大小 就会调用扩容方法
        // 无参构造创建就是10,所以得add第11次才会再扩容(11 - 10 > 0)
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

进入grow(重点,拓展容量

private void grow(int minCapacity) {
        // 当前数组的长度
        int oldCapacity = elementData.length;
        // 新的容量是 当前数组长度 + 当前数组长度右移一位(相当于/2) = 0
        //为啥不直接除2?别的地方不好优化了,就优化下运算速度
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //新的容量 如果小于 最小容量
        if (newCapacity - minCapacity < 0)
           //将最小容量赋给新的容量
            newCapacity = minCapacity;
            //判断新容量和最大容量对比MAX_ARRAY_SIZE  = Integer.MAX_VALUE - 8
        if (newCapacity - MAX_ARRAY_SIZE > 0)
        //如果比Integer.MAX_VALUE - 8还要大的话调用hugeCapacity,这里就是如果小于0报错 大于Integer.MAX_VALUE - 8就把这个值设置为Integer.MAX_VALUE
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

先进入Arrays.copyOf

public static <T> T[] copyOf(T[] original, int newLength) {
//传入一个旧数组类型object(纠结泛型可以去看看泛型),和新的数组长度
        return (T[]) copyOf(original, newLength, original.getClass());
    }

再进入三个参数的copyof

 public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        //如果是Object类型自接就new一个新的数组,附上一个长度
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            //否则就是其他类型的数组
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
         //第一个参数原数组,就是初始化的数组,第二个参数是数组起始位置,第三个参数是目标数组,也就是刚刚new的,第四个参数是目标数组的起始位置,数组要copy的长度
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

最后将这个值通过grow方法复制给了elementData ,最后回到add方法中

// 将数据放入size中,然后 size++ 
 elementData[size++] = e;

所以总结arrayList还是很简单,扩容也就是底层数组copy来copy去。

什么?没看到怎么扩容的?

  ensureCapacityInternal(size + 1); 

第一个元素插入完成
int newCapacity = oldCapacity + (oldCapacity >> 1)
假如现在数组长度是10了,然后又要add一次,那么这次扩容就是10+(10/2)=15


ArrayList get方法

public E get(int index) {
		//输入一个下标,这个index如果大于等于size那么抛IndexOutOfBoundsException异常
        rangeCheck(index);

        return elementData(index);
    }

进入rangeCheck方法

E elementData(int index) {
		//看起来怎么这么眼熟呢?
        return (E) elementData[index];
    }

看到这里就知道为啥arraylist添加删除(remove这个也一样复制来复制去的,感兴趣自己看一下)慢了,就是因为每次都需要复制到个新的数组,而查找比较快是因为通过下标去访问


LinkList add方法

 public boolean add(E e) {
 		//
        linkLast(e);
        return true;
    }

直接进去看linkLast方法

 void linkLast(E e) {
 		//last是一个Node类型的对象里面只有三个属性 item数据,next下一个对象 和 prev上一个对象 也都是Node类型,卧槽?我怎么有种递归的即视感
        final Node<E> l = last;
        //第一个参数是上一个节点,第一次进来就是个null,e就是数据,下一个节点是null
        final Node<E> newNode = new Node<>(l, e, null);
        //把实例化好的node给成员变量last,其实就是每次往屁股后边加
        last = newNode;

        if (l == null)
            //第一次进来 第一个节点就是刚才实例化的
            first = newNode;
        else
           //否则放到屁股后面
            l.next = newNode;    
        size++;
        modCount++;
    }

然后?然后就没了啊,直接写死了一个true返回了,我有什么办法

再看看get方法

public E get(int index) {
		//依然传入一个下标,然后检查是否>0并且小于size
        checkElementIndex(index);
        return node(index).item;
    }

进入node(index)方法

Node<E> node(int index) {
		// 传入的下标如果小于集合长度/2,你跟我在这玩二分法呢啊
        if (index < (size >> 1)) {
        	//在前半部分就从头开始找,找到输入的下标位置需要从头开始找
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
          //否则就是从最后一个向前找
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

所以看到这又知道了,为什么链表插入速度比较快,就是因为代码少(开玩笑哈哈),就是因为只要修改了某个位置对象的prev就可以插入进去了,不需要像数组一样还得copy,缺点就是得遍历一个一个找虽然分了一半,但是大量数据还是很难受

发布了41 篇原创文章 · 获赞 11 · 访问量 9538
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览