2012-05-23 12 views
244

Często widzę kod, który polega na iteracji wyniku zapytania bazy danych, wykonaniu każdego wiersza, a następnie przejściu do następnego wiersza. Typowe przykłady są następujące.Jaki jest najlepszy sposób na iterowanie kursora Android Cursor?

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

Te wszystkie wydają się zbyt rozwlekły do ​​mnie, każdy z wielu połączeń do Cursor metod. Z pewnością musi być jakiś lepszy sposób?

+1

Jaki był cel tego? Sam odpowiedziałeś w ciągu minuty od wysłania ... – Barak

+9

Odpowiedziałem na to ** w tym samym czasie **, prosząc o to. –

+1

Ah, nigdy nie widziałem tego łącza wcześniej. Po prostu wydawało się głupio zadać pytanie, na które najwyraźniej już pani odpowiedziała. – Barak

Odpowiedz

465

Najprostszym sposobem jest taka:

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

Kursor zacznie przed pierwszy wynik wiersz, więc na pierwszej iteracji ten przesuwa się do pierwszego wyniku jeśli istnieje. Jeśli kursor jest pusty lub ostatni wiersz został już przetworzony, pętla wychodzi starannie.

Oczywiście, nie zapomnij zamknąć kursora, gdy skończysz, najlepiej w klauzuli finally.

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

Jeśli kierujesz się na interfejs API 19+, możesz użyć metody try-with-resources.

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

Dzięki Graham pomaga +1 ... – GoCrazy

+17

, więc jeśli chcesz zrobić tę iterację z kursorem w pozycji abritrary wcześniej, należy użyć cursor.moveToPosition (-1) przed pętli while? – Sam

+2

@ Tak, dokładnie tak. –

94

Najlepszym sposobem patrząc Znalazłem przejść kursorem jest następujący:

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

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

Nie zapomnij zamknąć kursor potem

EDIT: danego rozwiązanie jest świetne, jeśli zajdzie potrzeba powtórzenia kursora, za który nie odpowiadasz. Dobrym przykładem może być, jeśli bierzesz kursor jako argument w metodzie, i musisz zeskanować kursor dla danej wartości, bez martwienia się o aktualną pozycję kursora.

+6

Po co wywoływać trzy różne metody, kiedy można to zrobić za pomocą tylko jednego? Dlaczego uważasz, że jest lepiej? –

+10

To jest najbezpieczniejszy sposób postępowania, jeśli ponownie wczytujesz istniejący kursor i chcesz mieć pewność, że twoja iteracja zaczyna się od początku. –

+9

Zgadzam się, że jest jaśniejszy niż prostsza alternatywa. Generalnie faworyzuję jasność do zwięzłości. Podobna odmiana z pętlą while - http://android.codota.com/scenarios/51891850da0a87eb5be3cc22/android.database.Cursor?tag=out_2013_05_05_07_19_34&fullSource=1 – drorw

40

Chciałbym tylko zwrócić uwagę na trzecią alternatywę, która działa także wtedy, gdy kursor nie znajduje się w pozycji startowej:

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

Istnieje nadmiarowa kontrola. Możesz zastąpić if + do-while, z prostym, jak podano w przyjętym rozwiązaniu, które jest również prostsze/bardziej czytelne. – mtk

+3

@mtk nie, to nie jest nadmiarowe, to jest sedno - jeśli kursor jest ponownie wykorzystywany, może być na pozycji, stąd potrzeba jawnego wywoływania moveToFirst –

+0

Jest to przydatne tylko, jeśli masz instrukcję else z logowaniem; w przeciwnym razie [odpowiedź Grahama Borlanda] (http://stackoverflow.com/a/10723771/94363) jest bardziej zwięzła. – rds

4

The Do/Choć rozwiązanie jest bardziej elegancki, ale jeśli używać tylko rozwiązanie While zamieszczone powyżej, bez moveToPosition (-1) przegapisz pierwszy element (przynajmniej w zapytaniu Contact).

Proponuję:

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

Jak o korzystaniu z pętli foreach:

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

Jednak moja wersja CursorUtils powinno być mniej brzydki, ale to automatycznie zamyka kursor:

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

Jest to całkiem fajne rozwiązanie, ale ukrywa ono fakt, że używasz tej samej instancji 'Cursor' w każdej iteracji pętli. – npace

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

Przykładowy kod:

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

+1 za miłą i kompaktową realizację. Bugfix: 'iterator()' powinien również ponownie przeliczyć 'toVisit = cursor.getCount();' Używam 'class IterableCursor implementuje Iterable , Iterator {...' który otrzymuje klasę, która 'rozszerza narzędzia CursorWrapper MyInterface', gdzie MyInterface definiuje moduły pobierające dla właściwości bazy danych. W ten sposób mam Iterator oparty na kursorze k3b

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

cursor.moveToFirst(); 

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

cursor.close(); 
5

Poniżej mógłby być lepszy sposób:

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

Powyższy kod będzie pewność, że będzie przejść przez cały iteracji i nie ucieknie pierwszy i ostatni iteracji.

0

Początkowo kursor nie znajduje się na pierwszym rzędzie pokazu użyciem moveToNext() można iteracyjne kursor po rekord nie istnieje wtedy return false, chyba że return true,

while (cursor.moveToNext()) { 
    ... 
} 
Powiązane problemy