迭代 Android 光标的最佳方法是啥?

Posted

技术标签:

【中文标题】迭代 Android 光标的最佳方法是啥?【英文标题】:What's the best way to iterate an Android Cursor?迭代 Android 光标的最佳方法是什么? 【发布时间】:2012-05-30 05:27:15 【问题描述】:

我经常看到涉及对数据库查询结果进行迭代、对每一行执行某些操作,然后移至下一行的代码。典型例子如下。

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 方法。肯定有更整洁的方法吗?

【问题讨论】:

这样做的目的是什么?您在发布后一分钟内自己回答了... 我在提问的同时回答了这个问题 啊,以前从未见过那个链接。问一个你显然已经得到答案的问题似乎很愚蠢。 @Barak:我认为他发布这个帖子很棒——现在我知道了一种更简洁的做事方式,否则我不会知道。 我似乎很清楚,您发布此内容是为了帮助任何可能前来寻找的人。为此向您提供支持,并感谢您的有用提示! 【参考方案1】:

最简单的方法是这样的:

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()) 
        ...
    

【讨论】:

因此,如果您想事先将光标置于任意位置进行此迭代,您将在 while 循环之前使用 cursor.moveToPosition(-1)? 但在此之前我们必须检查光标是否为“空”。该空检查是否有任何替代方法? SQLite 数据库查询永远不会返回 null。如果没有找到结果,它将返回一个空的光标。不过,ContentProvider 查询有时会返回 null。 只是为了增加几分钱......不要通过调用 moveToFirst() 来检查光标是否有数据,然后再遍历光标 - 您将丢失第一个条目 如果使用CursorLoader,请确保在迭代之前调用cursor.moveToPosition(-1),因为当屏幕方向改变时加载器会重用光标。花了一个小时追踪这个问题!【参考方案2】:

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

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

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

之后别忘了关闭光标

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

【讨论】:

为什么要调用三种不同的方法,而只用一种方法呢?为什么你认为这样更好? 如果您要重新加载预先存在的光标并希望确保您的迭代从头开始,这是最安全的方法。 我同意它比更简单的替代方案更清晰。我通常倾向于简洁明了。与 while 循环类似的变体 - android.codota.com/scenarios/51891850da0a87eb5be3cc22/… 玩了更多游标之后,我更新了我的答案。给定的代码肯定不是迭代游标的最有效方法,但它确实有它的用途。 (见编辑) @AlexStyl 是的,确实如此!这拯救了我的理智!【参考方案3】:

我只想指出第三种选择,如果光标不在起始位置,它也可以:

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

【讨论】:

存在冗余检查。您可以将 if + do-while 替换为已接受的解决方案中给出的简单 while,这也更简单/更具可读性。 @mtk 不,这不是多余的,这就是重点 - 如果光标被重用,它可能在某个位置,因此需要显式调用 moveToFirst 这只有在你有一个带有日志记录的 else 语句时才有用;否则Graham Borland's answer 更简洁。 正如Vicky Chijwani's 评论指出的那样,在实际使用中,Graham Borland 的答案是不安全的,需要moveToPosition(-1)。因此,两个答案都同样简洁,因为它们都有两个游标调用。我认为这个答案只是超越了 Borland 的答案,因为它不需要 -1 幻数。【参考方案4】:

下面可能是更好的方法:

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

cursor.close();

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

【讨论】:

【参考方案5】:

如何使用 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 实例这一事实。【参考方案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() 还应该重新计算toVisit = cursor.getCount(); 我使用class IterableCursor&lt;T extends Cursor&gt; implements Iterable&lt;T&gt;, Iterator&lt;T&gt; ... 接收一个extends CursorWrapper implements MyInterface 的类,其中MyInterface 定义了数据库属性的getter。这样我就有了一个基于游标的 Iterator【参考方案7】:

Do/While 解决方案更优雅,但如果您只使用上面发布的 While 解决方案,没有 moveToPosition(-1),您将错过第一个元素(至少在 Contact 查询中)。

我建议:

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

【讨论】:

【参考方案8】:

游标是表示任何数据库的2-dimensional 表的接口。

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

这个返回引用的指针指向 第 0 个位置,否则它在游标的第一个位置之前被称为,所以当你想从游标中检索数据时,你必须第 1 个移动到第一条记录,所以我们必须使用 moveToFirst

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

最佳外观:

光标光标

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

【讨论】:

【参考方案9】:
if (cursor.getCount() == 0)
  return;

cursor.moveToFirst();

while (!cursor.isAfterLast())

  // do something
  cursor.moveToNext();


cursor.close();

【讨论】:

【参考方案10】:

最初游标不在使用moveToNext()的第一行显示,您可以在记录不存在时迭代游标然后它return false,除非它return true

while (cursor.moveToNext()) 
    ...

【讨论】:

以上是关于迭代 Android 光标的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

将两个列表组合成地图(Java)的最佳方法是啥?

避免android片段中内存泄漏的最佳方法是啥

在 Android 上实现 facebook 登录的最佳方法是啥?

Android隐藏日志的最佳方法是啥? [复制]

在android中获取当前准确位置的最佳方法是啥?

使 Android 应用程序崩溃的最佳方法是啥?