java Collection-LinkedList

本文详细介绍了LinkedList的概念、源码分析,包括添加、删除元素的过程,以及与ArrayList的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、LinkedList的概念

1.LinkedList实现了List的接口,元素中允许存在null,并且内部的元素有序并且每个元素都有索引值;

2.LinkedList是以链表的形式存储数据的,因此对增加和删除元素有很高的效率,但是查询效率低,需要对链表进行遍历;

3.LinkedList继承了AbstractSequentialList,所以可以通过索引来对元素进行增删改查的操作;

4.LinkedList实现了Deque接口,因此可以被用作双向链表的数据结构来操作元素;

5.LinkedList是不同步的,如果有多个线程同时访问一个链表,并且其中一个线程改变了链表的结构,如(insert、remove),那么必须保持外部同步,这种情况下一般是通过对自然封装该列表的对象进行同步操作来完成,如果不存在这样的对象,应该使用Collections.synchronizedList方法来包装该列表,如:

List list = Collections.synchronizedList(new LinkedList(...));

二、LinkedList源码分析

1.LinkedList的结构


在源码中,最开始就定义了两个变量,

(1)一个是Entry实体,在源码中也可以看到LinkedList是通过实体类Entry来实现链表的存储的,这个类住哟啊是用来表示链表中的一个节点,主要包括对上一个节点的引用,链接下一个节点的引用和节点的属性三个元素;

(2)初始定义链表的表头header时不包含任何数据;

(3)size表示LinkedList中元素的个数,初始化为0;


2.LinkedList的初始化

LinkedList的初始化主要有两种方法,一是初始化一个空的list,其中不包含任何元素,此时header的后置节点和前置节点军事他本身,而是初始化一个包含指定集合元素的list

      


3、LinkedList添加元素

    LinkedList添加元素主要有三种方法,但他们最终都是调用addBefore方法,

     //添加一个元素在队列的头部
	 public void addFirst(E e) {
		addBefore(e, header.next);
     }
	 //添加一个元素在队列的尾部
	 public void addLast(E e) {
		 addBefore(e, header);
	 }
	 //添加一个元素在指定的位置
	 public void add(int index, E element) {
	     addBefore(element, (index==size ? header : entry(index)));
	 }
	 
	 private Entry<E> addBefore(E e, Entry<E> entry) {
		Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
		newEntry.previous.next = newEntry;
		newEntry.next.previous = newEntry;
		size++;
		modCount++;
		return newEntry;
    }

   addBefore(E e, Entry<E> entry)是LinkedList中的一个私有方法,当往LinkedList中插入一个元素的时候,都需要调用addBefore方法,其中传递的参数是:要插入的元素Element,要插入的元素的后置节点next,

下面说一下插入一个元素主要的步骤:

(1)当在一个空的LinkedList中插入一个元素时:

 

     (2)当向LinkedList中的某个index位置插入元素时,其处理方法与上面类似:

            例如在LinkedList中已经有三个元素A,B,C,此时需要在A和B中间插入一个元素D,所需的步骤主要有三步:

           ①初始化一个新的节点D,此时他的后置结点指向B,他的前置结点指向A

           ②修改A的后置结点由原本的B改为D

           ③修改B的前置结点由原本的A改为D



4.LinkedList删除元素

LinkedList的删除元素的操作主要有4种,但最终都需要调用remove(Entry e)

//删除队头元素:删除header的后置结点元素
	 public E removeFirst() {
		return remove(header.next);
	 }
	 //删除队尾元素:删除header的前置结点元素
	 public E removeLast() {
		return remove(header.previous);
	 }
	 //删除指定位置元素
	 public E remove(int index) {
	    return remove(entry(index));
	 }
	 //删除指定值得元素
	 public boolean remove(Object o) {
	    if (o==null) {
	        for (Entry<E> e = header.next; e != header; e = e.next) {
	           if (e.element==null) {
	              remove(e);
	              return true;
	           }
	        }
	   } else {
	        for (Entry<E> e = header.next; e != header; e = e.next) {
	           if (o.equals(e.element)) {
	               remove(e);
	               return true;
	           }
	        }
	   }
	   return false;
	}
	 //删除指定结点元素
	 private E remove(Entry<E> e) {
		 if (e == header)
			 throw new NoSuchElementException();
		 E result = e.element;
		 e.previous.next = e.next;
		 e.next.previous = e.previous;
		 e.next = e.previous = null;
		 e.element = null;
		 size--;
		 modCount++;
		 return result;
     }

在remove(Entry e)中删除元素主要有几步:

e.previous.next = e.next :将要删除的结点的前置结点的后置结点修改为删除结点的后置结点

e.next.previous = e.prevoius:将要删除的结点的后置结点的前置结点修改为删除结点的前置结点

e.next = e.previous = null; e.element = null:清空要删除的结点,将他的前置结点和后置结点都置为空,元素值也为空

此时删除操作完成


5.Entry(index):根据索引获取元素

       在add、remove、get等操作中均有根据index获取结点的操作,在这些操作中都需要调用entry(int index)方法,根据双向链表的特性,查找某一个元素都需要对链表进行遍历,而为了提高查询的效率,可以根据要查询的位置决定是从头开始遍历还是从尾部开始遍历。

    首先需要对查询的位置信息进行检查,如果查询的位置在链表的前半部分,那么从头部开始查询,如果查询的位置在链表的后半部分,那么从尾部开始查询,这样使得每次遍历查询的位置不会超过链表长度的一半。  

private Entry<E> entry(int index) {
        if (index < 0 || index >= size)
            throw new IndexOutOfBoundsException("Index: "+index+
                                                ", Size: "+size);
        Entry<E> e = header;
        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;
    }

6.LinkedList中的ListItr

在LinkedList中实现了ListIterator,他主要通过ListItr对LinkedList进行遍历和修改

   
 //从指定位置的元素开始进行遍历:
public ListIterator<E> listIterator(int index) {
	return new ListItr(index);
}

private class ListItr implements ListIterator<E> {
	private Entry<E> lastReturned = header;
	private Entry<E> next;
	private int nextIndex;
	private int expectedModCount = modCount;

	//首先对index进行检查,先对LinkedList进行遍历,查询到指定的位置,这里遍历的方法与前面的一致,根据size和index决定是从头部还是尾部开始遍历查找到指定结点
	ListItr(int index) {
		if (index < 0 || index > size)
			throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
		if (index < (size >> 1)) {
			next = header.next;
			for (nextIndex=0; nextIndex<index; nextIndex++)
				next = next.next;
		} else {
			next = header;
			for (nextIndex=size; nextIndex>index; nextIndex--)
				next = next.previous;
		}
	}
        //判断指定位置的结点后面是否还有元素
	public boolean hasNext() {
		return nextIndex != size;
	}
        //获取指定位置的结点的后置结点
	public E next() {
		checkForComodification();
		if (nextIndex == size)
			throw new NoSuchElementException();

		lastReturned = next;
		next = next.next;
		nextIndex++;
		return lastReturned.element;
	}
        //判断指定位置的结点是否有前置结点
	public boolean hasPrevious() {
		return nextIndex != 0;
	}
        //获取指定位置的结点的前置结点
	public E previous() {
		if (nextIndex == 0)
			throw new NoSuchElementException();

		lastReturned = next = next.previous;
		nextIndex--;
		checkForComodification();
		return lastReturned.element;
	}
        //获取index的下一个索引值
	public int nextIndex() {
		return nextIndex;
	}
        //获取index的前一个索引值
	public int previousIndex() {
		return nextIndex-1;
	}
       //删除index结点
	public void remove() {
		checkForComodification();
		Entry<E> lastNext = lastReturned.next;
		try {
		     LinkedList.this.remove(lastReturned);
		} catch (NoSuchElementException e) {
		     throw new IllegalStateException();
		}
		if (next==lastReturned)
		     next = lastNext;
		 else
		      nextIndex--;
		lastReturned = header;
		expectedModCount++;
	}
        //修改index的结点值
	public void set(E e) {
		if (lastReturned == header)
			throw new IllegalStateException();
		checkForComodification();
		lastReturned.element = e;
	}
        //在index位置增加一个节点
	public void add(E e) {
		checkForComodification();
		lastReturned = header;
		addBefore(e, next);
		nextIndex++;
		expectedModCount++;
	}
	//此方法用来判断创建迭代对象的时候List的modCount与现在List的modCount是否一样,不一样的话就报ConcurrentModificationException异常
	final void checkForComodification() {
		if (modCount != expectedModCount)
			throw new ConcurrentModificationException();
		}
	}
}

    List对象有一个成员变量modCount,它代表该List对象被修改的次数,每对List对象修改一次,modCount都会加1.

    ListItr中有一个成员变量expectedModCount,他的值在创建ListItr对象时的modCount值,他主要是用来检查在迭代的过程中List的对象是否被修改过,如果修改了就会抛出异常,而每次调用itr的next方法时都会调用checkForComdification方法进行检验,他的主要操作就是检查expectedModCount和modCount的值是否相等,如果不相等,就认为list被修改过

    下面是ListItr中的主要方法执行的示例:

public static void main(String[] args) {
		//初始化一个LinkedList,里面包含元素A,B,C,D
		LinkedList list = new LinkedList<String>();
		list.add('A');
		list.add('B');
		list.add('C');
		list.add('D');
		//从index=2的位置进行iterator
		ListIterator it = list.listIterator(2);
		System.out.println("判断index=2是否有后置结点:"+it.hasNext());  //结果应该为true
		System.out.println("获取index=2的后置结点的值:"+it.next());  //结果应该为C
		System.out.println("判断index=2是否有前置结点:"+it.hasPrevious());  //结果为true
		System.out.println("判断index=2的前置结点的值:"+it.previous());  //结果为A
		System.out.println("获取index=2的下一个index:"+it.nextIndex());  //结果应该为3
		System.out.println("获取index=2的前一个index:"+it.previousIndex()); //结果应该为1
		it.remove();  //删除index的结点,此时链表中的值为:A,B,D
		it.add("E");  //在index位置增加E元素,此时链表中的值为A,B,E,D
                it.set('C');   //修改index位置的元素为C,此时链表中的值为A,B,C,D
}


三、LinkedList与ArrayList比较

(1)ArrayList本质上是数组结构;LinkedList本质上是链表的数据结构;

(2)对于随机访问的get和set,ArrayList可以直接获取到指定的位置,而LinkedList需要对链表进行遍历;

(3)对于add和remove等需要修改结构的操作,ArrayList需要对数组中的元素进行移动,而LinkedList只需要对元素的前置和后置结点进行操作,比较方便;

(4)总之,ArrayList更适合对数据进行读取操作,LinkedList更适合对数据进行增加和删除操作


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值