Elasticsearch:painless script 语法基础和实战

摘要:ElasticsearchJava

script的作用

script是Elasticsearch的拓展功能,通过定制的表达式实现已经预设好的API无法完成的个性化需求,比如完成以下操作

  • 字段再加工/统计输出
  • 字段之间逻辑运算
  • 定义查询得分的计算公式
  • 定义特殊过滤条件完成搜索
  • 类似于pandas的个性化增删改操作
内容概述
  • (1)script格式说明,inline和stored脚本的调用方法
  • (2)在无新增文档的情况下,对现有文档的字段个性化字段更新(update_update_by_queryctx._source,Math,数组add/remove)
  • (3)在不修改文档的情况下,在搜索返回中添加个性化统计字段(_searchdocscript_fieldsreturn
  • (4)在无新增文档的情况下,对现有文档的字段进行新增和删除(ctx._sourcectx._source.remove,条件判断)
  • (5)在无新增文档的情况下,基于现有的多个字段生成新字段(加权求和,大小比较)
  • (6)搜索文档时使用script脚本
  • (7)其他painless语法(循环,null判断)

script格式

语法都遵循相同的模式

"script": {"lang":   "...",  "source" | "id": "...", "params": { ... } }

其中三要素功能如下

  • lang:指定编程语言,默认是painless,还有其他编程语言选项如expression
  • source | id: source,id二者选其一,source后面接inline脚本(就是将脚本逻辑直接放在DSL里面),id对应一个stored脚本(就是预先设置类似UDF,使用的时候根据UDF的id进行调用和传参
  • params:在脚本中任何有名字的参数,用params传参

inline和stored脚本快速开始

使用script脚本修改某文档的某个字段,先插入一条文档

POST /hotel/_doc/100
{"name": "苏州木棉花酒店","city": "苏州","price": 399,"start_date": "2023-01-01"
}
(1)使用inline的方式将脚本写在DSL里面

POST /hotel/_doc/100/_update
{"script": {"source": "ctx._source.price=333"}
}

注意在kibiban客户端带上_update,否则相当于覆盖整个文档,新建了一个含有script字段的文档。本例中将price字段修改为333,如果是带有单引号的'333'则修改为字符串数据,字符串还可以使用\转义

POST /hotel/_doc/100/_update
{"script": {"source": "ctx._source.price=\"333\""}
}

获取字段的方式除了使用ctx._source.字段之外,还可以ctx._source['字段']

POST /hotel/_doc/100/_update
{"script": {"source": "ctx._source['price']=333"}
}

只要inline脚本中的内容出现些许不一样就需要重新编译,因此推荐的方法是把inline中固定的部分编译一次,变量命名放在params中传参使用,这样只需要编译一次,下次使用调用缓存

POST /hotel/_doc/100/_update
{"script": {"source": "ctx._source.price=params.price","params": {"price": 334}}
}
(2)使用stored预先设置脚本的方式

这种类似于先注册UDF函数,使用PUT_scripts传入脚本

PUT /_scripts/my_script_1
{"script": {"lang": "painless", "source": "ctx._source.price=params.price"}
}

在插入之后使用GET可以查看到对应的脚本内容

GET /_scripts/my_script_1
{"_id" : "my_script_1","found" : true,"script" : {"lang" : "painless","source" : "ctx._source.price=params.price"}
}

脚本中并没有指定params,params在调用的是有进行设置,调用的时候使用id指定my_script_1这个id即可,不再使用source

POST /hotel/_doc/100/_update
{"script": {"id": "my_script_1","params": {"price": 335}}
}

script脚本更新字段

所有update/update_by_query 脚本使用 ctx._source

(1)普通字段更新

除了上面快速开始的直接使用=赋值修改的情况,还可以对字段做数值运算,比如加减乘除开方等等

POST /hotel/_doc/100/_update
{"script": {"source": "ctx._source.price += 100"}
}

使用Math.pow对数值进行开方

POST /hotel/_doc/100/_update
{"script": {"source": "ctx._source.price=Math.pow(ctx._source.price, 2)"}
}

Math下的方法还有sqrtlog

(2)集合字段更新

主要说明下数组类型字段的更新,使用ctx._source.字段.add/remove,先新建一个带有数组字段的文档

POST /hotel/_doc/101
{"name": "苏州大酒店","city": "苏州","tag": ["贵"]
}

使用script将tag数组字段增加元素,使用add

POST /hotel/_doc/101/_update
{"script": {"source": "ctx._source.tag.add('偏')"}
}

插入新元素后看下数据,已经成功

      {"_index" : "hotel","_type" : "_doc","_id" : "101","_score" : 1.0,"_source" : {"name" : "苏州大酒店","city" : "苏州","tag" : ["贵","偏"]}

删除数组元素使用remove指定对应的索引位置即可

POST /hotel/_doc/101/_update
{"script": {"source": "ctx._source.tag.remove(0)"}
}

如果位数不足会报错类似数组越界


script脚本对字段再加工返回

此功能使用search脚本,配合script中的doc实现,整体效果类似于map操作,对所选定的文档操作返回

(1)提取日期类型的元素并返回一个自定义字段

先设置一个字段schema

POST /hotel/_doc/_mapping
{"properties": {"dt": {"type": "date", "format":  "yyyy-MM-dd HH:mm:ss"}}
}

插入一条日期数据

POST  /hotel/_doc/301
{"dt": "2021-01-01 13:13:13"
}

插入效果如下

      {"_index" : "hotel","_type" : "_doc","_id" : "301","_score" : 1.0,"_source" : {"dt" : "2021-01-01 13:13:13"}

下面检索所有文档,提取日期的年份,使用GET+_search请求,DSL中指定script_fields的自定义字段year,给year设置script脚本

GET /hotel/_doc/_search
{"script_fields": {"year": {"script": {"source": "if (doc.dt.length != 0) {doc.dt.value.year}"}}}
}

doc的取值方式
假设有一个字段:"a": 1,那么:

  • doc['a']返回的是[1],是一个数组,如果文档没有该字段,返回空数组及doc['a'].length=0
  • doc['a'].value返回的是1,也就是取第一个元素。
  • doc['a'].values与doc['a']表现一致,返回整个数组[1]

script_fields脚本字段
每个_search 请求的匹配(hit)可以使用 script_fields定制一些属性,一个 _search 请求能定义多于一个以上的 script field这些定制的属性通常是:

  • 针对原有值的修改(比如,价钱的转换,不同的排序方法等)
  • 一个崭新的及算出来的属性(比如,总和,加权,指数运算,距离测量等)

script_fields在结果中的返回是{fileds: 字段名:[]}的json格式和_source同一级

doc.dt.value获取第一个数组元素,存储数据类型为amic getter [org.elasticsearch.script.JodaComp,该类型通过year属性获得年份。查看以下返回结果,由于没有筛选条件所有文档都被返回,存在dt字段的提取年份,不存在dt字段的也会有返回值为null,由此可见_search + doc操作实际上是完成了原始文档的一个映射转换操作,并产生了一个自定义的临时字段,不会对原始索引做任何更改操作

    {"_index" : "hotel","_type" : "_doc","_id" : "301","_score" : 1.0,"fields" : {"year" : [2021]}},{"_index" : "hotel","_type" : "_doc","_id" : "002","_score" : 1.0,"fields" : {"year" : [null]}},
...

如果只返回存在dt字段的,需要在DSL中增加query逻辑

GET /hotel/_doc/_search
{"query": {"exists": {"field": "dt"}},"script_fields": {"year": {"script": {"source": "doc.dt.value.year"}}}
}
(2)统计一个数组字段数组的和并且返回

插入一个数值数组字段,搜索统计返回数组的和

POST /hotel/_doc/_mapping
{"properties": {"goals" : {"type": "keyword"}}
}

插入数据

POST /_bulk
{"index": {"_index": "hotel", "_type": "_doc", "_id": "123"}}
{"name": "a酒店","city": "扬州", "goals": [1, 5, 3] }
{"index": {"_index": "hotel", "_type": "_doc", "_id": "124"}}
{"name": "b酒店","city": "杭州", "goals": [9, 5, 1] }
{"index": {"_index": "hotel", "_type": "_doc", "_id": "125"}}
{"name": "c酒店","city": "云州", "goals": [2, 7, 9] }

下面计算有goals字段的求goals的和到一个临时字段

GET /hotel/_doc/_search
{"query": {"exists": {"field": "goals"}},"script_fields": {"goals_sum": {"script": {"source": """int total =0;for (int i=0; i < doc.goals.length; i++) {total += Integer.parseInt(doc.goals[i])}return total"""}}}
}

在script中每一行结束要加分号;,使用Java语法的循环求得数组的和,每个数组元素需要使用Java语法中的Integer.parseInt解析,否则报错String类型无法转Num,查看返回

    "hits" : [{"_index" : "hotel","_type" : "_doc","_id" : "123","_score" : 1.0,"fields" : {"goals_sum" : [9]}},{"_index" : "hotel","_type" : "_doc","_id" : "124","_score" : 1.0,"fields" : {"goals_sum" : [15]}},{"_index" : "hotel","_type" : "_doc","_id" : "125","_score" : 1.0,"fields" : {"goals_sum" : [18]}}

script脚本新建/删除字段

新建字段和删除字段都是update操作,使用ctx._source

(1)新建字段

对于存在dt字段的文档,新增一个字段dt_year,值为dt的年份

POST /hotel/_doc/_update_by_query
{"query": {"exists": {"field": "dt"}}, "script": {"source": "ctx._source.dt_year = ctx._source.dt.year"}
}

以上直接在source中使用ctx._source.dt_year引入一个新列,可惜直接报错

   "reason": "dynamic getter [java.lang.String, year] not found

此处并没有向doc一样数据为日期类型而是字符串,因此需要引入Java解析

POST /hotel/_doc/_update_by_query
{"query": {"exists": {"field": "dt"}}, "script": {"source": """LocalDateTime time2Parse = LocalDateTime.parse(ctx._source.dt, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));ctx._source.dt_year = time2Parse.getYear()"""}
}

查看结果

      {"_index" : "hotel","_type" : "_doc","_id" : "301","_score" : 1.0,"_source" : {"dt" : "2021-01-01 13:13:13","dt_year" : 2021}
}

也可以做其他操作比如获得LocalDateTime类型之后再做格式化输出

POST /hotel/_doc/_update_by_query
{"query": {"exists": {"field": "dt"}}, "script": {"source": """LocalDateTime time2Parse = LocalDateTime.parse(ctx._source.dt, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));ctx._source.dt_year = time2Parse.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))"""}
}
(2)删除字段

删除字段直接使用ctx._source.remove(\"字段名\"),可以删除单个文档,也可以update_by_query批量删除

POST /hotel/_doc/123
{"script": {"source": "ctx._source.remove(\"goals\")"}
}

POST /hotel/_doc/_update_by_query
{"query": {"exists": {"field": "goals"}},"script": {"source": "ctx._source.remove(\"goals\")"}
}

script脚本条件判断

支持if,else if,else,比如根据某值进行二值判断生成新字段

POST /hotel/_doc/_update_by_query
{"query": {"exists": {"field": "price"}},"script": {"source": """double price = ctx._source.price;if (price >= 10) {ctx._source.expensive = 1} else {ctx._source.expensive = 0}"""}
}

POST /hotel/_doc/_update_by_query
{"query": {"exists": {"field": "price"}},"script": {"source": """double price = ctx._source.price;if (price >= 10) {ctx._source.expensive = 1} else if (price == 0) {ctx._source.expensive = -1} else {ctx._source.expensive = 0}"""}
}

注意:经过多轮测试如果source中有多轮if判断语法会报错,貌似只能支持一个if,解决方案是使用Java的三元表达式?;,三元表达式写多少个判断都行


script使用return

return用在_search操作中,配合script_fields使用,例如在搜索结果中新增一个字段area为china,此字段不更新到索引只是在搜索时返回

GET /hotel/_doc/_search
{"_source": true,"script_fields": {"area": {"script": {"source": "return \"china\""}}}
}

以上指定"_source": true防止被script_fields覆盖,一条输出结果如下

    {"_index" : "hotel","_type" : "_doc","_id" : "123","_score" : 1.0,"_source" : {"city" : "扬州","name" : "a酒店"},"fields" : {"area" : ["china"]}

script多个字段组合/逻辑判断
(1)多个字段加权求和

先插入3个子模型分,在生成一个总分,权重是0.6,0.2,0.2

POST /_bulk
{"index": {"_index": "hotel", "_type": "_doc", "_id": "333"}}
{"name": "K酒店","city": "扬州", "model_1": 0.79, "model_2": 0.39, "model_3": 0.72}
{"index": {"_index": "hotel", "_type": "_doc", "_id": "334"}}
{"name": "L酒店","city": "江州", "model_1": 0.62, "model_2": 0.55, "model_3": 0.89}
{"index": {"_index": "hotel", "_type": "_doc", "_id": "335"}}
{"name": "S酒店","city": "兖州", "model_1": 0.83, "model_2": 0.45, "model_3": 0.58}

现在计算总分给到score字段

POST /hotel/_doc/_update_by_query
{"query": {"bool": {"must":  [{"exists": {"field": "model_1"}},{"exists": {"field": "model_2"}},{"exists": {"field": "model_3"}}]}},"script": {"source": "ctx._source.score = 0.6 * ctx._source.model_1 + 0.2 * ctx._source.model_2 + 0.2 * ctx._source.model_3"  }
}

看一下运行结果

GET /hotel/_doc/_search
{"query": {"exists": {"field": "score"}}
}

   "hits" : [{"_index" : "hotel","_type" : "_doc","_id" : "335","_score" : 1.0,"_source" : {"score" : 0.704,"city" : "兖州","name" : "S酒店","model_1" : 0.83,"model_3" : 0.58,"model_2" : 0.45}},{"_index" : "hotel","_type" : "_doc","_id" : "333","_score" : 1.0,"_source" : {"score" : 0.6960000000000001,"city" : "扬州","name" : "K酒店","model_1" : 0.79,"model_3" : 0.72,"model_2" : 0.39}},...
(2)两个字段大小比较

直接取ctx._source对应字段进行比较,使用Java三元表达式?:赋值给新字段

POST /hotel/_doc/_update_by_query
{"query": {"bool": {"must":  [{"exists": {"field": "model_1"}},{"exists": {"field": "model_2"}}]}},"script": {"source": "ctx._source.max_score = ctx._source.model_1 > ctx._source.model_2 ? ctx._source.model_1 : ctx._source.model_2"  }
}

script脚本null判断

有两种情况字段为null和params为null

(1)字段为null

如果某字段为空,文档不存在该字段,则填充为0

POST /hotel/_doc/_update_by_query
{"script": {"source": "if (ctx._source.score == null) ctx._source.score = 0.0"}
}
(2)params传参为null

如果传入params不存在某个key,则删除该字段

POST /hotel/_doc/_update_by_query
{"script": {"source": """String[] cols = new String[3];cols[0] = "name";cols[1] = "city";cols[2] = "price";for (String c : cols) {if (params[c] == null) {ctx._source.remove(c)} else {ctx._source[c] = params[c]}}""","params": {"name": "test","city": "test_loc"}}
}

注意:在循环中拿到局部变量c传递给params,params[c]不能用点.或者带有双引号params["c"],否则是判断params中是否有c这个名字的字段

在本例中使用String[] cols = new String[3];创建了一个静态变量,对于这种集合类的变量painless的语法和Java略有不同,写几个例子如下

ArrayList l = new ArrayList();  // Declare an ArrayList variable l and set it to a newly allocated ArrayList
Map m = new HashMap();          // Declare a Map variable m and set it   to a newly allocated HashMapList l = new ArrayList(); // Declare List variable l and set it a newly allocated ArrayList
List m;                   // Declare List variable m and set it the default value null
int[] ia1;                      //Declare int[] ia1; store default null to ia1    
int[] ia2 = new int[2];               //Allocate 1-d int array instance with length [2] → 1-d int array reference; store 1-d int array reference to ia1        
ia2[0] = 1;                     //Load from ia1 → 1-d int array reference; store int 1 to index [0] of 1-d int array reference 
int[][] ic2 = new int[2][5];    //Declare int[][] ic2; allocate 2-d int array instance with length [2, 5] → 2-d int array reference; store 2-d int array reference to ic2
ic2[1][3] = 2;                  //Load from ic2 → 2-d int array reference; store int 2 to index [1, 3] of 2-d int array reference
ic2[0] = ia1;                   //Load from ia1 → 1-d int array reference; load from ic2 → 2-d int array reference; store 1-d int array reference to index [0] of 2-d int array reference; (note ia1, ib1, and index [0] of ia2 refer to the same instance)

List,Map这些集合都没有泛型,并且集合的值貌似不能直接初始化,需要add,put进来


script作为查询过滤条件

查看某列的值大于某列,在query下可以使用script,注意格式script下还套着一个script,search请求使用doc获取值

GET /hotel/_doc/_search
{"query": {"script" : {"script" : {"source": "doc.score.value < doc.model_3.value"}}}}

以上语句会报warn,doc选取字段如果字段为空会填充默认值,因此再限制一下字段不为空

GET /hotel/_doc/_search
{"query": {"bool" : {"must" : [{"script" : {"script" : {"source": "doc.score.value < doc.model_3.value"}}},{"exists": {"field": "score"}}, {"exists": {"field": "model_3"}}]}}
}



作者:xiaogp
链接:https://www.jianshu.com/p/66a72d7ba3da
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/114121.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

cnn 直线检测笔记

目录 mlsd直线检测: ULSD-ISPRS曲线检测 划线标注工具: 可视化标注代码: mlsd直线检测: mlsd_pytorch

JavaScript中 判断网络状态的几种方法

1. 使用 Navigator onLine 属性 Navigator onLine 属性判断浏览器是否在线&#xff0c;在线返回 true&#xff0c;离线返回 false&#xff1b; Navigator onLine 是只读属性&#xff0c;所有主流浏览器都支持 onLine 属性&#xff1b; if (window.navigator.onLine) {console…

【proteus】8086仿真、汇编语言

1.创建好新项目 2.点击source code 弹出VSM 3. 4.注意两个都不勾选 可以看到schematic有原理图出现 5. 再次点击source code 6.project/project settings&#xff0c;取消勾选embed 7. add 8.输入文件名保存后&#xff1a; 注意&#xff1a;proteus不用写dos的相关语句 。

【NPM】particles.vue3 + tsparticles 实现粒子效果

在 NPM 官网搜索这两个库并安装&#xff1a; npm install element-plus --save npm i tsparticles使用提供的 vue 案例和方法&#xff1a; <template><div><vue-particlesid"tsparticles":particlesInit"particlesInit":particlesLoaded&…

Kubernetes 学习总结(39)—— Kubernetes 之 Pause 容器详解

一、概念和作用 在 Kubernetes 中&#xff0c;Pause 容器是一种特殊类型的容器&#xff0c;它的主要作用是充当依赖其他容器的容器&#xff0c;为其他容器提供一个可靠的、隔离的运行环境。 Pause 容器是一种轻量级的容器&#xff0c;它本身不包含任何业务逻辑&#xff0c;只是…

华为OD 磁盘容量排序(100分)【java】A卷+B卷

华为OD统一考试A卷+B卷 新题库说明 你收到的链接上面会标注A卷还是B卷。目前大部分收到的都是B卷。 B卷对应20022部分考题以及新出的题目,A卷对应的是新出的题目。 我将持续更新最新题目 获取更多免费题目可前往夸克网盘下载,请点击以下链接进入: 我用夸克网盘分享了「华为O…

自然语言处理---Transformer机制详解之BERT模型特点

1 BERT的优点和缺点 1.1 BERT的优点 通过预训练, 加上Fine-tunning, 在11项NLP任务上取得最优结果.BERT的根基源于Transformer, 相比传统RNN更加高效, 可以并行化处理同时能捕捉长距离的语义和结构依赖.BERT采用了Transformer架构中的Encoder模块, 不仅仅获得了真正意义上的b…

华为OD机试 - 代表团坐车 - 动态规划(Java 2023 B卷 200分)

目录 专栏导读一、题目描述二、输入描述三、输出描述四、解题思路五、Java算法源码六、效果展示1、输入2、输出3、说明 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷&#…

Ubuntu 22.04 中安装 fcitx5

Ubuntu 22.04 中安装 fcitx5 可以按照以下步骤进行&#xff1a; 添加 fcitx5 的 PPA 首先&#xff0c;添加 fcitx5 的官方 PPA&#xff1a; sudo add-apt-repository ppa:fcitx-team/fcitx5更新软件包列表 sudo apt update安装 fcitx5 sudo apt install fcitx5 fcitx5-conf…

信息学奥赛一本通1000:入门测试题目

1000&#xff1a;入门测试题目 时间限制: 1000 ms 内存限制: 32768 KB 提交数: 318392 通过数: 190210 【题目描述】 求两个整数的和。 【输入】 一行&#xff0c;两个用空格隔开的整数。 【输出】 两个整数的和。 【输入样例】 2 3 【输出样例】 5 思路&#…

Mysql表结构差异比较

1、背景 我们在开发过程中&#xff0c;大部分情况下都是好几个版本一起并行&#xff0c;有时候如果某个版本表结构改动较大&#xff0c;但是忘记了记录DDL脚本&#xff0c;这个时候需要人工去把新增或修改的DDL脚本整理出来&#xff08;主要是为了解决 数据库新增字段&#xff…

高效表达三步

一、高效表达 高效表达定主题搭架子填素材 第一&#xff1a; 1个核心主题&#xff0c;让别人秒懂你的想法 &#xff08;表达要定主题&#xff09; 第二&#xff1a; 3种经典框架&#xff0c;帮你快速整理表达思路 第三&#xff1a; 2种表达素材&#xff0c;让发言更具说服力…

在 Python 中执行 Shell 命令并获取输出

在本文中&#xff0c;我们将学习如何借助 os.system() 从 Python 脚本执行 cmd 命令。 我们还将学习如何借助 Python 中的 subprocess 模块以更简单的方式从脚本执行 cmd 命令。 从 Python 脚本执行 CMD 命令并使用 os.system() 获取输出 我们出于不同目的在命令提示符或任何其…

JAVA IO-序列化与反序列化

1.注意&#xff1a; 输入和输出都是从程序的角度来说的。 字节流&#xff1a;一次读入或读出是8位二进制。 字符流&#xff1a;一次读入或读出是16位二进制。 字节流和字符流的原理是相同的&#xff0c;只不过处理的单位不同而已。后缀是Stream是字节流&#xff0c;而后缀是Rea…

可视化 | python可视化相关库梳理(自用)| pandas | Matplotlib | Seaborn | Pyecharts | Plotly

文章目录 &#x1f4da;Plotly&#x1f407;堆叠柱状图&#x1f407;环形图&#x1f407;散点图&#x1f407;漏斗图&#x1f407;桑基图&#x1f407;金字塔图&#x1f407;气泡图&#x1f407;面积图⭐️快速作图工具&#xff1a;plotly.express&#x1f407;树形图&#x1f…

正点原子嵌入式linux驱动开发——Linux中断

不管是单片机裸机实验还是Linux下的驱动实验&#xff0c;中断都是频繁使用的功能&#xff0c;在裸机中使用中断需要做一大堆的工作&#xff0c;比如配置寄存器&#xff0c;使能IRQ等等。但是Linux内核提供了完善的中断框架&#xff0c;只需要申请中断&#xff0c;然后注册中断处…

怎样修改ESP32的CPU主频

ESP32的主频默认设置为160mhz&#xff0c;但ESP32最高可以跑到240mhz&#xff0c; 修改方法&#xff1a; idf.py menuconfig --> Component config --> ESP System Settings --> CPU frequency 可以看到三个选项&#xff0c;80&#xff0c;160&#xff0c; 240&…

跟我一起写个虚拟机 .Net 7(四)- LC_3 解析实例

没想到这篇文章持续了这么久&#xff0c;越学越深&#xff0c;愣是又买了一本书《计算机系统概论》&#xff0c;当然&#xff0c;也看完了&#xff0c;受益匪浅。 系统化的学习才是正确的学习方式&#xff0c;我大学就没看到过这本书&#xff0c;如果早点看到&#xff0c;可能…

STM32F4之系统滴答定时器

一、系统滴答定时器概述 传统定时器&#xff1a;如手机闹钟&#xff0c;闹钟等就是一个简单地计数器。 定时器概念&#xff1a;由时钟源计数器计数值组成的计数单元。 系统嘀嗒定时器首先是存在于内核里&#xff0c;系统嘀嗒时钟假如用的是同一个内核那么里面相关的配置&…

Android问题笔记 - NoSuchmethodException: could not find Fragment constructor

点击跳转>Unity3D特效百例点击跳转>案例项目实战源码点击跳转>游戏脚本-辅助自动化点击跳转>Android控件全解手册点击跳转>Scratch编程案例点击跳转>软考全系列 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&…