วิธีที่ดีที่สุดในการทำซ้ำเคอร์เซอร์ 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วิธีเรียกหลายวิธี แน่นอนว่าจะต้องมีวิธี neater?


1
จุดประสงค์ของสิ่งนี้คืออะไร? คุณตอบด้วยตัวคุณเองภายในหนึ่งนาทีหลังจากโพสต์มัน ...
บารั

10
ฉันตอบไปพร้อม ๆกับถาม
Graham Borland

1
อ่าไม่เคยเห็นลิงก์นั้นมาก่อน ดูเหมือนว่าโง่ที่จะถามคำถามที่คุณมีคำตอบแล้ว
Barak

5
@Barak: ฉันคิดว่ามันยอดเยี่ยมที่เขาโพสต์ขึ้น - ตอนนี้ฉันรู้วิธีการทำบางสิ่งบางอย่าง neater เล็กน้อยที่ฉันไม่รู้จัก
George

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) ก่อนวงลูป?
Sam

43
อย่าลืมปิดมัน!
simon

13
แบบสอบถามฐานข้อมูล SQLite จะไม่ส่งคืนค่าว่าง มันจะคืนค่าเคอร์เซอร์ที่ว่างเปล่าหากไม่พบผลลัพธ์ แม้ว่าเคียวรี ContentProvider สามารถส่งคืนค่า null ได้ในบางครั้ง
Graham Borland

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
}

อย่าลืมปิดเคอร์เซอร์ในภายหลัง

แก้ไข:โซลูชันที่ให้มานั้นยอดเยี่ยมหากคุณจำเป็นต้องทำซ้ำเคอร์เซอร์ที่คุณไม่รับผิดชอบ ตัวอย่างที่ดีคือถ้าคุณใช้เคอร์เซอร์เป็นอาร์กิวเมนต์ในวิธีหนึ่งและคุณต้องสแกนเคอร์เซอร์เพื่อหาค่าที่กำหนดโดยไม่ต้องกังวลเกี่ยวกับตำแหน่งปัจจุบันของเคอร์เซอร์


8
ทำไมต้องเรียกสามวิธีที่แตกต่างกันเมื่อคุณสามารถทำได้ด้วยวิธีเดียว? ทำไมคุณคิดว่าดีกว่า
Graham Borland

11
นี่เป็นวิธีที่ปลอดภัยที่สุดหากคุณโหลดเคอร์เซอร์ที่มีอยู่แล้วและต้องการให้แน่ใจว่าการวนซ้ำของคุณเริ่มต้นตั้งแต่ต้น
Michael Eilers Smith

9
ฉันยอมรับว่ามันชัดเจนกว่าทางเลือกที่ง่ายกว่า โดยทั่วไปฉันชอบความชัดเจนในการกระชับ รูปแบบที่คล้ายกันกับ while loop - 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 ด้วย simple ในขณะที่กำหนดในโซลูชันที่ยอมรับซึ่งยังง่ายกว่า / อ่านได้มากขึ้น
mtk

6
@mtk ไม่ไม่ใช่ของซ้ำซ้อนนั่นคือประเด็น - ถ้าเคอร์เซอร์ถูกนำมาใช้ซ้ำมันอาจจะอยู่ในตำแหน่งดังนั้นจำเป็นต้องโทรไปที่ moveToFirst อย่างแน่นอน
Mike Repass

สิ่งนี้มีประโยชน์เฉพาะเมื่อคุณมีคำสั่งอื่นที่มีการบันทึก; มิฉะนั้นคำตอบของเกรแฮมบอร์แลนด์นั้นกระชับกว่า
rds

5
ในฐานะที่เป็นVicky Chijwani ของmoveToPosition(-1)ความคิดเห็นชี้ให้เห็นการใช้งานในโลกแห่งความจริงคำตอบที่เกรแฮมบอร์แลนด์จะไม่ปลอดภัยและต้องใช้ ดังนั้นทั้งสองคำตอบจึงมีความเท่าเทียมกันเนื่องจากทั้งคู่มีการเรียกเคอร์เซอร์สองครั้ง ฉันคิดว่าคำตอบนี้แค่ตอบคำตอบของ Borland เพราะมันไม่จำเป็นต้องใช้-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;
    }
}
}

นี่เป็นวิธีแก้ปัญหาที่ค่อนข้างดี แต่มันซ่อนข้อเท็จจริงที่ว่าคุณกำลังใช้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 สำหรับคุณสมบัติฐานข้อมูล วิธีนี้ฉันมีเคอร์เซอร์ตาม Iterator <MyInterface>
k3b

4

โซลูชัน Do / while นั้นมีความสวยงามมากกว่า แต่ถ้าคุณใช้เพียงขณะที่โซลูชันโพสต์ด้านบนโดยไม่มี moveToPosition (-1) คุณจะพลาดองค์ประกอบแรก (อย่างน้อยในแบบสอบถามผู้ติดต่อ)

ฉันแนะนำ:

if (cursor.getCount() > 0) {
    cursor.moveToPosition(-1);
    while (cursor.moveToNext()) {
          <do stuff>
    }
}


2

เคอร์เซอร์เป็นอินเตอร์เฟซที่แสดงถึง2-dimensionalตารางของฐานข้อมูลใด ๆ

เมื่อคุณพยายามดึงข้อมูลบางอย่างโดยใช้ SELECTคำสั่งแล้วฐานข้อมูลจะสร้างวัตถุเคอร์เซอร์ที่ 1และส่งกลับการอ้างอิงถึงคุณ

ตัวชี้ของการอ้างอิงที่ส่งคืนนี้ชี้ไปยังตำแหน่งที่0ซึ่งถูกเรียกเป็นอย่างอื่นก่อนหน้าตำแหน่งแรกของเคอร์เซอร์ดังนั้นเมื่อคุณต้องการดึงข้อมูลจากเคอร์เซอร์คุณต้องย้ายอันดับที่ 1 ไปยังระเบียนที่ 1 เพื่อที่เราจะต้องใช้ moveToFirst

เมื่อคุณเรียกใช้moveToFirst()เมธอดบน Cursorจะใช้ตัวชี้เคอร์เซอร์ไปยังตำแหน่งที่ 1 ตอนนี้คุณสามารถเข้าถึงข้อมูลที่มีอยู่ในระเบียนที่ 1

วิธีที่ดีที่สุดในการมอง:

เคอร์เซอร์เคอร์เซอร์

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.