Android İmlecini yinelemenin en iyi yolu nedir?


291

Sık sık bir veritabanı sorgusunun sonucu üzerinde yineleme, her satır ile bir şeyler yapmak ve daha sonra sonraki satıra geçmek içeren kodu görüyorum. Tipik örnekler aşağıdaki gibidir.

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

Bunların hepsi bana çok uzun soluklu görünüyor, her biri birden fazla Cursormetoda çağrı yapıyor . Elbette daha temiz bir yol olmalı mı?


1
Bunun amacı neydi? Bunu bir dakika içinde kendiniz cevapladınız ...
Barak

10
Ben de aynı zamanda bunu sorarak cevapladım .
Graham Borland

1
Ah, bu bağlantıyı daha önce hiç görmedim. Görünüşe göre zaten yanıtladığınız bir soru sormak aptalca görünüyordu.
Barak

5
@Barak: Sanırım gönderiyi yerleştirmesi harika - şimdi bir şey yapmanın biraz daha temiz bir yolunu biliyorum, aksi halde bilemezdim.
George

4
Görünüşe göre bunu, gelebilecek herkese yardım etmek için yayınladınız. Bunun için sahne ve yararlı ipucu için teşekkürler!
muttley91

Yanıtlar:


515

En basit yol şudur:

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

İmleç ilk sonuç satırından önce başlar , bu nedenle ilk yinelemede varsa ilk sonuca geçer . İmleç boşsa veya son satır zaten işlenmişse, döngü düzgün bir şekilde çıkar.

Tabii ki, imleci tamamladıktan sonra, tercihen bir finallymaddede kapatmayı unutmayın .

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

API 19+ 'yı hedeflerseniz, kaynaklarla deneme özelliğini kullanabilirsiniz.

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

19
öyleyse bu yinelemeyi önceden bir imleçle aniden yapmak isterseniz, while döngüsünden önce cursor.moveToPosition (-1) kullanılır?
Sam,

43
kapatmayı unutmayın!
simon

13
SQLite veritabanı sorgusu asla null değerini döndürmez. Hiçbir sonuç bulunmazsa boş bir İmleç döndürür. ContentProvider sorguları bazen null değeri döndürebilir.
Graham Borland

8
Sadece birkaç sent eklemek için ... İmleç üzerinde yineleme yapmadan önce moveToFirst () öğesini çağırarak imlecin veri olup olmadığını kontrol etmeyin - ilk girişi kaybedeceksiniz
AAverin

47
A kullanıyorsanız CursorLoader, cursor.moveToPosition(-1)tekrarlamadan önce aradığınızdan emin olun , çünkü ekran yönü değiştiğinde yükleyici imleci yeniden kullanır. Bu sorunu takip etmek için bir saat geçirdik!
Vicky Chijwani

111

Bir imleçten geçmek için bulduğum en iyi yol şudur:

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

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

İmleci daha sonra kapatmayı unutmayın

EDIT: Sorumlu olmayan bir imleci yinelemek gerekirse, verilen çözüm harika. Bir imleci yöntemde bağımsız değişken olarak alıyorsanız ve imlecin geçerli konumu hakkında endişelenmenize gerek kalmadan imleci belirli bir değer için taramanız gerekiyorsa iyi bir örnek olur.


8
Neden sadece bir yöntemle yapabileceğiniz üç farklı yöntem? Bunun neden daha iyi olduğunu düşünüyorsun?
Graham Borland

11
Önceden var olan bir imleci yeniden yüklüyorsanız ve yinelemenin baştan başladığından emin olmak istiyorsanız, bu en güvenli yoldur.
Michael Eilers Smith

9
Daha basit bir alternatiften daha net olduğunu kabul ediyorum. Genelde kısalığa açıklıktan yanayım. While loop ile benzer bir varyasyon - android.codota.com/scenarios/51891850da0a87eb5be3cc22/…
drorw

İmleçlerle biraz daha oynadıktan sonra cevabımı güncelledim. Verilen kod, bir imleci yinelemenin en etkili yolu değildir, ancak kullanımları vardır. (
Alex Styl

@AlexStyl Evet, gerçekten öyle! Bu benim akıl sağlığımı kurtardı!
Alessandro

45

Sadece imleç başlangıç ​​konumunda değilse de çalışan üçüncü bir alternatifi belirtmek isterim:

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

1
Fazlalık bir kontrol var. İf + do-while öğesini, daha basit / daha okunabilir olan kabul edilen çözümde verildiği gibi basit bir süre ile değiştirebilirsiniz.
mtk

6
@mtk no, gereksiz değil, mesele bu - imleç yeniden kullanılıyorsa, bir konumda olabilir, bu nedenle açıkça moveToFirst'i çağırmak gerekir
Mike Repass

Bu yalnızca günlüğe kaydetme ile başka bir deyiminiz varsa kullanışlıdır; aksi halde Graham Borland'ın cevabı daha açıktır.
rds

5
As Vicky Chijwani en comment işaret, gerçek dünyadaki kullanımlarda Graham Borland cevabı güvensiz ve gerektirir moveToPosition(-1). Yani her iki cevap da eşit derecede özlüdür, çünkü her ikisinin de iki imleç çağrısı vardır. Bence bu cevap -1sihirli sayı gerektirmediği için Borland'ın cevabını dışlıyor .
Benjamin

Aslında imlecin kolay olduğu ve burada if deyimine başka bir şey eklemek için mantıklı olduğunu bilmek istediğiniz durumda yararlı olduğunu düşünüyorum.
chacham15

9

Foreach döngüsünü kullanmaya ne dersiniz?

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

Ancak CursorUtils sürümüm daha az çirkin olmalı, ancak imleci otomatik olarak kapatır:

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

Bu oldukça havalı bir çözümdür, ancak Cursordöngünün her yinelemesinde aynı örneği kullandığınız gerçeğini gizler .
npace

8

Aşağıda daha iyi bir yol olabilir:

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

Yukarıdaki kod, tüm yinelemeden geçmesini ve ilk ve son yinelemeden kaçmamasını sağlayacaktır.


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

Örnek kod:

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

Güzel ve kompakt uygulama için +1. Bugfix: iterator()ayrıca yeniden hesaplamak gerekir MyInterface veritabanı özellikleri için alıcıları tanımladığı bir sınıf alır toVisit = cursor.getCount();kullanın . Bu şekilde imleç tabanlı yineleyici <MyInterface> varclass IterableCursor<T extends Cursor> implements Iterable<T>, Iterator<T> {...extends CursorWrapper implements MyInterface
k3b

4

Do / While çözümü daha zariftir, ancak yalnızca yukarıda yayınlanan while çözümünü kullanırsanız, moveToPosition (-1) olmadan ilk öğeyi (en azından Kişi sorgusunda) kaçırırsınız.

Öneririm:

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

İmleç bir temsil ettiğini Arayüz ise2-dimensional herhangi bir veritabanı tablosu.

İfade kullanarak bazı verileri almaya çalıştığınızda SELECT, veritabanı 1. bir CURSOR nesnesi oluşturur ve başvurusunu size döndürür.

Bu döndürülen başvurunun işaretçisi 0. konuma işaret ediyor , imlecin ilk konumundan önce olarak adlandırılan , bu nedenle imleçten veri almak istediğinizde, 1. kayda gitmelisiniz, böylece kullanmalıyız moveToFirst

İmleçmoveToFirst() üzerindeki yöntemi çağırdığınızda , imleç işaretçisini 1. konuma götürür. Artık 1. kayıtta bulunan verilere erişebilirsiniz

Bakmanın en iyi yolu:

İmleç imleci

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

0

Başlangıçta imleç, moveToNext()kayıt olmadığında imleci yineleyebildiğiniz kullanarak ilk satır gösterisinde değil return false, daha sonra return true,

while (cursor.moveToNext()) {
    ...
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.