精华内容
下载资源
问答
  • 在flash中做检索功能
    千次阅读
    2019-03-13 16:32:02

    摘要

    对于一个document,size是N个characters,和一个包含了M个关键字的字典,在这个document里替换或者找到这些关键字,如果用正则匹配的方式,时间复杂度应该是O(M*N)。而FlashText算法的时间复杂度可以达到O(N)。与Aho Corasick不同,FlashText算法不会匹配sub-strings。

    FlashText被设计来只匹配完整的单词(两边都有边界字符的单词)。比如输入的字典是{Apple},算法不会认为它和'I like Pineapple'匹配。这个算法也被设计来先找到最长的匹配。对于一个输入字典{Machine, Learning, Machine Learning},在字符串'I like Machine learning',它只会考虑最长的匹配,也就是Machine Learning。

    1 介绍

    在信息检索领域,关键字搜索和更换是一个标准的任务。往往我们想或者在文本中找到特定的关键字,或者将关键字更换为标准化的名字。

    例如:

    1. 关键字搜索:假设我们有一份软件工程师的简历resume(D),并且我们有一个20K的编程技能列表语料库corpus={java, python, javascript, machine learning,…}。我们想找到这20K个项目中的哪项出现在了resume中(也就是​)。

    2. 关键字更换:另一个常见的使用事例是当我们有了一个同义词的语料库(不同的拼写表示的意思相同),比如corpus={javascript:['javascript', 'javascripting', 'java script'],…},并且我们想在这份候选简历中,用标准化的名字更换掉这些同义词。

    为了解决这样的问题,Regular-Expressions(Regex)是普遍使用的。然而Regex在corpus项目terms的数目在100的规模比较奏效,但是在terms在1000的规模时,就相当的慢了。当no. of terms在10K规模,并且no. of documents在百万规模时,运行时间会达到几天。

    当no. of terms增加的时候,Regex所需的时间几乎是线性的增长。然而FlashText却几乎是一个常数。

    1.1 关键字搜索的Regex算法

    Regex作为一个工具在模式匹配上是非常多功能和有用的。在一个文本文件中,我们可以搜索像'\d{4}'的模式(这会匹配任何4个数字的号码),或者像'2017'的关键字。在给定字符串中,搜索‘2017‘或者任何4个数字的号码的样本python代码如下:

    import re
    compiled_regex= re.compile(r'\b2017\b|\b\d{4}\b')
    compiled_regex.findall('In 2017 2311 is my birthday.')
    # ['2017', '2311']

    这里'\b'用来表示单词的边界,使用它是为了23114不会返回2311作为一个匹配。

    1.2 关键字更换的Regex算法

    import re
    re.sub(r"\bjave script\b", 'javascript', 'java script is awesome.')
    # 'javascript is awesome'

    2 FlashText

    FlashText是一个基trie tree(字典树,又叫做单词查找树)数据结构的算法,并且被Aho Corasick算法所启发。它运行的方式是,首先,它将所有相关的关键字作为输入。用这些关键字建造一个trie dictionary。

    话不多说了,直接上关键部分参考代码

    如果需要这个包可以pip install flashtext

    FlashText源码地址在https://github.com/vi3k6i5/flashtext

    import os
    import string
    import io
    
    
    class KeywordProcessor(object):
        """KeywordProcessor
        Attributes:
            _keyword (str): Used as key to store keywords in trie dictionary.
                Defaults to '_keyword_'
            non_word_boundaries (set(str)): Characters that will determine if the word is continuing.
                Defaults to set([A-Za-z0-9_])
            keyword_trie_dict (dict): Trie dict built character by character, that is used for lookup
                Defaults to empty dictionary
            case_sensitive (boolean): if the search algorithm should be case sensitive or not.
                Defaults to False
        Examples:
            >>> # import module
            >>> from flashtext import KeywordProcessor
            >>> # Create an object of KeywordProcessor
            >>> keyword_processor = KeywordProcessor()
            >>> # add keywords
            >>> keyword_names = ['NY', 'new-york', 'SF']
            >>> clean_names = ['new york', 'new york', 'san francisco']
            >>> for keyword_name, clean_name in zip(keyword_names, clean_names):
            >>>     keyword_processor.add_keyword(keyword_name, clean_name)
            >>> keywords_found = keyword_processor.extract_keywords('I love SF and NY. new-york is the best.')
            >>> keywords_found
            >>> ['san francisco', 'new york', 'new york']
        Note:
            * loosely based on `Aho-Corasick algorithm <https://en.wikipedia.org/wiki/Aho%E2%80%93Corasick_algorithm>`_.
            * Idea came from this `Stack Overflow Question <https://stackoverflow.com/questions/44178449/regex-replace-is-taking-time-for-millions-of-documents-how-to-make-it-faster>`_.
        """
    
        def __init__(self, case_sensitive=False):
            """
            Args:
                case_sensitive (boolean): Keyword search should be case sensitive set or not.
                    Defaults to False
            """
            self._keyword = '_keyword_'
            self._white_space_chars = set(['.', '\t', '\n', '\a', ' ', ','])
            try:
                # python 2.x
                self.non_word_boundaries = set(string.digits + string.letters + '_')
            except AttributeError:
                # python 3.x
                self.non_word_boundaries = set(string.digits + string.ascii_letters + '_')
            self.keyword_trie_dict = dict()
            self.case_sensitive = case_sensitive
            self._terms_in_trie = 0
    
        def __len__(self):
            """Number of terms present in the keyword_trie_dict
            Returns:
                length : int
                    Count of number of distinct terms in trie dictionary.
            """
            return self._terms_in_trie
    
        def __contains__(self, word):
            """To check if word is present in the keyword_trie_dict
            Args:
                word : string
                    word that you want to check
            Returns:
                status : bool
                    If word is present as it is in keyword_trie_dict then we return True, else False
            Examples:
                >>> keyword_processor.add_keyword('Big Apple')
                >>> 'Big Apple' in keyword_processor
                >>> # True
            """
            if not self.case_sensitive:
                word = word.lower()
            current_dict = self.keyword_trie_dict
            len_covered = 0
            for char in word:
                if char in current_dict:
                    current_dict = current_dict[char]
                    len_covered += 1
                else:
                    break
            return self._keyword in current_dict and len_covered == len(word)
    
        def __getitem__(self, word):
            """if word is present in keyword_trie_dict return the clean name for it.
            Args:
                word : string
                    word that you want to check
            Returns:
                keyword : string
                    If word is present as it is in keyword_trie_dict then we return keyword mapped to it.
            Examples:
                >>> keyword_processor.add_keyword('Big Apple', 'New York')
                >>> keyword_processor['Big Apple']
                >>> # New York
            """
            if not self.case_sensitive:
                word = word.lower()
            current_dict = self.keyword_trie_dict
            len_covered = 0
            for char in word:
                if char in current_dict:
                    current_dict = current_dict[char]
                    len_covered += 1
                else:
                    break
            if self._keyword in current_dict and len_covered == len(word):
                return current_dict[self._keyword]
    
        def __setitem__(self, keyword, clean_name=None):
            """To add keyword to the dictionary
            pass the keyword and the clean name it maps to.
            Args:
                keyword : string
                    keyword that you want to identify
                clean_name : string
                    clean term for that keyword that you would want to get back in return or replace
                    if not provided, keyword will be used as the clean name also.
            Examples:
                >>> keyword_processor['Big Apple'] = 'New York'
            """
            status = False
            if not clean_name and keyword:
                clean_name = keyword
    
            if keyword and clean_name:
                if not self.case_sensitive:
                    keyword = keyword.lower()
                current_dict = self.keyword_trie_dict
                for letter in keyword:
                    current_dict = current_dict.setdefault(letter, {})
                if self._keyword not in current_dict:
                    status = True
                    self._terms_in_trie += 1
                current_dict[self._keyword] = clean_name
            return status
    
        def __delitem__(self, keyword):
            """To remove keyword from the dictionary
            pass the keyword and the clean name it maps to.
            Args:
                keyword : string
                    keyword that you want to remove if it's present
            Examples:
                >>> keyword_processor.add_keyword('Big Apple')
                >>> del keyword_processor['Big Apple']
            """
            status = False
            if keyword:
                if not self.case_sensitive:
                    keyword = keyword.lower()
                current_dict = self.keyword_trie_dict
                character_trie_list = []
                for letter in keyword:
                    if letter in current_dict:
                        character_trie_list.append((letter, current_dict))
                        current_dict = current_dict[letter]
                    else:
                        # if character is not found, break out of the loop
                        current_dict = None
                        break
                # remove the characters from trie dict if there are no other keywords with them
                if current_dict and self._keyword in current_dict:
                    # we found a complete match for input keyword.
                    character_trie_list.append((self._keyword, current_dict))
                    character_trie_list.reverse()
    
                    for key_to_remove, dict_pointer in character_trie_list:
                        if len(dict_pointer.keys()) == 1:
                            dict_pointer.pop(key_to_remove)
                        else:
                            # more than one key means more than 1 path.
                            # Delete not required path and keep the other
                            dict_pointer.pop(key_to_remove)
                            break
                    # successfully removed keyword
                    status = True
                    self._terms_in_trie -= 1
            return status
    
        def __iter__(self):
            """Disabled iteration as get_all_keywords() is the right way to iterate
            """
            raise NotImplementedError("Please use get_all_keywords() instead")
    
        def set_non_word_boundaries(self, non_word_boundaries):
            """set of characters that will be considered as part of word.
            Args:
                non_word_boundaries (set(str)):
                    Set of characters that will be considered as part of word.
            """
            self.non_word_boundaries = non_word_boundaries
    
        def add_non_word_boundary(self, character):
            """add a character that will be considered as part of word.
            Args:
                character (char):
                    Character that will be considered as part of word.
            """
            self.non_word_boundaries.add(character)
    
        def add_keyword(self, keyword, clean_name=None):
            """To add one or more keywords to the dictionary
            pass the keyword and the clean name it maps to.
            Args:
                keyword : string
                    keyword that you want to identify
                clean_name : string
                    clean term for that keyword that you would want to get back in return or replace
                    if not provided, keyword will be used as the clean name also.
            Returns:
                status : bool
                    The return value. True for success, False otherwise.
            Examples:
                >>> keyword_processor.add_keyword('Big Apple', 'New York')
                >>> # This case 'Big Apple' will return 'New York'
                >>> # OR
                >>> keyword_processor.add_keyword('Big Apple')
                >>> # This case 'Big Apple' will return 'Big Apple'
            """
            return self.__setitem__(keyword, clean_name)
    
        def remove_keyword(self, keyword):
            """To remove one or more keywords from the dictionary
            pass the keyword and the clean name it maps to.
            Args:
                keyword : string
                    keyword that you want to remove if it's present
            Returns:
                status : bool
                    The return value. True for success, False otherwise.
            Examples:
                >>> keyword_processor.add_keyword('Big Apple')
                >>> keyword_processor.remove_keyword('Big Apple')
                >>> # Returns True
                >>> # This case 'Big Apple' will no longer be a recognized keyword
                >>> keyword_processor.remove_keyword('Big Apple')
                >>> # Returns False
            """
            return self.__delitem__(keyword)
    
        def get_keyword(self, word):
            """if word is present in keyword_trie_dict return the clean name for it.
            Args:
                word : string
                    word that you want to check
            Returns:
                keyword : string
                    If word is present as it is in keyword_trie_dict then we return keyword mapped to it.
            Examples:
                >>> keyword_processor.add_keyword('Big Apple', 'New York')
                >>> keyword_processor.get('Big Apple')
                >>> # New York
            """
            return self.__getitem__(word)
    
        def add_keyword_from_file(self, keyword_file, encoding="utf-8"):
            """To add keywords from a file
            Args:
                keyword_file : path to keywords file
                encoding : specify the encoding of the file
            Examples:
                keywords file format can be like:
                >>> # Option 1: keywords.txt content
                >>> # java_2e=>java
                >>> # java programing=>java
                >>> # product management=>product management
                >>> # product management techniques=>product management
                >>> # Option 2: keywords.txt content
                >>> # java
                >>> # python
                >>> # c++
                >>> keyword_processor.add_keyword_from_file('keywords.txt')
            Raises:
                IOError: If `keyword_file` path is not valid
            """
            if not os.path.isfile(keyword_file):
                raise IOError("Invalid file path {}".format(keyword_file))
            with io.open(keyword_file, encoding=encoding) as f:
                for line in f:
                    if '=>' in line:
                        keyword, clean_name = line.split('=>')
                        self.add_keyword(keyword, clean_name.strip())
                    else:
                        keyword = line.strip()
                        self.add_keyword(keyword)
    
        def add_keywords_from_dict(self, keyword_dict):
            """To add keywords from a dictionary
            Args:
                keyword_dict (dict): A dictionary with `str` key and (list `str`) as value
            Examples:
                >>> keyword_dict = {
                        "java": ["java_2e", "java programing"],
                        "product management": ["PM", "product manager"]
                    }
                >>> keyword_processor.add_keywords_from_dict(keyword_dict)
            Raises:
                AttributeError: If value for a key in `keyword_dict` is not a list.
            """
            for clean_name, keywords in keyword_dict.items():
                if not isinstance(keywords, list):
                    raise AttributeError("Value of key {} should be a list".format(clean_name))
    
                for keyword in keywords:
                    self.add_keyword(keyword, clean_name)
    
        def remove_keywords_from_dict(self, keyword_dict):
            """To remove keywords from a dictionary
            Args:
                keyword_dict (dict): A dictionary with `str` key and (list `str`) as value
            Examples:
                >>> keyword_dict = {
                        "java": ["java_2e", "java programing"],
                        "product management": ["PM", "product manager"]
                    }
                >>> keyword_processor.remove_keywords_from_dict(keyword_dict)
            Raises:
                AttributeError: If value for a key in `keyword_dict` is not a list.
            """
            for clean_name, keywords in keyword_dict.items():
                if not isinstance(keywords, list):
                    raise AttributeError("Value of key {} should be a list".format(clean_name))
    
                for keyword in keywords:
                    self.remove_keyword(keyword)
    
        def add_keywords_from_list(self, keyword_list):
            """To add keywords from a list
            Args:
                keyword_list (list(str)): List of keywords to add
            Examples:
                >>> keyword_processor.add_keywords_from_list(["java", "python"]})
            Raises:
                AttributeError: If `keyword_list` is not a list.
            """
            if not isinstance(keyword_list, list):
                raise AttributeError("keyword_list should be a list")
    
            for keyword in keyword_list:
                self.add_keyword(keyword)
    
        def remove_keywords_from_list(self, keyword_list):
            """To remove keywords present in list
            Args:
                keyword_list (list(str)): List of keywords to remove
            Examples:
                >>> keyword_processor.remove_keywords_from_list(["java", "python"]})
            Raises:
                AttributeError: If `keyword_list` is not a list.
            """
            if not isinstance(keyword_list, list):
                    raise AttributeError("keyword_list should be a list")
    
            for keyword in keyword_list:
                self.remove_keyword(keyword)
    
        def get_all_keywords(self, term_so_far='', current_dict=None):
            """Recursively builds a dictionary of keywords present in the dictionary
            And the clean name mapped to those keywords.
            Args:
                term_so_far : string
                    term built so far by adding all previous characters
                current_dict : dict
                    current recursive position in dictionary
            Returns:
                terms_present : dict
                    A map of key and value where each key is a term in the keyword_trie_dict.
                    And value mapped to it is the clean name mapped to it.
            Examples:
                >>> keyword_processor = KeywordProcessor()
                >>> keyword_processor.add_keyword('j2ee', 'Java')
                >>> keyword_processor.add_keyword('Python', 'Python')
                >>> keyword_processor.get_all_keywords()
                >>> {'j2ee': 'Java', 'python': 'Python'}
                >>> # NOTE: for case_insensitive all keys will be lowercased.
            """
            terms_present = {}
            if not term_so_far:
                term_so_far = ''
            if current_dict is None:
                current_dict = self.keyword_trie_dict
            for key in current_dict:
                if key == '_keyword_':
                    terms_present[term_so_far] = current_dict[key]
                else:
                    sub_values = self.get_all_keywords(term_so_far + key, current_dict[key])
                    for key in sub_values:
                        terms_present[key] = sub_values[key]
            return terms_present
    
        def extract_keywords(self, sentence, span_info=False):
            """Searches in the string for all keywords present in corpus.
            Keywords present are added to a list `keywords_extracted` and returned.
            Args:
                sentence (str): Line of text where we will search for keywords
            Returns:
                keywords_extracted (list(str)): List of terms/keywords found in sentence that match our corpus
            Examples:
                >>> from flashtext import KeywordProcessor
                >>> keyword_processor = KeywordProcessor()
                >>> keyword_processor.add_keyword('Big Apple', 'New York')
                >>> keyword_processor.add_keyword('Bay Area')
                >>> keywords_found = keyword_processor.extract_keywords('I love Big Apple and Bay Area.')
                >>> keywords_found
                >>> ['New York', 'Bay Area']
            """
            keywords_extracted = []
            if not sentence:
                # if sentence is empty or none just return empty list
                return keywords_extracted
            if not self.case_sensitive:
                sentence = sentence.lower()
            current_dict = self.keyword_trie_dict
            sequence_start_pos = 0
            sequence_end_pos = 0
            reset_current_dict = False
            idx = 0
            sentence_len = len(sentence)
            while idx < sentence_len:
                char = sentence[idx]
                # when we reach a character that might denote word end
                if char not in self.non_word_boundaries:
    
                    # if end is present in current_dict
                    if self._keyword in current_dict or char in current_dict:
                        # update longest sequence found
                        sequence_found = None
                        longest_sequence_found = None
                        is_longer_seq_found = False
                        if self._keyword in current_dict:
                            sequence_found = current_dict[self._keyword]
                            longest_sequence_found = current_dict[self._keyword]
                            sequence_end_pos = idx
    
                        # re look for longest_sequence from this position
                        if char in current_dict:
                            current_dict_continued = current_dict[char]
    
                            idy = idx + 1
                            while idy < sentence_len:
                                inner_char = sentence[idy]
                                if inner_char not in self.non_word_boundaries and self._keyword in current_dict_continued:
                                    # update longest sequence found
                                    longest_sequence_found = current_dict_continued[self._keyword]
                                    sequence_end_pos = idy
                                    is_longer_seq_found = True
                                if inner_char in current_dict_continued:
                                    current_dict_continued = current_dict_continued[inner_char]
                                else:
                                    break
                                idy += 1
                            else:
                                # end of sentence reached.
                                if self._keyword in current_dict_continued:
                                    # update longest sequence found
                                    longest_sequence_found = current_dict_continued[self._keyword]
                                    sequence_end_pos = idy
                                    is_longer_seq_found = True
                            if is_longer_seq_found:
                                idx = sequence_end_pos
                        current_dict = self.keyword_trie_dict
                        if longest_sequence_found:
                            keywords_extracted.append((longest_sequence_found, sequence_start_pos, idx))
                        reset_current_dict = True
                    else:
                        # we reset current_dict
                        current_dict = self.keyword_trie_dict
                        reset_current_dict = True
                elif char in current_dict:
                    # we can continue from this char
                    current_dict = current_dict[char]
                else:
                    # we reset current_dict
                    current_dict = self.keyword_trie_dict
                    reset_current_dict = True
                    # skip to end of word
                    idy = idx + 1
                    while idy < sentence_len:
                        char = sentence[idy]
                        if char not in self.non_word_boundaries:
                            break
                        idy += 1
                    idx = idy
                # if we are end of sentence and have a sequence discovered
                if idx + 1 >= sentence_len:
                    if self._keyword in current_dict:
                        sequence_found = current_dict[self._keyword]
                        keywords_extracted.append((sequence_found, sequence_start_pos, sentence_len))
                idx += 1
                if reset_current_dict:
                    reset_current_dict = False
                    sequence_start_pos = idx
            if span_info:
                return keywords_extracted
            return [value[0] for value in keywords_extracted]
    
        def replace_keywords(self, sentence):
            """Searches in the string for all keywords present in corpus.
            Keywords present are replaced by the clean name and a new string is returned.
            Args:
                sentence (str): Line of text where we will replace keywords
            Returns:
                new_sentence (str): Line of text with replaced keywords
            Examples:
                >>> from flashtext import KeywordProcessor
                >>> keyword_processor = KeywordProcessor()
                >>> keyword_processor.add_keyword('Big Apple', 'New York')
                >>> keyword_processor.add_keyword('Bay Area')
                >>> new_sentence = keyword_processor.replace_keywords('I love Big Apple and bay area.')
                >>> new_sentence
                >>> 'I love New York and Bay Area.'
            """
            if not sentence:
                # if sentence is empty or none just return the same.
                return sentence
            new_sentence = []
            orig_sentence = sentence
            if not self.case_sensitive:
                sentence = sentence.lower()
            current_word = ''
            current_dict = self.keyword_trie_dict
            current_white_space = ''
            sequence_end_pos = 0
            idx = 0
            sentence_len = len(sentence)
            while idx < sentence_len:
                char = sentence[idx]
                current_word += orig_sentence[idx]
                # when we reach whitespace
                if char not in self.non_word_boundaries:
                    current_white_space = char
                    # if end is present in current_dict
                    if self._keyword in current_dict or char in current_dict:
                        # update longest sequence found
                        sequence_found = None
                        longest_sequence_found = None
                        is_longer_seq_found = False
                        if self._keyword in current_dict:
                            sequence_found = current_dict[self._keyword]
                            longest_sequence_found = current_dict[self._keyword]
                            sequence_end_pos = idx
    
                        # re look for longest_sequence from this position
                        if char in current_dict:
                            current_dict_continued = current_dict[char]
                            current_word_continued = current_word
                            idy = idx + 1
                            while idy < sentence_len:
                                inner_char = sentence[idy]
                                current_word_continued += orig_sentence[idy]
                                if inner_char not in self.non_word_boundaries and self._keyword in current_dict_continued:
                                    # update longest sequence found
                                    current_white_space = inner_char
                                    longest_sequence_found = current_dict_continued[self._keyword]
                                    sequence_end_pos = idy
                                    is_longer_seq_found = True
                                if inner_char in current_dict_continued:
                                    current_dict_continued = current_dict_continued[inner_char]
                                else:
                                    break
                                idy += 1
                            else:
                                # end of sentence reached.
                                if self._keyword in current_dict_continued:
                                    # update longest sequence found
                                    current_white_space = ''
                                    longest_sequence_found = current_dict_continued[self._keyword]
                                    sequence_end_pos = idy
                                    is_longer_seq_found = True
                            if is_longer_seq_found:
                                idx = sequence_end_pos
                                current_word = current_word_continued
                        current_dict = self.keyword_trie_dict
                        if longest_sequence_found:
                            new_sentence.append(longest_sequence_found + current_white_space)
                            current_word = ''
                            current_white_space = ''
                        else:
                            new_sentence.append(current_word)
                            current_word = ''
                            current_white_space = ''
                    else:
                        # we reset current_dict
                        current_dict = self.keyword_trie_dict
                        new_sentence.append(current_word)
                        current_word = ''
                        current_white_space = ''
                elif char in current_dict:
                    # we can continue from this char
                    current_dict = current_dict[char]
                else:
                    # we reset current_dict
                    current_dict = self.keyword_trie_dict
                    # skip to end of word
                    idy = idx + 1
                    while idy < sentence_len:
                        char = sentence[idy]
                        current_word += orig_sentence[idy]
                        if char not in self.non_word_boundaries:
                            break
                        idy += 1
                    idx = idy
                    new_sentence.append(current_word)
                    current_word = ''
                    current_white_space = ''
                # if we are end of sentence and have a sequence discovered
                if idx + 1 >= sentence_len:
                    if self._keyword in current_dict:
                        sequence_found = current_dict[self._keyword]
                        new_sentence.append(sequence_found)
                    else:
                        new_sentence.append(current_word)
                idx += 1
            return "".join(new_sentence)

    衡量FlashText算法与正则表达式匹配

    import random
    import re
    import string
    import time
    
    
    def get_word_of_length(str_length):
        # generate a random word of given length
        return ''.join(random.choice(string.ascii_lowercase) for _ in range(str_length))
    
    
    # generate a list of 100K words of randomly chosen size
    all_words = [get_word_of_length(random.choice([3, 4, 5, 6, 7, 8])) for i in range(100000)]
    
    print('Count  | FlashText | Regex     |')
    print('−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−')
    for keywords_length in [0, 1000, 5000, 10000, 15000]:
        # chose 1000 terms and create a string to search in .
        all_words_chosen = random.sample(all_words, 1000)
        story = ' '.join(all_words_chosen)
    
        # get unique keywords from the l i s t of words generated .
        unique_keywords_sublist = list(set(random.sample(all_words, keywords_length)))
        # compile Regex
        compiled_re = re.compile('|'.join([r'\b' + keyword + r'\b' for keyword in unique_keywords_sublist]))
    
        # add keywords to Flashtext
        keyword_processor = KeywordProcessor()
        # keyword_processor.add_keywords_from_list(unique_keywords_sublist)
        for keyword in unique_keywords_sublist:
            keyword_processor.add_keyword(keyword)
    
        # time the modules
        start = time.time()
        _ = keyword_processor.extract_keywords(story)
        mid = time.time()
        _ = compiled_re.findall(story)
        end = time.time()
    
        # print output
        print(str(keywords_length).ljust(6), '|',
              "{0:.5f}".format(mid - start).ljust(9), '|',
              "{0:.5f}".format(end - mid).ljust(9), '|', )

    在我的笔记本电脑上某一次运行的结果(story长度为1000个单词,Count是所要查找的单词数量):

    Count  | FlashText | Regex     |
    −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
    0      | 0.00083   | 0.00069   |
    1000   | 0.00106   | 0.00716   |
    5000   | 0.00110   | 0.03609   |
    10000  | 0.00121   | 0.07547   |
    15000  | 0.00155   | 0.11627   |

     

    更多相关内容
  • EasyFlash V4.0 ENV 功能设计与实现

    千次阅读 2020-04-23 20:07:31
    文档链接:EasyFlash V4.0 ENV 功能设计与实现 EasyFlash V4.0 ENV 功能设计与实现 1、为什么要开发 V4.0 EasyFlash 是我个人开发的第二款开源软件,自 2015 年初正式开源出来,至今(2019.02)已经经历了 4 年多...

    声明:本文档来源于EasyFlash的仓库,原作者armink。因github上图片显示缓慢,我转载到了CSDN。
    文档链接:EasyFlash V4.0 ENV 功能设计与实现

    EasyFlash V4.0 ENV 功能设计与实现

    1、为什么要开发 V4.0

    EasyFlash 是我个人开发的第二款开源软件,自 2015 年初正式开源出来,至今(2019.02)已经经历了 4 年多时间。期间有很多其他行业的嵌入式开发者与我取得联系,得知他们已经将 EasyFlash 应用于自己的产品上,我心里也倍感欣慰,可见 EasyFlash 的成熟性已经得到了很多行业的认可。

    1.1 功能简洁,但性能差强人意

    大家普遍的感觉是 EasyFlash 功能简洁,可以很容易的应用于产品上。但随着技术的演进,大家对于 KV 需求的多样化,对于 MCU 资源(主要是 RAM)、Flash 存储资源、Flash 寿命等性能指标越来越高,旧版本的 EasyFlash 在这些方面还是有提升的空间。比如:

    1.2 旧版本的痛点

    • 每个存储在 Flash 上的 ENV 都会在 RAM 中缓存一份,这样做虽然能够简化实现,但确实会占用很多 RAM 资源;
    • ENV 的值类型只支持字符串,如果想要保存其他类型的值(比如:数组、结构体)就比较麻烦了,虽然我后来为此又专门开发了 struct2json 开源软件,但还是不够便捷;
    • 每次保存 ENV 都需要重新擦写整个 Flash 扇区,那么位于扇区尾部未使用的区域始终无法得到利用,降低了 Flash 的使用效率,也就降低了 Flash 的使用寿命

    1.3 从 0 开始的 NG 版本

    也就是从 2017 年初开始,我便开始准备 EasyFlash 的性能优化工作,结合大家的需求,不断的整理、迭代设计文档,也与一些社区爱好者做过非常深入的交流。最终确定下来,如果单纯的在原有基础上进行完善,那么会有太多的功能实现受到限制,所以干脆重新开发全新一代 ENV 功能组件,这个版本被命名为 NG(Next Generation) 版本。

    NG 版本差不多在 2017 年底就已经设计完毕,但一直没时间去开发。后来在亲人的支持下,终于利用 2019 年猪年春节的假期,在岳父母家完成了 V4.0 NG 版本的开发(在此感谢岳父母、爱人的支持)。

    2、V4.0 的特色有哪些

    • 更小的资源占用,内存占用 几乎为 0
    • ENV 的值类型支持 任意类型 、任意长度,相当于直接 memcpy 变量至 flash ;
    • ENV 操作效率比以前的模式高,充分利用剩余空闲区域,擦除次数及操作时间显著降低;
    • 原生支持 磨损平衡、掉电保护功能 (V4.0 之前需要占用额外的 Flash 扇区);
    • ENV 支持 增量升级 ,固件升级后 ENV 也支持升级;
    • 支持大数据存储模式,长度无限制,数据可在多个 Flash 扇区上顺序存储。像脚本程序、音频等占用 Flash 超过 1 个扇区的资源也都可以存入 ENV;
    • 支持 数据加密 ,提升存储的安全性,物联网时代的必备功能;
    • 支持 数据压缩 ,减低 Flash 占用;

    3、如何实现

    3.1 算法

    假定 ENV 分区里有 4 个扇区,以下将按照操作 ENV 的方式,逐一举例讲解不同操作下,对应的 Flash 状态及数据变化。

    3.1.1 ENV 操作过程1(常规模式)

    3.1.1.1 首次使用

    在这里插入图片描述

    首次使用时,EasyFlash 会检查各个扇区的 header,如果不符合规定的格式将执行全部格式化操作,格式化后,每个扇区的顶部将被存入 header ,负责记录当前扇区的状态、魔数等信息。格式化的初始化状态为空状态。

    3.1.1.2 添加 KV1、KV2、KV3

    在这里插入图片描述

    在执行添加操作前,会先检索合适地址来存放即将添加的新 KV,这里检索策略主要是:

    • 确定当前选择的扇区剩余容量充足
    • 优选选择正在使用状态的扇区,最后使用空状态扇区
    • 检查新 KV 是否有同名的 KV 存在,存在还需要额外执行删除旧值的动作

    通过上图可以看出, KV1、KV2 及 KV3 已经被放入 sector1 ,添加后,扇区状态也被修改为正在使用

    3.1.1.3 修改 KV2 KV3,删除 KV1,添加 KV4


    修改 ENV 时,旧的 ENV 将被删除,扇区的状态也将被修改为脏状态,然后再执行新增 ENV 的操作。

    • 执行修改 KV2 时,已经存在的 KV2 旧值被修改为已删除,sector1 状态被修改为脏状态,此后将 KV2 新值放入 sector1,发现 sector1 已经没有空间了,sector1 的状态还会被修改为已满状态;

    • 执行修改 KV3 时,已经存在的 KV3 旧值被修改为已删除,sector1 状态已经为脏状态,无需再做修改。经过查找发现 KV3 的新值只能放到 sector2,放到 sector2 后将其修改为正在使用状态;

    • 执行删除 KV1 时,找到 KV1 的位置,将其修改为已删除状态,sector1 状态已经为脏状态,无需再做修改;

    • 执行添加 KV4 时,经过查找在 sector2 找到合适的存储位置,将其添加后,sector2 状态已经为正在使用状态,无需再做修改。

    3.1.1.4 添加 KV5 KV6,触发 GC

    • 执行添加 KV5 操作,由于 KV5 体积较大,sector2 放不下,所以只能放在一个新扇区 sector3 上,添加后,修改 sector3 状态为正在使用
    • 执行添加 KV6 操作,KV6 也只能放在 sector3 下,将其放入 sector 3 后,发现 sector3 空间已满,所以将其修改已满状态。执行完成后,发现整个 ENV 的 4 个扇区只有 1 个状态为空的扇区了,这个扇区如果再继续使用就没法再执行 GC 操作了,所以此时触发了 GC 请求;
    • 执行 GC 请求,EasyFlash 会找到所有被标记为已满并且为脏状态的扇区,并将其内部的 ENV 搬运至其他位置。就这样 sector1 上的 KV2 被搬运至了 sector2,腾空 sector1 后,又对其执行了格式化操作,这样整个 ENV 分区里又多了一个空状态的扇区。

    3.1.2 ENV 操作过程2(开启大数据存储模式)

    马上就来……

    3.2 数据结构

    结合上面的算法不难发现,其实所有的操作都围绕着 扇区状态ENV状态 ,这些状态将被存放在扇区及 ENV 头部,并且保证在不擦除扇区数据的前提下进行单向修改,在程序代码实现上称这些状态及其他一些数据信息为 元数据

    除了常规功能外,还有一项重要指标是 EasyFlash 非常看重的,那就是掉电保护能力,相当于在任何操作出现掉电异常,整个 EasyFlash 的容错能力是否过硬,是否可以进行掉电恢复。像 准备写入、准备删除这些中间状态就是为了掉电保护功能而设计。

    出于后期扩展性的考虑这里也预留了一些保留属性,还有一些提前规划好的状态及属性后面将用过多扇区存储、加密、压缩功能的实现。

    设计完成后,整个 ENV 的数据结构如下图,该图最终也可转换为对应的结构体。

    展开全文
  • 实现 嵌入式手持移动终端,应用程序需要存储和检索大量的用户信息,这些信息应用程序退出时应该被记录保存下来,同时这些信息要能被多个应用程序并发使用,比如电话本的数据要能够被电话,短消息,名片等多...
  • 它从头开始,构建了相应的功能来获取和设置数据,以及通过本地手机上的Async-Storage设置数据的功能,即使关闭应用程序也可以检索数据。 此APP使用REDUX和Async-Storage,初始数据从Async-storage加载并设置为...
  • 前言,公司之前在线文档使用的Flash预览,用的es2全文检索,现在要进行项目整改,Flash现在不能用了,所以调整为KKFileView。对于ES也需要进行升级,添加IK中文分词器。所以就写了这篇文档进行总结与存档。 关于...

    前言,公司之前在线文档使用的Flash预览,用的es2全文检索,现在要进行项目整改,Flash现在不能用了,所以调整为KKFileView。对于ES也需要进行升级,添加IK中文分词器。所以就写了这篇文档进行总结与存档。

    关于KKFileView的搭建与使用这里就不多说了,KKFileView官网基本都给出了解决方案,有一些个别的复制问题,我也在另一篇文档中写了。KKFileView在线预览初使用记录,主要解决不可复制等一些限制问题。

    Elasticsearch在Java中使用

    下面我贴出了已经写好工具类,方便后续使用。

    文件处理

    如果是纯文本的格式,那么我们直接上传就好了,但是如果是word、PDF等其他的文件形式,就需要进行预处理操作了。所以我们要先建立一个通道;关于为什么建立通道的问题,有兴趣的同学可以去看一下es的PUT请求原理;

    安装文本抽取插件

    ## 安装目录下运行下面的命令就可以进行安装
    ./bin/elasticsearch-plugin install ingest-attachment
    

    定义文本抽取管道

    利用kibana运行下面的代码段,提示true就OK了.
    [记得重启es,不然一会定义管道的时候,会报错哦。如果是集群的话,所有的服务都要重启才可以]
    如果不知道kibana是什么的同学,可以去学习一下
    对于安装kibana和运行可以去看一下我的另一篇文档
    Linux安装运行
    Mac安装运行
    ik分词器这两篇文档也都有说

    PUT /_ingest/pipeline/attachment
    {
        "description": "Extract attachment information",
        "processors": [
            {
                "attachment": {
                    "field": "content",
                    "ignore_missing": true
                }
            },
            {
                "remove": {
                    "field": "content"
                }
            }
        ]
    }
    

    创建索引

    PUT /索引名称
    这里的properties可以根据实际字段进行调整

    PUT /fileindex
    {
      "mappings": {
        "properties": {
          "id":{
            "type": "keyword"
          },
          "name":{
            "type": "text",
            "analyzer": "ik_max_word"
          },
          "sfName":{
            "type": "text",
            "analyzer": "ik_max_word"
          },
          "createBy":{
            "type": "text",
            "analyzer": "ik_max_word"
          },
          "type":{
            "type": "keyword"
          },
          "attachment": {
            "properties": {
              "content":{
                "type": "text",
                "analyzer": "ik_smart"
              }
            }
          }
        }
      }
    }
    

    如果上面两步都成功的话,那可以进行一个测试了

    因为ElasticSearch是基于JSON 格式的文档数据库,所以附件文档在插入ElasticSearch之前必须进行Base64编码。先通过下面的网站将一个pdf文件转化为base64的文本。PDF to Base64

    POST /docwrite/_doc?pipeline=attachment
    {
    	"name":"进口红酒",
    	"type":"pdf",
    	"content":"这里放入你转换后的base64"
    }
    

    然后我们可以通过GET来查询刚刚上传的文档是否成功。

    GET /docwrite/_search
    

    如果不出意外的话,应该是可以正常看到已经解析后的信息,这里我就不贴图了。
    如果不指定pipline的话,是无法被es解析的。查询出来就是不是你所认识的中文,哈哈哈。

    Java操作实例

    POM引入

    <dependency>
    			<groupId>org.elasticsearch.client</groupId>
    			<artifactId>elasticsearch-rest-high-level-client</artifactId>
    			<version>7.13.4</version>
    		</dependency>
    		<dependency>
    			<groupId>org.elasticsearch</groupId>
    			<artifactId>elasticsearch</artifactId>
    			<version>7.13.4</version>
    		</dependency>
    
    		<dependency>
    			<groupId>org.apache.httpcomponents</groupId>
    			<artifactId>httpclient</artifactId>
    			<version>4.5.8</version>
    		</dependency>
    
    		<dependency>
    			<groupId>org.apache.httpcomponents</groupId>
    			<artifactId>httpcore</artifactId>
    			<version>4.4.9</version>
    		</dependency>
    
    		<dependency>
    			<groupId>com.alibaba</groupId>
    			<artifactId>fastjson</artifactId>
    			<version>1.2.7</version>
    		</dependency>
    

    必要信息的实体类

    //这个实体类可以是 你的实际业务为主,但是要保留content字段
    public class fileMessage {
        String id; //用于存储文件id
        String name; //文件名
        String type; //文件的type,pdf,word,or txt
        String content; //文件转化成base64编码后所有的内容。
    }
    

    上传代码

    使用下面的updateESFile方法就可以上传了。具体的业务逻辑,你们可以根据实际业务来做,实体类也要根据实际业务来做。

    private void updateESFile(String filePath){
    	File file = new File(filePath);
    	if (!file.exists()) {
    		System.out.println("找不到文件");
    	}
    	fileMessage fileM = new fileMessage();
    	try {
    		byte[] bytes = getContent(file);
    		String base64 = Base64.getEncoder().encodeToString(bytes);
    		fileM.setId("1");
    		fileM.setName(file.getName());
    		fileM.setContent(base64);
    		IndexRequest indexRequest = new IndexRequest("fileindex");
    		//上传同时,使用attachment pipline进行提取文件
    		indexRequest.source(JSON.toJSONString(fileM), XContentType.JSON);
    		indexRequest.setPipeline("attachment");
    		IndexResponse indexResponse = EsUtil.client.index(indexRequest, RequestOptions.DEFAULT);
    		logger.info("send to eSearch:" + fileName);
    		logger.info("send to eSeach results:" + indexResponse);
    	} catch (IOException | SAXException | TikaException e) {
    		e.printStackTrace();
    	}
    }
    
    
    /**
    	 * 文件转base64
    	 * @param file
    	 * @return
    	 * @throws IOException
    	 */
    	private byte[] getContent(File file) throws IOException {
    
    		long fileSize = file.length();
    		if (fileSize > Integer.MAX_VALUE) {
    			System.out.println("file too big...");
    			return null;
    		}
    		FileInputStream fi = new FileInputStream(file);
    		byte[] buffer = new byte[(int) fileSize];
    		int offset = 0;
    		int numRead = 0;
    		while (offset < buffer.length
    				&& (numRead = fi.read(buffer, offset, buffer.length - offset)) >= 0) {
    			offset += numRead;
    		}
    		// 确保所有数据均被读取
    		if (offset != buffer.length) {
    			throw new IOException("Could not completely read file "
    					+ file.getName());
    		}
    		fi.close();
    		return buffer;
    	}
    
    

    JavaEsUtil工具类

    package util;
    import com.alibaba.fastjson.JSON;
    import org.apache.http.HttpHost;
    import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
    import org.elasticsearch.action.bulk.BulkRequest;
    import org.elasticsearch.action.bulk.BulkResponse;
    import org.elasticsearch.action.delete.DeleteRequest;
    import org.elasticsearch.action.delete.DeleteResponse;
    import org.elasticsearch.action.get.GetRequest;
    import org.elasticsearch.action.get.GetResponse;
    import org.elasticsearch.action.index.IndexRequest;
    import org.elasticsearch.action.index.IndexResponse;
    import org.elasticsearch.action.search.SearchRequest;
    import org.elasticsearch.action.search.SearchResponse;
    import org.elasticsearch.action.support.master.AcknowledgedResponse;
    import org.elasticsearch.action.update.UpdateRequest;
    import org.elasticsearch.action.update.UpdateResponse;
    import org.elasticsearch.client.RequestOptions;
    import org.elasticsearch.client.RestClient;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.elasticsearch.client.indices.CreateIndexRequest;
    import org.elasticsearch.client.indices.CreateIndexResponse;
    import org.elasticsearch.client.indices.GetIndexRequest;
    import org.elasticsearch.common.text.Text;
    import org.elasticsearch.common.unit.TimeValue;
    import org.elasticsearch.common.xcontent.XContentType;
    import org.elasticsearch.index.query.QueryBuilders;
    import org.elasticsearch.search.SearchHit;
    import org.elasticsearch.search.builder.SearchSourceBuilder;
    import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
    import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
    
    import java.io.IOException;
    import java.lang.reflect.Method;
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    import java.util.Map;
    
    
    public class EsUtil {
        /**
         * 客户端,本次使用本地连接
         */
        public static RestHighLevelClient client = new RestHighLevelClient(
                RestClient.builder(
                        new HttpHost("127.0.0.1", 9200, "http")));
    
    
        /**
         * 停止连接
         */
        public static void shutdown() {
            if (client != null) {
                try {
                    client.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
    
            }
        }
    
        /**
         * 默认类型(整个type类型传闻在8.0版本后可能会废弃,但是目前7.13版本下先保留)
         */
        public static final String DEFAULT_TYPE = "_doc";
    
        /**
         * set方法前缀
         */
        public static final String SET_METHOD_PREFIX = "set";
    
        /**
         * 返回状态-CREATED
         */
        public static final String RESPONSE_STATUS_CREATED = "CREATED";
    
        /**
         * 返回状态-OK
         */
        public static final String RESPONSE_STATUS_OK = "OK";
    
        /**
         * 返回状态-NOT_FOUND
         */
        public static final String RESPONSE_STATUS_NOT_FOUND = "NOT_FOUND";
    
        /**
         * 需要过滤的文档数据
         */
        public static final String[] IGNORE_KEY = {"@timestamp", "@version", "type"};
    
        /**
         * 超时时间 1s
         */
        public static final TimeValue TIME_VALUE_SECONDS = TimeValue.timeValueSeconds(1);
    
        /**
         * 批量新增
         */
        public static final String PATCH_OP_TYPE_INSERT = "insert";
    
        /**
         * 批量删除
         */
        public static final String PATCH_OP_TYPE_DELETE = "delete";
    
        /**
         * 批量更新
         */
        public static final String PATCH_OP_TYPE_UPDATE = "update";
    
    //==========================================数据操作(工具)(不参与调用es)=================================================
    
        /**
         * 方法描述: 剔除指定文档数据,减少不必要的循环
         *
         * @param map 文档数据
         * @return: void
         * @author: gxf
         * @date: 2021年07月27日
         * @time: 10:39 上午
         */
        public static void ignoreSource(Map<String, Object> map) {
            for (String key : IGNORE_KEY) {
                map.remove(key);
            }
        }
    
    
        /**
         * 方法描述: 将文档数据转化为指定对象
         *
         * @param sourceAsMap 文档数据
         * @param clazz       转换目标Class对象
         * @return 对象
         * @author: gxf
         * @date: 2021年07月27日
         * @time: 10:38 上午
         */
        public static <T> T dealObject(Map<String, Object> sourceAsMap, Class<T> clazz) {
            try {
                ignoreSource(sourceAsMap);
                Iterator<String> keyIterator = sourceAsMap.keySet().iterator();
                T t = clazz.newInstance();
                while (keyIterator.hasNext()) {
                    String key = keyIterator.next();
                    String replaceKey = key.replaceFirst(key.substring(0, 1), key.substring(0, 1).toUpperCase());
                    Method method = null;
                    try {
                        method = clazz.getMethod(SET_METHOD_PREFIX + replaceKey, sourceAsMap.get(key).getClass());
                    } catch (NoSuchMethodException e) {
                        continue;
                    }
                    method.invoke(t, sourceAsMap.get(key));
                }
                return t;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
    
    //==========================================索引操作=================================================
    
        /**
         * 方法描述: 创建索引,若索引不存在且创建成功,返回true,若同名索引已存在,返回false
         *
         * @param: [index] 索引名称
         * @return: boolean
         * @author: gxf
         * @date: 2021年07月27日
         * @time: 11:01 上午
         */
        public static boolean insertIndex(String index) {
            //创建索引请求
            CreateIndexRequest request = new CreateIndexRequest(index);
            //执行创建请求IndicesClient,请求后获得响应
            try {
                CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
                return response != null;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return false;
        }
    
    
        /**
         * 方法描述: 判断索引是否存在,若存在返回true,若不存在或出现问题返回false
         *
         * @param: [index] 索引名称
         * @return: boolean
         * @author: gxf
         * @date: 2021年07月27日
         * @time: 11:09 上午
         */
        public static boolean isExitsIndex(String index) {
            GetIndexRequest request = new GetIndexRequest(index);
            try {
                return client.indices().exists(request, RequestOptions.DEFAULT);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return false;
        }
    
    
        /*
         * 方法描述: 删除索引,删除成功返回true,删除失败返回false
         * @param: [index] 索引名称
         * @return: boolean
         * @author: gxf
         * @date: 2021年07月27日
         * @time: 11:23 上午
         */
        public static boolean deleteIndex(String index) {
            DeleteIndexRequest request = new DeleteIndexRequest(index);
            try {
                AcknowledgedResponse response = client.indices().delete(request, RequestOptions.DEFAULT);
                return response.isAcknowledged();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return false;
        }
    
    
    //==========================================文档操作(新增,删除,修改)=================================================
    
        /**
         * 方法描述: 新增/修改文档信息
         *
         * @param index 索引
         * @param id    文档id
         * @param data  数据
         * @return: boolean
         * @author: gxf
         * @date: 2021年07月27日
         * @time: 10:34 上午
         */
        public static boolean insertOrUpdateDocument(String index, String id, Object data) {
            try {
                IndexRequest request = new IndexRequest(index);
                request.timeout(TIME_VALUE_SECONDS);
                if (id != null && id.length() > 0) {
                    request.id(id);
                }
                request.source(JSON.toJSONString(data), XContentType.JSON);
                IndexResponse response = client.index(request, RequestOptions.DEFAULT);
                String status = response.status().toString();
                if (RESPONSE_STATUS_CREATED.equals(status) || RESPONSE_STATUS_OK.equals(status)) {
                    return true;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return false;
        }
    
        /**
         * 方法描述: 更新文档信息
         *
         * @param index 索引
         * @param id    文档id
         * @param data  数据
         * @return: boolean
         * @author: gxf
         * @date: 2021年07月27日
         * @time: 10:34 上午
         */
        public static boolean updateDocument(String index, String id, Object data) {
            try {
                UpdateRequest request = new UpdateRequest(index, id);
                request.doc(JSON.toJSONString(data), XContentType.JSON);
                UpdateResponse response = client.update(request, RequestOptions.DEFAULT);
                String status = response.status().toString();
                if (RESPONSE_STATUS_OK.equals(status)) {
                    return true;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return false;
        }
    
        /**
         * 方法描述:删除文档信息
         *
         * @param index 索引
         * @param id    文档id
         * @return: boolean
         * @author: gxf
         * @date: 2021年07月27日
         * @time: 10:33 上午
         */
        public static boolean deleteDocument(String index, String id) {
            try {
                DeleteRequest request = new DeleteRequest(index, id);
                DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
                String status = response.status().toString();
                if (RESPONSE_STATUS_OK.equals(status)) {
                    return true;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return false;
        }
    
    
        /**
         * 方法描述: 小数据量批量新增
         *
         * @param index    索引
         * @param dataList 数据集 新增修改需要传递
         * @param timeout  超时时间 单位为秒
         * @return: boolean
         * @author: gxf
         * @date: 2021年07月27日
         * @time: 10:31 上午
         */
        public static boolean simplePatchInsert(String index, List<Object> dataList, long timeout) {
            try {
                BulkRequest bulkRequest = new BulkRequest();
                bulkRequest.timeout(TimeValue.timeValueSeconds(timeout));
                if (dataList != null && dataList.size() > 0) {
                    for (Object obj : dataList) {
                        bulkRequest.add(
                                new IndexRequest(index)
                                        .source(JSON.toJSONString(obj), XContentType.JSON)
                        );
                    }
                    BulkResponse response = client.bulk(bulkRequest, RequestOptions.DEFAULT);
                    if (!response.hasFailures()) {
                        return true;
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return false;
        }
    
        /**
         * 功能描述:
         * @param index 索引名称
         * @param idList 需要批量删除的id集合
         * @return : boolean
         * @author : gxf
         * @date : 2021/6/30 1:22
         */
        public static boolean patchDelete(String index, List<String> idList) {
            BulkRequest request = new BulkRequest();
            for (String id:idList) {
                request.add(new DeleteRequest().index(index).id(id));
            }
            try {
                BulkResponse response = EsUtil.client.bulk(request, RequestOptions.DEFAULT);
                return !response.hasFailures();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return false;
        }
    
    
    //==========================================文档操作(查询)=================================================
    
        /**
         * 方法描述: 判断文档是否存在
         *
         * @param index 索引
         * @param id    文档id
         * @return: boolean
         * @author: gxf
         * @date: 2021年07月27日
         * @time: 10:36 上午
         */
        public static boolean isExistsDocument(String index, String id) {
            return isExistsDocument(index, DEFAULT_TYPE, id);
        }
    
    
        /**
         * 方法描述: 判断文档是否存在
         *
         * @param index 索引
         * @param type  类型
         * @param id    文档id
         * @return: boolean
         * @author: gxf
         * @date: 2021年07月27日
         * @time: 10:36 上午
         */
        public static boolean isExistsDocument(String index, String type, String id) {
            GetRequest request = new GetRequest(index, type, id);
            try {
                GetResponse response = client.get(request, RequestOptions.DEFAULT);
                return response.isExists();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return false;
        }
    
    
        /**
         * 方法描述: 根据id查询文档
         *
         * @param index 索引
         * @param id    文档id
         * @param clazz 转换目标Class对象
         * @return 对象
         * @author: gxf
         * @date: 2021年07月27日
         * @time: 10:36 上午
         */
        public static <T> T selectDocumentById(String index, String id, Class<T> clazz) {
            return selectDocumentById(index, DEFAULT_TYPE, id, clazz);
        }
    
    
        /**
         * 方法描述: 根据id查询文档
         *
         * @param index 索引
         * @param type  类型
         * @param id    文档id
         * @param clazz 转换目标Class对象
         * @return 对象
         * @author: gxf
         * @date: 2021年07月27日
         * @time: 10:35 上午
         */
        public static <T> T selectDocumentById(String index, String type, String id, Class<T> clazz) {
            try {
                type = type == null || type.equals("") ? DEFAULT_TYPE : type;
                GetRequest request = new GetRequest(index, type, id);
                GetResponse response = client.get(request, RequestOptions.DEFAULT);
                if (response.isExists()) {
                    Map<String, Object> sourceAsMap = response.getSourceAsMap();
                    return dealObject(sourceAsMap, clazz);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
    
        /**
         * 方法描述:(筛选条件)获取数据集合
         *
         * @param index         索引
         * @param sourceBuilder 请求条件
         * @param clazz         转换目标Class对象
         * @return: java.util.List<T>
         * @author: gxf
         * @date: 2021年07月27日
         * @time: 10:35 上午
         */
        public static <T> List<T> selectDocumentList(String index, SearchSourceBuilder sourceBuilder, Class<T> clazz) {
            try {
                SearchRequest request = new SearchRequest(index);
                if (sourceBuilder != null) {
                    // 返回实际命中数
                    sourceBuilder.trackTotalHits(true);
                    request.source(sourceBuilder);
                }
                SearchResponse response = client.search(request, RequestOptions.DEFAULT);
                if (response.getHits() != null) {
                    List<T> list = new ArrayList<>();
                    SearchHit[] hits = response.getHits().getHits();
                    for (SearchHit documentFields : hits) {
                        Map<String, Object> sourceAsMap = documentFields.getSourceAsMap();
                        list.add(dealObject(sourceAsMap, clazz));
                    }
                    return list;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
    
        /**
         * 方法描述:(筛选条件)获取数据
         *
         * @param index         索引
         * @param sourceBuilder 请求条
         * @return: java.util.List<T>
         * @author: gxf
         * @date: 2021年07月27日
         * @time: 10:35 上午
         */
        public static SearchResponse selectDocument(String index, SearchSourceBuilder sourceBuilder) {
            try {
                SearchRequest request = new SearchRequest(index);
                if (sourceBuilder != null) {
                    // 返回实际命中数
                    sourceBuilder.trackTotalHits(true);
                    sourceBuilder.size(10000);
                    request.source(sourceBuilder);
                }
                return client.search(request, RequestOptions.DEFAULT);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
    
        /**
         * 方法描述: 筛选查询,返回使用了<span style='color:red'></span>处理好的数据.
         *
         * @param: index 索引名称
         * @param: sourceBuilder sourceBuilder对象
         * @param: clazz 需要返回的对象类型.class
         * @param: highLight 需要表现的高亮匹配字段
         * @return: java.util.List<T>
         * @author: gxf
         * @date: 2021年07月27日
         * @time: 6:39 下午
         */
        public static <T> List<T> selectDocumentListHighLight(String index, SearchSourceBuilder sourceBuilder, Class<T> clazz, String highLight) {
            try {
                SearchRequest request = new SearchRequest(index);
                if (sourceBuilder != null) {
                    // 返回实际命中数
                    sourceBuilder.trackTotalHits(true);
                    //高亮
                    HighlightBuilder highlightBuilder = new HighlightBuilder();
                    highlightBuilder.field(highLight);
                    highlightBuilder.requireFieldMatch(false);//多个高亮关闭
                    highlightBuilder.preTags("<span style='color:red'>");
                    highlightBuilder.postTags("</span>");
                    sourceBuilder.highlighter(highlightBuilder);
                    request.source(sourceBuilder);
                }
                SearchResponse response = client.search(request, RequestOptions.DEFAULT);
                if (response.getHits() != null) {
                    List<T> list = new ArrayList<>();
                    for (SearchHit documentFields : response.getHits().getHits()) {
                        Map<String, HighlightField> highlightFields = documentFields.getHighlightFields();
                        HighlightField title = highlightFields.get(highLight);
                        Map<String, Object> sourceAsMap = documentFields.getSourceAsMap();
                        if (title != null) {
                            Text[] fragments = title.fragments();
                            String n_title = "";
                            for (Text fragment : fragments) {
                                n_title += fragment;
                            }
                            sourceAsMap.put(highLight, n_title);//高亮替换原来的内容
                        }
                        list.add(dealObject(sourceAsMap, clazz));
                    }
                    return list;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
        /**
         * 方法描述: 返回索引内所有内容,返回SearchResponse对象,需要自己解析,不对数据封装
         * @param: index 索引名称
         * @return: SearchResponse
         * @author: gxf
         * @date: 2021/6/30
         * @time: 1:28 上午
         */
        public static SearchResponse queryAllData(String index){
            //创建搜索请求对象
            SearchRequest request = new SearchRequest(index);
            //构建查询的请求体
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            //查询所有数据
            sourceBuilder.query(QueryBuilders.matchAllQuery());
            request.source(sourceBuilder);
            try {
                return client.search(request, RequestOptions.DEFAULT);
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }
    
    
        /**
         * 方法描述: 返回索引内所有内容,返回指定类型
         * @param: index 索引名称
         * @param: clazz 需要接受转换的对象类型
         * @return: java.util.List<T>
         * @author: gxf
         * @date: 2021/6/30
         * @time: 1:32 上午
         */
        public static <T> List<T> queryAllData(String index, Class<T> clazz){
            //创建搜索请求对象
            SearchRequest request = new SearchRequest(index);
            //构建查询的请求体
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            //查询所有数据
            sourceBuilder.query(QueryBuilders.matchAllQuery());
            request.source(sourceBuilder);
            try {
                SearchResponse response = client.search(request, RequestOptions.DEFAULT);
                if (response.getHits() != null) {
                    List<T> list = new ArrayList<>();
                    SearchHit[] hits = response.getHits().getHits();
                    for (SearchHit documentFields : hits) {
                        Map<String, Object> sourceAsMap = documentFields.getSourceAsMap();
                        list.add(dealObject(sourceAsMap, clazz));
                    }
                    return list;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }
    }
    

    Java ES全文检索

    
    public List<Map<String, Object>> eSearch(String msg) throws UnknownHostException {
    //
    		List<Map<String, Object>> matchRsult = new LinkedList<Map<String, Object>>();
    		SearchSourceBuilder builder = new SearchSourceBuilder();
    
    		//因为我这边实际业务需要其他字段的查询,所以进行查询的字段就比较,如果只是查询文档中内容的话,打开注释的代码,然后注释掉这行代码		
    		builder.query(QueryBuilders.multiMatchQuery(msg,"attachment.content","name","sfName","createBy").analyzer("ik_smart"));
    
    		//builder.query(QueryBuilders.matchQuery("attachment.content", msg).analyzer("ik_smart"));
    		SearchResponse searchResponse = EsUtil.selectDocument("fileindex", builder);
    		SearchHits hits = searchResponse.getHits();
    
    		for (SearchHit hit : hits.getHits()) {
    			hit.getSourceAsMap().put("msg", "");
    			matchRsult.add(hit.getSourceAsMap());
    			// System.out.println(hit.getSourceAsString());
    		}
    		System.out.println("over in the main");
    
    		return matchRsult;
    	}
    

    完结

    至此简单使用Java对ES文档上传后全文检索已经完成。祝愿大家完美撒花;
    ✿✿ヽ(°▽°)ノ✿      帅哥美女,留个赞再走吧。

    展开全文
  • 信息检索的应用 & 如何检索

    千次阅读 2020-05-05 18:26:34
    信息检索在日常生活的广泛应用 1. 防止上当受骗 2.健康 3. 特价旅游信息 4.免费读书 5. 名校课程、开放教育资源OERs 搜索引擎SE的分类 1. 按内容的组织方式 2. 按收录资源的范围 3.按信息采集的方式 4....

    目录

    1. 综合搜索引擎 

     2. 特定搜索引擎

    信息检索在日常生活中的广泛应用

    1. 防止上当受骗

    2.健康 

    3. 特价旅游信息

    4. 免费读书

     5. 名校课程、开放教育资源OERs

    搜索引擎SE的分类

    1. 按内容的组织方式

     2. 按收录资源的范围

    3.按信息采集的方式

    4.按搜索引擎SE的功能

    5. 按面向成人/孩子

    6. 综合性搜索引擎的使用


    1. 综合搜索引擎 

    Google 使用指南 -Docs

     2. 特定搜索引擎

    信息检索在日常生活中的广泛应用

    1. 防止上当受骗

     

    2.健康 

    3. 特价旅游信息

    4. 免费读书

     5. 名校课程、开放教育资源OERs


    搜索引擎SE的分类

    1. 按内容的组织方式

     2. 按收录资源的范围

    3.按信息采集的方式

    4.按搜索引擎SE的功能

    5. 按面向成人/孩子

      小孩的SE: kidrex、Google的safe search kids, 中文的yam番薯藤网 

    6. 综合性搜索引擎的使用

    查找最受欢迎的综合SE: ebiz 、 comscore

    Google支持的10种非HTML文件:PDF文件,Microsoft Office (doc, ppt, xls, rtf)、Shockwave Flash (swf)、PostScript (ps)、Autodesk DWF (dwf)、 Google earth (kml,kmz) 

    参考:信息检索-慕课,2014,武汉大学,黄如花老师

    导图的PDF完整版获取:网盘链接 --- 提取码: 3sfe

    展开全文
  • $(function(){ - 每列下面添加检索框 $('#dataTable tfoot th').each( function () { var title = $('#dataTable thead th').eq( $(this).index() ).text(); $(this).htm...
  • 如何将Flash模拟成EEPROM (EEPROM Emulation) 温馨提示: 本文参考《EEPROM Emulation with Qorivva MPC55xx, MPC56xx, and MPC57xx Microcontrollers》 关注公众号“嵌入式软件实战派”,并回复“EEP”可获得本文...
  • AdvancedWebServerHuzzah_SPIFFS:这最后一种技术使用SPIFFS闪存文件系统在FLASH存储器实现网站,以便可以像检索外部SD卡上的文件一样检索项目。 SPIFFS编写实用程序(可以按照说明将其添加到ArduinoIDE)似乎
  • Flash Chat是一个基于互联网的消息传递应用程序,类似于WhatsApp,后者是Facebook以220亿美元收购的流行消息传递应用程序。 该应用程序使用Firebase Firestore等服务作为后端数据库,以从云存储和检索我们的消息。 ...
  • STM32F4xx的NAND Flash应用事项

    千次阅读 2020-07-07 08:03:02
    8-bit NAND Flash NAND Flash operations Timing diagrams for NAND Error Correction Code (ECC) Timing diagrams for NAND NAND Flash prewait functionality NAND Flash Card control registers PC Card/NAND ...
  • 网易闪电邮(FlashMail)是一款网易自主研发的电子邮件客户端软件,支持所有网易邮箱帐户和POP3邮箱帐户的本地收发和管理邮件功能。 网易闪电邮(FlashMail)的最大特点是: (1)超高速:使用网易自主研发的专有...
  • :motorcycle:功能选择图库图片从Google云端硬盘选择图片捕捉相机图像裁切图像(根据提供的宽高比裁切图像或让用户选择其中一个) 压缩图像(根据提供的分辨率和尺寸压缩图像) 以文件形式检索图像结果,以字符串或...
  • 通过本系统,网站架设者不需要进行任何编程,经过简单的安装和设置,网上 搭建起功能完善的论坛服务。 EasyJForum 是一套精简的论坛系统,具备一个完整的论坛系统所需要的常用的功能,尤其适用于小网站。本论坛...
  • FLASH相关知识扫盲

    2018-01-24 11:27:41
    在flash的动态存储(单链表方式),遇到flash坏块问题,从中引申出许多不熟的知识点,此梳理标记。其大致过程,经过和同事及朋友的沟通,nand flash的坏块处理方法如下:flash寄存器标志位检测、采用文件系统...
  • 第24章 QSPI—读写串行FLASH

    千次阅读 2020-12-24 14:41:47
    本章参考资料:《STM32F76xxx参考手册》、《STM32F76xxx规格书》、库帮助文档《STM32F...关于FLASH存储器,请参考“常用存储器介绍”章节,实验中FLASH芯片的具体参数,请参考其规格书《W25Q128》来了解。 24.1 Q..
  • 引言:本文我们介绍下使用串行SPI Flash配置Xilinx 7系列FPGA的优点以及相应的配置方案实现细节,包括FPGA和SPI Flash之间的信号连接及如何选择恰当的SPI Flash。 1.简介 图1显示了利用Vivado开发工具间接编程SPI ...
  • 通过本系统,网站架设者不需要进行任何编程,经过简单的安装和设置,网上 搭建起功能完善的论坛服务。 EasyJForum 是一套精简的论坛系统,具备一个完整的论坛系统所需要的常用的功能,尤其适用于小网站。本论坛...
  • 信息检索旨在根据用户查询,大规模数据查找与查询相关的文档或网页,并根据其相关程度给出相应的排序列表。而用户提交给搜索引擎的查询往往仅包含少量关键词,很难全面描述用户的信息需求,进而对查询意图的理解...
  • 通过本系统,网站架设者不需要进行任何编程,经过简单的安装和设置,网上 搭建起功能完善的论坛服务。 EasyJForum 是一套精简的论坛系统,具备一个完整的论坛系统所需要的常用的功能,尤其适用于小网站。本论坛...
  • Flask实现简单搜索功能

    千次阅读 2021-05-10 23:22:35
    主要功能是通过form的方式传值,再到数据库查询。下面是数据库的内容我们这里主要是实现对content进行模糊匹配。 首先打开pycharm,构建一个最简单的flask应用。 #search.py from flask import Flask ...
  • 【转载】ROM、RAM、FLASH、NVM……详解

    千次阅读 2021-06-28 13:46:21
    原文链接:ROM、RAM、FLASH、NVM……一文搞定 原文: /-----------------------------------------------------------------------/ 前段时间面试了一个小伙子,他有三四年嵌入式软件开发经验的了,随意问了一个问题...
  • NRF52832 内部 Flash 的存储官方提供了两种方式,一种是 FStorage 方式,另一种是 FStorage 基础上的 FDS 方式。 1.1 FStorage方式 FStorage 是一个用于读取、写入和擦除持久闪存数据的模块。该模块定义了一个...
  • 原教程 ... 现在我们来给博客增加文章检索功能,即根据关键字模糊查询文章标题,且字母不区分大小写。 首先,我们修改 header.ejs , 前添加一行代码: style.css 添加一行样式: .search{bord
  • 这样得好处,就是非常适合物联网终端设备使用,使得日志可以更加容易的存储非文件系统,并具有历史日志检索功能。 GitHub:https://github.com/armink/EasyLogger/blob/master/docs/zh/port/flash.md 硬件...
  • 例如一个有毒物质降解实验,实验反应温度、 时间、pH值这些数据得处理都需要耗费大量的人力、物力、财力,易表XP 的分 组统计功能为争叫实验设计数据处理提供了巨大的方便。 (二) 环境科学与工程的网络应用 环境...
  • 关于flash跨域问题

    千次阅读 2016-03-23 15:51:00
    Security.allowDomain("*"); Security.allowInsecureDomain("*");   Security.loadPolicyFile(... 过游戏开发的时候,往往会遇到安全域的问题,然而这个问题,确实实实在在的困扰着我们
  • JFlash关于JLINK升级烧录脚本指令指令解释合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一...
  • Ajax和Flash的区别

    2017-10-05 21:04:47
    由于中间有一层Flashplayer代理层,因此许多辅助功能无法被Flash灵活利用。而且Flash在一些方面有着不好的口碑。比如弹出广告、比如恶意代码。 (awflasher.com个人认为这八成是乱上xx网站造成的) 易于开发 ...
  • 优化 Flash 性能

    千次阅读 2013-10-30 09:31:27
    原文:... 本文,您将找到使用 Flash Professional 创建的应用程序的性能优化战略。 优化过程包括编辑 FLA 项目文件,确保所发布的应用程序已实现的(或实际的)帧速率足以能流畅地回放动画。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 14,341
精华内容 5,736
热门标签
关键字:

在flash中做检索功能