Was ist der beste Weg, um einen Android-Cursor zu iterieren?

291

Ich sehe häufig Code, bei dem das Ergebnis einer Datenbankabfrage durchlaufen wird, mit jeder Zeile etwas getan wird und dann mit der nächsten Zeile fortgefahren wird. Typische Beispiele sind wie folgt.

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

Diese scheinen mir alle übermäßig langwierig zu sein, jeder mit mehreren CursorMethodenaufrufen. Sicherlich muss es einen saubereren Weg geben?

Graham Borland
quelle
1
Was war der Zweck davon? Sie haben es selbst innerhalb einer Minute nach dem Posten beantwortet ...
Barak
10
Ich antwortete es gleichzeitig mit der Frage.
Graham Borland
1
Ah, ich habe diesen Link noch nie gesehen. Es schien nur albern, eine Frage zu stellen, auf die Sie anscheinend bereits eine Antwort hatten.
Barak
5
@Barak: Ich finde es großartig, dass er den Beitrag veröffentlicht hat - jetzt kenne ich eine etwas ordentlichere Art, etwas zu tun, die ich sonst nicht gewusst hätte.
George
4
Mir scheint klar zu sein, dass Sie dies gepostet haben, um allen zu helfen, die auf der Suche sein könnten. Wir danken Ihnen dafür und bedanken uns für den hilfreichen Tipp!
muttley91

Antworten:

515

Der einfachste Weg ist folgender:

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

Der Cursor startet vor der ersten Ergebniszeile. Bei der ersten Iteration wird zum ersten Ergebnis gewechselt, falls vorhanden . Wenn der Cursor leer ist oder die letzte Zeile bereits verarbeitet wurde, wird die Schleife ordnungsgemäß beendet.

Vergessen Sie natürlich nicht, den Cursor zu schließen, wenn Sie damit fertig sind, vorzugsweise in einer finallyKlausel.

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

Wenn Sie auf API 19+ abzielen, können Sie Try-with-Resources verwenden.

try (Cursor cursor = db.rawQuery(...)) {
    while (cursor.moveToNext()) {
        ...
    }
}
Graham Borland
quelle
19
Wenn Sie diese Iteration also vorher mit einem Cursor an einer beliebigen Position ausführen möchten, würden Sie cursor.moveToPosition (-1) vor der while-Schleife verwenden?
Sam
43
Vergiss nicht, es zu schließen!
Simon
13
Eine SQLite-Datenbankabfrage gibt niemals null zurück. Es wird ein leerer Cursor zurückgegeben, wenn keine Ergebnisse gefunden werden. ContentProvider-Abfragen können jedoch manchmal null zurückgeben.
Graham Borland
8
Nur um ein paar Cent hinzuzufügen ... Überprüfen Sie nicht, ob der Cursor Daten enthält, indem Sie moveToFirst () aufrufen, bevor Sie über den Cursor iterieren - Sie verlieren den ersten Eintrag
AAverin
47
Wenn Sie a verwenden CursorLoader, stellen Sie sicher, dass Sie cursor.moveToPosition(-1)vor dem Iterieren aufrufen , da der Loader den Cursor wiederverwendet, wenn sich die Bildschirmausrichtung ändert. Wir haben eine Stunde damit verbracht, dieses Problem aufzuspüren!
Vicky Chijwani
111

Der beste Weg, den ich gefunden habe, um durch einen Cursor zu gehen, ist der folgende:

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

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

Vergessen Sie nicht, den Cursor danach zu schließen

BEARBEITEN: Die angegebene Lösung ist großartig, wenn Sie jemals einen Cursor iterieren müssen, für den Sie nicht verantwortlich sind. Ein gutes Beispiel wäre, wenn Sie einen Cursor als Argument in einer Methode verwenden und den Cursor nach einem bestimmten Wert durchsuchen müssen, ohne sich um die aktuelle Position des Cursors kümmern zu müssen.

Alex Styl
quelle
8
Warum drei verschiedene Methoden aufrufen, wenn Sie dies mit nur einer tun können? Warum denkst du, ist das besser?
Graham Borland
11
Dies ist der sicherste Weg, wenn Sie einen bereits vorhandenen Cursor neu laden und sicherstellen möchten, dass Ihre Iteration von vorne beginnt.
Michael Eilers Smith
9
Ich bin damit einverstanden, dass es klarer ist als die einfachere Alternative. Generell bevorzuge ich Klarheit gegenüber Kürze. Eine ähnliche Variation mit while-Schleife - android.codota.com/scenarios/51891850da0a87eb5be3cc22/…
drorw
Nachdem ich ein bisschen mehr mit Cursorn gespielt habe, habe ich meine Antwort aktualisiert. Der angegebene Code ist sicher nicht die effizienteste Methode, um einen Cursor zu iterieren, aber er hat seine Verwendung. (siehe die Bearbeitung)
Alex Styl
@AlexStyl Ja, das tut es wirklich! Das hat meine geistige Gesundheit gerettet!
Alessandro
45

Ich möchte nur auf eine dritte Alternative hinweisen, die auch funktioniert, wenn sich der Cursor nicht an der Startposition befindet:

if (cursor.moveToFirst()) {
    do {
        // do what you need with the cursor here
    } while (cursor.moveToNext());
}
Jörg Eisfeld
quelle
1
Es gibt eine redundante Prüfung. Sie können das if + do-while durch ein einfaches while ersetzen, wie in der akzeptierten Lösung angegeben, die auch einfacher / besser lesbar ist.
MTK
6
@mtk nein, es ist nicht redundant, das ist der Punkt - wenn der Cursor wiederverwendet wird, könnte er sich an einer Position befinden, daher muss explizit moveToFirst aufgerufen werden
Mike Repass
Dies ist nur nützlich, wenn Sie eine else-Anweisung mit Protokollierung haben. ansonsten ist die Antwort von Graham Borland prägnanter.
rds
5
Wie Vicky Chijwanis Kommentar hervorhebt, ist die Antwort von Graham Borland im realen Gebrauch unsicher und erfordert moveToPosition(-1). Beide Antworten sind also gleichermaßen konsistent, da beide zwei Cursoraufrufe haben. Ich denke, diese Antwort übertrifft Borlands Antwort nur, da sie nicht die -1magische Zahl erfordert .
Benjamin
Ich denke tatsächlich, dass es nützlich ist, wenn Sie wissen möchten, dass der Cursor keine Werte hatte, da es einfach und sinnvoll ist, der if-Anweisung hier ein anderes hinzuzufügen.
Chacham15
9

Wie wäre es mit foreach loop:

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

Meine Version von CursorUtils sollte jedoch weniger hässlich sein, schließt jedoch automatisch den Cursor:

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
quelle
Dies ist eine ziemlich coole Lösung, verbirgt jedoch die Tatsache, dass Sie Cursorin jeder Iteration der Schleife dieselbe Instanz verwenden.
Npace
8

Unten könnte der bessere Weg sein:

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

Der obige Code würde sicherstellen, dass er die gesamte Iteration durchläuft und nicht der ersten und letzten Iteration entgeht.

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

Beispielcode:

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
quelle
+1 für eine schöne und kompakte Implementierung. Bugfix: iterator()sollte auch neu berechnen toVisit = cursor.getCount();Ich verwende, class IterableCursor<T extends Cursor> implements Iterable<T>, Iterator<T> {...dass eine Klasse empfängt, extends CursorWrapper implements MyInterfacein der MyInterface Getter für Datenbankeigenschaften definiert. Auf diese Weise habe ich einen Cursor-basierten Iterator <MyInterface>
k3b
4

Die Do / While-Lösung ist eleganter, aber wenn Sie nur die oben angegebene While-Lösung verwenden, wird ohne die moveToPosition (-1) das erste Element übersehen (zumindest in der Kontaktabfrage).

Ich schlage vor:

if (cursor.getCount() > 0) {
    cursor.moveToPosition(-1);
    while (cursor.moveToNext()) {
          <do stuff>
    }
}
Lars
quelle
2
if (cursor.getCount() == 0)
  return;

cursor.moveToFirst();

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

cursor.close();
Changhoon
quelle
2

Der Cursor ist die Schnittstelle, die eine 2-dimensionalTabelle einer beliebigen Datenbank darstellt.

Wenn Sie versuchen, einige Daten mithilfe einer SELECTAnweisung abzurufen , erstellt die Datenbank zunächst ein CURSOR-Objekt und gibt dessen Referenz an Sie zurück.

Der Zeiger dieser zurückgegebenen Referenz zeigt auf die 0. Stelle die ansonsten wie vor der ersten Position des Cursors aufgerufen wird. Wenn Sie also Daten vom Cursor abrufen möchten, müssen Sie zuerst zum 1. Datensatz wechseln, damit wir sie verwenden können moveToFirst

Wenn Sie eine moveToFirst()Methode für den Cursor aufrufen , wird der Cursorzeiger an die erste Stelle geführt. Jetzt können Sie auf die im 1. Datensatz vorhandenen Daten zugreifen

Der beste Weg, um zu schauen:

Cursor Cursor

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

Am Anfang Cursor ist nicht auf der ersten Zeile zeigt mit moveToNext()den Cursor durchlaufen kann , wenn Datensatz nicht existiert dann return false, wenn sie nicht return true,

while (cursor.moveToNext()) {
    ...
}
Kundan Kamal
quelle