Який найкращий спосіб відібрати курсор Android?


291

Я часто бачу код, який включає повторення результату запиту бази даних, виконуючи щось із кожного рядка, а потім переходжу до наступного рядка. Типові приклади наступні.

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методів. Напевно повинен бути акуратний спосіб?


1
Яка була мета цього? Ви відповіли на це самостійно протягом хвилини після публікації ...
Барак

10
Я відповів на це одночасно із запитанням.
Грем Борланд

1
А, ніколи раніше не бачив цього посилання. Просто здавалося нерозумним задати питання, на яке ви, мабуть, вже мали відповідь.
Барак

5
@Barak: Я думаю, що це здорово, що він поставив цю посаду - тепер я знаю трохи акуратніший спосіб зробити щось, про що я б не знав інакше.
Джордж

4
Мені здається зрозумілим, що ти написав це, щоб допомогти кожному, хто може прийти шукати. Реквізит вам за це, і дякую за корисну пораду!
muttley91

Відповіді:


515

Найпростіший спосіб такий:

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

Курсор запускається перед першим рядом результатів, тому при першій ітерації він переходить до першого результату, якщо він існує . Якщо курсор порожній або останній рядок уже оброблений, цикл акуратно виходить.

Звичайно, не забудьте закрити курсор, коли ви закінчите з ним, бажано в finallyпункті.

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

Якщо ви орієнтуєтесь на API 19+, ви можете скористатися спробними ресурсами.

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

19
тож якщо ви хотіли заздалегідь виконати цю ітерацію з курсором у атрибутивному положенні, ви б використали cursor.moveToPosition (-1) перед циклом while?
Сем

43
не забудьте закрити його!
симон

13
Запит на базу даних SQLite ніколи не поверне нуль. Якщо результат не буде знайдено, він поверне порожній курсор. Однак запити ContentProvider іноді можуть повернути нуль.
Грем Борланд

8
Просто додайте кілька центів ... Не перевіряйте, чи є курсор дані, зателефонувавши moveToFirst (), перш ніж ви перейдете до перегляду курсору - ви втратите перший запис
AAverin

47
Якщо ви користуєтесь a CursorLoader, переконайтеся, що дзвоните cursor.moveToPosition(-1)перед ітерацією, оскільки завантажувач повторно використовує курсор, коли зміна орієнтації екрана. Витратили годину на відстеження цієї проблеми!
Vicky Chijwani

111

Найкращий спосіб, який я знайшов, щоб пройти курсор:

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

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

Не забудьте згодом закрити курсор

EDIT: Дане рішення є чудовим, якщо вам коли-небудь потрібно буде повторити курсор, за який ви не відповідаєте. Хорошим прикладом може бути, якщо ви приймаєте курсор як аргумент методом, і вам потрібно сканувати курсор на задане значення, не турбуючись про поточне положення курсора.


8
Навіщо називати три різні методи, коли ви можете це зробити лише одним? Чому ти вважаєш, що це краще?
Грем Борланд

11
Це найбезпечніший шлях, якщо ви перезавантажуєте наявний курсор і хочете бути впевнені, що ітерація починається з початку.
Майкл Ейлерс Сміт

9
Я згоден, це зрозуміліше, ніж простіша альтернатива. Я, як правило, віддаю чіткість стислості. Аналогічний варіант з циклом while - android.codota.com/scenarios/51891850da0a87eb5be3cc22/…
drorw

Погравши трохи більше з курсорами, я оновив свою відповідь. Даний код впевнений, що це не найефективніший спосіб ітерації курсору, але він має свої звички. (див. редагування)
Alex Styl

@AlexStyl Так, це дійсно так! Це врятувало мою здоровість!
Алессандро

45

Я просто хотів би зазначити третю альтернативу, яка також працює, якщо курсор не знаходиться в початковому положенні:

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

1
Існує зайва перевірка. Ви можете замінити if + do-while на простий час, як зазначено в прийнятому рішенні, яке також простіше / читабельніше.
mtk

6
@mtk ні, це не зайве, в цьому і справа - якщо курсор буде повторно використаний, він може бути в положенні, отже, потрібно чітко викликати moveToFirst
Mike Repass

Це корисно лише в тому випадку, якщо у вас є інший оператор із реєстрацією даних; інакше відповідь Грем Борленда є більш стислою.
rds

5
Як зазначає коментар Вікі Чівані , в реальному використанні відповідь Грем Борланд небезпечна і вимагає moveToPosition(-1). Тому обидві відповіді однаковою мірою, оскільки в обох є два дзвінки курсору. Я думаю, що ця відповідь просто перекреслює відповідь Борленда, оскільки не вимагає -1магічного числа.
Бенджамін

Насправді я вважаю його корисним у тому випадку, якщо ви хочете знати, що курсор не мав значень, оскільки це легко і має сенс додати інше до оператора if тут.
чача15

9

Як щодо використання циклу 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екземпляр у кожній ітерації циклу.
npace

8

Нижче може бути кращий спосіб:

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

Вищевказаний код гарантував би, що він пройде через всю ітерацію, і не уникне першої та останньої ітерації.


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<T extends Cursor> implements Iterable<T>, Iterator<T> {...що отримує клас, у extends CursorWrapper implements MyInterfaceякому MyInterface визначає getters для властивостей бази даних. Таким чином у мене є курсор на основі ітератора <MyInterface>
k3b

4

Рішення "Do / while" є більш елегантним, але якщо ви використовуєте лише розроблене рішення "while", без переміщенняToPosition (-1) ви пропустите перший елемент (принаймні, за запитом "Контакт").

Я пропоную:

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

Курсор є інтерфейсом , який представляє собою 2-dimensionalтаблицю в будь-якій базі даних.

Коли ви намагаєтеся отримати деякі дані за допомогою SELECTоператора, то база даних спочатку створить об'єкт CURSOR і поверне вам його посилання.

Вказівник цього повернутого посилання вказує на 0-е місце, яке інакше називається як до першого місця розташування Курсору, тому, коли ви хочете отримати дані з курсору, вам потрібно 1-й перейти до 1-го запису, тому ми маємо використовувати moveToFirst

Коли ви викликаєте moveToFirst()метод на курсорі, він переводить покажчик курсору на 1-е місце. Тепер ви можете отримати доступ до даних, присутніх у 1-му записі

Найкращий спосіб виглядати:

Курсор курсору

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

0

Спочатку курсор не відображається на показі першого ряду, використовуючи його, moveToNext()ви можете ітератувати курсор, коли запису немає, тоді він return false, якщо він не є return true,

while (cursor.moveToNext()) {
    ...
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.