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