ArrayList和LinkedList增删查改详解

上次面试,被面试官怼着问数据结构,问的我很无奈,后来回去查找资料,总结了这一篇有关java数据结构的增删查改的实现的内容。
(一)ArrayList
1 新增
(1)往ArrayList中添加对象,也就是调用add(element)方法,首先会确认容量是否足够,如果足够,将新加入的元素直接放入,如果直接调用的add(element)方法,会从最后一个元素的末尾开始增加新的元素,如果是调用的add(index,element)方法,则直接从下标为index的位置开始插入新的元素E,该下标靠后位置(index+1)元素向右移一位。
(2)如果容量不够,则需要先扩容,扩容结束后,调用了Arrays.copyOf(elementData, newCapacity)方法,这个方法中:对于我们这里而言,先创建了一个新的容量为newCapacity的对象数组,然后使用System.arraycopy()方法将旧的对象数组复制到新的对象数组中去了。扩容结束之后新增的操作和之前一样。
2 删除
ArrayList删除是调用的remove(index)方法,将下标为index位置之后的元素向前移动一个位置,最后的一个位置设为null,等待gc回收之后,集合的size()减少1。
/*
* 删除单个位置的元素,是ArrayList的私有方法
*/
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)//删除的不是最后一个元素
System.arraycopy(elementData, index + 1, elementData, index,numMoved);//删除的元素到最后的元素整块前移
elementData[–size] = null; //将最后一个元素设为null,在下次gc的时候就会回收掉了
}
3 查询
直接根据下标去寻找对应的值,get(index)
/**
* 按照索引查询对象E
*/
public E get(int index) {
RangeCheck(index);//检查索引范围
return (E) elementData[index];//返回元素,并将Object转型为E
}
4 修改
这里修改调用的是set(int index, E element)方法,找到下标对应的值,直接update
/**
* 更换特定位置index上的元素为element,返回该位置上的旧值
*/
public E set(int index, E element) {
RangeCheck(index);//检查索引范围
E oldValue = (E) elementData[index];//旧值
elementData[index] = element;//该位置替换为新值
return oldValue;//返回旧值
}
总结:所以ArrayList集合新增或者删除一个中间元素会很慢,因为会涉及到很多元素位置的变动,甚至是扩容,花费时间会多一些。
(二)LinkedList
先看一下LinkedList的双向列表结构
这里写图片描述
LinkedList 是基于链表结构实现,所以在类中包含了 first 和 last 两个指针(Node)。Node 中包含了上一个节点和下一个节点的引用,这样就构成了双向的链表。

    transient int size = 0;
    transient Node<E> first; //链表的头指针
    transient Node<E> last; //尾指针
    //存储对象的结构 Node, LinkedList的内部类
    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;
        }

这里写图片描述
1 新增
add(int index, E element),该方法是在指定 index 位置插入元素。如果 index 位置正好等于 size,则调用 linkLast(element) 将其插入末尾;否则调用 linkBefore(element, node(index))方法进行插入。
这里写图片描述

public void add(int index, E element) {
       checkPositionIndex(index);
 //如果index==size,则直接在最末尾位置新增元素,
 //将 last 的 Node 引用指向了一个新的 Node(l),
 //然后根据l新建了一个 newNode,其中的元素就为要添加的 e;
 //而后,我们让 last 指向了 newNode。
       if (index == size)
           linkLast(element);
       else
           linkBefore(element, node(index));
   }
   /**
        * Inserts element e before non-null Node succ.
        * 在指定位置新增元素
        */
       void linkBefore(E e, Node<E> succ) {
           // assert succ != null;
           //node(index)返回指定元素索引处的(非空)节点
           //succ.prev为前一节点
           final Node<E> pred = succ.prev;
           //类似于上面图中,根据pred,element,next生成
           final Node<E> newNode = new Node<>(pred, e, succ);
           //将该节点付给succ.prev,也就是为它的前一节点
           succ.prev = newNode;
           //如果pred为null,则表示是第一个节点位置,
           if (pred == null)
               first = newNode;
           //反之前一节点pred的后一节点为新节点
           else
               pred.next = newNode;
           //大小增加1
           size++;
           modCount++;
       }

2 删除
remove(element),由于删除了某一节点因此调整相应节点的前后指针信息,如下:
e.previous.next = e.next;//预删除节点的前一节点的后指针指向预删除节点的后一个节点。
e.next.previous = e.previous;//预删除节点的后一节点的前指针指向预删除节点的前一个节点。
清空预删除节点:
e.next = e.previous = null;
e.element = null;
交给gc完成资源回收,删除操作结束。
}
3 查询
get(index),首先判断位置信息是否合法(大于等于0,小于当前LinkedList实例的Size),然后遍历到具体位置,获得节点的业务数据(element)并返回。
注意:为了提高效率,需要根据获取的位置判断是从头还是从尾开始遍历。

// 获取双向链表中指定位置的节点    
    private Entry<E> entry(int index) {    
        if (index < 0 || index >= size)    
            throw new IndexOutOfBoundsException("Index: "+index+    
                                                ", Size: "+size);    
        Entry<E> e = header;    
        // 获取index处的节点。    
        // 若index < 双向链表长度的1/2,则从前先后查找;    
        // 否则,从后向前查找。    
        if (index < (size >> 1)) {    
            for (int i = 0; i <= index; i++)    
                e = e.next;    
        } else {    
            for (int i = size; i > index; i--)    
                e = e.previous;    
        }    
        return e;    
    }

4 修改

 //set 很简单,找到这个节点,替换数据就好了
public E set(int index, E element) {
    checkElementIndex(index);
    Node<E> x = node(index);
    E oldVal = x.item;
    x.item = element;
    return oldVal;
}

总结:LinkedList的双向链表结构导致查询比较慢,但是新增和删除的话效率是比较高的,因为只涉及到指向节点的指针的改变,不需要像ArrayList那样需要对数组位置的变动。
后续会对set,map,tree等数据结构做详解,上述可能有不当之处,欢迎指出!

  • 5
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值