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
이것의 목적은 무엇입니까? 당신은 그것을 게시한지 1 분 안에 스스로 대답했습니다 ...
Barak

10
나는 그것을 묻는 것과 동시에 대답 했다.
Graham Borland 16:23에

1
아, 전에는 그 링크를 본 적이 없습니다. 이미 당신이 이미 대답 한 질문을하는 것은 어리석은 것처럼 보였다.
Barak

5
@Barak : 나는 그가 게시물을 올리는 것이 훌륭하다고 생각합니다. 이제는 다른 방법으로는 알지 못했던 약간 깔끔한 방법을 알고 있습니다.
George

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
따라서 사전에 커서를 임의의 위치에 놓고이 반복을 수행하려면 while 루프 전에 cursor.moveToPosition (-1)을 사용합니까?
Sam

43
닫는 것을 잊지 마십시오!
사이먼

13
SQLite 데이터베이스 쿼리는 null을 반환하지 않습니다. 결과가 없으면 빈 커서를 반환합니다. 그러나 ContentProvider 쿼리는 때때로 null을 반환 할 수 있습니다.
Graham Borland

8
그냥 몇 센트를 추가하려면 ... 커서를 반복하기 전에 moveToFirst ()를 호출하여 커서에 데이터가 있는지 확인하지 마십시오-첫 번째 항목이 손실됩니다
AAverin

47
를 사용하는 경우 화면 방향이 변경 될 때 로더가 커서를 재사용하므로 반복하기 전에 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
}

나중에 커서를 닫는 것을 잊지 마십시오

편집 : 당신이 책임지지 않은 커서를 반복 해야하는 경우 주어진 솔루션이 좋습니다. 메소드에서 커서를 인수로 사용하고 커서의 현재 위치에 대해 걱정할 필요없이 주어진 값에 대해 커서를 스캔해야하는 경우를 예로들 수 있습니다.


8
하나의 방법으로 세 가지 방법을 호출해야하는 이유는 무엇입니까? 왜 그것이 더 낫다고 생각합니까?
Graham Borland

11
기존 커서를 다시로드하고 반복이 처음부터 시작되도록하려면 가장 안전한 방법입니다.
Michael Eilers Smith 4

9
나는 그것이 간단한 대안보다 명확하다는 데 동의합니다. 나는 일반적으로 간결함에 대한 명확성을 선호합니다. While 루프와 유사한 변화 - android.codota.com/scenarios/51891850da0a87eb5be3cc22/...
drorw

커서로 조금 더 연주 한 결과 내 답변이 업데이트되었습니다. 주어진 코드는 커서를 반복하는 가장 효율적인 방법은 아니지만 사용법이 있습니다. (편집 참조)
Alex Styl

@AlexStyl 네, 정말 그렇습니다! 이것은 나의 정신을 구했다!
Alessandro

45

커서가 시작 위치에 있지 않은 경우에도 작동하는 세 번째 대안을 지적하고 싶습니다.

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

1
중복 검사가 있습니다. if + do-while을 허용 된 솔루션에 주어진 것처럼 단순하면서 간단하게 대체 할 수 있으며 더 간단하고 읽기 쉽습니다.
mtk

6
어떤 @mtk, 그것은 중복 아니에요, 점이야 그 - 커서가 재사용되는 경우, 그것은 따라서 명시 적으로 할 필요가 moveToFirst 전화 위치에있을 수 있습니다
마이크 Repass

로깅이있는 else 문이있는 경우에만 유용합니다. 그렇지 않으면 Graham Borland의 답변 이 더 간결합니다.
rds

5
Vicky Chijwani의 의견에서 알 수 있듯이 실제 사용에서 Graham Borland의 답변은 안전하지 않으며 필요합니다 moveToPosition(-1). 따라서 두 개의 커서 호출이 있기 때문에 두 답변 모두 똑같이 간결합니다. 나는이 답변이 -1마법 번호를 필요로하지 않기 때문에 볼랜드의 답변보다 우위에 있다고 생각합니다 .
Benjamin

실제로 커서에 값이 없다는 것을 알고 싶을 때 유용하며 if 문에 else를 추가하는 것이 좋습니다.
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;
    }
}
}

이것은 매우 멋진 솔루션이지만 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 버그 픽스 : MyInterface가 데이터베이스 속성에 대한 게터를 정의 하는 클래스를받는 클래스를 사용하여 iterator()다시 계산해야 합니다. 내가 반복자 <하는 MyInterface>를 기반으로 커서가이 방법toVisit = cursor.getCount();class IterableCursor<T extends Cursor> implements Iterable<T>, Iterator<T> {...extends CursorWrapper implements MyInterface
K3B

4

Do / While 솔루션은보다 우아하지만, 위에 게시 된 While 솔루션 만 사용하면 moveToPosition (-1)없이 첫 번째 요소가 누락됩니다 (적어도 Contact 쿼리에서).

나는 제안한다 :

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 번째 위치를 가리키고 있습니다. 커서의 첫 번째 위치 이전과 같이 호출되는 를 므로 커서에서 데이터를 검색하려면 첫 번째 레코드로 이동해야하므로 첫 번째 레코드로 이동해야합니다. moveToFirst

커서에서moveToFirst() 메소드 를 호출 하면 커서 포인터가 첫 번째 위치로 이동합니다. 이제 첫 번째 레코드에있는 데이터에 액세스 할 수 있습니다

보는 가장 좋은 방법 :

커서 커서

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

0

처음에 커서를 사용하여 첫 번째 행 쇼에없는 moveToNext()기록이 다음 존재하지 않을 때 커서를 반복 할 수 return false, 그것은하지 않는 한 return true,

while (cursor.moveToNext()) {
    ...
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.