ArrayList、LinkedList

一、ArrayList

首先上两张图给大家看一下:

在这里插入图片描述
在这里插入图片描述
ArrayList是一个普通的类。

1.1 ArrayList初始化第一步做了什么?

个人代码部分:

List<String> list1 = new ArrayList<String>();

源码解析:

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

我们看看Array List的构造方法,给elementData初始化了一个参数,elementData是什么?DEFAULTCAPACITY_EMPTY_ELEMENTDATA是什么?继续往下看

    /**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     * 翻译:共享空数组实例,用于默认大小的空实例。我们
	 * 		将其与EMPTY_ELEMENTDATA区分开来,以了解何时膨胀多少
	 * 		添加第一个元素。
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     * 翻译:存储ArrayList元素的数组缓冲区。
     * 		ArrayList的容量是这个数组缓冲区的长度。任何
     * 		使用elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA清空ArrayList
     * 		将在添加第一个元素时扩展为DEFAULT_CAPACITY。
     * 
     */
    transient Object[] elementData; // non-private to simplify nested class access

DEFAULTCAPACITY_EMPTY_ELEMENTDATA 是一个空Object数组

我们用有道翻译来看看这断代码注释是什么意思,总结一下:

  1. 存储ArrayList元素的数组缓冲区
  2. ArrayList的容量是这个数组缓冲区的长度
  3. 添加第一个元素时扩展为DEFAULT_CAPACITY

DEFAULT_CAPACITY ,我们来看看这个常量是多少,往下看

    /**
     * Default initial capacity.
     * 翻译:默认初始容量。
     */
    private static final int DEFAULT_CAPACITY = 10;

综上,我们可以得出一个结论,Array List初始化就是创建了一个空的Object数组,并且当你第一次添加元素的时候,会给这个数组长度赋值为10

注意:这里还有一个想说的,就是elementData是一个成员变量,虽然是一个Object类型的数组,但是并没有一开始就赋值,而是通过另一个常量DEFAULTCAPACITY_EMPTY_ELEMENTDATA ,赋值给elementData。为什么要这样呢?最最根本的原因,个人认为,是因为Java中数组一旦创建长度就已经确定了,不允许更改。这样做还是为了节省内存空间,赋值只是更换个引用而已。

1.2 Array List 中add() 之上集

第一步我们知道Array List初试化第一次添加元素的时候会创建一个长度为10的Object数组,那我们来验证一下,它创建了没有?它是怎么创建的?

个人代码:

        List<String> list1 = new ArrayList<String>();
        list1.add("第一个元素");

源码片段:

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!! 翻译:修改次数
        elementData[size++] = e;
        return true;
    }

我们一行一行代码来看:

ensureCapacityInternal(size + 1);

    /**
     * The size of the ArrayList (the number of elements it contains).
     * 翻译:ArrayList的大小(它包含的元素的数量)。
     *
     * @serial
     */
    private int size;

下面进去这个ensureCapacityInternal()看看:

    private void ensureCapacityInternal(int minCapacity) {//minCapacity = 1
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

哟这里面又调用了两个方法?别慌我们慢慢消化。继续看calculateCapacity()

    private static int calculateCapacity(Object[] elementData, int minCapacity) {//elementData = {},minCapacity = 1
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {},elementData  = {},等式成立,进去if()
            return Math.max(DEFAULT_CAPACITY, minCapacity);//DEFAULT_CAPACITY = 10,minCapacity = 1 ,返回大数值10
        }
        return minCapacity;
    }

calculateCapacity()返回 10 ,下面我们再看ensureExplicitCapacity()

    private void ensureExplicitCapacity(int minCapacity) {//minCapacity = 10
        modCount++;//modCount = 0,modCount++ = 1

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)//minCapacity  = 10,elementData.length = 0,表达式成立,进入if()
            grow(minCapacity);//minCapacity  = 10
    }

modCount是什么?

    /**
     * The number of times this list has been <i>structurally modified</i>.
     * Structural modifications are those that change the size of the
     * list, or otherwise perturb it in such a fashion that iterations in
     * progress may yield incorrect results.
     *
     * <p>This field is used by the iterator and list iterator implementation
     * returned by the {@code iterator} and {@code listIterator} methods.
     * If the value of this field changes unexpectedly, the iterator (or list
     * iterator) will throw a {@code ConcurrentModificationException} in
     * response to the {@code next}, {@code remove}, {@code previous},
     * {@code set} or {@code add} operations.  This provides
     * <i>fail-fast</i> behavior, rather than non-deterministic behavior in
     * the face of concurrent modification during iteration.
     *
     * <p><b>Use of this field by subclasses is optional.</b> If a subclass
     * wishes to provide fail-fast iterators (and list iterators), then it
     * merely has to increment this field in its {@code add(int, E)} and
     * {@code remove(int)} methods (and any other methods that it overrides
     * that result in structural modifications to the list).  A single call to
     * {@code add(int, E)} or {@code remove(int)} must add no more than
     * one to this field, or the iterators (and list iterators) will throw
     * bogus {@code ConcurrentModificationExceptions}.  If an implementation
     * does not wish to provide fail-fast iterators, this field may be
     * ignored.
     */
    protected transient int modCount = 0;

这断注释有点长啊,你只要知道modCount就是记录修改次数就行了

有点个多啊?怎么又有个grow(),源码就这样哦,要有耐心哦,我们继续看grow()

    /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     * 翻译:增加能力,以确保它至少可以持有
	 *		由最小容量参数指定的元素数量。
     *
     * @param minCapacity the desired minimum capacity
     * 	翻译:所需的最小容量
     */
    private void grow(int minCapacity) {//minCapacity = 10
        // overflow-conscious code
        int oldCapacity = elementData.length;//elementData.length = 0,oldCapacity = 0
        int newCapacity = oldCapacity + (oldCapacity >> 1);//newCapacity  = 0
        if (newCapacity - minCapacity < 0)//等式成立,进入if()
            newCapacity = minCapacity;//newCapacity  = 10
        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);//elementData = {},newCapacity = 10
    }

看一下方法,copyOf()

    public static <T> T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());//original 泛型数组,newLength = 10
    }

草,又调了一次方法,此刻我们已经有些不赖烦了!!!带代码还是要分析

    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {//newLength = 10
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)//等式成立,执行 new Object[newLength]
            ? (T[]) new Object[newLength]//newLength = 10 ,创建一个长度为10的Object数组
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

刚说完又来了?还调?操操操,含着泪…

    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);//src表示源数组,srcPos表示源数组要复制的起始位置,desc表示目标数组,destPos表示目标数组位置,length表示要复制的长度。

这是一个native方法,是由其它语言来实现的,一般是(C或C++),所以这儿没有实现代码。这是一个数组拷贝方法,大家还在写for循环拷贝数组吗?以后多用这个方法吧,简单又方便还能获得得更好的性能。感觉发现了一个宝贝吧?我们的幸苦还是有点用的。

    public boolean add(E e) {//e = “第一个元素”
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;//size = 0 ,
        return true;
    }

终于走完了ensureCapacityInternal()我们elementData现在是一个长度为10的Objec类型的数组。 知道下面我们再看看后面的,后面就比较简单了。

elementData[size++] = e 等价于 elementData[0] = e

就是往这个长度为10的数组 下标为0的位置赋值e ,e是我们调用add()传进来的值。

总结,原来第一次add()真的会创建一个长度为10的Object数组,还有就是size此时为1。

1.3 Array List 中add() 之下集

通过上一篇文章,我们知道了第一次调用 add()Array List底层会创建一个长度为10的Object类型的数组,那你想过没有?如果我添加的元素超过10个怎么办?

个人代码:

        List<String> list1 = new ArrayList<String>();
        list1.add("第一个元素");
        list1.add("第2个元素");
        list1.add("第3个元素");
        list1.add("第4个元素");
        list1.add("第5个元素");
        list1.add("第6个元素");
        list1.add("第7个元素");
        list1.add("第8个元素");
        list1.add("第9个元素");
        list1.add("第10个元素");
        list1.add("第11个元素");

源码部分:

    private void ensureExplicitCapacity(int minCapacity) {//minCapacity = 11
        modCount++;//modCount = 11

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)//minCapacity  = 11,elementData.length = 10,等式成立,进入if()
            grow(minCapacity);
    }

我们再来看看grow()

    /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     * 翻译:增加能力,以确保它至少可以持有
	 *		由最小容量参数指定的元素数量。
     *
     * @param minCapacity the desired minimum capacity
     * 	翻译:所需的最小容量
     */
    private void grow(int minCapacity) {//minCapacity = 11
        // overflow-conscious code
        int oldCapacity = elementData.length;//oldCapacity = 10
        int newCapacity = oldCapacity + (oldCapacity >> 1);//newCapacity  = 15
        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);//elementData = {“第一个元素”,“第2个元素”,....,“第10个元素”},newCapacity = 15
    }

下面又调用了Arrays.copyOf(),这个方法我前面说过了,将原来的数组内容,赋值到新的数组里面,它的作用就是数组扩容。
这个grow方法里面有一个最最重要的步骤就是这句话 int newCapacity = oldCapacity + (oldCapacity >> 1);

1.3.1 题外话:位运算

怕有些同学忘了位运算,给大家补充一下知识。

: >> 表示右移,如果该数为正,则高位补0,若为负数,则高位补1;
正数:r = 10 >> 1

10的二进制补码:0000 1010
向右移动1位后:0000 0101
结果:r = 5

总结,ArrayList 当底层数组不够存储元素时,就会扩容,扩多大呢?在原来的基础上扩大1.5倍。

1.4 Array List 中 remove()之上集

在这里插入图片描述
我们看到remove有两个重载方法,一个需要int 参数,一个需要Object参数

我们先看传int参数的方法,int其实就是数组下标索引

   /**
     * Removes the element at the specified position in this list.
     * Shifts any subsequent elements to the left (subtracts one from their
     * indices).
     * 翻译:移除列表中指定位置的元素。
	 *		将任何后续元素向左移动(从它们的元素中减去一个)
	 *		指数)。
     *
     * @param index the index of the element to be removed 翻译:要删除的元素的索引
     * @return the element that was removed from the list 翻译: 从列表中删除的元素
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E remove(int index) {//index = 1
        rangeCheck(index);//范围审核,判断下标是否越界,index = 1

        modCount++;//记录修改次数
        E oldValue = elementData(index);//根据下标返回数组元素,oldValue = “第2个元素”

        int numMoved = size - index - 1;//计算数组需要拷贝的元素个数,size = 10,index = 1,numMoved = 8
        if (numMoved > 0)//等式成立,进入if()
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);//elementData = {“第一个元素”,“第2个元素”,...,“第10个元素”},index = 1,numMoved = 8
        elementData[--size] = null; // clear to let GC do its work 翻译:清除,让GC完成它的工作

        return oldValue;
    }

我们又看到 System.arraycopy(elementData, index+1, elementData, index,numMoved);,看来这玩意是Array List核心方法啊?

这句话就是把elementData数组,下标为2的元素开始,拷贝8个(拷贝下标2-9的所有元素),到原数组(elementData数组),从下标1开始复制。

下面画个图给你们讲一下这个方法(System.arraycopy(elementData, index+1, elementData, index,numMoved))实际干了什么?
在这里插入图片描述
看着有点乱?希望各位同学谅解哈,这个画图操作不太熟练,画画技术不行(丢人啊,以前高中还是美术班出生呢)

分析下:这拷贝前和拷贝后,有三个需要关注的地方

  1. 拷贝后,从下标1-8 所有的元素指向都发生了改变
  2. 拷贝后,第2个元素不再有下标指向
  3. 拷贝后,最后一个元素还是指向了 “第10个元素”

由于最后一个元素还指向它原来的引用,所以就有了下一步骤:把下标9的引用置空。等待GC清除。

// size = 9
elementData[--size] = null; // clear to let GC do its work 翻译:清除,让GC完成它的工作

1.5 Array List 中 remove()之下集

废话不说了,直接看源码吧,节约时间

    public boolean remove(Object o) {//0 = "第五个元素"
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {//进入else代码块
            for (int index = 0; index < size; index++)//size = 9 (因为我们之前删除了一个元素了)
                if (o.equals(elementData[index])) {//循环比较,如果存在“第5个元素”,表达式成立,进入if()
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

老规矩,我们继续看 fastRemove(int index)

    private void fastRemove(int index) {//index = 3
        modCount++;//记录修改次数
        int numMoved = size - index - 1;//size =9 ,index = 3,numMoved = 5
        if (numMoved > 0)//表达式成立
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }

这里我就不多说了,跟上面 remove(int index) 逻辑一样。

但是,这里有个最最重要的事,需要说一下,就是如果你Array List存的是对象,那么当你想通过remove(Object o)删除某个对象的时候,你就要注意了,可能需要重写equals()和 hashCode()。 感兴趣的同学可以试试啊。我这里就不写代码给你们看了。

1.6 ArrayList 之时间复杂度

我们之前讲解了add(),remove()一些源码。但是我们最常用的get()方法还没说,没说是因为太简单了,下面我们还是来看一下吧

    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

就两步骤,审核和返回元素。你就这样看也知道get()这个方法的效率是不是很高呢?当然咯,直接通过数组的下标索引返回元素,时间复杂度为O(1),还有一个set()

    public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

两个参数,索引和元素。就是你告诉它在哪里添加什么元素,一次完成,时间复杂度也是O(1)
但是我们知道add()和remove()方法都可能触发底层数组复制,时间复杂度为O(n)。
以上说的,两个时间复杂度有不理解的吗?不理解没关系,给大家再画个图,打个比喻。
如图:现在有一个小游戏,需要两个人来完成。桌子上有九个连续排放小盒子,每个盒子上面有一个数字,从0开始,从左到右依次递增。盒子里面的月饼(中秋节快到了)都是你亲自放的,每块月饼的花纹都不一样,你现在有两种方式让另一个人帮你从盒子里面拿,第一种方式:告诉另一个人你需要的月饼在哪个数字的盒子里面。这种方式最快,直接奔着数字的盒子过去拿给你。一次完成,所以时间复杂度为O(1)。第二种方式:告诉另一个人你想要的月饼花纹的图案是什么,另一个人需要从左往右,依此拿出来看一眼。假设你需要的月饼正好在第九个盒子里面,那么另一个人需要找九次才能拿到你需要的月饼。如果盒子的数量是n,那么就需要找n次。所以时间复杂度为O(n)。这个小游戏只是告诉大家时间复杂度的概念,让大家有个了解,什么叫时间复杂度,它往往是用来衡量计算机算法的效率。生活中的例子就是一件事的复杂程度,是否耗时。

你只要知道,在数组ArrayList中读取和存储(get/set)的性能非常高,为O(1),但插入(add(int index, E element))和删除(remove(int index))却花费了O(N)时间,效率并不高。
在这里插入图片描述

1.7 Arraylist与Vector的区别

首先我们给出标准答案:

1、Vector是线程安全的,ArrayList不是线程安全的。
2、ArrayList在底层数组不够用时在原来的基础上扩展0.5倍,Vector是扩展1倍。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
随便看了几个方法,都是加了synchronized 关键字,来保证线程的安全性。当执行synchronized修饰的方法前,系统会对该方法加一把锁,方法执行完成后释放锁,加锁和释放锁的这个过程,在系统中是有开销的,因此,在单线程的环境中,Vector效率要差很多。

和ArrayList和Vector一样,同样的类似关系的类还有HashMap和HashTable,StringBuilder和StringBuffer,后者是前者线程安全版本的实现。

二、LinkedList

今天我们来看Java中的另一种List即LinkedList,LinkedList是基于双向链表来实现的。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这几张图大家先看看,了解了解。我们之前学习的ArraList属于什么?没错,ArrayList属于线性表–顺序表

2.1 LinkedList 之 add()

个人代码:

    public static void main(String[] args) {

        List<String> linked = new LinkedList();
        linked.add("元素1");
        linked.add("元素2");
        linked.add("元素3");
        linked.add("元素4");
        System.out.println(linked.size());
    }

老规矩,我们来看看LinkedList的构造方法干了什么?

    public LinkedList() {
    }

啥也没干?没错,构造方法确实啥也没干…
那我只能看看有没有代码块啊?成员变量都有什么啊?

发现了三个成员变量

    transient int size = 0;

    /**
     * Pointer to first node.
     * Invariant: (first == null && last == null) ||
     *            (first.prev == null && first.item != null)
     */
    transient Node<E> first;

    /**
     * Pointer to last node.
     * Invariant: (first == null && last == null) ||
     *            (last.next == null && last.item != null)
     */
    transient Node<E> last;

这里说明一下,transient这个关键字告诉我们该对象在序列化的时候请忽略这个元素。 不影响我们看代码,不懂序列化的同学可以忽略这个字段。

size就不多说了,大家猜一下就知道是LinkedList的逻辑长度,初始化为0,并持有两个Node引用,first看名字一猜就是第一个,last看名字就是最后一个。

Node是什么?之前好像都没遇到过是吧?

    private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

Node是一个私有的静态内部类。

  • item --> 就是一个泛型变量
  • next --> Node类型的,见名知意,next 就是下一个
  • prev --> Node类型的,见名知意,prev 就是前一个

还是有点蒙蔽,感觉一头雾水啊?反正这个Node类肯定是核心了,这个毋庸置疑。下面我们debug看一下,这个Node到底存了什么?

    public boolean add(E e) {//e:"元素1"
        linkLast(e);//e:"元素1"
        return true;
    }

进入linkLast(e)方法

    /**
     * Links e as last element.
     */
    void linkLast(E e) {//e:"元素1"
        final Node<E> l = last;//l:null
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

进入 Node有参构造方法


    private static class Node<E> {
        E item;//item:"元素1"
        Node<E> next;//next:null
        Node<E> prev;//prev:null

        Node(Node<E> prev, E element, Node<E> next) {//prev:null 	element:"元素1"	next:null
            this.item = element;//element:"元素1"
            this.next = next;//next:null
            this.prev = prev;//prev:null
        }
    }

继续下面的,现在newNode对象有引用,last也指向了newNode的引用

    /**
     * Links e as last element.
     */
    void linkLast(E e) {//e:"元素1"
        final Node<E> l = last;//l:null
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;//newNode:有引用  last :有引用
        if (l == null)//等成成立
            first = newNode;//first:有引用
        else
            l.next = newNode;
        size++;//size:1
        modCount++;//modCount:1
    }

代码是走完了,元素1也添加进去了,感觉还是不知道在干嘛啊?别慌,我又来画图了(哈哈,绝对画好看点)
在这里插入图片描述
下面我们继续添加:“元素2”

    void linkLast(E e) {//e:"元素2"
        final Node<E> l = last;//l:{item="元素1",next = null,prev:null}
        final Node<E> newNode = new Node<>(l, e, null);//newNode:{item="元素2",next=null,prev=l}
        last = newNode;//last : newNode
        if (l == null)
            first = newNode;
        else
            l.next = newNode;//l.next : newNode
        size++;//size:2
        modCount++;//modCount:2
    }

执行完了,看懂了没?没看到的来看我的画图哦(●’◡’●)
在这里插入图片描述
继续哈,添加 “元素3”、“元素4”。

    void linkLast(E e) {//e:"元素4"
        final Node<E> l = last;//l:{item="元素3",next = null,prev:{...}}
        final Node<E> newNode = new Node<>(l, e, null);//newNode:{item="元素4",next=null,prev=l}
        last = newNode;//last : newNode
        if (l == null)
            first = newNode;
        else
            l.next = newNode;//l.next : newNode
        size++;//size:4
        modCount++;//modCount:4
    }

最终的效果图给大家重新画一下。是不是清晰了许多?
在这里插入图片描述

2.2 LinkedList 之 remove(int index)

个人代码:

    public static void main(String[] args) {

        List<String> linked = new LinkedList();
        linked.add("元素1");
        linked.add("元素2");
        linked.add("元素3");
        linked.add("元素4");
        linked.remove(2);
        linked.remove("元素4");
        System.out.println(linked.size());
    }

源码部分:

    public E remove(int index) {
        checkElementIndex(index);//审核元素索引,这个没什么好说的。
        return unlink(node(index));
    }

老规矩,一点点分析

unlink(node(index));

node(index),进去看看

    Node<E> node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)) {//size >> 1这里等价于size/2
            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;
        }
    }

很明显这里用到一个二分查找算法。然后再循环找到索引对应的元素。

我们回过头再看看 unlink(Node x)

    E unlink(Node<E> x) {//x:{item:"元素3",prev:{item:"元素2"...},next:{item:"元素4"...}}
        // assert x != null;
        final E element = x.item;//element :"元素3"
        final Node<E> next = x.next;//next:next:{item:"元素4"...}
        final Node<E> prev = x.prev;//prev:{item:"元素2"...}

        if (prev == null) {//等式不成立,进入else代码块
            first = next;
        } else {
            prev.next = next;//把元素2的prev指向了元素4
            x.prev = null;//把元素3的prev置空
        }

        if (next == null) {//等式不成立,进入else代码块
            last = prev;
        } else {
            next.prev = prev;把元素4的prev指向了元素2
            x.next = null;//把元素3的next 置空
        }

        x.item = null;//把元素3的item 置空
        size--;//size:3
        modCount++;//modCount:5
        return element;//返回元素3
    }

源码看懂没?没看到来看图。看懂的就不用看图了。
在这里插入图片描述
这就是效果图。

2.3 LinkedList 之 remove(int index)

源码部分

    public boolean remove(Object o) {//o:"元素4"
        if (o == null) {//等式不成立,进入else代码块
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {//循环遍历Node对象,从first指向的第一Node开始
                if (o.equals(x.item)) {//比较元素是否相等
                    unlink(x);//清除这个Node对象的引用
                    return true;
                }
            }
        }
        return false;
    }

unlink(x); 这个方法刚才带大家看过了,再看一遍,上次我们是删除双向链表中间的节点,这次删除的是最后一个节点,看看他又做了什么?

    E unlink(Node<E> x) {//x:{item:"元素4"...}
        // assert x != null;
        final E element = x.item;//element :"元素4"
        final Node<E> next = x.next;//next :null
        final Node<E> prev = x.prev;//prev :{item:"元素2"...}

        if (prev == null) {//等式不成立,进入else代码块
            first = next;
        } else {
            prev.next = next;//把元素2的next置空
            x.prev = null;//把元素4的prev 置空
        }

        if (next == null) {//等式成立
            last = prev;//把LinkedList对象的last指向元素2
        } else {
            next.prev = prev;
            x.next = null;
        }

        x.item = null;//把元素4的item置空
        size--;//size:2
        modCount++;//modCount:6
        return element;
    }

老规矩,看不懂源码的看图。

在这里插入图片描述

2.4 LinkedList 之 get()和set()

ArrayList源码部分:

    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

LinkedList源码部分:

    Node<E> node(int index) {
        // assert isElementIndex(index);

        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;
        }
    }

为什么LinkedList读取和存储性能慢?
上面上这断源码你就应该知道了,它是根据判断你要查的索引靠近first还是last,然后从头部或尾部开始一个个的匹配。假设你要查的这个元素的索引在中间值,那么很消耗性能。 和ArrayList源码对比一下,ArrayList直接根据数组下标获取元素,速度相当的快。

三、ArrayList和LinkedList性能对比

其实带大家看完源码,你们应该都知道Array List 的插入和删除都可能触发底层的数组复制,可以性能上可能会降低很多。
给大家做个Array List和LinkedList性能的对比。

个人代码:

/*
 * Copyright (C), 2013-2019, 天津大海云科技有限公司
 */
package com.jikang.myArrayList;


import java.util.ArrayList;
import java.util.List;

/**
 * @author yangjikang
 * @date 2019/7/22 14:42
 * @modified By yangjikang
 */
public class TestList01 {
    public static void main(String[] args) {
        ArrayList<String> list=new ArrayList<String>();
        int maxTestCount=50000;

        //测试添加
        long start =System.currentTimeMillis();

        for(int i =0;i<maxTestCount;i++){
            list.add(0,String.valueOf(i));
        }

        long end =System.currentTimeMillis();

        System.out.println("ArrayList add cost time :"+(end-start));

        //测试查询
        start =System.currentTimeMillis();

        for(int i =0;i<maxTestCount;i++){
            list.get(i);
        }

        end =System.currentTimeMillis();

        System.out.println("ArrayList get cost time :"+(end-start));

        //测试删除
        start =System.currentTimeMillis();

        for(int i =maxTestCount;i>0;i--){
            list.remove(0);
        }

        end =System.currentTimeMillis();

        System.out.println("ArrayList remove cost time :"+(end-start));
    }

}

控制台效果图:
在这里插入图片描述

个人代码:

/*
 * Copyright (C), 2013-2019, 天津大海云科技有限公司
 */
package com.jikang.myArrayList;


import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * @author yangjikang
 * @date 2019/7/22 14:42
 * @modified By yangjikang
 */
public class TestList01 {
    public static void main(String[] args) {
        LinkedList<String> list=new LinkedList<String>();
        int maxTestCount=50000;

        //测试添加
        long start =System.currentTimeMillis();

        for(int i =0;i<maxTestCount;i++){
            list.add(0,String.valueOf(i));
        }

        long end =System.currentTimeMillis();

        System.out.println("LinkedList add cost time :"+(end-start));

        //测试查询
        start =System.currentTimeMillis();

        for(int i =0;i<maxTestCount;i++){
            list.get(i);
        }

        end =System.currentTimeMillis();

        System.out.println("LinkedList get cost time :"+(end-start));


        //测试查询
        start =System.currentTimeMillis();

        for(int i =maxTestCount;i>0;i--){
            list.remove(0);
        }

        end =System.currentTimeMillis();

        System.out.println("LinkedList remove cost time :"+(end-start));
    }

}

控制台效果图
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值