1.与ES服务集群交互方式
可以通过两种方式来连接到elasticsearch(简称es)集群,第一种是通过在你的程序中创建一个嵌入es节点(Node),使之成为es集群的一部分,然后通过这个节点来与es集群通信。第二种方式是用TransportClient这个接口和es集群通信。
1.1 Node方式
创建嵌入节点的方式如下:
- import staticorg.elasticsearch.node.NodeBuilder.*;
- //启动节点
- Node node = nodeBuilder().node();
- Client client = node.client();
- //关闭节点
- node.close();
import staticorg.elasticsearch.node.NodeBuilder.*; //启动节点 Node node = nodeBuilder().node(); Client client = node.client(); //关闭节点 node.close();
当你启动一个节点,它会自动加入同网段的es集群,一个前提就是es的集群名(cluster.name)这个参数要设置一致。
默认的话启动一个节点,es集群会自动给它分配一些索引的分片,如果你想这个节点仅仅作为一个客户端而不去保存数据,你就可以设置把node.data设置成false或 node.client设置成true。下面是例子:
Node node =nodeBuilder().clusterName(clusterName).client(true).node();
还有一种情况是你并不想把节点加入集群,只想用它进行单元测试时,就要启动一个“本地”的es,这里“本地”指的是在jvm的级别下运行,即两个不同的es节点运行在同一个JVM中时会组成一个集群。它需要把节点的local参数设置成true,下面是例子:
Node node =nodeBuilder().local(true).node();
1.2 TransportClient方式
通过TransportClient这个接口,我们可以不启动节点就可以和es集群进行通信,它需要指定es集群中其中一台或多台机的ip地址和端口,例子如下:
- Client client = new TransportClient()
- .addTransportAddress(newInetSocketTransportAddress("host1", 9300))
- .addTransportAddress(newInetSocketTransportAddress("host2", 9300));
- client.close();
Client client = new TransportClient() .addTransportAddress(newInetSocketTransportAddress("host1", 9300)) .addTransportAddress(newInetSocketTransportAddress("host2", 9300)); client.close();
如果你需要更改集群名(默认是elasticsearch),需要如下设置:
- Settings settings =ImmutableSettings.settingsBuilder()
- .put("cluster.name","myClusterName").build();
- Client client = newTransportClient(settings);
Settings settings =ImmutableSettings.settingsBuilder() .put("cluster.name","myClusterName").build(); Client client = newTransportClient(settings);
你可以设置client.transport.sniff为true来使客户端去嗅探整个集群的状态,把集群中其它机器的ip地址加到客户端中,这样做的好处是一般你不用手动设置集群里所有集群的ip到连接客户端,它会自动帮你添加,并且自动发现新加入集群的机器。代码实例如下:
- Settings settings = ImmutableSettings.settingsBuilder()
- .put("client.transport.sniff", true).build();
- TransportClientclient = new TransportClient(settings);
Settings settings = ImmutableSettings.settingsBuilder() .put("client.transport.sniff", true).build(); TransportClientclient = new TransportClient(settings);
2. put Mapping定义索引字段属性
Mapping,就是对索引库中索引的字段名及其数据类型进行定义,类似于关系数据库中表建立时要定义字段名及其数据类型那样,不过es的mapping比数据库灵活很多,它可以动态添加字段。一般不需要要指定mapping都可以,因为es会自动根据数据格式定义它的类型,如果你需要对某些字段添加特殊属性(如:定义使用其它分词器、是否分词、是否存储等),就必须手动添加mapping。有两种添加mapping的方法,一种是定义在配置文件中,一种是运行时手动提交mapping,两种选一种就行了。
先介绍在配置文件中定义mapping,你可以把[mapping名].json文件放到config/mappings/[索引名]目录下,这个目录要自己创建,一个mapping和一个索引对应,你也可以定义一个默认的mapping,把自己定义的default-mapping.json放到config目录下就行。json格式如下:
- {
- "mappings":{
- "properties":{
- "title":{
- "type":"string",
- "store":"yes"
- },
- "description":{
- "type":"string",
- "index":"not_analyzed"
- },
- "price":{
- "type":"double"
- },
- "onSale":{
- "type":"boolean"
- },
- "type":{
- "type":"integer"
- },
- "createDate":{
- "type":"date"
- }
- }
- }
- }
{ "mappings":{ "properties":{ "title":{ "type":"string", "store":"yes" }, "description":{ "type":"string", "index":"not_analyzed" }, "price":{ "type":"double" }, "onSale":{ "type":"boolean" }, "type":{ "type":"integer" }, "createDate":{ "type":"date" } } } }
接下来介绍通过请求添加mapping,下面为一个添加productIndex索引库的mapping的json格式请求。其中productIndex为索引类型,properties下面的为索引里面的字段,type为数据类型,store为是否存储,"index":"not_analyzed"为不对该字段进行分词。
- {
- "productIndex":{
- "properties":{
- "title":{
- "type":"string",
- "store":"yes"
- },
- "description":{
- "type":"string",
- "index":"not_analyzed"
- },
- "price":{
- "type":"double"
- },
- "onSale":{
- "type":"boolean"
- },
- "type":{
- "type":"integer"
- },
- "createDate":{
- "type":"date"
- }
- }
- }
- }
{ "productIndex":{ "properties":{ "title":{ "type":"string", "store":"yes" }, "description":{ "type":"string", "index":"not_analyzed" }, "price":{ "type":"double" }, "onSale":{ "type":"boolean" }, "type":{ "type":"integer" }, "createDate":{ "type":"date" } } } }
用java api调用的代码如下:
先创建空索引库
- client.admin().indices().prepareCreate("productIndex").execute().actionGet();
client.admin().indices().prepareCreate("productIndex").execute().actionGet();
put mapping:
- XContentBuilder mapping = jsonBuilder()
- .startObject()
- .startObject("properties")
- .startObject("title").field("type", "string").field("store", "yes").endObject()
- .startObject("description").field("type", "string").field("index", "not_analyzed").endObject()
- .startObject("price").field("type", "double").endObject()
- .startObject("onSale").field("type", "boolean").endObject()
- .startObject("type").field("type", "integer").endObject()
- .startObject("createDate").field("type", "date").endObject()
- .endObject()
- .endObject();
- PutMappingRequest mappingRequest = Requests.putMappingRequest("productIndex").type("productIndex").source(mapping);
- client.admin().indices().putMapping(mappingRequest).actionGet();
XContentBuilder mapping = jsonBuilder() .startObject() .startObject("properties") .startObject("title").field("type", "string").field("store", "yes").endObject() .startObject("description").field("type", "string").field("index", "not_analyzed").endObject() .startObject("price").field("type", "double").endObject() .startObject("onSale").field("type", "boolean").endObject() .startObject("type").field("type", "integer").endObject() .startObject("createDate").field("type", "date").endObject() .endObject() .endObject(); PutMappingRequest mappingRequest = Requests.putMappingRequest("productIndex").type("productIndex").source(mapping); client.admin().indices().putMapping(mappingRequest).actionGet();
3. 索引数据
es索引数据非常方便,只需构建个json格式的数据提交到es就行,下面是个java api的例子
- XContentBuilder doc = jsonBuilder()
- .startObject()
- .field("title", "this is a title!")
- .field("description", "descript what?")
- .field("price", 100)
- .field("onSale", true)
- .field("type", 1)
- .field("createDate", new Date())
- .endObject();
- client.prepareIndex("productIndex","productType").setSource(doc).execute().actionGet();
XContentBuilder doc = jsonBuilder() .startObject() .field("title", "this is a title!") .field("description", "descript what?") .field("price", 100) .field("onSale", true) .field("type", 1) .field("createDate", new Date()) .endObject(); client.prepareIndex("productIndex","productType").setSource(doc).execute().actionGet();
其中productIndex为索引库名,一个es集群中可以有多个索引库。productType为索引类型,是用来区分同索引库下不同类型的数据的,一个索引库下可以有多个索引类型。
4. 删除索引数据
删除api允许从特定索引通过id删除json文档。有两种方法,一是通过id删除,二是通过一个Query查询条件删除,符合这些条件的数据都会被删除。
一、通过id删除
下面的例子是删除索引名为twitter,类型为tweet,id为1的文档:
- DeleteResponse response = client.prepareDelete("twitter", "tweet", "1")
- .execute()
- .actionGet();
DeleteResponse response = client.prepareDelete("twitter", "tweet", "1") .execute() .actionGet();
二、通过Query删除
下面的例子是删除索引名为productIndex,title中包含query的所有文档:
- QueryBuilder query = QueryBuilders.fieldQuery("title", "query");
- client.prepareDeleteByQuery("productIndex").setQuery(query).execute().actionGet();
QueryBuilder query = QueryBuilders.fieldQuery("title", "query"); client.prepareDeleteByQuery("productIndex").setQuery(query).execute().actionGet();
设置线程
当删除api在同一个节点上执行时(在一个分片中执行一个api会分配到同一个服务器上),删除api允许执行前设置线程模式(operationThreaded选项),operationThreaded这个选项是使这个操作在另外一个线程中执行,或在一个正在请求的线程(假设这个api仍是异步的)中执行。默认的话operationThreaded会设置成true,这意味着这个操作将在一个不同的线程中执行。下面是设置成false的方法:
- DeleteResponse response = client.prepareDelete("twitter", "tweet", "1")
- .setOperationThreaded(false)
- .execute()
- .actionGet();
DeleteResponse response = client.prepareDelete("twitter", "tweet", "1") .setOperationThreaded(false) .execute() .actionGet();
官方文档:
http://www.elasticsearch.org/guide/reference/api/delete.html
http://www.elasticsearch.org/guide/reference/java-api/delete.html
5. 搜索
elasticsearch的查询是通过执行json格式的查询条件,在java api中就是构造QueryBuilder对象,elasticsearch完全支持queryDSL风格的查询方式,QueryBuilder的构建类是QueryBuilders,filter的构建类是FilterBuilders。下面是构造QueryBuilder的例子:
- import static org.elasticsearch.index.query.FilterBuilders.*;
- import static org.elasticsearch.index.query.QueryBuilders.*;
- QueryBuilder qb1 = termQuery("name", "kimchy");
- QueryBuilder qb2 = boolQuery()
- .must(termQuery("content", "test1"))
- .must(termQuery("content", "test4"))
- .mustNot(termQuery("content", "test2"))
- .should(termQuery("content", "test3"));
- QueryBuilder qb3 = filteredQuery(
- termQuery("name.first", "shay"),
- rangeFilter("age")
- .from(23)
- .to(54)
- .includeLower(true)
- .includeUpper(false)
- );
import static org.elasticsearch.index.query.FilterBuilders.*; import static org.elasticsearch.index.query.QueryBuilders.*; QueryBuilder qb1 = termQuery("name", "kimchy"); QueryBuilder qb2 = boolQuery() .must(termQuery("content", "test1")) .must(termQuery("content", "test4")) .mustNot(termQuery("content", "test2")) .should(termQuery("content", "test3")); QueryBuilder qb3 = filteredQuery( termQuery("name.first", "shay"), rangeFilter("age") .from(23) .to(54) .includeLower(true) .includeUpper(false) );
其中qb1构造了一个TermQuery,对name这个字段进行项搜索,项是最小的索引片段,这个查询对应lucene本身的TermQuery。 qb2构造了一个组合查询(BoolQuery),其对应lucene本身的BooleanQuery,可以通过must、should、mustNot方法对QueryBuilder进行组合,形成多条件查询。 qb3构造了一个过滤查询,就是在TermQuery的基础上添加一个过滤条件RangeFilter,这个范围过滤器将限制查询age字段大于等于23,小于等于54的结果。除了这三个,elasticsearch还支持很多种类的查询方式,迟点写个介绍。
构造好了Query就要传到elasticsearch里面进行查询,下面是例子:
- SearchResponse response = client.prepareSearch("test")
- .setQuery(query)
- .setFrom(0).setSize(60).setExplain(true)
- .execute()
- .actionGet();
SearchResponse response = client.prepareSearch("test") .setQuery(query) .setFrom(0).setSize(60).setExplain(true) .execute() .actionGet();
这句的意思是,查询test索引,查询条件为query,从第0条记录开始,最多返回60条记录。返回结果为SearchResponse,下面解析SearchResponse:
- SearchHits hits = searchResponse.hits();
- for (int i = 0; i < 60; i++) {
- System.out.println(hits.getAt(i).getSource().get("field"));
- }
SearchHits hits = searchResponse.hits(); for (int i = 0; i < 60; i++) { System.out.println(hits.getAt(i).getSource().get("field")); }
获得SearchResponse中的SearchHits,然后hits.getAt(i).getSource().get("field")获得field字段的值。
6. 批量添加索引
elasticsearch支持批量添加或删除索引文档,java api里面就是通过构造BulkRequestBuilder,然后把批量的index/delete请求添加到BulkRequestBuilder里面,执行BulkRequestBuilder。下面是个例子:
- import static org.elasticsearch.common.xcontent.XContentFactory.*;
- BulkRequestBuilder bulkRequest = client.prepareBulk();
- bulkRequest.add(client.prepareIndex("twitter", "tweet", "1")
- .setSource(jsonBuilder()
- .startObject()
- .field("user", "kimchy")
- .field("postDate", new Date())
- .field("message", "trying out Elastic Search")
- .endObject()
- )
- );
- bulkRequest.add(client.prepareIndex("twitter", "tweet", "2")
- .setSource(jsonBuilder()
- .startObject()
- .field("user", "kimchy")
- .field("postDate", new Date())
- .field("message", "another post")
- .endObject()
- )
- );
- BulkResponse bulkResponse = bulkRequest.execute().actionGet();
- if (bulkResponse.hasFailures()) {
- //处理错误
- }
import static org.elasticsearch.common.xcontent.XContentFactory.*; BulkRequestBuilder bulkRequest = client.prepareBulk(); bulkRequest.add(client.prepareIndex("twitter", "tweet", "1") .setSource(jsonBuilder() .startObject() .field("user", "kimchy") .field("postDate", new Date()) .field("message", "trying out Elastic Search") .endObject() ) ); bulkRequest.add(client.prepareIndex("twitter", "tweet", "2") .setSource(jsonBuilder() .startObject() .field("user", "kimchy") .field("postDate", new Date()) .field("message", "another post") .endObject() ) ); BulkResponse bulkResponse = bulkRequest.execute().actionGet(); if (bulkResponse.hasFailures()) { //处理错误 }
7. 与MongoDB同步数据
elasticsearch提供river这个模块来读取数据源中的数据到es中,es官方有提供couchDB的同步插件,因为项目用到的是mongodb,所以在找mongodb方面的同步插件,在git上找到了elasticsearch-river-mongodb。
这个插件最初是由aparo写的,最开始的功能就是读取mongodb里面的表,记录最后一条数据的id,根据时间间隔不断访问mongodb,看看有没有大于之前记录的id的数据,有的话就索引数据,这种做法的缺点就是只能同步最新的数据,修改或删除的就不能同步。后来又由richardwilly98等人修改成通过读取mongodb的oplog来同步数据。因为mongodb是通过oplog这个表来使集群中的不同机器数据同步的,这样做的话可以保证es里面的数据和mongodb里面的是一样的,因为mongodb中的数据一有改变,都会通过oplog反映到monogodb中。他们还添加了个索引mongodb gridfs里文件的功能,非常好。
但他们修改完后的插件还是有些不满意的地方。他把local库(放oplog的)和普通库的访问密码都设置成同一个,如果local库和普通库的用户名和密码不同那这个插件就不能用了。还有一个就是同步时会把mongodb的表中所有的字段都同步过去,但是有些字段我们并不想把它放到索引中,于是对这个插件再作修改,把local库和普通库的鉴权分开,添加可选字段功能。
运行环境:Elasticsearch 0.19.X
集群环境下的MongoDB 2.X
注意:该插件只支持集群环境下的mongodb,因为集群环境下的mongodb才有oplog这个表。
安装方法:
安装elasticsearch-mapper-attachments插件(用于索引gridfs里的文件)
%ES_HOME%\bin\plugin.bat -install elasticsearch/elasticsearch-mapper-attachments/1.4.0
安装elasticsearch-river-mongodb(同步插件)
%ES_HOME%\bin\plugin.bat -install laigood/elasticsearch-river-mongodb/laigoodv1.0.0
创建river方法:
curl方式:
- $ curl -XPUT "localhost:9200/_river/mongodb/_meta" -d '
- {
- type: "mongodb",
- mongodb: {
- db: "test",
- host: "localhost",
- port: "27017",
- collection: "testdb",
- fields:"title,content",
- gridfs: "true",
- local_db_user: "admin",
- local_db_password:"admin",
- db_user: "user",
- db_password:"password"
- },
- index: {
- name: "test",
- type: "type",
- bulk_size: "1000",
- bulk_timeout: "30"
- }
- }
$ curl -XPUT "localhost:9200/_river/mongodb/_meta" -d ' { type: "mongodb", mongodb: { db: "test", host: "localhost", port: "27017", collection: "testdb", fields:"title,content", gridfs: "true", local_db_user: "admin", local_db_password:"admin", db_user: "user", db_password:"password" }, index: { name: "test", type: "type", bulk_size: "1000", bulk_timeout: "30" } }
db为同步的数据库名,
host mongodb的ip地址(默认为localhost),
port mongodb的端口,
collection 要同步的表名
fields 要同步的字段名(用逗号隔开,默认全部)
gridfs 是否是gridfs文件(如果collection是gridfs的话就设置成true)
local_db_user local数据库的用户名(没有的话不用写)
local_db_password local数据库的密码(没有的话不用写)
db_user 要同步的数据库的密码(没有的话不用写)
db_password 要同步的数据库的密码(没有的话不用写)
name 索引名(不能之前存在)
type 类型
bulk_size 批量添加的最大数
bulk_timeout 批量添加的超时时间
java api方式:
- client.prepareIndex("_river", "testriver", "_meta")
- .setSource(
- jsonBuilder().startObject()
- .field("type", "mongodb")
- .startObject("mongodb")
- .field("host","localhost")
- .field("port",27017)
- .field("db","testdb")
- .field("collection","test")
- .field("fields","title,content")
- .field("db_user","user")
- .field("db_password","password")
- .field("local_db_user","admin")
- .field("local_db_password","admin")
- .endObject()
- .startObject("index")
- .field("name","test")
- .field("type","test")
- .field("bulk_size","1000")
- .field("bulk_timeout","30")
- .endObject()
- .endObject()
- ).execute().actionGet();
client.prepareIndex("_river", "testriver", "_meta") .setSource( jsonBuilder().startObject() .field("type", "mongodb") .startObject("mongodb") .field("host","localhost") .field("port",27017) .field("db","testdb") .field("collection","test") .field("fields","title,content") .field("db_user","user") .field("db_password","password") .field("local_db_user","admin") .field("local_db_password","admin") .endObject() .startObject("index") .field("name","test") .field("type","test") .field("bulk_size","1000") .field("bulk_timeout","30") .endObject() .endObject() ).execute().actionGet();
本插件git地址:https://github.com/laigood/elasticsearch-river-mongodb
8. 使用More like this实现基于内容的推荐
基于内容的推荐通常是给定一篇文档信息,然后给用户推荐与该文档相识的文档。Lucene的api中有实现查询文章相似度的接口,叫MoreLikeThis。Elasticsearch封装了该接口,通过Elasticsearch的More like this查询接口,我们可以非常方便的实现基于内容的推荐。
先看一个查询请求的json例子:
{ "more_like_this" : { "fields" : ["title", "content"], "like_text" : "text like this one", } }
其中:
fields是要匹配的字段,如果不填的话默认是_all字段
like_text是匹配的文本。
除此之外还可以添加下面条件来调节结果
percent_terms_to_match:匹配项(term)的百分比,默认是0.3
min_term_freq:一篇文档中一个词语至少出现次数,小于这个值的词将被忽略,默认是2
max_query_terms:一条查询语句中允许最多查询词语的个数,默认是25
stop_words:设置停止词,匹配时会忽略停止词
min_doc_freq:一个词语最少在多少篇文档中出现,小于这个值的词会将被忽略,默认是无限制
max_doc_freq:一个词语最多在多少篇文档中出现,大于这个值的词会将被忽略,默认是无限制
min_word_len:最小的词语长度,默认是0
max_word_len:最多的词语长度,默认无限制
boost_terms:设置词语权重,默认是1
boost:设置查询权重,默认是1
analyzer:设置使用的分词器,默认是使用该字段指定的分词器
下面介绍下如何用java api调用,一共有三种调用方式,不过本质上都是一样的,只不过是做了一些不同程度的封装。
- MoreLikeThisRequestBuilder mlt = new MoreLikeThisRequestBuilder(client, "indexName", "indexType", "id");
- mlt.setField("title");//匹配的字段
- SearchResponse response = client.moreLikeThis(mlt.request()).actionGet();
MoreLikeThisRequestBuilder mlt = new MoreLikeThisRequestBuilder(client, "indexName", "indexType", "id"); mlt.setField("title");//匹配的字段 SearchResponse response = client.moreLikeThis(mlt.request()).actionGet();
这种是在查询与某个id的文档相似的文档。这个接口是直接在client那调用的,比较特殊。还有两种就是构造Query进行查询
- MoreLikeThisQueryBuilder query = QueryBuilders.moreLikeThisQuery();
- query.boost(1.0f).likeText("xxx").minTermFreq(10);
MoreLikeThisQueryBuilder query = QueryBuilders.moreLikeThisQuery(); query.boost(1.0f).likeText("xxx").minTermFreq(10);
这里的boost、likeText方法完全和上面的参数对应的。下面这种就是把要匹配的字段作为参数传进来,参数和MoreLikeThisQueryBuilder是一样的。
- MoreLikeThisFieldQueryBuilder query = QueryBuilders.moreLikeThisFieldQuery("fieldNmae");
相关推荐
标签《ES Java API 中文文档》强调了文档的内容属性,它属于ElasticSearch的一个重要组成部分,即用Java语言进行数据交互和操作的应用程序接口部分。 从部分内容中可以提取出以下知识点: 1. **Transport Client**...
**Elasticsearch Java API详解** Elasticsearch是一个分布式、RESTful风格的搜索和数据分析引擎,广泛应用于大数据领域的实时分析和信息检索。Java API是Elasticsearch官方提供的与Elasticsearch服务器进行交互的...
ElasticSearch 官方 java API
**Elasticsearch Java API**是Elasticsearch官方提供的用于与Elasticsearch服务器进行交互的Java客户端库。在Java应用程序中,我们通常会使用这个API来创建、查询、更新和删除索引中的数据。Elasticsearch 2.3版本的...
**Elasticsearch Java API**是Elasticsearch与Java应用程序交互的主要工具,它允许开发者在Java环境中无缝地创建、管理和查询Elasticsearch索引。Elasticsearch是一个分布式、RESTful风格的搜索和数据分析引擎,用于...
**Elasticsearch Java API 离线文档** Elasticsearch 是一个开源的全文搜索引擎,它提供了高度可扩展的、实时的搜索与分析引擎服务。Java API 是 Elasticsearch 提供的用于与集群交互的主要接口,允许开发者在 Java...
**Elasticsearch离线Java API文档详解** Elasticsearch(简称ES)是一款强大的开源搜索引擎,广泛应用于数据分析、日志分析和全文检索等场景。它的Java API是开发人员与Elasticsearch进行交互的主要工具,提供了...
ElasticSearch Java API 1.5.chm
Elasticsearch Java api 工具类,包括增删改查索引,增删改查数据等代码,欢迎下载
《Elasticsearch Java API 手册》是针对Java开发者深入理解和使用Elasticsearch的重要参考资料。Elasticsearch是一款功能强大的开源搜索引擎,广泛应用于日志分析、实时监控、数据存储与检索等多个领域。它基于...
标签:elasticsearch、中文文档、jar包、java; 使用方法:解压翻译后的API文档,用浏览器打开“index.html”文件,即可纵览文档内容。 人性化翻译,文档中的代码和结构保持不变,注释和说明精准翻译,请放心使用。
标签:elasticsearch、中文文档、jar包、java; 使用方法:解压翻译后的API文档,用浏览器打开“index.html”文件,即可纵览文档内容。 人性化翻译,文档中的代码和结构保持不变,注释和说明精准翻译,请放心使用。
在这个基于Elasticsearch 2.1.1的Java API基本操作代码示例中,我们将探讨如何利用Java API进行常见的数据操作,如索引创建、文档插入、查询以及更新。 首先,为了使用Elasticsearch的Java API,我们需要在项目中...
Java API是Elasticsearch与Java应用程序进行交互的主要方式,提供了丰富的类库和方法,使得开发者能够方便地在Java应用中集成Elasticsearch的功能。 在描述中提到的"Elasticsearch 5.1.1 java api maven工程"是指一...
《从Lucene到Elasticsearch全文检索实战》第8章Elasticsearch Java API测试案例代码
标签:elasticsearch、中文文档、jar包、java; 使用方法:解压翻译后的API文档,用浏览器打开“index.html”文件,即可纵览文档内容。 人性化翻译,文档中的代码和结构保持不变,注释和说明精准翻译,请放心使用。