搜索引擎技术内幕之索引

搜索引擎中索引的好坏直接影响着搜索引擎的性能,最终影响到用户的体验,可见索引的重要性。

今天我们就来谈谈索引技术。谈到索引大家第一想到的是倒排索引,的确倒排在全文检索中的优势,在搜索引擎中的大量使用令它声名鹊起。所以在此就以倒 排进行分析。但是除了倒排索引外还有很多的索引方式,如静态索引方式有:位图、签名文件、倒排等;动态索引有:B树、B+树等等。

搜索引擎之所以大量使用倒排作为它内部的索引结构,本人觉得主要有两个原因:

1、容易实现、存储简单,更重要的一点是方便进行rank排序,当然还包括倒排列表可以压缩。像位图和签名文件就没有rank排序和压缩的优势。

2、方便查询结果处理,很容易实现布尔计算。现今的主流搜索引擎用的本质计算都属于布尔计算。如要查询包含A和B的文档,其实质是先找出包含A的文档列表,再找出包含B的文档列表,最后把那些既在列表A又在列表B的文档作为结果返回。其实质就是进行一个合取查询操作。

 

下面就以简单的程序方式来说明到排索引(基于内存的索引)的原理:

[java] view plain copy
  1. import java.util.ArrayList;  
  2. import java.util.HashMap;  
  3. import java.util.List;  
  4. import java.util.Map;  
  5. import java.util.Scanner;  
  6. import java.util.Set;  
  7. import java.util.TreeSet;  
  8. import java.util.regex.Matcher;  
  9. import java.util.regex.Pattern;  
  10. public class ReverseIndex {       
  11.       
  12.     /** 
  13.      * 字典.<术语,倒排列表> 
  14.      */  
  15.     private static Map<String,Set<Node>> dictionary = new HashMap<String,Set<Node>>();  
  16.       
  17.     private static Pattern extraPattern = Pattern.compile("(//w+)");  
  18.       
  19.     public void addTerm(String term){  
  20.         if (term != null) {  
  21.             term = term.trim().toLowerCase(); //大小写折叠  
  22.             if (! dictionary.containsKey(term)) {  
  23.                 dictionary.put(term, new TreeSet<Node>());  
  24.             }  
  25.         }  
  26.     }  
  27.       
  28.     //建立倒排列表  
  29.     private void index(String term,Integer doc,int f){  
  30.         term = term.toLowerCase();  
  31.         Set<Node> reverseList = dictionary.get(term);  
  32.         if(reverseList !=null){  
  33.             reverseList.add(new Node(doc,f));  
  34.         }         
  35.     }  
  36.       
  37.     /** 
  38.      * 这一步其实属于关键词抽取,在搜索引擎中由专门的抽取程序处理。 
  39.      * @param txt 要建索引的文档 
  40.      * @param doc 文档编号 
  41.      */  
  42.     public void buildIndex(String txt,Integer doc){  
  43.         if(txt == null)  
  44.             return;  
  45.         Matcher m = extraPattern.matcher(txt);  
  46.         Map<String,Integer> map = new HashMap<String, Integer>();  
  47.         while(m.find()){              
  48.             //算没个词条的频率,准备使用坐标匹配的rank  
  49.             String t = m.group(1);  
  50.             if(! map.containsKey(t))   
  51.                 map.put(t, 1);  
  52.             else  
  53.                 map.put(t, map.get(t) + 1);  
  54.         }         
  55.         for (Map.Entry<String, Integer> entry : map.entrySet()) {  
  56.             index(entry.getKey(),doc,entry.getValue());  
  57.         }  
  58.     }     
  59.       
  60.     /* 
  61.      * 对查询结果进行布尔查询中的合取操作 
  62.      */  
  63.     private Set<Node> mergeResult(List<Set<Node>> queryResult){  
  64.         if(queryResult == null || queryResult.size() == 0){  
  65.             return new TreeSet<Node>();  
  66.         }  
  67.           
  68.         Set<Node> min = null;  
  69.         for (Set<Node> set : queryResult) {  
  70.             //选择元素最少的查询列表,这样可以减少合取时计算量  
  71.             if(min == null){  
  72.                 min = set;  
  73.             }else if(min.size() > set.size()){  
  74.                 min = set;  
  75.             }  
  76.         }  
  77.           
  78.         Set<Node> ret = new TreeSet<Node>();  
  79.         for (Node n : min) {  
  80.             ret.add(n);  
  81.         }  
  82.           
  83.         for (Node n : min) {  
  84.             for (Set<Node> set : queryResult) {  
  85.                 if(min == set){  
  86.                     continue;  
  87.                 }else if(! set.contains(n)){  
  88.                     ret.remove(n); //如果在此查询词中不包括文档号,则直接删除此文档  
  89.                     break;  
  90.                 }  
  91.             }  
  92.         }  
  93.                   
  94.         return ret;  
  95.           
  96.           
  97.     }  
  98.       
  99.     /** 
  100.      * 查询包含查询术语query的文档 
  101.      * @param query  
  102.      * @return 文档列表 
  103.      */  
  104.     public Node[] retrieve(String query){  
  105.         if(query ==null)  
  106.             return new Node[0];  
  107.         query = query.trim().toLowerCase();  
  108.         if(query.length() ==0)  
  109.             return new Node[0];  
  110.         String[] terms = query.split("//s+");  
  111.         List<Set<Node>> queryResults = new ArrayList<Set<Node>>();  
  112.         for (String t : terms) {  
  113.             Set<Node> reverseList = dictionary.get(t);  
  114.             if (reverseList != null) {  
  115.                 queryResults.add(reverseList);  
  116.             }  
  117.         }  
  118.         Set<Node> result = mergeResult(queryResults);  
  119.           
  120.         return result.toArray(new Node[result.size()]);  
  121.           
  122.     }  
  123.       
  124.     /** 
  125.      * @param args 
  126.      */  
  127.     public static void main(String[] args) {  
  128.         String books = "This distribution includes cryptographic software.  The country in   /n"  
  129.         + "which you currently reside may have restrictions on the import,       /n"   
  130.         + "possession, use, and/or re-export to another country, of              /n"   
  131.         + "encryption software.  BEFORE using any encryption software, please    /n"   
  132.         + "check your country's laws, regulations and policies concerning the    /n"   
  133.         + "import, possession, or use, and re-export of encryption software, to  /n"   
  134.         + "see if this is permitted.  See <http://www.wassenaar.org/> for more   /n"   
  135.         + "information.                                                          /n"   
  136.         + "The U.S. Government Department of Commerce, Bureau of Industry and    /n"   
  137.         + "Security (BIS), has classified this software as Export Commodity      /n"   
  138.         + "Control Number (ECCN) 5D002.C.1, which includes information security  /n"   
  139.         + "software using or performing cryptographic functions with asymmetric  /n"   
  140.         + "algorithms.  The form and manner of this Apache Software Foundation   /n"   
  141.         + "distribution makes it eligible for export under the License Exception /n"   
  142.         + "ENC Technology Software Unrestricted (TSU) exception (see the BIS     /n"   
  143.         + "Export Administration Regulations, Section 740.13) for both object    /n"   
  144.         + "code and source code.                                                 /n"   
  145.         + "The following provides more details on the included cryptographic     /n"   
  146.         + "software:                                                             /n"   
  147.         + "  Hadoop Core uses the SSL libraries from the Jetty project written   /n"   
  148.         + "by mortbay.org.                                                       /n" ;  
  149.           
  150.         /* 
  151.          * 要建索引的文档,每一行为一个文档 
  152.          */  
  153.         String[] docs = books.split("/n");   
  154.         //字典文件  
  155.         String[] dics = "the provides details Commerce Security Commodity Hadoop libraries TSU laws distribution software country reside import possession".split("//s+");  
  156.           
  157.         ReverseIndex index = new ReverseIndex();  
  158.           
  159.         for (String term : dics) {  
  160.             //构建字典  
  161.             index.addTerm(term);   
  162.         }  
  163.         int sno = 0;  
  164.         for(String doc:docs){  
  165.             //建立索引  
  166.             index.buildIndex(doc, sno++);  
  167.         }  
  168.           
  169.         System.err.println("please type query words:");  
  170.         while(true){  
  171.             Scanner in = new Scanner(System.in);  
  172.             String query = in.nextLine();  
  173.             if("exit".equalsIgnoreCase(query))  
  174.                 break;  
  175.             System.out.println("query:"+query);  
  176.             Node[] dosList = index.retrieve(query);  
  177.             if(dosList.length ==0 )  
  178.             {  
  179.                 System.out.printf("there are not docs relate to query words '%s'",query);  
  180.                 continue;  
  181.             }  
  182.               
  183.             for(Node dc:dosList){  
  184.                 System.out.printf("[%d,%d] %s/n", dc.doc, dc.frequency, docs[dc.doc]);  
  185.                   
  186.             }  
  187.               
  188.         }  
  189.           
  190.     }  
  191.     static class Node implements Comparable<Node>{  
  192.         private Integer doc;  
  193.         private int frequency;  
  194.         @Override  
  195.         public int compareTo(Node o) {            
  196.             return o.doc - doc ;  
  197.         }  
  198.           
  199.         public Node(int doc,int f){  
  200.             this.doc = doc;  
  201.             this.frequency = f;  
  202.         }  
  203.         public Integer getDoc() {  
  204.             return doc;  
  205.         }  
  206.         public int getFrequency() {  
  207.             return frequency;  
  208.         }  
  209.           
  210.     }  
  211. }  

 

说明:

 1、索引必须要有一个字典存在,字典就是一些术语的集合.如dog、cat、hotel、beijing .....索引引擎中的字典一般来自公共知识库,商场、娱乐新闻、电影简报等等。

2、索引以索引文件的形式存在磁盘上,在使用的时候加载进内存,或者部分加载进内存,大多数情况下内存不够存放所有的索引,所以有时候索引会进行压 缩存储,字典文件和倒排列表都有相应的压缩方式。如字典中常用的压缩有前缀压缩、最小完美hash、基于磁盘的字典等;到排列表压缩有:一元编码等

3、这里给出的索引方式为基于内存的索引,在索引数据量大时不适用。当然这里把它放在内存是没问题,毕竟才一篇文章,文章的每一行作为一个文档对待。

4、真正的搜索引擎当到索引这一步时所有的数据都已经就绪,关键词的抽取(extract)、术语的权重等等。

你可能感兴趣的:(搜索引擎,索引)