今天在看LinkedList的源代码的时候,遇到了一个坑。我研究源码时,发现LinkedList是一个直线型的链表结构,但是我在baidu搜索资料的时候,关于这部分的源码解析,全部都说LinkedList是一个环形链表结构。。我纠结了好长时间,还以为我理解错了,最后还是在Google搜到了结果:因为我看的源码是1.7的而baidu出来的几乎全部都是1.6的。而且也没有对应的说明。在1.7之后,oracle将LinkedList做了一些优化,将1.6中的环形结构优化为了直线型了链表结构。这里要提示一下朋友们,看源码的时候,一定要看版本,有的情况是属于小改动,有的地方可能有大改动,这样只会越看越迷糊。
好,言归正传。我们来分析一下Java中LinkedList的部分源码。(本文针对的是1.7的源码)
LinkedList的基本结构
LinkedList的构造方法
-
// 什么都没做,是一个空实现
-
public LinkedList() {
-
}
-
-
public LinkedList(Collection<? extends E> c) {
-
this();
-
addAll(c);
-
}
-
-
public boolean addAll(Collection<? extends E> c) {
-
return addAll(size, c);
-
}
-
-
public boolean addAll(int index, Collection<? extends E> c) {
-
// 检查传入的索引值是否在合理范围内
-
checkPositionIndex(index);
-
// 将给定的Collection对象转为Object数组
-
Object[] a = c.toArray();
-
int numNew = a.length;
-
// 数组为空的话,直接返回false
-
if (numNew ==
0)
-
return
false;
-
// 数组不为空
-
Node<E> pred, succ;
-
if (index == size) {
-
// 构造方法调用的时候,index = size = 0,进入这个条件。
-
succ =
null;
-
pred = last;
-
}
else {
-
// 链表非空时调用,node方法返回给定索引位置的节点对象
-
succ = node(index);
-
pred = succ.prev;
-
}
-
// 遍历数组,将数组的对象插入到节点中
-
for (Object o : a) {
-
@SuppressWarnings(
“unchecked”) E e = (E) o;
-
Node<E> newNode =
new Node<>(pred, e,
null);
-
if (pred ==
null)
-
first = newNode;
-
else
-
pred.next = newNode;
-
pred = newNode;
-
}
-
-
if (succ ==
null) {
-
last = pred;
// 将当前链表最后一个节点赋值给last
-
}
else {
-
// 链表非空时,将断开的部分连接上
-
pred.next = succ;
-
succ.prev = pred;
-
}
-
// 记录当前节点个数
-
size += numNew;
-
modCount++;
-
return
true;
-
}
-
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;
-
}
-
}
构造方法的参数顺序是:前继节点的引用,数据,后继节点的引用。
这段代码还是很好理解的。我们可以配合图片来深入理解。
LinkedList部分方法分析
addFirst/addLast分析
-
public void addFirst(E e) {
-
linkFirst(e);
-
}
-
-
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++;
-
}
其实只要理解了上边的数据结构,这段代码是很好理解的。
addLast方法在实现上是个addFirst是一致的,这里就不在赘述了。有兴趣的朋友可以看看源代码。
getFirst/getLast方法分析
-
public E getFirst() {
-
final Node<E> f = first;
-
if (f ==
null)
-
throw
new NoSuchElementException();
-
return f.item;
-
}
-
-
public E getLast() {
-
final Node<E> l = last;
-
if (l ==
null)
-
throw
new NoSuchElementException();
-
return l.item;
-
}
这段代码即不需要解析了吧。。很简单的。
get方法分析
-
public E get(int index) {
-
// 校验给定的索引值是否在合理范围内
-
checkElementIndex(index);
-
return node(index).item;
-
}
-
-
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;
-
}
-
}
一开始我很费解,这是要干嘛?后来我才明白,代码要做的是:判断给定的索引值,若索引值大于整个链表长度的一半,则从后往前找,若索引值小于整个链表的长度的一般,则从前往后找。这样就可以保证,不管链表长度有多大,搜索的时候最多只搜索链表长度的一半就可以找到,大大提升了效率。
-
public E removeFirst() {
-
final Node<E> f = first;
-
if (f ==
null)
-
throw
new NoSuchElementException();
-
return unlinkFirst(f);
-
}
-
-
private E unlinkFirst(Node<E> f) {
-
// assert f == first && f != null;
-
final E element = f.item;
-
final Node<E> next = f.next;
-
f.item =
null;
-
f.next =
null;
// help GC
-
first = next;
-
if (next ==
null)
-
last =
null;
-
else
-
next.prev =
null;
-
size–;
-
modCount++;
-
return element;
-
}
摘掉头结点,将原来的第二个节点变为头结点,改变frist的指向,若之前仅剩一个节点,移除之后全部置为了null。
</div>
</div>
本Markdown编辑器使用StackEdit修改而来,用它写博客,将会带来全新的体验哦:
// 什么都没做,是一个空实现
public LinkedList() {
}
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
public boolean addAll(int index, Collection<? extends E> c) {
// 检查传入的索引值是否在合理范围内
checkPositionIndex(index);
// 将给定的Collection对象转为Object数组
Object[] a = c.toArray();
int numNew = a.length;
// 数组为空的话,直接返回false
if (numNew == 0)
return false;
// 数组不为空
Node<E> pred, succ;
if (index == size) {
// 构造方法调用的时候,index = size = 0,进入这个条件。
succ = null;
pred = last;
} else {
// 链表非空时调用,node方法返回给定索引位置的节点对象
succ = node(index);
pred = succ.prev;
}
// 遍历数组,将数组的对象插入到节点中
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null)
first = newNode;
else
pred.next = newNode;
pred = newNode;
}
if (succ == null) {
last = pred; // 将当前链表最后一个节点赋值给last
} else {
// 链表非空时,将断开的部分连接上
pred.next = succ;
succ.prev = pred;
}
// 记录当前节点个数
size += numNew;
modCount++;
return true;
}
- Markdown和扩展Markdown简洁的语法
- 代码块高亮
- 图片链接和图片上传
- LaTex数学公式
- UML序列图和流程图
- 离线写博客
- 导入导出Markdown文件
- 丰富的快捷键
快捷键
- 加粗
Ctrl + B
- 斜体
Ctrl + I
- 引用
Ctrl + Q
- 插入链接
Ctrl + L
- 插入代码
Ctrl + K
- 插入图片
Ctrl + G
- 提升标题
Ctrl + H
- 有序列表
Ctrl + O
- 无序列表
Ctrl + U
- 横线
Ctrl + R
- 撤销
Ctrl + Z
- 重做
Ctrl + Y
Markdown及扩展
Markdown 是一种轻量级标记语言,它允许人们使用易读易写的纯文本格式编写文档,然后转换成格式丰富的HTML页面。 —— [ 维基百科 ]
使用简单的符号标识不同的标题,将某些文字标记为粗体或者斜体,创建一个链接等,详细语法参考帮助?。
本编辑器支持 Markdown Extra , 扩展了很多好用的功能。具体请参考Github.
表格
Markdown Extra 表格语法:
项目 | 价格 |
---|---|
Computer | $1600 |
Phone | $12 |
Pipe | $1 |
可以使用冒号来定义对齐方式:
项目 | 价格 | 数量 |
---|---|---|
Computer | 1600 元 | 5 |
Phone | 12 元 | 12 |
Pipe | 1 元 | 234 |
定义列表
- Markdown Extra 定义列表语法:
- 项目1
- 项目2
- 定义 A
- 定义 B
- 项目3
- 定义 C
-
定义 D
定义D内容
代码块
代码块语法遵循标准markdown代码,例如:
@requires_authorization
def somefunc(param1='', param2=0):
'''A docstring'''
if param1 > param2: # interesting
print 'Greater'
return (param2 - param1 + 1) or None
class SomeClass:
pass
>>> message = '''interpreter
... prompt'''
脚注
生成一个脚注1.
目录
用 [TOC]
来生成目录:
数学公式
使用MathJax渲染LaTex 数学公式,详见math.stackexchange.com.
- 行内公式,数学公式为: 。
- 块级公式:
更多LaTex语法请参考 这儿.
UML 图:
可以渲染序列图:
或者流程图:
离线写博客
即使用户在没有网络的情况下,也可以通过本编辑器离线写博客(直接在曾经使用过的浏览器中输入write.blog.csdn.net/mdeditor即可。Markdown编辑器使用浏览器离线存储将内容保存在本地。
用户写博客的过程中,内容实时保存在浏览器缓存中,在用户关闭浏览器或者其它异常情况下,内容不会丢失。用户再次打开浏览器时,会显示上次用户正在编辑的没有发表的内容。
博客发表后,本地缓存将被删除。
用户可以选择 把正在写的博客保存到服务器草稿箱,即使换浏览器或者清除缓存,内容也不会丢失。
注意:虽然浏览器存储大部分时候都比较可靠,但为了您的数据安全,在联网后,请务必及时发表或者保存到服务器草稿箱。
浏览器兼容
- 目前,本编辑器对Chrome浏览器支持最为完整。建议大家使用较新版本的Chrome。
- IE9以下不支持
- IE9,10,11存在以下问题
- 不支持离线功能
- IE9不支持文件导入导出
- IE10不支持拖拽文件导入
- 这里是 脚注 的 内容. ↩
今天在看LinkedList的源代码的时候,遇到了一个坑。我研究源码时,发现LinkedList是一个直线型的链表结构,但是我在baidu搜索资料的时候,关于这部分的源码解析,全部都说LinkedList是一个环形链表结构。。我纠结了好长时间,还以为我理解错了,最后还是在Google搜到了结果:因为我看的源码是1.7的而baidu出来的几乎全部都是1.6的。而且也没有对应的说明。在1.7之后,oracle将LinkedList做了一些优化,将1.6中的环形结构优化为了直线型了链表结构。这里要提示一下朋友们,看源码的时候,一定要看版本,有的情况是属于小改动,有的地方可能有大改动,这样只会越看越迷糊。
好,言归正传。我们来分析一下Java中LinkedList的部分源码。(本文针对的是1.7的源码)
LinkedList的基本结构
LinkedList的构造方法
-
// 什么都没做,是一个空实现
-
public LinkedList() {
-
}
-
-
public LinkedList(Collection<? extends E> c) {
-
this();
-
addAll(c);
-
}
-
-
public boolean addAll(Collection<? extends E> c) {
-
return addAll(size, c);
-
}
-
-
public boolean addAll(int index, Collection<? extends E> c) {
-
// 检查传入的索引值是否在合理范围内
-
checkPositionIndex(index);
-
// 将给定的Collection对象转为Object数组
-
Object[] a = c.toArray();
-
int numNew = a.length;
-
// 数组为空的话,直接返回false
-
if (numNew ==
0)
-
return
false;
-
// 数组不为空
-
Node<E> pred, succ;
-
if (index == size) {
-
// 构造方法调用的时候,index = size = 0,进入这个条件。
-
succ =
null;
-
pred = last;
-
}
else {
-
// 链表非空时调用,node方法返回给定索引位置的节点对象
-
succ = node(index);
-
pred = succ.prev;
-
}
-
// 遍历数组,将数组的对象插入到节点中
-
for (Object o : a) {
-
@SuppressWarnings(
“unchecked”) E e = (E) o;
-
Node<E> newNode =
new Node<>(pred, e,
null);
-
if (pred ==
null)
-
first = newNode;
-
else
-
pred.next = newNode;
-
pred = newNode;
-
}
-
-
if (succ ==
null) {
-
last = pred;
// 将当前链表最后一个节点赋值给last
-
}
else {
-
// 链表非空时,将断开的部分连接上
-
pred.next = succ;
-
succ.prev = pred;
-
}
-
// 记录当前节点个数
-
size += numNew;
-
modCount++;
-
return
true;
-
}
-
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;
-
}
-
}
构造方法的参数顺序是:前继节点的引用,数据,后继节点的引用。
这段代码还是很好理解的。我们可以配合图片来深入理解。
LinkedList部分方法分析
addFirst/addLast分析
-
public void addFirst(E e) {
-
linkFirst(e);
-
}
-
-
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++;
-
}
其实只要理解了上边的数据结构,这段代码是很好理解的。
addLast方法在实现上是个addFirst是一致的,这里就不在赘述了。有兴趣的朋友可以看看源代码。
getFirst/getLast方法分析
-
public E getFirst() {
-
final Node<E> f = first;
-
if (f ==
null)
-
throw
new NoSuchElementException();
-
return f.item;
-
}
-
-
public E getLast() {
-
final Node<E> l = last;
-
if (l ==
null)
-
throw
new NoSuchElementException();
-
return l.item;
-
}
这段代码即不需要解析了吧。。很简单的。
get方法分析
-
public E get(int index) {
-
// 校验给定的索引值是否在合理范围内
-
checkElementIndex(index);
-
return node(index).item;
-
}
-
-
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;
-
}
-
}
一开始我很费解,这是要干嘛?后来我才明白,代码要做的是:判断给定的索引值,若索引值大于整个链表长度的一半,则从后往前找,若索引值小于整个链表的长度的一般,则从前往后找。这样就可以保证,不管链表长度有多大,搜索的时候最多只搜索链表长度的一半就可以找到,大大提升了效率。
-
public E removeFirst() {
-
final Node<E> f = first;
-
if (f ==
null)
-
throw
new NoSuchElementException();
-
return unlinkFirst(f);
-
}
-
-
private E unlinkFirst(Node<E> f) {
-
// assert f == first && f != null;
-
final E element = f.item;
-
final Node<E> next = f.next;
-
f.item =
null;
-
f.next =
null;
// help GC
-
first = next;
-
if (next ==
null)
-
last =
null;
-
else
-
next.prev =
null;
-
size–;
-
modCount++;
-
return element;
-
}
摘掉头结点,将原来的第二个节点变为头结点,改变frist的指向,若之前仅剩一个节点,移除之后全部置为了null。
</div>
</div>
本Markdown编辑器使用StackEdit修改而来,用它写博客,将会带来全新的体验哦: