روشهای زیادی برای ذخیره دادههای شما وجود دارد، مانند یک پایگاه داده آنلاین، یک پایگاه داده محلی SQLite یا حتی در یک فایل متنی. تصمیم با شماست که بهترین راه حل برای برنامه شما چیست. این درس به شما نشان میدهد که چگونه یک جدول مجازی SQLite ایجاد کنید که بتواند جستجوی متن کامل قوی را ارائه دهد. این جدول با دادههای یک فایل متنی پر میشود که شامل یک جفت کلمه و تعریف در هر خط از فایل است.
ایجاد جدول مجازی
یک جدول مجازی رفتاری مشابه جدول SQLite دارد، اما به جای خواندن و نوشتن در یک فایل پایگاه داده، از طریق فراخوانیهای مجدد، یک شیء در حافظه را میخواند و مینویسد. برای ایجاد یک جدول مجازی، یک کلاس برای جدول ایجاد کنید:
کاتلین
class DatabaseTable(context: Context) { private val databaseOpenHelper = DatabaseOpenHelper(context) }
جاوا
public class DatabaseTable { private final DatabaseOpenHelper databaseOpenHelper; public DatabaseTable(Context context) { databaseOpenHelper = new DatabaseOpenHelper(context); } }
یک کلاس داخلی در DatabaseTable ایجاد کنید که SQLiteOpenHelper ارثبری کند. کلاس SQLiteOpenHelper متدهای انتزاعی را تعریف میکند که باید آنها را بازنویسی کنید تا جدول پایگاه داده شما بتواند در صورت لزوم ایجاد و ارتقا یابد. برای مثال، در اینجا کدی وجود دارد که یک جدول پایگاه داده را تعریف میکند که شامل کلماتی برای یک برنامه فرهنگ لغت خواهد بود:
کاتلین
private const val TAG = "DictionaryDatabase" // The columns we'll include in the dictionary table const val COL_WORD = "WORD" const val COL_DEFINITION = "DEFINITION" private const val DATABASE_NAME = "DICTIONARY" private const val FTS_VIRTUAL_TABLE = "FTS" private const val DATABASE_VERSION = 1 private const val FTS_TABLE_CREATE = "CREATE VIRTUAL TABLE $FTS_VIRTUAL_TABLE USING fts3 ($COL_WORD, $COL_DEFINITION)" class DatabaseTable(context: Context) { private val databaseOpenHelper: DatabaseOpenHelper init { databaseOpenHelper = DatabaseOpenHelper(context) } private class DatabaseOpenHelper internal constructor(private val helperContext: Context) : SQLiteOpenHelper(helperContext, DATABASE_NAME, null, DATABASE_VERSION) { private lateinit var mDatabase: SQLiteDatabase override fun onCreate(db: SQLiteDatabase) { mDatabase = db mDatabase.execSQL(FTS_TABLE_CREATE) } override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { Log.w( TAG, "Upgrading database from version $oldVersion to $newVersion , which will " + "destroy all old data" ) db.execSQL("DROP TABLE IF EXISTS $FTS_VIRTUAL_TABLE") onCreate(db) } } }
جاوا
public class DatabaseTable { private static final String TAG = "DictionaryDatabase"; // The columns we'll include in the dictionary table public static final String COL_WORD = "WORD"; public static final String COL_DEFINITION = "DEFINITION"; private static final String DATABASE_NAME = "DICTIONARY"; private static final String FTS_VIRTUAL_TABLE = "FTS"; private static final int DATABASE_VERSION = 1; private final DatabaseOpenHelper databaseOpenHelper; public DatabaseTable(Context context) { databaseOpenHelper = new DatabaseOpenHelper(context); } private static class DatabaseOpenHelper extends SQLiteOpenHelper { private final Context helperContext; private SQLiteDatabase mDatabase; private static final String FTS_TABLE_CREATE = "CREATE VIRTUAL TABLE " + FTS_VIRTUAL_TABLE + " USING fts3 (" + COL_WORD + ", " + COL_DEFINITION + ")"; DatabaseOpenHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); helperContext = context; } @Override public void onCreate(SQLiteDatabase db) { mDatabase = db; mDatabase.execSQL(FTS_TABLE_CREATE); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { Log.w(TAG, "Upgrading database from version " + oldVersion + " to " + newVersion + ", which will destroy all old data"); db.execSQL("DROP TABLE IF EXISTS " + FTS_VIRTUAL_TABLE); onCreate(db); } } }
جدول مجازی را پر کنید
اکنون جدول به دادهها برای ذخیره نیاز دارد. کد زیر به شما نشان میدهد که چگونه یک فایل متنی (واقع در res/raw/definitions.txt ) را که حاوی کلمات و تعاریف آنهاست، بخوانید، چگونه آن فایل را تجزیه کنید و چگونه هر خط از آن فایل را به عنوان یک ردیف در جدول مجازی وارد کنید. همه این کارها در یک thread دیگر انجام میشود تا از قفل شدن رابط کاربری جلوگیری شود. کد زیر را به کلاس داخلی DatabaseOpenHelper خود اضافه کنید.
نکته: همچنین میتوانید یک فراخوانی تنظیم کنید تا فعالیت رابط کاربری شما را از تکمیل این نخ مطلع سازد.
کاتلین
private fun loadDictionary() { Thread(Runnable { try { loadWords() } catch (e: IOException) { throw RuntimeException(e) } }).start() } @Throws(IOException::class) private fun loadWords() { val inputStream = helperContext.resources.openRawResource(R.raw.definitions) BufferedReader(InputStreamReader(inputStream)).use { reader -> var line: String? = reader.readLine() while (line != null) { val strings: List<String> = line.split("-").map { it.trim() } if (strings.size < 2) continue val id = addWord(strings[0], strings[1]) if (id < 0) { Log.e(TAG, "unable to add word: ${strings[0]}") } line = reader.readLine() } } } fun addWord(word: String, definition: String): Long { val initialValues = ContentValues().apply { put(COL_WORD, word) put(COL_DEFINITION, definition) } return database.insert(FTS_VIRTUAL_TABLE, null, initialValues) }
جاوا
private void loadDictionary() { new Thread(new Runnable() { public void run() { try { loadWords(); } catch (IOException e) { throw new RuntimeException(e); } } }).start(); } private void loadWords() throws IOException { final Resources resources = helperContext.getResources(); InputStream inputStream = resources.openRawResource(R.raw.definitions); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); try { String line; while ((line = reader.readLine()) != null) { String[] strings = TextUtils.split(line, "-"); if (strings.length < 2) continue; long id = addWord(strings[0].trim(), strings[1].trim()); if (id < 0) { Log.e(TAG, "unable to add word: " + strings[0].trim()); } } } finally { reader.close(); } } public long addWord(String word, String definition) { ContentValues initialValues = new ContentValues(); initialValues.put(COL_WORD, word); initialValues.put(COL_DEFINITION, definition); return database.insert(FTS_VIRTUAL_TABLE, null, initialValues); }
هر جا که لازم بود، متد loadDictionary() را برای پر کردن جدول فراخوانی کنید. یک جای مناسب میتواند متد onCreate() از کلاس DatabaseOpenHelper ، درست پس از ایجاد جدول باشد:
کاتلین
override fun onCreate(db: SQLiteDatabase) { database = db database.execSQL(FTS_TABLE_CREATE) loadDictionary() }
جاوا
@Override public void onCreate(SQLiteDatabase db) { database = db; database.execSQL(FTS_TABLE_CREATE); loadDictionary(); }
جستجو برای عبارت مورد نظر
وقتی جدول مجازی ایجاد و پر شد، از کوئری ارائه شده توسط SearchView خود برای جستجوی دادهها استفاده کنید. متدهای زیر را به کلاس DatabaseTable اضافه کنید تا یک دستور SQL بسازید که کوئری را جستجو کند:
کاتلین
fun getWordMatches(query: String, columns: Array<String>?): Cursor? { val selection = "$COL_WORD MATCH ?" val selectionArgs = arrayOf("$query*") return query(selection, selectionArgs, columns) } private fun query( selection: String, selectionArgs: Array<String>, columns: Array<String>? ): Cursor? { val cursor: Cursor? = SQLiteQueryBuilder().run { tables = FTS_VIRTUAL_TABLE query(databaseOpenHelper.readableDatabase, columns, selection, selectionArgs, null, null, null) } return cursor?.run { if (!moveToFirst()) { close() null } else { this } } ?: null }
جاوا
public Cursor getWordMatches(String query, String[] columns) { String selection = COL_WORD + " MATCH ?"; String[] selectionArgs = new String[] {query+"*"}; return query(selection, selectionArgs, columns); } private Cursor query(String selection, String[] selectionArgs, String[] columns) { SQLiteQueryBuilder builder = new SQLiteQueryBuilder(); builder.setTables(FTS_VIRTUAL_TABLE); Cursor cursor = builder.query(databaseOpenHelper.getReadableDatabase(), columns, selection, selectionArgs, null, null, null); if (cursor == null) { return null; } else if (!cursor.moveToFirst()) { cursor.close(); return null; } return cursor; }
با فراخوانی getWordMatches() یک کوئری جستجو کنید. هر نتیجه منطبقی در یک Cursor برگردانده میشود که میتوانید در آن پیمایش کنید یا از آن برای ساخت یک ListView استفاده کنید. این مثال getWordMatches() را در متد handleIntent() از activity قابل جستجو فراخوانی میکند. به یاد داشته باشید که activity قابل جستجو، کوئری را به عنوان یک ورودی اضافی در داخل intent مربوط به ACTION_SEARCH دریافت میکند، به دلیل فیلتر intent که قبلاً ایجاد کردهاید:
کاتلین
private val db = DatabaseTable(this) ... private fun handleIntent(intent: Intent) { if (Intent.ACTION_SEARCH == intent.action) { val query = intent.getStringExtra(SearchManager.QUERY) val c = db.getWordMatches(query, null) // process Cursor and display results } }
جاوا
DatabaseTable db = new DatabaseTable(this); ... private void handleIntent(Intent intent) { if (Intent.ACTION_SEARCH.equals(intent.getAction())) { String query = intent.getStringExtra(SearchManager.QUERY); Cursor c = db.getWordMatches(query, null); // process Cursor and display results } }
