Jaki jest najlepszy sposób na iterację Kursora Android?


291

Często widzę kod, który polega na iteracji wyniku zapytania do bazy danych, zrobieniu czegoś z każdym wierszem, a następnie przejściu do następnego wiersza. Typowe przykłady są następujące.

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

Wszystko to wydaje mi się zbyt długie, każde z wieloma wezwaniami do Cursormetod. Z pewnością musi być lepszy sposób?


1
Jaki był tego cel? Odpowiedziałeś na to sam w ciągu minuty od opublikowania ...
— Barak

10
Odpowiedziałem na to w tym samym czasie, kiedy o to poprosiłem.
— Graham Borland

1
Ach, nigdy wcześniej nie widziałem tego linku. Głupio wydawało się pytanie, na które najwyraźniej już miałeś odpowiedź.
— Barak

5
@Barak: Myślę, że to wspaniale, że podniósł post - teraz znam nieco bardziej zgrabny sposób robienia czegoś, czego inaczej nie poznałbym.
— George

4
Wydaje mi się jasne, że opublikowałeś to, aby pomóc każdemu, kto może przyjrzeć. Dziękuję ci za to i dziękuję za pomocną wskazówkę!
— muttley91

Odpowiedzi:


515

Najprostszy sposób to:

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

Kursor zaczyna się przed pierwszym wierszem wyników, więc przy pierwszej iteracji przechodzi on do pierwszego wyniku, jeśli istnieje . Jeśli kursor jest pusty lub ostatni wiersz został już przetworzony, pętla kończy się poprawnie.

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

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

Jeśli celujesz w API 19+, możesz użyć try-with-resources.

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

19
więc jeśli chcesz wcześniej wykonać tę iterację z kursorem w pozycji abritrowej, użyjesz kursora.moveToPosition (-1) przed pętlą while?
— Sam

43
nie zapomnij go zamknąć!
— simon

13
Zapytanie do bazy danych SQLite nigdy nie zwróci wartości null. Zwróci pusty Kursor, jeśli nie zostaną znalezione żadne wyniki. Zapytania ContentProvider mogą czasami zwracać wartość null.
— Graham Borland,

8
Wystarczy dodać kilka centów ... Nie sprawdzaj, czy kursor ma dane, wywołując moveToFirst (), zanim przejdziesz do iteracji nad kursorem - stracisz pierwszy wpis
— AAverin

47
Jeśli używasz a CursorLoader, upewnij się, że dzwonisz cursor.moveToPosition(-1)przed iteracją, ponieważ moduł ładujący ponownie używa kursora, gdy zmienia się orientacja ekranu. Spędziłem godzinę na śledzeniu tego problemu!
— Vicky Chijwani

111

Najlepiej wyglądający sposób przejścia kursora to:

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

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

Nie zapomnij później zamknąć kursora

EDYCJA: Podane rozwiązanie jest świetne, jeśli potrzebujesz powtórzyć kursor, za który nie jesteś odpowiedzialny. Dobrym przykładem jest, jeśli bierzesz kursor jako argument w metodzie i musisz przeskanować kursor pod kątem określonej wartości, nie martwiąc się o bieżącą pozycję kursora.


8
Po co wywoływać trzy różne metody, skoro można to zrobić za pomocą tylko jednej? Jak myślisz, dlaczego tak jest lepiej?
— Graham Borland

11
Jest to najbezpieczniejszy sposób, jeśli przeładowujesz wcześniej istniejący kursor i chcesz mieć pewność, że iteracja rozpocznie się od początku.
— Michael Eilers Smith

9
Zgadzam się, że jest to bardziej przejrzyste niż prostsza alternatywa. Zasadniczo opowiadam się za przejrzystością za zwięzłość. Podobna odmiana z pętlą while - android.codota.com/scenarios/51891850da0a87eb5be3cc22/…
— drorw

Po dłuższym graniu kursorami zaktualizowałem swoją odpowiedź. Podany kod z pewnością nie jest najskuteczniejszym sposobem iteracji kursora, ale ma swoje zastosowania. (patrz edycja)
— Alex Styl

@AlexStyl Tak, naprawdę! To uratowało mi zdrowie psychiczne!
— Alessandro,

45

Chciałbym tylko wskazać trzecią alternatywę, która działa również wtedy, gdy kursor nie znajduje się w pozycji początkowej:

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

1
Jest nadmiarowy czek. Możesz zamienić if + do-while na prosty while, jak podano w zaakceptowanym rozwiązaniu, który jest również prostszy / bardziej czytelny.
— Mt

6
@mtk nie, to nie jest zbędne, o to chodzi - jeśli kursor jest ponownie używany, może znajdować się w pozycji, stąd potrzeba jawnego wywołania moveToFirst
— Mike Repass

Jest to przydatne tylko wtedy, gdy masz instrukcję else z logowaniem; w przeciwnym razie odpowiedź Grahama Borlanda jest bardziej zwięzła.
— RDS

5
Jak zauważa komentarz Vicky Chijwani , w rzeczywistym świecie odpowiedź Grahama Borlanda jest niebezpieczna i wymaga moveToPosition(-1). Obie odpowiedzi są jednakowo spójne, ponieważ oba mają dwa wywołania kursora. Myślę, że ta odpowiedź jest na wyciągnięcie ręki od odpowiedzi Borlanda, ponieważ nie wymaga -1magicznej liczby.
— Benjamin

Właściwie uważam, że jest to przydatne w przypadku, gdy chcesz wiedzieć, że kursor nie ma wartości, ponieważ jest to łatwe i sensowne jest dodanie tutaj innej instrukcji if.
— chacham15

9

Co powiesz na użycie pętli foreach:

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

Jednak moja wersja CursorUtils powinna być mniej brzydka, ale 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;
    }
}
}

To całkiem fajne rozwiązanie, ale ukrywa fakt, że używasz tego samego Cursorwystąpienia w każdej iteracji pętli.
— npace

8

Poniżej może być lepszy sposób:

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

Powyższy kod zapewniłby, że przejdzie całą iterację i nie uniknie pierwszej i ostatniej iteracji.


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

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

+1 za ładne i kompaktowe wdrożenie. Bugfix: iterator()powinien również ponownie przeliczyć, toVisit = cursor.getCount();którego używam, class IterableCursor<T extends Cursor> implements Iterable<T>, Iterator<T> {...który otrzymuje klasę, w extends CursorWrapper implements MyInterfacektórej MyInterface definiuje metody pobierające właściwości bazy danych. W ten sposób mam Iterator oparty na kursorze <MyInterface>
— k3b

4

Rozwiązanie Do / While jest bardziej eleganckie, ale jeśli użyjesz tylko rozwiązania While opublikowanego powyżej, bez moveToPosition (-1) przegapisz pierwszy element (przynajmniej w zapytaniu Kontakt).

Sugeruję:

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

The Kursor jest interfejs, który oznacza2-dimensional tabelę dowolnej bazy danych.

Podczas próby pobrania niektórych danych za pomocą SELECT instrukcji baza danych najpierw utworzy obiekt CURSOR i zwróci ci swoje odwołanie.

Wskaźnik tego zwróconego odwołania wskazuje na 0 lokalizację która jest inaczej nazywana tak jak przed pierwszą lokalizacją Kursora, więc jeśli chcesz odzyskać dane z kursora, musisz 1. przejść do 1. rekordu, więc musimy użyć moveToFirst

Po wywołaniu moveToFirst()metody w Kursorze, kursor przesuwa się do 1. lokalizacji. Teraz możesz uzyskać dostęp do danych obecnych w 1. rekordzie

Najlepszy sposób na wygląd:

Kursor kursora

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

0

Początkowo kursor nie jest pokazywany w pierwszym wierszu za pomocą moveToNext(), możesz iterować kursor, gdy rekord nie istnieje return false, chyba że return true,

while (cursor.moveToNext()) {
    ...
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.