前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >小公司工作 6 年,后面怎么走?

小公司工作 6 年,后面怎么走?

作者头像
沉默王二
发布2024-04-26 16:24:23
1100
发布2024-04-26 16:24:23
举报
文章被收录于专栏:沉默王二沉默王二

Java 基础(详细)

==和equals()有什么区别?

在 Java 中,== 操作符和 equals() 方法用于比较两个对象:

①、==:用于比较两个对象的引用,即它们是否指向同一个对象实例。

如果两个变量引用同一个对象实例,== 返回 true,否则返回 false

对于基本数据类型(如 int, double, char 等),== 比较的是值是否相等。

②、equals() 方法:用于比较两个对象的内容是否相等。默认情况下,equals() 方法的行为与 == 相同,即比较对象引用,如在超类 Object 中:

代码语言:javascript
复制
public boolean equals(Object obj) {
    return (this == obj);
}

然而,equals() 方法通常被各种类重写。例如,String 类重写了 equals() 方法,以便它可以比较两个字符串的字符内容是否完全一样。

二哥的 Java 进阶之路,String的equals()源码

举个例子:

代码语言:javascript
复制
String a = new String("沉默王二");
String b = new String("沉默王二");

// 使用 == 比较
System.out.println(a == b); // 输出 false,因为 a 和 b 引用不同的对象

// 使用 equals() 比较
System.out.println(a.equals(b)); // 输出 true,因为 a 和 b 的内容相同

String变量直接赋值和构造方法赋值==比较相等吗?

直接使用双引号为字符串变量赋值时,Java 首先会检查字符串常量池中是否已经存在相同内容的字符串。

如果存在,Java 就会让新的变量引用池中的那个字符串;如果不存在,它会创建一个新的字符串,放入池中,并让变量引用它。

使用 new String("abc") 的方式创建字符串时,实际分为两步:

  • 第一步,先检查字符串字面量 "abc" 是否在字符串常量池中,如果没有则创建一个;如果已经存在,则引用它。
  • 第二步,在堆中再创建一个新的字符串对象,并将其初始化为字符串常量池中 "abc" 的一个副本。

三分恶面渣逆袭:堆与常量池中的String

也就是说:

代码语言:javascript
复制
String s1 = "沉默王二";
String s2 = "沉默王二";
String s3 = new String("沉默王二");

System.out.println(s1 == s2); // 输出 true,因为 s1 和 s2 引用的是字符串常量池中同一个对象。
System.out.println(s1 == s3); // 输出 false,因为 s3 是通过 new 关键字显式创建的,指向堆上不同的对象。

String都有哪些常用方法?

有很多,比如说前面提到到的 equals,hashCode,substring,indexOf 等等。

抽象类和接口有什么区别?

一个类只能继承一个抽象类;但一个类可以实现多个接口。所以我们在新建线程类的时候一般推荐使用实现 Runnable 接口的方式,这样线程类还可以继承其他类,而不单单是 Thread 类。

抽象类符合 is-a 的关系,而接口更像是 has-a 的关系,比如说一个类可以序列化的时候,它只需要实现 Serializable 接口就可以了,不需要去继承一个序列化类。

Java容器有哪些?List、Set还有Map的区别?

Java 集合框架可以分为两条大的支线:

①、Collection,主要由 List、Set、Queue 组成:

  • List 代表有序、可重复的集合,典型代表就是封装了动态数组的 ArrayList 和封装了链表的 LinkedList;
  • Set 代表无序、不可重复的集合,典型代表就是 HashSet 和 TreeSet;
  • Queue 代表队列,典型代表就是双端队列 ArrayDeque,以及优先级队列 PriorityQueue。

②、Map,代表键值对的集合,典型代表就是 HashMap。

二哥的 Java 进阶之路:Java集合主要关系

线程创建的方式?Runable和Callable有什么区别?

Java 中创建线程主要有三种方式,分别为继承 Thread 类、实现 Runnable 接口、实现 Callable 接口。

第一种,继承 Thread 类,重写 run()方法,调用 start()方法启动线程。

代码语言:javascript
复制
class ThreadTask extends Thread {
    public void run() {
        System.out.println("看完二哥的 Java 进阶之路,上岸了!");
    }

    public static void main(String[] args) {
        ThreadTask task = new ThreadTask();
        task.start();
    }
}

这种方法的缺点是,由于 Java 不支持多重继承,所以如果类已经继承了另一个类,就不能使用这种方法了。

第二种,实现 Runnable 接口,重写 run() 方法,然后创建 Thread 对象,将 Runnable 对象作为参数传递给 Thread 对象,调用 start() 方法启动线程。

代码语言:javascript
复制
class RunnableTask implements Runnable {
    public void run() {
        System.out.println("看完二哥的 Java 进阶之路,上岸了!");
    }

    public static void main(String[] args) {
        RunnableTask task = new RunnableTask();
        Thread thread = new Thread(task);
        thread.start();
    }
}

这种方法的优点是可以避免 Java 的单继承限制,并且更符合面向对象的编程思想,因为 Runnable 接口将任务代码和线程控制的代码解耦了。

第三种,实现 Callable 接口,重写 call() 方法,然后创建 FutureTask 对象,参数为 Callable 对象;紧接着创建 Thread 对象,参数为 FutureTask 对象,调用 start() 方法启动线程。

代码语言:javascript
复制
class CallableTask implements Callable<String> {
    public String call() {
        return "看完二哥的 Java 进阶之路,上岸了!";
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CallableTask task = new CallableTask();
        FutureTask<String> futureTask = new FutureTask<>(task);
        Thread thread = new Thread(futureTask);
        thread.start();
        System.out.println(futureTask.get());
    }
}

这种方法的优点是可以获取线程的执行结果。

启动一个线程是run()还是start()?

在 Java 中,启动一个新的线程应该调用其start()方法,而不是直接调用run()方法。

当调用start()方法时,会启动一个新的线程,并让这个新线程调用run()方法。这样,run()方法就在新的线程中运行,从而实现多线程并发。

代码语言:javascript
复制
class MyThread extends Thread {
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.start(); // 正确的方式,创建一个新线程,并在新线程中执行 run()
        t1.run(); // 仅在主线程中执行 run(),没有创建新线程
    }
}

如果直接调用run()方法,那么run()方法就在当前线程中运行,没有新的线程被创建,也就没有实现多线程的效果。

来看输出结果:

代码语言:javascript
复制
main
Thread-0

也就是说,start() 方法的调用会告诉 JVM 准备好所有必要的新线程结构,分配其所需资源,并调用线程的 run() 方法在这个新线程中执行。

三分恶面渣逆袭:start方法

Spring 全家桶

介绍Spring IoC 和 AOP?

所谓的IoC(控制反转,Inversion of Control),就是由容器来控制对象的生命周期和对象之间的关系。以前是我们想要什么就自己创建什么,现在是我们需要什么容器就帮我们送来什么。

引入IoC之前和引入IoC之后

也就是说,控制对象生命周期的不再是引用它的对象,而是容器,这就叫控制反转

三分恶面渣逆袭:控制反转示意图

talk is cheap,show me the code,我们来看一个例子,没有 IoC 之前:

代码语言:javascript
复制
我需要一个女朋友,刚好大街上突然看到了一个小姐姐,人很好看,于是我就自己主动上去搭讪,要她的微信号,找机会聊天关心她,然后约她出来吃饭,打听她的爱好,三观。。。

有了 IoC 之后:

代码语言:javascript
复制
我需要一个女朋友,于是我就去找婚介所,告诉婚介所,我需要一个长的像赵露思的,会打 Dota2 的,于是婚介所在它的人才库里开始找,找不到它就直接说没有,找到它就直接介绍给我。

婚介所就相当于一个 IoC 容器,我就是一个对象,我需要的女朋友就是另一个对象,我不用关心女朋友是怎么来的,我只需要告诉婚介所我需要什么样的女朋友,婚介所就帮我去找。

Spring 倡导的开发方式就是这样,所有的类创建都通过 Spring 容器来,不再是开发者去 new,去 = null 销毁,这些创建和销毁的工作都交给 Spring 容器来。

于是,对于某个对象来说,以前是它控制它依赖的对象,现在是所有对象都被 Spring 控制,这就是控制反转

图片来源于网络

AOP,也就是 Aspect-oriented Programming,译为面向切面编程。

简单点说,就是把一些业务逻辑中的相同代码抽取到一个独立的模块中,让业务逻辑更加清爽。

三分恶面渣逆袭:横向抽取

举个例子,假如我们现在需要在业务代码开始前进行参数校验,在结束后打印日志,该怎么办呢?

我们可以把日志记录数据校验这两个功能抽取出来,形成一个切面,然后在业务代码中引入这个切面,这样就可以实现业务逻辑和通用逻辑的分离。

三分恶面渣逆袭:AOP应用示例

业务代码不再关心这些通用逻辑,只需要关心自己的业务实现,这样就实现了业务逻辑和通用逻辑的分离。

我们来回顾一下 Java 语言的执行过程:

三分恶面渣逆袭:Java 执行过程

AOP 的核心是动态代理,可以使用 JDK 动态代理来实现,也可以使用 CGLIB 来实现。

Spring框架使用到的设计模式?

三分恶面渣逆袭:Spring中用到的设计模式

Spring 框架中用了蛮多设计模式的:

①、工厂模式:IoC 容器本身可以看作是一个巨大的工厂,负责创建和管理 Bean 的生命周期和依赖关系。

像 BeanFactory 和 ApplicationContext 接口都提供了工厂模式的实现,负责实例化、配置和组装 Bean。

②、代理模式:AOP 的实现就是基于代理模式的,如果配置了事务管理,Spring 会使用代理模式创建一个连接数据库的代理对象,来进行事务管理。

③、单例模式:Spring 容器中的 Bean 默认都是单例的,这样可以保证 Bean 的唯一性,减少系统开销。

④、模板模式:Spring 中的 JdbcTemplate,HibernateTemplate 等以 Template 结尾的类,都使用了模板方法模式。

比如,我们使用 JdbcTemplate,只需要提供 SQL 语句和需要的参数就可以了,至于如何创建连接、执行 SQL、处理结果集等都由 JdbcTemplate 这个模板方法来完成。

④、观察者模式:Spring 事件驱动模型就是观察者模式很经典的一个应用,Spring 中的 ApplicationListener 就是观察者,当有事件(ApplicationEvent)被发布,ApplicationListener 就能接收到信息。

⑤、适配器模式:Spring MVC 中的 HandlerAdapter 就用了适配器模式。它允许 DispatcherServlet 通过统一的适配器接口与多种类型的请求处理器进行交互。

MyBatis

Mybatis#()和$()有什么区别?

在 MyBatis 中,#{} 和 {} 是两种不同的占位符,#{} 是预编译处理,{} 是字符串替换。

三分恶面渣逆袭:#{}和${}比较

①、当使用 #{} 时,MyBatis 会在 SQL 执行之前,将占位符替换为问号 ?,并使用参数值来替代这些问号。

由于 #{} 使用了预处理,它能有效防止 SQL 注入,可以确保参数值在到达数据库之前被正确地处理和转义。

代码语言:javascript
复制
<select id="selectUser" resultType="User">
  SELECT * FROM users WHERE id = #{id}
</select>

②、当使用 ${} 时,参数的值会直接替换到 SQL 语句中去,而不会经过预处理。

这就存在 SQL 注入的风险,因为参数值会直接拼接到 SQL 语句中,假如参数值是 1 or 1=1,那么 SQL 语句就会变成 SELECT * FROM users WHERE id = 1 or 1=1,这样就会导致查询所有用户的结果。

${} 通常用于那些不能使用预处理的场合,比如说动态表名、列名、排序等,要提前对参数进行安全性校验。

代码语言:javascript
复制
<select id="selectUsersByOrder" resultType="User">
  SELECT * FROM users ORDER BY ${columnName} ASC
</select>

MySQL

MySQL的四个隔离级别以及默认隔离级别?

事务的隔离级别定了一个事务可能受其他事务影响的程度,MySQL 支持的四种隔离级别分别是:读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。

三分恶面渣逆袭:事务的四个隔离级别

什么是读未提交?

读未提交是最低的隔离级别,在这个级别,当前事务可以读取未被其他事务提交的数据,以至于会出现“脏读”、“不可重复读”和“幻读”的问题。

什么是读已提交?

当前事务只能读取已经被其他事务提交的数据,可以避免“脏读”现象。但不可重复读和幻读问题仍然存在。

什么是可重复读?

确保在同一事务中多次读取相同记录的结果是一致的,即使其他事务对这条记录进行了修改,也不会影响到当前事务。

是 MySQL 默认的隔离级别,避免了“脏读”和“不可重复读”,也在很大程度上减少了“幻读”问题。

什么是串行化?

最高的隔离级别,通过强制事务串行执行来避免并发问题,可以解决“脏读”、“不可重复读”和“幻读”问题。

但会导致大量的超时和锁竞争问题。

A事务未提交,B事务上查询到的是旧值还是新值?

在 MySQL 的默认隔离级别(可重复读)下,如果事务 A 修改了数据但未提交,事务 B 将看到修改之前的数据。

这是因为在可重复读隔离级别下,MySQL 将通过多版本并发控制(MVCC)机制来保证一个事务不会看到其他事务未提交的数据,从而确保读一致性。

编写SQL语句哪些情况会导致索引失效?

  • 在索引列上使用函数或表达式:如果在查询中对索引列使用了函数或表达式,那么索引可能无法使用,因为数据库无法预先计算出函数或表达式的结果。例如:SELECT * FROM table WHERE YEAR(date_column) = 2021
  • 使用不等于(<>)或者 NOT 操作符:这些操作符通常会使索引失效,因为它们会扫描全表。
  • 使用 LIKE 操作符,但是通配符在最前面:如果 LIKE 的模式串是以“%”或者“_”开头的,那么索引也无法使用。例如:SELECT * FROM table WHERE column LIKE '%abc'
  • OR 操作符:如果查询条件中使用了 OR,并且 OR 两边的条件分别涉及不同的索引,那么这些索引可能都无法使用。
  • 如果 MySQL 估计使用全表扫描比使用索引更快时(通常是小表或者大部分行都满足 WHERE 子句),也不会使用索引。
  • 联合索引不满足最左前缀原则时,索引会失效。

说说 SQL 的隐式数据类型转换?

在 SQL 中,当不同数据类型的值进行运算或比较时,会发生隐式数据类型转换。

比如说,当一个整数和一个浮点数相加时,整数会被转换为浮点数,然后再进行相加。

代码语言:javascript
复制
SELECT 1 + 1.0; -- 结果为 2.0

比如说,当一个字符串和一个整数相加时,字符串会被转换为整数,然后再进行相加。

代码语言:javascript
复制
SELECT '1' + 1; -- 结果为 2

数据类型隐式转换会导致意想不到的结果,所以要尽量避免隐式转换。

二哥的 Java 进阶之路

可以通过显式转换来规避这种情况。

代码语言:javascript
复制
SELECT CAST('1' AS SIGNED INTEGER) + 1; -- 结果为 2

了解的MVCC吗?

MVCC 是多版本并发控制(Multi-Version Concurrency Control)的简称,主要用来解决数据库并发问题。

在支持 MVCC 的数据库中,当多个用户同时访问数据时,每个用户都可以看到一个在某一时间点之前的数据库快照,并且能够无阻塞地执行查询和修改操作,而不会相互干扰。

在传统的锁机制中,如果一个事务正在写数据,那么其他事务必须等待写事务完成才能读数据,MVCC 允许读操作访问数据的一个旧版本快照,同时写操作创建一个新的版本,这样读写操作就可以并行进行,不必等待对方完成。

在 MySQL 中,特别是 InnoDB 存储引擎,MVCC 是通过版本链和 ReadView 机制来实现的。

什么是版本链?

在 InnoDB 中,每一行数据都有两个隐藏的列:一个是 DB_TRX_ID,另一个是 DB_ROLL_PTR。

  • DB_TRX_ID,保存创建这个版本的事务 ID。
  • DB_ROLL_PTR,指向 undo 日志记录的指针,这个记录包含了该行的前一个版本的信息。通过这个指针,可以访问到该行数据的历史版本。

假设有一张hero表,表中有一行记录 name 为张三,city 为帝都,插入这行记录的事务 id 是 80。此时,DB_TRX_ID的值就是 80,DB_ROLL_PTR的值就是指向这条 insert undo 日志的指针。

三分恶面渣逆袭:表记录

接下来,如果有两个DB_TRX_ID分别为100200的事务对这条记录进行了update操作,那么这条记录的版本链就会变成下面这样:

三分恶面渣逆袭:update 操作

当事务更新一行数据时,InnoDB 不会直接覆盖原有数据,而是创建一个新的数据版本,并更新 DB_TRX_ID 和 DB_ROLL_PTR,使得它们指向前一个版本和相关的 undo 日志。这样,老版本的数据不会丢失,可以通过版本链找到。

由于 undo 日志会记录每一次的 update,并且新插入的行数据会记录上一条 undo 日志的指针,所以可以通过这个指针找到上一条记录,这样就形成了一个版本链。

三分恶面渣逆袭:版本链

说说什么是 ReadView?

ReadView(读视图)是 InnoDB 为了实现一致性读(Consistent Read)而创建的数据结构,它用于确定在特定事务中哪些版本的行记录是可见的。

ReadView 主要用来处理隔离级别为"可重复读"(REPEATABLE READ)和"读已提交"(READ COMMITTED)的情况。因为在这两个隔离级别下,事务在读取数据时,需要保证读取到的数据是一致的,即读取到的数据是在事务开始时的一个快照。

当事务开始执行时,InnoDB 会为该事务创建一个 ReadView,这个 ReadView 会记录 4 个重要的信息:

  • creator_trx_id:创建该 ReadView 的事务 ID。
  • m_ids:所有活跃事务的 ID 列表,活跃事务是指那些已经开始但尚未提交的事务。
  • min_trx_id:所有活跃事务中最小的事务 ID。它是 m_ids 数组中最小的事务 ID。
  • max_trx_id :事务 ID 的最大值加一。换句话说,它是下一个将要生成的事务 ID。

乐观锁和悲观锁,库存的超卖问题的原因和解决方案?

①、乐观锁

乐观锁基于这样的假设:冲突在系统中出现的频率较低,因此在数据库事务执行过程中,不会频繁地去锁定资源。相反,它在提交更新的时候才检查是否有其他事务已经修改了数据。

可以通过在数据表中使用版本号(Version)或时间戳(Timestamp)来实现,每次读取记录时,同时获取版本号或时间戳,更新时检查版本号或时间戳是否发生变化。

如果没有变化,则执行更新并增加版本号或更新时间戳;如果检测到冲突(即版本号或时间戳与之前读取的不同),则拒绝更新。

②、悲观锁

悲观锁假设冲突是常见的,因此在数据处理过程中,它会主动锁定数据,防止其他事务进行修改。

可以直接使用数据库的锁机制,如行锁或表锁,来锁定被访问的数据。常见的实现是 SELECT FOR UPDATE 语句,它在读取数据时就加上了锁,直到当前事务提交或回滚后才释放。

如何解决库存超卖问题?

按照乐观锁的方式:

代码语言:javascript
复制
UPDATE inventory SET count = count - 1, version = version + 1 WHERE product_id = 1 AND version = current_version;

按照悲观锁的方式:

在事务开始时直接锁定库存记录,直到事务结束。

代码语言:javascript
复制
START TRANSACTION;
SELECT * FROM inventory WHERE product_id = 1 FOR UPDATE;
UPDATE inventory SET count = count - 1 WHERE product_id = 1;
COMMIT;

Redis

Redisson的底层原理?以及与SETNX的区别?

图片来源于网络

Redisson 是一个基于 Redis 的 Java 驻内存数据网格(In-Memory Data Grid),提供了一系列 API 用来操作 Redis,其中最常用的功能就是分布式锁。

代码语言:javascript
复制
RLock lock = redisson.getLock("lock");
lock.lock();
try {
    // do something
} finally {
    lock.unlock();
}

普通锁的实现源码是在 RedissonLock 类中,也是通过 Lua 脚本封装一些 Redis 命令来实现的的,比如说 tryLockInnerAsync 源码:

二哥的 Java 进阶之路:RedissonLock

其中 hincrby 命令用于对哈希表中的字段值执行自增操作,pexpire 命令用于设置键的过期时间。比 SETNX 更优雅。

Redis的持久化方式?RDB和AOF的区别?

Redis 支持两种主要的持久化方式:RDB(Redis DataBase)持久化和 AOF(Append Only File)持久化。这两种方式可以单独使用,也可以同时使用。

三分恶面渣逆袭:Redis持久化的两种方式

RDB 是一个非常紧凑的单文件(二进制文件 dump.rdb),代表了 Redis 在某个时间点上的数据快照。非常适合用于备份数据,比如在夜间进行备份,然后将 RDB 文件复制到远程服务器。但可能会丢失最后一次持久化后的数据。

AOF 的最大优点是灵活,实时性好,可以设置不同的 fsync 策略,如每秒同步一次,每次写入命令就同步,或者完全由操作系统来决定何时同步。但 AOF 文件往往比较大,恢复速度慢,因为它记录了每个写操作。

Redis宕机哪种恢复的比较快?

在 Redis 4.0 版本中,混合持久化模式会在 AOF 重写的时候同时生成一份 RDB 快照,然后将这份快照作为 AOF 文件的一部分,最后再附加新的写入命令。

三分恶面渣逆袭:混合持久化

这样,当需要恢复数据时,Redis 先加载 RDB 文件来恢复到快照时刻的状态,然后应用 RDB 之后记录的 AOF 命令来恢复之后的数据更改,既快又可靠。

参考链接

  • 三分恶的面渣逆袭:https://javabetter.cn/sidebar/sanfene/nixi.html
  • 二哥的 Java 进阶之路:https://javabetter.cn
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2024-04-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 沉默王二 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Java 基础(详细)
    • ==和equals()有什么区别?
      • String变量直接赋值和构造方法赋值==比较相等吗?
        • String都有哪些常用方法?
          • 抽象类和接口有什么区别?
            • Java容器有哪些?List、Set还有Map的区别?
              • 线程创建的方式?Runable和Callable有什么区别?
                • 启动一个线程是run()还是start()?
                • Spring 全家桶
                  • 介绍Spring IoC 和 AOP?
                    • Spring框架使用到的设计模式?
                    • MyBatis
                      • Mybatis#()和$()有什么区别?
                      • MySQL
                        • MySQL的四个隔离级别以及默认隔离级别?
                          • 什么是读未提交?
                          • 什么是读已提交?
                          • 什么是可重复读?
                          • 什么是串行化?
                        • A事务未提交,B事务上查询到的是旧值还是新值?
                          • 编写SQL语句哪些情况会导致索引失效?
                            • 说说 SQL 的隐式数据类型转换?
                              • 了解的MVCC吗?
                                • 什么是版本链?
                                • 说说什么是 ReadView?
                              • 乐观锁和悲观锁,库存的超卖问题的原因和解决方案?
                                • 如何解决库存超卖问题?
                            • Redis
                              • Redisson的底层原理?以及与SETNX的区别?
                                • Redis的持久化方式?RDB和AOF的区别?
                                  • Redis宕机哪种恢复的比较快?
                                  • 参考链接
                                  相关产品与服务
                                  云数据库 Redis
                                  腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
                                  领券
                                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档