查漏补缺第十一期(网易实习二面)
前言
目前正在出一个查漏补缺专题
系列教程, 篇幅会较多, 喜欢的话,给个关注❤️ ~
本专题主要以Java
语言为主, 好了, 废话不多说直接开整吧~
Mysql存储引擎有了解过吗?说说看
MySQL
是一个开源的关系型数据库管理系统,支持多种存储引擎。存储引擎
是MySQL
数据库的核心组件之一,负责数据的存储和检索。每个存储引擎都有自己的特点和适用场景,可以根据具体需求选择不同的存储引擎。
下面是MySQL中一些常见的存储引擎及其特点:
-
InnoDB
:- InnoDB是MySQL的
默认
存储引擎。 - 它支持事务和行级锁定,提供了高度可靠性和高并发性能。
- InnoDB还支持外键约束、崩溃恢复和数据完整性保护。
- 它适用于需要强调数据完整性和事务支持的应用程序。
- InnoDB是MySQL的
-
MyISAM
:- MyISAM是MySQL早期的默认存储引擎。
- 它提供了较高的性能和较低的存储空间占用。
- MyISAM不支持事务和行级锁定,但支持表级锁定。
- 它适用于读取密集型应用,例如数据仓库、日志分析等。
-
MEMORY
:- MEMORY存储引擎将数据存储在内存中,因此读取和写入速度非常快。
- 但是,它具有一定的限制,因为数据存储在内存中,所以对于大量数据的存储,可能会受到内存容量的限制。
- MEMORY存储引擎适用于临时表、缓存和其他需要快速访问的临时数据的场景。
-
CSV
:- CSV存储引擎以纯文本格式存储数据,以逗号分隔值(CSV)格式存储。
- 它适用于处理大量数据导入和导出的场景。
- CSV存储引擎不支持索引,也不支持事务。
除了上述存储引擎,MySQL还支持其他一些存储引擎,如Archive、Blackhole等。此外,MySQL还提供了插件式存储引擎架构,允许开发者根据需要自定义和扩展存储引擎。
在实际使用中,选择适合的存储引擎需要考虑多个因素,包括数据一致性要求、并发性能需求、存储空间占用、数据访问模式等。通过了解各个存储引擎的特点和适用场景,可以根据具体的业务需求来做出选择。
Mysql的事务隔离级别有了解过吗?
MySQL
支持多个事务隔离级别
,用于控制并发事务之间的隔离程度和数据一致性。
-
读未提交(Read Uncommitted)
:- 最低的隔离级别,事务可以看到其他事务尚未提交的修改。
- 可能会导致脏读(Dirty Read),即读取到未提交的数据。
- 不提供数据的一致性和隔离性保护。
-
读已提交(Read Committed)
:- 事务只能看到已经提交的其他事务所做的修改。
- 避免了脏读,但仍可能出现不可重复读(Non-Repeatable Read)和幻读(Phantom Read)问题。
- 不同的查询可能返回不同的结果,因为其他事务可能在事务执行过程中进行了提交。
-
可重复读(Repeatable Read)
:- 同一事务内的多个查询会看到一致的快照,即使其他事务进行了修改。
- 确保了相同查询的一致性,避免了不可重复读问题。
- 仍然可能出现幻读问题,即在同一事务中执行相同的查询,但返回的结果集不同,因为其他事务可能插入了新的数据。
-
串行化(Serializable)
:- 最高的隔离级别,确保了事务之间的完全隔离性。
- 通过强制事务的顺序执行,避免了脏读、不可重复读和幻读问题。
- 但是,由于串行执行的特性,可能会导致并发性能下降。
MySQL的默认隔离级别是可重复读(Repeatable Read)
,这是一个平衡了数据一致性和并发性能的级别。在实际应用中,可以根据具体需求选择合适的隔离级别,权衡数据一致性和并发性能的要求。
可以使用以下语句设置事务隔离级别:
SET TRANSACTION ISOLATION LEVEL <isolation_level>;
其中,<isolation_level>
可以是上述提到的隔离级别之一。另外,也可以在连接字符串或配置文件中设置默认的隔离级别。
需要注意的是,更高的隔离级别通常会引入更多的锁定机制和开销,因此在选择隔离级别时,还需考虑系统的并发负载和性能要求。
Mysql中主键和唯一索引以及联合索引区别,分别有什么作用
在MySQL
中,主键、唯一索引和联合索引是常用的索引类型,用于提高查询效率和维护数据的完整性。它们在定义和作用上有一些区别。
-
主键(Primary Key)
:- 主键是用于唯一标识表中每一行数据的列或列组合。
- 主键具有唯一性和非空性的特性,确保每行数据都具有唯一的标识,并且不允许为空。
- 主键可以由单个列或多个列组成,但每个表只能有一个主键。
- 主键索引在物理存储上是有序的,对于查找和连接操作非常高效。
- 主键通常用于加速数据的唯一性验证和快速查找。
-
唯一索引(Unique Index)
:- 唯一索引是用于确保某列或列组合的值的唯一性。
- 与主键不同,唯一索引允许空值,但对于非空值,每个值只能出现一次。
- 表可以有多个唯一索引,但每个索引都必须唯一。
- 唯一索引在查询时提供了较快的查找速度,同时保证了数据的一致性。
- 唯一索引常用于避免重复数据的插入和快速查找重复值。
-
联合索引(Composite Index)
:- 联合索引是指在多个列上创建的索引,以支持多列的查询条件。
- 联合索引可以包含两个或更多列,并按照定义的顺序进行排序。
- 联合索引可以提高联合查询中的性能,减少数据的物理读取。
- 查询条件中包含索引的第一个列时,索引将被充分利用。如果查询条件中涉及的列不在索引的前缀位置上,索引的效率可能降低。
- 联合索引的列顺序非常重要,需要根据查询的特点和频率进行合理的设计。
总结来说,主键用于唯一标识每一行数据,并且是一种特殊的唯一索引。唯一索引用于确保列或列组合的唯一性,而联合索引用于优化多列查询的性能。在设计数据库表时,合理选择和使用这些索引类型,可以提高数据的完整性和查询的效率。
jvm中堆和栈还有方法区的作用是什么
在Java虚拟机(JVM)
中,堆(Heap)
、栈(Stack)
和方法区(Method Area)
是三个重要的内存区域,分别用于不同的目的。
-
堆(Heap)
:- 堆是Java程序运行时动态分配内存的地方,用于存储对象实例和数组。
- 所有通过关键字 "new" 创建的对象都存储在堆中。
- 堆是一个共享的内存区域,在JVM启动时被创建,并被所有线程共享。
- 堆的大小可以通过JVM参数进行配置。
- 堆中的对象可通过垃圾回收(Garbage Collection)来进行自动内存管理。
-
栈(Stack)
:- 栈用于存储方法调用的信息、局部变量以及方法执行过程中的临时数据。
- 每个线程在运行时都有一个对应的栈。
- 栈采用先进后出(Last-In-First-Out)的方式管理数据。
- 栈中的数据是线程私有的,线程之间不共享栈的数据。
- 栈的大小在创建线程时被确定,并随着方法调用和返回的动态变化。
-
方法区(Method Area)
:- 方法区是用于存储类的结构信息、常量、静态变量、即时编译器编译后的代码等数据。
- 方法区也被称为永久代(
Permanent Generation
),在较新的JVM
版本中,永久代被移除,取而代之的是元空间(Metaspace)。 - 方法区存储的数据在整个应用程序运行期间都存在,包括类的字节码、方法、字段、运行时常量池等信息。
- 方法区的大小也可以通过JVM参数进行配置。
需要注意的是,堆、栈和方法区
在内存分配和管理方面具有不同的特点和用途。堆用于存储动态分配的对象,栈用于存储方法调用和临时数据,方法区存储类相关的信息。
创建线程的方式有哪些
在Java中,有多种方式可以创建线程,以下是常用的创建线程的方式:
继承Thread类
:- 创建一个继承自
Thread
类的子类,重写run()
方法来定义线程执行的逻辑。 - 通过创建子类的实例对象,调用
start()
方法启动线程。
- 创建一个继承自
class MyThread extends Thread {
public void run() {
// 线程执行的逻辑
}
}
// 创建并启动线程
MyThread myThread = new MyThread();
myThread.start();
- 实现
Runnable
接口:- 创建一个实现Runnable接口的类,实现run()方法来定义线程执行的逻辑。
- 创建一个Thread对象,将实现了Runnable接口的对象作为参数传递给Thread的构造函数。
- 调用Thread对象的start()方法启动线程。
class MyRunnable implements Runnable {
public void run() {
// 线程执行的逻辑
}
}
// 创建并启动线程
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
- 使用匿名类创建线程:
- 可以使用匿名类的方式创建线程对象并重写`run()``方法。
Thread thread = new Thread() {
public void run() {
// 线程执行的逻辑
}
};
// 启动线程
thread.start();
- 使用Lambda表达式创建线程:
- 可以使用Lambda表达式简化线程的创建和定义。
Thread thread = new Thread(() -> {
// 线程执行的逻辑
});
// 启动线程
thread.start();
需要注意的是,无论使用哪种方式创建线程,都需要调用线程对象的start()
方法来启动线程。线程的运行会自动调用run()
方法中定义的逻辑。
如何获取线程的返回值呢
在Java中,可以通过以下方式创建线程并获取返回值:
-
使用
Callable
和Future
:- 创建一个实现
Callable
接口的类,并实现call()
方法,定义线程执行的逻辑并返回一个结果。 - 创建
ExecutorService
对象,通常使用Executors
工厂类来创建。 - 提交
Callable
任务给ExecutorService
,返回一个Future
对象。 - 使用
Future
对象的get()
方法获取线程的返回值。
import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; class MyCallable implements Callable<Integer> { public Integer call() throws Exception { // 线程执行的逻辑 return 42; // 返回结果 } } // 创建并提交Callable任务 ExecutorService executorService = Executors.newSingleThreadExecutor(); MyCallable myCallable = new MyCallable(); Future<Integer> future = executorService.submit(myCallable); // 获取线程的返回值 try { Integer result = future.get(); System.out.println("线程返回值:" + result); } catch (Exception e) { e.printStackTrace(); }
- 创建一个实现
-
使用
CompletableFuture
:- 使用
CompletableFuture
类可以在异步任务完成后获取返回值。 - 使用
supplyAsync()
方法提交一个异步任务,该方法接受一个Supplier
函数式接口作为参数,用于定义异步任务的逻辑并返回结果。 - 调用
get()
方法获取异步任务的返回值。
import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; // 提交异步任务并获取返回值 CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> { // 异步任务的逻辑 return 42; // 返回结果 }); // 获取异步任务的返回值 try { Integer result = future.get(); System.out.println("异步任务返回值:" + result); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); }
- 使用
这两种方式都可以创建线程并获取返回值,具体选择哪种方式取决于具体的使用场景和需求。如果需要更多的线程控制和管理功能,Callable
和Future
结合使用是更好的选择。而CompletableFuture
提供了更强大的异步编程能力和组合操作,适合处理更复杂的异步任务场景。
创建线程池的方式有哪些
在Java中,可以使用以下方式创建线程池:
- 使用
Executors
工厂类:Executors
类提供了多个静态方法用于创建不同类型的线程池。- 常用的方法包括:
newFixedThreadPool(int nThreads)
: 创建一个固定大小的线程池,线程池中的线程数量固定为指定的大小。newCachedThreadPool()
: 创建一个可缓存的线程池,线程池中的线程数量根据需要自动增长,空闲线程会被回收。newSingleThreadExecutor()
: 创建一个单线程的线程池,线程池中只有一个线程按顺序执行任务。newScheduledThreadPool(int corePoolSize)
: 创建一个具有定时任务执行功能的线程池。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// 创建固定大小的线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
// 提交任务给线程池执行
executorService.submit(new MyTask());
- 使用
ThreadPoolExecutor
类:ThreadPoolExecutor
是Java提供的一个灵活的线程池实现类,可以手动配置线程池的各个参数。- 使用
ThreadPoolExecutor
可以自定义线程池的核心线程数、最大线程数、线程空闲时间等参数。
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
// 创建线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, // 核心线程数
10, // 最大线程数
60, // 线程空闲时间
TimeUnit.SECONDS, // 时间单位
new ArrayBlockingQueue<>(100) // 任务队列
);
// 提交任务给线程池执行
executor.execute(new MyTask());
需要根据具体需求选择合适的线程池类型和配置参数。线程池能够提供线程的复用、线程管理、线程调度等功能,可以有效控制并发线程的数量,提高程序的性能和资源利用率。
如果线程池的队列满了, 该怎么去处理
当线程池的任务队列已满时,可以根据具体需求选择以下几种处理方式:
- 调用线程池的
execute()
方法:- 使用
execute()
方法提交任务时,如果任务队列已满且线程池的最大线程数也已达到上限,execute()
方法将抛出RejectedExecutionException
异常。可以通过捕获异常并进行相应处理。
- 使用
import java.util.concurrent.*;
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, // 核心线程数
10, // 最大线程数
60, // 线程空闲时间
TimeUnit.SECONDS, // 时间单位
new ArrayBlockingQueue<>(5) // 任务队列
);
try {
executor.execute(new MyTask());
} catch (RejectedExecutionException e) {
// 处理任务队列已满的情况
// 可以选择重试、丢弃任务或进行其他处理
System.out.println("任务队列已满,无法执行任务");
}
- 使用
ThreadPoolExecutor
的饱和策略:ThreadPoolExecutor
提供了几种内置的饱和策略(RejectedExecutionHandler)来处理任务队列已满的情况。- 可以使用
setRejectedExecutionHandler()
方法设置饱和策略。 - 常用的饱和策略包括:
AbortPolicy
:默认策略,直接抛出RejectedExecutionException
异常。CallerRunsPolicy
:任务由调用线程执行,即主线程直接执行被拒绝的任务。DiscardPolicy
:默默地丢弃被拒绝的任务。DiscardOldestPolicy
:丢弃最老的未处理任务,然后尝试提交当前任务。
import java.util.concurrent.*;
RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy();
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, // 核心线程数
10, // 最大线程数
60, // 线程空闲时间
TimeUnit.SECONDS, // 时间单位
new ArrayBlockingQueue<>(5), // 任务队列
handler // 饱和策略
);
executor.execute(new MyTask()); // 提交任务给线程池执行
可以根据具体的业务需求选择适合的处理方式,如重试任务、丢弃任务、执行主线程等,以达到对任务队列已满的情况进行合理处理的目的。
找不重复子串的最长的字串(力扣第三题)
import java.util.HashSet;
public class LongestSubstring {
public static String findLongestSubstring(String str) {
int n = str.length();
int left = 0;
int right = 0;
int maxLength = 0;
String longestSubstring = "";
HashSet<Character> set = new HashSet<>();
while (right < n) {
char currentChar = str.charAt(right);
if (!set.contains(currentChar)) {
set.add(currentChar);
int currentLength = right - left + 1;
if (currentLength > maxLength) {
maxLength = currentLength;
longestSubstring = str.substring(left, right + 1);
}
right++;
} else {
set.remove(str.charAt(left));
left++;
}
}
return longestSubstring;
}
public static void main(String[] args) {
String input = "pwwkew";
String longestSubstring = findLongestSubstring(input);
System.out.println("Longest Substring: " + longestSubstring);
// Longest Substring: wke
}
}
这个算法使用滑动窗口
的概念来解决问题。它维护一个窗口,窗口的左边界是left
,右边界是right
,初始时两个边界
都指向字符串的开头。然后,通过不断移动
右边界,将字符加入到窗口中,并检查是否出现重复字符。如果出现重复字符,就移动左边界,直到窗口中不再有重复字符。在这个过程中,记录窗口的最大长度和对应的子串。
该算法的时间复杂度为O(n)
,其中n
是字符串的长度。它只需要对字符串进行一次遍历,并使用哈希集合来存储出现过的字符,以及窗口的边界来维护滑动窗口。
结束语
大家可以针对自己薄弱的地方进行复习, 然后多总结,形成自己的理解,不要去背~
本着把自己知道的都告诉大家,如果本文对您有所帮助,点赞+关注
鼓励一下呗~
相关文章
- 查漏补缺第一期(Redis相关)
- 查漏补缺第二期(synchronized & 锁升级)
- 查漏补缺第三期(分布式事务相关)
- 查漏补缺第四期(Mysql相关)
- 查漏补缺第五期(HashMap & ConcurrentHashMap)
- 查漏补缺第六期(京东一面)
- 查漏补缺第七期(美团到店一面)
- 查漏补缺第八期(阿里一面)
- 查漏补缺第九期(阿里二面)
- 查漏补缺第十期(网易实习一面)
项目源码(源码已更新 欢迎star⭐️)
往期设计模式相关文章
- 一起来学设计模式之认识设计模式
- 一起来学设计模式之单例模式
- 一起来学设计模式之工厂模式
- 一起来学设计模式之建造者模式
- 一起来学设计模式之原型模式
- 一起来学设计模式之适配器模式
- 一起来学设计模式之桥接模式
- 一起来学设计模式之组合模式
- 一起来学设计模式之装饰器模式
- 一起来学设计模式之外观模式
- 一起来学设计模式之享元模式
- 一起来学设计模式之代理模式
- 一起来学设计模式之责任链模式
- 一起来学设计模式之命令模式
- 一起来学设计模式之解释器模式
- 一起来学设计模式之迭代器模式
- 一起来学设计模式之中介者模式
- 一起来学设计模式之备忘录模式
- 一起来学设计模式之观察者模式
- 一起来学设计模式之状态模式
- 一起来学设计模式之策略模式
- 一起来学设计模式之模板方法模式
- 一起来学设计模式之访问者模式
- 一起来学设计模式之依赖注入模式
设计模式项目源码(源码已更新 欢迎star⭐️)
Kafka 专题学习
- 一起来学kafka之Kafka集群搭建
- 一起来学kafka之整合SpringBoot基本使用
- 一起来学kafka之整合SpringBoot深入使用(一)
- 一起来学kafka之整合SpringBoot深入使用(二)
- 一起来学kafka之整合SpringBoot深入使用(三)
项目源码(源码已更新 欢迎star⭐️)
ElasticSearch 专题学习
项目源码(源码已更新 欢迎star⭐️)
往期并发编程内容推荐
- Java多线程专题之线程与进程概述
- Java多线程专题之线程类和接口入门
- Java多线程专题之进阶学习Thread(含源码分析)
- Java多线程专题之Callable、Future与FutureTask(含源码分析)
- 面试官: 有了解过线程组和线程优先级吗
- 面试官: 说一下线程的生命周期过程
- 面试官: 说一下线程间的通信
- 面试官: 说一下Java的共享内存模型
- 面试官: 有了解过指令重排吗,什么是happens-before
- 面试官: 有了解过volatile关键字吗 说说看
- 面试官: 有了解过Synchronized吗 说说看
- Java多线程专题之Lock锁的使用
- 面试官: 有了解过ReentrantLock的底层实现吗?说说看
- 面试官: 有了解过CAS和原子操作吗?说说看
- Java多线程专题之线程池的基本使用
- 面试官: 有了解过线程池的工作原理吗?说说看
- 面试官: 线程池是如何做到线程复用的?有了解过吗,说说看
- 面试官: 阻塞队列有了解过吗?说说看
- 面试官: 阻塞队列的底层实现有了解过吗? 说说看
- 面试官: 同步容器和并发容器有用过吗? 说说看
- 面试官: CopyOnWrite容器有了解过吗? 说说看
- 面试官: Semaphore在项目中有使用过吗?说说看(源码剖析)
- 面试官: Exchanger在项目中有使用过吗?说说看(源码剖析)
- 面试官: CountDownLatch有了解过吗?说说看(源码剖析)
- 面试官: CyclicBarrier有了解过吗?说说看(源码剖析)
- 面试官: Phaser有了解过吗?说说看
- 面试官: Fork/Join 有了解过吗?说说看(含源码分析)
- 面试官: Stream并行流有了解过吗?说说看
推荐 SpringBoot & SpringCloud (源码已更新 欢迎star⭐️)
博客(阅读体验较佳)
转载自:https://juejin.cn/post/7243450433812971575