全文检索引擎lucene

全文检索引擎的开放源代码Lucene

 2010-10-25

Lucene 是一个开放源代码、高性能的Java 全文检索工具包。

1 全文检索引擎lucene

1 .1 Lucene 简介

Lucene 是apache 软件基金会jakarta 项目组的一个子项目,是一个开放源代码的全文检索引擎工具包,即它不是一个的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,它为数据访问和管理提供了简单的函数调用接口,可以方便的嵌入到各种应用中实现针对应用的全文索引/检索功能。Lucene 的APl 接口设计得比较通用,输入输出结构都很像数据库的表、记录和字段,所以很多传统的应用的文件、数据库等都可以比较方便的映射至lJLucene 的存储结构或接口中。总体上看,可以先把Lucene 当成一个支持全文索引的数据库系统。

 

Lucene 的原作者是Doug Cutting ,他是一位资深全文索引/ 检索专家,曾经是V-Twin 搜索引擎[6] 的主要

开发者,后在Excite[7] 担任高级系统架构设计师,目前从事于一些Internet 底层架构的研究。早先发布

在作者自己的 http://www.lucene.com/ ,后来发布在 SourceForge [8] ,2001 年年底成为apache 软件基金

会jakarta 的一个子项目: http://jakarta.apache.org/lucene/ 

 

Lucene 目前是 Apache Jakarta 家族中的一个开源项目,下载 http://lucene.apache.org/

 

 

1 .2 Lucene 系统结构

Lucene 作为~个优秀的全文检索引擎,其系统结构运用了大量的面向对象的设计思想。首先是定义了一个与平台无关的索引文件格式,其次通过抽象将系统的核心组成部分设

计为抽象类,具体的平台部分设计为抽象类,此外与具体平台相关的部分比如文件存储也封装为类,经过层层的面向对象编程的处理,最终达成了一个低耦合、高效率、容易二次开发

等的检索引擎系统。Lucene 系统其结构如图1 所示。可以从图1 清楚地看到,Lucene 系统是由基础结构封装、索引核心、对外接口3 大部分组成。其中直接操作索引文件的索引核心又是系统的重点,索引的最后结果就是产生许多的索引文件,这些索引文件构成索引库。

Lucene 系统将所有源码分为了7 个模块( 在Java 语言中以包即package 来表示) ,各个模块( 包) 完成特定的功能。在Lucene 系统的这7 个包中,核心类包主要有3 个(org .apache .1ucene .analysis 、org .apache.1ucene .index 、org .apache .1ucene .search) :

(1)org .apache .1ucene .analysis 该模块主要用于切分词。切分词的工作由Analyzer 的扩展类来实现,Lucene 自带了Stan .dardAnalyzer 类,我们可以参照该类的实现写出自己的切分词分析器类,如中文分析器等。

(2)org .apache .1ucene .index 该模块主要提供库的读写接口。通过该包可以创建库、添加删除记录及读取记录等。

(3)org .apache .hcene .search 该模块主要提供了检索接口。通过该包,我们可以输入条件,得到查询结果集,与org .apache .1ucene .queryParser 包配合还可以自定义查询规则,像google 一样支持查询条件问的与、或、非、属于等复合查询。

 

1 .3 Lucene 程序运行机制

Lucene 系统,其功能强大,实现复杂,但从根本上说,主要包括两个主要功能:一是建立索引库,也就是将待索引的纯文本内容经切分词后的索引入库;二是检索索引库,即根据查询

条件从索引库中找出符合条件的文档。

1 .3 .1 建立索引逻辑库

建立索引逻辑库按先后顺序,其查询逻辑如下:

首先,建库者定义入库文档的结构,比如需要把网站内容加载到全文检索库,让用户通过“站内检索”搜索到相关的网页内容。入库文档结构与关系型数据库中表的行结构类似,

每个入库的文档由多个域构成,假设这里需要入库的网站内容包括如下项目:文章标题、作者、发布时间、原文链接、正文内容( 一般作为网页快照) ,那么每个网页的这些项目可以作为文档的域,共有5 个域。

   其次,对于文档中需要切分词的域,系统使用语言解析器(hcene .analysis) 对其切分词,形成一个个Token,Token 是Lucene 内部所使用的概念,是对传统文字中的词的概念的抽象,

也是Lucene 在建立索引时直接处理的最小单位;简单的讲Token 就是一个词和所在域值的组合。

   最后,切分后的Token 通过索引器(1ucene .index) 的处理,最终添加( 插入) 到索引库中,存储器(1ucene.store) 负责数据存储管理,主要包括一些底层的I /O 操作。

1 .3 .2 检索索引逻辑库

建立检索索引逻辑库按先后顺序,查询逻辑如下:

首先,输入查询条件,比如用户希望查询到含有词“china ”

和“panda ”但不含词“cat ”的记录,那么输入条件为“china+panda-cat ”;查询条件传入搜索器(1ucene.search) ,搜索器里有一个查询解析器(incene .queryParser) ,搜索器调用这个查询解析器来解析查询条件。

其次,查询条件“china+panda-cat" 被传到查询解析器中,解析器将对“china+panda-cat" 进行分析,首先分析器解析字符串的连接符,即加号和减号,然后调用语言解析器(1ucene .analysis) 对每个词进行切词,一般英文将按空格来切词,最后得到的查询条件表示为:“china"AND “panda"AND NOT “cat ”。

最后,查询器根据这个条件去检索事先已建立好的索引库,得到查询结果,并返回结果集lucene .search.Hits ,Hits 类似于JDBC 中的ResultSet 。

2 基于Lucene 的搜索引擎设计与实现

下面介绍一个采用Lucene 全文检索技术实现的一个校园网站内搜索引擎,通过介绍该搜索引擎的设计与实现来展示如何运用Lucene 。

2 .1 系统设计要求

一个校园网站的搜索引擎应该满足下列要求:①以校园网为搜索目标,用户可以通过该系统检索校园网站上所有静态网页的内容和大多数动态网页的内容,提供基于Web 的查

询接口;②具有较高的查询准确率和较快的响应速度。

2 .2 系统设计与实现

基于Lucene 全文检索搜索引擎的系统结构图如图2 所示。

上述系统分两大部分:创建维护索引与检索索引,本文将主要介绍与Lucene 相关的设计与实现,由于Lucene不是一个完整的全文检索引擎,而是一个全文检索引擎的工具

包,所以需要利用工具包提供的类,并对其扩展来实现具体的应用。

2 .2 .1 建立索引库

建立索引库就是往索引库添加一条条索引记录,Lucene 为添加一条索引记录提供了接口,添加索引。

主要用到了“写索引器”、“文档”、“域”这3 个类。

要建立索引,首先要构造一个Document 文档对象,确定Document 的各个域,这类似于关系型数据库中表结构的建立,Document 相当于表中的一个记录行,域相当于一行中的列,在

Lucene 中针对不同域的属性和数据输出的需求,对域还可以选择不同的索引/存储字段规则,在本系统中,网页标题、内容、URL 和建立日期作为Document 的域。IndexWriter 负责接收新加入的文档,并写入索引库中。在创建“写索引器”IndexWriter 时需要指定所使用的语言

分析器,Lucene 从1 .3 版开始支持中文,它通过个包org .apache .1ucene .analysis .standard 来支持中文,它对汉字采用单字切分,这种切分方法实现简单,但检索准确率不高,对于本系统来讲足够了,当然也可以通过扩展Analyzer 类来开发自己的语言所在的目录,IndexSearcher 有一个search 方法执行索引的检索,这个方法接受Query 作为参数,返回Hits ,Hists 是一系列排好序的查询结果的集合,集合的元素是Document 。通过Document 的get 方法可以得到与这个文档对应网页的信息,比如:网页标题,URL 等。

这些信息经过处理显示给用户。

下面是索引检索的示例代码片断:

……

Searcher searcher=new IndexSearcher( ”E :/index ”) :

Analyzer analyzer=new SimpleAnalyzer0 ;

Query query=QueryParser .parse(1ine ,”Fulllndex",analyzer) ;

Hits hits=searcher .search(query) ;

for(int i-O ;i<hits .1ength0 ;i++){

Document doe=hits .doe(i) ;

String path=doe .get( ”Ud ”) ;

String title=doe .get( ”Title ”) ;

String content--doe .get( ”Content ”) :

String pubTime=doc .get( ”PubTime ”) ;

……

2 .3 系统特点

本系统在开放源码工具包Lucene 的基础上,根据系统特点和要求,按照Lucene 的框架规范,扩展Lucene 的功能,将Lucene 很好地嵌入到搜索引擎中。系统具有Lucene 的技术特点,全文检索效率高,查全率和准确率都达到了设计的要求,系统运行稳定,查询精确,支持跨平台,多用户。

 

 

 

Lucene 作为一个全文检索引擎,其具有如下突出的 优点 :

(1 )索引文件格式独立于应用平台。Lucene 定义了一套以8 位字节为基础的索引文件格式,使得兼容系

统或者不同平台的应用能够共享建立的索引文件。

(2 )在传统全文检索引擎的倒排索引的基础上,实现了分块索引,能够针对新的文件建立小文件索引,提

升索引速度。然后通过与原有索引的合并,达到优化的目的。

(3 )优秀的面向对象的系统架构,使得对于Lucene 扩展的学习难度降低,方便扩充新功能。

(4 )设计了独立于语言和文件格式的文本分析接口,索引器通过接受Token 流完成索引文件的创立,用户

扩展新的语言和文件格式,只需要实现文本分析的接口。

(5 )已经默认实现了一套强大的查询引擎,用户无需自己编写代码即使系统可获得强大的查询能力,Luc

ene 的查询实现中默认实现了布尔操作、模糊查询(Fuzzy Search[11] )、分组查询等等。

 

表 3.2 基础类包org.apache.lucene.util

说明

Arrays

一个关于数组的排序方法的静态类,提供了优化的基于快排序的排序方法sort

BitVector

C/C++ 语言中位域的java 实现品,但是加入了序列化能力

Constants

常量静态类,定义了一些常量

PriorityQueue

一个优先队列的抽象类,用于后面实现各种具体的优先队列,提供常数时间内的最小元素访问能力,内部

实现机制是哈析表和堆排序算法

表 3.3 基础类包org.apache.lucene.document

说明

Document

是文档概念的一个实现类,每个文档包含了一个域表(fieldList ),并提供了一些实用的方法,比如多

种添加域的方法、返回域表的迭代器的方法

Field

是域概念的一个实现类,每个域包含了一个域名和一个值,以及一些相关的属性

DateField

提供了一些辅助方法的静态类,这些方法将java 中Date 和Time 数据类型和String 相互转化

总的来说,这两个基础类包中含有的类都比较简单,通过阅读源代码,可以很容易的理解,因此这里不作

过多的展开。

 

 

Lucence API 介绍

org.apache.Lucene.search 搜索入口

org.apache.Lucene.index  索引入口

org.apache.Lucene.analysis 语言分析器

org.apache.Lucene.queryParser 查询分析器

org.apache.Lucene.document 存储结构

org.apache.Lucene.store  底层IO/ 存储结构

org.apache.Lucene.util 一些公用的数据结构

 

 

全文检索常见问题

•    文档域的存储设置

•    分词分析器个性化实现

•    文档检索高亮显示查询过程

•    查询分页、排序

•    索引建立过程异常处理

•    部分搜索结果搜索不到

索引内容的删除、更新

中文分词处理

文章相关内容计算法

分布式全文检索

不能及时更新索引时与数据库内容一致方法

高级查询:通配符、模糊、间距查询、范围查询、权重查询、组合条件、转义字符

•    英文检索:单词缩为词根形式 steamming   lemmatization 两个过程需要建立对应的分词器

•    Linux Window 平台差异

•    Nutch

•    不同版本的Lucene 之间API 有较大差异

•    是否要转向Sphinx

 

 

 

 

简单实例(一)

说明一下 , 这一篇文章的用到的 lucene, 是用 2.0 版本的 , 主要在查询的时候 2.0 版本的 lucene 与以前的版本有了一些区别  
其实这一些代码都是早几个月写的 , 自己很懒 , 所以到今天才写到自己的博客上 , 高深的文章自己写不了,只能记录下一些简单的记录与点滴,其中的代码算是自娱自乐的,希望高手不要把重构之类的砸下来 ...  

1 、在 windows 系统下的的 C 盘,建一个名叫 s 的文件夹 , 在该文件夹里面随便建三个 txt 文件,随便起名啦,就叫 "1.txt","2.txt" "3.txt"    
其中 1.txt 的内容如下:  

Java 代码  

1.  中华人民共和国   

2.  全国人民   

3.  2006    


 "2.txt"  "3.txt" 的内容也可以随便写几写,这里懒写,就复制一个和 1.txt 文件的内容一样吧   

2 、下载 lucene 包,放在 classpath 路径中   
建立索引 

Java 代码  

1.  package  lighter.javaeye.com;  

2.    

3.  import  java.io.BufferedReader;  

4.  import  java.io.File;  

5.  import  java.io.FileInputStream;  

6.  import  java.io.IOException;  

7.  import  java.io.InputStreamReader;  

8.  import  java.util.Date;  

9.    

10. import  org.apache.lucene.analysis.Analyzer;  

11. import  org.apache.lucene.analysis.standard.StandardAnalyzer;  

12. import  org.apache.lucene.document.Document;  

13. import  org.apache.lucene.document.Field;  

14. import  org.apache.lucene.index.IndexWriter;  

15.   

16. /**  

17.  * author lighter date 2006-8-7  

18.  */   

19. public   class  TextFileIndexer {  

20.      public   static   void  main(String[] args)  throws  Exception {  

21.          /*  指明要索引文件夹的位置 , 这里是 C 盘的 S 文件夹下  */   

22.         File fileDir =  new  File( "c://s" );  

23.   

24.          /*  这里放索引文件的位置  */   

25.         File indexDir =  new  File( "c://index" );  

26.         Analyzer luceneAnalyzer =  new  StandardAnalyzer();  

27.         IndexWriter indexWriter =  new  IndexWriter(indexDir, luceneAnalyzer,  

28.                  true );  

29.         File[] textFiles = fileDir.listFiles();  

30.          long  startTime =  new  Date().getTime();  

31.           

32.          // 增加 document 到索引去   

33.          for  ( int  i =  0 ; i < textFiles.length; i++) {  

34.              if  (textFiles[i].isFile()  

35.                     && textFiles[i].getName().endsWith( ".txt" )) {  

36.                 System.out.println( "File "  + textFiles[i].getCanonicalPath()  

37.                         +  " 正在被索引 ...." );  

38.                 String temp = FileReaderAll(textFiles[i].getCanonicalPath(),  

39.                          "GBK" );  

40.                 System.out.println(temp);  

41.                 Document document =  new  Document();  

42.                 Field FieldPath =  new  Field( "path" , textFiles[i].getPath(),  

43.                         Field.Store.YES, Field.Index.NO);  

44.                 Field FieldBody =  new  Field( "body" , temp, Field.Store.YES,  

45.                         Field.Index.TOKENIZED,  

46.                         Field.TermVector.WITH_POSITIONS_OFFSETS);  

47.                 document.add(FieldPath);  

48.                 document.add(FieldBody);  

49.                 indexWriter.addDocument(document);  

50.             }  

51.         }  

52.          //optimize() 方法是对索引进行优化   

53.         indexWriter.optimize();  

54.         indexWriter.close();  

55.           

56.          // 测试一下索引的时间   

57.          long  endTime =  new  Date().getTime();  

58.         System.out  

59.                 .println( " 这花费了 "   

60.                         + (endTime - startTime)  

61.                         +   毫秒来把文档增加到索引里面去 !"   

62.                         + fileDir.getPath());  

63.     }  

64.   

65.      public   static  String FileReaderAll(String FileName, String charset)  

66.              throws  IOException {  

67.         BufferedReader reader =  new  BufferedReader( new  InputStreamReader(  

68.                  new  FileInputStream(FileName), charset));  

69.         String line =  new  String();  

70.         String temp =  new  String();  

71.           

72.          while  ((line = reader.readLine()) !=  null ) {  

73.             temp += line;  

74.         }  

75.         reader.close();  

76.          return  temp;  

77.     }  

78. }  



索引的结果:  

Java 代码  

1.  File C:/s/ 1 .txt 正在被索引 ....  

2.  中华人民共和国全国人民 2006    

3.  File C:/s/ 2 .txt 正在被索引 ....  

4.  中华人民共和国全国人民 2006    

5.  File C:/s/ 3 .txt 正在被索引 ....  

6.  中华人民共和国全国人民 2006    

7.  这花费了 297   毫秒来把文档增加到索引里面去 !c:/s  



3 、建立了索引之后,查询啦 .... 

Java 代码  

1.  package  lighter.javaeye.com;  

2.    

3.  import  java.io.IOException;  

4.    

5.  import  org.apache.lucene.analysis.Analyzer;  

6.  import  org.apache.lucene.analysis.standard.StandardAnalyzer;  

7.  import  org.apache.lucene.queryParser.ParseException;  

8.  import  org.apache.lucene.queryParser.QueryParser;  

9.  import  org.apache.lucene.search.Hits;  

10. import  org.apache.lucene.search.IndexSearcher;  

11. import  org.apache.lucene.search.Query;  

12.   

13. public   class  TestQuery {  

14.      public   static   void  main(String[] args)  throws  IOException, ParseException {  

15.         Hits hits =  null ;  

16.         String queryString =  " 中华 " ;  

17.         Query query =  null ;  

18.         IndexSearcher searcher =  new  IndexSearcher( "c://index" );  

19.   

20.         Analyzer analyzer =  new  StandardAnalyzer();  

21.          try  {  

22.             QueryParser qp =  new  QueryParser( "body" , analyzer);  

23.             query = qp.parse(queryString);  

24.         }  catch  (ParseException e) {  

25.         }  

26.          if  (searcher !=  null ) {  

27.             hits = searcher.search(query);  

28.              if  (hits.length() >  0 ) {  

29.                 System.out.println( " 找到 :"  + hits.length() +   个结果 !" );  

30.             }  

31.         }  

32.     }  

33.   

34. }  



其运行结果:  

引用

找到 :3 个结果 !

简单实例(二)

写文章的时候 , 感觉比较难写的就是标题 , 有时候不知道起什么名字好 , 反正这里写的都是关于 lucene 的一些简单的实例 , 就随便起啦  

Lucene 其实很简单的 , 它最主要就是做两件事 : 建立索引和进行搜索   
来看一些在 lucene 中使用的术语 , 这里并不打算作详细的介绍 , 只是点一下而已 ---- 因为这一个世界有一种好东西,叫搜索。   

IndexWriter :lucene 中最重要的的类之一,它主要是用来将文档加入索引,同时控制索引过程中的一些参数使用。   

Analyzer : 分析器 , 主要用于分析搜索引擎遇到的各种文本。常用的有 StandardAnalyzer 分析器 ,StopAnalyzer 分析器,WhitespaceAnalyzer 分析器等。   

Directory : 索引存放的位置 ;lucene 提供了两种索引存放的位置,一种是磁盘,一种是内存。一般情况将索引放在磁盘上;相应地lucene 提供了 FSDirectory  RAMDirectory 两个类。   

Document : 文档 ;Document 相当于一个要进行索引的单元,任何可以想要被索引的文件都必须转化为 Document 对象才能进行索引。   

Field :字段。   

IndexSearcher :  lucene 中最基本的检索工具,所有的检索都会用到 IndexSearcher 工具  

Query : 查询, lucene 中支持模糊查询,语义查询,短语查询,组合查询等等 , 如有TermQuery,BooleanQuery,RangeQuery,WildcardQuery 等一些类。   

QueryParser 是一个解析用户输入的工具,可以通过扫描用户输入的字符串,生成 Query 对象。   

Hits : 在搜索完成之后,需要把搜索结果返回并显示给用户,只有这样才算是完成搜索的目的。在 lucene 中,搜索的结果的集合是用Hits 类的实例来表示的。   

上面作了一大堆名词解释,下面就看几个简单的实例吧  
1 、简单的的 StandardAnalyzer 测试例子  

Java 代码  

1.  package  lighter.javaeye.com;  

2.    

3.  import  java.io.IOException;  

4.  import  java.io.StringReader;  

5.    

6.  import  org.apache.lucene.analysis.Analyzer;  

7.  import  org.apache.lucene.analysis.Token;  

8.  import  org.apache.lucene.analysis.TokenStream;  

9.  import  org.apache.lucene.analysis.standard.StandardAnalyzer;  

10.   

11. public   class  StandardAnalyzerTest   

12. {  

13.      // 构造函数,   

14.      public  StandardAnalyzerTest()  

15.     {  

16.     }  

17.      public   static   void  main(String[] args)   

18.     {  

19.          // 生成一个 StandardAnalyzer 对象   

20.         Analyzer aAnalyzer =  new  StandardAnalyzer();  

21.          // 测试字符串   

22.         StringReader sr =  new  StringReader( "lighter javaeye com is the are on" );  

23.          // 生成 TokenStream 对象   

24.         TokenStream ts = aAnalyzer.tokenStream( "name" , sr);   

25.          try  {  

26.              int  i= 0 ;  

27.             Token t = ts.next();  

28.              while (t!= null )  

29.             {  

30.                  // 辅助输出时显示行号   

31.                 i++;  

32.                  // 输出处理后的字符   

33.                 System.out.println( "  " +i+ "  :" +t.termText());  

34.                  // 取得下一个字符   

35.                 t=ts.next();  

36.             }  

37.         }  catch  (IOException e) {  

38.             e.printStackTrace();  

39.         }  

40.     }  

41. }  


显示结果:  

引用

 1  :lighter   
 2  :javaeye   
 3  :com


提示一下:   
StandardAnalyzer  lucene 中内置的 " 标准分析器 ", 可以做如下功能  
1 、对原有句子按照空格进行了分词   
2 、所有的大写字母都可以能转换为小写的字母   
3 、可以去掉一些没有用处的单词,例如 "is","the","are" 等单词,也删除了所有的标点   
查看一下结果与 "new StringReader("lighter javaeye com is the are on")" 作一个比较就清楚明了。   
这里不对其 API 进行解释了,具体见 lucene 的官方文档。需要注意一点,这里的代码使用的是 lucene2  API ,与 1.43 版有一些明显的差别。   

2 、看另一个实例 , 简单地建立索引,进行搜索  

Java 代码  

1.  package  lighter.javaeye.com;  

2.  import  org.apache.lucene.analysis.standard.StandardAnalyzer;  

3.  import  org.apache.lucene.document.Document;  

4.  import  org.apache.lucene.document.Field;  

5.  import  org.apache.lucene.index.IndexWriter;  

6.  import  org.apache.lucene.queryParser.QueryParser;  

7.  import  org.apache.lucene.search.Hits;  

8.  import  org.apache.lucene.search.IndexSearcher;  

9.  import  org.apache.lucene.search.Query;  

10. import  org.apache.lucene.store.FSDirectory;  

11.   

12. public   class  FSDirectoryTest {  

13.   

14.      // 建立索引的路径   

15.      public   static   final  String path =  "c://index2" ;  

16.   

17.      public   static   void  main(String[] args)  throws  Exception {  

18.         Document doc1 =  new  Document();  

19.         doc1.add(  new  Field( "name"  "lighter javaeye com",Field.Store.YES,Field.Index.TOKENIZED));  

20.   

21.         Document doc2 =  new  Document();  

22.         doc2.add( new  Field( "name"  "lighter blog" ,Field.Store.YES,Field.Index.TOKENIZED));  

23.   

24.         IndexWriter writer =  new  IndexWriter(FSDirectory.getDirectory(path,  true ),  new StandardAnalyzer(),  true );  

25.         writer.setMaxFieldLength( 3 );  

26.         writer.addDocument(doc1);  

27.         writer.setMaxFieldLength( 3 );  

28.         writer.addDocument(doc2);  

29.         writer.close();  

30.   

31.         IndexSearcher searcher =  new  IndexSearcher(path);  

32.         Hits hits =  null ;  

33.         Query query =  null ;  

34.         QueryParser qp =  new  QueryParser( "name" , new  StandardAnalyzer());  

35.           

36.         query = qp.parse( "lighter" );  

37.         hits = searcher.search(query);  

38.         System.out.println( " 查找 /"lighter/"   "  + hits.length() +  " 个结果 " );  

39.   

40.         query = qp.parse( "javaeye" );  

41.         hits = searcher.search(query);  

42.         System.out.println( " 查找 /"javaeye/"   "  + hits.length() +  " 个结果 " );  

43.   

44.     }  

45.   

46. }  


运行结果:  

Java 代码  

1.  查找 "lighter"    2 个结果   

2.  查找 "javaeye"    1 个结果   

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值