公号:码农充电站pro

主页:https://codeshellme.github.io

1,ES 中的 Mapping

ES 中的 Mapping 相当于传统数据库中的表定义,它有以下作用:

  • 定义索引中的字段的名字。
  • 定义索引中的字段的类型,比如字符串,数字等。
  • 定义索引中的字段是否建立倒排索引。

一个 Mapping 是针对一个索引中的 Type 定义的:

  • ES 中的文档都存储在索引的 Type 中
  • ES 7.0 之前,一个索引可以有多个 Type,所以一个索引可拥有多个 Mapping
  • ES 7.0 之后,一个索引只能有一个 Type,所以一个索引只对应一个 Mapping

通过下面语法可以获取一个索引的 Mapping 信息:

GET index_name/_mapping

2,ES 字段的 mapping 参数

字段的 mapping 可以设置很多参数,如下:

  • analyzer:指定分词器,只有 text 类型的数据支持。
  • enabled:如果设置成 false,表示数据仅做存储,不支持搜索和聚合分析(数据保存在 _source 中)。
    • 默认值为 true
  • index:字段是否建立倒排索引。
    • 如果设置成 false,表示不建立倒排索引(节省空间),同时数据也无法被搜索,但依然支持聚合分析,数据也会出现在 _source 中。
    • 默认值为 true
  • norms:字段是否支持算分。
    • 如果字段只用来过滤和聚合分析,而不需要被搜索(计算算分),那么可以设置为 false,可节省空间。
    • 默认值为 true
  • doc_values:如果确定不需要对字段进行排序或聚合,也不需要从脚本访问字段值,则可以将其设置为 false,以节省磁盘空间。
    • 默认值为 true
  • fielddata:如果要对 text 类型的数据进行排序和聚合分析,则将其设置为 true
    • 默认为 false
  • store:默认值为 false,数据存储在 _source 中。
    • 默认情况下,字段值被编入索引以使其可搜索,但它们不会被存储。这意味着可以查询字段,但无法检索原始字段值。
    • 在某些情况下,存储字段是有意义的。例如,有一个带有标题、日期和非常大的内容字段的文档,只想检索标题和日期,而不必从一个大的源字段中提取这些字段。
  • boost:可增强字段的算分。
  • coerce:是否开启数据类型的自动转换,比如字符串转数字。
    • 默认是开启的。
  • dynamic:控制 mapping 的自动更新,取值有 truefalsestrict
  • eager_global_ordinals
  • fields:多字段特性。
    • 一个字段拥有多个子字段类型,使得一个字段能够被多个不同的索引方式进行索引。
  • copy_to
  • format
  • ignore_above
  • ignore_malformed
  • index_options
  • index_phrases
  • index_prefixes
  • meta
  • normalizer
  • null_value:定义 null 的值。
  • position_increment_gap
  • properties
  • search_analyzer
  • similarity
  • term_vector

2.1,fields 参数

一个字段拥有多个子字段类型,使得一个字段能够被多个不同的索引方式进行索引。

示例 1:

PUT index_name
{
"mappings": { # 设置 mappings
"properties": { # 属性,固定写法
"city": { # 字段名
"type": "text", # city 字段的类型为 text
"fields": { # 多字段域,固定写法
"raw": { # 子字段名称
"type": "keyword" # 子字段类型
}
}
}
}
}
}

示例 2 :

PUT index_name
{
"mappings": {
"properties": {
"title": { # 字段名称
"type": "text", # 字段类型
"analyzer": "english", # 字段分词器
"fields": { # 多字段域,固定写法
"std": { # 子字段名称
"type": "text", # 子字段类型
"analyzer": "standard" # 子字段分词器
}
}
}
}
}
}

3,ES 字段的数据类型

ES 中字段的数据类型有以下这些:

text 类型与 keyword 类型

字符串数据可以定义成 textkeyword 类型,text 类型数据会做分词处理,而 keyword 类型数据不会做分词处理。

数组类型

对于数组类型 Arrays,ES 并没有提供专门的数组类型,但是任何字段都可以包含多个相同类型的数据,比如:

["one", "two"] # 一个字符串数组
[1, 2] # 一个整数数组
[1, [ 2, 3 ]] # 相当于 [ 1, 2, 3 ]
[{ "name": "Mary", "age": 12 }, { "name": "John", "age": 10 }] # 一个对象数组

当在 Mapping 中查看这些数组的类型时,其实还是数组中的元素的类型,而不是一个数组类型

3.1,Nested 类型

Nested 是一种对象类型,它保留了子字段之间的关系。

1,为什么需要 Nested 类型

假如我们有如下结构的数据:

POST my_movies/_doc/1
{
"title":"Speed",
"actors":[ # actors 是一个数组类型,数组中的元素是对象类型
{
"first_name":"Keanu",
"last_name":"Reeves"
},
{
"first_name":"Dennis",
"last_name":"Hopper"
}
]
}

将数据插入 ES 之后,执行下面的查询:

# 查询电影信息
POST my_movies/_search
{
"query": {
"bool": {
"must": [
{"match": {"actors.first_name": "Keanu"}},
{"match": {"actors.last_name": "Hopper"}}
]
}
}
}

按照上面的查询语句,我们想查询的是 first_name=Keanulast_name=Hopper 的数据,所以我们刚才插入的 id 为 1 的文档应该不符合这个查询条件。

但是在 ES 中执行上面的查询语句,却能查出 id 为 1 的文档。这是为什么呢?

这是因为,ES 对于这种 actors 字段这样的结构的数据,ES 并没有考虑对象的边界

实际上,在 ES 内部,id 为 1 的那个文档是这样存储的:

"title":"Speed"
"actors.first_name":["Keanu","Dennis"]
"actors.last_name":["Reeves","Hopper"]

所以这种存储方式,并不是我们想象的那样。

如果我们查看 ES 默认为上面(id 为 1)结构的数据生成的 mappings,如下:

{
"my_movies" : {
"mappings" : {
"properties" : {
"actors" : { # actors 内部又嵌套了一个 properties
"properties" : {
"first_name" : { # 定义 first_name 的类型
"type" : "text",
"fields" : {
"keyword" : {"type" : "keyword", "ignore_above" : 256}
}
},
"last_name" : { # 定义 last_name 的类型
"type" : "text",
"fields" : {
"keyword" : {"type" : "keyword", "ignore_above" : 256}
}
}
}
}, # end actors
"title" : {
"type" : "text",
"fields" : {
"keyword" : {"type" : "keyword", "ignore_above" : 256}
}
}
}
}
}
}

那如何才能真正的表达一个对象类型呢?这就需要使用到 Nested 类型。

2,使用 Nested 类型

Nested 类型允许对象数组中的对象被独立(看作一个整体)索引。

我们对 my_movies 索引设置这样的 mappings

DELETE my_movies
PUT my_movies
{
"mappings" : {
"properties" : {
"actors" : {
"type": "nested", # 将 actors 设置为 nested 类型
"properties" : { # 这时 actors 数组中的每个对象就是一个整体了
"first_name" : {"type" : "keyword"},
"last_name" : {"type" : "keyword"}
}},
"title" : {
"type" : "text",
"fields" : {"keyword":{"type":"keyword","ignore_above":256}}
}
}
}
}

写入数据后,在进行这样的搜索,就不会搜索出数据了:

# 查询电影信息
POST my_movies/_search
{
"query": {
"bool": {
"must": [
{"match": {"actors.first_name": "Keanu"}},
{"match": {"actors.last_name": "Hopper"}}
]
}
}
}

但是这样的查询也查不出数据:

POST my_movies/_search
{
"query": {
"bool": {
"must": [
{"match": {"actors.first_name": "Keanu"}},
{"match": {"actors.last_name": "Reeves"}}
]
}
}
}
3,搜索 Nested 类型

这是因为,查询 Nested 类型的数据,要像下面这样查询:

POST my_movies/_search
{
"query": {
"bool": {
"must": [
{
"nested": { # nested 查询
"path": "actors", # 自定 actors 字段路径
"query": { # 查询语句
"bool": {
"must": [
{"match": {"actors.first_name": "Keanu"}},
{"match": {"actors.last_name": "Hopper"}}
]
}
}
} # end nested
}
] # end must
} # end bool
}
}
4,聚合 Nested 类型

对 Nested 类型的数据进行聚合,示例:

# Nested Aggregation
POST my_movies/_search
{
"size": 0,
"aggs": {
"actors": { # 自定义聚合名称
"nested": { # 指定 nested 类型
"path": "actors" # 聚合的字段名称
},
"aggs": { # 子聚合
"actor_name": { # 自定义子聚合名称
"terms": { # terms 聚合
"field": "actors.first_name", # 子字段名称
"size": 10
}
}
}
}
}
}

使用普通的聚合方式则无法工作

POST my_movies/_search
{
"size": 0,
"aggs": {
"actors": { # 自定义聚合名称
"terms": { # terms 聚合
"field": "actors.first_name",
"size": 10
}
}
}
}

3.2,Join 类型

Nested 类型的对象与其父/子级文档的关系,使得每次文档有更新的时候需要重建整个文档(包括根对象和嵌套对象)的索引。

Join 数据类型(类似关系型数据库中的 Join 操作)为同一索引中的文档定义父/子关系。

Join 数据类型可以维护一个父/子关系,从而分离两个对象,它的优点是:

  • 父文档和子文档是两个完全独立的文档,这使得更新父文档不会影响到子文档,更新子文档也不会影响到父文档。

Nested 类型与 Join(Parent/Child) 类型的优缺点对比

1,定义 Join 类型

定义 Join 类型的语法如下:

DELETE my_blogs

# 设定 Parent/Child Mapping
PUT my_blogs
{
"mappings": {
"properties": {
"blog_comments_relation": { # 字段名称
"type": "join", # 定义 join 类型
"relations": { # 定义父子关系
"blog": "comment" # blog 表示父级文档,comment 表示子级文档
}
},
"content": {
"type": "text"
},
"title": {
"type": "keyword"
}
}
}
}
2,插入 Join 数据

先插入两个父文档:

# 插入 blog1
PUT my_blogs/_doc/blog1
{
"title":"Learning Elasticsearch",
"content":"learning ELK @ geektime",
"blog_comments_relation":{
"name":"blog" # name 为 blog 表示父文档
}
} # 插入 blog2
PUT my_blogs/_doc/blog2
{
"title":"Learning Hadoop",
"content":"learning Hadoop",
"blog_comments_relation":{
"name":"blog" # name 为 blog 表示父文档
}
}

插入子文档:

  • 其中需要注意 routing 的值是父文档 id
  • 这样可以确保父子文档被索引到相同的分片,从而确保 join 查询的性能
# 插入comment1
PUT my_blogs/_doc/comment1?routing=blog1 # routing 的值是父文档 id
{ # 确保父子文档被索引到相同的分片
"comment":"I am learning ELK",
"username":"Jack",
"blog_comments_relation":{
"name":"comment", # name 为 comment 表示子文档
"parent":"blog1" # 指定父文档的 id,表示子文档属于哪个父文档
}
} # 插入 comment2
PUT my_blogs/_doc/comment2?routing=blog2 # routing 的值是父文档 id
{ # 确保父子文档被索引到相同的分片
"comment":"I like Hadoop!!!!!",
"username":"Jack",
"blog_comments_relation":{
"name":"comment", # name 为 comment 表示子文档
"parent":"blog2" # 指定父文档的 id,表示子文档属于哪个父文档
}
} # 插入 comment3
PUT my_blogs/_doc/comment3?routing=blog2 # routing 的值是父文档 id
{ # 确保父子文档被索引到相同的分片
"comment":"Hello Hadoop",
"username":"Bob",
"blog_comments_relation":{
"name":"comment", # name 为 comment 表示子文档
"parent":"blog2" # 指定父文档的 id,表示子文档属于哪个父文档
}
}
3,parent_id 查询

根据父文档 id 来查询父文档,普通的查询无法查出子文档的信息:

GET my_blogs/_doc/blog2

如果想查到子文档的信息,需要使用 parent_id 查询:

POST my_blogs/_search
{
"query": {
"parent_id": { # parent_id 查询
"type": "comment", # comment 表示是子文档,即是表示想查询子文档信息
"id": "blog2" # 指定父文档的 id
} # 这样可以查询到 blog2 的所有 comment
}
}
4,has_child 查询

has_child 查询可以通过子文档的信息,查到父文档信息

POST my_blogs/_search
{
"query": {
"has_child": { # has_child 查询
"type": "comment", # 指定子文档类型,表示下面的 query 中的信息要在 comment 子文档中匹配
"query" : {
"match": {"username" : "Jack"}
} # 在子文档中匹配信息,最终返回所有的相关父文档信息
}
}
}
5,has_parent 查询

has_parent 查询可以通过父文档的信息,查到子文档信息

POST my_blogs/_search
{
"query": {
"has_parent": { # has_parent 查询
"parent_type": "blog", # 指定子文档类型,表示下面的 query 中的信息要在 blog 父文档中匹配
"query" : {
"match": {"title" : "Learning Hadoop"}
} # 在父文档中匹配信息,最终返回所有的相关子文档信息
}
}
}
6,通过子文档 id 查询子文档信息

普通的查询无法查到

GET my_blogs/_doc/comment3

需要指定 routing 参数,提供父文档 id

GET my_blogs/_doc/comment3?routing=blog2
7,更新子文档信息

更新子文档不会影响到父文档

示例:

# URI 中指定子文档 id,并通过 routing 参数指定父文档 id
PUT my_blogs/_doc/comment3?routing=blog2
{
"comment": "Hello Hadoop??",
"blog_comments_relation": {
"name": "comment",
"parent": "blog2"
}
}

4,ES 动态 Mapping

ES 中的动态 Mapping 指的是:

  • 在写入新文档的时候,如果索引不存在,ES 会自动创建索引。
  • 动态 Mapping 使得我们可以不定义 Mapping,ES 会自动根据文档信息,推断出字段的类型。
  • 但有时候也会推断错误,不符合我们的预期,比如地理位置信息等。

ES 类型的自动识别规则如下:

5,修改文档字段类型

字段类型是否能够修改,分两种情况:

  • 对于新增字段:

    • 如果 mappings._doc.dynamicture,当有新字段写入时,Mappings 会自动更新。
    • 如果 mappings._doc.dynamicfalse,当有新字段写入时,Mappings 不会更新;新增字段不会建立倒排索引,但是信息会出现在 _source 中。
    • 如果 mappings._doc.dynamicstrict,当有新字段写入时,写入失败。
  • 对于已有字段:
    • 字段的类型不允许再修改。因为如果修改了,会导致已有的信息无法被搜索。
    • 如果希望修改字段类型,需要 Reindex 重建索引。

dynamic 有 3 种取值,使用下面 API 可以修改 dynamic 的值:

PUT index_name/_mapping
{
"dynamic": false/true/strict
}

通过下面语法可以获取一个索引的 Mapping:

GET index_name/_mapping

6,自定义 Mapping

自定义 Mapping 的语法如下:

PUT index_name
{
"mappings" : {
# 定义
}
}

自定义 Mapping 的小技巧:

  1. 创建一个临时索引,写入一些测试数据
  2. 获取该索引的 Mapping 值,修改后,使用它创建新的索引
  3. 删除临时索引

Mappings 有很多参数可以设置,可以参考这里

6.1,一个嵌套对象的 mappings

如果我们要在 ES 中插入如下结构的数据:

PUT blog/_doc/1
{
"content":"I like Elasticsearch",
"time":"2019-01-01T00:00:00",
"user": { # 是一个对象类型
"userid":1,
"username":"Jack",
"city":"Shanghai"
}
}

其中的 user 字段是一个对象类型

这种结构的数据对应的 mappings 应该像下面这样定义:

PUT /blog
{
"mappings": {
"properties": {
"content": {
"type": "text"
},
"time": {
"type": "date"
},
"user": { # user 内部又嵌套了一个 properties
"properties": {
"city": {
"type": "text"
},
"userid": {
"type": "long"
},
"username": {
"type": "keyword"
}
}
}
}
}
}

6.2,一个对象数组的 mappings

如果我们要在 ES 中插入如下结构的数据:

POST my_movies/_doc/1
{
"title":"Speed",
"actors":[ # actors 是一个数组类型,数组中的元素是对象类型
{
"first_name":"Keanu",
"last_name":"Reeves"
},
{
"first_name":"Dennis",
"last_name":"Hopper"
}
]
}

其中的 actors 字段是一个数组类型,数组中的元素是对象类型。

像这种结构的数据对应的 mappings 应该像下面这样定义:

PUT my_movies
{
"mappings": {
"properties": {
"actors": { # actors 字段
"properties": { # 嵌入了一个 properties
"first_name": {"type": "keyword"},
"last_name": {"type": "keyword"}
}
},
"title": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}

7,控制字段是否可被索引

可以通过设置字段的 index 值,来控制某些字段是否可被搜索。

index 有两种取值:true / false,默认为 true

当某个字段的 index 值为 false 时,ES 就不会为该字段建立倒排索引(节省空间),该字段也不能被搜索(如果搜索的话会报错)。

设置语法如下:

PUT index_name
{
"mappings" : { # 固定写法
"properties" : { # 固定写法
"firstName" : { # 字段名
"type" : "text"
},
"lastName" : { # 字段名
"type" : "text"
},
"mobile" : { # 字段名
"type" : "text",
"index": false # 设置为 false
}
}
}
}

8,控制倒排索引项的内容

我们可以通过设置 index_options 的值来控制倒排索引项的内容,它有 4 种取值:

  • docs:只记录文档 id
  • freqs:记录文档 id词频
  • positions:记录文档 id词频单词 position
  • offsets:记录文档 id词频单词 position字符 offset

Text 类型的数据,index_options 的值默认positions其它类型的数据,index_options 的值默认docs

注意:对于 index_options 的默认值,不同版本的 ES,可能不一样,请查看相应版本的文档。

对于倒排索引项,其记录的内容越多,占用的空间也就越大,同时 ES 也会对字段进行更多的分析。

设置语法如下:

PUT index_name
{
"mappings": { # 固定写法
"properties": { # 固定写法
"text": { # 字段名
"type": "text", # 字段的数据类型
"index_options": "offsets" # index_options 值
}
}
}
}

9,设置 null 值可被搜索

默认情况下 null 和 空数组[] 是不能够被搜索的,比如下面的两个文档:

PUT my_index/_doc/1
{
"status_code": null
} PUT my_index/_doc/2
{
"status_code": []
}

要想使得这两个文档能够被搜索,需要设置 null_value 参数,如下:

PUT my_index
{
"mappings": {
"properties": {
"status_code": {
"type": "keyword", # 只有 Keyword 类型的数据,才支持设置 null_value
"null_value": "NULL" # 将 null_value 设置为 NULL,就可以通过 NULL 搜索了
}
}
}
}

注意只有 Keyword 类型的数据,才支持设置 null_value,将 null_value 设置为 NULL,就可以通过 NULL 搜索了,如下:

GET my-index/_search?q=status_code:NULL

10,索引模板

索引模板(Index Template)设置一个规则,自动生成索引的 Mappings 和 Settings。

索引模板有以下特性

  • 模板只在索引创建时起作用,修改模板不会影响已创建的索引。
  • 可以设置多个索引模板,这些设置会被 merge 在一起。
  • 可以设置 order 的数值,控制 merge 的过程。

多个模板时的 merge 规则,当一个索引被创建时:

  • 使用 ES 默认的 mappings 和 settings。
  • 使用 order 值低的模板。
  • 使用 order 值高的模板,它会覆盖 order 值低的模板。
  • 使用用户自带的,指定的 mappings 和 settings,这个级别的最高,会覆盖之前所有的。

对于相同字段的不同只会进行覆盖,对于不同的字段会进行叠加依次使用。

索引模板示例:

PUT _template/template_1  # template_1 是自定义的索引模板的名称
{
"index_patterns": ["te*", "bar*"], # 匹配索引的规则,该模板会作用于这些索引名上
"settings": { # settings 设置
"number_of_shards": 1
},
"mappings": { # mappings 设置
"_source": {
"enabled": false
},
"properties": {
"host_name": {
"type": "keyword"
},
"created_at": {
"type": "date",
"format": "EEE MMM dd HH:mm:ss Z yyyy"
}
}
}
}

多个索引模板:

PUT /_template/template_1
{
"index_patterns" : ["*"],
"order" : 0,
"settings" : {
"number_of_shards" : 1
},
"mappings" : {
"_source" : { "enabled" : false }
}
} PUT /_template/template_2
{
"index_patterns" : ["te*"],
"order" : 1,
"settings" : {
"number_of_shards" : 1
},
"mappings" : {
"_source" : { "enabled" : true }
}
}

11,动态模板

动态模板(Dynamic Template)用于设置某个指定索引中的字段的数据类型

(本节完。)


推荐阅读:

ElasticSearch URI 查询

ElasticSearch DSL 查询

ElasticSearch 文档及操作

ElasticSearch 搜索模板与建议

ElasticSearch 聚合分析


欢迎关注作者公众号,获取更多技术干货。

ElasticSearch 中的 Mapping的更多相关文章

  1. elasticsearch中的mapping映射配置与查询典型案例

    elasticsearch中的mapping映射配置与查询典型案例 elasticsearch中的mapping映射配置示例比如要搭建个中文新闻信息的搜索引擎,新闻有"标题".&q ...

  2. elasticsearch中的mapping简介

    默认mapping elasticsearch(以下简称ES)是没有模式(schema)的,当我们执行以下命令: curl -d '{"name":"zach" ...

  3. 如何在Elasticsearch中安装中文分词器(IK+pinyin)

    如果直接使用Elasticsearch的朋友在处理中文内容的搜索时,肯定会遇到很尴尬的问题--中文词语被分成了一个一个的汉字,当用Kibana作图的时候,按照term来分组,结果一个汉字被分成了一组. ...

  4. 使用Hive或Impala执行SQL语句,对存储在Elasticsearch中的数据操作(二)

    CSSDesk body { background-color: #2574b0; } /*! zybuluo */ article,aside,details,figcaption,figure,f ...

  5. 使用Hive或Impala执行SQL语句,对存储在Elasticsearch中的数据操作

    http://www.cnblogs.com/wgp13x/p/4934521.html 内容一样,样式好的版本. 使用Hive或Impala执行SQL语句,对存储在Elasticsearch中的数据 ...

  6. elasticsearch的映射(mapping)和分析(analysis)

    转发自:http://blog.csdn.net/hzrandd/article/details/47128895 分析和分析器 分析(analysis)是这样一个过程: 首先,表征化一个文本块为适用 ...

  7. ES 15 - Elasticsearch中的数据类型 (text、keyword、date、geo等)

    目录 1 核心数据类型 1.1 字符串类型 - string(不再支持) 1.1.1 文本类型 - text 1.1.2 关键字类型 - keyword 1.2 数字类型 - 8种 1.3 日期类型 ...

  8. ES 11 - 配置Elasticsearch的映射 (mapping)

    目录 1 映射的相关概念 1.1 什么是映射 1.2 映射的组成 1.3 元字段 1.4 字段的类型 2 如何配置mapping 2.1 创建mapping 2.2 更新mapping 2.3 查看m ...

  9. Elasticsearch学习之图解Elasticsearch中的_source、_all、store和index属性

    转自 : https://blog.csdn.net/napoay/article/details/62233031 1. 概述 Elasticsearch中有几个关键属性容易混淆,很多人搞不清楚_s ...

随机推荐

  1. 图的深度优先遍历算法(DFS)

    搜索算法有很多种,本次文章主要分享图(无向图)的深度优先算法.深度优先算法(DFS)主要是应用于搜索中,早期是在爬虫中使用.其主要的思想有如下: 1.先访问一个节点v,然后标记为已被访问过2.找到第一 ...

  2. 关于RabbitMQ的简单理解

    说明:想要理解RabbitMQ,需要先理解MQ是什么?能做什么?然后根据基础知识去理解RabbitMQ是什么.提供了什么功能. 一.MQ的简单理解 1. 什么是MQ? 消息队列(Message Que ...

  3. Codeforces Round #305 (Div. 1) B. Mike and Feet

    Mike is the president of country What-The-Fatherland. There are n bears living in this country besid ...

  4. .net core mvc 获取Web根目录和内容根目录的物理路径

    从ASP.NET Core RC2开始,可以通过注入 IHostingEnvironment 服务对象来取得Web根目录和内容根目录的物理路径,如下所示: using Microsoft.AspNet ...

  5. canvas绘制五星红旗

    代码 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8& ...

  6. 重新上手c语言的一些坑

    c语言结构体不能声明函数,放几个函数指针倒是没问题 c语言结构体不能在声明时初始化 声明两个指针 int *a,*b; 或者typedef int* int_P int_P a,b; typedef要 ...

  7. USB2.0协议学习笔记---基本概念

    概念  USB是一种串行通信总线(Universal Serial Bus),经历的版本有USB1.0,USB1.1.USB2.0等.USB是一种主从模式的结构,因此它无法在设备与设备.主机与主机之间 ...

  8. Hive Tutorial 阅读记录

    Hive Tutorial 目录 Hive Tutorial 1.Concepts 1.1.What Is Hive 1.2.What Hive Is NOT 1.3.Getting Started ...

  9. gradle中的增量构建

    目录 简介 增量构建 自定义inputs和outputs 运行时API 隐式依赖 输入校验 自定义缓存方法 输入归一化 其他使用技巧 gradle中的增量构建 简介 在我们使用的各种工具中,为了提升工 ...

  10. yarn global add !== yarn add global

    yarn global add !== yarn add global yarn does not exist the --global flag, but exits yarn global com ...