Java集合之LinkedList详解

Java集合之LinkedList

一、LinkedList类的继承关系

LinkedList

1. 基类功能说明

1.1. Iterator:提供了一种方便、安全、高效的遍历方式。

  1. Iterator是一个迭代器接口,它提供了一种安全的遍历集合元素的方法,可以避免在遍历过程中修改集合引起的ConcurrentModificationException异常,同时还可以避免在遍历过程中删除集合元素时出现索引越界等问题。

  2. LinkedList使用Iterator遍历集合时,可以使用hasNext()方法判断是否还有元素,使用next()方法获取下一个元素,使用remove()方法安全地删除当前元素。因此,使用Iterator遍历LinkedList既方便又安全,是一种非常推荐的遍历方式。

  3. 另外,Iterator接口是Java集合框架中的一部分,实现Iterator接口可以使LinkedList更加符合集合框架的统一标准,方便与其他集合类一起使用。

1.2. Collection:为了使ArrayList具有集合的基本特性和操作。

  1. LinkedList是Java中常用的集合类之一,它实现了Collection接口,这是为了使LinkedList具有集合的基本特性和操作,例如添加、删除、遍历等。
  2. 实现Collection接口可以让LinkedList能够和其他Java集合类兼容,可以方便地进行集合之间的转换和使用。同时,实现Collection接口也使得LinkedList能够支持泛型,可以在编译期间进行类型检查,避免了运行时出现类型错误的问题。
  3. 总之,LinkedList实现Collection接口是为了使其具有更多的特性和更好的兼容性。

1.3. Queue: LinkedList是一种队列(Queue)数据结构的实现。

  1. Queue接口定义了一些基本的队列操作,例如添加元素、删除元素、查看队首元素等,LinkedList实现了这些操作,因此可以被视为一种队列数据结构。

1.4. List:为了让其具有列表的特性和更多的操作。

  1. ArrayList实现了List接口,是为了使其具有列表的特性和操作,例如按索引访问元素、插入、删除、替换等。List是Collection接口的子接口,提供了更多针对列表的操作。
  2. 例如按索引操作、排序、子列表等。因此,实现List接口可以让ArrayList具有更多的操作和更好的兼容性,可以和其他实现List接口的Java集合类进行交互和转换。

1.5. AbstractCollection:提供了一些通用的集合操作。

  1. 例如containsAll、removeAll、retainAll等方法,这些方法可以被ArrayList直接继承和使用,从而避免了重复实现这些操作。
  2. 同时,AbstractCollection也提供了一些抽象方法,例如size、iterator、toArray等方法,这些方法需要由具体的集合类去实现,LinkedList也需要实现这些方法以完成自身的功能。

1.6. Deque: 使得LinkedList可以满足更多的使用场景,例如实现双端队列、实现栈等数据结构。

  1. 这使得LinkedList可以满足更多的使用场景,例如实现双端队列、实现栈等数据结构。同时,LinkedList还可以在队首和队尾进行高效的插入和删除操作,因为它的底层实现是双向链表,而双向链表可以在常数时间内进行插入和删除操作。
  2. 因此,LinkedList继承Deque接口的作用在于让LinkedList具备了双端队列的功能,同时也提高了LinkedList的灵活性和可用性。

1.7. AbstractList:为了使其具有列表的抽象特性和操作。

  1. AbstractList是List接口的一个抽象实现,实现了List接口的大部分方法,包括添加、删除、获取元素、遍历等。

  2. LinkedList作为List接口的一个具体实现,需要实现List接口中定义的所有方法。

  3. 通过继承AbstractList抽象类,LinkedList可以重用AbstractList中已经实现的方法,减少重复代码,提高代码的复用性和可维护性。

  4. 另外,AbstractList还提供了一些抽象方法,例如get()、set()、add()、remove()等,这些抽象方法需要LinkedList子类实现,从而使得LinkedList具备列表的基本操作。

  5. AbstractList还提供了一些模板方法,例如addAll()、removeAll()等,这些方法可以通过调用抽象方法实现具体的操作。通过继承AbstractList抽象类,LinkedList可以利用这些模板方法快速实现各种列表操作,提高了开发效率。

  6. 综上所述,LinkedList实现了AbstractList抽象类是为了重用AbstractList中已经实现的方法,提高代码的复用性和可维护性,同时也能够利用AbstractList中提供的模板方法快速实现各种列表操作。

1.8. AbstractSequentialList:可以更方便地实现List接口中的方法,同时也提供了一些保护方法,供LinkedList实现自己特有的方法。

  1. AbstractSequentialList是一个抽象类,它实现了List接口中大部分方法,例如add、remove、get、set等。而这些方法的实现都是基于迭代器(Iterator)进行的,迭代器是用于遍历集合中元素的一个对象。因此,继承AbstractSequentialList可以让LinkedList更容易地实现List接口中的这些方法,只需要实现迭代器即可。

  2. 另外,AbstractSequentialList还提供了一些保护方法(protected method),例如getIterator、getListIterator等,可以供子类使用。LinkedList继承了AbstractSequentialList后,可以使用这些保护方法来实现自己特有的方法。

  3. 总之,LinkedList继承AbstractSequentialList的作用在于可以更方便地实现List接口中的方法,同时也提供了一些保护方法,供LinkedList实现自己特有的方法。

1.9. Cloneable:支持克隆操作。

  1. Cloneable接口是一个标记接口,用于表示实现该接口的对象可以进行克隆操作。

  2. 在LinkedList中,克隆操作可以用于创建一个与原列表相同的新列表,这个新列表与原列表相互独立,对新列表的修改不会影响到原列表。这在某些场景下非常有用,例如需要对一个列表进行操作,但是又需要保留原列表不变的情况下,可以先克隆出一个新列表进行操作。

  3. 需要注意的是,LinkedList实现Cloneable接口只是表示它支持克隆操作,并不代表它的克隆操作一定是完全正确和安全的。在进行克隆操作时,需要注意可能存在的浅拷贝和深拷贝问题,以及可能会影响到对象的不变性和线程安全性问题等。

  4. 因此,在使用LinkedList的克隆操作时,需要仔细考虑其对程序的影响,并进行必要的安全性和正确性检查。

10. Serializable:支持序列化。

  1. 序列化是将对象转换为字节流的过程,可以用于持久化对象、网络传输等操作。实现Serializable接口可以让LinkedList的实例对象被序列化,以便于在需要的时候进行序列化操作。

二、LinkedList的优缺点

1. 优点:

1.1. 高效的插入和删除操作

  1. 由于LinkedList底层实现是双向链表,因此在插入和删除操作方面具有高效性。在链表的任意位置插入或删除一个元素的时间复杂度是O(1),而在数组中插入或删除元素的时间复杂度是O(n)。

1.2. 不需要预先分配空间

  1. 由于LinkedList是动态的数据结构,因此在使用时不需要预先分配空间,可以根据需要动态地添加或删除元素,使得空间利用率更高。

1.3. 可以作为双端队列使用

  1. 由于LinkedList实现了Deque接口,因此可以在队首和队尾进行高效的插入和删除操作,使得LinkedList可以作为双端队列使用。

2. 缺点:

2.1. 随机访问性能较差

  1. 由于LinkedList底层实现是链表,因此在访问某个元素时需要从头开始遍历整个链表,时间复杂度为O(n)。而在数组中,访问某个元素的时间复杂度为O(1)。

2.2. 占用更多的内存空间

  1. 由于LinkedList需要存储指向下一个元素和上一个元素的指针,因此占用的内存空间相对于数组要更多。

2.3. 不支持随机访问

  1. 由于LinkedList是基于链表实现的,因此不支持随机访问,只能通过遍历整个链表来访问元素。这也使得LinkedList在某些场景下性能较差,例如在对元素进行排序、查找等操作时。

3. 总结

  1. 总之,LinkedList的优点在于高效的插入和删除操作、不需要预先分配空间、可以作为双端队列使用;缺点在于随机访问性能较差、占用更多的内存空间、不支持随机访问。因此,在使用LinkedList时需要根据具体的场景进行选择。

三、LinkedList数据结构

数据结构

四、LinkedList源码分析

1. 基本属性

1.1 size :节点个数

transient int size = 0;

1.2 first:头结点

transient Node<E> last;

1.3 last:尾结点

transient Node<E> last;

2. 构造方法

public LinkedList() {
}
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
  1. 无参构造方法:因为底层是双向链表,所以无需指定初始长度。
  2. 有参构造方法:创建一个包含指定Collection中所有元素的LinkedList对象。
  3. 这两个构造方法的使用场景如下:
    3.1 无参构造方法可以用于创建一个空的LinkedList对象,然后通过add()方法逐个添加元素。
    3.2 带有Collection参数的构造方法可以用于创建一个包含指定元素的LinkedList对象,例如:
List<Integer> list = Arrays.asList(1, 2, 3);
LinkedList<Integer> linkedList = new LinkedList<>(list);
  1. 总之,LinkedList的两个构造方法分别用于创建一个空的LinkedList对象和创建一个包含指定元素的LinkedList对象。通过这两个构造方法,我们可以实现LinkedList的快速创建和初始化。

3. add方法

public boolean add(E e) {
	linkLast(e);
	return true;
}
public void add(int index, E element) {
	checkPositionIndex(index);
	if (index == size)
	    linkLast(element);
	else
	    linkBefore(element, node(index));
}

add(E e) :调用linkLast方法将当前元素添加到链表尾部。(linkLast见下文)
add(int index, E element):

  1. 调用checkPositionIndex校验下标是否越界(index >= 0 && index <= size)。
  2. 如果要添加的元素下标位于链表的最后一个,则调用linkLast方法追加到链表尾部。
  3. 否则不是最后一个,调用linkBefore方法将元素插入指定位置。(linkBefore见下文)。

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;
}
  1. 调用checkPositionIndex校验下标是否越界(index >= 0 && index <= size)
  2. 调用node(index) (见下文)
  3. 获取要插入结点的原值oldVal
  4. 将当前元素设置找到的结点位置
  5. 返回结点的原值(oldVal )

5. get方法

public E get(int index) {
	 checkElementIndex(index);
	 return node(index).item;
}
public E getFirst() {
	 final Node<E> f = first;
	 if (f == null)
	     throw new NoSuchElementException();
	 return f.item;
}

get(int index):

  1. 调用checkPositionIndex校验下标是否越界(index >= 0 && index <= size)
  2. 调用node(index) (该方法见下文)
    getFirst():
  3. 返回链表的头结点

6. remove方法

public boolean remove(Object o) {
	 if (o == null) {
	     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) {
	         if (o.equals(x.item)) {
	             unlink(x);
	             return true;
	         }
	     }
	 }
 return false;
}
public E remove(int index) {
	 checkElementIndex(index);
	 return unlink(node(index));
}

remove(int index):

  1. 调用checkPositionIndex校验下标是否越界(index >= 0 && index <= size)
  2. 调用node(index) (见下文)
  3. 调用unlink方法,移除index位置的节点(unlink方见下文)

remove(Object o) :

  1. 如果 o == null 从链表的第一个结点first开始遍历,寻找结点item属性为null的结点,调用unlink()方法将该节点移除。
  2. 如果 o 不等于 null 从链表的第一个结点first开始遍历,寻找item属性与之相同的为null的结点,调用unlink()方法将该节点移除。

7. clear方法

public void clear() {
    // Clearing all of the links between nodes is "unnecessary", but:
    // - helps a generational GC if the discarded nodes inhabit
    //   more than one generation
    // - is sure to free memory even if there is a reachable Iterator
    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;
    modCount++;
}
  1. 从头结点first开始遍历,将所有属性item、next、prev置为null。
  2. 将头尾结点置为null。
  3. 将size属性设置为0。
  4. 将modCount++,在这个过程中,LinkedList的结构发生了变化,因此modCount需要+1。

8. node(index)方法

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;
    }
}
  1. 根据下标调用node(index)找到目标结点
  2. 判断当前下标位于链表的左侧还是右侧(size >> 1)
  3. 如果是左侧从first结点开始遍历
  4. 否则从链表的尾部last结点往前遍历,根据prev指针遍历

9. unlink方法

E unlink(Node<E> x) {//移除链表上的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;
}
  1. 分别获取当前结点的ele元素、前驱结点prev、后继结点next
  2. 如果prev等于null,则头结点first指向next,否则将prev的下一个结点指向当前结点的下一个结点。
  3. 如果next等于null,将last指向当前要删除结点的前一个结点,也即prev。否则将next的前驱结点指向要删除的前驱结点prev,同时将当前要删除的结点的next指向为null
  4. 将当前结点的item元素置为null
  5. size元素数量个数-1
  6. modCount+1,因为链表结构发生改变了

10. linkBefore方法

//将元素e插入succ结点的前面
void linkBefore(E e, Node<E> succ) {
    // assert succ != null;
    final Node<E> pred = succ.prev;
    final Node<E> newNode = new Node<>(pred, e, succ);
    succ.prev = newNode;
    if (pred == null)
        first = newNode;
    else
        pred.next = newNode;
    size++;
    modCount++;
}
  1. 定义succ的前驱结点pred
  2. 构造一个新的结点newNode pred就是前驱结点、e当前元素、succ做为当前newNode 的下一个结点。
  3. 修改succ结点的前驱结点为newNode
  4. 如果pred == null 说明链表中只有一个结点succ,则将newNode 作为头结点,将first指向newNode
  5. 否则pred 不等于null,修改pred的后续结点指向,将pred.next 指向刚刚构造出来的新结点newNode
  6. 将size+1
  7. 修改modCount

助解图:

linkbefore

11. 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++;
}
  1. 将变量 l 指向链表的最后一个结点last(暂存)。
  2. 把传进来的元素e,构造一个新的结点newNode。
  3. 将last引用指向newNode。
  4. 如果l == null,说明链表中没有元素,将first指向newNode,做为头结点。
  5. 否则l 不等于null,将 newNode 追加为链表的最后一个元素。
  6. 将链表的size元素数量+1,modCount+1,用来实现快速失败机制。

五、常见遍历方式

LinkedList有以下4种常见的遍历方式:

1、使用for循环遍历:

可以使用for循环和get方法,从头到尾遍历LinkedList中的元素。

for (int i = 0; i < linkedList.size(); i++) {
    E element = linkedList.get(i);
    // 处理当前元素
}

2、使用foreach循环遍历:

可以使用foreach循环,从头到尾遍历LinkedList中的元素。

for (E element : linkedList) {
    // 处理当前元素
}

3、使用Iterator遍历:

可以使用Iterator,从头到尾遍历LinkedList中的元素。

Iterator<E> iterator = linkedList.iterator();
while (iterator.hasNext()) {
    E element = iterator.next();
    // 处理当前元素
}

4、使用ListIterator遍历:

可以使用ListIterator,从头到尾遍历LinkedList中的元素,还可以在遍历时进行添加、删除、修改等操作。

ListIterator<E> listIterator = linkedList.listIterator();
while (listIterator.hasNext()) {
    E element = listIterator.next();
    // 处理当前元素
}
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Run,boy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值