1 什么是全文检索

1.1 数据分类

我们生活中的数据总体分为两种:结构化数据和非结构化数据。

结构化数据:指具有固定格式或有限长度的数据,如数据库,元数据等。

非结构化数据:指不定长或无固定格式的数据,如邮件,word 文档等磁盘上的文件

1.2 结构化数据搜索

常见的结构化数据也就是数据库中的数据

在数据库中搜索很容易实现,通常都是使用 sql语句进行查询,而且能很快的得到查询结果。

为什么数据库搜索很容易?

因为数据库中的数据存储是有规律的,有行有列而且数据格式、数据长度都是固定的。

1.3 非结构化数据查询方法

(1 ) 顺序扫描法(Serial Scanning)

用户搜索----->文件

所谓顺序扫描,比如要找内容包含某一个字符串的文件,就是一个文档一个文档的看,对于每一个文 档,从头看到尾,如果此文档包含此字符串,则此文档为我们要找的文件,接着看下一个文件,直到扫 描完所有的文件。如利用 windows 的搜索也可以搜索文件内容,只是相当的慢。

(2 ) 全文检索(Full-text Search)

用户通过查询索引库---->生成索引----->文档

全文检索是指计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在 文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果 反馈给用户的检索方法。这个过程类似于通过字典的目录查字的过程。

将非结构化数据中的一部分信息提取出来,重新组织,使其变得有一定结构,然后对此有一定结构 的数据进行搜索,从而达到搜索相对较快的目的。这部分从非结构化数据中提取出的然后重新组织的信 息,我们称之索引

例如:字典。字典的拼音表和部首检字表就相当于字典的索引,对每一个字的解释是非结构化的, 如果字典没有音节表和部首检字表,在茫茫辞海中找一个字只能顺序扫描。然而字的某些信息可以提取 出来进行结构化处理,比如读音,就比较结构化,分声母和韵母,分别只有几种可以一一列举,于是将 读音拿出来按一定的顺序排列,每一项读音都指向此字的详细解释的页数。我们搜索时按结构化的拼音 搜到读音,然后按其指向的页数,便可找到我们的非结构化数据——也即对字的解释。

这种先建立索引,再对索引进行搜索的过程就叫全文检索(Full-Text Search) 。虽然创建索引的过 程也是非常耗时的,但是索引一旦创建就可以多次使用,全文检索主要处理的是查询,所以耗时间创建 索引是值得的。

建立索引

检索索引

1.4 如何实现全文检索

可以使用 Lucene 实现全文检索。Lucene 是 apache 下的一个开放源代码的全文检索引擎工具包。提 供了完整的查询引擎和索引引擎,部分文本分析引擎(英文与德文两种西方语言)。Lucene 的目的是 为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能。

Lucene适用场景:

  • 在应用中为数据库中的数据提供全文检索实现。
  • 开发独立的搜索引擎服务、系统

Lucene的特性:

1. 稳定、索引性能高

  • 每小时能够索引150GB以上的数据
  • 对内存的要求小,只需要1MB的堆内存
  • 增量索引和批量索引一样快
  • 索引的大小约为索引文本大小的20%~30%

2.高效、准确、高性能的搜索算法

  • 良好的搜索排序
  • 强大的查询方式支持:短语查询、通配符查询、临近查询、范围查询等
  • 支持字段搜索(如标题、作者、内容)
  • 可根据任意字段排序
  • 支持多个索引查询结果合并
  • 支持更新操作和查询操作同时进行
  • 支持高亮、join、分组结果功能
  • 速度快
  • 可扩展排序模块,内置包含向量空间模型、BM25模型可选
  • 可配置存储引擎

3.跨平台

  • 纯java编写
  • 作为Apache开源许可下的开源项目,你可以在商业或开源项目中使用
  • Lucene有多种语言实现版(如C,C++、Python等),不仅仅是JAVA

Lucene架构:

1.5 全文检索的应用场景

对于数据量大、数据结构不固定的数据可采用全文检索方式搜索,

  • 单机软件的搜索:word、markdown
  • 站内搜索:京东、淘宝、拉勾,索引源是数据库
  • 搜索引擎:百度、Google,索引源是爬虫程序抓取的数据

2 Lucene 实现全文检索的流程说明

2.1 索引和搜索流程图

1、绿色表示索引过程,对要搜索的原始内容进行索引构建一个索引库,索引过程包括: 确定原始内容即要搜索的内容-->采集文档-->创建文档-->分析文档-->索引文档

2、红色表示搜索过程,从索引库中搜索内容,搜索过程包括: 用户通过搜索界面-->创建查询-->执行搜索,从索引库搜索-->渲染搜索结果

2.2 创建索引

核心概念:

Document:

用户提供的源是一条条记录,它们可以是文本文件、字符串或者数据库表的一条记录等等。一条记录 经过索引之后,就是以一个Document的形式存储在索引文件中的。用户进行搜索,也是以Document 列表的形式返回。

Field

一个Document可以包含多个信息域,例如一篇文章可以包含“标题”、“正文”、“最后修改时间”等信息 域,这些信息域就是通过Field在Document中存储的。

Field有两个属性可选:存储和索引。通过存储属性你可以控制是否对这个Field进行存储;通过索引 属性你可以控制是否对该Field进行索引。

如果对标题和正文进行全文搜索,所以我们要把索引属性设置为真,同时我们希望能直接从搜索结果 中提取文章标题,所以我们把标题域的存储属性设置为真,但是由于正文域太大了,我们为了缩小索引 文件大小,将正文域的存储属性设置为假,当需要时再直接读取文件;我们只是希望能从搜索解果中提 取最后修改时间,不需要对它进行搜索,所以我们把最后修改时间域的存储属性设置为真,索引属性设 置为假。上面的三个域涵盖了两个属性的三种组合,还有一种全为假的没有用到,事实上Field不允许你 那么设置,因为既不存储又不索引的域是没有意义的。

Term:

Term是搜索的最小单位,它表示文档的一个词语,Term由两部分组成:它表示的词语和这个词语所 出现的Field的名称。

网站的索引数据需要提前创建的。以下是创建的过程:

第一步:获得原始文档:就是从mysql数据库中通过sql语句查询需要创建索引的数据

第二步:创建文档对象(Document),把查询的内容构建成lucene能识别的Document对象,获取原 始内容的目的是为了索引,在索引前需要将原始内容创建成文档,文档中包括一个一个的域(Field), 这个域对应就是表中的列。

注意:每个 Document 可以有多个 Field,不同的 Document 可以有不同的 Field,同一个Document 可以有相同的 Field(域名和域值都相同)。每个文档都有一个唯一的编号,就是文档 id。

第三步:分析文档 将原始内容创建为包含域(Field)的文档(document),需要再对域中的内容进行分析,分析的过程 是经过对原始文档提取单词、将字母转为小写、去除标点符号、去除停用词等过程生成最终的语汇单 元,可以将语汇单元理解为一个一个的单词。

分好的词会组成索引库中最小的单元:term,一个term由域名和词组成

第四步:创建索引, 对所有文档分析得出的语汇单元进行索引,索引的目的是为了搜索,最终要实现只搜索被索引的语汇单 元从而找到 Document(文档)。 注意:创建索引是对语汇单元索引,通过词语找文档,这种索引的结构叫 倒排索引结构。 倒排索引结构是根据内容(词语)找文档,如下图:

倒排索引结构也叫反向索引结构,包括索引文档两部分,索引即词汇表,它的规模较小,而文档集合 较大。

2.3 倒排索引

倒排索引记录每个词条出现在哪些文档,及在文档中的位置,可以根据词条快速定位到包含这个词条的 文档及出现的位置。

文档:索引库中的每一条原始数据,例如一个商品信息、一个职位信息

词条:原始数据按照分词算法进行分词,得到的每一个词

创建倒排索引,分为以下几步:

1)创建文档列表:

lucene首先对原始文档数据进行编号(DocID),形成列表,就是一个文档列表

2)创建倒排索引列表

对文档中数据进行分词,得到词条(分词后的一个又一个词)。对词条进行编号,以词条创建索引。然 后记录下包含该词条的所有文档编号(及其它信息)。

搜索的过程:

当用户输入任意的词条时,首先对用户输入的数据进行分词,得到用户要搜索的所有词条,然后拿着这 些词条去倒排索引列表中进行匹配。找到这些词条就能找到包含这些词条的所有文档的编号。然后根据 这些编号去文档列表中找到文档

  • 1.用户在搜索页面,搜索Java开发工程师   ;搜索条件会被切分词 Java、开发、工程师
  • 2.将搜索条件分词去索引中查询,匹配词条
  • 3.通过词条获取倒排列表,通过倒排列表获得Document队列,将document列表封装返回

2.4 查询索引

查询索引也是搜索的过程。搜索就是用户输入关键字,从索引(index)中进行搜索的 过程。根据关键字搜索索引,根据索引找到对应的文档

第一步:创建用户接口:用户输入关键字的地方

第二步:创建查询 指定查询的域名和关键字

第三步:执行查询

第四步:渲染结果 (结果内容显示到页面上 关键字需要高亮)

3 Lucene实战

3.1 需求说明

生成职位信息索引库,从索引库检索数据

3.2 准备开发环境

第一步:创建一个maven工程 ,创建一个SpringBoot项目

第二步:导入依赖

<properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!--mybatis-plus--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.3.2</version></dependency><!--pojo持久化使用--><dependency><groupId>javax.persistence</groupId><artifactId>javax.persistence-api</artifactId><version>2.2</version></dependency><!--mysql驱动--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><!--引入Lucene核心包及分词器包--><dependency><groupId>org.apache.lucene</groupId><artifactId>lucene-core</artifactId><version>4.10.3</version></dependency><dependency><groupId>org.apache.lucene</groupId><artifactId>lucene-analyzers-common</artifactId><version>4.10.3</version></dependency><!--IK中文分词器--><dependency><groupId>com.janeluo</groupId><artifactId>ikanalyzer</artifactId><version>2012_u6</version></dependency></dependencies>

第三步:创建引导类

@SpringBootApplication
@MapperScan(basePackages = "com.panghl.mapper")
public class LuceneDemoApplication {public static void main(String[] args) {SpringApplication.run(LuceneDemoApplication.class, args);}}

第四步:配置yml文件

server:port: 9000
spring:application:name: lagou-lucenedatasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/lg_jy?useUnicode=true&characterEncoding=utf8&serverTimezone=UTCusername: rootpassword: 123456mybatis:configuration:map-underscore-to-camel-case: true

第五步:创建实体类、mapper、service

@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "job_info")
public class JobInfo {@Idprivate long id;private String companyName;private String companyAddr;private String companyInfo;private String jobName;private String jobAddr;private String jobInfo;private Integer salaryMin;private Integer salaryMax;private String url;private String time;}
/*** @Author panghl* @Date 2021/8/31 21:42* @Version 1.0* @Description TODO**/
public interface JobInfoMapper extends BaseMapper<JobInfo> {}
/*** @Author panghl* @Date 2021/8/31 21:43* @Version 1.0* @Description TODO**/
public interface JobInfoService {/*** 通过id查询* @param id* @return*/public JobInfo selectById(Long id);/*** 查询所有job* @return*/public List<JobInfo> selectAll();
}
/*** @Author panghl* @Date 2021/8/31 21:44* @Description TODO**/
@Service
public class JobInfoServiceImpl implements JobInfoService {@Autowiredprivate JobInfoMapper jobInfoMapper;@Overridepublic JobInfo selectById(Long id) {return jobInfoMapper.selectById(id);}@Overridepublic List<JobInfo> selectAll() {return jobInfoMapper.selectList(null);}
}

3.3创建索引

@SpringBootTest
class LuceneDemoApplicationTests {@Autowiredprivate JobInfoService jobInfoService;/*** 创建索引*/@Testpublic void createIndex() throws Exception {//1.指定索引文件的存储位置,索引具体的表现形式就是一组有规则的文件Directory directory = FSDirectory.open(new File("D:\\class\\index"));//2.配置版本及其分词器
//        Analyzer analyzer = new StandardAnalyzer();Analyzer analyzer = new IKAnalyzer();IndexWriterConfig config = new IndexWriterConfig(Version.LATEST, analyzer);//3.创建IndexWriter对象,作用就是创建索引IndexWriter indexWriter = new IndexWriter(directory, config);//先删除已经存在的索引库indexWriter.deleteAll();//4.获取索引源(原始数据)List<JobInfo> jobInfoList = jobInfoService.selectAll();//5.遍历jobInfoList,每次遍历创建一个Document对象for (JobInfo jobInfo : jobInfoList) {//创建Document对象Document document = new Document();//创建Field对象document.add(new LongField("id", jobInfo.getId(), Field.Store.YES));//切分词、索引、存储document.add(new TextField("companyName", jobInfo.getCompanyName(), Field.Store.YES));document.add(new TextField("companyAddr", jobInfo.getCompanyName(), Field.Store.YES));document.add(new TextField("companyInfo", jobInfo.getCompanyName(), Field.Store.YES));document.add(new TextField("jobName", jobInfo.getCompanyName(), Field.Store.YES));document.add(new TextField("jobAddr", jobInfo.getCompanyName(), Field.Store.YES));document.add(new TextField("jobInfo", jobInfo.getCompanyName(), Field.Store.YES));document.add(new IntField("salaryMin", jobInfo.getSalaryMin(), Field.Store.YES));document.add(new IntField("salaryMax", jobInfo.getSalaryMax(), Field.Store.YES));document.add(new StringField("url", jobInfo.getUrl(), Field.Store.YES));document.add(new StringField("time", jobInfo.getTime(), Field.Store.YES));//将文档追加到索引库中indexWriter.addDocument(document);}indexWriter.close();System.out.println("create index success");}}

生成的索引目录:D:\class\index

  • 索引(Index):

    • 在Lucene中一个索引是放在一个文件夹中的。
    • 如下图,同一文件夹中的所有的文件构成一个Lucene索引。
  • 段(Segment):
    • 按层次保存了从索引,一直到词的包含关系:索引(Index) –> 段(segment) –> 文档 (Document) –> 域(Field) –> 词(Term)
    • 也即此索引包含了那些段,每个段包含了那些文档,每个文档包含了那些域,每个域包含了 那些词。
    • 一个索引可以包含多个段,段与段之间是独立的,添加新文档可以生成新的段,不同的段可 以合并。
    • 如上图,具有相同前缀文件的属同一个段,图中共一个段 "_0" 。
    • segments.gen和segments_1是段的元数据文件,也即它们保存了段的属性信息。

Field的特性:

Document(文档)是Field(域)的承载体, 一个Document由多个Field组成. Field由名称和值两部分组成, Field的值是要索引的内容, 也是要搜索的内容.

  • 是否分词(tokenized)
    是: 将Field的值进行分词处理, 分词的目的是为了索引. 如: 商品名称, 商品描述. 这些内容用户会通 过输入关键词进行查询, 由于内容多样, 需要进行分词处理建立索引.
    否: 不做分词处理. 如: 订单编号, 身份证号, 是一个整体, 分词以后就失去了意义, 故不需要分词.
  • 是否索引(indexed)
    是: 将Field内容进行分词处理后得到的词(或整体Field内容)建立索引, 存储到索引域. 索引的目的是 为了搜索. 如: 商品名称, 商品描述需要分词建立索引. 订单编号, 身份证号作为整体建立索引. 只要 可能作为用户查询条件的词, 都需要索引.
    否: 不索引. 如: 商品图片路径, 不会作为查询条件, 不需要建立索引.
  • 是否存储(stored)
    是: 将Field值保存到Document中. 如: 商品名称, 商品价格. 凡是将来在搜索结果页面展现给用户的 内容, 都需要存储.
    否: 不存储. 如: 商品描述. 内容多格式大, 不需要直接在搜索结果页面展现, 不做存储. 需要的时候可 以从关系数据库取.

常用的Field类型:

3.4查询索引

/*** 查询索引*/@Testpublic void query() throws Exception {//1.指定索引文件的存储位置,索引具体的表现形式就是一组有规则的文件Directory directory = FSDirectory.open(new File("D:/class/index"));//2.IndexReader对象IndexReader indexReader = DirectoryReader.open(directory);//3.创建查询对象,IndexSearcherIndexSearcher indexSearcher = new IndexSearcher(indexReader);//4.使用term查询 ,查询公司名称中包含“北京”的所有的文档对象Query query = new TermQuery(new Term("companyName", "北京云"));TopDocs topDocs = indexSearcher.search(query, 100);//获得符合条件查询的文档数int totalHits = topDocs.totalHits;System.out.println("符合条件的文档数:" + totalHits);//获得命中的文档 ScoreDoc 封装了文档id信息ScoreDoc[] scoreDocs = topDocs.scoreDocs;for (ScoreDoc scoreDoc : scoreDocs) {//文档idint docId = scoreDoc.doc;//通过文档id获取文档对象Document doc = indexSearcher.doc(docId);System.out.println("id-->" + doc.get("id"));System.out.println("companyName-->" + doc.get("companyName"));System.out.println("companyAddr-->" + doc.get("companyAddr"));System.out.println("companyInfo-->" + doc.get("companyInfo"));System.out.println("jobName-->" + doc.get("jobName"));System.out.println("jobAddr-->" + doc.get("jobAddr"));System.out.println("jobInfo-->" + doc.get("jobInfo"));System.out.println("salaryMin-->" + doc.get("salaryMin"));System.out.println("salaryMax-->" + doc.get("salaryMax"));System.out.println("url-->" + doc.get("url"));System.out.println("time-->" + doc.get("time"));System.out.println("****************************");}indexReader.close();}

查看结果你会发现,居然没有数据,如果把查询的关键字“北京”那里改为“北”或“京”就可以,原因是因为 中文会一个字一个字的分词,显然是不合适的,所以我们需要使用可以合理分词的分词器,其中最有名 的是IKAnalyzer分词器

3.5中文分词器的使用

第一步:导依赖

<!--IK中文分词器-->
<dependency>
<groupId>com.janeluo</groupId>
<artifactId>ikanalyzer</artifactId>
<version>2012_u6</version>
</dependency>

第二步:可以添加配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>  <comment>IK Analyzer 扩展配置</comment><!--用户可以在这里配置自己的扩展字典--><entry key="ext_dict">ext.dic;</entry><!--用户可以在这里配置自己的停止词字典--><entry key="ext_stopwords">stopword.dic;</entry> </properties>

放入到resources文件夹中。

第三步 创建索引时使用IKanalyzer

把原来的索引数据删除,再重新生成索引文件,再使用关键字“北京”就可以查询到结果了

考虑一个问题:一个大型网站中的索引数据会很庞大的,所以使用lucene这种原生的写代码的方式就不 合适了,所以需要借助一个成熟的项目或软件来实现,目前比较有名是solr和elasticSearch,所以接下 来我们学习elasticSearch的使用。

Elastic search入门到集群实战操作详解(原生API操作、springboot整合操作)

https://blog.csdn.net/qq_45441466/article/details/120110968

Lucene入门及操作详解相关推荐

  1. 第二讲:ADS入门和Data DisPlay操作详解

    第二讲:ADS入门和Data DisPlay操作详解 设计流程简介 创建Workspace的过程和设计讲解 仿真设计要素和原理图 元件面板和元器件操作 仿真控件 仿真分析设置和运行仿真分析 查看分析结 ...

  2. python输入参数改变图形_Python基于Tensor FLow的图像处理操作详解

    本文实例讲述了Python基于Tensor FLow的图像处理操作.分享给大家供大家参考,具体如下: 在对图像进行深度学习时,有时可能图片的数量不足,或者希望网络进行更多的学习,这时可以对现有的图片数 ...

  3. python3d动态图-Python图像处理之gif动态图的解析与合成操作详解

    本文实例讲述了Python图像处理之gif动态图的解析与合成操作.分享给大家供大家参考,具体如下: gif动态图是在现在已经司空见惯,朋友圈里也经常是一言不合就斗图.这里,就介绍下如何使用python ...

  4. linux Shell(脚本)编程入门实例讲解详解

    linux Shell(脚本)编程入门实例讲解详解 为什么要进行shell编程 在Linux系统中,虽然有各种各样的图形化接口工具,但是sell仍然是一个非常灵活的工具.Shell不仅仅是命令的收集, ...

  5. [Python从零到壹] 十一.数据分析之Numpy、Pandas、Matplotlib和Sklearn入门知识万字详解(1)

    欢迎大家来到"Python从零到壹",在这里我将分享约200篇Python系列文章,带大家一起去学习和玩耍,看看Python这个有趣的世界.所有文章都将结合案例.代码和作者的经验讲 ...

  6. python excel数据处理教程pdf_python对excel操作详解.pdf

    python对excel操作详解 拟 制 人 胡张东 太仓同维电子有限公司 审 批 人 xxx 测试中心软件测试科 时 间 2013-11-04 Python对Excel操作 详解 文档摘要: 本文档 ...

  7. S-function入门及案例详解(2)——S-function基本案例介绍

    目录 一.案例1--对波形的幅值进行放大 1.1 案例分析 1.2 案例详解 1.3 输出波形 二.案例2--对波形的幅值进行放大拓展 2.1 案例分析 2.2 案例详解 2.3 输出波形 三.案例3 ...

  8. 用python处理excel的基本语法_Python对Excel操作详解

    Python对Excel操作详解 文档摘要: 本文档主要介绍如何通过python对office excel进行读写操作,使用了xlrd.xlwt和xlutils模块.另外还演示了如何通过Tcl tco ...

  9. 如何用c语言编写stm32的程序吗,STM32入门C语言详解

    <STM32入门C语言详解>由会员分享,可在线阅读,更多相关<STM32入门C语言详解(6页珍藏版)>请在人人文库网上搜索. 1.最新 料推荐阅读 flash : 芯片内部存储 ...

最新文章

  1. 自动生成Makefile的全过程详解
  2. 企业做SEO优化哪些行为会被判定为作弊?
  3. php数组转为js json,php如何将数组转为json数组,php数组转为js数组
  4. hbase java api
  5. API网关—Spring Cloud Zuul
  6. 春节特惠活动┃一张纸一幅图,竟然提高了10倍的学习和工作效率!?
  7. python面试题之解释一下python的and-or语法
  8. 【报告分享】2020中国企业直播应用场景趋势研究报告.pdf(附下载链接)
  9. 5G组网方案和频谱规划
  10. linux创建文件夹操作步骤,linux如何用命令创建新建文件夹
  11. linux如何运行rpm,LINUX下RPM的使用方法
  12. Direct-X学习笔记--三维摄像机
  13. android 获取本地图片路径
  14. 转载:详解P=Q->NEXT和P->NEXT=Q的区别,链表操作,附代码
  15. Python 把两张图片拼起来
  16. 3. 梯度提升决策树(GBDT)详解
  17. 地方旅游产业运行监测与应急指挥平台、旅游资源管理平台、旅游产业监测平台、旅游应急指挥平台、旅游资源统计、旅游线路数据、旅游产业可视化大屏、餐饮场所数据、游客流量监测、景区数据监测、视频监控、环境监测
  18. 智慧树\知到——程序设计基础(C语言)入门篇第六章到第十章测试答案
  19. 机组配对算法matlab,基于MATLAB的风力发电机组控制算法的研究 - 北极星风力发电网...
  20. IDL学习:语法基础-变量

热门文章

  1. Affinity:手把手教你“借鉴”好莱坞大片和AAA游戏大作中的配色方案
  2. File文件创建方法createNewFile
  3. 人工智能进入生产生活的各个领域,将深刻改变方式和思维模式
  4. docker 常见命令学习
  5. php程序设计英文版书,PHP程序设计 ( 李英梅,刘新飞) pdf扫描版
  6. 主题和母版页 如何在web窗体调用操作母版页内容元素
  7. JavaScript DOM部分基础知识学习笔记(4)
  8. 计算机一级excel操作试题及答案,计算机一级考试选择题题库之excel题及答案(最新版).doc...
  9. Materialize——扁平化卡片式网页模板
  10. 360孙浩:物联网安全需要良好的研发规范和安审流程保证