2024面试offer收割宝典百度篇

 1.JAVA 中的几种基本数据类型是什么,各自占用多少字节。

  • byte: 占用 1 个字节(8位),取值范围是-128到127。
  •  short:占用 2 个字节(16位),取值范围是-32,768到32,767。
  • int : 占用 4 个字节(32位),取值范围是 2^{31} (-2,147,483,648) 到 2^{31-1} (2,147,483,647)。
  •  long : 占用8个字节(64位),取值范围是-2^{63}  (-9,223,372,036,854,775,808) 到 -2^{63-1}  (9,223,372,036,854,775,807)。
  •  float : 占用4个字节(32位),用于存储单精度浮点数。
  •  double:占用8个字节(64位),用于存储双精度浮点数。
  •  char :  占用2个字节(16位),用于存储Unicode字符,取值范围从\u0000到\uffff(包括两端)。
  •  boolean : 在Java虚拟机(JVM)内部,boolean类型的变量不是直接按字节存储的。虽然它没有明确指定其字节数,但在内存中通常会使用1个字节或更高效的方式来表示 true 或 false(具体实现依赖于JVM,但对程序员来说是透明的)。在标准Java语法层面,并不关心boolean类型的确切字节数,因为它的值只有两种可能。

2.String 类能被继承吗,为什么?

String 类是不能被继承的。这是因为 String 类被声明为 final 类
在Java中,使用 final 关键字修饰的类表示它是不可扩展的,也就是说,你不能从一个final 类派生出新的子类。

设计上将 String 类声明为 final 的原因主要有以下几点:

  • 安全性和不变性:String 类型的对象代表不可变的字符序列。由于它的不变性,确保了多线程环境下的安全性以及字符串常量池的高效运作。如果允许继承并修改其行为,可能会破坏这种不变性和安全性。
  • 设计决策:Java的设计者为了避免由于继承而带来的潜在问题(例如方法覆盖导致的行为改变),选择将一些核心库中的关键类如 String 设计为不可继承的。
  • 性能优化 :虽然现代JVM可能不再强制要求这样做,但早期的Java实现中,编译器可以对 final 类的方法进行内联等优化,提升运行效率。

因此,尽管在技术层面上可以通过创建一个新的类来模仿或扩展 String 类的功能,但在Java语言规范下,直接继承并扩展 String 类是不允许的。


3.String,Stringbuffer,StringBuilder 的区别 ?

Java中的String、StringBuilder和StringBuffer类都是用来处理字符串的,但它们之间存在显著的区别:
1.不可变性 (Immutable)

  • String:是不可变的。一旦创建了一个String对象,它的值就不能改变。每次对String进行拼接、替换等操作时,都会创建一个新的String对象,并丢弃旧的对象。因此在大量修改字符串内容的操作中,会产生较多的临时对象,可能造成性能问题。

2. 可变性 (Mutable)

  •  StringBuilder:是可变的,它允许高效的字符串内容修改。当你需要频繁地对字符串进行修改(比如通过append()方法添加字符或子串)时,使用StringBuilder可以避免不必要的内存分配和拷贝操作,从而提升效率。
  • StringBuffer:同样也是可变的,其功能与StringBuilder相似,也支持高效地修改字符串内容。

3. 线程安全性 (Thread Safety)

  •  String:由于不可变性,多个线程同时读取一个共享的String对象不会出现问题,因此从某种程度上说,在多线程环境下它是安全的
  •  StringBuilder不是线程安全的,如果在多线程环境中不采取任何同步措施直接共享使用同一个StringBuilder对象,可能会导致数据不一致或其他并发问题。
  • StringBuffer是线程安全的 ,它的所有方法都被synchronized关键字修饰,这意味着在多线程环境下,即使多个线程同时访问和修改StringBuffer对象,也能保证内部状态的一致性和正确性。然而,这种同步机制会带来一定的性能开销,所以在单线程环境或者不需要考虑线程安全的场景下,通常更倾向于使用StringBuilder以获得更好的性能。

总结来说:

  • 当你需要构建一个只读且不会修改的字符串时,使用String是最合适的。
  • 在单线程环境中,需要频繁修改字符串内容时,推荐使用StringBuilder以提高效率。
  • 在多线程环境中,当多个线程需要共同修改同一字符串内容时,应选择StringBuffer来确保线程安全。

 4.ArrayList 和 LinkedList 有什么区别。

ArrayList 和 LinkedList 是 Java 中两种常用的动态数组实现的 List 接口的数据结构,它们之间的主要区别在于 底层数据结构、性能特点和内存占用

1.  底层数据结构

  • ArrayList:基于动态数组实现。它内部维护了一个可扩容的数组,元素在数组中是连续存储的。
  • LinkedList:基于双向链表实现。它的每个节点(也称为元素)都包含一个指向前一个节点和后一个节点的引用。

2. 随机访问效率:

  • ArrayList:由于其内部是数组结构,支持通过索引进行快速随机访问,时间复杂度为 O(1)。
  • LinkedList:不直接支持随机访问,需要从头或尾部开始遍历到指定位置才能访问某个元素,因此随机访问的时间复杂度为 O(n)。

3. 插入和删除操作效率:

  • ArrayList:当在列表中间进行插入或删除操作时,尤其是非末尾位置,除了要执行相应的插入或删除动作外,可能还需要移动后续元素来填补空位或者填充新插入元素的位置,所以插入和删除的时间复杂度通常为 O(n)。
  •  LinkedList:由于链表结构可以方便地改变节点间的连接关系,所以在链表中的插入和删除操作通常更快,尤其是在非首尾位置插入或删除元素时,时间复杂度为 O(1)。

4. 内存占用:

  • ArrayList:除了存储元素本身外,还存在一定的空间开销(主要是数组对象),但整体上空间利用率较高,尤其当列表接近满容量时。
  • LinkedList:每个元素都需要额外的空间存储前后节点的引用,因此相比 ArrayList,相同数量元素下 LinkedList 的内存占用会更高。

5. 扩容机制:

  • ArrayList:当添加元素导致容量不足时,会自动扩容至当前容量的一定倍数(通常是1.5倍+1),这个过程可能导致较大的系统开销,特别是在大容量场景下。
  • LinkedList:不需要考虑扩容问题,因为它可以根据需要动态添加或移除节点。

6. 初始化和空间预估:

  • ArrayList:在创建时可以指定初始容量,对于已知大小范围的应用场景,合理预估初始容量可以减少扩容带来的性能损失。ArrayList的默认初始长度是10;有一些情况提到初始容量是0,那是在新建一个ArrayList的时候没有指定长度,ArrayList会分配一个长度为0 的数组,但是在第一次增加一个元素的时候数组的长度会自动调整为10.
  • LinkedList:无需预先指定容量,其容量随着元素的增加而增长。

综上所述,在选择使用 ArrayList 还是 LinkedList 时,应根据应用场景的需求权衡:

如果更关注高效随机访问,则选择 ArrayList;
如果更关注频繁的插入和删除操作,并且对随机访问速度要求不高,则可以选择 LinkedList。


5.讲讲类的实例化顺序,比如父类静态数据,构造函数,字段,子类静态数据,构造函数,字段,当 new 的时候,他们的执行顺序。

在Java中,当创建一个类的实例(对象)时,包括父类和子类,初始化顺序遵循以下规则:
1. 父类静态变量: 
        首先执行的是父类中的静态变量初始化和静态块(static {}),如果有的话。这些静态成员在类加载时就被初始化,并且只会初始化一次。
2.  子类静态变量:
        紧接着是子类中的静态变量初始化和静态块。同样地,这些静态成员也是在类加载时只初始化一次,与实例化无关。
3.  父类构造过程:
        当调用子类构造函数时,首先会隐式调用父类的无参数构造函数(如果没有显式指定super()语句的话)。如果有带参数的构造函数,则需要通过super(...)明确调用父类构造函数。
在父类构造函数内部,首先进行父类实例变量的初始化,然后执行父类构造函数体中的代码。
4. 父类实例字段:
        实例字段是在构造函数执行前由JVM自动完成初始化的,因此无论构造函数内部如何编写,父类实例字段总是先于任何构造器代码执行。
5. 子类实例字段:
        在父类构造函数完成后,开始初始化子类的实例字段。
6. 子类构造函数:
        最后执行的是子类自身的构造函数体中的代码。

所以整个实例化顺序可以总结为:

  • 父类静态变量初始化
  •  子类静态变量初始化
  • 父类构造函数(包含实例字段初始化)
  • 子类构造函数(包含实例字段初始化)

请注意,构造函数内的代码执行顺序,以及字段初始化的顺序,严格遵循它们在源代码中的声明顺序。


6.sql 优化有哪些?如何创建索引?创建索引的原则?索引的优缺点?

SQL优化主要包括以下几个方面:

1.查询语句优化:

  • 避免全表扫描,尽可能使用索引字段进行过滤和排序。
  • 减少JOIN操作的数量和复杂度,优先考虑内连接,并确保连接字段上有合适的索引。
  • 使用EXISTS、IN代替子查询(在某些情况下)。
  • 尽量避免在WHERE子句中对NULL值进行条件判断,因为NULL值不走索引。
  • 避免在函数内部使用列名,这可能导致无法利用索引。

2.数据表设计优化:

  • 数据规范化,减少冗余和更新异常。
  • 分区表策略:对于大型表,可以采用分区表技术,将数据分成多个物理部分,从而提高查询效率。
  • 选择合适的数据类型,减小存储空间。

3.索引创建与优化:

  • 根据查询模式创建索引,主要针对 WHERE、JOIN 和 ORDER BY 子句中的列。
  • 创建复合索引(组合索引),按照最频繁的查询顺序排列列。
  • 对于经常用于分组和聚合操作的列创建索引。
  • 索引不宜过多,过犹不及。过多的索引会占用更多存储空间,影响插入、删除、更新等DML操作性能。

4.创建索引的原则:

  • 频繁出现在WHERE子句中的列。
  • 主键和唯一性约束列通常需要创建索引。
  • 经常参与ORDER BY、GROUP BY、DISTINCT操作的列。
  • 复合索引应考虑查询的覆盖范围和查询条件的排列顺序。
  • 不应该为很少使用的列或只有少量不同值的列创建索引。

5.索引的优点:

  • 提高查询速度,尤其是针对大表的点查询和范围查询。
  • 可以减少磁盘I/O,通过B树索引直接定位到所需数据。
  • 在排序和分组时,如果能够利用索引,可以大大降低CPU消耗。

6.索引的缺点:

  • 占用额外的存储空间,增加数据库大小。
  • 创建和维护索引需要时间成本,会影响INSERT、UPDATE和DELETE操作的速度,因为这些操作不仅要修改数据本身,还要维护索引结构。
  • 当数据分布极不均匀或者表行数较小的时候,索引可能并不会带来明显的性能提升,反而会拖慢整体性能。

在实际应用中,创建索引需根据业务场景、数据规模和访问模式综合分析,既要考虑查询效率也要考虑更新效率。同时,定期分析数据库运行状况并调整索引策略也是必要的。


 7.sql 如何去重?

在SQL中,删除重复数据保留唯一行通常涉及到找到重复记录并确定要保留哪一行。以下是一个通用的步骤,假设我们有一个名为 users 的表,其中包含 id 字段作为主键,并且我们想根据 name 字段去重:

-- 1. 首先找出重复的名字
CREATE TEMPORARY TABLE temp_table AS
SELECT name, MIN(id) as id
FROM users
GROUP BY name HAVING COUNT(*) > 1;

-- 2. 确定哪些是重复项(非主键最小值对应的记录)
DELETE u1
FROM users u1
JOIN temp_table t ON u1.name = t.name AND u1.id != t.id;

-- 此时,已经删除了除每个名字的第一个出现之外的所有重复项

注意:

  • 上述代码假定你想要保留的是具有最小 id 值的那个重复记录。
  • 在执行删除操作前,请务必备份你的数据,因为这个操作不可逆。

另外,如果你的数据表很大或者需要考虑性能问题,可以创建一个临时表来存储去重后的结果,然后用临时表替换原始表的内容,以避免大表操作带来的性能压力和锁定问题。

但在许多数据库系统中,直接更新原表可能更为高效,具体取决于你的数据库管理系统以及表的具体情况。
对于没有主键的情况,可以根据实际业务需求选择合适的列作为“保留依据”(如:最新时间、最大或最小的某个字段等)。如果是MySQL 8.0+版本,还可以使用 ROW_NUMBER() 窗口函数来实现更复杂的去重逻辑。

下面是一个使用 ROW_NUMBER() 函数的例子:

WITH CTE AS (
    SELECT *, ROW_NUMBER() OVER (PARTITION BY name ORDER BY id) AS row_num
    FROM users
)
DELETE FROM CTE WHERE row_num > 1;

8.内连接和外连接的区别

内连接(INNER JOIN)和外连接(OUTER JOIN)是SQL查询中两种不同的表关联方式,它们的主要区别在于结果集中包含的数据行的范围。
内连接 (INNER JOIN):

  • 内连接返回的是两个或多个表中满足连接条件的记录。如果某一行在所有参与连接的表中都没有匹配项,则不会出现在结果集中。
  • 当你在ON子句中指定连接条件时,只有那些在连接列上相匹配的记录才会被包含在结果集里。 
SELECT column1, column2
FROM table1
INNER JOIN table2
ON table1.key = table2.key;

外连接:
外连接分为左外连接(LEFT OUTER JOIN / LEFT JOIN)、右外连接(RIGHT OUTER JOIN / RIGHT JOIN)、全外连接(FULL OUTER JOIN)三种类型。

  • 左外连接 (LEFT JOIN):返回左表中的所有记录以及与右表中匹配的记录。对于左表中有但右表中没有匹配项的记录,结果集中对应的右表字段将填充NULL值。
   SELECT column1, column2
   FROM table1
   LEFT JOIN table2
   ON table1.key = table2.key;
   
  • 右外连接 (RIGHT JOIN):与左外连接相反,返回右表中的所有记录以及与左表中匹配的记录。当右表中有而左表中没有匹配项时,左表字段在结果集中填充NULL值。
   SELECT column1, column2
   FROM table1
   RIGHT JOIN table2
   ON table1.key = table2.key;
   
  •  全外连接 (FULL OUTER JOIN):返回左表和右表中所有的记录组合,即使两边不完全匹配。不匹配的记录在另一边的结果中使用NULL填充
   SELECT column1, column2
   FROM table1
   FULL OUTER JOIN table2
   ON table1.key = table2.key;
   

总结:

  • 内连接只显示匹配的行;
  • 左外连接会显示左表的所有行,即使在右表中找不到匹配项;
  • 右外连接则会显示右表的所有行,即使在左表中找不到匹配项;
  • 全外连接则会显示左右两表的所有行,并在无法匹配的情况下用NULL填充缺失的一方。

9.sql 语句关键词的执行顺序 

 SQL语句关键词的执行顺序遵循以下顺序:

  • FROM:首先从数据库中选择表或视图,或者执行子查询以生成临时结果集。
  • JOIN:如果有多个表需要关联,JOIN操作将根据指定的连接条件和类型(如INNER JOIN、LEFT JOIN等)来合并这些表的数据行。
  • ON:在JOIN之后,定义用于连接表之间的关系的条件。
  • WHERE:对FROM和JOIN之后生成的结果集应用过滤条件。这是筛选数据的主要阶段,只保留满足给定条件的行。
  • GROUP BY:如果存在GROUP BY子句,那么数据将会按照一个或多个列进行分组,并且每组中的每一列都会被聚合函数(如COUNT、SUM、AVG、MAX、MIN等)处理。
  • HAVING:在GROUP BY之后,可以应用HAVING子句来进一步筛选分组后的结果,这个阶段允许基于分组后的聚合值设定条件。
  • SELECT:接下来计算SELECT列表中的表达式,包括列名、别名、常量以及任何聚合函数的结果。
  • DISTINCT:如果指定了DISTINCT关键字,则在此阶段消除重复行。
  • ORDER BY:最后,对结果集按指定列进行排序。
  • LIMIT / OFFSET(或其他数据库系统相应的分页机制):限制返回的记录数量或偏移量,用于实现分页功能。

请注意,不同数据库管理系统可能有一些微小的差异,但上述顺序是SQL标准的基本逻辑流程。实际执行过程中,数据库引擎可能会对部分步骤进行优化调整以提高性能。


10.什么情况下会发生栈内存溢出。 

栈内存溢出(Stack Overflow)在Java中发生的情况通常是由于以下原因:

  • 递归调用没有正确的退出条件: 当一个方法不断递归调用自身,且没有达到终止递归的条件时,每调用一次就会在栈上分配一个新的栈帧来保存局部变量表、操作数栈等信息。如果递归深度超过了Java虚拟机(JVM)为线程分配的栈空间大小限制,就会导致栈内存溢出。
  • 过深的方法调用链: 即使不涉及递归,如果程序中存在大量的嵌套方法调用(例如大量静态方法互相调用),当调用层次太深以至于所需栈空间超过了JVM的最大栈容量,也会触发栈内存溢出。
  • 过大或过多的局部变量: 如果某个方法的局部变量占用的空间过大,或者有大量的局部变量使得单个栈帧过大,也可能导致栈空间不足从而引发栈溢出错误。

在Java中,当你看到 java.lang.StackOverflowError 异常时,通常就是上述情况之一造成的栈内存溢出问题。

为了防止这种情况,可以检查并优化递归算法确保其有终止条件,避免过深的方法调用链,并合理控制局部变量的使用。此外,还可以通过调整JVM参数 -Xss 来增大每个线程的栈大小,但这并不能从根本上解决逻辑上的无限递归或过度方法调用问题。 


11.JVM 的内存结构,Eden 和 Survivor 比例。 

 

JVM(Java虚拟机)的内存结构主要包括堆内存(Heap)方法区(Method Area)程序计数器(Program Counter Register)虚拟机栈(VM Stack,也称为Java方法栈)本地方法栈(Native Method Stack)

这里主要讨论的是堆内存中的年轻代(Young Generation)部分,特别是其中的 Eden 区和 Survivor 区的比例。
在年轻代中,默认情况下,Eden 区与两个Survivor区(通常被称作S0和S1,或者From和To)的比例为 8:1:1。

也就是说:

  • Eden 区占年轻代空间的 80%。
  • Survivor 区由 S0 和 S1 组成,每个区域占年轻代空间的 10%。

当对象首先在Eden区分配内存时,如果Eden区满了且有存活的对象,则会触发Minor GC(年轻代垃圾回收),将Eden区和一个Survivor区中还存活的对象复制到另一个Survivor区,并进行必要的年龄标记。经过多次GC后依然存活的对象会被晋升到老年代(Old Generation)。

这个比例是可以根据应用的实际需求进行调整的,通过JVM参数

-XX:NewRatio

-XX:SurvivorRatio

等来设置新生代和老年代之间的比例,以及新生代内部各区域的比例。

例如: 

-XX:SurvivorRatio=8  # 表示eden:survivor空间比率为8:1

12.JVM 内存为什么要分成新生代,老年代,持久代。新生代中为什么要分为 Eden 和 Survivor?

JVM 内存划分成不同的区域,主要是为了更好地管理内存和优化垃圾回收(Garbage Collection)的效率

以下是新生代、老年代和持久代(PermGen在Java 8及以后版本中被元空间MetaSpace取代)的具体解释以及新生代内分为Eden区和Survivor区的原因:
 

1. 新生代(Young Generation):

  • 新生代主要存放的是新创建的对象和生命周期较短的对象。
  • 分为 Eden 区、From Survivor 区和 To Survivor 区。
  • Eden 区:大部分新创建的对象首先会被分配到 Eden 区。
  • Survivor 区:也称为 S0 和 S1,当 Eden 区满了进行 Minor GC 时,存活下来的对象会被复制到其中一个 Survivor 区(通常是空闲的那个),这个过程被称为“幸存者筛选”(Survivor Evacuation)。通过这种方式,可以减少年轻代内的碎片化,并且只有经过多次GC仍然存活的对象才会晋升到老年代。

2.老年代(Old Generation):

  • 老年代存储的是从新生代晋升过来的生命周期较长或者占用较大连续内存空间的对象。
  • 对于老年代的内存管理,通常采用完全不同的垃圾回收算法,如标记-压缩或标记-清除等,处理更复杂的内存分配和回收问题。

3. 持久代/元空间(Permanent Generation / Metaspace):

  • 在Java 8之前的HotSpot JVM中,存在一个持久代用于存储类信息、方法数据、常量池以及其他与类加载器相关的静态资源。
  • 自Java 8开始,持久代被元空间(Metaspace)替代,元空间使用的是本地内存(Native Memory),主要用于存储类元数据,不再有固定的大小限制,而是根据需要动态调整。

这种分代机制的设计基于大多数对象都是短暂的这一观察结果,使得垃圾回收器能够快速清理大量无用对象,同时又能高效地处理长期存活的对象,从而提升整体的系统性能和稳定性。 


13.JVM 中一次完整的 GC 流程是怎样的,对象如何晋升到老年代,说说你知道的几种主要的 JVM 参数。

 JVM中一次完整的垃圾回收(GC)流程通常包括以下几个阶段,这里以HotSpot虚拟机为例说明:
1. 新生代GC(Minor GC):

  • 当Eden区满时,触发Minor GC。
  • 首先进行STW(Stop-The-World),暂停程序的执行。
  • 将Eden区和其中一个Survivor区中存活的对象复制到另一个Survivor区(To空间)。
  • 对象年龄加1,若对象在 Survivor 区经历了一定次数的Minor GC后仍然存活(默认为15次),则晋升至老年代。

2. 老年代GC(Major GC / Full GC):

  • 老年代满了或者System.gc()被显式调用,或者由于Minor GC时需要分配老年代空间但无法满足,会触发Major GC或Full GC。
  • Major GC主要针对老年代进行回收,可能伴随对整个年轻代的回收(即包含Minor GC)。
  • Full GC是清理整个堆内存,包括年轻代、老年代和永久代/元空间(Java 8及以后版本为元空间)。

3. 并发标记(CMS)或G1等并发收集器:

  • 并发收集器如CMS(Concurrent Mark Sweep)、G1(Garbage First)会有并发标记阶段,在不完全停止应用的前提下进行垃圾标记。
  • CMS和G1的具体过程更复杂,并且尽量减少STW时间,比如CMS有初始标记、并发标记、重新标记和并发清除四个步骤;G1则引入了Region的概念并采用SATB(Snapshot at the Beginning)算法等。

关于晋升到老年代的条件:

  • 对象经过一定次数的Minor GC后依然存活,达到设定阈值(默认15次)。
  • 大对象直接进入老年代。大对象是指需要连续内存空间超过某个阈值(可通过-XX:PretenureSizeThreshold设置)的对象,默认情况下大于伊甸园区大小一半的对象会被认为是大对象。
  • 动态年龄判断:当Survivor区不足以容纳所有存活对象时,也会有一部分对象提前晋升到老年代,这就是所谓的“担保机制”。

常见的JVM参数与GC相关的有:

  • -Xms:指定JVM的最小堆内存大小。
  • -Xmx:指定JVM的最大堆内存大小。
  • -Xmn:指定年轻代(Young Generation)的大小。
  • -XX:NewRatio:指定年轻代和老年代的比例。
  • -XX:SurvivorRatio:指定年轻代中Eden区与一个Survivor区的比例。
  • -XX:+UseParallelGC -XX:+UseConcMarkSweepGC -XX:+UseG1GC:选择不同的垃圾收集器策略。
  • -XX:MaxTenuringThreshold:设置对象晋升到老年代之前在年轻代中经历的GC次数上限。
  • -XX:+PrintGCDetails -XX:+PrintGCDateStamps:打印详细的GC日志信息。

以上是一些基本的GC相关参数,具体参数和其功能可能因JDK版本的不同而有所差异,使用时请参考对应版本的官方文档。


14.Java 中如何使用 redis?redis 支持的数据类型及各种数据类型的使用场景?redis 如何解决数据过期?

在Java中使用Redis,首先需要添加Redis客户端库的依赖。以下是一个使用Jedis(一个广泛使用的Java Redis客户端)的例子:

// 添加Maven依赖
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.7.0</version> <!-- 请替换为最新稳定版本 -->
</dependency>

// 示例代码
import redis.clients.jedis.Jedis;

public class RedisExample {
    public static void main(String[] args) {
        // 创建Jedis连接实例
        Jedis jedis = new Jedis("localhost", 6379); // 默认端口是6379
        jedis.connect();

        // 操作Redis数据
        jedis.set("key", "value"); // 设置字符串类型数据
        String value = jedis.get("key"); // 获取字符串类型数据

        // 使用其他数据类型示例
        jedis.lpush("listKey", "item1", "item2"); // 列表类型
        jedis.zadd("sortedSetKey", 1, "member1"); // 有序集合类型
        jedis.hset("hashKey", "field", "value"); // 哈希类型

        // 设置数据过期时间(以秒为单位)
        jedis.expire("key", 60); // key将在60秒后过期并自动删除

        // 关闭连接
        jedis.close();
    }
}

 Redis支持多种数据类型及使用场景:

  • String:基本的键值对存储,可以用来缓存任意格式的数据,例如用户信息、页面内容等。
  • List(列表):存储有序的字符串元素序列,适用于消息队列、文章收藏列表等功能场景。
  • Set(集合):无序且不重复的字符串集合,常用于去重、共同好友计算、标签系统等。
  • Sorted Set(有序集合):与集合类似,但每个成员都有一个分数与之关联,根据这个分数排序,可用于排行榜、实时排名更新等场景。
  • Hash(哈希):包含字段-值对的映射,相当于内存中的一个小型数据库,适合存储对象属性或结构化数据。

关于如何解决数据过期:

  • Redis提供了EXPIRE和PEXPIRE命令来设置键的过期时间,也可以用TTL和PTTL命令查询剩余生存时间。当键达到指定的过期时间时,Redis会自动将其删除,从而实现数据的定时失效。
  • 另外,Redis还支持过期策略如“滑动过期”,可以在每次访问某个键时刷新其过期时间,确保频繁访问的数据不会过早被删除。这种策略可以通过编程方式实现,比如在每次访问前手动重新设置过期时间。

 15.存储过程的了解和使用?

存储过程(Stored Procedure)是数据库管理系统中预编译的子程序,它封装了一系列SQL语句和逻辑控制结构。在SQL Server等关系型数据库中,存储过程是一种可重复调用、包含零个或多个参数的程序块,可以执行查询、更新、删除等操作,并能返回结果集给调用者。

通俗来讲就是:存储过程其实就是能完成一定操作的一组 SQL 语句
 

存储过程的优点包括:

  • 性能提升:因为存储过程是在服务器端预先编译好的,所以执行效率相对较高。
  • 代码复用:定义一次存储过程后,可以在多处地方通过调用其名称来执行相同的任务。
  • 安全性:可以通过权限管理限制对存储过程的访问,而不必暴露具体的表结构和数据操作细节。
  • 减少网络传输:尤其是对于复杂的操作,只需要发送存储过程的名字和参数,而非整个SQL语句,从而减少了网络流量。
  • 事务处理:存储过程内部可以进行事务控制,确保一系列数据库操作要么全部成功,要么全部失败。
缺点:
存储过程,往往定制化于特定的数据库上,因为支持的编程语言不同。
当切换到其他厂商的数据库系统时,需要重写原有的存储过程。
存储过程的性能调校和撰写,受限于各种数据库系统。

Java中调用SQL Server存储过程的基本步骤如下:

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;

public class StoredProcExample {
    public static void main(String[] args) throws Exception {
        // 建立数据库连接
        String url = "jdbc:sqlserver://localhost:1433;databaseName=myDatabase";
        String username = "sa";
        String password = "your_password";
        Connection conn = DriverManager.getConnection(url, username, password);

        // 创建CallableStatement对象以执行存储过程
        String storedProcName = "{call sp_GetUserDetails (?,?)}"; // 假设sp_GetUserDetails是一个带有IN/OUT参数的存储过程
        CallableStatement cstmt = conn.prepareCall(storedProcName);

        // 设置输入参数
        cstmt.setInt(1, 1); // 第一个问号对应第一个输入参数

        // 注册输出参数(如果存储过程有输出参数)
        cstmt.registerOutParameter(2, Types.INTEGER); // 假设第二个参数是整数类型的输出参数

        // 执行存储过程
        cstmt.execute();

        // 获取输出参数
        int outputValue = cstmt.getInt(2);

        // 处理结果集(如果有)
        ResultSet rs = cstmt.getResultSet();
        while (rs.next()) {
            // 处理每一行数据
        }

        // 关闭资源
        rs.close();
        cstmt.close();
        conn.close();
    }
}

16.消息队列的使用场景。

消息队列(Message Queue, MQ)在分布式系统中扮演着至关重要的角色,以下是几种典型的消息队列使用场景:
1. 异步处理:
        在电商系统中,用户下单后,需要进行库存扣减、订单生成、支付确认、邮件通知等多个步骤。如果这些操作同步执行,可能会导致用户等待时间过长。通过消息队列,可以将后续的业务逻辑异步处理,例如先发送一个“创建订单”消息到消息队列,由后台服务依次完成剩余流程,从而提升用户体验和系统响应速度。
2. 应用解耦:
        不同的服务或微服务之间通过消息队列通信,而不是直接调用接口。这样可以降低各个服务间的依赖性,当某个服务升级或修改时,不会影响其他服务的运行。比如,订单服务只需发送一个订单已支付的消息到消息队列,库存服务从队列中消费该消息来更新库存,两者之间无需直接调用对方接口。
3. 流量削峰:
        当系统面临高并发请求时,可以通过消息队列暂存瞬时大量的请求,然后按照服务的实际处理能力逐个取出并执行。这样能避免因短时间内大量请求涌入而导致系统崩溃的情况,如秒杀活动或者高峰期访问。
4. 数据一致性保证:
        在分布式事务处理中,通过消息队列可以实现最终一致性。当一个事务操作成功后,发送一条消息到消息队列,待事务最终完成后,再通过消息队列触发补偿操作或其他相关操作,确保多个子系统的数据最终一致。
5. 日志处理与分析:
        用于收集系统产生的事件日志,将日志信息发送至消息队列,然后由专门的日志分析服务订阅并处理,便于进行实时监控和离线统计分析。
6. 消息广播与发布/订阅模式:
        消息队列支持发布/订阅模式,生产者发布消息到特定主题,多个消费者可以订阅这个主题,每个消费者都能接收到消息。这种机制常用于系统间的通知推送、系统内部的状态更新等场景。
7. 数据迁移与集成:
        在不同的系统间进行数据迁移或集成时,消息队列可以作为中间件,负责不同系统之间的数据传输,保证数据流动的可靠性和顺序性。 


17.消息的重发,补充策略。

在消息队列中,确保消息的可靠性是至关重要的。当消息由于各种原因(如网络故障、消费者处理失败等)未能正确消费时,通常需要采用重发和补充策略来保证消息至少被成功处理一次。

以下是几种常见的策略:
1. 重试机制:

  • 当消息队列检测到消息消费失败时,可以设置一个重试次数和重试间隔时间。例如,第一次消费失败后,等待一定时间(如5秒)再尝试第二次,如果仍然失败,则继续按照设定的重试策略进行多次尝试。
  • 如果达到最大重试次数后仍无法正常消费,可以选择将消息放入死信队列,由人工或专门的服务进行后续处理。

2. 幂等性处理:

  • 消费者端需要设计为幂等操作,即无论收到同一消息多少次,其对系统的影响都是一致的。这样,在重试过程中即使多次接收到相同的消息也不会造成数据不一致等问题。
  • 为了实现幂等性,消费者可以利用消息唯一标识符,每次收到消息时先检查是否已处理过该消息,只有未处理过的消息才执行实际业务逻辑。

3. 事务补偿与确认机制:

  • 消息队列可以支持事务型消息或半事务型消息。发送方在完成本地事务的同时提交消息至消息队列,消息队列在确保本地事务完成后才正式投递消息给消费者。
  • 消费者处理完消息后向消息队列反馈确认信息,若未收到确认,则消息队列会重新投递消息。

4. 延迟/定时消息:

  • 对于某些特定场景,可以设定消息在消费失败后的某个时间点再次投递,这种机制也属于重发的一种形式。

综合以上策略,结合具体应用场景和业务需求,选择合适的重发和补充策略,以提高消息传递的可靠性。同时,通过日志记录、监控报警等方式,及时发现并处理异常情况。 


18.如何保证消息的有序性。

保证消息队列中消息的有序性通常涉及以下几种策略:
1. 顺序消费:
        如果需要确保一个生产者产生的消息按发送顺序被消费者处理,可以设计单线程或同步处理的消息消费者。这样,在单个消费者实例内部,消息将按照接收到的顺序进行消费。
2. 分区/主题有序:
        在支持分区(Partition)或主题(Topic)级别的消息队列中,如Kafka,可以为消息指定特定的键(Key),该键用于确定消息被投递到哪个分区。因为同一分区内的消息是有序的,所以通过控制消息写入和读取的分区,可以实现消息在分区层面的有序性。
3. 全局有序:
        对于要求严格全局有序的情况,一种方法是在单一消费者实例上实现,并且不允许有多个并发消费者。另一种方法是使用具有排序功能的消息队列系统,比如某些MQ服务提供专门的有序消息队列功能,但请注意这种全局有序通常会牺牲一部分性能和扩展性。
4. 分布式序号:
        使用分布式ID生成器(如Snowflake算法)来为每条消息生成全局唯一的、有序的序列号,然后消费者可以根据序列号对消息进行排序后消费。
5. 事务消息:
        部分消息队列服务提供了事务消息的功能,例如RocketMQ的事务消息,它允许消息生产和消费过程具备ACID特性,从而确保在分布式环境下的消息处理有序。
6. 消息确认与重试:
        为了在异常情况下仍能保持有序性,可以结合消息确认机制,确保前一条消息成功处理后再处理下一条消息。当发生错误时,可以选择回滚未完成的操作并重新开始处理。

总之,保证消息的有序性往往需要结合具体的业务场景和所使用的消息队列技术特点来选择合适的方案。 


用过哪些 MQ,和其他 mq 比较有什么优缺点,MQ 的连接是线程安全的吗?

下面是关于几种常见消息队列(MQ)的对比和线程安全性的说明:
1. ActiveMQ:

  • 优点:是Apache项目,开源且历史悠久,支持多种协议(如JMS、AMQP、STOMP等),功能全面,兼容性好。
  • 缺点:性能相较于其他现代MQ产品略低,内存占用相对较大,管理界面不够友好。

2. RabbitMQ:

  • 优点:基于AMQP协议,易于管理和扩展,有丰富的插件支持,适用于微服务架构,高可用性和可扩展性强。
  • 缺点:Erlang编写,对部分开发者来说学习成本较高,处理大量小消息时性能可能不如专门针对此场景优化的消息队列。

3. Kafka:

  • 优点:专为大数据处理设计,具有极高的吞吐量和良好的伸缩性,适合日志收集、监控数据传输等场景,持久化能力强。
  • 缺点:不支持事务消息,消息顺序保证仅在分区级别,对于需要严格消息顺序的应用不太合适。

4. RocketMQ:

  • 优点:阿里巴巴开发的分布式消息中间件,拥有高性能、高可靠、高实时的特点,支持事务消息,具备优秀的消息有序性保障能力。
  • 缺点:社区活跃度相比其他国际主流MQ较低,文档资料主要以中文为主,对英文使用者略有不便。

关于MQ连接是否线程安全:

  • 对于大多数MQ客户端库,在多线程环境下创建一个连接实例后,通常可以通过多线程共享这个连接实例来发送和接收消息。客户端库内部一般会对操作进行同步处理,确保并发访问的安全性。
  • 然而,具体到每个MQ产品的客户端实现可能存在差异,比如创建Producer或Consumer对象时,推荐遵循官方文档指导,确保线程安全地使用这些对象。例如,多个线程可以共享同一个Producer实例,但同时写入不同主题的消息;而对于Consumer,由于消费行为通常是线程安全的,但如果要确保每个线程消费特定消息队列的部分,可能需要创建多个Consumer实例并设置合适的线程模型。

总之,在实际应用中,为了确保线程安全性,建议根据所使用的MQ客户端API规范进行编程,并适当利用连接池等技术手段提高资源利用率和系统稳定性。


 19.数据库表的设计注意事项有哪些?三大范式的了解?

数据库表的设计注意事项:


1. 字段设计:
        确保每个字段都有明确的含义和用途,避免冗余字段。
        字段名应具有描述性且遵循统一命名规范。
        选择合适的数据类型(例如整数、字符串、日期等),并考虑存储空间和性能影响。
2. 主键与外键:
        每个表都需要一个唯一的主键以标识每行记录,主键可以是单字段或多字段组成。
        使用外键关联多个表时,要确保引用完整性,即子表中的外键值必须存在于父表的主键中。
3. 索引设计:
        对于频繁查询和排序的字段,创建合适的索引以提高查询性能,但注意过多的索引会影响写入性能和占用额外存储空间。
4. 分区策略:
        对于大数据量表,可能需要考虑分区策略以提高查询效率和管理便利性。
5. 表结构扩展性:
        预见未来需求,设计表结构时允许灵活扩展,比如通过添加新字段而不是修改现有字段来适应新的业务需求。
6. 业务需求分析:
        根据实际业务场景和需求来确定表结构,理解业务流程和实体关系。
7. 规范化设计:
        应用三大范式或其他数据库设计原则来消除冗余和依赖,提升数据一致性。                        

8.数据一致性与完整性约束:
        设计合适的约束条件如唯一性约束、非空约束、检查约束等,以保证数据的一致性和准确性。
        考虑使用触发器或程序逻辑来实现复杂的业务规则和事务处理。

关于三大范式:

  • 第一范式 (1NF):要求数据库表的每一列都是不可分割的基本数据项,不允许出现重复组。这意味着每个字段只包含一个原子值,不存在多值属性。
  • 第二范式 (2NF):在满足1NF的基础上,要求表中的所有非主键列完全依赖于整个主键,而非主键的一部分。这样可以消除部分依赖,确保每一个非主属性都与主键有直接联系,防止数据冗余。
  • 第三范式 (3NF):在满足2NF的前提下,任何非主属性既不传递依赖于主键也不依赖于其他非主属性。也就是说,没有任何非主属性是通过依赖主键以外的其他非主属性来决定的,进一步消除数据冗余和更新异常。

然而,在实际数据库设计过程中,并非总是严格遵循三大范式,有时为了优化读取性能和减少JOIN操作,会适当引入冗余并违反一些范式要求。这通常发生在性能瓶颈明显,且业务场景复杂的情况下。


20.百万级量的数据分页查询如何优化? 

对于百万级数据量的分页查询优化,可以考虑以下几种策略:
1. 使用索引:
        对于分页查询涉及的字段建立索引,尤其是排序和过滤条件中的字段。这将显著加快查询速度,特别是当需要进行排序时,覆盖索引(索引包含了查询所需的全部信息)会非常有效。
2. 避免排序操作:
        如果可能的话,尝试在数据库表中预先对数据进行物理排序,或者利用已有的自然顺序索引,以减少查询时临时排序产生的开销。
3. SQL查询优化:
        使用LIMIT与OFFSET配合进行分页,但注意OFFSET值较大时性能下降严重,因为它必须扫描到指定偏移量的位置才能返回结果。
        例如:SELECT * FROM table LIMIT 10 OFFSET 999990;
        对于MySQL等支持的数据库,可以改用子查询或JOIN的方式结合行号记录实现更高效的分页,如使用ROW_NUMBER()窗口函数(MySQL 8.0+支持),或者根据主键/唯一索引做范围查询(如果知道最后一页的最大ID)。 

   -- MySQL 8.0+
   SELECT *
   FROM (
       SELECT *, ROW_NUMBER() OVER (ORDER BY id) AS row_num
       FROM table
   ) t
   WHERE row_num BETWEEN :start AND :end;

   -- 或者基于已知最大ID(假设id是自增主键)
   SELECT * 
   FROM table 
   WHERE id > :last_id_on_previous_page 
   ORDER BY id 
   LIMIT :page_size;
   

 4. 缓存机制:
        对于频繁访问的前几页,可以将结果缓存起来,减少数据库直接查询的压力。
5. 数据库优化:
        考虑数据库层面的优化,比如合理分区、增加硬件资源、调整数据库参数等。
6. 业务层优化:
        分析业务需求,看是否真的需要展示所有百万级别的数据给用户。如果不是,可以考虑前端懒加载、后台合并数据等方式减轻数据库压力。
7. 批量处理:
        如果允许一定的延迟,可以通过定时任务将大量数据预处理成汇总数据或者更便于快速查询的数据结构。

综上所述,针对百万级别数据的分页查询优化是一个综合性的过程,包括但不限于索引设计、SQL查询语句优化以及业务逻辑上的调整。

同时,也要考虑到系统的整体架构和未来扩展性,选择合适的解决方案。


21.数据库的乐观锁和悲观锁的理解和使用? 

数据库的乐观锁和悲观锁是两种不同的并发控制机制,主要用于解决多用户同时访问同一数据时可能出现的数据不一致问题。
1. 悲观锁(Pessimistic Lock):

  • 理解:悲观锁假设每次操作都可能造成冲突,所以在事务开始时就立即对需要修改的数据进行锁定,直到事务结束才释放。在此期间,其他事务试图访问该数据会被阻塞,等待锁被释放。
  • 使用:在关系型数据库中,悲观锁可以通过SQL的SELECT ... FOR UPDATE语句实现,在获取数据的同时锁定记录,防止其他事务修改。另外,也可以使用数据库提供的行级锁、表级锁等机制。
START TRANSACTION;
SELECT * FROM table WHERE id = ? FOR UPDATE;  -- 获取并锁定数据
-- 执行更新操作...
COMMIT;  -- 提交事务后释放锁

 2.乐观锁(Optimistic Lock):

  • 理解:乐观锁假定并发冲突并不频繁,因此在读取数据时不立即加锁,而是允许并发读写。在更新数据时才会检查在此期间是否有其他事务已经修改了数据,如果发现数据有变化则本次更新失败,通常通过版本号或时间戳来判断数据是否被修改过。
  • 使用:乐观锁的实现方式是在数据表中增加一个version字段或者timestamp字段,每次更新数据时都会检查这个字段是否与预期值一致。
  • 在Java应用中,可以结合Hibernate或JPA等持久层框架实现乐观锁。例如,添加 @Version 注解到实体类的某个字段上,框架会在执行更新时自动增加此字段,并在提交前检查它是否发生变化。
    @Entity
    @Table(name = "table")
    public class MyEntity {
        @Id
        private Long id;
    
        // 其他属性...
    
        @Version
        private int version;  // 自动维护的版本号
    
        // ...
    }
    

总结起来,悲观锁在并发场景下更加保守,会减少并发度但能保证数据的一致性;而乐观锁在大多数情况下不会阻塞其他事务,提高了系统的并发性能,但在高并发且冲突概率较高的场景下可能导致更多的事务重试。选择哪种锁策略取决于具体的应用场景和性能要求。 


22.数据库如何实现分页? 

在数据库中实现分页(Pagination)通常有两种主要方法:
1. OFFSET和LIMIT: 这是SQL标准中的方法,适用于大多数关系型数据库系统,如MySQL、PostgreSQL等。基本的查询语句结构如下:

   SELECT * FROM table_name
   ORDER BY some_column -- 排序依据列
   LIMIT page_size OFFSET (page_number - 1) * page_size;
   

其中 page_size 是每页显示的数据条数page_number 是当前请求的页码OFFSET 表示从结果集的第几条记录开始选取,LIMIT 则限制返回的结果数量
示例:假设每页大小为20条数据,请求的是第3页的数据,则查询语句可能是:

   SELECT * FROM your_table
   ORDER BY id ASC
   LIMIT 20 OFFSET 40;  -- 从第41条记录开始取20条数据
   

2. 窗口函数与ROW_NUMBER(): 对于大规模数据和高偏移量的场景下,使用OFFSET的方式可能会非常低效,因为它需要扫描所有前面的行以跳过不需要的行。在这种情况下,可以利用窗口函数 ROW_NUMBER() 来实现更高效的分页,例如在SQL Server和PostgreSQL中:

   WITH OrderedData AS (
       SELECT *, ROW_NUMBER() OVER (ORDER BY some_column) AS row_num
       FROM table_name
   )
   SELECT * FROM OrderedData
   WHERE row_num BETWEEN ((page_number - 1) * page_size + 1) AND (page_number * page_size);
   

这种方法的优点在于即使面对大数据量时也能高效地获取所需分页数据。


请注意,并不是所有的数据库都支持上述两种方式,具体的语法可能因数据库的不同而有所差异,应查阅对应数据库系统的文档来获得准确的用法。

此外,在实际应用中,为了优化性能,还应该结合索引以及其他查询优化技术进行处理。


23.#{}和${}的区别是什么?

在MyBatis中,#{} 和 ${} 都是用来在SQL语句中传递参数的占位符,但它们有以下显著区别:
1. #{}(预编译参数):

  • #{param} 是预编译参数占位符,它会将传入的参数值进行预编译处理,并使用PreparedStatement设置参数,可以防止SQL注入攻击。
  • MyBatis会根据传入参数的类型自动进行类型转换和空值处理。

示例:SELECT * FROM table WHERE id = #{id}。这里,id 参数会被安全地包含在SQL查询中。
2. ${}(字符串替换):

  • ${param} 是字符串替换占位符,它会直接将变量的值插入到SQL语句中,不做任何转义或预编译处理。
  • 使用${} 的风险在于如果传入的参数来自不可信源,则可能导致SQL注入问题。

示例:SELECT * FROM table WHERE column = '${columnValue}'。这里的columnValue将被直接替换到SQL文本中。

总结来说,在大部分情况下,为了安全起见,推荐使用#{}来传递参数,因为它能够确保数据安全性并提供更好的性能。而${}主要用于那些需要原样插入到SQL语句中的场景,比如构建动态SQL时的部分片段。但务必谨慎使用以避免潜在的安全风险。


24.数据库中字符串和日期的相互转换?

在数据库中,字符串和日期类型的相互转换是常见的操作。以下说明如何进行转换:
1. 在SQL Server中:

  • 字符串转日期:CONVERT 函数将字符串转换为日期类型,第二个参数是需要转换的字符串,第三个参数是样式代码,用于指定日期的格式。
       SELECT CONVERT(datetime, '2024-02-18', 120) AS DateValue; -- 使用ISO格式(yyyy-mm-dd)
       
  • 日期转字符串:这里使用了当前日期 (GETDATE() 函数),将其转换为字符串,并通过第三个参数定义输出的格式。
       SELECT CONVERT(varchar(10), GETDATE(), 23) AS StringValue; -- 格式化为 yyyy-mm-dd
       

2.在MySQL中:

  • 字符串转日期:STR_TO_DATE 函数将符合指定格式的字符串转换为日期。
       SELECT STR_TO_DATE('2024-02-18', '%Y-%m-%d') AS DateValue;
       
  • 日期转字符串:使用 DATE_FORMAT 函数将当前日期 (CURDATE()) 转换为指定格式的字符串。
       SELECT DATE_FORMAT(CURDATE(), '%Y-%m-%d') AS StringValue;
       

3. 在Oracle数据库中:

  • 字符串转日期:TO_DATE 函数将符合指定格式的字符串转换为日期。这里 'YYYY-MM-DD' 是输入字符串的格式模型。
       SELECT TO_DATE('2024-02-18', 'YYYY-MM-DD') AS DateValue FROM dual;
       
  •  日期转字符串:SYSDATE 返回当前系统日期和时间,TO_CHAR 函数用于将日期转换为字符串,并通过第二个参数指定输出的格式。
       SELECT TO_CHAR(SYSDATE, 'YYYY-MM-DD') AS StringValue FROM dual;
       

示例中,'YYYY-MM-DD' 代表年-月-日的格式。Oracle支持多种日期格式模型,可以根据实际需求选择合适的格式进行转换。


 25.MySQL 的存储引擎有哪些?

InnoDB

MyISAM

Memory

Merge

Archive

Federate

CSV

BLACKHOLE 


26.事务隔离级别有哪些?mysql 和 oracle 默认的隔离级别是什么? 

事务隔离级别主要有四种,这是由SQL标准定义的:

  • 读未提交(Read Uncommitted): 在这种级别下,一个事务可以看到其他事务未提交的数据。这可能导致脏读(Dirty Read),即事务读取了另一个事务尚未提交且最终可能被回滚的数据。
  • 读已提交(Read Committed): 这是大多数数据库系统默认的隔离级别,一个事务只能看到已经提交的数据。在同一个事务中,即使前后两次查询,如果在这两次查询之间有其他事务对数据进行了修改并提交,那么第二次查询可能会得到不同的结果,这称为不可重复读(Non-Repeatable Read)。
  • 可重复读(Repeatable Read): 在这个级别下,同一事务内多次读取同一条记录的结果是一致的,不会受到其他事务对该记录的修改影响。但可能出现幻读(Phantom Read),即在同一事务中,当第一次和第二次执行相同的查询时,会因为其他事务的插入操作而返回更多的行。
  • 串行化(Serializable): 最高的隔离级别,提供严格的事务隔离,确保事务按顺序执行,避免脏读、不可重复读和幻读的发生。在该级别下,事务会获取表级别的锁或行级锁,直到事务结束,这样可以防止其他事务在此期间对相同数据进行任何更改。

关于MySQL和Oracle的默认事务隔离级别:

  • MySQL 的默认事务隔离级别为 REPEATABLE READ
  • Oracle 的默认事务隔离级别为 READ COMMITTED

不过,Oracle提供了自己的机制来避免不可重复读的问题,例如使用“快照读”(Snapshot Reads)。

事务的四大特性,也被称为ACID(Atomicity、Consistency、Isolation和Durability),是数据库系统中事务处理必须满足的四个基本要求:

  • 原子性(Atomicity): 事务是一个不可分割的工作单元。事务中的所有操作要么全部成功完成,要么全部失败回滚。如果事务中有任何部分未能成功执行,则整个事务将被撤销,确保系统状态不会因为部分成功而发生不一致。
  • 一致性(Consistency): 在事务开始前和结束后,数据都必须处于一致状态。这意味着事务执行后,所有的完整性约束(如外键约束、唯一性约束等)仍然得到满足,保证了数据库从一个有效状态转换到另一个有效状态。
  • 隔离性(Isolation): 多个事务并发执行时,每个事务之间应互不干扰,就像在单线程环境下串行执行一样。即使多个事务同时进行,也应该防止脏读(Dirty Read)、不可重复读(Non-Repeatable Read)和幻读(Phantom Read)等问题的发生。不同数据库系统通过不同的隔离级别来实现这一特性,例如READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ和SERIALIZABLE。
  • 持久性(Durability): 当事务提交之后,对数据库的修改将会永久保存,即便出现系统崩溃或电源故障等情况,在恢复后也能保持这些修改结果的正确性和完整性。通常通过日志文件等机制来保证事务的持久性。

 27.SQL 如何行转列和列转行?

 行转列(Pivot)

-- 假设有如下原始数据表 SalesData
CREATE TABLE SalesData (
    Product VARCHAR(50),
    SaleDate DATE,
    Quantity INT
);

INSERT INTO SalesData VALUES ('ProductA', '2021-01-01', 10), 
                              ('ProductA', '2021-02-01', 20),
                              ('ProductB', '2021-01-01', 30),
                              ('ProductB', '2021-02-01', 40);

-- 在MySQL中模拟行转列,假设我们只关注 '2021-01-01' 和 '2021-02-01' 这两个日期
SELECT 
    Product,
    MAX(IF(SaleDate = '2021-01-01', Quantity, NULL)) AS '2021-01-01',
    MAX(IF(SaleDate = '2021-02-01', Quantity, NULL)) AS '2021-02-01'
FROM SalesData
GROUP BY Product;

列转行(Unpivot)

-- 假设已经进行过类似PIVOT操作的数据表 PivotedSalesData
CREATE TABLE PivotedSalesData (
    Product VARCHAR(50),
    JanSales INT,
    FebSales INT
);

INSERT INTO PivotedSalesData VALUES ('ProductA', 10, 20),
                                     ('ProductB', 30, 40);

-- 列转行,创建一个临时的结果集以实现转换
SELECT 
    Product,
    'JanSales' AS SaleDate,
    JanSales AS Quantity
FROM PivotedSalesData
UNION ALL
SELECT 
    Product,
    'FebSales' AS SaleDate,
    FebSales AS Quantity
FROM PivotedSalesData;

 28.如何查看 sql 的执行计划?

在MySQL中查看SQL查询的执行计划,可以使用EXPLAIN关键字。执行计划可以帮助我们了解数据库如何执行特定的SQL语句,包括表的读取顺序、使用的索引以及是否进行了全表扫描等。
以下是如何查看一个简单查询的执行计划:

EXPLAIN SELECT * FROM your_table WHERE some_column = 'some_value';

对于上述命令,只需将your_table替换为你的表名,将som_column = 'some_value'替换为实际的查询条件即可。执行该命令后,MySQL会返回一个包含多列信息的结果集,这些列描述了MySQL如何处理这个查询的各个细节。

例如:
id:标识SELECT子句中的查询部分。
select_type:表示查询类型(SIMPLE、PRIMARY、SUBQUERY等)。
table:输出行对应的数据表。
type:访问类型,如ALL(全表扫描)、index(全索引扫描)、range(范围扫描)、ref(非唯一性索引扫描)等。
possible_keys:可能用到的索引列表。
key:实际选择使用的索引。
key_len:所使用的键长度。
ref:显示哪个字段或常量与key一起被使用。
rows:估计需要检查的行数。
Extra:包含其他额外信息,如“Using index”(覆盖索引)、“Using where”(使用WHERE条件过滤)等。
对于更复杂的查询,可能需要使用EXPLAIN EXTENDED或EXPLAIN FORMAT=JSON以获取更多详细信息。


29.union 和 unionAll 区别? 

UNION 和 UNION ALL 是 SQL 中用于合并多个 SELECT 语句结果集的两种操作,它们的主要区别在于对重复行的处理:

  • UNION:当使用 UNION 操作符时,它会去除结果集中所有重复的行。也就是说,从每个查询结果集中选择不同的记录合并在一起。
        SELECT column1, column2 FROM table1
        UNION
        SELECT column1, column2 FROM table2;
        
  • UNION ALL:当使用 UNION ALL 操作符时,它将简单地连接两个或多个查询结果集中的所有行,包括可能存在的重复行。
        SELECT column1, column2 FROM table1
        UNION ALL
        SELECT column1, column2 FROM table2;
        

总结来说,如果需要合并的结果集中不允许有重复的数据项,则使用 UNION;而如果希望保留所有数据项(即使存在重复),则使用 UNION ALL,因为它的执行效率通常更高,因为它不会执行去重操作。 


30.为什么说 Mybatis 是半自动 ORM 映射工具?它与全自动的区别在哪里?

MyBatis 被称为半自动 ORM 映射工具,主要体现在它在处理对象关系映射(ORM)时提供了灵活且部分自动化的方式。与全自动 ORM 框架相比,如 Hibernate,MyBatis 在以下几个方面体现了其“半自动”特点:
1. SQL 语句的编写:
  • MyBatis:需要手动编写 SQL 查询、更新等语句,并通过 XML 配置文件或注解方式将 SQL 映射到 Java 方法上。开发者可以精确控制 SQL 的结构和内容,优化查询性能。
  • 全自动 ORM(如 Hibernate):根据实体类的元数据自动生成 SQL 语句,对 SQL 控制相对较少,但提供了更高级别的抽象。
2. 数据库表结构变化与代码同步:
  • MyBatis:当数据库表结构发生变化时,通常需要手动修改相应的 SQL 映射配置,使得映射关系保持一致。
  • 全自动 ORM:基于实体类与数据库表之间的映射规则,一般能自动适应表结构的变化,减少了维护成本。
3. 关联对象的加载策略:
  • MyBatis:对于关联对象的查询,需要手动编写嵌套查询、关联查询或者利用 MyBatis 提供的 resultMap 结构来处理一对多、多对一等关联关系的加载。
  • 全自动 ORM:可以根据定义好的关联关系自动进行延迟加载或立即加载,简化了关联对象处理的复杂度。
因此,MyBatis 在提供了一定程度的 ORM 功能的同时,也赋予了开发者更多地控制 SQL 执行细节的能力,而在处理一些复杂场景或需要高度优化性能的应用中,这种灵活性尤为可贵。而全自动 ORM 工具则更注重于减少开发者的负担,提高开发效率,但在某些特定情况下可能牺牲一定的性能优化空间。

31.oracle 中的分析函数有哪些? 

Oracle数据库提供了丰富的分析函数,用于对查询结果集进行计算和统计。以下是一些常用的Oracle分析函数:

  • SUM() over():对窗口或分区中的值求和。
  • AVG() over():计算窗口或分区内的平均值。
  • COUNT() over():计算窗口或分区内满足条件的行数。
  • MAX() over() 和 MIN() over():分区中查找最大值和最小值。
  • RANK() over()、DENSE_RANK() over() 和 ROW_NUMBER() over():这三个函数都用于生成基于排序的行号,但处理相同值的方式不同。
  •   RANK():当遇到相同值时,所有相同的行都会得到相同的排名,然后下一个不同的值会跳过相同的排名继续计数。
  • DENSE_RANK():与RANK()类似,但在遇到相同值时不会跳过排名,而是连续分配排名。
  • ROW_NUMBER():总是为每一行分配唯一的数字,即使值相同。
  • LEAD() over() 和 LAG() over():返回当前行之后(LEAD)或之前(LAG)指定偏移量的行的列值。
  • FIRST_VALUE() over() 和 LAST_VALUE() over():在一个窗口或分区内返回第一个或最后一个指定列的值。
  • CUME_DIST() over() 和 PERCENT_RANK() over():用于计算累积分布和百分比排名。
  • NTILE() over():将行分配到指定数量的桶中,每个桶包含大致相等数量的行。

示例:

SELECT 
    id, 
    value,
    SUM(value) OVER (ORDER BY id) AS RunningTotal,
    AVG(value) OVER () AS AverageValue,
    RANK() OVER (ORDER BY value DESC) AS RankByValue,
    LAG(value, 1) OVER (ORDER BY id) AS PreviousValue
FROM your_table;

32.数据库中除了聚合函数之外还有哪些常用的函数?oracle 数据库的 merge()函数的作用和使用?

数据库中除了聚合函数(如SUM、COUNT、AVG、MAX、MIN等)之外,还有很多其他常用的函数,以下是一些示例:
1. 字符串函数:

  • LENGTH(): 返回字符串的长度。
  • UPPER(), LOWER(): 将字符串转换为大写或小写。
  • TRIM(): 删除字符串首尾的空格或其他指定字符。
  • CONCAT(), || 连接符: 用于连接两个或更多的字符串。

2. 日期函数:

  • SYSDATE: 返回当前系统的日期和时间。
  • CURRENT_DATE, CURRENT_TIMESTAMP: 获取当前日期或包含时间的当前时间戳。
  • TO_CHAR(date, format): 将日期转换为格式化的字符串。
  • ADD_MONTHS(): 增加或减少给定日期的月数。

3. 数学函数:

  • ABS(): 返回数字的绝对值。
  • ROUND(): 对数字进行四舍五入。
  • MOD(): 计算除法的余数。
  • POWER(): 求幂运算。

4. 条件表达式函数:

  • CASE WHEN THEN ELSE END: 多分支条件判断语句。

33.MySQL 中如何忽略表名的大小写?

在MySQL中,表名的大小写敏感性取决于操作系统的文件系统和MySQL服务器的配置。MySQL通过lower_case_table_names系统变量来控制是否将表名转换为小写。该变量的值可以在MySQL服务器启动时设置,并且其行为与数据库的实际存储和引用方式密切相关。

  • 在Windows系统中,默认情况下lower_case_table_names设置为1,这意味着MySQL会忽略表名的大小写,所有表名都会被存储为小写。
  • 在大多数Unix/Linux系统中,默认情况下lower_case_table_names设置为0,表示表名区分大小写。

如果你想在安装MySQL后更改此行为(例如,在Linux上实现不区分大小写的表名),你需要编辑MySQL配置文件(通常是my.cnf或my.ini),并在 [mysqld] 部分添加或修改以下行:

[mysqld]
lower_case_table_names=1

        然后,根据你的MySQL版本和数据安全性要求,你可能需要重新初始化MySQL服务或者创建一个新的数据目录以确保新设置生效。对于生产环境中的现有数据库,请务必在更改此设置前做好充分的数据备份。

请注意,在MySQL 8.0中,lower_case_table_names在某些操作系统上的可更改性受到了限制,可能只能在首次初始化实例时设置。对于已经存在数据的实例,改变这个变量的行为可能会导致一致性问题,因此强烈建议在创建新的数据库结构时一开始就规划好表名大小写策略。


34.Aspect 切面 

在Java编程中,Aspect Oriented Programming(面向切面编程,AOP)是一种编程范式,它允许开发者将横切关注点(例如日志、事务管理、权限验证等)与业务逻辑分离,通过预编译方式织入到程序的各个模块中。Spring框架提供了对AOP的强大支持。


在AOP术语中,“切面”(Aspect)是指跨越多个对象的行为或关注点的模块化。一个切面可以包含多个通知(Advice),这些通知会在特定的连接点(Pointcut)上执行。

例如,定义一个简单的日志切面可能包括以下部分:

  • 切面(Aspect):整个日志处理逻辑的封装,包括何时何地应用这个逻辑。
  • 连接点(JoinPoint):在程序执行过程中明确的点,如方法调用、异常抛出等。
  • 通知(Advice):在连接点处要执行的动作,比如前置通知(Before advice)、后置通知(After advice)、环绕通知(Around advice)等。
  • 切点(Pointcut):匹配连接点的表达式,定义了通知应该在哪些连接点上执行。

以下是一个简单的Spring AOP切面示例代码: 

@Aspect
@Component
public class LoggingAspect {

    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("Executing: " + joinPoint.getSignature());
    }

    @AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
    public void logAfterReturning(Object result) {
        System.out.println("Executed successfully with result: " + result);
    }
}

在这个例子中,LoggingAspect就是一个切面,它包含了两个通知:一个在方法执行前记录日志的前置通知和一个在方法成功返回后记录结果的日志的后置通知。

execution(* com.example.service.*.*(..))是一个切点表达式,表示该切面应用于com.example.service包下所有类的所有方法。


35.常见的高并发场景有哪些,对应的架构设计方案是什么? 

高并发场景通常出现在用户访问量巨大的互联网应用中,例如:
1. 电商秒杀活动:短时间内大量用户同时抢购有限的商品资源。

架构设计方案:

  • 集群部署:通过负载均衡将请求分发到多个服务器节点上。
  • 缓存优化:利用Redis等分布式缓存系统预加载商品库存和用户信息,减少数据库压力,并处理热点key问题。
  • 异步处理:使用消息队列处理下单、减库存等操作,将同步变为异步,缓解瞬时高并发写入数据库的压力。
  • 熔断降级:对非核心服务进行降级处理,保证核心业务的正常运行。
  • 限流策略:采用令牌桶或漏桶算法限制用户的请求数量。

2. 在线抢票系统(如春运火车票、演唱会门票):瞬间产生极高并发请求,且数据更新频繁。

架构设计方案:

  • 动态扩容:提前规划好弹性伸缩策略,在高峰期自动增加服务器资源。
  • 读写分离与分库分表:数据库层面实现水平扩展,分散查询和写入压力。
  • 索引优化:对关键字段建立合适索引,提高查询效率。
  • 排队机制:为用户提供公平的抢票机会,避免并发冲突导致的数据不一致。
  • 分布式锁:在多线程或多进程环境下,确保同一时刻只有一个客户端能完成关键操作。

3. 社交平台/新闻网站:突发热点事件引发的流量洪峰。

架构设计方案:

  • 内容分发网络(CDN):静态资源和服务端渲染页面缓存在边缘节点,降低主站带宽压力。
  • 多级缓存:包括本地缓存、分布式缓存以及CDN缓存,减少数据库访问次数。
  • 服务拆分与微服务架构:将大系统拆分为多个小的服务单元,独立部署和扩展。
  • 实时计算与热数据存储:对于热点内容的访问统计,可以采用实时计算框架进行分析并存储至内存数据库以快速响应查询。

4. 在线支付系统:订单生成、支付确认等环节需要在极短的时间内完成,且涉及资金安全。

架构设计方案:

  • 强一致性事务处理:借助分布式事务解决方案(如2PC、TCC)保证交易的一致性。
  • 分布式事务中间件:利用Seata、DTX等组件来协调跨服务的事务。
  • 幂等性设计:防止重复支付,确保多次相同的请求只会执行一次有效操作。
  • 冗余设计与故障切换:支付网关、数据库等关键组件做主备或集群部署,保证高可用性。

针对上述场景,通用的设计原则和方案还包括:

  • 高性能后端服务:采用高性能的语言和技术栈构建后端服务,比如Java中的高性能Netty框架用于构建网络通信层。
  • API网关:统一接入点,实现认证、限流、路由等功能,保护内部服务不受直接冲击。
  • 监控告警与运维自动化:实时监控系统性能指标,及时发现瓶颈并报警,结合自动化运维工具进行快速响应和调整。 

36.介绍完整的分布式中间件有哪些,各自的应用场景和作用。 

分布式中间件是一类软件系统,它们为构建和管理分布式应用提供了关键的服务和功能。这类中间件在不同的应用场景中承担着不同角色,以下是几种常见的分布式中间件及其作用和应用场景:
1. 消息队列/消息中间件:

  • RabbitMQ、Apache Kafka、RocketMQ、ActiveMQ:这些消息队列中间件用于解耦应用程序的组件,提供异步处理、流量削峰、数据同步等功能。例如,在电商场景下,订单创建后通过消息队列发送给库存服务进行扣减,即使库存服务出现短暂故障也不会影响下单流程。

2. 分布式事务协调器:

  • Seata(Simple Extensible Autonomous Transaction Architecture):解决分布式环境下的事务一致性问题,保证分布式操作的ACID特性。
  • Google Spanner 和 阿里OceanBase:提供跨数据中心的分布式事务支持,适用于金融级高并发且强一致性的交易场景。

3. 服务注册与发现:

  • Eureka(Spring Cloud Netflix项目的一部分)、Consul、Zookeeper:这些服务治理中间件在微服务架构中起到关键作用,实现服务自动注册、发现以及健康检查,确保服务间的高效调用。
  • nacos:Nacos 是阿里巴巴开源的一款集成了服务发现、配置管理和服务元数据管理的平台。

4. 配置中心:

  • Apollo(阿波罗配置中心)、Spring Cloud Config Server:集中化管理和动态推送配置信息到各个服务节点,使得配置变更无需重启服务就能生效,适用于大规模分布式系统的配置管理。

5. 分布式缓存:

  • Redis、Memcached:作为高性能的内存数据库,可以减轻数据库访问压力,提高读取速度,适用于存储热点数据、会话管理、计数器等场景。

6.分布式追踪系统:

  • Zipkin、Jaeger、OpenTelemetry:用于收集、可视化和分析分布式系统的延迟数据,帮助开发者了解分布式系统中请求链路的耗时情况,排查性能瓶颈。

7. 服务网格:

  • Istio、Linkerd:作为服务间通信的基础设施层,能够对服务间的网络调用进行控制、监控和安全防护,简化服务治理和运维工作。

8. 分布式协调服务:

  • Zookeeper:不仅可以用作服务注册与发现,还可以提供分布式锁、选举、队列等功能,常用于分布式系统中的数据一致性维护。

每种分布式中间件都有其独特的设计目标和适用场景,共同支撑起复杂的分布式系统架构,以满足高性能、高可用性和可扩展性需求。 


37.双 11 秒杀活动,你的技术架构设计思路。 

设计一个双11秒杀活动的技术架构时,需要考虑的关键技术栈包括:
1. 入口层与流量控制

  • Nginx:作为反向代理服务器和负载均衡器,通过配置限流模块如limit_req或使用第三方模块(如lua-resty-limit-traffic)实现令牌桶算法进行流量控制。
  • API Gateway(例如Spring Cloud Gateway、Kong等):除了负载均衡外,还可以处理身份验证、请求路由、熔断降级、限流等功能。

2. 分布式服务架构

  • 微服务框架:基于Spring Boot结合Spring Cloud生态构建微服务应用,如使用Eureka/Zookeeper做服务注册与发现,Hystrix/Sentinel进行熔断降级和服务保护。
  • 消息队列中间件:采用RabbitMQ、RocketMQ或Kafka,将秒杀下单操作异步化,减轻数据库压力。比如下单成功后发送消息到队列,由库存服务消费并执行减库存操作。

3. 数据库设计与优化

  • 关系型数据库:MySQL或PostgreSQL作为主要的数据存储,实现读写分离,使用主从复制或者分库分表工具如ShardingSphere进行数据分片。
  • 缓存系统:Redis用于存储热点商品信息和库存数量,提供快速响应,避免直接对数据库造成压力。利用Redis的原子操作来处理减库存问题,防止超卖。
  • 分布式事务:在分布式环境下保证事务一致性,可以选用Seata或TCC模式(Try-Confirm-Cancel)来处理跨服务的事务问题。

4. 弹性伸缩

  • 容器化部署:使用Docker和Kubernetes(K8s)进行服务容器化,并设置自动扩缩容策略,根据CPU、内存等资源使用情况动态调整实例数量。

5. 监控告警

  • 日志收集与分析:使用ELK Stack(Elasticsearch、Logstash、Kibana)收集并分析服务日志,及时发现问题。
  • APM工具:如SkyWalking、Pinpoint等,用于追踪分布式系统的性能瓶颈和调用链路。
  • 系统监控:Prometheus + Grafana进行基础设施及应用层面的实时监控,并配合Alertmanager进行告警通知。

6. 安全防护

  • 防火墙与WAF:阿里云或其他云服务商提供的防火墙服务,结合Web应用防火墙(WAF)防御SQL注入、XSS攻击等。
  • 验证码:使用图形验证码、滑动验证码等方式防止机器人恶意抢购。

7. 测试与压测

  • 接口自动化测试:使用Junit、TestNG等进行单元测试,Postman、JMeter等工具进行接口集成测试。
  • 性能测试与压测:通过Apache JMeter、LoadRunner等工具模拟大量用户并发访问场景,确保系统在高并发下的稳定性和性能表现。

以上是针对双11秒杀活动的一些关键技术和组件选择,实际架构设计中还需结合业务特点和预期规模进一步细化和完善。


38.用 java 自己实现一个 LRU。  

在Java中实现一个简单的LRU(Least Recently Used,最近最少使用)缓存,我们可以利用LinkedHashMap的特性来简化实现。LinkedHashMap继承自HashMap,并提供了对元素访问顺序的维护,可以通过重写其removeEldestEntry方法来控制缓存大小。

以下是一个基本的LRU缓存实现: 

import java.util.LinkedHashMap;
import java.util.Map;

public class LRUCache<K, V> {
    private final int capacity;
    private LinkedHashMap<K, V> cache;

    public LRUCache(int capacity) {
        // 初始化LinkedHashMap时设置accessOrder为true,表示按访问顺序排序
        this.capacity = capacity;
        this.cache = new LinkedHashMap<>(capacity + 1, 0.75f, true) {
            @Override
            protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
                // 当缓存容量超过设定的最大值时,移除最老的条目(最近最少使用的)
                return size() > capacity;
            }
        };
    }

    public V get(K key) {
        // 访问操作会自动将该键值对移动到链表末尾,即最近被访问的位置
        return cache.getOrDefault(key, null);
    }

    public void put(K key, V value) {
        // 更新或插入新的键值对。由于LinkedHashMap的特性,新插入或者访问过的元素都会移到队列末尾
        cache.put(key, value);
    }

    // 可选方法:如果需要删除某个特定key的项
    public void remove(K key) {
        cache.remove(key);
    }

    // 示例用法
    public static void main(String[] args) {
        LRUCache<Integer, String> lruCache = new LRUCache<>(3);

        lruCache.put(1, "one");
        lruCache.put(2, "two");
        lruCache.put(3, "three");

        System.out.println(lruCache.get(1)); // 输出:one

        // 添加第四个元素时,最早的元素会被移除
        lruCache.put(4, "four");

        System.out.println(lruCache.containsKey(2)); // 输出:false,因为2已经被移除
        System.out.println(lruCache.get(3)); // 输出:three,3是最新的访问项,仍在缓存中
        System.out.println(lruCache.get(4)); // 输出:four,再次访问4,确保它仍保留在缓存中

        // 查看当前缓存内容
        for (Map.Entry<Integer, String> entry : lruCache.cache.entrySet()) {
            System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
        }
    }
}

这个LRU缓存类使用了LinkedHashMap作为底层数据结构,并重写了其removeEldestEntry()方法来实现当缓存大小超过预设容量时自动移除最早添加的元素。每次调用get()和put()方法时,都会触发元素在链表中的位置更新,从而保持最近访问过的元素位于链表末尾,符合LRU算法的设计原则。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

默语玄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值