linkedlist是用链表结构存储数据的,比较适合数据的动态插入和删除,随机访问和遍历速度比较慢,还提供了list接口i中没有定义的方法,专门用于操作表头和表尾的元素,所以可以当作堆栈、队列和双向队列来使用。linkedlist持有头节点和尾节点的引用,有两个构造器,一个是无参构造器,另一个是传入外部集合构造器,它没有像arraylist一样的初始大小的构造器。
1 //集合元素个数
2
3 transient int size = 0;
4
5
6
7 //头结点引用
8
9 transient node first;
10
11
12
13 //尾节点引用
14 transient node last;
15
16 //无参构造器
17 public linkedlist() {}
18
19 //传入外部集合的构造器
20 public linkedlist(collection extends e> c) {
21 this();
22 addall(c);
23 }
24
25 //增(添加)
26 public boolean add(e e) {
27
28 //在链表尾部添加
29 linklast(e);
30 return true;
31 }
32
33
34 //增(插入)
35 public void add(int index, e element) {
36
37 checkpositionindex(index);
38
39 if (index == size) {
40 //在链表尾部添加
41 linklast(element);
42 } else {
43 //在链表中部插入
44 linkbefore(element, node(index));
45 }
46 }
47
48
49
50 //删(给定下标)
51 public e remove(int index) {
52
53 //检查下标是否合法
54 checkelementindex(index);
55 return unlink(node(index));
56 }
57
58 //删(给定元素)
59 public boolean remove(object o) {
60 if (o == null) {
61 for (node x = first; x != null; x = x.next) {
62 if (x.item == null) {
63 unlink(x);
64 return true;
65 }
66 }
67 } else {
68 //遍历链表
69 for (node x = first; x != null; x = x.next) {
70 if (o.equals(x.item)) {
71 //找到了就删除
72 unlink(x);
73 return true;
74 }
75 }
76 }
77 return false;
78 }
79
80
81
82 //改
83 public e set(int index, e element) {
84
85 //检查下标是否合法
86 checkelementindex(index);
87
88 //获取指定下标的结点引用
89 node x = node(index);
90
91 //获取指定下标结点的值
92
93 e oldval = x.item;
94
95 //将结点元素设置为新的值
96
97 x.item = element;
98
99 //返回之前的值
100
101 return oldval;
102
103 }
104
105
106 //查
107 public e get(int index) {
108
109 //检查下标是否合法
110 checkelementindex(index);
111
112 //返回指定下标的结点的值
113 return node(index).item;
114 }
linkedlist的添加元素的方法主要是调用linklast和linkbefore两个方法,linklast方法是在链表后面链接一个元素,linkbefore方法是在链表中间插入一个元素。linkedlist的删除方法通过调用unlink方法将某个元素从链表中移除。下面是链表的插入和删除操作的核心代码。
1 //链接到指定结点之前
2 void linkbefore(e e, node succ) {
3
4 //获取给定结点的上一个结点引用
5 final node pred = succ.prev;
6
7 //创建新结点, 新结点的上一个结点引用指向给定结点的上一个结点
8 //新结点的下一个结点的引用指向给定的结点
9 final node newnode = new node<>(pred, e, succ);
10
11 //将给定结点的上一个结点引用指向新结点
12 succ.prev = newnode;
13
14 //如果给定结点的上一个结点为空, 表明给定结点为头结点
15 if (pred == null) {
16
17 //将头结点引用指向新结点
18 first = newnode;
19 } else {
20 //否则, 将给定结点的上一个结点的下一个结点引用指向新结点
21 pred.next = newnode;
22 }
23
24 //集合元素个数加一
25 size++;
26
27 //修改次数加一
28 modcount++;
29
30 }
31
32
33
34 //卸载指定结点
35 e unlink(node x) {
36
37 //获取给定结点的元素
38 final e element = x.item;
39
40 //获取给定结点的下一个结点的引用
41 final node next = x.next;
42
43 //获取给定结点的上一个结点的引用
44 final node prev = x.prev;
45
46 //如果给定结点的上一个结点为空, 说明给定结点为头结点
47 if (prev == null) {
48
49 //将头结点引用指向给定结点的下一个结点
50 first = next;
51 } else {
52 //将上一个结点的后继结点引用指向给定结点的后继结点
53 prev.next = next;
54 //将给定结点的上一个结点置空
55 x.prev = null;
56
57 }
58
59 //如果给定结点的下一个结点为空, 说明给定结点为尾结点
60 if (next == null) {
61
62 //将尾结点引用指向给定结点的上一个结点
63 last = prev;
64 } else {
65
66 //将下一个结点的前继结点引用指向给定结点的前继结点
67 next.prev = prev;
68 x.next = null;
69
70 }
71
72 //将给定结点的元素置空
73 x.item = null;
74
75 //集合元素个数减一
76 size--;
77 //修改次数加一
78 modcount++;
79 return element;
80
81 }
通过源码的分析,对链表的插入和删除的时间复杂度都是o(1),而对链表的查找和修改操作都需要遍历链表进行元素的定位,这两个操作都是调用的node(int index) 方法定位元素,如下代码演示根据下标来定位元素。
1 //根据指定位置获取结点
2 node node(int index) {
3 //如果下标在链表前半部分, 就从头开始查起
4 if (index < (size >> 1)) {
5 node x = first;
6 for (int i = 0; i < index; i++) {
7 x = x.next;
8 }
9 return x;
10 } else {
11 //如果下标在链表后半部分, 就从尾开始查起
12 node x = last;
13 for (int i = size - 1; i > index; i--) {
14 x = x.prev;
15 }
16 return x;
17 }
18 }
通过下标定位时先判断是在链表的上半部还是下半部
上半部:从头开始找;
下半部:从尾开始找;
因此通过下标的查找和修改操作的时间复杂度是o(n/2),通过对双向链表的操作还可以实现单项队列,双向队列和栈的功能。
单向队列的操作的代码:
1 //获取头结点
2 public e peek() {
3 final node f = first;
4 return (f == null) ? null : f.item;
5 }
6
7 //获取头结点
8 public e element() {
9 return getfirst();
10 }
11
12 //弹出头结点
13 public e poll() {
14 final node f = first;
15 return (f == null) ? null : unlinkfirst(f);
16 }
17
18 //移除头结点
19 public e remove() {
20 return removefirst();
21 }
22
23 //在队列尾部添加结点
24 public boolean offer(e e) {
25 return add(e);
26 }
双向队列的操作:
1 //在头部添加
2 public boolean offerfirst(e e) {
3 addfirst(e);
4 return true;
5 }
6
7 //在尾部添加
8 public boolean offerlast(e e) {
9 addlast(e);
10 return true;
11 }
12
13 //获取头结点
14 public e peekfirst() {
15 final node f = first;
16 return (f == null) ? null : f.item;
17 }
18
19 //获取尾结点
20 public e peeklast() {
21 final node l = last;
22 return (l == null) ? null : l.item;
23 }
栈操作:
1 //入栈
2 public void push(e e) {
3 addfirst(e);
4 }
5
6 //出栈
7 public e pop() {
8 return removefirst();
9 }
对lindedlist,有:
1. linkedlist是基于双向链表实现的,不论是增删改查方法还是队列和栈的实现,都可通过操作结点实现
2. linkedlist无需提前指定容量,因为基于链表操作,集合的容量随着元素的加入自动增加
3. linkedlist删除元素后集合占用的内存自动缩小,无需像arraylist一样调用trimtosize()方法
4. linkedlist的所有方法没有进行同步,因此它也不是线程安全的,应该避免在多线程环境下使用
5. 以上分析基于jdk1.7,其他版本会有些出入,因此不能一概而论
以上内容部分借鉴于:https://blog.csdn.net/iteen/article/details/83109717
希望与广大网友互动??
点此进行留言吧!