01-16-17 迭代器模式 - Cursor的数据遍历设计
摘要: 迭代器模式是一种行为型设计模式,它通过提供统一的遍历接口,使客户端能够顺序访问聚合对象中的元素,而无需了解其内部结构。Android中的Cursor接口完美体现了这一模式,将数据库结果集抽象为可遍历序列。Cursor定义了moveToNext()等遍历方法,AbstractCursor实现了基本逻辑,SQLiteCursor提供具体实现。这种设计解耦了数据访问与存储结构,支持灵活更换底层数
01-16-17 迭代器模式 - Cursor的数据遍历设计
模式定义
迭代器模式(Iterator Pattern)属于行为型设计模式,其核心思想是:提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。客户端通过统一的迭代接口遍历不同类型的集合,无需关心集合的底层数据结构。
解决的问题
聚合对象(如数组、链表、树、数据库结果集)的内部结构各不相同,如果客户端直接依赖具体的数据结构进行遍历,那么更换底层实现时所有遍历代码都需要修改。迭代器模式将遍历逻辑从聚合对象中抽离,封装到独立的迭代器对象中,使得客户端代码与集合的内部结构解耦。
类图说明
┌─────────────────┐ ┌──────────────────┐
│ Aggregate │ │ Iterator │
│─────────────────│ │──────────────────│
│ + iterator(): │────────>│ + hasNext(): bool │
│ Iterator │ creates │ + next(): Element │
└────────┬────────┘ └────────┬─────────┘
│ implements │ implements
┌────────┴────────┐ ┌────────┴─────────┐
│ConcreteAggregate│ │ ConcreteIterator │
│─────────────────│ │──────────────────│
│ - elements[] │<────────│ - aggregate │
│ + iterator() │ holds │ - currentIndex │
└─────────────────┘ ref │ + hasNext() │
│ + next() │
└──────────────────┘
模式中的角色:
- Iterator(迭代器接口):声明遍历所需的
hasNext()、next()等方法 - ConcreteIterator(具体迭代器):实现遍历逻辑,维护当前遍历位置
- Aggregate(聚合接口):声明创建迭代器的工厂方法
- ConcreteAggregate(具体聚合):返回与自身数据结构匹配的具体迭代器
Android源码中的实现
案例一:Cursor 数据库结果集遍历
源码路径:
frameworks/base/core/java/android/database/Cursor.java(接口)frameworks/base/core/java/android/database/AbstractCursor.java(抽象实现)frameworks/base/core/java/android/database/sqlite/SQLiteCursor.java(SQLite 实现)frameworks/base/core/java/android/database/CursorWrapper.java(装饰器)
Cursor 是 Android 数据库访问的核心接口,其设计完全遵循迭代器模式。Cursor 将数据库查询结果集抽象为可遍历的序列,客户端无需了解数据在底层是如何存储和读取的。
// frameworks/base/core/java/android/database/Cursor.java
public interface Cursor extends Closeable {
// ---- 迭代器核心方法 ----
// 获取结果集总行数
int getCount();
// 获取当前游标位置(从 0 开始,初始值为 -1)
int getPosition();
// 移动到第一条记录(相当于 reset + next)
boolean moveToFirst();
// 移动到最后一条记录
boolean moveToLast();
// 移动到下一条记录,返回 false 表示已到末尾
boolean moveToNext();
// 移动到上一条记录(支持双向遍历)
boolean moveToPrevious();
// 移动到指定位置(支持随机访问)
boolean moveToPosition(int position);
// 边界检查
boolean isFirst();
boolean isLast();
boolean isBeforeFirst();
boolean isAfterLast();
// ---- 数据访问方法 ----
// 按列索引获取数据
String getString(int columnIndex);
int getInt(int columnIndex);
long getLong(int columnIndex);
float getFloat(int columnIndex);
double getDouble(int columnIndex);
byte[] getBlob(int columnIndex);
// 按列名获取列索引
int getColumnIndex(String columnName);
int getColumnIndexOrThrow(String columnName);
String getColumnName(int columnIndex);
String[] getColumnNames();
int getColumnCount();
// ---- 资源管理 ----
void close();
boolean isClosed();
}
// frameworks/base/core/java/android/database/AbstractCursor.java
public abstract class AbstractCursor implements CrossProcessCursor {
// 当前游标位置,初始值为 -1(表示在第一条记录之前)
protected int mPos;
// 缓存的结果集行数
protected int mCount = NO_COUNT;
@Override
public final boolean moveToFirst() {
return moveToPosition(0);
}
@Override
public final boolean moveToLast() {
return moveToPosition(getCount() - 1);
}
@Override
public final boolean moveToNext() {
return moveToPosition(mPos + 1);
}
@Override
public final boolean moveToPrevious() {
return moveToPosition(mPos - 1);
}
@Override
public final boolean moveToPosition(int position) {
// 边界检查:目标位置超出范围
final int count = getCount();
if (position >= count) {
mPos = count; // 设置为 afterLast
return false;
}
if (position < 0) {
mPos = -1; // 设置为 beforeFirst
return false;
}
// 已经在目标位置,无需移动
if (position == mPos) {
return true;
}
// 调用子类的具体移动实现
boolean result = onMove(mPos, position);
if (!result) {
mPos = -1;
} else {
mPos = position;
}
return result;
}
// 钩子方法:子类实现具体的游标移动逻辑
// SQLiteCursor 在此方法中加载对应位置的数据到 CursorWindow
public boolean onMove(int oldPosition, int newPosition) {
return true;
}
@Override
public final boolean isFirst() {
return mPos == 0 && getCount() != 0;
}
@Override
public final boolean isLast() {
int count = getCount();
return mPos == (count - 1) && count != 0;
}
@Override
public final boolean isBeforeFirst() {
return getCount() == 0 || mPos == -1;
}
@Override
public final boolean isAfterLast() {
return getCount() == 0 || mPos == getCount();
}
}
// frameworks/base/core/java/android/database/sqlite/SQLiteCursor.java
public class SQLiteCursor extends AbstractWindowedCursor {
private final SQLiteCursorDriver mDriver;
private final String mEditTable;
private final SQLiteQuery mQuery;
private int mCount = NO_COUNT;
@Override
public int getCount() {
if (mCount == NO_COUNT) {
// 延迟计算:首次访问时才执行 COUNT 查询
fillWindow(0);
}
return mCount;
}
// 填充 CursorWindow(共享内存块),加载指定位置附近的数据
private void fillWindow(int requiredPos) {
clearOrCreateWindow(getDatabase().getPath());
try {
// requiredPos 不在当前窗口范围内时,重新加载
if (mCount == NO_COUNT) {
mCount = mQuery.fillWindow(
mWindow, requiredPos,
requiredPos, true);
} else {
mQuery.fillWindow(
mWindow, requiredPos,
requiredPos, false);
}
} catch (RuntimeException ex) {
// 窗口填充失败时的异常处理
mCount = 0;
throw ex;
}
}
@Override
public boolean onMove(int oldPosition, int newPosition) {
// 当新位置超出当前 CursorWindow 的范围时,重新加载数据
if (mWindow == null
|| newPosition < mWindow.getStartPosition()
|| newPosition >= (mWindow.getStartPosition()
+ mWindow.getNumRows())) {
fillWindow(newPosition);
}
return true;
}
}
关键设计分析:
Cursor接口不仅支持单向遍历(moveToNext),还支持双向遍历(moveToPrevious)和随机访问(moveToPosition),功能远超标准迭代器AbstractCursor将位置管理和边界检查等通用逻辑封装在基类中,通过模板方法onMove()将具体的数据加载委托给子类SQLiteCursor使用CursorWindow(共享内存块)实现数据的分页加载——结果集可能有上万条记录,但CursorWindow只加载当前窗口内的数据,实现了内存高效的惰性加载CursorWrapper采用装饰器模式包装Cursor,可以在不修改原始 Cursor 的情况下添加过滤、变换等功能
案例二:ViewGroup 的子 View 遍历
源码路径:frameworks/base/core/java/android/view/ViewGroup.java
ViewGroup 管理着一组子 View,虽然没有显式实现 Iterator 接口,但其子 View 遍历机制体现了迭代器模式的思想。
// frameworks/base/core/java/android/view/ViewGroup.java
public abstract class ViewGroup extends View
implements ViewParent, ViewManager {
// 内部数组存储子 View
private View[] mChildren;
// 子 View 数量(不等于 mChildren.length,数组可能预分配了更大空间)
private int mChildrenCount;
// 获取子 View 数量 —— 对应 Iterator 的边界条件
public int getChildCount() {
return mChildrenCount;
}
// 按索引获取子 View —— 对应 Iterator 的随机访问
public View getChildAt(int index) {
if (index < 0 || index >= mChildrenCount) {
return null;
}
return mChildren[index];
}
// 遍历分发绘制 —— 迭代器模式在绘制流程中的应用
@Override
protected void dispatchDraw(Canvas canvas) {
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
// 按绘制顺序遍历子 View
// preorderedList 支持自定义绘制顺序(Z 轴排序)
ArrayList<View> preorderedList =
usingRenderNodeProperties ? null : buildOrderedChildList();
for (int i = 0; i < childrenCount; i++) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null) {
// 绘制单个子 View
more |= drawChild(canvas, child, drawingTime);
}
}
}
// 遍历分发事件 —— 迭代器模式在事件处理中的应用
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// ...
if (!canceled && !intercepted) {
final int childrenCount = mChildrenCount;
// 反向遍历(Z 轴最高的子 View 优先接收事件)
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex =
getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child =
getAndVerifyPreorderedView(
preorderedList, children, childIndex);
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(
x, y, child, null)) {
continue;
}
if (dispatchTransformedTouchEvent(
ev, false, child, idBitsToAssign)) {
// 子 View 消费了事件
break;
}
}
}
// ...
}
// 查找特定子 View —— 遍历匹配
public View findViewTraversal(int id) {
if (id == mID) {
return this;
}
final View[] where = mChildren;
final int len = mChildrenCount;
for (int i = 0; i < len; i++) {
View v = where[i];
v = v.findViewById(id);
if (v != null) {
return v;
}
}
return null;
}
}
关键设计分析:
ViewGroup通过getChildCount()+getChildAt()提供了索引式遍历接口,隐藏了内部数组的预分配策略(mChildren.length可能大于mChildrenCount)- 不同场景使用不同的遍历策略:绘制时正向遍历(底层 View 先绘制),事件分发时反向遍历(顶层 View 优先接收触摸事件)
buildOrderedChildList()支持按 Z 轴排序遍历,体现了迭代器模式对遍历顺序的灵活控制findViewTraversal()在遍历过程中递归进入子 ViewGroup,实现了树结构的深度优先遍历
案例三:SparseArray 的遍历
源码路径:frameworks/base/core/java/android/util/SparseArray.java
SparseArray 是 Android 提供的轻量级 Map<Integer, Object> 替代方案,其遍历方式也体现了迭代器思想。
// frameworks/base/core/java/android/util/SparseArray.java
public class SparseArray<E> implements Cloneable {
// 内部使用两个平行数组存储键值对
private int[] mKeys;
private Object[] mValues;
private int mSize;
// 获取元素数量
public int size() {
if (mGarbage) {
gc(); // 先清理被标记删除的元素
}
return mSize;
}
// 按索引获取 key —— 遍历所需
public int keyAt(int index) {
if (mGarbage) {
gc();
}
if (index >= mSize) {
throw new ArrayIndexOutOfBoundsException(index);
}
return mKeys[index];
}
// 按索引获取 value —— 遍历所需
@SuppressWarnings("unchecked")
public E valueAt(int index) {
if (mGarbage) {
gc();
}
if (index >= mSize) {
throw new ArrayIndexOutOfBoundsException(index);
}
return (E) mValues[index];
}
// 删除操作并不立即移除元素,而是标记为 DELETED
// 延迟到下次 gc() 时批量压缩数组
public void delete(int key) {
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
if (i >= 0) {
if (mValues[i] != DELETED) {
mValues[i] = DELETED;
mGarbage = true;
}
}
}
// 延迟压缩:在遍历前统一清理被删除的元素
private void gc() {
int n = mSize;
int o = 0;
int[] keys = mKeys;
Object[] values = mValues;
for (int i = 0; i < n; i++) {
Object val = values[i];
if (val != DELETED) {
if (i != o) {
keys[o] = keys[i];
values[o] = val;
values[i] = null;
}
o++;
}
}
mGarbage = false;
mSize = o;
}
}
标准遍历方式:
SparseArray<String> sparseArray = new SparseArray<>();
sparseArray.put(10, "ten");
sparseArray.put(20, "twenty");
sparseArray.put(30, "thirty");
// 通过 size() + keyAt() / valueAt() 遍历
for (int i = 0; i < sparseArray.size(); i++) {
int key = sparseArray.keyAt(i);
String value = sparseArray.valueAt(i);
Log.d(TAG, key + " -> " + value);
}
关键设计分析:
SparseArray没有实现Iterable接口,而是通过size()+keyAt()/valueAt()提供索引式遍历,避免创建迭代器对象的开销mGarbage延迟删除机制:delete()只做标记,gc()在遍历前批量压缩。这种设计避免了删除操作导致数组频繁移动- 双数组(
mKeys+mValues)的存储方式相比HashMap的Entry对象减少了内存分配,适合 Android 内存敏感的环境 - 由于
keyAt()和valueAt()在遍历前会触发gc(),遍历过程中的索引是稳定的
源码设计分析
为什么 Android 选择迭代器模式
Cursor 场景:数据库查询结果集的大小不可预知——可能是几条记录,也可能是数万条。将全部数据一次性加载到内存是不可接受的。Cursor 通过迭代器模式实现了按需加载:CursorWindow 只加载当前窗口的数据,onMove() 在游标移出窗口范围时触发新一轮数据加载。客户端通过 moveToNext() 逐条遍历,完全感知不到背后的分页加载机制。
ViewGroup 场景:View 树是一个递归结构,不同的操作需要不同的遍历顺序(绘制用正序,事件分发用倒序,布局测量用正序)。通过将遍历逻辑内置于各个操作方法中(而非暴露裸数组给外部),ViewGroup 可以灵活控制遍历策略。
SparseArray 场景:Android 移动设备内存有限,标准 HashMap<Integer, Object> 的每个 Entry 都是一个独立对象,自动装箱的 Integer 键也会产生额外内存分配。SparseArray 使用原始 int[] 数组存储键,通过索引式遍历避免创建迭代器对象,极大降低了内存压力和 GC 频率。
优缺点权衡
优点:
- 封装内部结构:客户端通过统一接口遍历,更换底层数据结构不影响遍历代码
- 支持多种遍历策略:同一聚合可以提供正序、倒序、过滤等不同的迭代器
- 惰性求值:Cursor 的
CursorWindow机制实现了数据的按需加载,节省内存 - 单一职责:遍历逻辑从聚合类中分离,聚合类专注于数据管理
缺点:
- 额外的类层次:引入迭代器接口和实现类,增加了类的数量
- 遍历性能开销:对于简单数组,直接索引访问比通过迭代器间接访问更快
- 并发修改风险:遍历过程中如果聚合对象被修改,可能导致不可预期的行为(
ConcurrentModificationException)
实战应用
场景一:分页数据迭代器
在 IoT 应用中,设备事件日志可能有数千条,需要分页加载。
/**
* 分页迭代器:自动管理分页加载,对外提供连续的遍历体验。
* 适用于列表数据量大、需要按需加载的场景。
*/
class PagingIterator<T>(
private val pageSize: Int = 20,
private val fetcher: suspend (page: Int, size: Int) -> List<T>
) {
private var currentPage = 0
private var currentData = emptyList<T>()
private var index = 0
private var exhausted = false
/**
* 检查是否还有更多元素。
* 当前页数据用尽时自动加载下一页。
*/
suspend fun hasNext(): Boolean {
if (index < currentData.size) return true
if (exhausted) return false
// 加载下一页
currentData = fetcher(currentPage, pageSize)
currentPage++
index = 0
if (currentData.size < pageSize) {
exhausted = true // 最后一页
}
return currentData.isNotEmpty()
}
/**
* 获取下一个元素
*/
suspend fun next(): T {
if (!hasNext()) throw NoSuchElementException(
"No more elements"
)
return currentData[index++]
}
/**
* 重置迭代器到起始位置
*/
fun reset() {
currentPage = 0
currentData = emptyList()
index = 0
exhausted = false
}
}
/**
* 将分页迭代器转换为 Flow,与 Kotlin 协程生态无缝集成
*/
fun <T> PagingIterator<T>.asFlow(): Flow<T> = flow {
reset()
while (hasNext()) {
emit(next())
}
}
// 使用示例
class DeviceLogRepository(private val api: DeviceApi) {
fun getEventLogs(deviceId: String): Flow<EventLog> {
val iterator = PagingIterator<EventLog>(pageSize = 50) {
page, size -> api.fetchEventLogs(deviceId, page, size)
}
return iterator.asFlow()
}
}
// 在 ViewModel 中消费
class DeviceLogViewModel(
private val repository: DeviceLogRepository
) : ViewModel() {
private val _logs = MutableStateFlow<List<EventLog>>(emptyList())
val logs: StateFlow<List<EventLog>> = _logs.asStateFlow()
fun loadLogs(deviceId: String) {
viewModelScope.launch {
repository.getEventLogs(deviceId)
.chunked(20) // 每 20 条为一批更新 UI
.collect { batch ->
_logs.update { current -> current + batch }
}
}
}
}
场景二:树结构遍历迭代器
IoT 场景中,设备和房间通常构成树形层级结构。
/**
* 树节点
*/
data class TreeNode<T>(
val data: T,
val children: List<TreeNode<T>> = emptyList()
)
/**
* 深度优先迭代器(前序遍历)
*/
class DepthFirstIterator<T>(root: TreeNode<T>) : Iterator<T> {
private val stack = ArrayDeque<TreeNode<T>>()
init {
stack.addLast(root)
}
override fun hasNext(): Boolean = stack.isNotEmpty()
override fun next(): T {
if (!hasNext()) throw NoSuchElementException()
val node = stack.removeLast()
// 将子节点逆序压栈,保证左子节点先被访问
for (i in node.children.indices.reversed()) {
stack.addLast(node.children[i])
}
return node.data
}
}
/**
* 广度优先迭代器(层序遍历)
*/
class BreadthFirstIterator<T>(root: TreeNode<T>) : Iterator<T> {
private val queue = ArrayDeque<TreeNode<T>>()
init {
queue.addLast(root)
}
override fun hasNext(): Boolean = queue.isNotEmpty()
override fun next(): T {
if (!hasNext()) throw NoSuchElementException()
val node = queue.removeFirst()
// 将子节点加入队尾
node.children.forEach { queue.addLast(it) }
return node.data
}
}
/**
* 为 TreeNode 添加可迭代能力
*/
fun <T> TreeNode<T>.depthFirst(): Iterable<T> = Iterable {
DepthFirstIterator(this)
}
fun <T> TreeNode<T>.breadthFirst(): Iterable<T> = Iterable {
BreadthFirstIterator(this)
}
// 使用示例:IoT 设备房间树
data class Room(val name: String, val deviceCount: Int)
val home = TreeNode(
Room("Home", 0),
listOf(
TreeNode(
Room("客厅", 3),
listOf(
TreeNode(Room("阳台", 1))
)
),
TreeNode(
Room("卧室", 2),
listOf(
TreeNode(Room("卫生间", 1)),
TreeNode(Room("衣帽间", 0))
)
)
)
)
// 深度优先:Home -> 客厅 -> 阳台 -> 卧室 -> 卫生间 -> 衣帽间
home.depthFirst().forEach { room ->
println("${room.name}: ${room.deviceCount} 台设备")
}
// 广度优先:Home -> 客厅 -> 卧室 -> 阳台 -> 卫生间 -> 衣帽间
home.breadthFirst().forEach { room ->
println("${room.name}: ${room.deviceCount} 台设备")
}
场景三:Cursor 的 Kotlin 扩展
封装 Cursor 的迭代逻辑,避免在业务代码中反复编写 while (cursor.moveToNext()) 样板代码。
/**
* 将 Cursor 转换为 Sequence,支持 Kotlin 标准库的
* map/filter/take 等操作,并自动管理资源释放。
*/
fun <T> Cursor.asSequence(transform: (Cursor) -> T): Sequence<T> {
return sequence {
use { cursor -> // use 会在结束时自动 close
while (cursor.moveToNext()) {
yield(transform(cursor))
}
}
}
}
/**
* 将 Cursor 映射为对象列表
*/
fun <T> Cursor.toList(transform: (Cursor) -> T): List<T> {
return asSequence(transform).toList()
}
// 使用示例
data class DeviceRecord(
val id: Long,
val name: String,
val type: Int,
val isOnline: Boolean
)
fun ContentResolver.queryDevices(): List<DeviceRecord> {
val cursor = query(
DeviceContract.CONTENT_URI,
arrayOf("_id", "name", "type", "is_online"),
null, null, "name ASC"
) ?: return emptyList()
return cursor.toList { c ->
DeviceRecord(
id = c.getLong(0),
name = c.getString(1),
type = c.getInt(2),
isOnline = c.getInt(3) == 1
)
}
}
// 结合 Sequence 的惰性求值:只取前 10 台在线设备
fun ContentResolver.queryOnlineDevices(limit: Int = 10):
List<DeviceRecord> {
val cursor = query(
DeviceContract.CONTENT_URI,
arrayOf("_id", "name", "type", "is_online"),
null, null, null
) ?: return emptyList()
return cursor.asSequence { c ->
DeviceRecord(
id = c.getLong(0),
name = c.getString(1),
type = c.getInt(2),
isOnline = c.getInt(3) == 1
)
}
.filter { it.isOnline }
.take(limit)
.toList()
}
与其他模式的对比
迭代器模式 vs 访问者模式
| 维度 | 迭代器模式 | 访问者模式 |
|---|---|---|
| 关注点 | 按顺序访问每个元素 | 对每个元素执行特定操作 |
| 操作定义 | 遍历逻辑在迭代器中,处理逻辑在客户端 | 处理逻辑封装在访问者中 |
| 元素类型 | 通常同质(同一类型的元素) | 通常异质(不同类型的元素) |
| 典型场景 | Cursor 遍历数据库结果 | 编译器对 AST 节点的操作 |
迭代器模式 vs 组合模式
组合模式定义了树形结构,迭代器模式定义了遍历策略。二者经常配合使用:ViewGroup(组合模式)管理 View 树的结构,遍历子 View 时使用迭代器模式的思想。findViewTraversal() 对 View 树的深度优先遍历就是二者协作的典型案例。
Cursor vs Java Iterator
| 维度 | Cursor | Java Iterator |
|---|---|---|
| 遍历方向 | 双向(moveToNext / moveToPrevious) | 单向(next) |
| 随机访问 | 支持(moveToPosition) | 不支持 |
| 数据访问 | 提供类型化访问方法(getString/getInt) | 返回泛型对象 |
| 资源管理 | 需要显式 close | 无资源管理 |
| 适用场景 | 大数据集的分页遍历 | 内存中集合的遍历 |
总结与最佳实践
迭代器模式的核心价值在于将遍历逻辑从数据结构中分离,使客户端代码通过统一接口访问不同类型的聚合对象。Android 中的 Cursor 是这一模式在数据库访问领域的经典实现。
最佳实践:
-
优先使用 Kotlin 标准库的迭代能力:
Sequence、Iterable、Flow等已经内建了丰富的遍历和变换操作,自定义迭代器前应评估标准库是否已经满足需求 -
注意资源释放:
Cursor等持有系统资源的迭代器必须在使用完毕后关闭。Kotlin 的use {}扩展函数可以保证异常情况下资源也能正确释放 -
惰性求值优先:对于大数据集,使用
Sequence(惰性)而非List(急切)可以避免将全量数据加载到内存。Cursor的CursorWindow机制正是惰性求值的系统级实现 -
防止遍历期间修改:遍历过程中修改聚合对象会导致不可预期的行为。如果需要在遍历时删除元素,应使用
Iterator.remove()或收集待删除元素后统一处理 -
索引式遍历的性能优势:在 Android 性能敏感的场景(如
onDraw、事件分发),for (i in 0 until count)+getChildAt(i)的索引式遍历比创建Iterator对象更高效,避免了对象分配开销 -
选择合适的遍历策略:树结构选择深度优先还是广度优先取决于业务场景。搜索特定元素时深度优先更省内存,按层级处理时广度优先更直观
更多推荐



所有评论(0)