對List對象列表屬性值的快速搜索

對于數(shù)據(jù)的搜索已有很多成熟的方案楣黍,比如Apace Lucene框架,結(jié)合ikanalyer等分詞器能實現(xiàn)很復(fù)雜和高效的搜索,或直接使用sql語言對數(shù)據(jù)庫關(guān)鍵字進行搜索等。

但這些搜索都很重谍咆,對于已經(jīng)加載完成的數(shù)據(jù)列表并不適用。

比如有這樣一個需求:已經(jīng)加載了一個班的學(xué)生在一個List<Student>列表中香罐,要根據(jù)學(xué)生和姓名和住址做一個模糊搜索卧波。因為數(shù)據(jù)已經(jīng)加載到List中,存在于內(nèi)存中庇茫,若再從數(shù)據(jù)庫或網(wǎng)絡(luò)上去使用關(guān)鍵字取值并不是很優(yōu)的方案港粱。而一般做法是在查找關(guān)鍵字里,遍歷整個列表逐個匹配旦签,但若是如手機上做逐個輸入搜索的話會產(chǎn)生大量的循環(huán)操作查坪,效率很低。

為此宁炫,今天分享一種比較對List列表比較高效的搜索方式偿曙。

思路:
1、傳入數(shù)據(jù)源List羔巢,并指定要搜索的字段望忆;將這些字段的值拼接成一個字符串罩阵,并保存每個對象的值的起始和結(jié)束位置:
2、搜索時启摄,先使用正則表達式在保存的搜索字符串找到位置稿壁,再利用這些位置在索引數(shù)據(jù)數(shù)組中找到對應(yīng)對象索引;

具體實現(xiàn):

package com.fansion;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class FSearchTool {
    private StringBuffer mKeyWordString = new StringBuffer();
    private List<Object> mSearchObjs = new ArrayList<>();
    private int[] mIndexes;

    public FSearchTool(List<? extends Object> objects, String... fields) throws Exception {
        super();
        init(objects, fields);
    }

    private void init(List<? extends Object> objs, String... fields) throws Exception {
        if (objs != null) {
            mKeyWordString.setLength(0);
            mSearchObjs.clear();
            mSearchObjs = new ArrayList<>(objs);
            mIndexes = new int[mSearchObjs.size() * 2];
            int index = 0;
            for (int i = 0; i < mSearchObjs.size(); i++) {
                Object info = mSearchObjs.get(i);
                // 指定要搜索的字段
                String searchKey = getSearchKey(info, fields);
                // 將該字符串在總字符串中的起終位置保存下來,位置是索引值而非長度
                int length = mKeyWordString.length();
                mIndexes[index] = length;
                mKeyWordString.append(searchKey);
                length = mKeyWordString.length();
                index++;
                // 保存新加搜索字段的索引值
                mIndexes[index] = (length > 0) ? length - 1 : 0;
                index++;
            }
        }
    }

    /**
     * 通過反射從對象中取出指定字段的值
     */
    private String getSearchKey(Object obj, String... fields) throws Exception {
        StringBuilder searchKeys = new StringBuilder();
        Class<? extends Object> clazz = obj.getClass();
        try {
            for (String str : fields) {
                // 搜索字段使用空格隔開
                Field f = clazz.getDeclaredField(str);
                f.setAccessible(true);
                Object val = f.get(obj);
                searchKeys.append(val).append(" ");
                f.setAccessible(false);
            }
        } catch (Exception e) {
            throw new Exception("取值異常:" + e.getMessage());
        }
        return searchKeys.toString();
    }

    /**
     * 搜索結(jié)果
     *
     * @param keyWords
     *            搜索的關(guān)鍵字歉备,要去掉首尾的空格
     * @return 返回搜索到的對象
     */
    public List<Object> searchTasks(String keyWords) {
        List<Object> searchedTask = new ArrayList<>();
        int[] searchIndex = getSearchIndex(keyWords);
        for (int index : searchIndex) {
            if (index != -1 && index < mSearchObjs.size() * 2) {
                Object info = mSearchObjs.get(index / 2);
                if (info != null && !searchedTask.contains(info)) {
                    searchedTask.add(info);
                }
            }
        }
        return searchedTask;
    }

    /**
     * 找到匹配的索引數(shù)據(jù)
     *
     * @param keyWords
     *            搜索的關(guān)鍵字
     * @return 在初始化的索引數(shù)組的下標數(shù)組
     */
    private int[] getSearchIndex(String keyWords) {
        Pattern pattern = Pattern.compile(keyWords, Pattern.CASE_INSENSITIVE | Pattern.LITERAL);
        Matcher matcher = pattern.matcher(mKeyWordString.toString());
        ArrayList<Integer> searchResult = new ArrayList<>();
        while (matcher.find()) {
            // 不宜在此處再做循環(huán)傅是,否則可能造成循環(huán)次數(shù)過多錯誤
            searchResult.add(matcher.start());
        }
        int[] searchIndexes = new int[searchResult.size()];
        for (int i = 0; i < searchIndexes.length; i++) {
            int findIndex = findIndex(searchResult.get(i));
            searchIndexes[i] = (findIndex / 2) * 2;
        }
        return searchIndexes;
    }

    /**
     * 使用二分法找到指定字符位置在索引數(shù)組中的位置
     *
     * @param charAt
     *            字符在整個字符串中的位置
     * @return 在索引數(shù)組中的位置
     */
    private int findIndex(int charAt) {
        int low = 0;
        int high = mIndexes.length - 1;
        int mid = -1;
        while (low <= high) {
            mid = (low + high) >>> 1;
            int midVal = mIndexes[mid];
            if (midVal < charAt) {
                low = mid + 1;
            } else if (midVal > charAt) {
                high = mid - 1;
            } else {
                return mid;
            }
        }
        return mid;
    }

}

簡單的使用和測試:

    public static void main(String[] args) {
        List<Student> students = new ArrayList<>();
        students.add(new Student("001", "小王", "天府大道一街"));
        students.add(new Student("002", "小王", "天府大道二街"));
        students.add(new Student("003", "小紅", "天府大道三街"));
        students.add(new Student("004", "小明", "天府大道四街"));
        students.add(new Student("005", "小王", "天府大道五街"));
        students.add(new Student("006", "小王", "軟件園南門"));
        students.add(new Student("007", "小張", "吉泰路"));
        try {
            FSearchTool tool = new FSearchTool(students, "name", "address");
            System.out.println(tool.searchTasks("明"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

輸出結(jié)果:

[Student [id=004, name=小明, address=天府大道四街]]

博客地址:對List對象列表屬性值的快速搜索

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蕾羊,隨后出現(xiàn)的幾起案子喧笔,更是在濱河造成了極大的恐慌,老刑警劉巖龟再,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件书闸,死亡現(xiàn)場離奇詭異,居然都是意外死亡吸申,警方通過查閱死者的電腦和手機梗劫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來截碴,“玉大人梳侨,你說我怎么就攤上這事∪盏ぃ” “怎么了走哺?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長哲虾。 經(jīng)常有香客問我丙躏,道長,這世上最難降的妖魔是什么束凑? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任晒旅,我火速辦了婚禮,結(jié)果婚禮上汪诉,老公的妹妹穿的比我還像新娘废恋。我一直安慰自己,他們只是感情好扒寄,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布鱼鼓。 她就那樣靜靜地躺著,像睡著了一般该编。 火紅的嫁衣襯著肌膚如雪迄本。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天课竣,我揣著相機與錄音嘉赎,去河邊找鬼置媳。 笑死,一個胖子當(dāng)著我的面吹牛曹阔,可吹牛的內(nèi)容都是我干的半开。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼赃份,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了奢米?” 一聲冷哼從身側(cè)響起抓韩,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鬓长,沒想到半個月后谒拴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡涉波,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年英上,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片啤覆。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡苍日,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出窗声,到底是詐尸還是另有隱情相恃,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布笨觅,位于F島的核電站拦耐,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏见剩。R本人自食惡果不足惜杀糯,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望苍苞。 院中可真熱鬧固翰,春花似錦、人聲如沸柒啤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽担巩。三九已至方援,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間涛癌,已是汗流浹背犯戏。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工送火, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人先匪。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓种吸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親呀非。 傳聞我的和親對象是個殘疾皇子坚俗,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容