最近在研究Handler机制的时候发现Java层的消息队列MessageQueue是通过单向链表的数据结构来管理各个Message的,考虑到单向链表是一种很基础的数据结构,不掌握的话太不像话了,所以系统性的研究了单向链表。
针对链表的节点操作主要是增、删、查,下面 通过一个Demo来实际操作一遍,最后结合MessageQueue,研究下Android是怎么通过链表来管理Message的,这对研究Handler机制也是有意义的,先来看Demo:
/**
* 测试单向链表
* @author tushihao
*
*/
public class TestLinked {
/*
* 链表长度
*/
private int length;
/*
* 链表的头节点
*/
private Node header;
/**
* 获取链表的长度
*
* @return 返回链表长度
*/
public int getLenght() {
return length;
}
/**
* 获取链表的头结点
*
* @return 返回链表头结点
*/
public Node getHeader() {
return header;
}
public static void main(String[] args) {
}
/**
* 打印整个链表的内容
*/
private void showLinked() {
String lk = "";
Node tmp = header;
if (length == 0) {
lk += "the linked is empty";
} else {
while (tmp.next != null) {
lk += tmp + "---> ";
tmp = tmp.next;
}
lk += tmp;
}
System.out.println(lk);
}
/**
* 节点类,类名随意
*
* @author tushihao
*
*/
class Node {
//创建链表肯定是为了保存数据,所以每个节点要有一个成员变量来保存数据
public Object data;
//下线节点
public Node next;
public Node(Object object) {
data = object;
}
@Override
public String toString() {
return "Node[" + data + "]";
}
}
1.增加节点:
/**
* 将目标节点插入链表指定的位置
*
* @param node 待插入的节点
* @param index 节点要插入的位置;0代表插入链表头;-1代表插入链表尾;大于0插入大于0的位置
*/
public void addNode(Node node, int index) {
if (node == null || node.data == null) {
return;
}
//两个中间变量
Node tmp = header;
Node prev = null;
// 如果插入的位置是链表头
if (index == 0) {
if (length == 0) {
//如果链表为空,那么直接将目标节点赋值给头节点
header = node;
} else {
//首先将目标节点赋值给头节点,然后将目标节点的下线节点指向原来的头节点
header = node;
header.next = tmp;
}
} else if (index == -1) {
// 如果要插入链表尾,那么遍历整个链表
while (tmp.next != null) {
tmp = tmp.next;
}
// while循环完成之后,tmp就指向了链表的尾节点,将tmp的
//next指向待插入的节点即可,这样待插入的节点就成了尾节点
tmp.next = node;
} else {
// 如果要插入任意指定的位置,那么需要记录那个位置的前一个节点,因为前一个节点的next就是
// 待插入的节点,这个连接关系要接上,定义leng就是为了判断有没有遍历到目标位置
int leng = 0;
// 如果目标位置比链表长度还大的话,可以直接返回,也可以插入链表末尾,视需求而定,这里直接返回
if (index > length) {
return;
}
while (tmp.next != null) {
// 如果遍历到目标索引了,那么首先记录该节点,因为目标节点的next就是这个节点,
//同时还要记录这个节点上线节点,因为上线节点的next就是目标节点,这里涉及到三个节点
if (leng == index) {
prev.next = node;
node.next = tmp;
break;
} else {
leng++;
prev = tmp;
tmp = tmp.next;
}
}
}
length++;
}
测试代码:
TestLinked tl = new TestLinked();
tl.addNode(new Node("F"), 0);
tl.addNode(new Node("E"), 0);
tl.addNode(new Node("D"), 0);
tl.addNode(new Node("C"), 0);
tl.addNode(new Node("B"), 0);
tl.addNode(new Node("A"), 0);
System.out.println("原始链表长度:" + tl.length + " , 链表内容:");
tl.showLinked();
// 往链表头插入一个节点
tl.addNode(new Node("tuhao"), 0);
System.out.println("将tuhao插入头节点,插入后长度:" + tl.length + " , 链表内容:");
tl.showLinked();
//往链表尾插入一个节点
tl.addNode(new Node("dana"), -1);
System.out.println("将dana插入尾节点,插入后长度:" + tl.length + " , 链表内容:");
tl.showLinked();
//往第三个位置插入一个节点
System.out.println("将china插入第3个位置");
tl.addNode(new Node("china"), 2);
System.out.println("插入后的长度:" + tl.length + " , 链表内容:");
tl.showLinked();
测试结果:
原始链表长度:6 , 链表内容:
Node[A]---> Node[B]---> Node[C]---> Node[D]---> Node[E]---> Node[F]
将tuhao插入头节点,插入后长度:7 , 链表内容:
Node[tuhao]---> Node[A]---> Node[B]---> Node[C]---> Node[D]---> Node[E]---> Node[F]
将dana插入尾节点,插入后长度:8 , 链表内容:
Node[tuhao]---> Node[A]---> Node[B]---> Node[C]---> Node[D]---> Node[E]---> Node[F]---> Node[dana]
将china插入第3个位置
插入后的长度:9 , 链表内容:
Node[tuhao]---> Node[A]---> Node[china]---> Node[B]---> Node[C]---> Node[D]---> Node[E]---> Node[F]---> Node[dana]
2.删除节点:
/**
* 根据内容删除指定的节点
*
* @param object 待删除的节点的内容
* @return 删除的节点的索引,如果没有删除成功,那么返回-1
*/
private int removeNode(Object object) {
// 空链表当然不存在移除的问题
if (length <= 0 || object == null) {
return -1;
}
// 定义三个中间变量
Node tmp = header;
Node prev = null;
// 目标节点的索引
int index = -1;
// 首先判断是不是移除头节点
if (object.equals(header.data)) {
index = 0;
header = header.next;
length--;
return index;
}
// 遍历整个链表,对比data,符合条件就移除
while (tmp.next != null) {
// 判断相等是用==还是equals亦或用别的方法,视需求而定,
//本例中的object是String类型,所以用equals
if (object.equals(tmp.data)) {
// 所谓的移除,就是将目标节点的前一个节点的next指向目标
//节点的下一个节点,这样这个节点就没有上线节点和下线节点
// 将目标节点的前一个节点的next指向目标节点的后一个节点
prev.next = tmp.next;
length--;
return index;
} else {
prev = tmp;
tmp = tmp.next;
index++;
}
}
// 如果没有指定的节点,那么返回-1
return index;
}
测试代码:
System.out.println("tuhao太高调了,把这个节点移除掉");
int i = tl.removeNode("tuhao");
System.out.println("目标节点索引:" + i + ",移除后的长度:" + tl.length + " , 链表内容:");
tl.showLinked();
测试结果:
tuhao太高调了,把这个节点移除掉
目标节点索引:0移除后的长度:8 , 链表内容:
Node[A]---> Node[china]---> Node[B]---> Node[C]---> Node[D]---> Node[E]---> Node[F]---> Node[dana]
3.查找节点:
/**
* 根据指定内容查找对应的节点(也可以根据索引查找指定的内容)
*
* @param object 查找的条件
* @return 返回节点,没有找到的话,返回null
*/
private Node findNode(Object object) {
Node result = null;
if (length == 0 || object == null) {
return result;
}
Node tmp = header;
while (tmp.next != null) {
if (object.equals(tmp.data)) {
result = tmp;
} else {
tmp = tmp.next;
}
}
// 目标节点可能是最后一个节点,所以最后一个节点要单独判断
if (object.equals(tmp.data)) {
result = tmp;
}
return result;
}
测试代码:
System.out.println("查找dana");
System.out.println(tl.findNode("dana"));
测试结果:
查找dana
Node[dana]
上面的Demo应该是自从改革开放以来最简单的单向链表使用的例子,仅适合入门;下面结合源码来分析单向链表在Android中的运用。
Handler相信每个从事Android开发的人都用过,应用进程通过sendMessage或者其他的方法将Message发送到framework后,framework就是通过单向链表这种数据结构来管理各个Message的,代码如下:
路径:frameworks/base/core/java/android/os/MessageQueue.java
public final class MessageQueue {
......
//MessageQueue有个Message类型的成员变量,这个成员变量mMessage
//就是单向链表的头节点,上面也说过,拿到头节点就相当于拿到了单向链表
Message mMessages;
......
//sendMessage等方法最终会走到enqueueMessage这个方法
boolean enqueueMessage(Message msg, long when) {
//target是Message中Handler类型的成员变量,因为每个Message
//被Handler发送出去后,最终还是要回到Handler的handleMessage
//方法处理的,所以和Message绑定的Handler肯定是不能为空的
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
//如果要发送的Message已经在消息队列里面了,那么不能再次发送此Message
if (msg.isInUse()) {
throw new IllegalStateException(msg +
" This message is already in use.");
}
synchronized (this) {
//mQuitting默认是false,只有当Looper退出的时候才会把它置为true
//Looper退出了,意味着这个Handler被废弃了,不用了
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
//首先将此Message添加正在使用的标记为
msg.markInUse();
//给Message的when属性赋值,这个属性很重要,
//他决定了此Message在单向链表中的位置
msg.when = when;
//首先将头消息赋值给局部变量p
Message p = mMessages;
//是否需要唤醒主线程
boolean needWake;
//p指向是的是消息读队列的头Message,p == null说明该队列里面目
//前没有Message,发过来的Message就是头Message;when == 0说明
//发过来的消息是马上需要处理的Message(有些Message可能存在delay)
//when < p.when说明当前消息队列的Message全是delay的Message,而
//发过来的Message没有delay,或者delay的时间比那些Message短,此时
//就应该优先处理这种Message,这种Message就应该放到链表的头,因为Looper
//是从链表的头Message开始获取Message,然后分发给Handler来处理的。
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
//符合上面三个条件的Message就是头Message,原来的头Message
//就成为了发过来的Message的下线Message(就是上面Demo说的下线节点)
msg.next = p;
//将头Message指向发过来的Message
mMessages = msg;
//是否需要唤醒主线程
needWake = mBlocked;
}else {
// 源码是有注释的,太长了,删掉
//一旦进入这个分支,说明发过来的Message必须插到链表的某个
//位置,这个位置不能是头节点,怎么确定这个节点的位置呢?就
//是把目标Message插入到链表的第二个位置,然后比较第二个Message
//和第三个Message的when的大小,如果第三个的when小,说明
//目标Message应该在第三个Message的后面,两者交换位置,接
//着比较第三个Message和第四个Message的when的大小,依次类
//推,如果找到了一个Message的when比目标Message的when大的
//Message,那么停止比较和交换;如果没有找到,那么就插入链表尾
//是否需要唤醒主线程,不影响链表的分析,暂且不管,在Hanlder专题再说
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
//刚进入循环时,p指向头Message,赋值给prev,prev记录
//的是目标Message的上线Message
prev = p;
//把p指向p的下线Message
p = p.next;
//p == null代表遍历到节点的尾部了;when < p.when说明目标Message
//应该插入到p的前面,这两种情况都应该跳出循环,没必要再循环了
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
//目标Message强行插队,下次循环时如果发现msg的when
//比msg.next的when大的话,那就打脸了,插队失败,msg
//往后挪,msg代表的是目标Message,也就是发送过来的Message
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
}
}
}
以上就是单向链表在Android中的使用