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


291

我经常看到一些代码,其中涉及遍历数据库查询的结果,对每一行进行处理,然后移至下一行。典型示例如下。

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

这些对我来说似乎太冗长了,每个都有多次调用Cursor方法。肯定有更整洁的方法吗?


1
目的是什么?您在发布后的一分钟内亲自回答了该问题
Barak 2012年

10
询问的同时回答了它。
Graham Borland 2012年

1
啊,以前没看过那个链接。问一个您显然已经回答的问题似乎很愚蠢。
巴拉克(Barak)

5
@Barak:我认为他能干这个职位真是太好了-现在我知道做某事的方式更整洁了,否则我将一无所知。
乔治

4
在我看来,您发布此信息的目的似乎是为了帮助任何可能会寻找它的人。为您提供支持,并感谢您提供的有用提示!
muttley91 2013年

Answers:


515

最简单的方法是这样的:

while (cursor.moveToNext()) {
    ...
}

光标第一个结果行之前开始,因此在第一次迭代时,它会移动到第一个结果(如果存在)。如果游标为空,或者最后一行已被处理,则循环将干净地退出。

当然,不要忘记在完成游标后就将其关闭,最好在finally子句中。

Cursor cursor = db.rawQuery(...);
try {
    while (cursor.moveToNext()) {
        ...
    }
} finally {
    cursor.close();
}

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

try (Cursor cursor = db.rawQuery(...)) {
    while (cursor.moveToNext()) {
        ...
    }
}

19
因此,如果您希望事先将光标放在一个矮小的位置上进行此迭代,则可以在while循环之前使用cursor.moveToPosition(-1)?
山姆

43
别忘了关闭它!
simon

13
SQLite数据库查询将永远不会返回null。如果找不到结果,它将返回一个空的游标。但是,ContentProvider查询有时可以返回null。
Graham Borland

8
只是增加几分钱...在要遍历游标之前,不要通过调用moveToFirst()来检查游标是否有数据-您将丢失第一个条目
AAverin13年

47
如果使用CursorLoader,请确保cursor.moveToPosition(-1)在进行迭代之前先进行调用,因为加载器会在屏幕方向更改时重用光标。花费了一个小时追踪此问题!
Vicky Chijwani 2014年

111

我发现通过游标的最佳外观是:

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

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

不要忘了之后关闭光标

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


8
如果只用一种方法就可以调用三种不同的方法?您为什么认为那更好?
Graham Borland

11
如果要重新加载预先存在的游标,并要确保迭代从头开始,这是最安全的方法。
Michael Eilers Smith

9
我同意它比简单的替代方案更为清晰。为了简洁起见,我通常都主张清晰。与while循环类似的变化- android.codota.com/scenarios/51891850da0a87eb5be3cc22/...
drorw

在使用游标播放了更多内容之后,我更新了答案。给定的代码肯定不是迭代游标的最有效方法,但是它确实有其用法。(查看修改)
Alex Styl 2014年

@AlexStyl是的,确实如此!这节省了我的理智!
亚历山德罗(Alessandro)2016年

45

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

if (cursor.moveToFirst()) {
    do {
        // do what you need with the cursor here
    } while (cursor.moveToNext());
}

1
有多余的检查。您可以用公认的解决方案中给出的简单while代替if + do-while,它也更简单/更具可读性。
mtk 2014年

6
@mtk不,这不是多余的,这就是重点-如果要重复使用光标,则它可能在某个位置,因此需要显式调用moveToFirst
Mike Repass

仅当您具有带有日志记录的else语句时,此选项才有用。否则,格雷厄姆·博兰(Graham Borland)的答案会更加简洁。
2014年

5
正如Vicky Chijwani的评论所指出的那样,在现实世界中,Graham Borland的回答是不安全的,并且是必需的moveToPosition(-1)。因此,两个答案都同样简洁,因为它们都有两个游标调用。我认为这个答案只是在Borland的答案之外,因为它不需要-1幻数。
本杰明

我实际上认为它在您想知道游标没有值的情况下很有用,因为这很容易,并且在此处将if添加到else语句是有意义的。
chacham15'1

9

如何使用foreach循环:

Cursor cursor;
for (Cursor c : CursorUtils.iterate(cursor)) {
    //c.doSth()
}

但是我的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;
    }
}
}

这是一个很酷的解决方案,但它掩盖了您Cursor在循环的每次迭代中都使用相同实例的事实。
npace

8

下面可能是更好的方法:

if (cursor.moveToFirst()) {
   while (!cursor.isAfterLast()) {
         //your code to implement
         cursor.moveToNext();
    }
}
cursor.close();

上面的代码将确保它会经历整个迭代,并且不会逃脱第一次和最后一次迭代。


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

示例代码:

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

+1实现美观,紧凑的实现。错误修正:我iterator()也应该重新计算接收到的类,该类的MyInterface定义了数据库属性的获取方法。这样我有一个基于游标的Iterator <MyInterface>toVisit = cursor.getCount();class IterableCursor<T extends Cursor> implements Iterable<T>, Iterator<T> {...extends CursorWrapper implements MyInterface
k3b

4

“执行/同时执行”解决方案更为优雅,但如果仅使用上面发布的While解决方案,而没有moveToPosition(-1),则将错过第一个元素(至少在Contact查询中)。

我建议:

if (cursor.getCount() > 0) {
    cursor.moveToPosition(-1);
    while (cursor.moveToNext()) {
          <do stuff>
    }
}

2
if (cursor.getCount() == 0)
  return;

cursor.moveToFirst();

while (!cursor.isAfterLast())
{
  // do something
  cursor.moveToNext();
}

cursor.close();

2

光标是一个表示界面2-dimensional的任何数据库的表。

当您尝试使用SELECT语句检索某些数据时,数据库将首先创建一个CURSOR对象,并将其引用返回给您。

该返回引用的指针指向第0个位置,否则将其称为Cursor第一个位置之前的位置,因此,当您要从游标中检索数据时,必须先移至第一个记录,所以我们必须使用moveToFirst

当您moveToFirst()游标上调用方法时它将光标指针移到第一个位置。现在您可以访问第一条记录中的数据

最好的外观:

光标游标

for (cursor.moveToFirst(); 
     !cursor.isAfterLast();  
     cursor.moveToNext()) {
                  .........
     }

0

最初,光标不在第一行显示中,使用moveToNext()可以在不存在记录时对其进行迭代return false,除非它存在return true

while (cursor.moveToNext()) {
    ...
}
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.