栈和队列在安卓中的运用

  • Post author:
  • Post category:其他




栈和队列的定义






队列

都是一种基本的数据结构,可以用来存放数据,



的顺序是先入后出,而

队列

的顺序是先入先出,我们可以通过它们实现不同很多特定的功能

比如在安卓中,遍历一个布局文件里所有子view,并打印它们。

首先我们先思考一下这个问题,在Android的布局文件的结构,我们可以把它类比做数据结构中的多叉树。

布局文件结构

上图就是一个常见的

RootView

的结构,我们可以通过

getChildAt(int)

这个方法,来遍历这个

RootView

并获取它的所有子View。



方法一:递归

当我们想要遍历一个ViewGroup所有子View的时候,我们通常想到的就是通过递归,而递归也是最容易写出来的。

直接上代码:

fun printAllChildren(rootView : View){
	print(rootView)
	if(rootView is ViewGroup){
		for(index in 0 until rootView.childCount) {
			val view = rootView.getChildAt(index)
			printAllChildren(view)
		}
	}
}

递归的优点很明显,代码清晰,浅显易懂,但是缺点也同样明显,就是当RootView的层级较深的时候,可能会引起栈溢出,而程序会抛出

StackOverflowError

这个错误。这是因为在JVM中,每当启动一个新的线程,JVM都会为为其分配一个java栈,而每调用一个方法,都会被封装成一个栈帧,进行

压栈

的操作;当方法执行完毕,就会进行

弹栈

的操作。



StackOverflowError

这个错误又是怎么产生的呢,这是因为在每个栈帧中,都会保存该方法中的一些局部变量、返回地址等数据,这些都是会占用内存,而递归每调用一次方法,就会

压栈

产生一个新的栈帧并消耗内存,但是一直到最后不会再调用新方法,才会执行

弹栈

的操作,所以有可能在执行的过程当中超过了内存的限制,就报出

StackOverflowError

所以我们需要考虑通过其他的方法来遍历ViewGroup。



方法二:栈和队列

之前我们说过,安卓的布局结构其实就是一个多叉树,而在数据结构中遍历多叉树有两种方式,

广度优先



深度优先

。如下图:

在这里插入图片描述



2.1 广度优先

广度优先其实就是对多叉树的每一层节点依次访问,当一层访问完毕,再进入下一层。就是遵循

树的层数

,一层一层地遍历。

广度优先算法,是非常适合先入先出的队列来实现的,每层子View依次进入队列,然后取出队头的子View并处理,如果该子View是ViewGroup,那么再取出它所有的子View放入队尾。

所以上图如果通过广度优先来遍历,那么最后的结果就是:

A -> B -> C -> D -> E -> F

,通过图来看一下:

在这里插入图片描述

java中,可以使用

LinkedList

来完成队列的功能,因为它实现了

Deque

双端队列接口,代码实现:

fun printAllChildren(rootView : View){
	val viewDeque = LinkedList<View>()
  	var view = root
  	viewDeque.addLast(view)
  	while(viewDeque.isNotEmpty()){
  		view = viewDeque.poll()	//取出队列第一个元素
  		print(view)
  		if(view is ViewGroup){
  			for(index in 0 until view.childCount){
  				val childView = view.getChildAt(index)
  				viewDeque.addLast(childView)
  			}
  		}
  	}
|



2.2 深度优先

深度优先则是对每个分支的路径,深入访问到叶子节点之后,才会访问其他的节点。

深度优先算法,是非常适合先入后出的栈来实现,对一个子View先做压栈操作,再弹栈,输出该View,然后遍历得到该子View所有的子View并做压栈操作,再弹栈输出栈顶的子View,重复上述的操作。

所以如果通过深度优先算法来遍历,那么最后得到的结果就是

A -> C -> F -> B -> E -> D

, 通过图来看一下:

在这里插入图片描述

我们还是用

LinkedList

来实现栈,因为LinkedList有

push()



pop()

方法来模仿

压栈



弹栈

的操作,代码如下:

fun getAllChildren(rootView: View){
	val viewStack = LinkedList()
	var view = rootView
	viewStack.push(view)
	while(viewStack.isNotEmpty()){
		view = viewStack.pop()	//弹出栈顶的元素
		print(view)
		if(view is ViewGroup){
			for(index in 0 until view.getChildCount()){
				val childView = view.getChildAt(index)
				viewStack.push(childView)
			}
		}
	}
}



总结

我们通过三种方法来实现了ViewGroup的遍历,明白了ViewGroup其实就是一种多叉树的数据结构,也明白了栈和队列在安卓中的运用,当然上面的场景也可以变换,比如统计一个

ViewGroup所有子View的数量



自己实现一个方法查找指定id的子View

等等。


转载请注明出处



版权声明:本文为zzx752904457原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。