241221面经
1,JVM 的实现中堆、栈和方法区的区别是什么?
- 堆(Heap)
- 功能
- 堆是 JVM 内存中最大的一块,主要用于存储对象实例。无论是通过
new
关键字创建的对象,还是数组,都在堆上分配内存。 - 它是被所有线程共享的内存区域。这意味着多个线程可以访问和操作堆中的对象。
- 堆是 JVM 内存中最大的一块,主要用于存储对象实例。无论是通过
- 特点
- 由于堆是共享的,并且对象的生命周期不确定,所以在堆上分配内存时需要考虑垃圾回收(GC)机制。当对象不再被引用时,垃圾回收器会回收这些对象占用的内存空间。
- 堆内存的大小可以通过 JVM 参数进行调整,例如
-Xmx
用于设置最大堆内存,-Xms
用于设置初始堆内存。
- 功能
- 栈(Stack)
- 功能
- 栈主要用于存储局部变量、方法调用信息(包括方法参数、局部变量、返回地址等)。
- 每个线程都有自己的栈,即栈是线程私有的。这保证了每个线程在执行方法时的独立性,不会受到其他线程的干扰。
- 特点
- 栈的操作遵循后进先出(LIFO)原则。当一个方法被调用时,方法的局部变量和操作数栈等信息会被压入栈中,当方法执行结束时,这些信息会从栈中弹出。
- 栈的大小通常是固定的(虽然可以通过 JVM 参数在一定范围内调整),如果一个线程的栈空间不够用,例如在递归调用没有终止条件的情况下,会导致栈溢出(
StackOverflowError
)。
- 功能
- 方法区(Method Area)
- 功能
- 方法区用于存储类的结构信息,如类的常量池、字段、方法数据、方法代码、构造函数、接口定义等。
- 它也是被所有线程共享的内存区域。在类加载时,类的相关信息会被加载到方法区。
- 特点
- 在 Java 8 之前,方法区是堆的一部分,在 Java 8 中,方法区被元空间(Metaspace)取代,元空间使用本地内存。
- 如果方法区中的内存不够用,例如在大量加载类并且没有卸载的情况下(在 Java 7 及以前可能导致永久代内存溢出,Java 8 中可能导致元空间内存溢出),会抛出
OutOfMemoryError
。
- 功能
2,导致 OOM(Out of Memory)的原因有哪些?
一、堆内存溢出
- 对象创建过多且未及时释放
- 解释
- 如果程序中不断地创建大对象,并且这些对象在使用完毕后没有被及时释放(例如没有失去引用),堆内存就会逐渐被填满,最终导致 OOM。例如,在一个循环中不断地创建大数组:
- 解释
import java.util.ArrayList;
import java.util.List;public class HeapOOMExample {public static void main(String[] args) {List<byte[]> list = new ArrayList<>();while (true) {list.add(new byte[1024 * 1024]); // 不断分配1MB大小的数组}}
}
- 上述代码中,在
while
循环里不断地往list
中添加 1MB 大小的字节数组,堆内存会很快被耗尽,从而引发OutOfMemoryError: Java heap space
- 内存泄漏
- 解释
- 内存泄漏是指程序中存在某些对象,它们本应该被回收但由于错误的引用关系而无法被回收,长期积累导致堆内存耗尽。例如,以下是一个简单的内存泄漏示例:
- 解释
import java.util.ArrayList;
import java.util.List;class MemoryLeakExample {private static List<Object> leakList = new ArrayList<>();public static void main(String[] args) {for (int i = 0; i < 100000; i++) {Object obj = new Object();leakList.add(obj);}// 这里没有将leakList中的对象释放,导致这些对象无法被垃圾回收}
}
- 在这个例子中,
leakList
不断地添加对象,但没有机制将这些对象从列表中移除,导致这些对象始终被leakList
所引用,无法被垃圾回收,最终可能导致堆内存溢出。
二、方法区内存溢出
- 大量加载类且未卸载(Java 7 及以前永久代相关)
- 在 Java 7 及以前,方法区的实现是永久代。如果程序中大量使用动态代理或者加载了过多的类并且没有卸载机制,可能会导致永久代内存溢出。例如,以下是一个简单的导致永久代 OOM 的示例(在 Java 7 及以前):
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;interface IService {}class ServiceImpl implements IService {}public class MethodAreaOOMExample {public static void main(String[] args) {List<IService> services = new ArrayList<>();while (true) {IService service = (IService) Proxy.newProxyInstance(MethodAreaOOMExample.class.getClassLoader(),new Class[]{IService.class},(proxy, method, args1) -> null);services.add(service);}}
}
- 上述代码通过不断地生成动态代理类,可能会导致永久代内存溢出,抛出
OutOfMemoryError: PermGen space
。
- 元空间配置不当(Java 8 及以后)
- 在 Java 8 中,方法区被元空间取代,元空间使用本地内存。如果元空间的大小配置不合理,例如设置得太小,当程序需要加载大量的类信息时,就可能导致元空间内存溢出,抛出
OutOfMemoryError: Metaspace
。
- 在 Java 8 中,方法区被元空间取代,元空间使用本地内存。如果元空间的大小配置不合理,例如设置得太小,当程序需要加载大量的类信息时,就可能导致元空间内存溢出,抛出
三、栈内存溢出
- 递归调用没有终止条件
- 当一个方法进行递归调用且没有终止条件时,栈帧会不断地压入栈中,最终导致栈内存不够用,引发
StackOverflowError
。例如:
- 当一个方法进行递归调用且没有终止条件时,栈帧会不断地压入栈中,最终导致栈内存不够用,引发
public class StackOverflowExample {public static void main(String[] args) {recursiveMethod();}public static void recursiveMethod() {recursiveMethod();}
}
- 上述代码中,
recursiveMethod
方法不断地递归调用自身,没有终止条件,很快就会导致栈内存溢出。
- 线程请求的栈深度大于虚拟机所允许的最大深度
- 解释
- 每个线程的栈大小是有一定限制的,如果一个线程的方法调用层次过深,即请求的栈深度超过了虚拟机所允许的最大深度,也会导致栈内存溢出。这种情况可能在一些复杂的算法或者嵌套调用很深的程序中出现。
- 解释
3,OOM 怎么分析?
当出现 OOM(Out of Memory)问题时,可以通过以下方法进行分析:
一、使用内存分析工具
- Eclipse Memory Analyzer(MAT)
- 原理和使用方法
- MAT 可以分析堆转储文件(heap dump)。当 JVM 发生 OOM 时,可以配置 JVM 参数
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=<path>
,让 JVM 在 OOM 时自动生成堆转储文件到指定路径<path>
。 - 打开 MAT 后,导入堆转储文件。它可以通过分析对象的引用关系来查找内存泄漏。例如,它能生成各种报告:
- 对象的支配树(Object Dominator Tree):可以显示哪些对象在内存中占据主导地位,帮助找到大对象的持有者。
- 直方图(Histogram):可以列出堆中每个类的实例数量和占用内存大小,通过查看直方图可以快速发现是否存在某个类的大量实例导致内存溢出。
- MAT 可以分析堆转储文件(heap dump)。当 JVM 发生 OOM 时,可以配置 JVM 参数
- 原理和使用方法
- VisualVM
- 原理和使用方法
- VisualVM 是 JDK 自带的性能分析工具。它可以实时监控 JVM 的内存使用情况。在运行 Java 程序时,可以启动 VisualVM 来观察内存变化。
- 当发生 OOM 时,也可以通过它来生成和分析堆转储文件。在 VisualVM 界面中,选择目标 Java 进程,然后通过 “堆 Dump” 功能生成堆转储文件,之后可以在 VisualVM 中对堆转储文件进行分析,查看线程状态、类加载情况等。它可以直观地看到堆内存、方法区等区域的使用比例,帮助定位问题。
- 原理和使用方法
二、查看日志
- 应用服务器日志
- 原理和查看要点
- 很多应用服务器(如 Tomcat、WebLogic 等)在发生 OOM 时会记录相关的错误日志。这些日志通常包含了发生 OOM 时的栈跟踪信息、内存使用情况等。
- 查看日志时,要重点关注是在创建哪个对象时发生的 OOM,或者是在加载类、执行特定方法等操作时出现的问题。例如,如果日志中显示在创建数据库连接对象时发生 OOM,可能需要检查数据库连接池的配置和使用情况。
- 原理和查看要点
- 框架相关日志
- 原理和查看要点
- 如果应用使用了一些框架(如 Spring 等),框架本身也可能会记录与内存相关的信息。例如,Spring 在管理对象的创建和生命周期时,如果出现内存问题,可能会在其日志中有所体现。
- 查看框架日志可以了解是否是由于框架的某些操作(如对象的缓存机制、循环依赖等)导致的 OOM。
- 原理和查看要点
三、分析 GC 日志
- 配置和获取 GC 日志
- 原理和操作方法
- 通过配置 JVM 参数来输出详细的 GC 日志,例如
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log
。这些参数可以让 JVM 在运行过程中记录垃圾回收的详细情况到gc.log
文件中。
- 通过配置 JVM 参数来输出详细的 GC 日志,例如
- 原理和操作方法
- 分析 GC 日志内容
- 分析要点
- 如果发现频繁的 Full GC 但内存仍然无法释放,可能存在内存泄漏。例如,在 GC 日志中,如果看到 Full GC 的频率越来越高,并且每次 Full GC 后堆内存的使用量并没有明显下降,这就暗示可能有对象无法被正确回收。
- 还可以查看新生代和老年代的垃圾回收情况,判断是新生代对象创建过多导致频繁 Minor GC 进而引发 Full GC,还是老年代本身存在内存占用过大的问题(如大对象直接进入老年代且长期不释放)。
- 分析要点
4,Java GC 的完整流程是什么?
Java GC(垃圾回收)的完整流程如下:
一、新生代 GC(Minor GC)
- 区域划分
- 新生代主要分为 Eden 区和两个 Survivor 区(通常是 Survivor0 和 Survivor1)。大部分对象在 Eden 区中创建。
- 触发条件
- 当 Eden 区满时,会触发 Minor GC。
- 回收过程
- 在 Minor GC 过程中,Eden 区中存活的对象会被复制到 Survivor 区(其中一个 Survivor 区,假设是 Survivor0)。
- 如果 Survivor0 区放不下,部分对象会直接晋升到老年代。
- 同时,上一次 Minor GC 后存留在 Survivor1 区的对象如果还存活且年龄达到一定阈值(默认是 15,可以通过
-XX:MaxTenuringThreshold
参数设置),也会晋升到老年代。
二、老年代 GC(Major GC/Full GC)
- 触发条件
- 当老年代空间不足时,会触发 Full GC。
- 或者在进行 Minor GC 时发现要晋升到老年代的对象大小超过了老年代剩余空间时,也会触发 Full GC。
- 此外,System.gc () 方法调用(虽然不保证一定会执行)、元空间不足等情况也可能导致 Full GC。
- 回收过程
- Full GC 会对整个堆(包括新生代和老年代)进行垃圾回收。
- 老年代的垃圾回收算法通常采用标记 - 整理(Mark - Compact)或标记 - 清除(Mark - Sweep)。
- 标记 - 清除(Mark - Sweep):
- 首先标记出需要回收的对象,然后统一回收被标记的对象。这种方法会产生内存碎片。
- 标记 - 整理(Mark - Compact):
- 首先标记出需要回收的对象,然后将存活的对象向一端移动,最后清理掉端边界以外的内存。
- 标记 - 清除(Mark - Sweep):
三、垃圾收集算法
- 标记 - 复制(Mark - Copy)
- 新生代的垃圾回收(Minor GC)通常采用这种算法的变种(使用 Eden 区和 Survivor 区)。即将内存分为两块(Eden 和 Survivor 区组合),每次只使用其中一块。当这一块内存满时,将存活的对象复制到另一块内存,然后清空使用过的这块内存。
- 标记 - 清除(Mark - Sweep)
- 如上述老年代 GC 中提到,先标记要回收的对象,再进行清除,但会产生内存碎片。
- 标记 - 整理(Mark - Compact)
- 同样先标记要回收的对象,然后将存活对象整理到一端,清理另一端的空间,常用于老年代 GC。
5,Java 中的 young Gc、old Gc、full Gc 和 mixed Gc 的区别是什么?
在 Java 中,Young GC、Old GC、Full GC 和 Mixed GC 存在以下区别:
回收区域
- Young GC:也称为 Minor GC,主要针对新生代进行垃圾回收。新生代通常由 Eden 区和两个 Survivor 区组成,大部分对象在 Eden 区创建,当 Eden 区满时,就会触发 Young GC。
- Old GC:只针对老年代进行垃圾回收。在一些特殊情况下,如老年代空间不足时,会单独触发 Old GC 来尝试回收老年代的垃圾对象,但这种情况相对较少见。
- Full GC:对整个 Java 堆(包括新生代和老年代)以及方法区进行垃圾回收。当老年代空间不足,或者在进行 Young GC 时发现要晋升到老年代的对象大小超过了老年代剩余空间等情况时会触发 Full GC。
- Mixed GC:主要用于 G1 垃圾收集器,它会同时回收新生代和部分老年代的垃圾对象。G1 收集器会根据预测模型,优先回收垃圾最多的区域,即可能既有新生代的垃圾回收,也有老年代的部分区域垃圾回收。
回收算法
- Young GC:通常采用复制算法。将 Eden 区和 Survivor 区中的存活对象复制到另一个 Survivor 区,然后清空 Eden 区和原来的 Survivor 区。
- Old GC:一般采用标记 - 压缩算法或标记 - 清除算法。标记 - 压缩算法会先标记出存活对象,然后将存活对象向一端移动,最后清理掉端边界以外的内存;标记 - 清除算法则是先标记需要回收的对象,然后直接回收被标记的对象,会产生内存碎片。
- Full GC:新生代部分采用复制算法,老年代部分采用标记 - 压缩或标记 - 清除算法。
- Mixed GC:在回收新生代时采用复制算法,在回收老年代部分区域时采用类似标记 - 压缩或标记 - 清除的算法。
回收频率
- Young GC:相对频繁,因为新生代的对象通常朝生夕死,垃圾对象产生的速度较快。
- Old GC:频率相对较低,因为老年代的对象生命周期较长,一般不会频繁地变为垃圾。
- Full GC:频率最低,但一旦发生 Full GC,通常会暂停所有应用线程,对系统的性能影响较大,所以应尽量避免频繁的 Full GC。
- Mixed GC:在 G1 收集器中,其频率介于 Young GC 和 Full GC 之间,会根据垃圾的分布情况动态调整回收的频率和区域。
暂停时间
- Young GC:暂停时间通常较短,因为新生代的对象数量相对较少,复制存活对象的操作相对较快。
- Old GC:暂停时间相对较长,因为老年代的对象数量可能较多,标记和整理的操作比较复杂。
- Full GC:暂停时间最长,由于要对整个堆和方法区进行垃圾回收,涉及的对象数量多,回收过程复杂,所以会导致应用程序长时间停顿。
- Mixed GC:暂停时间通常比 Full GC 短,但比 Young GC 长,因为它既要回收新生代又要回收部分老年代。
6,MySQL 中是使用悲观锁还是乐观锁?
在 MySQL 中,悲观锁和乐观锁都有使用场景,以下是它们的具体情况:
悲观锁
- 概念:假定会发生并发冲突,在操作数据之前先获取锁,以阻塞其他事务对该数据的访问,直到当前事务完成。
- 使用方式
- 在 MySQL 中,可以通过
FOR UPDATE
和LOCK IN SHARE MODE
语句来实现悲观锁。 FOR UPDATE
会对查询的行加排他锁,其他事务不能对这些行进行读写操作,直到当前事务提交或回滚。例如SELECT * FROM table_name WHERE condition FOR UPDATE;
。LOCK IN SHARE MODE
则是对查询的行加共享锁,允许其他事务对这些行进行读操作,但不能进行写操作,如SELECT * FROM table_name WHERE condition LOCK IN SHARE MODE;
。
- 在 MySQL 中,可以通过
- 适用场景
- 适用于对数据的一致性要求非常高,且并发冲突概率较大的场景。
- 如在金融系统中,对账户余额的修改操作,需要确保在修改过程中数据的准确性和一致性,不允许其他事务同时进行修改。
乐观锁
- 概念:假定在数据处理过程中不会发生并发冲突,在更新数据时才去检查数据是否被其他事务修改过,如果没有被修改,则执行更新操作;如果被修改,则根据具体的业务逻辑进行处理,如重试或报错。
- 使用方式
- 通常通过在表中添加一个版本号或时间戳字段来实现。
- 当更新数据时,先查询出当前数据的版本号或时间戳,然后在更新语句中加入对版本号或时间戳的判断条件。例如,假设表中有
version
字段,更新语句可以写成UPDATE table_name SET column1=value1, version=version+1 WHERE condition AND version=original_version;
。
- 适用场景
- 适用于对并发性能要求较高,且并发冲突概率相对较小的场景。
- 如在一些电商系统中,对商品库存的查询和更新操作,如果并发冲突不是特别频繁,可以使用乐观锁来提高系统的并发性能。
7,MySQL支持哪几种索引类型?
MySQL 支持多种索引类型,以下是一些主要的索引类型:
B-Tree 索引
- 特点:B-Tree 索引是 MySQL 中最常用的索引类型,使用 B 树数据结构存储索引数据。它具有良好的平衡性,能够保证数据的查找、插入和删除操作的效率相对稳定,适用于范围查询、等值查询等多种查询场景。
- 适用场景:一般用于普通的列查询,如
WHERE
子句中的等值查询、范围查询,以及ORDER BY
和GROUP BY
操作等。
哈希索引
- 特点:哈希索引基于哈希表实现,通过对索引列的值进行哈希运算来确定数据的存储位置,因此具有非常快的等值查询速度,通常只需要一次哈希运算就可以找到对应的数据。但哈希索引不支持范围查询和排序操作,因为哈希表本身是无序的。
- 适用场景:适用于等值查询非常频繁且不需要范围查询和排序的场景,如在内存数据库或缓存系统中,对一些经常作为查询条件的唯一键进行哈希索引可以提高查询效率。
全文索引
- 特点:全文索引主要用于对文本类型的数据进行全文搜索,它能够在文本中查找包含特定关键词的记录。MySQL 使用特定的全文搜索算法来处理文本数据,支持自然语言查询和布尔查询等多种查询方式。
- 适用场景:适用于对大量文本数据进行搜索的场景,如文章搜索、博客搜索、产品描述搜索等。
空间索引
- 特点:空间索引用于对空间数据类型(如
GEOMETRY
、POINT
、LINESTRING
等)进行索引,以便快速查询和处理空间数据。空间索引通常采用 R 树或类似的数据结构来存储空间数据的索引信息,能够高效地处理空间关系查询,如包含、相交、距离等。 - 适用场景:在地理信息系统(GIS)、地图应用、位置服务等领域中,用于对地理位置数据进行快速查询和分析。
组合索引
- 特点:组合索引是由多个列组成的索引,也称为多列索引或复合索引。在使用组合索引时,MySQL 会根据索引列的顺序和查询条件来决定是否使用该索引以及如何使用该索引。组合索引的列顺序非常重要,应该将最常作为查询条件且选择性高的列放在前面。
- 适用场景:当查询经常需要同时使用多个列作为条件时,使用组合索引可以提高查询效率。
唯一索引
- 特点:唯一索引要求索引列的值必须是唯一的,不允许出现重复的值。在创建唯一索引时,MySQL 会自动检查索引列的值是否唯一,如果插入或更新的数据导致索引列出现重复值,将会报错。
- 适用场景:用于保证列的唯一性,如在用户表中对用户名或邮箱等字段创建唯一索引,可以防止重复注册等问题。
主键索引
- 特点:主键索引是一种特殊的唯一索引,它要求索引列的值不仅唯一,而且不能为空。每个表只能有一个主键,主键索引通常用于唯一标识表中的每一行数据,在数据查询、关联操作等方面具有重要作用。
- 适用场景:作为表的主键,用于唯一标识表中的每一行数据,在数据的插入、更新、删除和查询等操作中都经常使用。
7,MySQL中哈希索引是如何实现的?在什么情况下使用哈希索引?
哈希索引在 MySQL 中的实现及使用情况如下:
实现原理
- 哈希函数计算:当向表中插入一条记录时,MySQL 会对索引列的值使用特定的哈希函数进行计算,得到一个哈希值。这个哈希函数通常具有较好的均匀性,能将不同的输入值均匀地映射到哈希空间中。
- 哈希表存储:计算得到的哈希值作为索引,将记录的存储位置或指向记录的指针存储在哈希表中。哈希表是一种数据结构,它可以根据哈希值快速定位到对应的记录,通常具有接近常量时间的查找复杂度,即 O (1)。
- 冲突处理:由于不同的索引列值可能会计算出相同的哈希值,即发生哈希冲突,MySQL 会采用一定的冲突处理机制。常见的方法有链地址法,即将具有相同哈希值的记录以链表的形式存储在哈希表的同一个桶中;还有开放地址法,当发生冲突时,通过一定的探测算法寻找下一个可用的存储位置。
使用情况
- 等值查询频繁的场景:如果查询语句主要是基于某个列的等值查询,且该列的取值具有较高的离散度,即不同值的数量较多,那么哈希索引可以提供非常快速的查询速度。例如,在一个用户登录系统中,经常需要根据用户名查找用户记录,对用户名列创建哈希索引可以快速定位到对应的用户记录。
- 缓存系统或内存数据库:在一些缓存系统或内存数据库中,数据通常是临时存储且对查询速度要求极高,哈希索引可以充分发挥其快速等值查询的优势。例如,Memcached 和 Redis 等内存数据库在存储键值对时,通常会使用哈希索引来快速查找键对应的 value。
- 作为辅助索引提高性能:在一些复杂的查询场景中,如果某个列经常作为连接条件或过滤条件,且该列的等值查询性能对整体查询性能影响较大,可以考虑在该列上创建哈希索引作为辅助索引,以提高查询的执行效率。
- 不涉及范围查询和排序的场景:由于哈希索引本身是无序的,不支持范围查询和排序操作,如果业务场景中不存在对索引列的范围查询和排序需求,那么哈希索引是一个不错的选择。
8,B+ 树有哪些限制?
B + 树作为一种广泛应用的数据结构,在数据库索引等场景中具有诸多优势,但也存在一些限制,主要体现在以下几个方面:
空间复杂度
- 节点存储开销:B + 树的每个节点都需要存储一定的元数据,如指针、关键字等。随着数据量的增加,节点数量也会相应增多,这会导致较大的存储开销。
- 叶子节点链表空间:B + 树的叶子节点通常通过链表连接,以方便范围查询。但维护这个链表也需要额外的空间来存储指针,当数据量非常大时,链表指针所占用的空间也不容忽视。
写操作性能
- 插入操作的分裂问题:当向 B + 树中插入新的数据时,如果叶子节点已满,就需要进行分裂操作。分裂操作可能会涉及到多个节点的调整,包括重新分配关键字和指针,这会带来一定的性能开销,尤其是在频繁插入数据的情况下,可能会导致频繁的分裂,影响整体性能。
- 删除操作的合并问题:删除操作可能导致叶子节点或非叶子节点中的关键字数量过少,需要进行合并操作。合并操作同样需要对节点进行调整和重新分配,也会消耗一定的时间和资源,并且可能会影响到树的平衡性。
查询性能
- 高度对查询的影响:虽然 B + 树的高度通常相对平衡,但在数据量极大的情况下,树的高度可能仍然会比较高。这会导致在查询时需要多次访问磁盘或内存中的节点,增加了查询的时间成本,尤其是对于一些深度较深的查询,性能可能会受到一定的影响。
- 范围查询的局限性:尽管 B + 树的叶子节点通过链表连接,方便了一定范围内的顺序查询,但在处理一些复杂的范围查询时,可能效率并不理想。例如,当查询条件涉及多个不连续的范围时,可能需要多次遍历叶子节点链表,导致查询速度变慢。
并发控制难度
- 锁粒度问题:在多用户并发访问 B + 树时,为了保证数据的一致性,需要进行并发控制。由于 B + 树的节点之间存在关联,通常需要对整个树或较大的子树进行加锁,导致锁粒度较大,并发度受到限制,容易出现锁竞争,影响系统的并发性能。
- 死锁风险:在并发操作 B + 树时,由于多个事务可能同时对不同节点进行加锁和操作,容易出现死锁的情况。一旦发生死锁,需要进行复杂的死锁检测和解除操作,增加了系统的复杂性和开销。
数据动态性适应性
- 数据倾斜问题:当数据分布不均匀,出现数据倾斜时,B + 树的性能可能会受到较大影响。例如,某些关键字对应的记录数量远远多于其他关键字,可能导致 B + 树的某些分支非常深,而其他分支则很浅,破坏了树的平衡性,从而影响查询和更新操作的效率。
- 数据更新频繁问题:如果数据的更新频率非常高,B + 树需要不断地进行节点的调整和维护,以保证树的正确性和性能。这会增加系统的负担,尤其是在高并发的情况下,可能导致系统性能下降。
10,如何设计一个 MySQL 表?
设计一个良好的 MySQL 表需要综合考虑多个因素,以下是设计 MySQL 表及建表时的注意事项:
需求分析与规划
- 明确业务需求:在设计表之前,需要深入了解系统的业务需求,包括数据的来源、用途、处理流程等。与业务部门充分沟通,梳理出系统中涉及的实体和实体之间的关系,确定需要存储哪些数据以及数据之间的关联。
- 数据量预估:对系统未来的数据量进行预估,考虑业务的增长趋势、数据的产生速度等因素。这有助于确定表的规模和结构,以及是否需要进行分库分表等优化措施。
表结构设计
- 选择合适的数据类型:根据数据的特点和业务需求选择合适的数据类型。例如,对于整数类型,如果数据范围较小,可以选择 TINYINT、SMALLINT 等;如果是日期时间类型,根据精度需求选择 DATE、DATETIME 或 TIMESTAMP 等。避免使用过大的数据类型浪费存储空间。
- 合理设计字段:每个字段都应该有明确的含义和用途,避免冗余字段。字段的命名要规范、清晰,具有可读性,一般采用小写字母和下划线的组合方式。对于一些可能为空的字段,要根据实际情况合理设置是否允许为空。
- 确定主键和唯一键:为主表选择一个合适的主键,主键应具有唯一性和稳定性,一般不建议使用业务含义复杂或可能发生变化的字段作为主键。可以使用自增长整数类型作为主键,如 INT 或 BIGINT 类型的自增长列。同时,根据业务需求确定是否需要设置唯一键,以保证数据的唯一性。
- 设计外键关系:如果存在多个表之间的关联关系,需要设计外键来建立表之间的联系。外键可以保证数据的一致性和完整性,但在使用外键时要注意性能开销,避免过度使用外键导致数据库操作的复杂性增加。
索引设计
- 分析查询需求:根据系统中常见的查询语句和查询条件,分析哪些字段需要创建索引。一般来说,经常用于查询条件、连接条件和排序条件的字段都应该考虑创建索引。
- 选择索引类型:MySQL 支持多种索引类型,如 B 树索引、哈希索引等。根据数据的特点和查询需求选择合适的索引类型。B 树索引适用于范围查询和排序操作,是最常用的索引类型;哈希索引适用于等值查询场景。
- 避免过度索引:虽然索引可以提高查询速度,但过多的索引会增加数据插入、更新和删除操作的时间成本,同时也会占用大量的存储空间。因此,要避免在不必要的字段上创建索引,只对确实需要提高查询性能的字段创建索引。
数据完整性和约束设计
- 非空约束:对于必填字段,要设置非空约束,确保数据的完整性。在插入或更新数据时,如果违反非空约束,数据库会抛出错误。
- 默认值设置:为一些字段设置合理的默认值,可以简化数据插入操作,提高数据的一致性。例如,对于状态字段,可以设置默认值为 “未处理” 等。
- 检查约束:如果对某些字段有特定的取值范围或格式要求,可以使用检查约束来限制数据的输入。但要注意 MySQL 对检查约束的支持有限,一些复杂的检查约束可能需要通过应用程序来实现。
性能与优化考虑
- 数据分区:如果表中的数据量非常大,可以考虑进行数据分区,将数据按照一定的规则划分到不同的分区中。数据分区可以提高查询性能,特别是对于涉及大量数据的范围查询和聚合查询。
- 存储引擎选择:MySQL 提供了多种存储引擎,如 InnoDB、MyISAM 等。不同的存储引擎具有不同的特点和适用场景。InnoDB 是事务安全的存储引擎,支持行级锁和外键约束,适用于大多数业务场景;MyISAM 具有较高的查询速度,但不支持事务和行级锁,适用于一些对查询性能要求较高的只读场景。
兼容性与扩展性
- 字符集选择:选择合适的字符集来存储数据,一般建议使用 UTF-8 字符集,以支持多种语言和字符的存储。在设计表时,要确保字符集的一致性,避免不同表之间因字符集不兼容导致的数据乱码问题。
- 考虑未来扩展:在设计表结构时,要考虑到系统未来的发展和扩展需求,尽量设计得灵活一些。避免过于紧凑的表结构导致后续业务扩展时需要频繁修改表结构,影响系统的稳定性和可维护性。
11,什么是分布式锁,如何实现分布式锁?
分布式锁是控制分布式系统或不同系统之间共同访问共享资源的一种机制,用于在分布式环境中确保在同一时刻只有一个客户端能够访问某个共享资源或执行某个关键代码段,从而避免数据冲突和不一致性问题。以下是一些常见的实现分布式锁的方式:
以下是分别基于不同方式用 Java 代码实现分布式锁的示例,示例代码中的逻辑会更详细些,且都遵循 Java 语言规范,方便你更好地理解和应用:
基于数据库(以 MySQL 为例)实现分布式锁
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;public class DatabaseDistributedLock {private static final String DB_URL = "jdbc:mysql://localhost:3306/your_database_name";private static final String DB_USER = "your_username";private static final String DB_PASSWORD = "your_password";private Connection connection;private String lockKey;public DatabaseDistributedLock(String lockKey) {this.lockKey = lockKey;try {// 加载数据库驱动(对于较新版本的JDBC和Java,可能不需要显式加载了)Class.forName("com.mysql.cj.jdbc.Driver");// 建立数据库连接connection = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);} catch (SQLException | ClassNotFoundException e) {e.printStackTrace();}}// 获取锁public boolean acquireLock() {String sql = "INSERT INTO distributed_lock (lock_key, locked_by, locked_time) VALUES (?,?, NOW())";try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) {preparedStatement.setString(1, lockKey);preparedStatement.setString(2, Thread.currentThread().getName());// 执行插入语句,若插入成功(即获取锁成功)返回1,失败返回0return preparedStatement.executeUpdate() == 1;} catch (SQLException e) {e.printStackTrace();return false;}}// 释放锁public void releaseLock() {String sql = "DELETE FROM distributed_lock WHERE lock_key =? AND locked_by =?";try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) {preparedStatement.setString(1, lockKey);preparedStatement.setString(2, Thread.currentThread().getName());preparedStatement.executeUpdate();} catch (SQLException e) {e.printStackTrace();}}public static void main(String[] args) {DatabaseDistributedLock lock = new DatabaseDistributedLock("resource_lock");if (lock.acquireLock()) {System.out.println("获取锁成功,执行业务逻辑...");// 这里模拟业务逻辑处理try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}lock.releaseLock();System.out.println("释放锁成功");} else {System.out.println("获取锁失败");}}
}
基于 Redis 实现分布式锁
import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;public class RedisDistributedLock {private Jedis jedis;private String lockKey;private String clientId;private static final int DEFAULT_EXPIRE_TIME = 10; // 锁默认过期时间,单位秒public RedisDistributedLock(String lockKey, String clientId) {this.lockKey = lockKey;this.clientId = clientId;jedis = new Jedis("localhost", 6379); // 假设Redis在本地运行,端口为6379}// 获取锁public boolean acquireLock() {SetParams setParams = SetParams.setParams().nx().ex(DEFAULT_EXPIRE_TIME);String result = jedis.set(lockKey, clientId, setParams);return "OK".equals(result);}// 释放锁public boolean releaseLock() {String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";Object result = jedis.eval(script, 1, lockKey, clientId);return (long) result == 1;}public static void main(String[] args) {String clientId = "client_1";RedisDistributedLock lock = new RedisDistributedLock("resource_lock", clientId);if (lock.acquireLock()) {System.out.println("获取锁成功,执行业务逻辑...");// 模拟业务逻辑处理try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}if (lock.releaseLock()) {System.out.println("释放锁成功");} else {System.out.println("释放锁失败");}} else {System.out.println("获取锁失败");}}
}
基于 ZooKeeper 实现分布式锁
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;public class ZooKeeperDistributedLock implements Watcher {private ZooKeeper zk;private String lockPath;private String currentNode;private CountDownLatch latch = new CountDownLatch(1);public ZooKeeperDistributedLock() throws IOException, InterruptedException, KeeperException {zk = new ZooKeeper("localhost:2181", 5000, this);latch.await();}// 获取锁public void acquireLock() throws KeeperException, InterruptedException {lockPath = "/locks";if (zk.exists(lockPath, false) == null) {zk.create(lockPath, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);}currentNode = zk.create(lockPath + "/lock_", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);List<String> children = zk.getChildren(lockPath, false);Collections.sort(children);if (currentNode.equals(lockPath + "/" + children.get(0))) {latch.countDown();} else {String prevNode = children.get(Collections.binarySearch(children, currentNode.substring(currentNode.lastIndexOf("/") + 1)) - 1);Stat stat = zk.exists(lockPath + "/" + prevNode, this);if (stat!= null) {latch.await();}}}// 释放锁public void releaseLock() throws KeeperException, InterruptedException {zk.delete(currentNode, -1);}@Overridepublic void process(WatchedEvent event) {if (event.getType() == Event.EventType.NodeDeleted) {latch.countDown();}}public static void main(String[] args) throws IOException, InterruptedException, KeeperException {ZooKeeperDistributedLock lock = new ZooKeeperDistributedLock();lock.acquireLock();System.out.println("获取锁成功,执行业务逻辑...");// 模拟业务逻辑处理try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}lock.releaseLock();System.out.println("释放锁成功");}
}
上述代码分别展示了在 Java 中利用数据库(MySQL)、Redis 以及 ZooKeeper 来实现分布式锁的具体方式,你可以根据实际的项目场景和需求来选择合适的实现方式,并进一步调整代码逻辑以适配具体业务。需要注意的是,在实际应用中,这些代码还可以在健壮性、错误处理等方面做更完善的优化。
12,什么是分布式事务?为什么需要使用分布式事务?
同节点之上的一种事务处理方式。以下详细介绍其概念及使用需求:
概念
- 在分布式系统中,一个业务操作往往会涉及到多个数据源或多个服务的协同工作,这些操作可能分布在不同的服务器上,甚至可能跨越不同的网络区域。分布式事务就是为了保证在这种复杂的分布式环境下,一组相关的操作能够作为一个整体来执行,要么全部成功提交,要么全部失败回滚,从而确保数据的一致性和完整性。
需要使用分布式事务的原因
- 数据一致性保证:在分布式系统中,不同节点上的数据需要保持一致。例如,在电商系统中,用户下单购买商品的过程涉及到订单系统、库存系统和支付系统等多个子系统。如果订单创建成功,但库存未扣减或支付未完成,就会导致数据不一致,影响业务的正常运行。分布式事务通过协调各个子系统的操作,确保所有操作要么全部成功,要么全部失败,从而保证了数据的一致性。
- 业务完整性要求:许多业务操作具有原子性要求,即一系列操作必须作为一个不可分割的整体执行。例如,在银行转账业务中,涉及到从一个账户扣款和在另一个账户收款两个操作,这两个操作必须同时成功或同时失败,否则就会出现资金异常的情况。分布式事务能够将这些相关操作纳入一个事务范畴,保证业务的完整性。
- 服务化与微服务架构的需求:随着业务的发展,系统往往会采用微服务架构进行拆分,各个微服务之间相互独立但又需要协同工作。例如,一个在线旅游平台可能包含酒店预订、机票预订、旅游攻略等多个微服务。当用户预订一个旅游套餐时,可能需要同时调用多个微服务来完成整个业务流程。分布式事务能够在这种跨微服务的场景下,确保各个微服务的操作一致性,实现业务的顺利流转。
- 高可用性和容错性:分布式系统中的节点可能会出现故障,如网络延迟、服务器宕机等。分布式事务能够在部分节点出现故障的情况下,通过协调和补偿机制,保证整个业务操作的最终一致性。例如,当某个子系统出现故障导致操作失败时,分布式事务可以通过回滚或重试等机制,确保其他子系统的操作也能得到相应的处理,避免数据不一致或业务中断。
在 Java 中,可以使用多种方式实现分布式事务,以下是一些常见的方法:
使用分布式事务管理器
- Seata:是一款开源的分布式事务解决方案,提供了 AT、TCC、SAGA 和 XA 等多种事务模式。以 AT 模式为例,在 Java 项目中使用 Seata 实现分布式事务,首先需要引入 Seata 的相关依赖,然后在业务代码中通过
@GlobalTransactional
注解来标识需要进行分布式事务管理的方法。在方法内部,调用各个参与分布式事务的服务或数据源的操作,Seata 会自动拦截这些操作,并在出现异常时进行回滚,确保所有操作的一致性。
import io.seata.spring.boot.autoconfigure.SeataAutoConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;@SpringBootApplication
@Import(SeataAutoConfiguration.class)
public class YourApplication {public static void main(String[] args) {SpringApplication.run(YourApplication.class, args);}
}
import io.seata.spring.boot.autoconfigure.SeataAutoConfiguration;
import io.seata.spring.boot.autoconfigure.SeataDataSourceAutoConfiguration;
import io.seata.tm.api.GlobalTransactional;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;@Service
public class YourService {@GlobalTransactionalpublic void yourBusinessMethod() {// 调用多个服务或数据源的操作//...}
}
- Apache ServiceComb Pack:也是一款用于微服务架构的分布式事务解决方案,支持 TCC、SAGA 等事务模式。使用时需要在项目中引入相应的依赖,然后按照其规范编写事务协调逻辑。例如在 TCC 模式下,需要定义 Try、Confirm 和 Cancel 三个阶段的操作方法,并通过框架提供的注解或 API 进行注册和调用,从而实现分布式事务的管理。
使用消息队列实现最终一致性
- 基于可靠消息最终一致性方案:通过消息队列来协调各个分布式服务的操作,实现最终一致性。例如使用 RabbitMQ 或 Kafka 等消息队列,在一个服务完成本地事务后,向消息队列发送一条消息,其他服务监听该消息并执行相应的操作。如果操作成功,则整个业务流程完成;如果出现异常,则可以通过消息的重试机制或人工干预来保证最终一致性。
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;import java.io.IOException;
import java.util.concurrent.TimeoutException;public class MessageProducer {private static final String QUEUE_NAME = "your_queue_name";public static void main(String[] args) {ConnectionFactory factory = new ConnectionFactory();factory.setHost("localhost");try {Connection connection = factory.newConnection();Channel channel = connection.createChannel();channel.queueDeclare(QUEUE_NAME, false, false, false, null);String message = "Your message content";channel.basicPublish("", QUEUE_NAME, null, message.getBytes());System.out.println("Message sent: " + message);channel.close();connection.close();} catch (IOException | TimeoutException e) {e.printStackTrace();}}
}
import com.rabbitmq.client.*;import java.io.IOException;
import java.util.concurrent.TimeoutException;public class MessageConsumer {private static final String QUEUE_NAME = "your_queue_name";public static void main(String[] args) {ConnectionFactory factory = new ConnectionFactory();factory.setHost("localhost");try {Connection connection = factory.newConnection();Channel channel = connection.createChannel();channel.queueDeclare(QUEUE_NAME, false, false, false, null);Consumer consumer = new DefaultConsumer(channel) {@Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {String message = new String(body, "UTF-8");System.out.println("Received message: " + message);// 在这里执行接收到消息后的业务逻辑}};channel.basicConsume(QUEUE_NAME, true, consumer);} catch (IOException | TimeoutException e) {e.printStackTrace();}}
}
基于 TCC 编程模式实现
- 手动编写 TCC 事务代码:TCC(Try-Confirm-Cancel)是一种补偿型的分布式事务模式。在 Java 中,可以通过手动编写代码来实现 TCC 事务。首先需要定义一个 TCC 事务接口,其中包含 Try、Confirm 和 Cancel 三个方法。然后在各个参与分布式事务的服务中实现该接口,在 Try 阶段进行业务检查和资源预留,在 Confirm 阶段进行业务确认和提交,在 Cancel 阶段进行业务回滚和资源释放。在业务流程中,通过调用 TCC 事务接口的方法来协调各个服务的操作,实现分布式事务的管理。
public interface YourTccTransaction {void tryMethod();void confirmMethod();void cancelMethod();
}
public class YourServiceImpl implements YourTccTransaction {@Overridepublic void tryMethod() {// 业务检查和资源预留//...}@Overridepublic void confirmMethod() {// 业务确认和提交//...}@Overridepublic void cancelMethod() {// 业务回滚和资源释放//...}
}
14,你们在项目中用到了哪些设计模式?
在 Java 项目中,常用的设计模式有很多,以下是一些在实际项目中经常会用到的设计模式:
创建型模式
- 单例模式:确保一个类只有一个实例,并提供一个全局访问点。例如,在数据库连接池的实现中,通常会使用单例模式来保证整个应用程序中只有一个数据库连接池实例,避免多次创建和销毁连接池带来的性能开销。
public class DatabaseConnectionPool {private static DatabaseConnectionPool instance;private List<Connection> connections;private DatabaseConnectionPool() {// 初始化连接池connections = new ArrayList<>();//...}public static synchronized DatabaseConnectionPool getInstance() {if (instance == null) {instance = new DatabaseConnectionPool();}return instance;}public Connection getConnection() {// 从连接池中获取连接//...return null;}
}
- 工厂模式:将对象的创建和使用分离,通过一个工厂类来负责创建对象。比如在一个游戏开发项目中,游戏角色的创建可能会使用工厂模式,根据不同的角色类型,工厂类可以创建出不同的游戏角色实例。
public interface GameCharacter {void display();
}public class Warrior implements GameCharacter {@Overridepublic void display() {System.out.println("I am a warrior");}
}public class Mage implements GameCharacter {@Overridepublic void display() {System.out.println("I am a mage");}
}public class GameCharacterFactory {public GameCharacter createCharacter(String type) {if ("warrior".equals(type)) {return new Warrior();} else if ("mage".equals(type)) {return new Mage();}return null;}
}
结构型模式
- 代理模式:为其他对象提供一种代理以控制对这个对象的访问。在网络请求中,有时会使用代理模式来控制对远程服务器的访问,代理对象可以在实际请求前进行一些预处理,如权限验证、缓存处理等。
public interface NetworkService {String request(String url);
}public class RealNetworkService implements NetworkService {@Overridepublic String request(String url) {// 实际的网络请求操作//...return "Response from " + url;}
}public class ProxyNetworkService implements NetworkService {private RealNetworkService realService;public ProxyNetworkService() {realService = new RealNetworkService();}@Overridepublic String request(String url) {// 预处理,如权限验证、缓存检查等//...String response = realService.request(url);// 后处理,如缓存更新等//...return response;}
}
- 装饰器模式:动态地给一个对象添加一些额外的职责。在 Java I/O 流中就广泛使用了装饰器模式,如
BufferedInputStream
就是对InputStream
的一种装饰,在基本的输入流功能上增加了缓冲功能,提高了读取效率。
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;public class DecoratorExample {public static void main(String[] args) {try {InputStream inputStream = new FileInputStream("your_file.txt");InputStream bufferedInputStream = new BufferedInputStream(inputStream);int data;while ((data = bufferedInputStream.read())!= -1) {System.out.print((char) data);}bufferedInputStream.close();inputStream.close();} catch (IOException e) {e.printStackTrace();}}
}
行为型模式
- 观察者模式:定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。在社交媒体平台的消息推送系统中,当有新的消息发布时,多个用户作为观察者会收到通知,这里就可以使用观察者模式来实现。
import java.util.ArrayList;
import java.util.List;public interface Subject {void registerObserver(Observer observer);void removeObserver(Observer observer);void notifyObservers(String message);
}public interface Observer {void update(String message);
}public class MessageBoard implements Subject {private List<Observer> observers = new ArrayList<>();@Overridepublic void registerObserver(Observer observer) {observers.add(observer);}@Overridepublic void removeObserver(Observer observer) {observers.remove(observer);}@Overridepublic void notifyObservers(String message) {for (Observer observer : observers) {observer.update(message);}}
}public class User implements Observer {private String name;public User(String name) {this.name = name;}@Overridepublic void update(String message) {System.out.println(name + " received message: " + message);}
}
- 策略模式:定义了一系列的算法,并将每一个算法封装起来,而且使它们可以相互替换。在电商系统的促销活动中,不同的促销策略如满减、折扣、赠品等可以使用策略模式来实现,根据不同的促销规则选择相应的策略。
public interface PromotionStrategy {double calculateDiscount(double amount);
}public class FullReductionStrategy implements PromotionStrategy {@Overridepublic double calculateDiscount(double amount) {if (amount >= 500) {return amount * 0.2;}return 0;}
}public class DiscountStrategy implements PromotionStrategy {@Overridepublic double calculateDiscount(double amount) {return amount * 0.1;}
}public class PromotionContext {private PromotionStrategy strategy;public PromotionContext(PromotionStrategy strategy) {this.strategy = strategy;}public double executeStrategy(double amount) {return strategy.calculateDiscount(amount);}
}
相关文章:
241221面经
1,JVM 的实现中堆、栈和方法区的区别是什么? 堆(Heap) 功能 堆是 JVM 内存中最大的一块,主要用于存储对象实例。无论是通过new关键字创建的对象,还是数组,都在堆上分配内存。它是被所有线程共享…...
【论文复刻】新型基础设施建设是否促进了绿色技术创新的“量质齐升”—来自国家智慧城市试点的证据(C刊《中国人口·资源与环境》
一、数据来源:住建部、国家知识产权局、中国城市统计年鉴,内含原始数据、处理代码和基准回归 二、数据范围: DID 为了延长政策效应估计的时间区间,将住建部公布的首批国家智慧城市作为处理组,非试点城市作为对照组。将…...
libreoffice表格python宏教程 一
一、安装python宏扩展 LibreOffice自带了一个宏编辑器,但是只能用basic语言,无法用Python。 所以,我们必须在单独的编辑器中编写Python代码。 需要安装apso扩展,此扩展可以创建删除管理python宏文件,同时还能设置偏好…...
C/C++语言基础--C++STL库之仿函数、函数对象、bind、function简介
本专栏目的 更新C/C的基础语法,包括C的一些新特性 前言 STL无疑是C史上一个重要的发明,未来我将更新STL有关的知识点,入门绝对够了(看目录就知道了👀)这是第二篇,讲仿函数C语言后面也会继续更新知识点,如…...
前端导出PDF的组件及方法
前端导出PDF的组件及方法 在Web应用程序中,导出PDF文件是一项常见的需求。无论是为了打印、分享还是存档,能够将网页内容转换为PDF格式都非常有用。幸运的是,前端开发者有多种方法和组件可以实现这一功能。在本文中,我们将详细介…...
大数据-256 离线数仓 - Atlas 数据仓库元数据管理 正式安装 启动服务访问 Hive血缘关系导入
点一下关注吧!!!非常感谢!!持续更新!!! Java篇开始了! 目前开始更新 MyBatis,一起深入浅出! 目前已经更新到了: Hadoop࿰…...
水文知识图谱构建-学习+代码
文章目录 水文模型知识图谱构建与应用(核心)面向水利防汛抢险的知识图谱构建与应用知识图谱在水利工程中的构建与应用代码 水文模型知识图谱构建与应用(核心) 水文模型知识图谱构建与应用 题目:水文模型知识图谱构建…...
python rabbitmq实现简单/持久/广播/组播/topic/rpc消息异步发送可配置Django
windows首先安装rabbitmq 点击参考安装 1、环境介绍 Python 3.10.16 其他通过pip安装的版本(Django、pika、celery这几个必须要有最好版本一致) amqp 5.3.1 asgiref 3.8.1 async-timeout 5.0.1 billiard 4.2.1 celery 5.4.0 …...
clickhouse优化记录
一、注重使用分区键来加快查询 在大数据量的情况下,如果查询语句中,可以使用分区键来进行查询,可以极大缩小数据的查询范围,加快查询速度。 二、使用order by的列,适用最左前缀匹配原则 比如表的结构是 order by(id…...
RabbitMQ如何构建集群?
大家好,我是锋哥。今天分享关于【RabbitMQ如何构建集群?】面试题。希望对大家有帮助; RabbitMQ如何构建集群? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在RabbitMQ中,集群(Cluster&#x…...
Python解压tar压缩文件
import tarfile import os# 解压文件def untar(self, log_tar_file, destination_dir):# 打开tar文件tar_file_path for tar_file_path in glob.glob(os.path.join(log_tar_file, **/*.tar), recursiveTrue):print(日志压缩文件:,tar_file_path)if ! tar_file_pat…...
Mac升级macOS 15 Sequoia后,无法ssh连接本地虚拟机
现象 macOS 15后,无法ssh连接本地启动的虚拟机,提示错误: No route to host,也ping不通。包括UTM、Parallels Desktop这两个虚拟机软件。之前都是没问题的,通过一些简单排查,目前没发现什么问题。 在虚拟…...
Unity录屏插件-使用Recorder录制视频
目录 1.Recorder的下载 2.Recorder面板 2.1常规录制属性 2.2录制器配置 2.2.1添加录制器 2.2.2配置Input属性 2.2.3配置 Output Format 属性 2.2.4配置 Output File 属性 3.Recorder的使用 3.1录制Game View视频 3.1.1Recorder配置与场景搭建 3.1.2开始录制 3.1.3…...
[ESP]从零开始的Arduino IDE安装与ESP环境配置教程
一、前言 最近也是在比赛方面比较忙,没有更多的时间和精力去更新长文章了。这几周都更倾向于环境搭建的教程,这类教程写起来确实方便,也不怎么费时间,一个下午基本可以搞定,哈哈,我保证不是在为自己想摆烂找…...
重拾设计模式--状态模式
文章目录 状态模式(State Pattern)概述状态模式UML图作用:状态模式的结构环境(Context)类:抽象状态(State)类:具体状态(Concrete State)类&#x…...
2024年全球办公键盘行业总体规模、主要企业国内外市场占有率及排名
根据QYResearch研究团队调研统计,2023年全球办公键盘市场销售额达到了 亿元,预计2030年将达到 亿元,年复合增长率(CAGR)为 %(2024-2030)。中国市场在过去几年变化较快,2023年市场规模…...
ThreadLocal用法详解
ThreadLocal 是 Java 中的一个类,它提供了线程局部变量的功能。线程局部变量是线程隔离的,每个使用该变量的线程都有其自己的变量副本,因此每个线程可以操作自己的线程局部变量,而不会和其他线程冲突。 以下是 ThreadLocal 的一些…...
linux中docker命令大全
基本命令 docker pull 拉取镜像 docker pull docker push 推送镜像到DockerRegistry docker push docker images 查看本地镜像 docker images docker rmi 删除本地镜像 docker rmi docker run 创建并运行容器(不能重复创建) docker run d…...
linux-----常用指令
文件和目录操作指令 ls(list)指令 功能:用于列出目录的内容,包括文件和子目录。示例: ls:列出当前目录下的所有非隐藏文件和目录。例如,在一个包含文件file1.txt、file2.txt和目录dir1的目录中&…...
1.gitlab 服务器搭建流程
前提条件: 一、服务器硬件水平 搭建gitlab服务器最低配置要求2核4G,低于这个配置的服务器运行效果很差。 gitlab官网:https://about.gitlab.com/ 下载地址:gitlab/gitlab-ce - Packages packages.gitlab.com 本机ubuntu 二、安装依赖 su…...
C 语言基础运算:输入两个整数并计算和、差、积
一、C 语言编程世界初窥 在当今数字化浪潮汹涌澎湃的时代,编程已成为一项极具影响力的技能,它犹如一把神奇的钥匙,能够开启无数创新与可能的大门。而在众多编程语言中,C 语言无疑是一颗最为璀璨耀眼的恒星,长久以来在编程的浩瀚星空中熠熠生辉,散发着独特而迷人的魅力。…...
PC寄存器(Program Counter Register) jvm
在JVM(Java虚拟机)中,PC寄存器(Program Counter Register)扮演着至关重要的角色,它是JVM执行引擎的核心组成部分之一。以下是PC寄存器在JVM中的具体角色和职责: 指令执行指针: PC寄存…...
CPU概述随堂测试
1. [单选题] 下列部件不属于控制器的是( )。 A. 指令寄存器 B. 程序计数器 C. 程序状态字寄存器 D. 时序电路 正确答案:C 控制器的主要组成部分包括指令寄存器(IR)、程序计数器(PC),以及用于控制…...
centos7下docker 容器实现redis主从同步
1.下载redis 镜像 docker pull bitnami/redis2. 文件夹授权 此文件夹是 你自己映射到宿主机上的挂载目录 chmod 777 /app/rd13.创建docker网络 docker network create mynet4.运行docker 镜像 安装redis的master -e 是设置环境变量值 docker run -d -p 6379:6379 \ -v /a…...
【数据安全】如何保证其安全
数据安全风险 数字经济时代,数据已成为重要的生产要素。智慧城市、智慧政务的建设,正以数据为核心,推动城市管理的智能化和公共服务的优化。然而,公共数据开放共享与隐私保护之间的矛盾日益凸显,如何在确保数据安全的…...
GTID详解
概念和组成 1,全局事务表示:global transaction identifiers 2, GTID和事务一一对应,并且全局唯一 3,一个GTID在一个服务器上只执行一次 4,mysql 5.6.5开始支持 组成 GTID server_uuid:transaction_id 如…...
【bodgeito】攻防实战记录
也许有一天我们再相逢,睁开眼睛看清楚,我才是英雄。 进入网站整体浏览网页 点击页面评分进入关卡 一般搭建之后这里都是红色的,黄色是代表接近,绿色代表过关 首先来到搜索处本着见框就插的原则 构造payload输入 <script>…...
【基础篇】1. JasperSoft Studio编辑器与报表属性介绍
编辑器介绍 Jaspersoft Studio有一个多选项卡编辑器,其中包括三个标签:设计,源代码和预览。 Design:报表设计页面,可以图形化拖拉组件设计报表,打开报表文件的主页面Source:源代码页码ÿ…...
SpringBoot+Vue3实现阿里云视频点播 实现教育网站 在上面上传对应的视频,用户开会员以后才能查看视频
要使用阿里云视频点播(VOD)实现一个教育网站,其中用户需要成为会员后才能查看视频,这个过程包括上传视频、设置权限控制、构建前端播放页面以及确保只有付费会员可以访问视频内容。 1. 视频上传与管理 创建阿里云账号ÿ…...
在VBA中结合正则表达式和查找功能给文档添加交叉连接
在VBA中搜索文本有两种方式可用,一种是利用Range.Find对象(更常见的形式可能是Selection.Find,Selection是Range的子类,Selection.Find其实就是特殊的Range.Find),另一种方法是利用正则表达式,但…...
spring\strust\springboot\isp前后端那些事儿
后端 一. 插入\更新一条数据(老) Map<String, Object> parameterMap MybatisUtil.initParameterSave("Send_ProjectFrozenLog", sendProjectFrozenLog); commonMapper.insert(parameterMap);parameterMap MybatisUtil.initParameter…...
Redis——缓存预热+缓存雪崩+缓存击穿+缓存穿透
文章目录 1、 缓存预热2、 缓存雪崩3、 缓存击穿4、 缓存穿透总结 1、 缓存预热 什么是预热: mysql加入新增100条记录,一般默认以mysql为准作为底单数据,如何同步给redis(布隆过滤器)这100条新数据。 为什么需要预热…...
【Java计算机毕业设计】基于Springboot小药店销售管理系统【源代码+数据库+LW文档+开题报告+答辩稿+部署教程+代码讲解】
源代码数据库LW文档(1万字以上)开题报告答辩稿 部署教程代码讲解代码时间修改教程 一、开发工具、运行环境、开发技术 开发工具 1、操作系统:Window操作系统 2、开发工具:IntelliJ IDEA或者Eclipse 3、数据库存储:…...
AIGC与现代教育技术
目录 引言 一、AIGC在教育技术中的基本概念 1.1 什么是AIGC? 1.2 传统教育技术和AIGC的对比 二、实现过程:AIGC在现代教育中的实现 2.1 自动生成课件内容 2.1.1 代码示例:使用GPT生成教学文案 2.1.2 完善自动生成资料 2.1.3 多模态内…...
【活动邀请·深圳】深圳COC社区 深圳 AWS UG 2024 re:Invent re:Cap
re:Invent 是全球云计算领域的顶级盛会,每年都会吸引来自世界各地的技术领袖、创新者和实践者汇聚一堂,分享最新的技术成果和创新实践,深圳 UG 作为亚马逊云科技技术社区的重要组成部分,将借助 re:Invent 的东风,举办此…...
Java中的LIst
在Java中,List接口是集合框架(Collections Framework)的一部分,用于表示有序的集合(也称为序列)。List允许存储重复的元素,并且可以通过索引访问元素。以下是对Java中List的详细介绍:…...
源码分析之Openlayers中MousePosition鼠标位置控件
概述 本文主要介绍 Openlayers 中的MousePosition鼠标位置控件,该控件会创建一个元素在页面的右上方用来实时显示鼠标光标的位置坐标。该控件在实际应用很有效,可以实时获取鼠标位置,但是一般控件元素都会自定义。 源码分析 MousePosition…...
List深拷贝后,数据还是被串改
List深拷贝后数据还是被串改 List newList new ArrayList<>(oldList)newList.pushAll(oldList)你甚至想到了java8streamAPI以上还不行 List newList new ArrayList<>(oldList) 这是采用构造参数做到的深拷贝,是没问题的 newList.pushAll(oldList) …...
一级路由器与二级路由器网络互通配置,实现父网络访问子网络
一级路由器与二级路由器网络互通配置,实现父网络访问子网络 从图看a路由器是b的父路由。默认配置情况下b路由下的PC设备可以访问a路由器下的PC设备,但是a路由下的设备无法访问b路由下设备。 为了实现互通,需要配置静态路由表。 我的a路由器是…...
linux作 samba 服务端,linux windows文件互传,免账号密码
一 ubuntu 安装 sudo apt install samba二 修改samba 配置文件 1 路径 ls -l /etc/samba/smb.conf2 修改文件 a:配置成 匿名用户,无需输入账号 b:注意配置可读写且文件可创建可删除 [global] workgroup SAMBA security user passdb back…...
使用C#调用SAP的WebService接口
URL 是一个 WSDL 地址,这意味着你可以使用 SOAP Web Service 来调用ZRFC_WEB_MES_MM_015 接口。我们将使用 C# 中的 System.Web.Services.Protocols.SoapHttpClientProtocol 或 System.ServiceModel 命名空间来实现这一点。这里我们使用 System.ServiceModel 命名空…...
线程知识总结(二)
本篇文章以线程同步的相关内容为主。线程的同步机制主要用来解决线程安全问题,主要方式有同步代码块、同步方法等。首先来了解何为线程安全问题。 1、线程安全问题 卖票示例,4 个窗口卖 100 张票: class Ticket implements Runnable {priv…...
HarmonyOS(72)事件拦截处理详解
事件拦截 1、参考资料2、HitTestMode3、onTouchIntercept、onTouch、onClick事件执行顺序3.1、系统默认事件传递顺序3.2、子组件拦截事件1、参考资料 HarmonyOS(71) 自定义事件分发之TouchTestStrategy使用说明HarmonyOS(70) ArkUI 事件分发拦截,事件冲突解决方案HitTestModea…...
Leetcode-208. 实现Trie(前缀树)
前缀树是一个由“路径”和“节点”组成多叉树结构。由根节点出发,按照存储字符串的每个字符,创建对应字符路径,以此实现快速查找单词或是否为前缀的功能。 此题要求简单,只需实现下面几种功能: Trie() 初始化前缀树对…...
网络安全系列 之 SQL注入学习总结
1. sql注入概述 程序里面如果使用了未经校验的外部输入来构造SQL语句,就很可能会引入SQL注入漏洞。 注入攻击 对于字符串输入,如果这个字符串将被解释为某种指令,那么需要特别注意防止注入攻击。sql注入、os命令注入、xml注入是典型的攻击类…...
JVM中的方法绑定机制
JVM中的方法绑定机制主要分为静态绑定(Static Binding)和动态绑定(Dynamic Binding)两种。以下是关于这两种绑定机制的详细解释: 一、静态绑定(Static Binding) 定义:静态绑定是指在…...
tomato靶场攻略
前提:kali和tomato的连接方式都为net模式 tomato的默认网络连接方式为桥接模式,导入前注意修改,将tomato.ova的镜像导入虚拟机中 出现此页面则表示导入成功,打开kali虚拟机终端,切换为root权限 arp-scan -l 浏览器访…...
移动魔百盒中的 OpenWrt作为旁路由 安装Tailscale并配置子网路由实现在外面通过家里的局域网ip访问内网设备
移动魔百盒中的 OpenWrt作为旁路由 安装Tailscale并配置子网路由实现在外面通过家里的局域网ip访问内网设备 一、前提条件 确保路由器硬件支持: OpenWrt 路由器需要足够的存储空间和 CPU 性能来运行 Tailscale。确保设备架构支持 Tailscale 二进制文件,例…...
wxWidgets使用wxStyledTextCtrl(Scintilla编辑器)的正确姿势
开发CuteMySQL/CuteSqlite开源客户端的时候,需要使用Scintilla编辑器,来高亮显示SQL语句,作为C/C领域最成熟稳定又小巧的开源编辑器,Scintilla提供了强大的功能,wxWidgets对Scintilla进行包装后的是控件类:…...
Spring Boot中Bean的 构造器注入、字段注入和方法注入
在Spring中,依赖注入(DI)是实现控制反转(IoC)的一种方式,Spring提供了多种注入方式来将依赖关系注入到Bean中,常见的方式有构造器注入、字段注入和方法注入。下面将详细介绍这三种注入方式。 1…...