博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Lucene全文检索基础
阅读量:6284 次
发布时间:2019-06-22

本文共 17690 字,大约阅读时间需要 58 分钟。

---------------------------------------------------------------------------------------------------------------
[
版权申明:本文系作者原创,转载请注明出处]
文章出处:
作者:朱培     ID:sdksdk0

---------------------------------------------------------------------------------------------------------------

我提倡“少理论,少模型”,“多实践”,“多应用”的学习态度,今天带来的是关于站内搜索的基础使用,目前检索这个领域有非常多优秀的框架了,但是身为一个全文检索领域的经典祖先,我们还是需要了解和掌握其精华的。本文主要内容有Lucene简介、索引库的建立、关键字搜索、检索分页、网站排名优化、分词、搜索结果高亮等。要求掌握其基本开发流程,并可以使用servlet+easyUI+lucene+jsp+js等技术做一个简易的站内搜索的功能模块。

一、简介

1.1 简介

Lucene是apache软件基金会发布的一个开放源代码的全文检索引擎工具包,由资深全文检索专家Doug Cutting所撰写,它是一个全文检索引擎的架构,提供了完整的创建索引和查询索引,以及部分文本分析的引擎,Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便在目标系统中实现全文检索的功能,或者是以此为基础建立起完整的全文检索引擎,Lucene在全文检索领域是一个经典的祖先,现在很多检索引擎都是在其基础上创建的,思想是相通的。

即:Lucene是根据关健字来搜索的文本搜索工具,只能在某个网站内部搜索文本内容,不能跨网站搜索。

Lucene只能进行站内搜索。需要有索引库。

1.2 Lucene中存放的内容

Lucene中存的就是一系列的二进制压缩文件和一些控制文件,它们位于计算机的硬盘上,

这些内容统称为索引库,索引库有二部份组成:

(1)原始记录

存入到索引库中的原始文本,例如:传智是一家IT培训机构

(2)词汇表

按照一定的拆分策略(即分词器)将原始记录中的每个字符拆开后,存入一个供将来搜索的表

1.3 传统SQL的缺点

(1)SQL只能针对数据库表存储,不能直接针对硬盘上的文本搜索

(2)SQL没有相关度排名

(3)SQL搜索结果没有关健字高亮显示

(4)SQL需要数据库的支持,数据库本身需要内存开销较大,例如:Oracle

(5)SQL搜索有时较慢,尤其是数据库不在本地时,超慢,例如:Oracle

二、创建索引库

2.1 索引库创建

创建索引库:

1) 创建JavaBean对象

2) 创建Docment对象

3) 将JavaBean对象所有的属性值,均放到Document对象中去,属性名可以和JavaBean相同或不同

4) 创建IndexWriter对象

5) 将Document对象通过IndexWriter对象写入索引库中

6) 关闭IndexWriter对象

开发步骤:

1、新建一个javaweb工程,

  • 导入Lucene相关的jar包
  • lucene-core-3.0.2.jar【Lucene核心】
  • lucene-analyzers-3.0.2.jar【分词器】
  • lucene-highlighter-3.0.2.jar【Lucene会将搜索出来的字,高亮显示,提示用户】
  • lucene-memory-3.0.2.jar【索引库优化策略】

然后可以写javaBean对象,Article.java

private  Integer id;  //索引号	private  String title; //标题	private  String content;  //内容
get ,set方法,还有toString一下,有参和无参构造函数都要加上。

2、在demo1中邪一个方法,createIndexDB()

public void createIndexDB() throws IOException{		//创建article对象		Article  at=new Article(1, "lucene","Lucene是apache软件基金会发布的一个开放源代码的全文检索引擎工具包");		//创建Document对象		Document doc=new Document();		//将article对象中的3个属性分别绑定到document对象中				/*		 * 		 * 参数1:document对象中的属性名叫xid,article对象中的属性名叫id		 * 参数2:document对象中的xid的值,与article对象中相同		 * 参数3:是否将xid属性值存入词汇表,YES表示会存入,NO表示不会存入		 * 参数4:是否将xid属性值进行分词,ANALYZED表示会进行词汇拆分 ,NOT_ANALYZED不会进行拆分		 * 		 * 项目中提倡非id值进行词汇拆分,提倡非id值存入词汇表		 */				doc.add(new Field("xid",at.getId().toString(),Store.YES,Index.ANALYZED));		doc.add(new Field("xtitle",at.getTitle().toString(),Store.YES,Index.ANALYZED));		doc.add(new Field("xcontent",at.getContent().toString(),Store.YES,Index.ANALYZED));				//将document对象写入lucene索引库		/*		 * 参数1:lucene索引库最终对应于硬盘中的目录		 * 参数2:将文本拆分的策略,一个策略就是一个具体的实现类		 * 参数3:最多将文本拆分的词汇,LIMITED表示1万个,只取前1万个		 * 		 */		Directory d=FSDirectory.open(new File("D:/IndexDB"));		Analyzer a=new StandardAnalyzer(Version.LUCENE_30);		MaxFieldLength mfl=MaxFieldLength.LIMITED;				IndexWriter iw=new IndexWriter(d, a, mfl);		iw.addDocument(doc);		//关闭字符流对象		iw.close();			}

2.2工具类创建

我们发现以上方法创建索引比较麻烦,所以我们可以封装一下,新建一个工具类LuceneUtil.java,在这个类中把以下四个内容进行封装,各自取get,set方法。

private static Directory d;  //索引库存放的目录	private static Version version;  //版本号	private static Analyzer analyzer;  //词汇拆分的策略	private static  MaxFieldLength mfl;  //最大分词数

用静态块来防止外界new该帮助类;

//防止外界new该帮助类	static{		try {			d=FSDirectory.open(new File("D:/IndexDB"));			version=Version.LUCENE_30;			analyzer=new StandardAnalyzer(version);			mfl=MaxFieldLength.LIMITED;		} catch (IOException e) {			e.printStackTrace();			throw new RuntimeException(e);		}			}

将需要的对象进行封装:

//将javabean转成document对象	public static Document javabean2Document(Object obj) throws Exception{				Document document=new Document();				//获取obj引用的对象字节码		Class  clazz=obj.getClass();		java.lang.reflect.Field[] reflectFields=clazz.getDeclaredFields();		//迭代		for(java.lang.reflect.Field reflectField:reflectFields){			//反射			reflectField.setAccessible(true);			//获取属性名,			String name=reflectField.getName();			String methodName="get"+name.substring(0,1).toUpperCase()+name.substring(1);			//获取方法			Method method=clazz.getMethod(methodName, null);			//执行方法			String value=method.invoke(obj, null).toString();			//加入到document对象中去			document.add(new Field(name,value,Store.YES,Index.ANALYZED));								}		//返回document对象		return document;	}	//将document转成avabean对象	public static Object document2Javabean(Document document, Class clazz) throws InstantiationException, IllegalAccessException, InvocationTargetException{		Object obj=clazz.newInstance();				java.lang.reflect.Field[] reflectFields = clazz.getDeclaredFields();		for(java.lang.reflect.Field field : reflectFields){			field.setAccessible(true);			String name= field.getName();			String value = document.get(name);			BeanUtils.setProperty(obj,name,value);		}			return obj;		}

2.3 使用工具类对索引库的增删改

以上步骤都完成后,就可以直接用工具类来对索引库进行操作了

创建单个索引库

@Test	//创建索引库     public void createIndexDB() throws Exception{    	 Article  at=new Article(1, "lucene","Lucene是apache软件基金会发布的一个开放源代码的全文检索引擎工具包"); 		//创建Document对象 		Document doc=LuceneUtil.javabean2Document(at);    	IndexWriter iw=new IndexWriter(LuceneUtil.getD(), LuceneUtil.getAnalyzer(), LuceneUtil.getMfl()) ;    	iw.addDocument(doc);    	iw.close();     }
创建多个索引对象

@Test	public void addAll() throws Exception{		IndexWriter  iw=new IndexWriter(LuceneUtil.getD(), LuceneUtil.getAnalyzer(), LuceneUtil.getMfl()); 						Article  at1=new Article(1, "lucene","Lucene是apache软件基金会发布的一个开放源代码的全文检索引擎工具包");		Document document1=LuceneUtil.javabean2Document(at1);		iw.addDocument(document1);				Article  at2=new Article(2, "lucene","Lucene是根据关健字来搜索的文本搜索工具");		Document document2=LuceneUtil.javabean2Document(at2);		iw.addDocument(document2);				Article  at3=new Article(3, "lucene","Lucene在全文检索领域是一个经典的祖先,现在很多检索引擎都是在其基础上创建的,思想是相通的");		Document document3=LuceneUtil.javabean2Document(at3);		iw.addDocument(document3);				Article  at4=new Article(4, "lucene","Lucene它是一个全文检索引擎的架构,提供了完整的创建索引和查询索引");		Document document4=LuceneUtil.javabean2Document(at4);		iw.addDocument(document4);				iw.close();			}
修改索引、

@Test	public void update() throws Exception{		Article  newArticle=new Article(4, "lucene","4是一个全文检索引擎的架构,提供了完整的创建索引和查询索引");		Document document=LuceneUtil.javabean2Document(newArticle);		IndexWriter  iw=new IndexWriter(LuceneUtil.getD(), LuceneUtil.getAnalyzer(), LuceneUtil.getMfl()); 				iw.updateDocument(new Term("id","4"), document);		iw.close();	}
查询:

@Test	public void findAll() throws Exception{		String keywords="是";		List
atl=new ArrayList
(); QueryParser queryParser=new QueryParser(LuceneUtil.getVersion(),"content",LuceneUtil.getAnalyzer()); Query query=queryParser.parse(keywords); IndexSearcher is=new IndexSearcher(LuceneUtil.getD()); TopDocs td=is.search(query,100); for(int i=0;i
删除

@Test	public void delete() throws  LockObtainFailedException, IOException{		IndexWriter  iw=new IndexWriter(LuceneUtil.getD(), LuceneUtil.getAnalyzer(), LuceneUtil.getMfl()); 				iw.deleteDocuments(new Term("id","2"));		iw.close();	}			public void deleteAll() throws CorruptIndexException, LockObtainFailedException, IOException{		IndexWriter indexWriter = new IndexWriter(LuceneUtil.getD(),LuceneUtil.getAnalyzer(),LuceneUtil.getMfl());		indexWriter.deleteAll();		indexWriter.close();	}

三、关键字搜索

根据关键字查询索引库中的内容:

1) 创建IndexSearcher对象

2) 创建QueryParser对象

3) 创建Query对象来封装关键字

4) 用IndexSearcher对象去索引库中查询符合条件的前100条记录,不足100条记录的以实际为准

5) 获取符合条件的编号

6) 用indexSearcher对象去索引库中查询编号对应的Document对象

7) 将Document对象中的所有属性取出,再封装回JavaBean对象中去,并加入到集合中保存,以备将之用

传统的搜索方式:
/**	 * 根据关键字从索引库中搜索符合条件的记录	 * 	 * @throws IOException 	 * @throws ParseException 	 * 	 */	@Test	public void findIndexDB() throws IOException, ParseException{		//搜索"源"这个字		String keyword="apache";		List
atl=new ArrayList
(); Directory d=FSDirectory.open(new File("D:/IndexDB")); Analyzer a=new StandardAnalyzer(Version.LUCENE_30); MaxFieldLength mfl=MaxFieldLength.LIMITED; //创建IndexSearch字符流对象 IndexSearcher is=new IndexSearcher(d); /* * 参数1:使用分词器的版本,qitqt * 参数2:针对document对象中的哪个属性进行搜索 */ Version version=Version.LUCENE_30; Analyzer analyzer=new StandardAnalyzer(version); QueryParser queryParser=new QueryParser(version,"xcontent",analyzer); //封装查询关键字 Query query=queryParser.parse(keyword); /* * 参数1:查询对象及封装关键字的对象 * 参数2:MAX_RECORD表示如果根据关键字搜索出来的内容教多,只取前MAX_RECORD个 * */ int MAX_RECORD=100; TopDocs td=is.search(query,MAX_RECORD); //迭代词汇表中符合条件的编号 for(int i=0;i
使用工具类:其实就是前面那一节里面的查询。
//根据关键字查询	@Test	public void findIndexDB() throws Exception {		String keywords="是";		List
atl=new ArrayList
(); QueryParser queryParser=new QueryParser(LuceneUtil.getVersion(),"content",LuceneUtil.getAnalyzer()); Query query=queryParser.parse(keywords); IndexSearcher is=new IndexSearcher(LuceneUtil.getD()); TopDocs td=is.search(query,100); for(int i=0;i

单字段和多字段的搜索

单字段:

QueryParser queryParser = new QueryParser(LuceneUtil.getVersion(),"content",LuceneUtil.getAnalyzer());

多字段

QueryParser queryParser = new MultiF

搜索结果高亮显示

//格式对象    Formatter formatter = new SimpleHTMLFormatter("","");    //关键字对象    Scorer scorer = new QueryScorer(query);    //高亮对象    Highlighter highlighter = new Highlighter(formatter,scorer);

四、分词器

采用一种算法,将中英文本中的字符拆分开来,形成词汇,以待用户输入关健字后搜索

使用分词器的原因

因为用户输入的搜索的内容是一段文本中的一个关健字,和原始表中的内容有差别, 但作为搜索引擎来讲,又得将相关的内容搜索出来,此时就得采用分词器来最大限度 匹配原始表中的内容

分词器工作流程

步一:按分词器拆分出词汇 步二:去除停用词和禁用词 步三:如果有英文,把英文字母转为小写,即搜索不分大小写

//测试Lucene内置和第三方分词器的分词效果public class TestAnalyzer {		private static void testAnalyzer(Analyzer analyzer, String text) throws Exception {		System.out.println("当前使用的分词器:" + analyzer.getClass());		TokenStream tokenStream = analyzer.tokenStream("content",new StringReader(text));		tokenStream.addAttribute(TermAttribute.class);		while (tokenStream.incrementToken()) {			TermAttribute termAttribute = tokenStream.getAttribute(TermAttribute.class);			System.out.println(termAttribute.term());		}	}	public static void main(String[] args) throws Exception {		testAnalyzer(new StandardAnalyzer(LuceneUtil.getVersion()), "采用一种算法aa,将中英文本中的字符拆分开来,形成词汇,以待用户输入关健字后搜索");		testAnalyzer(new FrenchAnalyzer(LuceneUtil.getVersion()), "采用一种算法aa,将中英文本中的字符拆分开来,形成词汇,以待用户输入关健字后搜索");		testAnalyzer(new CJKAnalyzer(LuceneUtil.getVersion()), "采用一种算法aa,将中英文本中的字符拆分开来,形成词汇,以待用户输入关健字后搜索");		testAnalyzer(new IKAnalyzer(), "指令汇科技实业呀");			}	}

IK分词器

testAnalyzer(new IKAnalyzer(), "指令汇科技实业呀");

导入IKAnalyzer分词器核心jar包,IKAnalyzer3.2.0Stable.jar 

将IKAnalyzer.cfg.xml和stopword.dic和mydict.dic文件复制到MyEclipse的src目录下, 再进行配置,在配置时,首行需要一个空行。

IK Analyzer 扩展配置
/mydict.dic
/surname.dic

mydict.dic内容如下:首行空出来:
指令汇科技
stopword.dic内容如下,首行空出来

五、搜索排名优化

5.1 索引库优化

//设置合并因子,即满足3个cfs文件合并 iw.addDocument(doc); iw.setMergeFactor(3);

索引库 内存索引库 硬盘索引库

//合并.cfs文件,解决文件的大小和数量问题	@Test	public void type1() throws Exception{		Article  at=new Article(1, "lucene","Lucene是apache软件基金会发布的一个开放源代码的全文检索引擎工具包",10);		//创建Document对象		Document doc=LuceneUtil.javabean2Document(at);		IndexWriter  iw=new IndexWriter(LuceneUtil.getD(),LuceneUtil.getAnalyzer(),LuceneUtil.getMfl());		iw.optimize();		iw.close();	}		//合并.cfs文件,解决文件的大小和数量问题	@Test	public void type2() throws Exception{		Article  at=new Article(2, "lucene","Lucene是apache软件基金会发布的一个开放源代码的全文检索引擎工具包",10);		//创建Document对象		Document doc=LuceneUtil.javabean2Document(at);		IndexWriter  iw=new IndexWriter(LuceneUtil.getD(),LuceneUtil.getAnalyzer(),LuceneUtil.getMfl());				//设置合并因子,即满足3个cfs文件合并		iw.addDocument(doc);		iw.setMergeFactor(3);		iw.close();	}*/		//合并.cfs文件,解决文件的大小和数量问题	@Test	public void type4() throws Exception{		Article  at=new Article(2, "lucene","Lucene是apache软件基金会发布的一个开放源代码的全文检索引擎工具包",10);		Document doc=LuceneUtil.javabean2Document(at);				//硬盘索引库		Directory fsDirectory=FSDirectory.open(new File("D:/IndexDB"));						//内存索引库		Directory ramDirectory=new RAMDirectory(fsDirectory);				//指向硬盘索引库的字符流,true表示如果内存索引库中和硬盘索引库中有系统的document对象时,先删除硬盘索引库的值		IndexWriter fsIw=new IndexWriter(fsDirectory,LuceneUtil.getAnalyzer(),true,LuceneUtil.getMfl());				//指向内存索引库的字符流		IndexWriter ramIw=new IndexWriter(ramDirectory,LuceneUtil.getAnalyzer(),LuceneUtil.getMfl());				//将document对象写入内存索引库		ramIw.addDocument(doc);		ramIw.close();				//将内存索引库中所有的document对象同步到硬盘索引中		fsIw.addIndexesNoOptimize(ramDirectory);		fsIw.close();	}

5.2 相关度得分排序

Lucene中的显示结果次序与相关度得分有关 ScoreDoc.score; 默认情况下,Lucene是按相关度得分排序的,得分高排在前,得分低排在后 如果相关度得分相同,按插入索引库的先后次序排序。

//增加document对象索引库中	@Test	public void add() throws Exception{		//Article  at=new Article(1, "lucene","Lucene是apache软件基金会发布的一个开放源代码的全文检索引擎工具包",10);		//Article  at=new Article(2, "lucene2","Lucene2是apache软件基金会发布的一个开放源代码的全文检索引擎工具包",20);		Article  at=new Article(3, "新lucene3","Lucene3是apache软件基金会发布的一个开放源代码的全文检索引擎工具包",30);		//Article  at=new Article(4, "lucene4","Lucene4是是软件基金会发布是时的一个开放源是代码的全文是检索引擎是工具包",30);		//Article  at=new Article(5, "lucene5","是是是是是是是是是是是是是是是是是是是是是是是是是是是是是是是是是是是是是是",30);						//创建Document对象		Document doc=LuceneUtil.javabean2Document(at);		IndexWriter  iw=new IndexWriter(LuceneUtil.getD(),LuceneUtil.getAnalyzer(),LuceneUtil.getMfl());		doc.setBoost(20F);		iw.addDocument(doc);		iw.close();	}

//获取document对象的评分        float score=sd.score;

强行提高得分 doc.setBoost(100F);  搜索结果是按某个或某些字段高低排序来显示的结果.

5.3 字段排序

按单个字段排序

//创建排序的条件		/*		 * 参数1:id表示依据document对象中的那个字段排序,里如id		 * 参数2:sortField.INT表示document对象中该字段的类型,		 * 参数3:true表示降序,类似于order by		 * 		 * 		 */		Sort sort=new Sort(new SortField("id",SortField.INT,true));		TopDocs td=is.search(query, null,100,sort);

按多个字段排序

//创建排序的条件		/*		 * 参数1:id表示依据document对象中的那个字段排序,里如id		 * 参数2:sortField.INT表示document对象中该字段的类型,		 * 参数3:true表示降序,类似于order by		 * 		 * 		 */		Sort sort=new Sort(				new SortField("count",SortField.INT,true),				new SortField("count",SortField.INT,true ));		TopDocs td=is.search(query, null,100,sort);

六、分页(实现站内搜索功能)

6.1 Bean

同之前的Artucle.java

private  Integer id;  //索引号	private  String title; //标题	private  String content;  //内容
get ,set方法,还有toString一下,有参和无参构造函数都要加上。

同时还要一个分页的bean,进行封装

private Integer currPageNO;//当前页号	private Integer perPageSize = 3;//每页显示记录数,默认为2条	private Integer allRecordNO;//总记录数OK	private Integer allPageNO;//总页数OK	private List
articleList = new ArrayList
();//内容
get ,set方法,还有toString一下,有参和无参构造函数都要加上。

6.2 持久层

新建ArticleDao.java

//根据关键字,获取总记录数,返回总的记录数		public int getAllRecord(String keywords) throws Exception{			List
atl=new ArrayList
(); QueryParser queryParser=new QueryParser(LuceneUtil.getVersion(),"content",LuceneUtil.getAnalyzer()); Query query=queryParser.parse(keywords); IndexSearcher is=new IndexSearcher(LuceneUtil.getD()); TopDocs td=is.search(query,5); //返回符合条件的真实总记录数,不受search(query,5)中定义的5的影响 //return td.totalHits; //17条 return td.scoreDocs.length; //5条 } //根据关键字,批量查询记录 /* * start:从第几条记录的索引号开始查询,索引号从0开始 * size:最多查询几条记录,不满足最多数目时,以实际为准 * 返回集合 */ public List
findAll(String keywords,int start,int size) throws Exception{ List
atl=new ArrayList
(); QueryParser queryParser=new QueryParser(LuceneUtil.getVersion(),"content",LuceneUtil.getAnalyzer()); Query query=queryParser.parse(keywords); IndexSearcher is=new IndexSearcher(LuceneUtil.getD()); TopDocs td=is.search(query,100); int middle=Math.min(start+size,td.totalHits); for(int i=start;i

6.3、业务层

新建一个ArticleService.java ,对分页进行处理

private ArticleDao  articleDao=new ArticleDao();		//根据关键字和页号,查询内容	public PageBean show(String keywords,int currPageNO) throws Exception{				PageBean page=new PageBean();		//封装当前页号		page.setCurrPageNO(currPageNO);				//封装总记录数		int allRecordNO=articleDao.getAllRecord(keywords);		page.setAllRecordNO(allRecordNO);						//封装总页数		int allPageNO=0;		if(page.getAllRecordNO() % page.getPerPageSize() ==0){			allPageNO=page.getAllRecordNO() / page.getPerPageSize();		}else{			allPageNO=page.getAllRecordNO() /page.getPerPageSize()+1;			}		page.setAllPageNO(allPageNO);						//封装内容		int size=page.getPerPageSize();		int start=(page.getCurrPageNO()-1)*size;		List
art=articleDao.findAll(keywords, start, size) ; page.setArticleList(art); return page; }

6.4、控制器

使用一个servlet来对数据进行控制。doGet和doPost方法中进行处理。

public void doGet(HttpServletRequest request, HttpServletResponse response)			throws ServletException, IOException {		this.doPost(request, response);	}	public void doPost(HttpServletRequest request, HttpServletResponse response)			throws ServletException, IOException {		request.setCharacterEncoding("UTF-8");		try {		//获取关键字		String keywords=request.getParameter("keywords");		if(keywords==null || keywords.trim().length()==0){			keywords="是";		}		//获取当前页号		String temp=request.getParameter("page");		if(temp==null || temp.trim().length()==0){			temp="1";		}		//调用业务层		ArticleService articleService=new ArticleService();		PageBean page=articleService.show(keywords,Integer.parseInt(temp));				//构造map对象		Map
map=new LinkedHashMap
(); map.put("total", page.getAllPageNO()); map.put("rows", page.getArticleList()); //将map转成json JSONArray jsonArray=JSONArray.fromObject(map); String jsonJAVA=jsonArray.toString(); //去掉两边的[]符号 jsonJAVA=jsonJAVA.substring(1,jsonJAVA.length()-1); //以流的方式将json文本输出到DateGrid组件中 response.setContentType("text/html;charset=UTF-8"); PrintWriter pw=response.getWriter(); pw.write(jsonJAVA); pw.flush(); pw.close(); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } }

6.5 视图(界面处理)

我们使用一个jsp来书写界面。先把我们需要的js包,jquery,easyui相关的jar导入到项目的lib目录中,然后在这个jsp文件中引入我们所需要的文件。命名为list.jsp.

在这里就暂时假设你已经学会使用easyui了,如果对使用easyui还有什么问题的,欢迎查看我的下一篇文章。

输入关键字:

界面效果如下。

 

源码下载地址:  (基础部分,前两章节)

 (应用部分,后四章节的内容)

小结:这里对于lucene的使用都是比较基础的部分,重要的要是学会基本的编码流程,开发思想,学以致用,做一个有思想有深度的人,不要变成一个只会敲代码的机器。

你可能感兴趣的文章
再学 GDI+[43]: 文本输出 - 获取已安装的字体列表
查看>>
nginx反向代理
查看>>
操作系统真实的虚拟内存是什么样的(一)
查看>>
hadoop、hbase、zookeeper集群搭建
查看>>
python中一切皆对象------类的基础(五)
查看>>
modprobe
查看>>
android中用ExpandableListView实现三级扩展列表
查看>>
%Error opening tftp://255.255.255.255/cisconet.cfg
查看>>
java读取excel、txt 文件内容,传到、显示到另一个页面的文本框里面。
查看>>
《从零开始学Swift》学习笔记(Day 51)——扩展构造函数
查看>>
python多线程队列安全
查看>>
[汇编语言学习笔记][第四章第一个程序的编写]
查看>>
android 打开各种文件(setDataAndType)转:
查看>>
补交:最最原始的第一次作业(当时没有选上课,所以不知道)
查看>>
Vue实例初始化的选项配置对象详解
查看>>
PLM产品技术的发展趋势 来源:e-works 作者:清软英泰 党伟升 罗先海 耿坤瑛
查看>>
vue part3.3 小案例ajax (axios) 及页面异步显示
查看>>
浅谈MVC3自定义分页
查看>>
.net中ashx文件有什么用?功能有那些,一般用在什么情况下?
查看>>
select、poll、epoll之间的区别总结[整理]【转】
查看>>