【问题标题】:How to iterate through SparseArray?如何遍历 SparseArray?
【发布时间】:2011-12-21 09:39:44
【问题描述】:

有没有办法迭代 Java SparseArray(适用于 Android)?我使用sparsearray 轻松按索引获取值。我找不到。

【问题讨论】:

  • 哇,说说completely unloved class,符合零集合接口...
  • 您可以使用TreeMap<Integer, MyType>,它允许您按键按顺序进行迭代。如前所述,SparseArray 被设计为比 HashMap 更高效,但它不允许迭代。
  • 您选择的地图 impl 的性能不太可能成为您应用的瓶颈。
  • @JeffreyBlattman 并不意味着我们应该避免在明显合适的情况下使用正确的结构。
  • @frostymarvelous 说它的速度是两倍,这可能意味着节省不到 10 毫秒。 10ms 在应用程序的宏大计划中是否相关?是否值得使用更难理解和维护的次优界面?我不知道这些事情的答案,但答案不是“无论如何都绝对使用稀疏数组”。

标签: java android


【解决方案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 的方法是:使用来自 AnkoAndroid 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 等。其他情况也一样。

        【讨论】:

          猜你喜欢
          • 2011-04-09
          • 2010-12-18
          • 2017-01-10
          • 2012-02-27
          • 2016-11-23
          • 2017-03-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多