关于MyBatis的缓存详解

MyBatis 是一个流行的 Java 持久层框架,它提供了对数据库的简单操作和映射。MyBatis 的缓存机制是其核心特性之一,它可以帮助开发者提高应用程序的性能,通过减少对数据库的直接访问次数来降低数据库的负载。

1. MyBatis 缓存介绍

默认缓存行为

  • 局部的 session 缓存:MyBatis 默认开启的缓存是局部的 session 缓存,这意味着每个 MyBatis session 都会有自己的缓存,这个缓存仅在当前 session 内有效。它主要用于处理循环依赖和提升性能。

二级缓存(全局缓存)

  • 开启二级缓存:要开启 MyBatis 的二级缓存,需要在 SQL 映射文件中添加 <cache/> 标签。这将允许跨多个 session 共享缓存。

缓存的基本属性

  • select 语句缓存:所有 select 语句的结果都会被缓存。
  • 刷新机制:insert, update 和 delete 语句会触发缓存的刷新。
  • LRU 算法:默认使用最近最少使用(Least Recently Used)算法来决定哪些缓存项应该被移除。
  • 无时间刷新:默认情况下,缓存不会根据时间间隔自动刷新。
  • 引用数量:默认情况下,缓存可以存储 1024 个引用。
  • 可读/可写:默认情况下,缓存是可读写的,这意味着缓存的对象可以被调用者修改,而不会干扰其他调用者或线程。

高级缓存配置

  • eviction(回收策略):可以设置不同的回收策略,如 LRU、FIFO、SOFT 和 WEAK。
    • LRU:最近最少使用,移除最长时间不被使用的对象。
    • FIFO:先进先出,按对象进入缓存的顺序移除。
    • SOFT:软引用,基于垃圾收集器状态和软引用规则移除对象。
    • WEAK:弱引用,更积极地移除对象,基于垃圾收集器状态和弱引用规则。
  • flushInterval(刷新间隔):可以设置一个时间间隔,以毫秒为单位,缓存会在该时间间隔后自动刷新。
  • size(引用数目):可以设置缓存中存储的对象或列表的引用数量,需要根据可用内存资源来决定。
  • readOnly(只读):设置为 true 时,所有调用者将获得缓存对象的相同实例,这些对象不能被修改,提供了性能优势。设置为 false 时,缓存对象可以被修改,但会返回对象的拷贝,这会降低性能。

配置示例

以下是一个配置示例,展示了如何使用 <cache> 标签来自定义缓存行为:

<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
  • eviction=“FIFO”:使用先进先出的策略来管理缓存。
  • flushInterval=“60000”:每 60 秒刷新一次缓存。
  • size=“512”:缓存可以存储 512 个引用。
  • readOnly=“true”:缓存对象是只读的,不能被修改。

2. 四种回收策略的原理分析

1. LRU

LRU(Least Recently Used)算法是一种常见的缓存回收策略,用于决定哪些数据应该被从缓存中移除以腾出空间给新数据。这种策略基于一个简单的理念:如果数据在一段时间内没有被使用,那么它在未来被使用的可能性也相对较低。下面详细介绍LRU算法的实现原理:

数据结构

LRU算法通常使用以下两种数据结构来实现:

  1. 哈希表(Hash Map):用于快速定位缓存项,O(1)时间复杂度。
  2. 双向链表(Doubly Linked List):用于维护缓存项的使用顺序,允许快速添加和删除节点。
工作原理
  1. 缓存访问:当缓存被访问时(无论是读取还是写入),该缓存项会被视为“最近使用”的,并移动到双向链表的头部(最近使用的位置)。
  2. 缓存添加:当新数据被添加到缓存时,如果缓存未满,新数据会被添加到链表头部。如果缓存已满,则链表尾部的数据(最不常用的数据)会被移除,新数据添加到头部。
  3. 缓存淘汰:当缓存达到容量上限时,链表尾部的数据(最长时间未被使用的数据)会被移除,为新数据腾出空间。
具体实现步骤
  1. 初始化:创建一个空的哈希表和一个空的双向链表。
  2. 访问缓存
    • 检查数据是否在哈希表中:
      • 如果在,更新该数据在链表中的位置(移动到头部),并返回数据。
      • 如果不在,从数据源获取数据,添加到链表头部,并在哈希表中创建条目。
  3. 添加数据
    • 如果缓存未满,直接添加数据到链表头部,并在哈希表中创建条目。
    • 如果缓存已满,先从链表尾部移除最不常用的数据,并从哈希表中删除相应条目,然后添加新数据到链表头部。
  4. 维护顺序:每次访问或添加数据时,都需要更新数据在双向链表中的位置,确保最近使用的数据总是在链表头部。
性能考虑
  • 时间复杂度:LRU算法在访问和添加数据时都能保持O(1)的时间复杂度,这得益于哈希表和双向链表的结合使用。
  • 空间复杂度:主要取决于缓存的大小,即存储的数据量。
应用场景

LRU算法广泛应用于操作系统的页面置换算法、Web服务器的图片或资源缓存、数据库查询结果缓存等领域,以提高系统性能和响应速度。

示例代码(伪代码)
class LRUCache {HashMap<Integer, Node> map;DoublyLinkedList cacheList;int capacity;public LRUCache(int capacity) {this.capacity = capacity;this.map = new HashMap<>();this.cacheList = new DoublyLinkedList();}public get(int key) {if (map.containsKey(key)) {Node node = map.get(key);cacheList.moveToHead(node); // Move to head to mark as recently usedreturn node.value;}return -1; // Not found}public put(int key, int value) {if (map.containsKey(key)) {Node node = map.get(key);node.value = value;cacheList.moveToHead(node);} else {Node newNode = new Node(key, value);map.put(key, newNode);cacheList.addHead(newNode);if (map.size() > capacity) {Node tail = cacheList.removeTail();map.remove(tail.key);}}}
}class Node {int key;int value;Node prev;Node next;public Node(int key, int value) {this.key = key;this.value = value;}
}class DoublyLinkedList {Node head;Node tail;public addHead(Node node) {// Add node to the head of the list}public removeTail() {// Remove node from the tail of the list and return it}public moveToHead(Node node) {// Move node to the head of the list}
}

以上是对LRU算法实现原理的详细介绍,包括其数据结构、工作原理、具体实现步骤以及性能和应用场景。

2. FIFO

FIFO(First In, First Out)算法是一种简单的缓存回收策略,它按照数据进入缓存的顺序来决定哪些数据应该被移除。这种策略的核心思想是:最先进入缓存的数据将会是最先被移除的数据。FIFO算法在实现上相对简单,但可能不如LRU(最近最少使用)算法那样高效,特别是在某些访问模式下。以下是FIFO算法的实现原理和详细步骤:

数据结构

FIFO算法通常使用以下数据结构来实现:

  1. 队列(Queue):用于维护缓存项的顺序,确保最先进入的数据最先被移除。
  2. 哈希表(Hash Map):用于快速定位缓存项,提供O(1)时间复杂度的访问。
工作原理
  1. 缓存访问:当缓存被访问时(无论是读取还是写入),该缓存项会被视为“最近使用”的。
  2. 缓存添加
    • 如果缓存未满,新数据会被添加到队列的尾部。
    • 如果缓存已满,队列头部的数据会被移除,新数据添加到队列尾部。
  3. 缓存淘汰:当缓存达到容量上限时,队列头部的数据(最先进入的数据)会被移除,为新数据腾出空间。
具体实现步骤
  1. 初始化:创建一个空的队列和一个空的哈希表。
  2. 访问缓存
    • 检查数据是否在哈希表中:
      • 如果在,返回数据,但不需要移动数据在队列中的位置。
      • 如果不在,从数据源获取数据,添加到队列尾部,并在哈希表中创建条目。
  3. 添加数据
    • 如果缓存未满,直接添加数据到队列尾部,并在哈希表中创建条目。
    • 如果缓存已满,先从队列头部移除最旧的数据,并从哈希表中删除相应条目,然后添加新数据到队列尾部。
  4. 维护顺序:每次添加新数据时,都需要更新队列和哈希表。
性能考虑
  • 时间复杂度:FIFO算法在访问和添加数据时都能保持O(1)的时间复杂度,这得益于哈希表的使用。
  • 空间复杂度:主要取决于缓存的大小,即存储的数据量。
应用场景

FIFO算法由于其简单性,适用于那些对缓存一致性要求不高的场景。它可能不适用于那些频繁访问某些数据的应用程序,因为这些数据可能会被错误地移除。

示例代码(伪代码)
class FIFOCache {HashMap<Integer, Integer> map;LinkedList<Integer> queue;int capacity;public FIFOCache(int capacity) {this.capacity = capacity;this.map = new HashMap<>();this.queue = new LinkedList<>();}public get(int key) {if (map.containsKey(key)) {return map.get(key);}return -1; // Not found}public put(int key, int value) {if (map.containsKey(key)) {// Key already exists, update the value and remove the key from the queuequeue.remove(map.get(key));map.put(key, value);queue.addLast(key);} else {if (map.size() >= capacity) {// Cache is full, remove the oldest itemint oldestKey = queue.removeFirst();map.remove(oldestKey);}// Add new itemmap.put(key, value);queue.addLast(key);}}
}class LinkedList {Node head;Node tail;public addLast(int value) {// Add value to the end of the list}public removeFirst() {// Remove the first element from the list and return it}
}class Node {int value;Node next;public Node(int value) {this.value = value;}
}

以上是对FIFO算法实现原理的详细介绍,包括其数据结构、工作原理、具体实现步骤以及性能和应用场景。FIFO算法虽然简单,但在某些情况下可能不如LRU算法有效,特别是在数据访问模式不均匀的情况下。

3. SOFT

SOFT(软引用)是一种缓存回收策略,它在 Java 中通过 java.lang.ref.SoftReference 类实现。软引用允许对象在内存不足时被垃圾收集器回收,但只要内存足够,这些对象就可以继续存活。这种策略特别适用于缓存机制,因为它可以在不影响应用程序功能的情况下,动态地释放内存资源。以下是 SOFT 缓存策略的实现原理和详细步骤:

工作原理
  1. 软引用:软引用是一种比强引用(Strong Reference)弱,但比弱引用(Weak Reference)强的引用类型。软引用关联的对象在内存不足时可以被垃圾收集器回收,但只要内存足够,它们就会继续存活。
  2. 垃圾收集器:Java 的垃圾收集器会定期检查内存使用情况,并在内存不足时尝试回收软引用对象。
  3. 缓存管理:使用软引用实现的缓存会在内存不足时自动释放缓存对象,从而为新对象腾出空间。
具体实现步骤
  1. 初始化缓存:创建一个缓存容器,如 HashMap,用于存储键和软引用对象的映射。
  2. 访问缓存
    • 当访问缓存时,首先检查软引用是否仍然有效(即其关联的对象是否已被回收)。
    • 如果软引用有效,返回其关联的对象。
    • 如果软引用无效,说明对象已被回收,可以重新从数据源获取数据,并创建新的软引用。
  3. 添加数据
    • 当添加新数据到缓存时,使用 SoftReference 包装该对象,并将其存储在缓存容器中。
    • 由于软引用的特性,如果内存不足,这些对象可能会被垃圾收集器回收。
  4. 内存回收:当系统内存不足时,垃圾收集器会尝试回收软引用对象。这使得缓存可以自动调整大小,释放不再需要的内存。
性能考虑
  • 时间复杂度:访问和添加数据的时间复杂度通常为 O(1),因为 HashMap 提供了快速的键值对查找。
  • 空间复杂度:缓存的大小取决于缓存对象的数量和每个对象的大小,但软引用允许在内存不足时自动回收对象,从而动态调整缓存大小。
应用场景

软引用缓存适用于以下场景:

  • 内存敏感的应用程序:在内存资源有限的设备上,如移动设备或嵌入式系统,软引用缓存可以动态地释放内存。
  • 大对象缓存:对于占用大量内存的对象,如图片或大型文档,软引用缓存可以在内存不足时自动释放这些对象。
  • 可有可无的缓存:在某些情况下,缓存数据的丢失不会对应用程序的功能产生重大影响,软引用缓存是一个很好的选择。
示例代码(Java)
import java.lang.ref.SoftReference;
import java.util.HashMap;public class SoftReferenceCache<K, V> {private HashMap<K, SoftReference<V>> cache = new HashMap<>();public V get(K key) {SoftReference<V> ref = cache.get(key);if (ref != null) {V value = ref.get();if (value != null) {return value;}// SoftReference has been cleared, remove it from the cachecache.remove(key);}return null;}public void put(K key, V value) {cache.put(key, new SoftReference<>(value));}
}

在这个示例中,SoftReferenceCache 使用 HashMap 存储键和软引用对象的映射。当访问缓存时,首先检查软引用是否有效。如果软引用无效,说明对象已被回收,可以重新从数据源获取数据,并创建新的软引用。

小结

SOFT 缓存策略通过使用软引用来实现缓存对象的自动回收,从而在内存不足时动态地释放内存资源。这种策略特别适用于内存敏感的应用程序,或者那些缓存数据丢失不会对应用程序功能产生重大影响的场景。

4. WEAK

WEAK(弱引用)是一种比软引用(Soft Reference)更弱的引用类型,它允许对象在下一次垃圾收集时被回收,无论内存是否足够。在 Java 中,弱引用是通过 java.lang.ref.WeakReference 类实现的。弱引用通常用于实现缓存,其中对象的生命周期不需要超过引用本身的生命周期。以下是 WEAK 缓存策略的实现原理和详细步骤:

工作原理
  1. 弱引用:弱引用是一种对对象的引用,它不会阻止垃圾收集器回收其引用的对象。这意味着只要没有其他的强引用指向该对象,对象就可以被垃圾收集器回收。
  2. 垃圾收集器:Java 的垃圾收集器会定期执行,当它发现某个对象只被弱引用所引用时,就会回收该对象占用的内存。
  3. 缓存管理:使用弱引用实现的缓存允许对象在不再被使用时被快速回收,即使内存尚未不足。
具体实现步骤
  1. 初始化缓存:创建一个缓存容器,如 HashMap,用于存储键和弱引用对象的映射。
  2. 访问缓存
    • 当访问缓存时,首先检查弱引用是否仍然有效(即其关联的对象是否已被回收)。
    • 如果弱引用有效,返回其关联的对象。
    • 如果弱引用无效,说明对象已被回收,可以重新从数据源获取数据,并创建新的弱引用。
  3. 添加数据
    • 当添加新数据到缓存时,使用 WeakReference 包装该对象,并将其存储在缓存容器中。
    • 由于弱引用的特性,这些对象可能会在下一次垃圾收集时被回收。
  4. 内存回收:当垃圾收集器执行时,它会检查所有弱引用,并回收那些只被弱引用的对象。
性能考虑
  • 时间复杂度:访问和添加数据的时间复杂度通常为 O(1),因为 HashMap 提供了快速的键值对查找。
  • 空间复杂度:缓存的大小取决于缓存对象的数量和每个对象的大小,但由于弱引用允许对象在下一次垃圾收集时被回收,因此缓存不会长时间占用大量内存。
应用场景

弱引用缓存适用于以下场景:

  • 内存敏感的应用程序:在内存资源有限的设备上,如移动设备或嵌入式系统,弱引用缓存可以快速释放内存。
  • 临时对象缓存:对于只在特定时间内需要的对象,使用弱引用缓存可以确保这些对象在不再需要时迅速被回收。
  • 可丢弃的缓存:在某些情况下,缓存数据的丢失不会对应用程序的功能产生重大影响,弱引用缓存是一个很好的选择。
示例代码(Java)
import java.lang.ref.WeakReference;
import java.util.HashMap;public class WeakReferenceCache<K, V> {private HashMap<K, WeakReference<V>> cache = new HashMap<>();public V get(K key) {WeakReference<V> ref = cache.get(key);if (ref != null) {V value = ref.get();if (value != null) {return value;}// WeakReference has been cleared, remove it from the cachecache.remove(key);}return null;}public void put(K key, V value) {cache.put(key, new WeakReference<>(value));}
}

在这个示例中,WeakReferenceCache 使用 HashMap 存储键和弱引用对象的映射。当访问缓存时,首先检查弱引用是否有效。如果弱引用无效,说明对象已被回收,可以重新从数据源获取数据,并创建新的弱引用。

小结

WEAK 缓存策略通过使用弱引用来实现缓存对象的快速回收,这对于内存敏感的应用程序或临时对象的缓存非常有用。这种策略允许应用程序在不牺牲内存的情况下,临时存储和管理数据对象。

最后

MyBatis 的缓存机制非常灵活,可以通过简单的配置来满足不同的性能需求。合理地使用缓存可以显著提高应用程序的性能,尤其是在处理大量数据库查询时。然而,开发者需要注意缓存的一致性和并发问题,特别是在使用可读写缓存时。

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

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

相关文章

Vue3计算属性终极实战:可媲美Element Plus Tree组件研发之节点勾选

前面完成了JuanTree组件的节点编辑和保存功能后&#xff0c;我们把精力放到节点勾选功能实现上来。**注意&#xff0c;对于组件的开发者来说&#xff0c;要充分考虑用户的使用场景&#xff0c;组件提供的多个特性同时启用时必须要工作良好。**就拿Tree组件来说&#xff0c;用户…

大模型训练为何离不开GPU?深度解析与显卡推荐

在人工智能的蓬勃发展中&#xff0c;大模型的训练成为了热门话题。然而&#xff0c;许多人还不清楚为什么训练这些庞大的模型需要GPU&#xff08;图形处理单元&#xff09;。本文将深入探讨GPU在大模型训练中的重要性&#xff0c;并推荐几款适合的显卡。 一、GPU与CPU的区别 …

Java消失的数字

题目要求 数组nums包含从0到n的所有整数&#xff0c;但其中缺了一个。请编写代码找出那个缺失的整数。你有办法在O(n)时间内完成吗&#xff1f; 示例 1&#xff1a; 输入&#xff1a;[3,0,1] 输出&#xff1a;2 示例 2&#xff1a; 输入&#xff1a;[9,6,4,2,3,5,7,0,1] 输出&a…

如何保证前后端交互信息不被篡改。

先说说前后端有哪些认证方式来保证&#xff1a; 基于 session 的认证方式&#xff1a;前端在用户登录成功后&#xff0c;后端会在服务器端生成一个唯一的 session ID&#xff0c;并将该 session ID 返回给前端&#xff0c;在后续的请求中&#xff0c;前端需要带上该 session ID…

【CUDA Runtime】第一个“Hello World“程序

文章目录 前言前提须知CUDA Runtime 简介核心功能优势和应用 使用CudaRuntime进行第一个"Hello world"程序创建CudaRuntime工程选择GPU函数原型参数返回值作用 获取支持Cuda的GPU信息获取支持Cuda的GPU数量获取设备属性运行展示 在GPU上分配内存把需要运行的主机内存…

scrapy出现OSError: could not get source code错误解决

出现问题如下&#xff1a; Traceback (most recent call last):File "C:\Users\admin\Desktop\crawler_scrapy_us\venv\lib\site-packages\scrapy\utils\defer.py", line 73, in mustbe_deferredresult f(*args, **kw)File "C:\Users\admin\Desktop\crawler_sc…

数据库密码实现加盐加密处理

在实际的开发中&#xff0c;我们的数据库密码一般都是明文的方式存储在数据库中&#xff0c;但是&#xff0c;这种操作非常不安全&#xff0c;容易被黑&#xff01; 那么&#xff0c;此时我们就需要对其进行加密处理&#xff0c;市面上比较常见的就是MD5加密了&#xff0c;但是…

【Linux】syscall sys_write流程摸索

这是通过tty进行摸索sys_write的流程。 在前面的博客里&#xff0c;我们可以看到基于内核C语言源代码日志打印&#xff0c;在打印的日志里边包含&#xff1a;日期&#xff0c;时间&#xff0c;当前文件所在代码目录&#xff0c;当前执行函数名&#xff0c;当前文件执行行号&am…

运维团队如何借助分布式部署提升监控效率与可靠性

随着企业IT基础设施的日益复杂和分布式架构的广泛应用&#xff0c;传统的监控解决方案已经难以满足现代运维团队的需求。在这样的背景下&#xff0c;分布式部署作为一种新型的监控架构&#xff0c;以其灵活性、可扩展性和高可用性&#xff0c;成为了运维团队提升监控效率与可靠…

C++模版基础知识与STL基本介绍

目录 一. 泛型编程 二. 函数模板 1. 概念 2. 函数模版格式 3. 函数模版的原理 4. 模版函数的实例化 (1). 隐式实例化 (2.) 显式实例化 5. 模版参数的匹配原则 三. 类模板 1. 类模板的定义格式 2. 类模板的实例化 四. STL的介绍 1. 什么是STL&#xff1f; 2. STL的版…

Linux Centos防火墙相关操作命令

防火墙基础操作 #开启防火墙 systemctl start firewalld#关闭防火墙 systemctl stop firewalld重新加载防火墙规则(改了规则后均需执行) firewall-cmd --reload防火墙开放某端口 firewall-cmd --permanent --add-port8080/tcp防火墙禁用某ip访问 firewall-cmd --permanent …

3.5-RNN文本生成

1语言模型生成文本的顺序 前面我们已经能够实现使用下图的LSTM网络进行语言建模&#xff1b; 对于一个已经在语料库上学习好的LSTM模型&#xff1b;如果语料库就只是you say goobye and i say hello&#xff1b;那么当把单词i输入到模型中&#xff0c;Time xxx层的第一个LSTM…

苍穹外卖01

0. 配置maven (仅一次的操作 1.项目导入idea 2. 保证nginx服务器运行 &#xff08;nginx.exe要在非中文的目录下&#xff09; 开启服务&#xff1a; start nginx 查看任务进程是否存在&#xff1a; tasklist /fi "imagename eq nginx.exe" 关闭ngi…

查看、指定使用的 GPU 数量和编号

在使用 PyTorch 框架时&#xff0c;可以通过以下步骤查看可用的 GPU 数量&#xff0c;指定使用的 GPU 编号&#xff0c;并在代码中体现这一点。下面以2个GPU为例&#xff1a; 目录 一、脚本代码块实现1. 查看可用的 GPU2. 指定使用 GPU 的数量和编号使用 CUDA_VISIBLE_DEVICES…

中文之美,美在辞藻富丽,也美在情感含蓄内敛。

文章目录 引言句句不提幸福,句句都是幸福句句不提释怀,句句都是释怀句句不提爱意,句句都是爱意句句不提安慰,句句都是安慰句句不提遗憾,句句都是遗憾句句不提思念,句句都是思念引言 许多句子没有将主题直抒胸臆,却通过字词间的呼应、碰撞,让人感受到“言未表而意无穷”…

第12章 Express的RESTful API开发(二)

3. 路由与中间件 在Express中&#xff0c;路由用于定义应用的各个端点&#xff08;URI&#xff09;及其处理程序。中间件是一个可以访问请求对象&#xff08;req&#xff09;、响应对象&#xff08;res&#xff09;和下一个中间件函数的函数。中间件用于处理请求之前执行一些操…

java高级——Exception异常类基本解读

java高级——Exception异常类基本解读 前情提要文章介绍继承结构异常详解1. 异常的定义2. 异常的分类3.3 异常的处理机制3.3.1 try catch finally语句3.3.2 throw关键字3.3.3 throws关键字 4. 浅谈如何有效的避免异常的发生5. 自定义异常6. 常见的RuntimeException 总结 前情提…

JDBC(Java访问数据库)

Java Database Connectivity&#xff1a;Java访问数据库的解决方案 JDBC定义了一套标准接口&#xff0c;即访问数据库的通用API&#xff0c; 不同的数据库厂商根据各自数据库的特点去实现这些接口。 JDBC希望用相同的方式访问不同的数据库&#xff0c;让具体的数据库操作与数…

HDU1056——HangOver,HDU1057——A New Growth Industry,HDU1058——Humble Numbers

目录 HDU1056——HangOver 题目描述 运行代码 代码思路 HDU1057——A New Growth Industry 题目描述 运行代码 代码思路 HDU1058——Humble Numbers 题目描述 运行代码 代码思路 HDU1056——HangOver 题目描述 Problem - 1056 运行代码 #include <iostream&…

Elasticsearch面试三道题

针对Elasticsearch的面试题&#xff0c;从简单到困难&#xff0c;我可以给出以下三道题目&#xff1a; 1. Elasticsearch的基本概念与优势 问题&#xff1a;请简要介绍Elasticsearch是什么&#xff0c;并说明它相比传统数据库的优势有哪些&#xff1f; 答案&#xff1a; El…