LinkedList

2 篇文章 0 订阅
1 篇文章 0 订阅

LinkedList学习

这篇文章主要讲的是LinkedList。从数据结构,增删改查的原理入手。有错误的地方,欢迎大家指出,谢谢!

开始学习LinkedList首先要了解它所使用的数据结构。LinkedList使用的是Node,先了解LinkedList中的私有静态类Node。
Node中共有三个字段:
item:存储数据
next:该字段存储下一个Node的地址
prev:该字段存储上一个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;
        }
    }

首先先来了解add方法和存储数据的示意图

先看add方法代码。add方法中直接调用linkLast方法,意思是将e添加到链表的末尾。

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

再来看linkLast方法代码。这里我用三张图来演示它的存储过程。

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

第一次添加元素:
在这里插入图片描述

第二次添加元素:
在这里插入图片描述
第三次添加元素:
在这里插入图片描述

查找元素

直接查看查找元素的代码:首先判断index是否小于size/2,如果是,就从first开始正向遍历,一个一个的通过next向后找到对应的Node。。如果不小于size/2就反向遍历。一个一个的通过prev向前找到对应的Node。

    Node<E> node(int 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;
        }
    }

再来看add的另一个方法

//在指定位置插入元素
   public void add(int index, E element) {
        checkPositionIndex(index);//检查index,大于等于0并且小于size
        if (index == size)//如果index == size其实就是和add(E e)方法一致
            linkLast(element);
        else//node(index)就是查找出目前index所在的那个元素
            linkBefore(element, node(index));
    }
    void linkBefore(E e, Node<E> succ) {//e为即将插入的元素,succ为已经插入的元素
        final Node<E> pred = succ.prev;//取出原来的前一个元素
        final Node<E> newNode = new Node<>(pred, e, succ);//创建新元素
        succ.prev = newNode;//因为succ在新元素的前面,所以succ的prev指向新元素
        if (pred == null)//如果前一个元素为null,说明这个是first,就把新元素设置为first
            first = newNode;
        else//否则将前一个元素的next指向新元素
            pred.next = newNode;
        size++;//增加数量
    }

直接看图:
在这里插入图片描述

addAll方法

addAll(int index, Collection<? extends E> c)和addAll(Collection c)其实也是调用的addAll(int index, Collection c)
首先看源码,可以得出大概的流程就是找到index对应的元素,以及index对应的前一个元素,因为要在这两个元素中添加c中的所有元素。找到这两个元素后就遍历c,新建元素,将新建元素的地址赋值给前一个元素的prev。遍历完成后,判断succ是否为null,如果为null就将c的最后一个元素设置为last

   public boolean addAll(int index, Collection<? extends E> c) {
        checkPositionIndex(index);//检查index是否越界
        Object[] a = c.toArray();
        int numNew = a.length;//总的需要添加的数量
        if (numNew == 0)
            return false;

        LinkedList.Node<E> pred, succ;
        if (index == size) {//如果index==size说明在最后添加
            succ = null;//index对应的位置的元素就是null
            pred = last; //前一个为last也就是index-1对应的元素
        } else {
            succ = node(index);//如果不是在最后就取出index对应的元素
            pred = succ.prev;//前一个就是对应元素的前一个
        }

        for (Object o : a) {
            @SuppressWarnings("unchecked") E e = (E) o;
            //接在元素的后面
            LinkedList.Node<E> newNode = new LinkedList.Node<>(pred, e, null);
            if (pred == null)//说明原来没有元素,就将新建的元素设置为first
                first = newNode;
            else//就把前面一个元素的next指向新建的元素
                pred.next = newNode;
            pred = newNode;
        }
        //此时pred为最后一个需要添加的元素
        if (succ == null) {
            //如果最开始找到的index对应的元素为null,就是从最后面开始添加,那么此时的pred就是最后一个
            last = pred;
        } else {
            //如果最开始找到的index对应的元素不为null,就将此时的pred的next指向最开始index对应的元素
            //然后将最开始index对应的元素的prev指向此时的pred
            pred.next = succ;
            succ.prev = pred;
        }
        size += numNew;//增加size
        modCount++;
        return true;
    }

在这里插入图片描述

addFirst方法

addFirst方法是将元素添加到列表第一个。

public void addFirst(E e) {
	linkFirst(e);//直接调用linkFirst方法
}
private void linkFirst(E e) {
    final Node<E> f = first;
    final Node<E> newNode = new Node<>(null, e, f);//将新元素的next指向f
    first = newNode;//因为要添加到第一个,所以first需要是这个新元素
    if (f == null)//如果原来的first为null表示列表中没有元素,所以这个新元素也是最后一个
        last = newNode;
    else//如果原来的first不是null,就将原来的first的prev指向新元素
        f.prev = newNode;
    size++;//增加size
}
    

addLast方法

addFirst方法是将元素添加到列表最后一个。

    public void addLast(E e) {
        linkLast(e);//直接调用linkLast方法
    }
    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);//将新元素的prev指向原来的last
        last = newNode;//将新元素赋值给last,因为是添加到列表最后,所以新元素为最后一个元素
        if (l == null)//如果原来的lastweinull,说明原来列表没有元素,所以新元素也应该是first
            first = newNode;
        else//如果原来的last不为null,就将原来的last的next指向新元素
            l.next = newNode;
        size++;//增加size
    }
    

clear()

从此列表中删除所有元素。 此调用返回后,列表将为空。循环将所有元素都设置为null,并且将first和last都设置为null,方便GC回收。将size设置为0.

    public void clear() {
        for (Node<E> x = first; x != null; ) {
            Node<E> next = x.next;
            x.item = null;
            x.next = null;
            x.prev = null;
            x = next;
        }
        first = last = null;
        size = 0;
    }

contains方法

查找链表中是否存在指定的元素。查找列表中是指定元素的第一个下标。如果为null就返回第一个为null的下标,如果不为null就返回第一个equals为true的下标。
其中lastIndexOf其实就是反向遍历

	public boolean contains(Object o) {
        return indexOf(o) != -1;
    }
        public int indexOf(Object o) {
        int index = 0;
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null)
                    return index;
                index++;
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item))
                    return index;
                index++;
            }
        }
        return -1;
    }

get方法

get其实就是调用了查找元素的方法,前面有解释。
getFirst和getLast必须保证列表中至少存在1个元素不然会抛出异常直接返回first和last的item。

remove方法

public E remove(int index) {//通过下标删除
    checkElementIndex(index);//检查下标是否正常
    return unlink(node(index));//调用unlink方法,删除都是调用此方法
}
E unlink(Node<E> x) {//x不能为null
    final E element = x.item;
    final Node<E> next = x.next;//前一个元素
    final Node<E> prev = x.prev;//后一个元素
    if (prev == null) {//如果前一个元素为null,说明x是first,因为要删除x,所以就要让next为first
        first = next;
    } else {//如果前一个元素不为null,就把前一个元素的next指向下一个元素的地址
        prev.next = next;
        x.prev = null;
    }
    if (next == null) {//如果下一个元素为null那么就要把前一个元素设置为last
        last = prev;
    } else {//如果不为null,就要把下一个元素的prev指向x前面的元素
        next.prev = prev;
        x.next = null;
    }
    x.item = null;
    size--;//减少size
    return element;//返回删除的元素
}
//删除指定元素,如果存在就删除第一次出现的元素并返回true,如果没有就返回false
public boolean remove(Object o) {
    if (o == null) {
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null) {
            	//如果需要删除的元素为null,就遍历找到第一个null的下标对应的元素
                unlink(x);
                return true;
            }
        }
    } else {
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item)) {
            	//如果需要删除的元素不是null,就删除第一个equals为true的下标对应的元素
                unlink(x);
                return true;
            }
        }
    }
    return false;
}

set方法

    public E set(int index, E element) {
        checkElementIndex(index);//判断index范围
        Node<E> x = node(index);//查询出index对应的元素
        E oldVal = x.item;//设置新的值并且返回原来的值
        x.item = element;
        return oldVal;
    }

获取列表长度

    public int size() {
        return size;
    }

常见面试题

ArrayList与LinkedList的区别?

1、ArrayList的查找和访问元素的速度较快,但新增,删除的速度较慢,因为需要确保容量足够,所以有可能需要扩容有,操作还会移动元素位置,所以速度较慢。LinkedList的查找和访问元素的速度较慢,但是他的新增,删除的速度较快,因为是使用链表的形式。
2、ArrayList需要分配一块连续的内存空间,LinkedList不需要连续的内存空间
3、两个线程都不安全

END

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值