如何遍历 SparseArray?
Posted
技术标签:
【中文标题】如何遍历 SparseArray?【英文标题】:How to iterate through SparseArray? 【发布时间】:2011-12-21 09:39:44 【问题描述】:有没有办法迭代 Java SparseArray(适用于 android)?我使用sparsearray
轻松按索引获取值。我找不到。
【问题讨论】:
哇,说说completely unloved class,符合零集合接口... 您可以使用TreeMap<Integer, MyType>
,它允许您按键按顺序进行迭代。如前所述,SparseArray 被设计为比 HashMap 更高效,但它不允许迭代。
您选择的地图 impl 的性能不太可能成为您应用的瓶颈。
@JeffreyBlattman 并不意味着我们应该避免在明显合适的情况下使用正确的结构。
@frostymarvelous 说它的速度是两倍,这可能意味着节省不到 10 毫秒。 10ms 在应用程序的宏大计划中是否相关?是否值得使用更难理解和维护的次优界面?我不知道这些事情的答案,但答案不是“无论如何都绝对使用稀疏数组”。
【参考方案1】:
答案是否定的,因为SparseArray
没有提供它。正如pst
所说,这个东西不提供任何接口。
您可以从 0 - size()
循环并跳过返回 null
的值,但仅此而已。
正如我在评论中所说,如果您需要迭代使用 Map
而不是 SparseArray
。例如,使用一个TreeMap
,它按键的顺序进行迭代。
TreeMap<Integer, MyType>
【讨论】:
【参考方案2】:似乎我找到了解决方案。我没有注意到keyAt(index)
函数。
所以我会选择这样的:
for(int i = 0; i < sparseArray.size(); i++)
int key = sparseArray.keyAt(i);
// get the object by the key.
Object obj = sparseArray.get(key);
【讨论】:
文档指出“keyAt(int index) 给定范围 0...size()-1 的索引,从该 SparseArray 存储的第索引键值映射中返回键。”所以即使对于你描述的情况,它对我来说也很好。 最好预先计算数组的大小并在循环中使用常量值。 这里直接使用valueAt函数不是更方便吗? 这在循环内也可以工作:Object obj = sparseArray.valueAt(i);
valueAt(i)
比 get(key)
快,因为 valueAt(i)
和 keyAt(i)
都是 O(1),但 get(key)
是 O( log2 n),所以我肯定会一直使用valueAt
。【参考方案3】:
如果您不关心键,则可以使用valueAt(int)
来遍历稀疏数组以直接访问值。
for(int i = 0, nsize = sparseArray.size(); i < nsize; i++)
Object obj = sparseArray.valueAt(i);
【讨论】:
如果您的迭代不关心键,则使用 valueAt() 很有用(并且比公认的解决方案更快),即:循环计算特定值的出现次数。 将sparseArray.size()
放在一个变量中,这样它就不会每次都调用size()
。
将 size() 复制到变量是多余的。如果你只看 size() 方法的代码,很容易检查。我不明白为什么在你提出这些建议之前你不这样做......我记得 20 年前的一次,我们有简单的链表,每次你要求它们时都必须计算它们的大小,但我不相信这样的事情仍然存在......
这是否保证键序?【参考方案4】:
哦,您只需创建自己的 ListIterator:
public final class SparseArrayIterator<E> implements ListIterator<E>
private final SparseArray<E> array;
private int cursor;
private boolean cursorNowhere;
/**
* @param array
* to iterate over.
* @return A ListIterator on the elements of the SparseArray. The elements
* are iterated in the same order as they occur in the SparseArray.
* @link #nextIndex() and @link #previousIndex() return a
* SparseArray key, not an index! To get the index, call
* @link android.util.SparseArray#indexOfKey(int).
*/
public static <E> ListIterator<E> iterate(SparseArray<E> array)
return iterateAt(array, -1);
/**
* @param array
* to iterate over.
* @param key
* to start the iteration at. @link android.util.SparseArray#indexOfKey(int)
* < 0 results in the same call as @link #iterate(android.util.SparseArray).
* @return A ListIterator on the elements of the SparseArray. The elements
* are iterated in the same order as they occur in the SparseArray.
* @link #nextIndex() and @link #previousIndex() return a
* SparseArray key, not an index! To get the index, call
* @link android.util.SparseArray#indexOfKey(int).
*/
public static <E> ListIterator<E> iterateAtKey(SparseArray<E> array, int key)
return iterateAt(array, array.indexOfKey(key));
/**
* @param array
* to iterate over.
* @param location
* to start the iteration at. Value < 0 results in the same call
* as @link #iterate(android.util.SparseArray). Value >
* @link android.util.SparseArray#size() set to that size.
* @return A ListIterator on the elements of the SparseArray. The elements
* are iterated in the same order as they occur in the SparseArray.
* @link #nextIndex() and @link #previousIndex() return a
* SparseArray key, not an index! To get the index, call
* @link android.util.SparseArray#indexOfKey(int).
*/
public static <E> ListIterator<E> iterateAt(SparseArray<E> array, int location)
return new SparseArrayIterator<E>(array, location);
private SparseArrayIterator(SparseArray<E> array, int location)
this.array = array;
if (location < 0)
cursor = -1;
cursorNowhere = true;
else if (location < array.size())
cursor = location;
cursorNowhere = false;
else
cursor = array.size() - 1;
cursorNowhere = true;
@Override
public boolean hasNext()
return cursor < array.size() - 1;
@Override
public boolean hasPrevious()
return cursorNowhere && cursor >= 0 || cursor > 0;
@Override
public int nextIndex()
if (hasNext())
return array.keyAt(cursor + 1);
else
throw new NoSuchElementException();
@Override
public int previousIndex()
if (hasPrevious())
if (cursorNowhere)
return array.keyAt(cursor);
else
return array.keyAt(cursor - 1);
else
throw new NoSuchElementException();
@Override
public E next()
if (hasNext())
if (cursorNowhere)
cursorNowhere = false;
cursor++;
return array.valueAt(cursor);
else
throw new NoSuchElementException();
@Override
public E previous()
if (hasPrevious())
if (cursorNowhere)
cursorNowhere = false;
else
cursor--;
return array.valueAt(cursor);
else
throw new NoSuchElementException();
@Override
public void add(E object)
throw new UnsupportedOperationException();
@Override
public void remove()
if (!cursorNowhere)
array.remove(array.keyAt(cursor));
cursorNowhere = true;
cursor--;
else
throw new IllegalStateException();
@Override
public void set(E object)
if (!cursorNowhere)
array.setValueAt(cursor, object);
else
throw new IllegalStateException();
【讨论】:
恕我直言,这似乎有点过度设计。太棒了【参考方案5】:接受的答案有一些漏洞。 SparseArray 的美妙之处在于它允许索引中的间隙。所以,我们可以在一个 SparseArray 中有两个这样的映射...
(0,true)
(250,true)
注意这里的大小是 2。如果我们迭代大小,我们将只得到映射到索引 0 和索引 1 的值的值。所以键为 250 的映射不会被访问。
for(int i = 0; i < sparseArray.size(); i++)
int key = sparseArray.keyAt(i);
// get the object by the key.
Object obj = sparseArray.get(key);
执行此操作的最佳方法是迭代数据集的大小,然后使用数组上的 get() 检查这些索引。这是一个带有适配器的示例,我允许批量删除项目。
for (int index = 0; index < mAdapter.getItemCount(); index++)
if (toDelete.get(index) == true)
long idOfItemToDelete = (allItems.get(index).getId());
mDbManager.markItemForDeletion(idOfItemToDelete);
我认为理想情况下 SparseArray 系列应该有一个 getKeys() 方法,但可惜它没有。
【讨论】:
你错了 -keyAt
方法返回第 n 个键的值(在你的示例中,keyAt(1)
将返回 250
),不要与返回的 get
混淆键引用的元素的值。
我不确定您评论中的“这个”是什么。你是承认你的回答是错误的,还是说我的评论是错误的?如果是后者请查看developer.android.com/reference/android/util/…
我的回答错了,我不会删掉让别人学习。【参考方案6】:
简单如馅饼。只需确保在实际执行循环之前 获取数组大小。
for(int i = 0, arraySize= mySparseArray.size(); i < arraySize; i++)
Object obj = mySparseArray.get(/* int key = */ mySparseArray.keyAt(i));
希望这会有所帮助。
【讨论】:
【参考方案7】:使用上述循环从SparseArray
中删除所有元素会导致Exception
。
为避免这种情况,请按照以下代码使用普通循环从 SparseArray
中删除所有元素
private void getValues()
for(int i=0; i<sparseArray.size(); i++)
int key = sparseArray.keyAt(i);
Log.d("Element at "+key, " is "+sparseArray.get(key));
sparseArray.remove(key);
i=-1;
【讨论】:
i=-1;最后什么都不做。还有一种叫做.clear()
的方法应该受到青睐。
为什么要使用 for() 循环而不是 while()?你在做什么对循环没有意义
我假设 Sackurise 想写 i-=1;
来解释现在缺少的元素。但最好恢复循环:for(int i=sparseArray.size()-1; i>=0; i++)...
;或while (sparseArray.size()>0) int key=sparseArray.keyAt(0);...
像“上述循环”这样的引用根本没有任何意义。
我认为“迭代器”的重点是安全的对象删除。我还没有看到任何带有 sparseArrays 的 Iterator 类的例子,就像 hashmaps 的例子一样。这最接近解决安全对象删除问题,我希望它在没有并发修改异常的情况下工作。【参考方案8】:
这里是Iterator<T>
和Iterable<T>
的简单实现SparseArray<T>
:
public class SparseArrayIterator<T> implements Iterator<T>
private final SparseArray<T> array;
private int index;
public SparseArrayIterator(SparseArray<T> array)
this.array = array;
@Override
public boolean hasNext()
return array.size() > index;
@Override
public T next()
return array.valueAt(index++);
@Override
public void remove()
array.removeAt(index);
public class SparseArrayIterable<T> implements Iterable<T>
private final SparseArray<T> sparseArray;
public SparseArrayIterable(SparseArray<T> sparseArray)
this.sparseArray = sparseArray;
@Override
public Iterator<T> iterator()
return new SparseArrayIterator<>(sparseArray);
如果你不仅要迭代一个值,还要迭代一个键:
public class SparseKeyValue<T>
private final int key;
private final T value;
public SparseKeyValue(int key, T value)
this.key = key;
this.value = value;
public int getKey()
return key;
public T getValue()
return value;
public class SparseArrayKeyValueIterator<T> implements Iterator<SparseKeyValue<T>>
private final SparseArray<T> array;
private int index;
public SparseArrayKeyValueIterator(SparseArray<T> array)
this.array = array;
@Override
public boolean hasNext()
return array.size() > index;
@Override
public SparseKeyValue<T> next()
SparseKeyValue<T> keyValue = new SparseKeyValue<>(array.keyAt(index), array.valueAt(index));
index++;
return keyValue;
@Override
public void remove()
array.removeAt(index);
public class SparseArrayKeyValueIterable<T> implements Iterable<SparseKeyValue<T>>
private final SparseArray<T> sparseArray;
public SparseArrayKeyValueIterable(SparseArray<T> sparseArray)
this.sparseArray = sparseArray;
@Override
public Iterator<SparseKeyValue<T>> iterator()
return new SparseArrayKeyValueIterator<T>(sparseArray);
创建返回 Iterable<T>
和 Iterable<SparseKeyValue<T>>
的实用方法很有用:
public abstract class SparseArrayUtils
public static <T> Iterable<SparseKeyValue<T>> keyValueIterable(SparseArray<T> sparseArray)
return new SparseArrayKeyValueIterable<>(sparseArray);
public static <T> Iterable<T> iterable(SparseArray<T> sparseArray)
return new SparseArrayIterable<>(sparseArray);
现在你可以迭代SparseArray<T>
:
SparseArray<String> a = ...;
for (String s: SparseArrayUtils.iterable(a))
// ...
for (SparseKeyValue<String> s: SparseArrayUtils.keyValueIterable(a))
// ...
【讨论】:
【参考方案9】:对于使用 Kotlin 的人来说,老实说,最简单的迭代 SparseArray 的方法是:使用来自 Anko 或 Android KTX 的 Kotlin 扩展! (感谢 Yazazzello 指出 Android KTX)
只需拨打forEach i, item ->
【讨论】:
是的,你说得对。我的错,我查看了标签并认为 Kotlin 不应该在这里。但是现在重新考虑这个答案是对 Kotlin 本身的一个很好的参考。虽然我建议不要使用 Anko,而是使用 android.github.io/android-ktx/core-ktx(如果您可以编辑您的答案并添加 android-ktx,我会支持它) @Yazazzello 嘿,我什至不知道 Android KTX,好点子!【参考方案10】:如果你使用 Kotlin,你可以使用扩展函数,例如:
fun <T> LongSparseArray<T>.valuesIterator(): Iterator<T>
val nSize = this.size()
return object : Iterator<T>
var i = 0
override fun hasNext(): Boolean = i < nSize
override fun next(): T = valueAt(i++)
fun <T> LongSparseArray<T>.keysIterator(): Iterator<Long>
val nSize = this.size()
return object : Iterator<Long>
var i = 0
override fun hasNext(): Boolean = i < nSize
override fun next(): Long = keyAt(i++)
fun <T> LongSparseArray<T>.entriesIterator(): Iterator<Pair<Long, T>>
val nSize = this.size()
return object : Iterator<Pair<Long, T>>
var i = 0
override fun hasNext(): Boolean = i < nSize
override fun next() = Pair(keyAt(i), valueAt(i++))
如果您愿意,也可以转换为列表。示例:
sparseArray.keysIterator().asSequence().toList()
我认为使用remove
在LongSparseArray
本身(而不是在迭代器上)删除项目甚至可能是安全的,因为它是按升序排列的。
编辑:似乎还有更简单的方法,使用 collection-ktx (example here) 。实际上,它的实现方式与我写的非常相似。
Gradle 需要这个:
implementation 'androidx.core:core-ktx:#'
implementation 'androidx.collection:collection-ktx:#'
这是 LongSparseArray 的用法:
val sparse= LongSparseArray<String>()
for (key in sparse.keyIterator())
for (value in sparse.valueIterator())
sparse.forEach key, value ->
对于使用 Java 的用户,您可以使用 LongSparseArrayKt.keyIterator
、 LongSparseArrayKt.valueIterator
和 LongSparseArrayKt.forEach
等。其他情况也一样。
【讨论】:
以上是关于如何遍历 SparseArray?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用包含 Hashmap 和 SparseArray 的自定义类实现 parcelable?