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?

Graham Borland
źródło
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()) {
        ...
    }
}
Graham Borland
źródło
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.

Alex Styl
źródło
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());
}
Jörg Eisfeld
źródło
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;
    }
}
}
aleksander.w1992
źródło
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.

Pankaj
źródło
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();
}
18446744073709551615
źródło
+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>
    }
}
Lars
źródło
2
if (cursor.getCount() == 0)
  return;

cursor.moveToFirst();

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

cursor.close();
Changhoon
źródło
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()) {
                  .........
     }
Rajshah
źródło
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()) {
    ...
}
kundan kamal
źródło