如何通过SparseArray进行迭代?


311

有没有一种方法可以遍历Java SparseArray(适用于Android)?我曾经sparsearray很容易通过索引来获取值。我找不到一个。


30
哇,说说一个完全不受欢迎的课程,符合零收集接口...

1
您可以使用TreeMap<Integer, MyType>允许按键顺序迭代的。如前所述,SparseArray设计为比HashMap更有效,但不允许迭代。
约翰B

2
您选择的地图暗示的性能几乎不可能成为应用程序的瓶颈。
Jeffrey Blattman 2014年

3
@JeffreyBlattman并不意味着我们显然应该避免使用正确的结构。
frostymarvelous

1
@frostymarvelous说它快了两次,这可能意味着节省了不到10毫秒的时间。10ms与应用程序的宏伟计划相关吗?使用难以理解和维护的次优接口值得吗?我不知道这些事情的答案,但是答案不是“无论如何都绝对使用稀疏数组”。
杰弗里·布拉特曼

Answers:


536

似乎我找到了解决方案。我没有正确注意到该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);
}

25
文档指出“ keyAt(int index)给定范围为0 ... size()-1的索引,则从此SparseArray存储的索引值键-值映射中返回键”。因此即使您描述的情况对我来说也很好。
Ruzanna 2012年

12
最好预先计算数组的大小并在循环中使用常量。
德米特里·扎伊采夫2012年

25
在这里直接使用valueAt函数会更容易吗?
米兰·克里斯蒂克,

34
这在循环内也行得通:Object obj = sparseArray.valueAt(i);
Florian

27
valueAt(i)快于get(key),因为valueAt(i)keyAt(i)都为O(1),但是get(key)O(log2 n),所以我肯定会一直使用valueAt
梅基

180

如果您不关心键,则valueAt(int)可以在遍历稀疏数组以直接访问值时习惯。

for(int i = 0, nsize = sparseArray.size(); i < nsize; i++) {
    Object obj = sparseArray.valueAt(i);
}

7
如果您的迭代不关心键,则使用valueAt()很有用(并且比接受的解决方案还快),即:循环计数特定值的出现。
索格2013年

2
接受sparseArray.size()一个变量,这样它就不会size()每次都调用。
Pratik Butani '16

4
将size()复制到一个变量是多余的。只需查看size()方法的代码即可轻松检查。我不明白为什么您在提出这样的建议之前就没有……我记得20年前的某个时候,我们有简单的链表,每次您要求它们时,它们实际上都必须计算其大小,但我不相信这样的事情仍然存在...
令人难以置信的

是否保证按关键顺序进行?
HughHughTeotl

18

您只需创建自己的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();
    }
}
}

9
恕我直言,这似乎有点工程过度。这真棒寿
hrules6872

12

简单如馅饼。只要确保实际执行循环之前获取数组大小即可。

for(int i = 0, arraySize= mySparseArray.size(); i < arraySize; i++) {
   Object obj = mySparseArray.get(/* int key = */ mySparseArray.keyAt(i));
}

希望这可以帮助。


11

老实说,对于使用Kotlin的人来说,迭代SparseArray的最简单方法是:使用AnkoAndroid KTX的Kotlin扩展!(感谢Yazazzello指出了Android KTX)

只需致电 forEach { i, item -> }


是的,您实际上是对的。不好,我看了看标签,以为Kotlin不应该在这里。但是,现在又想一想,这个答案很好地参考了Kotlin本身。虽然建议不要使用Anko,但我建议使用android.github.io/android-ktx/core-ktx(如果您可以编辑答案并添加android-ktx,我会对其进行
投票

@Yazazzello嘿,我什至不了解Android KTX,很好!
0101100101 '18

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;
    }
}

2
i = -1; 最后什么也没做。还有一种.clear()应该被称为的方法。
Paul Woitaschek '16

为什么要使用for()循环而不是while()?你在做什么是没有意义的循环
菲尔

我认为Sackurise想要写信i-=1;以说明现在缺少的元素。但它是更好的恢复循环:for(int i=sparseArray.size()-1; i>=0; i++){...; 或者while (sparseArray.size()>0) { int key=sparseArray.keyAt(0);...
部份

诸如“上述循环”之类的引用根本没有任何意义。
令人难以置信

我认为“迭代器”的重点是安全地移除对象。我还没有看到像s哈希映射这样的带有sparseArrays的Iterator类的示例。这最接近解决安全对象删除问题,我希望它可以在没有并发修改异常的情况下工作。
Androidcoder

5

这是简单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)) {
  // ...
}

4

如果您使用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(示例在这里)。实际上,它的实现方式与我写的非常相似。

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.valueIterator并且LongSparseArrayKt.forEach,例如。其他情况相同。


-5

答案是否定的,因为SparseArray没有提供。如此pst说来,这个东西不提供任何接口。

您可以循环0 - size()并跳过返回的值null,仅此而已。

正如我在评论中指出的那样,如果您需要迭代,请使用Map而不是SparseArray。例如,使用TreeMap按键顺序迭代的a 。

TreeMap<Integer, MyType>

-6

接受的答案有一些漏洞。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()方法,但可惜没有。


4
您错了-该keyAt方法返回第n个键的值(在您的示例keyAt(1)中将返回250),不要与get哪个键返回键所引用的元素的值。
Eborbob 2015年

我不确定您的评论中的“ this”是什么。您是否承认答案是错误的,还是您对我的评论是错误的?如果是后者,请检查developer.android.com/reference/android/util/…–
Eborbob

17
我的答案是错误的,我不会删除它,以便其他人可以学习。
泰勒·帕夫
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.