如何遍历 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&gt;=0; i++)...;或while (sparseArray.size()&gt;0) int key=sparseArray.keyAt(0);... 像“上述循环”这样的引用根本没有任何意义。 我认为“迭代器”的重点是安全的对象删除。我还没有看到任何带有 sparseArrays 的 Iterator 类的例子,就像 hashmaps 的例子一样。这最接近解决安全对象删除问题,我希望它在没有并发修改异常的情况下工作。【参考方案8】:

这里是Iterator&lt;T&gt;Iterable&lt;T&gt; 的简单实现SparseArray&lt;T&gt;

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&lt;T&gt;Iterable&lt;SparseKeyValue&lt;T&gt;&gt; 的实用方法很有用:

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&lt;T&gt;

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 -&gt;

【讨论】:

是的,你说得对。我的错,我查看了标签并认为 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()

我认为使用removeLongSparseArray 本身(而不是在迭代器上)删除项目甚至可能是安全的,因为它是按升序排列的。


编辑:似乎还有更简单的方法,使用 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.keyIteratorLongSparseArrayKt.valueIteratorLongSparseArrayKt.forEach 等。其他情况也一样。

【讨论】:

以上是关于如何遍历 SparseArray?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用包含 Hashmap 和 SparseArray 的自定义类实现 parcelable?

SparseArray具体解释,我说SparseArray,你说要!

[HDOJ5876]Sparse Graph(补图最短路)

性能优化SparseArray

Android SparseArray源码分析

如何实现 sparse_vector 类