天道酬勤,学无止境

逼着面试官问了我ArrayList和LinkedList的区别,他对我彻底服了

ArrayList 和 LinkedList 有什么区别,是面试官非常喜欢问的一个问题。可能大部分小伙伴和我一样,能回答出“ArrayList 是基于数组实现的,LinkedList 是基于双向链表实现的。”

关于这一点,我之前的文章里也提到过了。但说实话,这样苍白的回答并不能令面试官感到满意,他还想知道的更多。

那假如小伙伴们继续做出下面这样的回答:

“ArrayList 在新增和删除元素时,因为涉及到数组复制,所以效率比 LinkedList 低,而在遍历的时候,ArrayList 的效率要高于 LinkedList。”

面试官会感到满意吗?我只能说,如果面试官比较仁慈的话,他可能会让我们回答下一个问题;否则的话,他会让我们回家等通知,这一等,可能意味着杳无音讯了。

为什么会这样呢?为什么为什么?回答的不对吗?

暴躁的小伙伴请喝口奶茶冷静一下。冷静下来后,请随我来,让我们一起肩并肩、手拉手地深入地研究一下 ArrayList 和 LinkedList 的数据结构、实现原理以及源码,可能神秘的面纱就揭开了。

01、ArrayList 是如何实现的?

ArrayList 实现了 List 接口,继承了 AbstractList 抽象类,底层是基于数组实现的,并且实现了动态扩容。

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final int DEFAULT_CAPACITY = 10;
    transient Object[] elementData;
    private int size;
}

ArrayList 还实现了 RandomAccess 接口,这是一个标记接口:

public interface RandomAccess {
}

内部是空的,标记“实现了这个接口的类支持快速(通常是固定时间)随机访问”。快速随机访问是什么意思呢?就是说不需要遍历,就可以通过下标(索引)直接访问到内存地址。

public E get(int index) {
    Objects.checkIndex(index, size);
    return elementData(index);
}
E elementData(int index) {
    return (E) elementData[index];
}

ArrayList 还实现了 Cloneable 接口,这表明 ArrayList 是支持拷贝的。ArrayList 内部的确也重写了 Object 类的 clone() 方法。

public Object clone() {
    try {
        ArrayList<?> v = (ArrayList<?>) super.clone();
        v.elementData = Arrays.copyOf(elementData, size);
        v.modCount = 0;
        return v;
    } catch (CloneNotSupportedException e) {
        // this shouldn't happen, since we are Cloneable
        throw new InternalError(e);
    }
}

ArrayList 还实现了 Serializable 接口,同样是一个标记接口:

public interface Serializable {
}

内部也是空的,标记“实现了这个接口的类支持序列化”。序列化是什么意思呢?Java 的序列化是指,将对象转换成以字节序列的形式来表示,这些字节序中包含了对象的字段和方法。序列化后的对象可以被写到数据库、写到文件,也可用于网络传输。

眼睛雪亮的小伙伴可能会注意到,ArrayList 中的关键字段 elementData 使用了 transient 关键字修饰,这个关键字的作用是,让它修饰的字段不被序列化。

这不前后矛盾吗?一个类既然实现了 Serilizable 接口,肯定是想要被序列化的,对吧?那为什么保存关键数据的 elementData 又不想被序列化呢?

这还得从 “ArrayList 是基于数组实现的”开始说起。大家都知道,数组是定长的,就是说,数组一旦声明了,长度(容量)就是固定的,不能像某些东西一样伸缩自如。这就很麻烦,数组一旦装满了,就不能添加新的元素进来了。

ArrayList 不想像数组这样活着,它想能屈能伸,所以它实现了动态扩容。一旦在添加元素的时候,发现容量用满了 s == elementData.length,就按照原来数组的 1.5 倍(oldCapacity >> 1)进行扩容。扩容之后,再将原有的数组复制到新分配的内存地址上 Arrays.copyOf(elementData, newCapacity)

private void add(E e, Object[] elementData, int s) {
    if (s == elementData.length)
        elementData = grow();
    elementData[s] = e;
    size = s + 1;
}

private Object[] grow() {
    return grow(size + 1);
}

private Object[] grow(int minCapacity) {
    int oldCapacity = elementData.length;
    if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        int newCapacity = ArraysSupport.newLength(oldCapacity,
                minCapacity - oldCapacity, /* minimum growth */
                oldCapacity >> 1           /* preferred growth */);
        return elementData = Arrays.copyOf(elementData, newCapacity);
    } else {
        return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
    }
}

动态扩容意味着什么?大家伙想一下。嗯,还是我来告诉大家答案吧,有点迫不及待。

意味着数组的实际大小可能永远无法被填满的,总有多余出来空置的内存空间。

比如说,默认的数组大小是 10,当添加第 11 个元素的时候,数组的长度扩容了 1.5 倍,也就是 15,意味着还有 4 个内存空间是闲置的,对吧?

序列化的时候,如果把整个数组都序列化的话,是不是就多序列化了 4 个内存空间。当存储的元素数量非常非常多的时候,闲置的空间就非常非常大,序列化耗费的时间就会非常非常多。

于是,ArrayList 做了一个愉快而又聪明的决定,内部提供了两个私有方法 writeObject 和 readObject 来完成序列化和反序列化。

private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
    // Write out element count, and any hidden stuff
    int expectedModCount = modCount;
    s.defaultWriteObject();

    // Write out size as capacity for behavioral compatibility with clone()
    s.writeInt(size);

    // Write out all elements in the proper order.
    for (int i=0; i<size; i++) {
        s.writeObject(elementData[i]);
    }

    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}

从 writeObject 方法的源码中可以看得出,它使用了 ArrayList 的实际大小 size 而不是数组的长度(elementData.length)来作为元素的上限进行序列化。

此处应该有掌声啊!不是为我,为 Java 源码的作者们,他们真的是太厉害了,可以用两个词来形容他们——殚精竭虑、精益求精。

02、LinkedList 是如何实现的?

LinkedList 是一个继承自 AbstractSequentialList 的双向链表,因此它也可以被当作堆栈、队列或双端队列进行操作。

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    transient int size = 0;
    transient Node<E> first;
    transient Node<E> last;
}

LinkedList 内部定义了一个 Node 节点,它包含 3 个部分:元素内容 item,前引用 prev 和后引用 next。代码如下所示:

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

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

LinkedList 还实现了 Cloneable 接口,这表明 LinkedList 是支持拷贝的。

LinkedList 还实现了 Serializable 接口,这表明 LinkedList 是支持序列化的。眼睛雪亮的小伙伴可能又注意到了,LinkedList 中的关键字段 size、first、last 都使用了 transient 关键字修饰,这不又矛盾了吗?到底是想序列化还是不想序列化?

答案是 LinkedList 想按照自己的方式序列化,来看它自己实现的 writeObject() 方法:

private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
    // Write out any hidden serialization magic
    s.defaultWriteObject();

    // Write out size
    s.writeInt(size);

    // Write out all elements in the proper order.
    for (LinkedList.Node<E> x = first; x != null; x = x.next)
        s.writeObject(x.item);
}

发现没?LinkedList 在序列化的时候只保留了元素的内容 item,并没有保留元素的前后引用。这样就节省了不少内存空间,对吧?

那有些小伙伴可能就疑惑了,只保留元素内容,不保留前后引用,那反序列化的时候怎么办?

private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
    // Read in any hidden serialization magic
    s.defaultReadObject();

    // Read in size
    int size = s.readInt();

    // Read in all elements in the proper order.
    for (int i = 0; i < size; i++)
        linkLast((E)s.readObject());
}

void linkLast(E e) {
    final LinkedList.Node<E> l = last;
    final LinkedList.Node<E> newNode = new LinkedList.Node<>(l, e, null);
    last = newNode;
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}

注意 for 循环中的 linkLast() 方法,它可以把链表重新链接起来,这样就恢复了链表序列化之前的顺序。很妙,对吧?

和 ArrayList 相比,LinkedList 没有实现 RandomAccess 接口,这是因为 LinkedList 存储数据的内存地址是不连续的,所以不支持随机访问。

03、ArrayList 和 LinkedList 新增元素时究竟谁快?

前面我们已经从多个维度了解了 ArrayList 和 LinkedList 的实现原理和各自的特点。那接下来,我们就来聊聊 ArrayList 和 LinkedList 在新增元素时究竟谁快?

1)ArrayList

ArrayList 新增元素有两种情况,一种是直接将元素添加到数组末尾,一种是将元素插入到指定位置。

添加到数组末尾的源码:

public boolean add(E e) {
    modCount++;
    add(e, elementData, size);
    return true;
}

private void add(E e, Object[] elementData, int s) {
    if (s == elementData.length)
        elementData = grow();
    elementData[s] = e;
    size = s + 1;
}

很简单,先判断是否需要扩容,然后直接通过索引将元素添加到末尾。

插入到指定位置的源码:

public void add(int index, E element) {
    rangeCheckForAdd(index);
    modCount++;
    final int s;
    Object[] elementData;
    if ((s = size) == (elementData = this.elementData).length)
        elementData = grow();
    System.arraycopy(elementData, index,
            elementData, index + 1,
            s - index);
    elementData[index] = element;
    size = s + 1;
}

先检查插入的位置是否在合理的范围之内,然后判断是否需要扩容,再把该位置以后的元素复制到新添加元素的位置之后,最后通过索引将元素添加到指定的位置。这种情况是非常伤的,性能会比较差。

2)LinkedList

LinkedList 新增元素也有两种情况,一种是直接将元素添加到队尾,一种是将元素插入到指定位置。

添加到队尾的源码:

public boolean add(E e) {
    linkLast(e);
    return true;
}
void linkLast(E e) {
    final LinkedList.Node<E> l = last;
    final LinkedList.Node<E> newNode = new LinkedList.Node<>(l, e, null);
    last = newNode;
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}

先将队尾的节点 last 存放到临时变量 l 中(不是说不建议使用 I 作为变量名吗?Java 的作者们明知故犯啊),然后生成新的 Node 节点,并赋给 last,如果 l 为 null,说明是第一次添加,所以 first 为新的节点;否则将新的节点赋给之前 last 的 next。

插入到指定位置的源码:

public void add(int index, E element) {
    checkPositionIndex(index);

    if (index == size)
        linkLast(element);
    else
        linkBefore(element, node(index));
}
LinkedList.Node<E> node(int index) {
    // assert isElementIndex(index);

    if (index < (size >> 1)) {
        LinkedList.Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        LinkedList.Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}
void linkBefore(E e, LinkedList.Node<E> succ) {
    // assert succ != null;
    final LinkedList.Node<E> pred = succ.prev;
    final LinkedList.Node<E> newNode = new LinkedList.Node<>(pred, e, succ);
    succ.prev = newNode;
    if (pred == null)
        first = newNode;
    else
        pred.next = newNode;
    size++;
    modCount++;
}

先检查插入的位置是否在合理的范围之内,然后判断插入的位置是否是队尾,如果是,添加到队尾;否则执行 linkBefore() 方法。

在执行 linkBefore() 方法之前,会调用 node() 方法查找指定位置上的元素,这一步是需要遍历 LinkedList 的。如果插入的位置靠前前半段,就从队头开始往后找;否则从队尾往前找。也就是说,如果插入的位置越靠近 LinkedList 的中间位置,遍历所花费的时间就越多。

找到指定位置上的元素(succ)之后,就开始执行 linkBefore() 方法了,先将 succ 的前一个节点(prev)存放到临时变量 pred 中,然后生成新的 Node 节点(newNode),并将 succ 的前一个节点变更为 newNode,如果 pred 为 null,说明插入的是队头,所以 first 为新节点;否则将 pred 的后一个节点变更为 newNode。

经过源码分析以后,小伙伴们是不是在想:“好像 ArrayList 在新增元素的时候效率并不一定比 LinkedList 低啊!”

当两者的起始长度是一样的情况下:

  • 如果是从集合的头部新增元素,ArrayList 花费的时间应该比 LinkedList 多,因为需要对头部以后的元素进行复制。
public class ArrayListTest {
    public static void addFromHeaderTest(int num) {
        ArrayList<String> list = new ArrayList<String>(num);
        int i = 0;

        long timeStart = System.currentTimeMillis();

        while (i < num) {
            list.add(0, i + "沉默王二");
            i++;
        }
        long timeEnd = System.currentTimeMillis();

        System.out.println("ArrayList从集合头部位置新增元素花费的时间" + (timeEnd - timeStart));
    }
}

/**
 * @author 微信搜「沉默王二」,回复关键字 PDF
 */
public class LinkedListTest {
    public static void addFromHeaderTest(int num) {
        LinkedList<String> list = new LinkedList<String>();
        int i = 0;
        long timeStart = System.currentTimeMillis();
        while (i < num) {
            list.addFirst(i + "沉默王二");
            i++;
        }
        long timeEnd = System.currentTimeMillis();

        System.out.println("LinkedList从集合头部位置新增元素花费的时间" + (timeEnd - timeStart));
    }
}

num 为 10000,代码实测后的时间如下所示:

ArrayList从集合头部位置新增元素花费的时间595
LinkedList从集合头部位置新增元素花费的时间15

ArrayList 花费的时间比 LinkedList 要多很多。

  • 如果是从集合的中间位置新增元素,ArrayList 花费的时间搞不好要比 LinkedList 少,因为 LinkedList 需要遍历。
public class ArrayListTest {
    public static void addFromMidTest(int num) {
        ArrayList<String> list = new ArrayList<String>(num);
        int i = 0;

        long timeStart = System.currentTimeMillis();
        while (i < num) {
            int temp = list.size();
            list.add(temp / 2 + "沉默王二");
            i++;
        }
        long timeEnd = System.currentTimeMillis();

        System.out.println("ArrayList从集合中间位置新增元素花费的时间" + (timeEnd - timeStart));
    }
}

public class LinkedListTest {
    public static void addFromMidTest(int num) {
        LinkedList<String> list = new LinkedList<String>();
        int i = 0;
        long timeStart = System.currentTimeMillis();
        while (i < num) {
            int temp = list.size();
            list.add(temp / 2, i + "沉默王二");
            i++;
        }
        long timeEnd = System.currentTimeMillis();

        System.out.println("LinkedList从集合中间位置新增元素花费的时间" + (timeEnd - timeStart));
    }
}

num 为 10000,代码实测后的时间如下所示:

ArrayList从集合中间位置新增元素花费的时间1
LinkedList从集合中间位置新增元素花费的时间101

ArrayList 花费的时间比 LinkedList 要少很多很多。

  • 如果是从集合的尾部新增元素,ArrayList 花费的时间应该比 LinkedList 少,因为数组是一段连续的内存空间,也不需要复制数组;而链表需要创建新的对象,前后引用也要重新排列。
public class ArrayListTest {
    public static void addFromTailTest(int num) {
        ArrayList<String> list = new ArrayList<String>(num);
        int i = 0;

        long timeStart = System.currentTimeMillis();

        while (i < num) {
            list.add(i + "沉默王二");
            i++;
        }

        long timeEnd = System.currentTimeMillis();

        System.out.println("ArrayList从集合尾部位置新增元素花费的时间" + (timeEnd - timeStart));
    }
}

public class LinkedListTest {
    public static void addFromTailTest(int num) {
        LinkedList<String> list = new LinkedList<String>();
        int i = 0;
        long timeStart = System.currentTimeMillis();
        while (i < num) {
            list.add(i + "沉默王二");
            i++;
        }
        long timeEnd = System.currentTimeMillis();

        System.out.println("LinkedList从集合尾部位置新增元素花费的时间" + (timeEnd - timeStart));
    }
}

num 为 10000,代码实测后的时间如下所示:

ArrayList从集合尾部位置新增元素花费的时间69
LinkedList从集合尾部位置新增元素花费的时间193

ArrayList 花费的时间比 LinkedList 要少一些。

这样的结论和预期的是不是不太相符?ArrayList 在添加元素的时候如果不涉及到扩容,性能在两种情况下(中间位置新增元素、尾部新增元素)比 LinkedList 好很多,只有头部新增元素的时候比 LinkedList 差,因为数组复制的原因。

当然了,如果涉及到数组扩容的话,ArrayList 的性能就没那么可观了,因为扩容的时候也要复制数组。

04、ArrayList 和 LinkedList 删除元素时究竟谁快?

1)ArrayList

ArrayList 删除元素的时候,有两种方式,一种是直接删除元素(remove(Object)),需要直先遍历数组,找到元素对应的索引;一种是按照索引删除元素(remove(int))。

public boolean remove(Object o) {
    final Object[] es = elementData;
    final int size = this.size;
    int i = 0;
    found: {
        if (o == null) {
            for (; i < size; i++)
                if (es[i] == null)
                    break found;
        } else {
            for (; i < size; i++)
                if (o.equals(es[i]))
                    break found;
        }
        return false;
    }
    fastRemove(es, i);
    return true;
}
public E remove(int index) {
    Objects.checkIndex(index, size);
    final Object[] es = elementData;

    @SuppressWarnings("unchecked") E oldValue = (E) es[index];
    fastRemove(es, index);

    return oldValue;
}

但从本质上讲,都是一样的,因为它们最后调用的都是 fastRemove(Object, int) 方法。

private void fastRemove(Object[] es, int i) {
    modCount++;
    final int newSize;
    if ((newSize = size - 1) > i)
        System.arraycopy(es, i + 1, es, i, newSize - i);
    es[size = newSize] = null;
}

从源码可以看得出,只要删除的不是最后一个元素,都需要数组重组。删除的元素位置越靠前,代价就越大。

2)LinkedList

LinkedList 删除元素的时候,有四种常用的方式:

  • remove(int),删除指定位置上的元素
public E remove(int index) {
    checkElementIndex(index);
    return unlink(node(index));
}

先检查索引,再调用 node(int) 方法( 前后半段遍历,和新增元素操作一样)找到节点 Node,然后调用 unlink(Node) 解除节点的前后引用,同时更新前节点的后引用和后节点的前引用:

    E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;

        if (prev == null) {
            first = next;
        } else {
            prev.next = next;
            x.prev = null;
        }

        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
    }
  • remove(Object),直接删除元素
public boolean remove(Object o) {
    if (o == null) {
        for (LinkedList.Node<E> x = first; x != null; x = x.next) {
            if (x.item == null) {
                unlink(x);
                return true;
            }
        }
    } else {
        for (LinkedList.Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item)) {
                unlink(x);
                return true;
            }
        }
    }
    return false;
}

也是先前后半段遍历,找到要删除的元素后调用 unlink(Node)

  • removeFirst(),删除第一个节点
public E removeFirst() {
    final LinkedList.Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
}
private E unlinkFirst(LinkedList.Node<E> f) {
    // assert f == first && f != null;
    final E element = f.item;
    final LinkedList.Node<E> next = f.next;
    f.item = null;
    f.next = null; // help GC
    first = next;
    if (next == null)
        last = null;
    else
        next.prev = null;
    size--;
    modCount++;
    return element;
}

删除第一个节点就不需要遍历了,只需要把第二个节点更新为第一个节点即可。

  • removeLast(),删除最后一个节点

删除最后一个节点和删除第一个节点类似,只需要把倒数第二个节点更新为最后一个节点即可。

可以看得出,LinkedList 在删除比较靠前和比较靠后的元素时,非常高效,但如果删除的是中间位置的元素,效率就比较低了。

这里就不再做代码测试了,感兴趣的小伙伴可以自己试试,结果和新增元素保持一致:

  • 从集合头部删除元素时,ArrayList 花费的时间比 LinkedList 多很多;

  • 从集合中间位置删除元素时,ArrayList 花费的时间比 LinkedList 少很多;

  • 从集合尾部删除元素时,ArrayList 花费的时间比 LinkedList 少一点。

我本地的统计结果如下所示,小伙伴们可以作为参考:

ArrayList从集合头部位置删除元素花费的时间380
LinkedList从集合头部位置删除元素花费的时间4
ArrayList从集合中间位置删除元素花费的时间381
LinkedList从集合中间位置删除元素花费的时间5922
ArrayList从集合尾部位置删除元素花费的时间8
LinkedList从集合尾部位置删除元素花费的时间12

05、ArrayList 和 LinkedList 遍历元素时究竟谁快?

1)ArrayList

遍历 ArrayList 找到某个元素的话,通常有两种形式:

  • get(int),根据索引找元素
public E get(int index) {
    Objects.checkIndex(index, size);
    return elementData(index);
}

由于 ArrayList 是由数组实现的,所以根据索引找元素非常的快,一步到位。

  • indexOf(Object),根据元素找索引
public int indexOf(Object o) {
    return indexOfRange(o, 0, size);
}

int indexOfRange(Object o, int start, int end) {
    Object[] es = elementData;
    if (o == null) {
        for (int i = start; i < end; i++) {
            if (es[i] == null) {
                return i;
            }
        }
    } else {
        for (int i = start; i < end; i++) {
            if (o.equals(es[i])) {
                return i;
            }
        }
    }
    return -1;
}

根据元素找索引的话,就需要遍历整个数组了,从头到尾依次找。

2)LinkedList

遍历 LinkedList 找到某个元素的话,通常也有两种形式:

  • get(int),找指定位置上的元素
public E get(int index) {
    checkElementIndex(index);
    return node(index).item;
}

既然需要调用 node(int) 方法,就意味着需要前后半段遍历了。

  • indexOf(Object),找元素所在的位置
public int indexOf(Object o) {
    int index = 0;
    if (o == null) {
        for (LinkedList.Node<E> x = first; x != null; x = x.next) {
            if (x.item == null)
                return index;
            index++;
        }
    } else {
        for (LinkedList.Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item))
                return index;
            index++;
        }
    }
    return -1;
}

需要遍历整个链表,和 ArrayList 的 indexOf() 类似。

那在我们对集合遍历的时候,通常有两种做法,一种是使用 for 循环,一种是使用迭代器(Iterator)。

如果使用的是 for 循环,可想而知 LinkedList 在 get 的时候性能会非常差,因为每一次外层的 for 循环,都要执行一次 node(int) 方法进行前后半段的遍历。

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

    if (index < (size >> 1)) {
        LinkedList.Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        LinkedList.Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

那如果使用的是迭代器呢?

LinkedList<String> list = new LinkedList<String>();
for (Iterator<String> it = list.iterator(); it.hasNext();) {
    it.next();
}

迭代器只会调用一次 node(int) 方法,在执行 list.iterator() 的时候:先调用 AbstractSequentialList 类的 iterator() 方法,再调用 AbstractList 类的 listIterator() 方法,再调用 LinkedList 类的 listIterator(int) 方法,如下图所示。

最后返回的是 LinkedList 类的内部私有类 ListItr 对象:

public ListIterator<E> listIterator(int index) {
    checkPositionIndex(index);
    return new LinkedList.ListItr(index);
}

private class ListItr implements ListIterator<E> {
    private LinkedList.Node<E> lastReturned;
    private LinkedList.Node<E> next;
    private int nextIndex;
    private int expectedModCount = modCount;

    ListItr(int index) {
        // assert isPositionIndex(index);
        next = (index == size) ? null : node(index);
        nextIndex = index;
    }

    public boolean hasNext() {
        return nextIndex < size;
    }

    public E next() {
        checkForComodification();
        if (!hasNext())
            throw new NoSuchElementException();

        lastReturned = next;
        next = next.next;
        nextIndex++;
        return lastReturned.item;
    }
}

执行 ListItr 的构造方法时调用了一次 node(int) 方法,返回第一个节点。在此之后,迭代器就执行 hasNext() 判断有没有下一个,执行 next() 方法下一个节点。

由此,可以得出这样的结论:遍历 LinkedList 的时候,千万不要使用 for 循环,要使用迭代器。

也就是说,for 循环遍历的时候,ArrayList 花费的时间远小于 LinkedList;迭代器遍历的时候,两者性能差不多。

06、总结

花了两天时间,终于肝完了!相信看完这篇文章后,再有面试官问你 ArrayList 和 LinkedList 有什么区别的话,你一定会胸有成竹地和他扯上半小时。

另外,我把自己看过的学习视频按照顺序分了类,共 500G,目录如下,还有 2020 年最新面试题,现在免费送给大家

链接:https://pan.baidu.com/s/1FOxmYlnU8qBGBNV2-VUk4Q 密码:hexs

我是沉默王二,一枚沉默但有趣的程序员,感谢各位同学的:点赞、收藏和评论,我们下篇见!

受限制的 HTML

  • 允许的HTML标签:<a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • 自动断行和分段。
  • 网页和电子邮件地址自动转换为链接。

相关推荐
  • 敖丙读者字节、滴滴面经(已拿offer)
    本期是读者投稿,他跟他聊的时候发现这个小伙子挺有意思的,是个校招生,全靠白嫖B站和我们一群号主的面试题复习,拿了挺多offer的,大家看看他的准备过程,校招的朋友可以看看他的学习路线。读者介绍目前他是电子科技大学的一名大三学生,在大一大二两年,主要的精力都用来准备校内课程学习。在今年的十月份开始准备 Java 实习面试,目前陆续拿到了滴滴、华为、字节跳动的实习offer(最后跟我讨论一番大概率去字节的基础架构研发实习)复习过程10月第一周:看完 JavaGuide的面试突击版中的 Java 基础部分10月第二周:开始看 B 站上看狂神说视频,学习 Spring 和 SpringBoot 开发,完成项目的初步搭建,看 Shiro 的权限管理和 JWT,为项目加入 Mysql、 Redis(之前有项目开发基础)10月第三周:看 B站上 马士兵的 2020年9月 JAVA 面试必问的102个知识点(全部弄懂记住),配合 JavaGuide 的面试突击版一起看10月第四 - 五周:发现了 敖丙的北大面试视频(每看到一个不会的知识点就暂停,去公众号找对应的文章去看,直到看懂整个视频),基本把公众号里的50%的内容都看懂,能够自己复述一遍,后面去敖丙github把面经和文章撸一遍基本上面试点都刷完了11月第一 - 二周:开始投了第一家面试公司(跟谁学),因为没有准备操作系统和计网以及算法
  • 2020年面试官太狠!Android研发面试腾讯、蘑菇街,被虐的体无完肤
    如果是第一次看我的文章的朋友,可以康康我上一篇关于面经的文章。第二位同学的面经终于在百忙之中整理出来了,希望能对正值找工作或者跳槽的朋友有所帮助。 总结下这个月的面试 上个月底是投了虎牙、完美世界,然后在boss直聘上沟通了一些,微派收了我简历。然后这个月初的收到了虎牙的拒信,吓得我都不敢投简历了,赶紧请学长学姐帮忙看了下简历。然后5号腾讯提前批开启,就当天投了腾讯、蘑菇街、斗鱼。这个月基本就是蘑菇街、腾讯、微派的面试同时进行的。微派那边似乎缺人吗不知道,然后一直给我安排了三次面试,第三次被面试官问哭了,我感觉可能自己一些想法和这个面试官不太契合,后面hr还想给我安排面试我就拒绝了。然后目前的话,完美的笔试放弃了,斗鱼的笔试几天前做了,似乎还在判卷。 蘑菇街的面试邀请提前了三天发,到了面试前一天,哭的很惨,哭到岔气,感觉自己快到了三月中旬了都,发现自己什么都不会,担心自己要是不能跟一号一起去实习,感觉自己特别没用,不知道怎么办。本来隔天要面蘑菇街,发现自己OkHttp都想不起来怎么回事,就开始看,可是太难受了,一点都没有看进去。 蘑菇街一面(3月12日 牛客视频 50min)
  • 三面阿里挂了之后获得美团内推名额,四面拿下offer,分享经历!
    目录 01 面试经验分享(阿里落榜+美团面试题)02 面试美团,应该具备什么样的能力?03 如何快速提升自己,通过面试? 写在前面 成长背景: 先说一下哥们的个人成长背景吧。出身江南小镇,大学没有出省,毕业之后就去了上海,在一家互联网金融工作,到去年年底整整两个年头。辞职之后运气不太好,碰到疫情肆虐,现在快步入3月月末了,实际上早早就已经在面试了,截止目前的结果,三面阿里已经“落榜”,后来拿到了一个内推的机会,4面揽下了美团offer。 面试感受: 这次面试下来,哥们的感受只有一个,大厂不愧是大厂,阿里的面试还是挺难的,没有那么容易进,面试的几个环节下来,可以说是人才济济,哥们感觉还挺自卑的,不过哥们也不是一个不自信的人,所以面试美团还算顺利,美团的发展还是挺快的,整场面试下来还是相当精彩的! 文章以下内容会给出阿里与美团的面试题(答案+解析)、面试题库、Java核心知识点梳理等 一、面试经验分享(阿里落榜+美团面试题) (1)阿里巴巴三面面试题(部分题): String和StringBuffer的区别gc的概念,如果A和B对象循环引用,是否可以被GC?Java中的内存溢出是如何造成的?String s = “123”;这个语句有几个对象产生?Error、Exception和RuntimeException的区别,作用又是什么
  • 社招三面阿里“凉凉”,幸获内推名额,4面揽下美团offer:GC+MySQL+线程+微服务+jvm
    目录: 01 面试经验分享(阿里落榜+美团面试题)02 面试美团,应该具备什么样的能力?03 如何快速提升自己,通过面试? 写在开头 成长背景: 先说一下个人的一个成长背景吧。出身江南小镇,大学没有出省,毕业之后就去了上海,在一家互联网金融工作,到去年年底整整两个年头。辞职之后运气不太好,碰到疫情肆虐,现在快步入3月月末了,实际上早早就已经在面试了,截止目前的结果,三面阿里已经“落榜”,后来拿到了一个内推的机会,4面揽下了美团offer。 面试感受: 这次面试下来,给我的感受只有一个,大厂不愧是大厂,阿里的面试还是挺难的,没有那么容易进,面试的几个环节下来,可以说是人才济济,自己感觉还挺自卑的,不过我也不是一个不自信的人,所以面试美团还算顺利,美团的发展还是挺快的,整场面试下来还是相当精彩的! 文章以下内容会给出阿里与美团的面试题(答案+解析)、面试题库、Java核心知识点梳理等,需要这些文档资料的,【见下图】即可免费领取~ 01 面试经验分享(阿里落榜+美团面试题) (1)阿里巴巴三面面试题(部分题): String和StringBuffer的区别gc的概念,如果A和B对象循环引用,是否可以被GC?Java中的内存溢出是如何造成的?String s = “123”;这个语句有几个对象产生?Error、Exception和RuntimeException的区别,作用又是什么
  • 三面阿里凉凉,天降内推名额,4面拿下美团offer
    目录: 01 面试经验分享(阿里落榜+美团面试题)02 面试美团,应该具备什么样的能力?03 如何快速提升自己,通过面试? 写在开头 成长背景: 先说一下个人的一个成长背景吧。出身江南小镇,大学没有出省,毕业之后就去了上海,在一家互联网金融工作,到去年年底整整两个年头。辞职之后运气不太好,碰到疫情肆虐,现在快步入3月月末了,实际上早早就已经在面试了,截止目前的结果,三面阿里已经“落榜”,后来拿到了一个内推的机会,4面揽下了美团offer。 面试感受: 这次面试下来,给我的感受只有一个,大厂不愧是大厂,阿里的面试还是挺难的,没有那么容易进,面试的几个环节下来,可以说是人才济济,自己感觉还挺自卑的,不过我也不是一个不自信的人,所以面试美团还算顺利,美团的发展还是挺快的,整场面试下来还是相当精彩的! 文章以下内容会给出阿里与美团的面试题(答案+解析)、面试题库、Java核心知识点梳理等,需要这些文档资料的,帮忙转发一下,关注后添加助理vx:yunduoa2019或扫描下图二维码,跟随助理指引自行获取 01 面试经验分享(阿里落榜+美团面试题) (1)阿里巴巴三面面试题(部分题): String和StringBuffer的区别gc的概念,如果A和B对象循环引用,是否可以被GC?Java中的内存溢出是如何造成的?String s = “123”;这个语句有几个对象产生?Error
  • 复习2个月拿下美团offer,我都做了些啥
    微信搜索【程序员囧辉】,关注这个坚持分享技术干货的程序员。 目录 前言 正文 咸鱼的现状 咸鱼的自我救赎 1.看面试题 2.源码初探 3.项目准备 咸鱼修炼出山 差点 FirstBlood 几次尴尬的经历 冥冥中注定 幸运女神的眷顾 进大厂的诀窍 写在最后 这是我17年的经历,觉得还挺励志的,希望能帮助到一些正在迷茫的同学。 前言 是去美团送外卖了?有配电瓶车吗? 答:亲,有配的哦,开起来贼顺滑。啊呸,说啥了,咱进的是正儿八经的技术部门。 正文 咸鱼的现状 时光回到2017年2月初,当时刚过完年回到上海,此时的我已经毕业一年半了,和大多数咸鱼一样,上班就是CRUD,下班就是看剧、玩游戏(英雄联盟、手游都玩)。 当时我的工资一个月到手就5k出头,但是因为在甲方驻场开发,加上差补餐补有接近8k,还包住。每到周末几个小伙伴一起打牌、一起搞情、一起洗脚,日子过得很是滋润,当时其实挺满足的。 咸鱼的自我救赎 让我萌生想跳槽的根本原因已经忘了,想了很久没想起来,可能是受其他小伙伴的影响吧,有了这种想法后就开始准备起来了。 刚开始只是每天下班后躺在床上看别人的博客学习一些东西,一般是22:30看一小时左右,当时最先看的是线程相关知识,就是类似于:创建线程的几种方式、Future 的使用、线程池的使用,等等这类比较基础的知识。 想跳槽的想法有了之后便一发不可收拾
  • 记录菜鸡前端面试日记--字节跳动
    今天面试字节跳动,前端开发,第一次面试好紧张呢。我一早就知道自己很菜,收到这个面试通知的时候还是蛮惊讶的。当时电话里hr和我说给我一周的准备时间,本来以为还挺好,没想到菜鸡终究是菜鸡,那个星期除了紧张只有紧张,恶补了一些前端的知识,从css到js到es6到计网,结果在面试的时候,都禁不起考究,只能答到一些皮毛。谨以此文,献给菜鸡的自己,方便一年后回来笑话自己。 1.自我介绍 看了网上很多自我介绍的案例,知道自我介绍还是有很多学问的。不能夸大其词,对提到的东西要十分小心,可能就在后面被问到了。所以我也特别准备了下,提到了css这部分我比较熟悉,给面试官一个心理暗示,好让后面他多点问我这些。好吧,给自己挖坑了。 前言: 面试官问了平常是如何系统学习前端的 问我看了哪些书(本菜鸡这两天才意识到得看书,之前都是看网上的教程,但是买的书还没拿到。 2.css考察 真的开始考我css的东西了,身子一下坐直起来 1)问了position的几个属性值,答了fixed、absolute、relative、static这几个,简单的说了一下区别,提到了absolute是基于上一个设置了static的父级元素定位的(这里我说反了,应该是基于不是static的父级元素定位,好气哦,狂揍自己)。面试官追问:那如果父级定位了一个absolute,子级也定位了absolute,那子级是基于谁定义的呢
  • 阿里全家桶面经分享!CBU,淘系架构,淘宝消息,技术风险部合集,一次性全部分享给大家!
    阿里CBU 楼主提前批海投了阿里 20 多个部门,基本都约了面试,系统开的那天收到了 16 封内推邮件,在系统开的前一个周,平均每天三场面试,极其的耗费心力,以至于春招除了阿里、腾讯、字节都没投过别的公司,我今天整理了一下这些面试中我认为有价值的部分面经,希望能给正在找实习的一些小伙伴一些帮助。 2021/2/21 15:00 50min 先让我讲一下实习的经历,大概问了下项目是做什么的,主要负责哪些模块,实习到什么时候,系统都哪些同学在用。还问了发布流程,说阿里这边有统一的发布平台,开发同学自己发布。项目用到的技术栈,这边用什么 RPC 框架。质疑工作量不大。Java 学了多久了,怎么学的? 类加载机制叫什么?有什么好处?有没有了解过如何打破类加载机制? Class 和 Object 类哪个先加载?什么时候加载的? Object 类里面的方法? 创建一个线程的方式,哪种方式可以获得返回值? 线程有哪几种状态? 线程池的核心参数有哪些? 两个线程交替打印奇偶数? @Resource 和 @Autowire 注解有什么区别?平常用哪个? AOP 动态代理的原理? BeanFactory 和 ApplicationContext 有什么区别? Spring Bean 加载流程? 当 MySQL 单表达到 1000 万的时候该如何优化?分表有实战过吗? TCP UDP HTTP
  • 2019年Android寒假实习面经-小米+字节跳动(已拿抖音offer)
    天学网 一投过去HR就加我微信了,结果说我实习时间不合适,没给面试 映客 实习僧投递过去到现在都没看我简历 小米-一面 面向对象的理解Java三大特性内部类、匿名内部类finalstatic死锁特性synchronizedwait、sleep线程池Handler(什么时候和线程进行绑定的)Activity生命周期Activity启动模式ContentProvider 算法 实现进制转换,我居然忘了怎么进制转换的了删除链表倒数第n个节点删除ArrayList值为10的所有节点 基础问的特别多,我项目啥的都没问题,疯狂问基础,抓的很细,只能怪自己当初没有准备好 超过1个星期没给我消息,应该是挂了。 关于字节这边先说明一下,我找学姐内推的是火山Android开发部门,结果那边把我推到了北京抖音工具线那边,所以我从一面开始就是抖音工具线的人在面我,只不过我不知道,我以为只是同一个大部门的面我而已,最后录取我的还是火山,直到面完后发offer我找HR问清楚后才知道我被抖音那边录取了。这也是个好事吧,如果我一开始就知道我被推到了抖音,那估计我会特紧张,估计还拿不到offer了。 字节一面-12.16(17:06-18:15) 一上来先问我知不知道这边是做啥的问我为啥想投这边接口与和抽象类final拆箱与装箱 问我一个Integer和int数通过equals和==进行比较,有啥区别
  • 4年Android开发13K,刷完这份1307页面试全套真题解析,跳槽涨薪15K!(限免领取)
    前言 我是大专毕业的,计算机专业,刚出来的时候,没有拿的出手的学历,也没什么特别突出的能力,工资也特别低,只能说能够在这个二线边缘城市养活自己吧,基本上没有什么余钱。 在小厂干了一年后,我跳了两次槽,都是不怎么知名的厂,但是工资有了10K,这对他来说已经是相当可观的一笔工资了。 在这个厂,我兢兢业业,干了三年,期间也有了女朋友,一年前结了婚,前不久怀上了孩子。家里的开支眼见着上涨了,我决定和老板商量一下涨工资的事情。 在那个小厂,我可以说是一个骨干,这几年,这个公司也在稳步发展,我的工作量也在增加,不仅仅是自己的工作,还要带新人,但是我的工资并没有上涨多少,只有13K不到。 前面我也跟老板隐晦的提过自己的经济压力,暗示涨工资的事情,但是老板一直在回避这个问题,于是,我决定跳槽。 以下是我的个人经历分享。 跳槽准备 一开始遭受的困难比较多。 我是从半年前开始准备跳槽的,当时因为疫情,很多计划都没能正常进行,甚至连现有的工作都岌岌可危,但是,我还是决定跳槽,再待下去也没有发展前途。 当我出去面试找工作时发现:现在面试,那些稍有名气的大厂往往更注重基础和深度,而且还有很多新的技术都会问到音视频开发,人工智能,混合开发这些,甚至底层和全栈方面都得会点。 项目这些倒是不怎么慌,用心整理一下应该可以,但是那些底层原理,技术题,音视频什么的是真的没什么底。 这两年里,经常加班加点赶项目
  • android远程控制!看懂这些帮你轻松解决就业问题!这原因我服了
    主要说一下个人经历,希望能给即将面试的小伙伴一些有效信息。大家都要加油~ 基本情况 先说一下个人基本情况,计算机科学与技术专业。主要学的Android开发,所以投的岗位都是Android开发工程师。投了知乎,内推了阿里蘑菇街腾讯百度网易。腾讯百度都没有收到面试,知乎Skype面试,蘑菇街阿里电话面试,网易现场面。知乎二面被拒,蘑菇街阿里一面被拒,网易三面,然后备胎了,前几天被通知过了,收到了offer。自己整理了一下被问到的问题,现在分享给大家,另外自己整理的一下资料也会发给大家,希望能帮助到各位求职的小伙伴。 面试题主要问的有以下这些: 一面 (因为过程比较顺利,题目没有好好记下来,大概就是下面这些吧) 1.项目相关 2.hashmap 3.线程a 等b,b等c的实现 4.类的equals重写 5.线程安全是什么 如何保证多线程安全 6.public private protected 7.简单工厂 抽象工厂 是什么 8.判断一个数组是不是后序遍历(代码 ) 二面 (二面一开始的时候我还是有些紧张的,但是我没想到居然挺简单,这也直接导致我三面轻敌了) 1.项目(10min) 2.算法题:判断一个字符串是否是一个IPV4 3.算法题:连续子数组的最大和(剑指Offer上的题) 4.Java异常体系(运行时与非运行时异常的区别) 5.HashCode与equals的区别 6
  • CVTE嵌入式面试汇总
    远程面试过程: 先是电话突袭面试,相当于笔试,问进程线程,TCP,UDP协议; 再是牛客网,在线视频代码编写, 询问算法,比较基础。 现场面试过程: 一面主要问基础,一对一技术面。 二面主要手写代码和问项目,二对一技术面。 三面为HR面,两个HR面三个应聘者。 面试官问的面试题: 一面主要问基础:Linux(查看进程、查看文件权限等)、C/C++(volatile、形参实参、内存结构代码段和堆栈等)、操作系统(系统的轮询、阻塞等)、单片机(中断、IO等),此外还有手写代码(字符串翻转),比较简单。 二面主要写代码和问项目:写半小时代码,分别为:bool变量与零值比较、浮点数与零值比较、程序改错(主要考形参和实参以及函数返回指针问题)、字符串转整数(电脑手撕完整代码,可调试);后面半个多小时主要问项目,问的比较细。 此外还夹杂着一些HR问题,比如接受的加班时间、未来的一些规划、对公司的了解、喜欢那个部门、若是给的工作给你期望的有所不符会怎样等、接下来有什么学习计划(学点什么)。 面试过程: 先是笔试,主要考察C\C++, Linux,单片机方面的知识。 20+2 20选择题+2道编程题 编程题主要考察字符串的一个处理,多看看网上有关于CVTE的题目,包括字符串处理的知识就可以了。 后期电话面试:主要考察你对于自己项目的一个介绍,包括使用到的一些知识点,主要考察C\C++的知识
  • GitHub标星1w的安卓架构师必备技能,这原因我服了
    前言 先介绍一下自己吧,不是什么二本渣校也不是什么非专业。我就是重点大学毕业,大学学的是Java,我个人比较乐于学习于是自学Android一年。趁着这次疫情,大洗牌我凭借天生优势,——聪明的脑袋以及自己不断地刻苦的学习,在一众高手之中成功脱颖而出。 现在网上都喜欢这么搞,哪有这么多二本渣校逆袭。年薪50w+都是吹出来的,只有你正真拿到年薪50w+你才知道,你要做多少事,付出多少。 由于涉及到的面试题较多导致篇幅较长,我根据这些面试题所涉及到的常问范围总结了并做出了一份学习进阶路线图​​​​​​​及面试题答案免费分享给大家,文末有免费领取方式! View面试专题 View的滑动方式View的事件分发机制View的加载流程View的measure layout 和 draw流程自定义view需要注意的几点ACTION_DOWN没有拦截,ACTION_MOVE ACTION_UP还会拦截吗 多线程专题 什么是线程线程的状态线程的创建线程中断Thread为什么不能用stop方法停止线程重入锁与条件对象,同步方法和同步代码块volatile关键字java内存模型原子性 可见性 有序性线程池ThreadPoolExecutor线程池的种类线程同步机制与原理,举例说明arrayList与linkedList的读写时间复杂度为什么HashMap线程不安全(hash碰撞与扩容导致
  • 字节跳动客户端开发实习生面经(已offer)
    时间线 2月25日一面,约45min 3月2日二面,约60min 3月8日三面,约60min 3月12日 Hr面 一面 0.自我介绍 【Java】 1.Integer和int类型的区别 2.讲一下重写、重载和多态 3.(接第2题)在什么场景下用,为什么要用,有什么作用 4.泛型有了解吗 5.设计模式有了解吗 答MVC、MVP和MVVM。说了MVC两两之间可以交互,M是模型核心(数据库),C是业务逻辑,V是视图(html)。面试官问没有数据库怎么办,我??? 6.Java的工厂模式 https://www.runoob.com/design-pattern/factory-pattern.html 【数据库】 1.一张学生表,一张成绩表,让查所有挂科学生 SELECT Student.Sno, Sname FROM Student, SC WHERE Student.Sno = SC.Sno AND Grade<60 面试官问怎么不用JOIN,后来改成了LEFT OUTER JOIN并且解释了一下,面试官说不对啊,学生表里没记录的学生是不要的,最后直接用JOIN 【计算机网络】 1.TCP和UDP的区别 答案如下: ①TCP面向连接,UDP无连接 ②TCP要求资源较多,UDP较少 ③UDP程序结构更简单 ④TCP面向字节流,UDP面向数据报 ⑤TCP保证数据可靠性,UDP可能丢包
  • 带你一步一步深入Handler源码,再不刷题就晚了!
    2021年1月4日,我终于结束了这两个月以来收到的最好的一个公司的视频面试,短短15分钟,我们双方就再无话题了。我是觉得我不够优秀,配不上这个岗位。面试官可能觉得已经看透我了。 让我回顾一下这稍纵即逝的15分钟: 提前2分钟,面试官到达视频现场并友好提示我们开始,先做个自我介绍。 我一开始就很紧张,自我介绍说的一塌糊涂,毫无重点。但面试官很友好,一直跟我点头互动,认真听我不知所云的自我介绍。我很愧疚自责:我浪费了人家的时间。 然后面试官问我是否在找新的工作机会。我说是的,我很想要上班,看到大家在字节上班都很开心,我也想成为字节风口上的那头猪,我想跟字节一起起飞----我可能有脱口秀的潜质。 面试官问:工作中哪件事情最让你有成就感?我回答说:我带了两个新员工,并鼓励他们积极提出自己的想法,去努力试错,更愿意通过鼓励去激发,而不是批评------这个能体现我有什么能力吗?我真是服了我自己。 一面: 自我介绍介绍一下自己做过什么项目 ps:这里会根据项目问一些问题跨线程通信 主要涉及LooperMessageHandler以及MessageQueue说一下自定义View需要注意哪些细节 主要说了一下View绘制的三大过程onDraw()不要做耗时操作 不要创建新对象 以及 怎么处理View的触摸事件View的事件冲突的解决办法 ps:重写dispatchEvent()或者touch(
  • Android 开发面试题集合整理(内含答案)
    1. 启动一个程序,可以主界面点击图标进入,也可以从一个程序中跳转过去,二者有什么区别? 通过主界面进入,就是设置默认启动的activity。在manifest.xml文件的activity标签中,写以下代码 <intent- filter> <intent android:name=“android.intent.action.MAIN”> <intent android:name=”android:intent.category.LAUNCHER”> </intent-filter> 从另一个组件跳转到目标 activity ,需要通过 intent 进行跳转。具体 Intent intent=new Intent(this,activity.class),startActivity(intent) 2.ArrayList和LinkedList各自特点,应用场景(一加) 参考答案: ArrayList和LinkedList都是实现了List接口的容器类,用于存储一系列的对象引用。他们都可以对元素的增删改查进行操作,那么他们区别、优缺点应用场景都有哪些呢?我们通过源码和数据结构来说明一下 ArrayList和LinkedList的大致区别如下: ArrayList是实现了基于动态数组的数据结构,LinkedList是基于链表结构。 对于随机访问的get和set方法
  • 最新美团java开发3轮技术面+hr面 点评(总结分析)
    面经 首先是Java虚拟机垃圾回收 Synchronized与EntrantLock的区别。 数据库事务隔离 LRU最近事物 快排 大根堆 大部分都能答上,但是回答的不够深入 题目 Java虚拟机垃圾回收 Synchronized与ReentrantLock的区别 数据库事务隔离 LRU最近事物 快排 大根堆 总结 Java虚拟机垃圾回收 Synchronized与ReentrantLock的区别 数据库事务隔离 LRU最近事物 快排 大根堆 技术一面: 一面基本上面的还算基础 没有过多的刁难 不过问题量特别大 1、自我介绍 巴拉巴拉,大致说了下实习和自己平时自己喜欢做的事情 2、我们先聊聊java基础知识吧,说出Object类的常用方法? toString(),clone(),线程的三个方法:wait()notify()notifyAll(),hashcode(),finallize(),equals() 大致讲了上面这些个方法 并说了下各个方法的作用。 3、刚刚说到object的常用方法,这些方法你应该都很熟悉吧,那你说说你对equal和hashcode这两个方法的认识? 主要谈了下两个的作用 以及 当重写equal方法时候一定要记得重写hashcode方法,然后面试官就问了为什么,然后就继续巴拉巴拉。。。 然后面试官紧接着问
  • GitHub上标星75k+的《Java面试突击版》到底有多牛?看完内容我服了!
    前言 不论是校招还是社招都避免不了各种面试。笔试,如何去准备这些东西就显得格外重要。不论是笔试还是面试都是有章可循的,我这个有章可循‘说的意思只是说应对技术面试是可以提前准备。 运筹帷幄之后,决胜千里之外!不打毫无准备的仗,我觉得大家可以先从下面几个方面来准备面试: 1.自我介绍。(你可千万这样介绍: “我叫某某,性别,来自哪里,学校是哪个,自己爱干什么”,记住:多说点简历_上没有的,多说点自己哪里比别人强! )2.自己面试中可能涉及哪些知识点、哪些知识点是重点。3.面试中哪些问题会被经常问到、面试中自己该如何回答。(强烈不推荐背题,第一:通过背这种方式你能记住多少?能记住多久? 第二:背题的方式的学习很难坚持下去! )4.自己的简历该如何写。 "80%的offer掌握在20%的人手中"这句话也不是不无道理的。决定你面试能否成功的因素中实力固然占有很大一部分比例,但是如果你的心态或者说运气不好的话,依然无法拿到满意的offer。运气暂且不谈,就拿心态来说,千万不要因为面试失败而气馁或者说怀疑自己的能力,面试失败之后多总结一下失败的原因,后面你就会发现自己会越来越强大。 另外,大家要明确的很重要的几点是: 1.写在简历上的东西一定要慎重,这可能是面试官大量提问的地方;2.大部分应届生找工作的硬伤是没有工作经验或实习经历;3.将自己的项目经历完美的展示出来非常重要。
  • 面试刷题10-27
    一面 电话面 +二面视频面 1.内存模型 2.谈谈了解的集合,arraylist和linkedlist区别,使用场景,concurrenthashmap讲一下。 3.线程同步的方式,synchronized和lock有啥区别,volatile讲一下,AtomicInteger怎么自增的,cas自旋 4.垃圾回收算法,回收机制。 1.标记-清除算法 2.复制算法(Java堆中新生代的垃圾回收算法) 3.标记-压缩算法(或称为标记-整理算法,Java堆中老年代的垃圾回收算法) 5.myisam和innodb的区别,事务的隔离级别,索引讲一下,什么情况下用索引 6.string, stringbuilder和stringbuffer有啥不一样 7.手撕代码就很简单的算法,忘了 8.平时怎么自学java的,通过哪些途径。 9.redis了解么,rdb和aof的区别,slot有什么作用 10.NIO了解么,讲一下 11.线程池了解么,各个参数干嘛的,具体讲一下 12.TCP和UDP区别,三次握手四次挥手,如何保证可靠性,拥塞控制讲一下 三面 1.怎么学的java啊(感觉都爱问我这个,因为我java是纯自学吧,想通过这个看我都学了啥然后决定提什么样的问题) 2.设计模式了解么,单例讲一下,懒汉饿汉double-check,怎么实现。策略模式,代理模式,适配器模式,装饰器模式,分别讲一下 3
  • 面试新东方,java集合框架把我问懵了
    今天视频面试了新东方,笔试题还算可以,涵盖面很广,多数都是牛客网上的题,笔试用了30分钟吧。有想知道笔试题的小伙伴私聊,这里就不展示了。 由于疫情期间,没去现场面试,是在钉钉上进行的视频面试。两个人都相互带着口罩,讲实话挺不舒服的。由于网络原因,一开始的通话质量不清晰,我感觉会影响面试效果。 常规自我介绍,说一下做过的项目,哪里出彩,之前公司是哪些等等。然后面试官看了看我的简历后,也没问业务,开始让我介绍一下集合框架。我印象中的集合框架就是collection,List、Map,这些算是项目中经常用到的,用来存取数据的。 然后让我以list举例,说说他的子集还有什么,Map的子集、实现接口都有什么。我承认我当时懵了,常用的arraylist、hashmap还算了解,面试题上见过,hashtable、hashset、set等,但是细细追问我一定答不上来,我就以list举例,简单说了说数据结构,和自己的源码。因为自己研究过hashmap源码及其数据结构,就深入地说了一下hashmap源码及设计。以为这样就可以了,面试官继续追问,讲讲set和list的区别。 list,set一个是有序集合,一个是无序集合。list可以存相同数据,set不能存相同数据。这些我还是知道的,但是面试官问set的源码,我只能承认自己没看过,set用的不多。 然后又问linklist的数据结构和源码