Какой лучший способ перебора курсора 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
@ Барак: Я думаю, это здорово, что он поднял пост - теперь я знаю немного более аккуратный способ сделать что-то, чего я не знал бы иначе.
Джордж
4
Мне кажется очевидным, что вы опубликовали это, чтобы помочь любому, кто может прийти посмотреть. Спасибо за это, и спасибо за полезный совет!
muttley91

Ответы:

515

Самый простой способ это:

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

Курсор начинается перед первой строкой результата, поэтому на первой итерации он перемещается к первому результату, если он существует . Если курсор пуст или последняя строка уже обработана, цикл завершается аккуратно.

Конечно, не забудьте закрыть курсор, как только закончите, желательно в finallyпредложении.

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

Если вы нацелены на API 19+, вы можете использовать try-with-resources.

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)перед итерацией, потому что загрузчик повторно использует курсор при изменении ориентации экрана. Потратил час на поиск этой проблемы!
Вики Чиджвани
111

Самый лучший способ прохождения курсора - это следующее:

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

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

Не забудьте потом закрыть курсор

РЕДАКТИРОВАТЬ: Данное решение отлично подходит, если вам когда-либо нужно перебрать курсор, за который вы не несете ответственности. Хорошим примером будет, если вы берете курсор в качестве аргумента в методе, и вам нужно сканировать курсор на предмет заданного значения, не беспокоясь о текущей позиции курсора.

Алекс Стайл
источник
8
Зачем вызывать три разных метода, если вы можете сделать это одним? Почему вы думаете, что это лучше?
Грэм Борланд
11
Это самый безопасный способ, если вы перезагружаете уже существующий курсор и хотите быть уверены, что ваша итерация начинается с самого начала.
Майкл Эйлерс Смит
9
Я согласен, что это понятнее, чем простая альтернатива. Я вообще предпочитаю ясность для краткости. Аналогичная вариация с циклом while - android.codota.com/scenarios/51891850da0a87eb5be3cc22/…
drorw
Поиграв немного больше с курсорами, я обновил свой ответ. Данный код, конечно, не самый эффективный способ перебора курсора, но он имеет свои применения. (см. правку)
Алекс Стил
@AlexStyl Да, это действительно так! Это спасло мое здравомыслие!
Алессандро
45

Я просто хотел бы указать на третий вариант, который также работает, если курсор не находится в начальной позиции:

if (cursor.moveToFirst()) {
    do {
        // do what you need with the cursor here
    } while (cursor.moveToNext());
}
Йорг Айсфельд
источник
1
Есть избыточная проверка. Вы можете заменить if + do-while на простой while, как указано в принятом решении, что также проще / более читабельно.
MTK
6
@mtk нет, это не избыточно, в этом суть - если курсор используется повторно, он может находиться в позиции, отсюда необходимость явного вызова moveToFirst
Mike Repass
Это полезно, только если у вас есть оператор else с ведением журнала; в противном случае ответ Грэма Борланда является более кратким.
Rds
5
Как указывает комментарий Вики Чиджвани , в реальном мире ответ Грэма Борланда небезопасен и требует moveToPosition(-1). Таким образом, оба ответа в равной степени состоят в том, что оба имеют два вызова курсора. Я думаю, что этот ответ только обрезает ответ Борланда, так как не требует -1магического числа.
Бенджамин
Я действительно считаю, что это полезно в том случае, если вы хотите знать, что курсор не имеет значений, поскольку это легко и имеет смысл добавить здесь еще оператор if.
chacham15
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;
    }
}
}
aleksander.w1992
источник
Это довольно крутое решение, но оно скрывает тот факт, что вы используете один и тот же 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();
}
18446744073709551615
источник
+1 за красивую и компактную реализацию. Исправление: iterator()следует также пересчитать toVisit = cursor.getCount();Я использую, class IterableCursor<T extends Cursor> implements Iterable<T>, Iterator<T> {...который получает класс, extends CursorWrapper implements MyInterfaceгде MyInterface определяет геттеры для свойств базы данных. Таким образом, у меня есть итератор на основе курсора <MyInterface>
k3b
4

Решение Do / While более элегантно, но если вы используете только решение While, опубликованное выше, без moveToPosition (-1) вы пропустите первый элемент (по крайней мере, в запросе Contact).

Я предлагаю:

if (cursor.getCount() > 0) {
    cursor.moveToPosition(-1);
    while (cursor.moveToNext()) {
          <do stuff>
    }
}
Lars
источник
2
if (cursor.getCount() == 0)
  return;

cursor.moveToFirst();

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

cursor.close();
Changhoon
источник
2

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

Когда вы пытаетесь получить некоторые данные с помощью SELECTоператора, база данных сначала создаст объект CURSOR и вернет вам его ссылку.

Указатель этой возвращенной ссылки указывает на 0-е местоположение которая в противном случае вызывается так же, как и до первой позиции Курсора, поэтому, когда вы хотите извлечь данные из курсора, вам нужно сначала перейти к первой записи, поэтому мы должны использовать moveToFirst

Когда вы вызываете moveToFirst()метод для курсора, он переводит указатель курсора в 1-е место. Теперь вы можете получить доступ к данным, присутствующим в 1-й записи

Лучший способ выглядеть:

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

for (cursor.moveToFirst(); 
     !cursor.isAfterLast();  
     cursor.moveToNext()) {
                  .........
     }
Rajshah
источник
0

Первоначально курсор находится не в первой строке показа, с помощью которого moveToNext()вы можете выполнить итерацию курсора, когда запись не существует, затем return false, если она не существует return true,

while (cursor.moveToNext()) {
    ...
}
Кундан Камаль
источник