迭代Android游标的最佳方法是什么?

Gra*_*and 284 android cursor

我经常看到代码涉及迭代数据库查询的结果,对每一行做一些事情,然后继续前进到下一行.典型的例子如下.

Cursor cursor = db.rawQuery(...);
cursor.moveToFirst();
while (cursor.isAfterLast() == false) 
{
    ...
    cursor.moveToNext();
}
Run Code Online (Sandbox Code Playgroud)
Cursor cursor = db.rawQuery(...);
for (boolean hasItem = cursor.moveToFirst(); 
     hasItem; 
     hasItem = cursor.moveToNext()) {
    ...
}
Run Code Online (Sandbox Code Playgroud)
Cursor cursor = db.rawQuery(...);
if (cursor.moveToFirst()) {
    do {
        ...                 
    } while (cursor.moveToNext());
}
Run Code Online (Sandbox Code Playgroud)

这些对我来说似乎过于冗长,每次都有多次调用Cursor方法.当然必须有一个更简洁的方式?

Gra*_*and 510

最简单的方法是:

while (cursor.moveToNext()) {
    ...
}
Run Code Online (Sandbox Code Playgroud)

游标第一个结果行之前开始,因此在第一次迭代时,如果存在,则移动到第一个结果.如果光标为空,或者已经处理了最后一行,则循环退出整齐.

当然,完成后不要忘记关闭光标,最好是在finally子句中.

Cursor cursor = db.rawQuery(...);
try {
    while (cursor.moveToNext()) {
        ...
    }
} finally {
    cursor.close();
}
Run Code Online (Sandbox Code Playgroud)

如果您的目标是API 19+,则可以使用try-with-resources.

try (Cursor cursor = db.rawQuery(...)) {
    while (cursor.moveToNext()) {
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 如果使用`CursorLoader`,请确保在迭代之前调用`cursor.moveToPosition(-1)`,因为加载器在屏幕方向改变时重用光标.花了一个小时追查这个问题! (47认同)
  • 别忘了关闭它! (42认同)
  • 所以如果你想事先在光标位置进行迭代,你可以在while循环之前使用cursor.moveToPosition(-1)吗? (19认同)
  • SQLite数据库查询永远不会返回null.如果没有找到结果,它将返回一个空的Cursor.但是,ContentProvider查询有时会返回null. (13认同)
  • 只是添加几美分......在你要迭代游标之前,不要通过调用moveToFirst()来检查游标是否有数据 - 你将丢失第一个条目 (8认同)
  • @Sam是的,确切地说. (2认同)
  • 但在此之前我们必须检查光标是否为"null".那个空检查还有其他选择吗? (2认同)

Ale*_*tyl 107

我发现通过光标的最佳方式如下:

Cursor cursor;
... //fill the cursor here

for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
    // do what you need with the cursor here
}
Run Code Online (Sandbox Code Playgroud)

不要忘记之后关闭光标

编辑:如果你需要迭代一个你不负责的游标,给定的解决方案很棒.一个很好的例子是,如果您将光标作为方法中的参数,并且您需要扫描光标以获得给定值,而不必担心光标的当前位置.

  • 如果您正在重新加载预先存在的游标并且想要确保迭代从头开始,那么这是最安全的方法. (11认同)
  • 我同意它比简单的替代方案更清楚.我通常赞成清晰简洁.与while循环类似的变体 - http://android.codota.com/scenarios/51891850da0a87eb5be3cc22/android.database.Cursor?tag=out_2013_05_05_07_19_34&fullSource=1 (9认同)
  • 为什么只用一种方法调用三种不同的方法?为什么你认为那更好? (8认同)

Jör*_*eld 43

我只想指出第三种替代方案,如果光标不在起始位置也可以使用:

if (cursor.moveToFirst()) {
    do {
        // do what you need with the cursor here
    } while (cursor.moveToNext());
}
Run Code Online (Sandbox Code Playgroud)

  • @mtk不,它不是多余的,这就是重点 - 如果光标被重用,它可能在一个位置,因此需要显式调用moveToFirst (6认同)
  • 正如[Vicky Chijwani](http://stackoverflow.com/questions/10723770/whats-the-best-way-to-iterate-an-android-cursor#comment33274077_10723771)评论指出,在现实世界中使用Graham Borland的答案是不安全的,需要`moveToPosition(-1)`.所以两个答案都是同样的,因为它们都有两个光标调用.我认为这个答案只是因为它不需要`-1`魔数来解决Borland的答案. (4认同)

小智 9

如何使用foreach循环:

Cursor cursor;
for (Cursor c : CursorUtils.iterate(cursor)) {
    //c.doSth()
}
Run Code Online (Sandbox Code Playgroud)

但是我的CursorUtils版本应该不那么难看,但它会自动关闭游标:

public class CursorUtils {
public static Iterable<Cursor> iterate(Cursor cursor) {
    return new IterableWithObject<Cursor>(cursor) {
        @Override
        public Iterator<Cursor> iterator() {
            return new IteratorWithObject<Cursor>(t) {
                @Override
                public boolean hasNext() {
                    t.moveToNext();
                    if (t.isAfterLast()) {
                        t.close();
                        return false;
                    }
                    return true;
                }
                @Override
                public Cursor next() {
                    return t;
                }
                @Override
                public void remove() {
                    throw new UnsupportedOperationException("CursorUtils : remove : ");
                }
                @Override
                protected void onCreate() {
                    t.moveToPosition(-1);
                }
            };
        }
    };
}

private static abstract class IteratorWithObject<T> implements Iterator<T> {
    protected T t;
    public IteratorWithObject(T t) {
        this.t = t;
        this.onCreate();
    }
    protected abstract void onCreate();
}

private static abstract class IterableWithObject<T> implements Iterable<T> {
    protected T t;
    public IterableWithObject(T t) {
        this.t = t;
    }
}
}
Run Code Online (Sandbox Code Playgroud)


Pan*_*kaj 8

以下可能是更好的方法:

if (cursor.moveToFirst()) {
   while (!cursor.isAfterLast()) {
         //your code to implement
         cursor.moveToNext();
    }
}
cursor.close();
Run Code Online (Sandbox Code Playgroud)

上面的代码将确保它将经历整个迭代,并且不会在第一次和最后一次迭代时转义.


184*_*615 6

import java.util.Iterator;
import android.database.Cursor;

public class IterableCursor implements Iterable<Cursor>, Iterator<Cursor> {
    Cursor cursor;
    int toVisit;
    public IterableCursor(Cursor cursor) {
        this.cursor = cursor;
        toVisit = cursor.getCount();
    }
    public Iterator<Cursor> iterator() {
        cursor.moveToPosition(-1);
        return this;
    }
    public boolean hasNext() {
        return toVisit>0;
    }
    public Cursor next() {
    //  if (!hasNext()) {
    //      throw new NoSuchElementException();
    //  }
        cursor.moveToNext();
        toVisit--;
        return cursor;
    }
    public void remove() {
        throw new UnsupportedOperationException();
    }
}
Run Code Online (Sandbox Code Playgroud)

示例代码:

static void listAllPhones(Context context) {
    Cursor phones = context.getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);
    for (Cursor phone : new IterableCursor(phones)) {
        String name = phone.getString(phone.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
        String phoneNumber = phone.getString(phone.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
        Log.d("name=" + name + " phoneNumber=" + phoneNumber);
    }
    phones.close();
}
Run Code Online (Sandbox Code Playgroud)