Cách tốt nhất để lặp lại một con trỏ Android là gì?


291

Tôi thường thấy mã liên quan đến việc lặp lại kết quả của một truy vấn cơ sở dữ liệu, làm một cái gì đó với mỗi hàng và sau đó chuyển sang hàng tiếp theo. Ví dụ điển hình như sau.

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

Tất cả những điều này dường như quá dài đối với tôi, mỗi người có nhiều cuộc gọi đến Cursorcác phương thức. Chắc chắn phải có một cách gọn gàng hơn?


1
Mục đích của việc này là gì? Bạn đã tự trả lời nó trong vòng một phút sau khi đăng nó ...
Barak

10
Tôi trả lời nó cùng lúc với nó.
Graham Borland

1
Ah, chưa bao giờ thấy liên kết đó trước đây. Có vẻ như thật ngớ ngẩn khi hỏi một câu hỏi mà bạn dường như đã có câu trả lời.
Barak

5
@Barak: Tôi nghĩ thật tuyệt khi anh ấy đăng bài lên - bây giờ tôi biết một cách gọn gàng hơn để làm một cái gì đó, mà tôi sẽ không biết khác.
George

4
Tôi thấy rõ ràng rằng bạn đã đăng bài này để giúp đỡ bất cứ ai có thể tìm đến. Đạo cụ cho bạn vì điều này, và cảm ơn vì lời khuyên hữu ích!
muttley91

Câu trả lời:


515

Cách đơn giản nhất là đây:

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

Con trỏ bắt đầu trước hàng kết quả đầu tiên, do đó, ở lần lặp đầu tiên, nó sẽ di chuyển đến kết quả đầu tiên nếu nó tồn tại . Nếu con trỏ trống hoặc hàng cuối cùng đã được xử lý, thì vòng lặp thoát ra gọn gàng.

Tất nhiên, đừng quên đóng con trỏ sau khi bạn hoàn thành nó, tốt nhất là trong một finallymệnh đề.

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

Nếu bạn nhắm mục tiêu API 19+, bạn có thể sử dụng tài nguyên thử.

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

19
Vì vậy, nếu bạn muốn thực hiện việc lặp lại này với một con trỏ ở vị trí khó hiểu trước đó, bạn sẽ sử dụng con trỏ.moveToP vị trí (-1) trước vòng lặp while?
Sam

43
đừng quên đóng nó
simon

13
Một truy vấn cơ sở dữ liệu SQLite sẽ không bao giờ trả về null. Nó sẽ trả về một con trỏ trống nếu không tìm thấy kết quả. Các truy vấn ContentProvider đôi khi có thể trả về null.
Graham Borland

8
Chỉ cần thêm một vài xu ... Đừng kiểm tra xem con trỏ có dữ liệu hay không bằng cách gọi moveToFirst () trước khi bạn định lặp lại con trỏ - bạn sẽ mất mục nhập đầu tiên
AAverin

47
Nếu sử dụng a CursorLoader, hãy đảm bảo bạn gọi cursor.moveToPosition(-1)trước khi lặp, bởi vì trình tải lại sử dụng lại con trỏ khi hướng màn hình thay đổi. Đã dành một giờ theo dõi vấn đề này!
Vicky Chijwani

111

Cách nhìn tốt nhất mà tôi tìm thấy để đi qua một con trỏ là như sau:

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

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

Đừng quên đóng con trỏ sau đó

EDIT: Giải pháp đã cho là tuyệt vời nếu bạn cần lặp lại một con trỏ mà bạn không chịu trách nhiệm. Một ví dụ điển hình là, nếu bạn đang lấy một con trỏ làm đối số trong một phương thức và bạn cần quét con trỏ cho một giá trị nhất định, mà không phải lo lắng về vị trí hiện tại của con trỏ.


8
Tại sao gọi ba phương pháp khác nhau, khi bạn có thể làm điều đó chỉ với một phương pháp? Tại sao bạn nghĩ rằng tốt hơn?
Graham Borland

11
Đây là cách an toàn nhất nếu bạn tải lại một con trỏ có sẵn và muốn chắc chắn rằng việc lặp lại của bạn bắt đầu lại từ đầu.
Michael Eilers Smith

9
Tôi đồng ý rằng nó rõ ràng hơn so với sự thay thế đơn giản hơn. Tôi thường ủng hộ sự rõ ràng để ngắn gọn. Một biến thể tương tự với vòng lặp while - android.codota.com/scenario/51891850da0a87eb5be3cc22/
Kẻ

Đã chơi nhiều hơn một chút với các con trỏ, tôi đã cập nhật câu trả lời của mình. Mã đã cho chắc chắn không phải là cách hiệu quả nhất để lặp lại một con trỏ, nhưng nó có công dụng của nó. (xem phần chỉnh sửa)
Alex Styl

@AlexStyl Vâng, nó thực sự có! Điều này đã cứu sự tỉnh táo của tôi!
Alessandro

45

Tôi chỉ muốn chỉ ra một giải pháp thay thế thứ ba cũng hoạt động nếu con trỏ không ở vị trí bắt đầu:

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

1
Có một kiểm tra dư thừa. Bạn có thể thay thế if + do-while, bằng cách đơn giản như được đưa ra trong giải pháp được chấp nhận, cũng đơn giản hơn / dễ đọc hơn.
mtk

6
@mtk không, nó không dư thừa, đó là điểm - nếu con trỏ đang được sử dụng lại, nó có thể ở một vị trí, do đó cần phải gọi rõ ràng moveToFirst
Mike Repass

Điều này chỉ hữu ích nếu bạn có một tuyên bố khác với việc đăng nhập; mặt khác , câu trả lời của Graham Borland ngắn gọn hơn.
rds

5
Như nhận xét của Vicky Chijwani chỉ ra, trong thế giới thực, câu trả lời của Graham Borland là không an toàn và đòi hỏi moveToPosition(-1). Vì vậy, cả hai câu trả lời đều giống nhau vì cả hai đều có hai cuộc gọi con trỏ. Tôi nghĩ rằng câu trả lời này chỉ vượt ra ngoài câu trả lời của Borland vì nó không yêu cầu -1số ma thuật.
Benjamin

Tôi thực sự nghĩ rằng nó hữu ích trong trường hợp bạn muốn biết rằng con trỏ không có giá trị vì nó dễ dàng và có ý nghĩa để thêm một cái khác vào câu lệnh if ở đây.
chacham15

9

Làm thế nào về việc sử dụng vòng lặp foreach:

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

Tuy nhiên, phiên bản CthonUtils của tôi sẽ ít xấu hơn, nhưng nó sẽ tự động đóng con trỏ:

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

Đây là một giải pháp khá tuyệt vời, nhưng nó che giấu thực tế rằng bạn đang sử dụng cùng một Cursorthể hiện trong mỗi lần lặp của vòng lặp.
gian

8

Dưới đây có thể là cách tốt hơn:

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

Đoạn mã trên sẽ đảm bảo rằng nó sẽ trải qua toàn bộ lần lặp và sẽ không thoát khỏi lần lặp đầu tiên và lần lặp cuối cùng.


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

Mã ví dụ:

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 để thực hiện tốt và nhỏ gọn. Bugfix: iterator()cũng nên tính toán lại toVisit = cursor.getCount();tôi sử dụng class IterableCursor<T extends Cursor> implements Iterable<T>, Iterator<T> {...để nhận một lớp extends CursorWrapper implements MyInterfacetrong đó MyInterface định nghĩa getters cho các thuộc tính cơ sở dữ liệu. Bằng cách này, tôi có một Iterator dựa trên con trỏ <MyInterface>
k3b

4

Giải pháp Do / While thanh lịch hơn, nhưng nếu bạn chỉ sử dụng giải pháp While được đăng ở trên, không có moveToPocation (-1), bạn sẽ bỏ lỡ phần tử đầu tiên (ít nhất là trên truy vấn Liên hệ).

Tôi đề nghị:

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

Con trỏ là Giao diện đại diện cho một 2-dimensionalbảng của bất kỳ cơ sở dữ liệu nào.

Khi bạn cố gắng truy xuất một số dữ liệu bằng cách sử dụng SELECTcâu lệnh, thì cơ sở dữ liệu sẽ lần đầu tiên tạo một đối tượng CURSOR và trả về tham chiếu của nó cho bạn.

Con trỏ của tham chiếu trả về này đang trỏ đến vị trí 0 được gọi là vị trí đầu tiên của Con trỏ, vì vậy khi bạn muốn lấy dữ liệu từ con trỏ, bạn phải di chuyển đến bản ghi thứ 1 để chúng ta phải sử dụng di chuyển đến đầu tiên

Khi bạn gọi moveToFirst()phương thức trên Con trỏ, nó sẽ đưa con trỏ đến vị trí đầu tiên. Bây giờ bạn có thể truy cập dữ liệu hiện tại trong bản ghi 1

Cách tốt nhất để xem:

Con trỏ con trỏ

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

0

Ban đầu con trỏ không nằm trên chương trình hàng đầu tiên bằng cách sử dụng moveToNext()bạn có thể lặp lại con trỏ khi bản ghi không tồn tại return false, trừ khi nó return true,

while (cursor.moveToNext()) {
    ...
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.