Java内存模型JMM重要知识点
一、Java内存模型JMM
Java内存模型即Java Memory Model,简称JMM。用来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各平台下都能够达到一致的内存访问效果。
JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。
JMM与Java内存结构并不是同一个层次的内存划分,两者基本没有关系。如果一定要勉强对应,那从变量、主内存、工作内存的定义看,主内存主要对应Java堆中的对象实例数据部分,工作内存则对应虚拟机栈的部分区域。
主内存:主要存储的是Java实例对象,所有线程创建的实例对象都存放在主内存中,不管该实例对象是成员变量还是方法中的本地变量(也称局部变量),当然也包括了共享的类信息、常量、静态变量。共享数据区域,多条线程对同一个变量进行访问可能会发现线程安全问题。
工作内存:主要存储当前方法的所有本地变量信息(工作内存中存储着主内存中的变量副本拷贝),每个线程只能访问自己的工作内存,即线程中的本地变量对其它线程是不可见的,就算是两个线程执行的是同一段代码,它们也会各自在自己的工作内存中创建属于当前线程的本地变量,当然也包括了字节码行号指示器、相关Native方法的信息。由于工作内存是每个线程的私有数据,线程间无法相互访问工作内存,因此存储在工作内存的数据不存在线程安全问题。
JMM模型与硬件模型直接的对照关系可简化为下图:
内存之间的交互操作
线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。
Java内存模型定义了8种操作来完成,虚拟机实现必须保证每一种操作都是原子的、不可再拆分的(double和long类型例外)。
- lock(锁定):作用于主内存的变量,它把一个变量标识为一条线程独占的状态。
- unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
- read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。
- load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
- use(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作。
- assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
- store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用。
- write(写入):作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。
如果需要把一个变量从主内存复制到工作内存,那就要顺序地执行read和load操作,如果要把变量从工作内存同步回主内存,就要顺序地执行store和write操作。注意,Java内存模型只要求上述两个操作必须按顺序执行,而没有保证是连续执行。也就是说read与load之间、store与write之间是可插入其他指令的,如对主内存中的变量a、b进行访问时,一种可能出现顺序是read a、read b、load b、load a。
除此之外,Java内存模型还规定了在执行上述8中基本操作时必须满足如下规则。
- 不允许read和load、store和write操作之一单独出现,即不允许一个变量从主内存读取了但工作内存不接受,或者从工作内存发起回写了但主内存不接受的情况出现。
- 不允许一个线程丢弃它的最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步回主内存。
- 不允许一个线程无原因地(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存。
- 一个新的变量只能在主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量,换句话说,就是对一个变量实施use、store操作之前,必须先执行过了assign和load操作。
- 一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。
- 如果对一个变量执行lock操作,那将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。
- 如果一个变量事先没有被lock操作锁定,那就不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁定住的变量。
- 对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行store、write操作)。
long和double型变量的特殊规则
Java内存模型要求lock,unlock,read,load,assign,use,store,write这8个操作都具有原子性,但对于64位的数据类型(long或double),在模型中定义了一条相对宽松的规定,允许虚拟机将没有被volatile修饰的64位数据的读写操作划分为两次32位的操作来进行,即允许虚拟机实现选择可以不保证64位数据类型的load,store,read,write这4个操作的原子性,即long和double的非原子性协定。
如果多线程的情况下double或long类型并未声明为volatile,可能会出现“半个变量”的数值,也就是既非原值,也非修改后的值。
虽然Java规范允许上面的实现,但商用虚拟机中基本都采用了原子性的操作,因此在日常使用中几乎不会出现读取到“半个变量”的情况。
二、Java内存模型解决的问题
JMM内存模型可谓是基础知识,原子性、可见性、有序性这三大特征几乎贯穿了并发编程,所以学习JMM内存模型是必须的。
原子性
原子 Atomic,意指不可分割,也就是作为一个整体,要么全部执行,要么不会执行
对于共享变量访问的一个操作,如果对于除了当前执行线程以外的任何线程来说,都是不可分割的,那么就是具有原子性
简言之,对于别的线程而言,他要么看到的是该线程还没有执行的情况,要么就是看到了线程执行后的情况,不会出现执行一半的场景,简言之,其他线程永远不会看到中间结果.
Java中有两种方式实现原子性
一种是使用锁机制,锁具有排他性,也就是说它能够保证一个共享变量在任意一个时刻仅仅被一个线程访问,这就消除了竞争;
另外一种是借助于处理器提供的专门的CAS指令(compare-and-swap)
在Java中,long和double以外的任何类型的变量的写操作都是原子操作
也就是基础类型(byte int short char float boolean)以及引用类型的变量的写操作都是原子的,由Java语言规范规定,JVM实现
对于long和double,64位长度,如果是在32位机器上,写操作可能分为两个步骤,分别处理高低32位,两个步骤就打破了原子性,可能出现数据安全问题
可见性
在多线程环境下,一个线程对某个共享变量进行更新之后,后续访问该变量的线程可能无法立刻读取到这个更新的结果,甚至永远也无法读取到这个更新的结果。
这就是线程安全问题的另外一个表现形式:可见性(Visibility )
如果一个线程对某个共享变量进行更新之后,后续访问该变量的线程可以读取到该更新的结果,那么就称这个线程对该共享变量的更新对其他线程可见,否则就称这个线程对该共享变量的更新对其他线程不可见。
简言之,如果一个线程对共享数据做出了修改,而另外的线程却并没有读取到最新的结果,这是有问题的
多线程程序在可见性方面存在问题意味着某些线程读取到了旧数据,通常也是不被希望的
为什么会出现可见性问题?
因为数据本质是要从主存存取的,但是对于线程来说,有了工作内存,这个私有的工作台,也就是read-modify-write模式
解决这个内存可见性问题你可以使用:
- Java中的volatile关键字:volatile关键字可以保证直接从主存中读取一个变量,如果这个变量被修改后,总是会被写回到主存中去。Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现可见性的,无论是普通变量还是volatile变量都是如此,普通变量与volatile变量的区别是:volatile的特殊规则保证了新值能立即同步到主内存,以及每个线程在每次使用volatile变量前都立即从主内存刷新。因此我们可以说volatile保证了多线程操作时变量的可见性,而普通变量则不能保证这一点。
- Java中的synchronized关键字:同步快的可见性是由“如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值”、“对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行store和write操作)”这两条规则获得的。
- Java中的final关键字:final关键字的可见性是指,被final修饰的字段在构造器中一旦被初始化完成,并且构造器没有把“this”的引用传递出去(this引用逃逸是一件很危险的事情,其他线程有可能通过这个引用访问到“初始化了一半”的对象),那么在其他线程就能看见final字段的值(无须同步)
有序性
在源代码中是有顺序的,经过编译后形成指令后,也必然是有顺序的在一个线程中从代码执行的角度来看,也总是有先后顺序的.,但是实际上,这种顺序是没有保障的。处理器可能并不会完全按照已经形成的指令(目标代码)顺序执行,这种现象就叫做重排序
三、为什么要重排序?
重排序是对内存访问操作的一种优化,他可以在不影响单线程程序正确性的前提下进行一定的调整,进而提高程序的性能
但是对于多线程场景下,就可能产生一定的问题
当然,重排序导致的问题,也不是必然出现的
比如,编译器进行编译时,处理器进行执行时,都有可能发生重排序
先声明几个概念
- 源代码顺序,很明显字面意思就是源代码的顺序
- 程序顺序,源码经过处理后的目标代码顺序(解释后或者JIT编译后的目标代码或者干脆理解成源代码解析后的机器指令)
- 执行顺序,处理器对目标代码执行时的顺序
- 感知顺序,处理器执行了,但是别人看到的并不一定就是你执行的顺序,因为操作后的数据涉及到数据的回写,可能会经过寄存器、缓存等,即使你先计算的a后计算的b,如果b先被写回呢?这就是感知顺序,简单说就是别人看到的结果
在此基础上,可以将重排序可以分为两种,指令重排序和存储重排序
下图来自《Java多线程编程实战指南-核心篇》
编译器可能导致目标代码与源代码顺序不一致;即时编译器JIT和处理器可能导致执行顺序与程序顺序不一致;缓存、缓冲器可能导致感知顺序不一致
指令重排序
不管是程序顺序与源代码顺序不一致还是执行顺序与程序顺序不一致,结果都是指令重排序,因为最终的效果就是源代码与最终被执行的指令顺序不一致
如下图所示,不管是哪一段顺序被重拍了,最终的结果都是最终执行的指令乱序了
ps:Java有两种编译器,一种是Javac静态编译器,将源文件编译为字节码,代码编译阶段运行;JIT是在运行时,动态的将字节码编译为本地机器码(目标代码)
通常javac不会进行重排序,而JIT则很可能进行重排序
此处不对为什么要重排序展开,简单说就是硬件或者编译器等为了能够更好地执行指令,提高性能,所做出的一定程度的优化,重排序也不是随随便便的就改变了顺序的,它具有一定的规则,叫做貌似串行语义As-if-serial Semantics,也就是从单线程的角度保障不会出现问题,但是对于多线程就可能出现问题。
貌似串行语义的规则主要是对于具有数据依赖关系的数据不会进行重排序,没有依赖关系的则可能进行重排序
比如下面的三条语句,c=a+b;依赖a和b,所以不会与他们进行重排序,但是a和b没有依赖关系,就可能发生重排序
a=1;
b=2;
c=a+b;
存储重排序
为什么会出现执行一种顺序,而结果的写入是另外的一种顺序?
前面说过,对于CPU来说并不是直接跟主存交互的,因为速度有天壤之别,所以有多级缓存,有读缓存,其实也有写缓存
有了缓存,也就意味着这中间就多了一些步骤,那么就可能即使严格按照指令的顺序执行,但是从结果上看起来却是乱序的
指令重排序是一种动作,实际发生了,而存储重排序则是一种现象,从结果看出来的一种现象,其实本身并没有在执行上重拍,但是这也可能引起问题
如何保证顺序?
貌似串行语义As-if-serial Semantics,只是保障单线程不会出问题,所以有序性保障,可以理解为,将貌似貌似串行语义As-if-serial Semantics扩展到多线程,在多线程中也不会出现问题
换句话说,有序性的保障,就是貌似串行语义在逻辑上看起来,有些必要的地方禁止重排序
从底层的角度来看,是借助于处理器提供的相关指令内存屏障来实现的
对于Java语言本身来说,Java已经帮我们与底层打交道,我们不会直接接触内存屏障指令,java提供的关键字synchronized和volatile,可以达到这个效果,保障有序性(借助于显式锁Lock也是一样的,Lock逻辑与synchronized一致)
四、内存屏障:内存可见性与指令重排序
那 JMM 如何保障指令重排序排序,内存可见性带来并发访问问题?
内存屏障(Memory Barrier)用于控制在特定条件下的重排序和内存可见性问题。JMM 内存屏障可分为读屏障和写屏障,Java 的内存屏障实际上也是上述两种的组合,完成一系列的屏障和数据同步功能。Java 编译器在生成字节码时,会在执行指令序列的适当位置插入内存屏障来限制处理器的重排序。
组合如下:
- Load-Load Barriers:load1 的加载优先于 load2 以及所有后续的加载指令,在指令前插入 Load Barrier,使得高速缓存中的数据失效,强制重新从驻内存中加载数据。
- Load-Store Barriers:确保 load1 数据的加载先于 store2 以及之后的存储指令刷新到内存。
- Store-Store Barriers:确保 store1 数据对其他处理器可见,并且先于 store2 以及所有后续的存储指令。在 Store Barrie 指令后插入 Store Barrie 会把写入缓存的最新数据刷新到主内存,使得其他线程可见。
- Store-Load Barriers:在 Load2 及后续所有读取操作执行前,保证 Store1 的写入对所有处理器可见。这条内存屏障指令是一个全能型的屏障,它同时具有其他 3 条屏障的效果,而且它的开销也是四种屏障中最大的一个。
五、Happens-Before原则
- 程序顺序原则:如果程序操作 A 在操作 B 之前,那么多线程中的操作依然是 A 在 B 之前执行。
- 监视器锁原则:在监视器锁上的解锁操作必须在同一个监视器上的加锁操作之前执行。
- volatile 变量原则:对 volatile 修饰的变量写入操作必须在该变量的毒操作之前执行。
- 线程启动原则:在线程对 Tread.start 调用必须在该线程执行任何操作之前执行。
- 线程结束原则:线程的任何操作必须在其他线程检测到该线程结束前执行,或者从 Thread.join 中成功返回,或者在调用 Thread.isAlive 返回 false。
- 中断原则:当一个线程在另一个线程上调用 interrupt 时,必须在被中断线程检测到 interrupt 调用之前执行。
- 终结器规则:对象的构造方法必须在启动对象的终结器之前完成。
- 传递性:如果操作 A 在操作 B 之前执行,并且操作 B 在操作 C 之前执行,那么操作 A 必须在操作 C 之前执行。
六、常见的问题
volatile 关键字、synchronized关键字、ReentrantLock的使用场景、解决的问题和优缺点等。
ElasticSearch进阶:一文全览各种ES查询在Java中的实现
- 前言
- 1 词条查询
- 1.1 等值查询-term1.2 多值查询-terms1.3 范围查询-range1.4 前缀查询-prefix1.5 通配符查询-wildcard
- 2 复合查询
- 2.1 布尔查询2.2 Filter查询
- 3 聚合查询
- 3.1 最值、平均值、求和3.2 去重查询3.3 分组聚合3.3.1 单条件分组3.3.2 多条件分组3.4 过滤聚合
- ElasticSearch第一篇:ElasticSearch基础:从倒排索引说起,快速认知ES
这篇博文的主题是ES的查询,因此我整理了尽可能齐全的ES查询场景,形成下面的图:
本文基于elasticsearch 7.13.2版本,es从7.0以后,发生了很大的更新。7.3以后,已经不推荐使用TransportClient这个client,取而代之的是Java High Level REST Client。
测试使用的数据示例
首先是,Mysql中的部分测试数据:
Mysql中的一行数据在ES中以一个文档形式存在:
简单梳理了一下ES JavaAPI的相关体系,感兴趣的可以自己研读一下源码。
接下来,我们用十几个实例,迅速上手ES的查询操作,每个示例将提供SQL语句、ES语句和Java代码。
所谓词条查询,也就是ES不会对查询条件进行分词处理,只有当词条和查询字符串完全匹配时,才会被查询到。
等值查询,即筛选出一个字段等于特定值得所有记录。
SQL:
而使用ES查询语句却很不一样(注意查询字段带上keyword):
ElasticSearch 5.0以后,string类型有重大变更,移除了string类型,string字段被拆分成两种新的数据类型: text用于全文搜索的,而keyword用于关键词搜索的。
查询结果:
Java中构造ES请求的方式:(后续例子中只保留SearchSourceBuilder的构建语句)
仔细观察查询结果,会发现ES查询结果中会带有_score这一项,ES会根据结果匹配程度进行评分。打分是会耗费性能的,如果确认自己的查询不需要评分,就设置查询语句关闭评分:
Java构建查询语句:
多条件查询类似Mysql里的IN查询,例如:
ES查询语句:
Java实现:
范围查询,即查询某字段在特定区间的记录。
SQL:
ES查询语句:
Java构建查询条件:
前缀查询类似于SQL中的模糊查询。
SQL:
ES查询语句:
Java构建查询条件:
通配符查询,与前缀查询类似,都属于模糊查询的范畴,但通配符显然功能更强。
SQL:
ES查询语句:
Java构建查询条件:
前面的例子都是单个条件查询,在实际应用中,我们很有可能会过滤多个值或字段。先看一个简单的例子:
这样的多条件等值查询,就要借用到组合过滤器了,其查询语句是:
Java构造查询语句:
布尔过滤器(bool filter)属于复合过滤器(compound filter)的一种 ,可以接受多个其他过滤器作为参数,并将这些过滤器结合成各式各样的布尔(逻辑)组合。
bool 过滤器下可以有4种子条件,可以任选其中任意一个或多个。filter是比较特殊的,这里先不说。
- must:所有的语句都必须匹配,与 ‘=’ 等价。
- must_not:所有的语句都不能匹配,与 ‘!=’ 或 not in 等价。
- should:至少有n个语句要匹配,n由参数控制。
精度控制:
所有 must 语句必须匹配,所有 must_not 语句都必须不匹配,但有多少 should 语句应该匹配呢?默认情况下,没有 should 语句是必须匹配的,只有一个例外:那就是当没有 must 语句的时候,至少有一个 should 语句必须匹配。
我们可以通过 minimum_should_match 参数控制需要匹配的 should 语句的数量,它既可以是一个绝对的数字,又可以是个百分比:
Java构建查询语句:
最后,看一个复杂些的例子,将bool的各子句联合使用:
用 Elasticsearch 来表示上面的 SQL 例子:
用Java构建这个查询条件:
query和filter的区别:query查询的时候,会先比较查询条件,然后计算分值,最后返回文档结果;而filter是先判断是否满足查询条件,如果不满足会缓存查询结果(记录该文档不满足结果),满足的话,就直接缓存结果,filter不会对结果进行评分,能够提高查询效率。
filter的使用方式比较多样,下面用几个例子演示一下。
方式一,单独使用:
单独使用时,filter与must基本一样,不同的是filter不计算评分,效率更高。
Java构建查询语句:
方式二,和must、must_not同级,相当于子查询:
ES查询语句:
Java:
方式三,将must、must_not置于filter下,这种方式是最常用的:
Java:
接下来,我们将用一些案例演示ES聚合查询。
案例:查询最大年龄、最小年龄、平均年龄。
SQL:
ES:
Java:
使用聚合查询,结果中默认只会返回10条文档数据(当然我们关心的是聚合的结果,而非文档)。返回多少条数据可以自主控制:
而Java中只需增加下面一条语句即可:
与max类似,其他统计查询也很简单:
案例:查询一共有多少个门派。
SQL:
ES:
Java:
案例:查询每个门派的人数
SQL:
ES:
Java:
案例:查询每个门派各有多少个男性和女性
SQL:
ES:
前面所有聚合的例子请求都省略了 query ,整个请求只不过是一个聚合。这意味着我们对全部数据进行了聚合,但现实应用中,我们常常对特定范围的数据进行聚合,例如下例。
案例:查询明教中的最大年龄。 这涉及到聚合与条件查询一起使用。
SQL:
ES:
Java:
另外还有一些更复杂的查询例子。
案例:查询0-20,21-40,41-60,61以上的各有多少人。
SQL:
ES:
Java:
查询结果:
以上是ElasticSearch查询的全部内容,丰富详实,堪比操作手册,强烈建议收藏!ElasticSearch多种查询操作
- 前言
- 1 词条查询
- 1.1 等值查询-term1.2 多值查询-terms1.3 范围查询-range1.4 前缀查询-prefix1.5 通配符查询-wildcard
- 2 复合查询
- 2.1 布尔查询2.2 Filter查询
- 3 聚合查询
- 3.1 最值、平均值、求和3.2 去重查询3.3 分组聚合3.3.1 单条件分组3.3.2 多条件分组3.4 过滤聚合
- ElasticSearch第一篇:ElasticSearch基础:从倒排索引说起,快速认知ES
这篇博文的主题是ES的查询,因此我整理了尽可能齐全的ES查询场景,形成下面的图:
本文基于elasticsearch 7.13.2版本,es从7.0以后,发生了很大的更新。7.3以后,已经不推荐使用TransportClient这个client,取而代之的是Java High Level REST Client。
测试使用的数据示例
首先是,Mysql中的部分测试数据:
Mysql中的一行数据在ES中以一个文档形式存在:
简单梳理了一下ES JavaAPI的相关体系,感兴趣的可以自己研读一下源码。
接下来,我们用十几个实例,迅速上手ES的查询操作,每个示例将提供SQL语句、ES语句和Java代码。
所谓词条查询,也就是ES不会对查询条件进行分词处理,只有当词条和查询字符串完全匹配时,才会被查询到。
等值查询,即筛选出一个字段等于特定值的所有记录。
SQL:
而使用ES查询语句却很不一样(注意查询字段带上keyword):
ElasticSearch 5.0以后,string类型有重大变更,移除了string类型,string字段被拆分成两种新的数据类型: text用于全文搜索的,而keyword用于关键词搜索的。
查询结果:
Java中构造ES请求的方式:(后续例子中只保留SearchSourceBuilder的构建语句)
仔细观察查询结果,会发现ES查询结果中会带有_score这一项,ES会根据结果匹配程度进行评分。打分是会耗费性能的,如果确认自己的查询不需要评分,就设置查询语句关闭评分:
Java构建查询语句:
多条件查询类似Mysql里的IN查询,例如:
ES查询语句:
Java实现:
范围查询,即查询某字段在特定区间的记录。
SQL:
ES查询语句:
Java构建查询条件:
前缀查询类似于SQL中的模糊查询。
SQL:
ES查询语句:
Java构建查询条件:
通配符查询,与前缀查询类似,都属于模糊查询的范畴,但通配符显然功能更强。
SQL:
ES查询语句:
Java构建查询条件:
前面的例子都是单个条件查询,在实际应用中,我们很有可能会过滤多个值或字段。先看一个简单的例子:
这样的多条件等值查询,就要借用到组合过滤器了,其查询语句是:
Java构造查询语句:
布尔过滤器(bool filter)属于复合过滤器(compound filter)的一种 ,可以接受多个其他过滤器作为参数,并将这些过滤器结合成各式各样的布尔(逻辑)组合。
bool 过滤器下可以有4种子条件,可以任选其中任意一个或多个。filter是比较特殊的,这里先不说。
- must:所有的语句都必须匹配,与 ‘=’ 等价。
- must_not:所有的语句都不能匹配,与 ‘!=’ 或 not in 等价。
- should:至少有n个语句要匹配,n由参数控制。
精度控制:
所有 must 语句必须匹配,所有 must_not 语句都必须不匹配,但有多少 should 语句应该匹配呢?默认情况下,没有 should 语句是必须匹配的,只有一个例外:那就是当没有 must 语句的时候,至少有一个 should 语句必须匹配。
我们可以通过 minimum_should_match 参数控制需要匹配的 should 语句的数量,它既可以是一个绝对的数字,又可以是个百分比:
Java构建查询语句:
最后,看一个复杂些的例子,将bool的各子句联合使用:
用 Elasticsearch 来表示上面的 SQL 例子:
用Java构建这个查询条件:
query和filter的区别:query查询的时候,会先比较查询条件,然后计算分值,最后返回文档结果;而filter是先判断是否满足查询条件,如果不满足会缓存查询结果(记录该文档不满足结果),满足的话,就直接缓存结果,filter不会对结果进行评分,能够提高查询效率。
filter的使用方式比较多样,下面用几个例子演示一下。
方式一,单独使用:
单独使用时,filter与must基本一样,不同的是filter不计算评分,效率更高。
Java构建查询语句:
方式二,和must、must_not同级,相当于子查询:
ES查询语句:
Java:
方式三,将must、must_not置于filter下,这种方式是最常用的:
Java:
接下来,我们将用一些案例演示ES聚合查询。
案例:查询最大年龄、最小年龄、平均年龄。
SQL:
ES:
Java:
使用聚合查询,结果中默认只会返回10条文档数据(当然我们关心的是聚合的结果,而非文档)。返回多少条数据可以自主控制:
而Java中只需增加下面一条语句即可:
与max类似,其他统计查询也很简单:
案例:查询一共有多少个门派。
SQL:
ES:
Java:
案例:查询每个门派的人数
SQL:
ES:
Java:
案例:查询每个门派各有多少个男性和女性
SQL:
ES:
前面所有聚合的例子请求都省略了 query ,整个请求只不过是一个聚合。这意味着我们对全部数据进行了聚合,但现实应用中,我们常常对特定范围的数据进行聚合,例如下例。
案例:查询明教中的最大年龄。 这涉及到聚合与条件查询一起使用。
SQL:
ES:
Java:
另外还有一些更复杂的查询例子。
案例:查询0-20,21-40,41-60,61以上的各有多少人。
SQL:
ES:
Java:
查询结果:
以上是ElasticSearch查询的全部内容,丰富详实,堪比操作手册,强烈建议收藏!ElasticSearch多种查询操作
来源:https://blog.csdn.net/mu_wind/article/details/118267537
基于Java的小医百科小程序
基于Java的小医百科小程序
随着移动互联网的普及,小程序成为了人们获取信息的重要途径。在医疗健康领域,一款能够提供丰富医疗知识、疾病信息、药品查询等功能的百科小程序,将极大地满足公众的需求。因此,基于Java开发一款小医百科小程序,具有重要的社会价值和技术挑战。
视频加载中…
- 用户需求:用户需要能够方便快捷地获取医疗知识,包括疾病信息、药品说明、健康资讯等。
- 功能需求:小程序需要提供搜索、浏览、收藏、分享等功能,以满足用户查询和分享医疗知识的需求。
- 性能需求:小程序需要具有良好的响应速度和稳定性,确保用户能够流畅地使用。
- 安全性需求:用户数据需要得到保护,确保数据的安全性和隐私性。
- 数据库选型:选择MySQL作为后台数据库,配合云服务器进行数据存储和处理。
- 数据表设计:设计用户表、疾病信息表、药品信息表、资讯表等,以存储用户信息、医疗知识和资讯。
- 数据关系:建立合理的数据关系,确保数据的一致性和完整性。
- 数据安全:采取数据加密、备份和恢复策略,保障数据的安全性和可靠性。
- 架构设计:采用Java语言开发,使用Spring Boot框架进行后端开发,使用微信小程序进行前端开发。
- 功能实现:实现搜索、浏览、收藏、分享等功能,确保用户能够方便快捷地获取医疗知识。
- 界面设计:设计简洁明了的界面,提供良好的用户体验。
- 交互设计:实现前后端的交互,确保数据的实时性和准确性。
- 测试与优化:进行功能测试、性能测试和安全性测试,确保小程序的质量和稳定性。
基于Java的小医百科小程序,实现了医疗知识的便捷获取和分享,满足了公众的需求。通过合理的数据库设计和系统实现,确保了小程序的性能和安全性。未来,随着医疗健康的信息化和智能化发展,小医百科小程序具有广阔的应用前景和社会价值。同时,需要不断优化和完善小程序的功能和性能,以适应不断变化的市场需求和技术发展。
本文作者及来源:Renderbus瑞云渲染农场https://www.renderbus.com
文章为作者独立观点不代本网立场,未经允许不得转载。