likes
comments
collection
share

查漏补缺第十一期(网易实习二面)

作者站长头像
站长
· 阅读数 28

前言

目前正在出一个查漏补缺专题系列教程, 篇幅会较多, 喜欢的话,给个关注❤️ ~

本专题主要以Java语言为主, 好了, 废话不多说直接开整吧~

Mysql存储引擎有了解过吗?说说看

MySQL是一个开源的关系型数据库管理系统,支持多种存储引擎。存储引擎MySQL数据库的核心组件之一,负责数据的存储和检索。每个存储引擎都有自己的特点和适用场景,可以根据具体需求选择不同的存储引擎。

下面是MySQL中一些常见的存储引擎及其特点:

  1. InnoDB

    • InnoDB是MySQL的默认存储引擎。
    • 它支持事务和行级锁定,提供了高度可靠性和高并发性能。
    • InnoDB还支持外键约束、崩溃恢复和数据完整性保护。
    • 它适用于需要强调数据完整性和事务支持的应用程序。
  2. MyISAM

    • MyISAM是MySQL早期的默认存储引擎。
    • 它提供了较高的性能和较低的存储空间占用。
    • MyISAM不支持事务和行级锁定,但支持表级锁定。
    • 它适用于读取密集型应用,例如数据仓库、日志分析等。
  3. MEMORY

    • MEMORY存储引擎将数据存储在内存中,因此读取和写入速度非常快。
    • 但是,它具有一定的限制,因为数据存储在内存中,所以对于大量数据的存储,可能会受到内存容量的限制。
    • MEMORY存储引擎适用于临时表、缓存和其他需要快速访问的临时数据的场景。
  4. CSV

    • CSV存储引擎以纯文本格式存储数据,以逗号分隔值(CSV)格式存储。
    • 它适用于处理大量数据导入和导出的场景。
    • CSV存储引擎不支持索引,也不支持事务。

除了上述存储引擎,MySQL还支持其他一些存储引擎,如Archive、Blackhole等。此外,MySQL还提供了插件式存储引擎架构,允许开发者根据需要自定义和扩展存储引擎。

在实际使用中,选择适合的存储引擎需要考虑多个因素,包括数据一致性要求、并发性能需求、存储空间占用、数据访问模式等。通过了解各个存储引擎的特点和适用场景,可以根据具体的业务需求来做出选择。

Mysql的事务隔离级别有了解过吗?

MySQL支持多个事务隔离级别,用于控制并发事务之间的隔离程度和数据一致性。

  1. 读未提交(Read Uncommitted)

    • 最低的隔离级别,事务可以看到其他事务尚未提交的修改。
    • 可能会导致脏读(Dirty Read),即读取到未提交的数据。
    • 不提供数据的一致性和隔离性保护。
  2. 读已提交(Read Committed)

    • 事务只能看到已经提交的其他事务所做的修改。
    • 避免了脏读,但仍可能出现不可重复读(Non-Repeatable Read)和幻读(Phantom Read)问题。
    • 不同的查询可能返回不同的结果,因为其他事务可能在事务执行过程中进行了提交。
  3. 可重复读(Repeatable Read)

    • 同一事务内的多个查询会看到一致的快照,即使其他事务进行了修改。
    • 确保了相同查询的一致性,避免了不可重复读问题。
    • 仍然可能出现幻读问题,即在同一事务中执行相同的查询,但返回的结果集不同,因为其他事务可能插入了新的数据。
  4. 串行化(Serializable)

    • 最高的隔离级别,确保了事务之间的完全隔离性。
    • 通过强制事务的顺序执行,避免了脏读、不可重复读和幻读问题。
    • 但是,由于串行执行的特性,可能会导致并发性能下降。

MySQL的默认隔离级别是可重复读(Repeatable Read),这是一个平衡了数据一致性和并发性能的级别。在实际应用中,可以根据具体需求选择合适的隔离级别,权衡数据一致性和并发性能的要求。

可以使用以下语句设置事务隔离级别:

SET TRANSACTION ISOLATION LEVEL <isolation_level>;

其中,<isolation_level>可以是上述提到的隔离级别之一。另外,也可以在连接字符串或配置文件中设置默认的隔离级别。

需要注意的是,更高的隔离级别通常会引入更多的锁定机制和开销,因此在选择隔离级别时,还需考虑系统的并发负载和性能要求。

Mysql中主键和唯一索引以及联合索引区别,分别有什么作用

MySQL中,主键、唯一索引和联合索引是常用的索引类型,用于提高查询效率和维护数据的完整性。它们在定义和作用上有一些区别。

  1. 主键(Primary Key)

    • 主键是用于唯一标识表中每一行数据的列或列组合。
    • 主键具有唯一性和非空性的特性,确保每行数据都具有唯一的标识,并且不允许为空。
    • 主键可以由单个列或多个列组成,但每个表只能有一个主键。
    • 主键索引在物理存储上是有序的,对于查找和连接操作非常高效。
    • 主键通常用于加速数据的唯一性验证和快速查找。
  2. 唯一索引(Unique Index)

    • 唯一索引是用于确保某列或列组合的值的唯一性。
    • 与主键不同,唯一索引允许空值,但对于非空值,每个值只能出现一次。
    • 表可以有多个唯一索引,但每个索引都必须唯一。
    • 唯一索引在查询时提供了较快的查找速度,同时保证了数据的一致性。
    • 唯一索引常用于避免重复数据的插入和快速查找重复值。
  3. 联合索引(Composite Index)

    • 联合索引是指在多个列上创建的索引,以支持多列的查询条件。
    • 联合索引可以包含两个或更多列,并按照定义的顺序进行排序。
    • 联合索引可以提高联合查询中的性能,减少数据的物理读取。
    • 查询条件中包含索引的第一个列时,索引将被充分利用。如果查询条件中涉及的列不在索引的前缀位置上,索引的效率可能降低。
    • 联合索引的列顺序非常重要,需要根据查询的特点和频率进行合理的设计。

总结来说,主键用于唯一标识每一行数据,并且是一种特殊的唯一索引。唯一索引用于确保列或列组合的唯一性,而联合索引用于优化多列查询的性能。在设计数据库表时,合理选择和使用这些索引类型,可以提高数据的完整性和查询的效率。

jvm中堆和栈还有方法区的作用是什么

Java虚拟机(JVM)中,堆(Heap)栈(Stack)方法区(Method Area)是三个重要的内存区域,分别用于不同的目的。

  1. 堆(Heap)

    • 堆是Java程序运行时动态分配内存的地方,用于存储对象实例和数组。
    • 所有通过关键字 "new" 创建的对象都存储在堆中。
    • 堆是一个共享的内存区域,在JVM启动时被创建,并被所有线程共享。
    • 堆的大小可以通过JVM参数进行配置。
    • 堆中的对象可通过垃圾回收(Garbage Collection)来进行自动内存管理。
  2. 栈(Stack)

    • 栈用于存储方法调用的信息、局部变量以及方法执行过程中的临时数据。
    • 每个线程在运行时都有一个对应的栈。
    • 栈采用先进后出(Last-In-First-Out)的方式管理数据。
    • 栈中的数据是线程私有的,线程之间不共享栈的数据。
    • 栈的大小在创建线程时被确定,并随着方法调用和返回的动态变化。
  3. 方法区(Method Area)

    • 方法区是用于存储类的结构信息、常量、静态变量、即时编译器编译后的代码等数据。
    • 方法区也被称为永久代(Permanent Generation),在较新的JVM版本中,永久代被移除,取而代之的是元空间(Metaspace)。
    • 方法区存储的数据在整个应用程序运行期间都存在,包括类的字节码、方法、字段、运行时常量池等信息。
    • 方法区的大小也可以通过JVM参数进行配置。

需要注意的是,堆、栈和方法区在内存分配和管理方面具有不同的特点和用途。堆用于存储动态分配的对象,栈用于存储方法调用和临时数据,方法区存储类相关的信息。

创建线程的方式有哪些

在Java中,有多种方式可以创建线程,以下是常用的创建线程的方式:

  1. 继承Thread类
    • 创建一个继承自Thread类的子类,重写run()方法来定义线程执行的逻辑。
    • 通过创建子类的实例对象,调用start()方法启动线程。
class MyThread extends Thread {
    public void run() {
        // 线程执行的逻辑
    }
}

// 创建并启动线程
MyThread myThread = new MyThread();
myThread.start();
  1. 实现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();
  1. 使用匿名类创建线程:
    • 可以使用匿名类的方式创建线程对象并重写`run()``方法。
Thread thread = new Thread() {
    public void run() {
        // 线程执行的逻辑
    }
};

// 启动线程
thread.start();
  1. 使用Lambda表达式创建线程:
    • 可以使用Lambda表达式简化线程的创建和定义。
Thread thread = new Thread(() -> {
    // 线程执行的逻辑
});

// 启动线程
thread.start();

需要注意的是,无论使用哪种方式创建线程,都需要调用线程对象的start()方法来启动线程。线程的运行会自动调用run()方法中定义的逻辑。

如何获取线程的返回值呢

在Java中,可以通过以下方式创建线程并获取返回值:

  1. 使用CallableFuture

    • 创建一个实现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();
    }
    
  2. 使用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();
    }
    

这两种方式都可以创建线程并获取返回值,具体选择哪种方式取决于具体的使用场景和需求。如果需要更多的线程控制和管理功能,CallableFuture结合使用是更好的选择。而CompletableFuture提供了更强大的异步编程能力和组合操作,适合处理更复杂的异步任务场景。

创建线程池的方式有哪些

在Java中,可以使用以下方式创建线程池:

  1. 使用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());
  1. 使用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());

需要根据具体需求选择合适的线程池类型和配置参数。线程池能够提供线程的复用、线程管理、线程调度等功能,可以有效控制并发线程的数量,提高程序的性能和资源利用率。

如果线程池的队列满了, 该怎么去处理

当线程池的任务队列已满时,可以根据具体需求选择以下几种处理方式:

  1. 调用线程池的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("任务队列已满,无法执行任务");
}
  1. 使用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是字符串的长度。它只需要对字符串进行一次遍历,并使用哈希集合来存储出现过的字符,以及窗口的边界来维护滑动窗口。

结束语

大家可以针对自己薄弱的地方进行复习, 然后多总结,形成自己的理解,不要去背~

本着把自己知道的都告诉大家,如果本文对您有所帮助,点赞+关注鼓励一下呗~

相关文章

项目源码(源码已更新 欢迎star⭐️)

往期设计模式相关文章

设计模式项目源码(源码已更新 欢迎star⭐️)

Kafka 专题学习

项目源码(源码已更新 欢迎star⭐️)

ElasticSearch 专题学习

项目源码(源码已更新 欢迎star⭐️)

往期并发编程内容推荐

推荐 SpringBoot & SpringCloud (源码已更新 欢迎star⭐️)

博客(阅读体验较佳)