LinkedList详解

Linked + List = 链表 + 列表 = LinkedList = 链表列表

 初始化

与ArrayList不同,LinkedList初始化不需要创建数组,因为它是一个链表结构。而且也没有传给构造函数初始化多少个空间的入参,例如这样是不可以的,如下;

但是,构造函数一样提供了和ArrayList一些相同的方式,来初始化入参,如下这四种方式;

@Test
public void test_init() {
	// 初始化方式;普通方式 
	LinkedList <String> list01 = new LinkedList <String> ();
	list01.add("a");
	list01.add("b");
	list01.add("c");
	System.out.println(list01);
	// 初始化方式;Arrays.asList 
	LinkedList <String> list02 = new LinkedList <String> (Arrays.asList("a", "b", "c"));
	System.out.println(list02);
	// 初始化方式;内部类
	LinkedList <String> list03 = new LinkedList <String> (){
		{
			add("a");
			add("b");
			add("c");
		}
	};
	System.out.println(list03);
	// 初始化方式;Collections.nCopies 
	LinkedList <Integer> list04 = new LinkedList <Integer> (Collections.nCopies(10, 0));
	System.out.println(list04);
}

 

插入

LinkedList的插入方法比较多,List中接口中默认提供的是add,也可以指定位置插入。但在LinkedList中还提供了头插addFirst和尾插addLast。
关于插入这部分就会讲到为什么;有的时候LinkedList插入更耗时、有的时候ArrayList插入更好。

头插

看上图我们可以分析出几点;
1. ArrayList头插时,需要把数组元素通过Arrays.copyOf的方式把数组元素移位,如果容量不足还需要扩容。
2. LinkedList头插时,则不需要考虑扩容以及移位问题,直接把元素定位到首位,接点链条链接上即可

private void linkFirst(E e) {
	final Node <E> f = first;
	final Node <E> newNode = new Node <> (null, e, f);
	first = newNode;
	if(f == null) last = newNode;
	else f.prev = newNode;
	size++;
	modCount++;
}

first,首节点会一直被记录,这样就非常方便头插。

插入时候会创建新的节点元素,new Node<>(null, e, f),紧接着把新的头元素赋值给first。

之后判断f节点是否存在,不存在则把头插作为最后一个、存在则用f节点的上一个链条prev链接。

最后记录size大小、和元素数量modCount 。modCount用在遍历时做校验,modCount != expected

尾插

 看上图我们可以分析出几点;
1. ArrayList尾插时,是不需要数据位移的,比较耗时的是数据的扩容时,需要拷贝迁移。
2. LinkedList尾插时,与头插相比耗时点会在对象的实例化上。 

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++;
}

与头插代码相比几乎没有什么区别,只是first换成last

耗时点只是在创建节上,Node<E>

中间插

看上图我们可以分析出几点;
1. ArrayList中间插入,首先我们知道他的定位时复杂度是O(1),比较耗时的点在于数据迁移和容量不足的时候扩容。

2. LinkedList中间插入,链表的数据实际时候并不会怎么耗时,但是它定位元素的时间复杂度是O(n),所以这部分及元素的实例化比较耗时。

//使用add(位置、元素)方法插入:
public void add(int index, E element) {
    checkPositionIndex(index);
    if(index == size) 
        linkLast(element);
    else 
        linkBefore(element, node(index));
}

//位置定位node(index):
Node <E> node(int index) {
    // assert isElementIndex(index);  size >> 1 ,这部分的代码判断元素位置在左半区间,还是右半区间,在进行循环查找。
    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;
    }
}

//执行插入
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++;
}

删除

确定出要删除的元素 ,把前后的链接进行替换。
如果是删除首尾元素,操作起来会更加容易,这也就为什么说插入和删除快。但中间位置删除,需要遍历找到对应。

//元素定位
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;
}

//unlink(x)解链
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;
}

1. 获取待删除节点的信息;元素item、元素下一个节点next、元素上一个节点prev 。
2. 如果上个节点为空则把待删除元素的下一个节点赋值给首节点,否则把待删除节点的下一个节点,赋值给待删除节点的上一个节点的子节点。
3. 同样待删除节点的下一个节点next,也执行2步骤同样操作。 
4. 最后是把删除节点设置为null,并扣减size和modeCount数量。

遍历

普通for循环

@Test
public void test_LinkedList_for0() {
	long startTime = System.currentTimeMillis();
	for(int i = 0; i < list.size(); i++) {
		xx += list.get(i);
	}
	System.out.println("耗时:" + (System.currentTimeMillis() - startTime));
}

增强for循环

@Test
public void test_LinkedList_for1() {
	long startTime = System.currentTimeMillis();
	for(Integer itr: list) {
		xx += itr;
	}
	System.out.println("耗时:" + (System.currentTimeMillis() - startTime));
}

 Iterator遍历

@Test
public void test_LinkedList_Iterator() {
	long startTime = System.currentTimeMillis();
	Iterator < Integer > iterator = list.iterator();
	while(iterator.hasNext()) {
		Integer next = iterator.next();
		xx += next;
	}
	System.out.println("耗时:" + (System.currentTimeMillis() - startTime))
}

forEach循环

@Test
public void test_LinkedList_forEach() {
	long startTime = System.currentTimeMillis();
	list.forEach(integer - > {
		xx += integer;
	});
	System.out.println("耗时:" + (System.currentTimeMillis() - startTime));
}

stream(流)

@Test
public void test_LinkedList_stream() {
	long startTime = System.currentTimeMillis();
	list.stream().forEach(integer - > {
		xx += integer;
	});
	System.out.println("耗时:" + (System.currentTimeMillis() - startTime));
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值