去哪儿打响了互联网反卷的第一枪

共 10896字,需浏览 22分钟

 ·

2024-07-11 14:04

大家好,我是二哥呀。

7 月 9 日的时候,去哪儿 CEO 发布了全员信,说:兄弟们,从 7 月 15 号开始,每周三和周五,咱们去哪儿的兄弟姐妹们可以灵活选择办公地点,公司或者在家都可以。

图片来源于去哪儿官宣

并且不需要审批哦。这种混合办公的形式,我觉得挺好的,尤其是通勤时间比较久的小伙伴,真的能节省不少时间。就是可能要费点家里的电了,哈哈。

图片里的字比较小,我给大家再抽取一点关键信息:

  • 有的同学利用通勤的时间撸铁,瘦了 8 斤;
  • 有的同学为了线上开会,自学了沟通技巧;
  • 有的同学跨域通勤,早一天回家陪家人;
  • 有的同学在家里上班的样子成了孩子的榜样,成绩都上去了。

这。。。真好啊,如果这些都确定真的话,那驼厂的小伙伴可太幸福了啊。去哪儿也算是打响了互联网反卷的第一枪,棒👍

希望所有的互联网公司都能争相模仿起来,不仅混合办公,最好减少加班的时间,别 996 了,965 吧。哪怕是做做样子

向往自由办公的小伙伴估计看到这就忍不住了,想冲去哪儿了!尤其是 25 届秋招的小伙伴。

那今天我们就以《Java 面试指南》中收录的《去哪儿面经同学 1》 技术二面为例,来看看去哪儿面试官都喜欢问哪些问题,好做到知彼知己百战不殆。

让天下所有的面渣都能逆袭 😁

去哪儿同学一二面面经

项目的难点,为什么做这个项目

我在做 PmHub 这个项目时,印象最深刻的三个点分别是:

  • 1、将流程引擎丝滑地结合到项目管理业务中,需要系统的设计,我用到了 DDD 抽象模型,抽象工厂模式设计监听器等。
  • 2、当海量请求触发流程流转时,如何解决缓存穿透、缓存和数据库一致性问题?
  • 3、以及如何利用分布式事务保证任务审批状态一致性等。

从个人角度来说,我觉得现有的项目管理系统无论是禅道还是 TAPD,更多关注的是需求迭代和缺陷本身,对项目的资源管控以及流程引擎关注度不够,PmHub 的诞生就是希望打造一套智能的项目管理系统,核心是可以对项目资源录入、任务智能流转分配,未来还打算增加企业物料管理、供应商管理、采购管理、仓库管理等,我们的目标是开源一套完整的 CRM 系统,结合现在的 LLM,实现智能化的项目管理。

pmhub

java集合用过哪些——collection继承哪些接口

最经常用的就是封装了动态数组的 ArrayList 和封装了链表的 LinkedList;以及键值对 HashMap。

Collection 继承了 Iterable 接口,这意味着所有实现 Collection 接口的类都必须实现 iterator() 方法,之后就可以使用增强型 for 循环遍历集合中的元素了。

二哥的 Java 进阶之路:Collection源码

接口可以多继承吗

接口可以多继承,一个接口可以继承多个接口,使用逗号分隔。

interface InterfaceA {
    void methodA();
}

interface InterfaceB {
    void methodB();
}

interface InterfaceC extends InterfaceAInterfaceB {
    void methodC();
}

class MyClass implements InterfaceC {
    public void methodA() {
        System.out.println("Method A");
    }

    public void methodB() {
        System.out.println("Method B");
    }

    public void methodC() {
        System.out.println("Method C");
    }

    public static void main(String[] args) {
        MyClass myClass = new MyClass();
        myClass.methodA();
        myClass.methodB();
        myClass.methodC();
    }
}

在上面的例子中,InterfaceA 和 InterfaceB 是两个独立的接口。

InterfaceC 继承了 InterfaceA 和 InterfaceB,并且定义了自己的方法 methodC。

MyClass 实现了 InterfaceC,因此需要实现 InterfaceA 和 InterfaceB 中的方法 methodA 和 methodB,以及 InterfaceC 中的方法 methodC。

异常和错误的区别,受检异常和非受检异常,常用的有哪些,公共的父类是哪个

三分恶面渣逆袭:Java异常体系

Throwable 是 Java 语言中所有错误和异常的基类。它有两个主要的子类:Error 和 Exception,这两个类分别代表了 Java 异常处理体系中的两个分支。

Error 类代表那些严重的错误,这类错误通常是程序无法处理的。比如,OutOfMemoryError 表示内存不足,StackOverflowError 表示栈溢出。这些错误通常与 JVM 的运行状态有关,一旦发生,应用程序通常无法恢复。

Exception 类代表程序可以处理的异常。它分为两大类:编译时异常(Checked Exception)和运行时异常(Runtime Exception)。

①、编译时异常(Checked Exception):这类异常在编译时必须被显式处理(捕获或声明抛出)。

如果方法可能抛出某种编译时异常,但没有捕获它(try-catch)或没有在方法声明中用 throws 子句声明它,那么编译将不会通过。例如:IOException、SQLException 等。

②、运行时异常(Runtime Exception):这类异常在运行时抛出,它们都是 RuntimeException 的子类。对于运行时异常,Java 编译器不要求必须处理它们(即不需要捕获也不需要声明抛出)。

运行时异常通常是由程序逻辑错误导致的,如 NullPointerException、IndexOutOfBoundsException 等。

Object底层的数据结构(蒙了)

在 HotSpot 中,对象在堆内存中的存储布局可以划分为三个部分:对象头(Object Header)、实例数据(Instance Data)和对齐填充(Padding)。

三分恶面渣逆袭:对象的存储布局

①、对象头是每个对象都有的,包含三部分主要信息:

  • 标记字(Mark Word):包含了对象自身的运行时数据,如哈希码(HashCode)、垃圾回收分代年龄、锁状态标志、线程持有的锁、偏向线程 ID 等信息。在 64 位操作系统下占 8 个字节,32 位操作系统下占 4 个字节。
  • 类型指针(Class Pointer):指向对象所属类的元数据的指针,JVM 通过这个指针来确定对象的类。在开启了压缩指针的情况下,这个指针可以被压缩。在开启指针压缩的情况下占 4 个字节,否则占 8 个字节。
  • 数组长度(Array Length):如果对象是数组类型,还会有一个额外的数组长度字段。占 4 个字节。

②、实例数据存储了对象的具体信息,即在类中定义的各种字段数据(不包括由父类继承的字段)。这部分的大小取决于对象的属性和它们的类型(如 int、long、引用类型等)。JVM 会对这些数据进行对齐,以确保高效的访问速度。

③、对齐填充,为了使对象的总大小是 8 字节的倍数(这在大多数现代计算机体系结构中是最优访问边界),JVM 可能会在对象末尾添加一些填充。这部分是为了满足内存对齐的需求,并不包含任何具体的数据。

用过哪些设计模式,设计模式有哪些好处

比如说单例模式,在需要控制资源访问,如配置管理、连接池管理时经常使用单例模式。它确保了全局只有一个实例,并提供了一个全局访问点。

在有多种算法或策略可以切换使用的情况下,我会使用策略模式。像技术派实战项目中,我就使用策略模式对接了讯飞星火、OpenAI 等多家 API 服务,实现了一个可以自由切换 AI 服务的对话聊天服务。

技术派派聪明 AI 助手

这样就不用在代码中写 if/else 判断,而是将不同的 AI 服务封装成不同的策略类,通过工厂模式创建不同的 AI 服务实例,从而实现 AI 服务的动态切换。

后面想添加新的 AI 服务,只需要增加一个新的策略类,不需要修改原有代码,这样就提高了代码的可扩展性。

设计模式是软件工程中常用的解决特定问题的模版或者蓝图,可以帮助我们开发者以一种更加清晰、高效和可重用的方式来编写代码。

锁升级,synchronized底层,会不会牵扯到os层面

锁升级是 Java 虚拟机中的一个优化机制,用于提高多线程环境下 synchronized 的并发性能。锁升级涉及从较轻的锁状态(如无锁或偏向锁)逐步升级到较重的锁状态(如轻量级锁和重量级锁),以适应不同程度的竞争情况。

Java 对象头里的 Mark Word 会记录锁的状态,一共有四种状态:

①、无锁状态,在这个状态下,没有线程试图获取锁。

②、偏向锁,当第一个线程访问同步块时,锁会进入偏向模式。Mark Word 会被设置为偏向模式,并且存储了获取它的线程 ID。

偏向锁的目的是消除同一线程的后续锁获取和释放的开销。如果同一线程再次请求锁,就无需再次同步。

③、当有多个线程竞争锁,但没有锁竞争的强烈迹象(即线程交替执行同步块)时,偏向锁会升级为轻量级锁。

线程尝试通过CAS 操作(Compare-And-Swap)将对象头的 Mark Word 替换为指向锁记录的指针。如果成功,当前线程获取轻量级锁;如果失败,说明有竞争。

④、重量级锁,当锁竞争激烈时,轻量级锁会膨胀为重量级锁。

重量级锁通过将对象头的 Mark Word 指向监视器(Monitor)对象来实现,该对象包含了锁的持有者、锁的等待队列等信息。

三分恶面渣逆袭:Mark Word变化

synchronized 是 JVM 帮我们实现的,因此在使用的时候不用手动去 lock 和 unlock,JVM 会帮我们自动加锁和解锁。

①、synchronized 修饰代码块时,JVM 会通过 monitorentermonitorexit 两个指令来实现同步:

  • monitorenter 指向同步代码块的开始位置
  • monitorexit 指向同步代码块的结束位置。

使用 javap -c -s -v -l SynchronizedDemo.class 反编译一段 synchronized 代码块时,可以看到 monitorenter 和 monitorexit 指令。

三分恶面渣逆袭:monitorenter和monitorexit

②、synchronized 修饰方法时,JVM 会通过 ACC_SYNCHRONIZED 标记符来实现同步。

三分恶面渣逆袭:synchronized修饰同步方法

会,synchronized 升级为重量级锁时,依赖于操作系统的互斥量(mutex)来实现,mutex 用于保证任何给定时间内,只有一个线程可以执行某一段特定的代码段。

spring的容器、web容器、springmvc的容器之间的区别

Spring 容器是 Spring 框架的核心部分,负责管理应用程序中的对象生命周期和依赖注入。

Web 容器(也称 Servlet 容器),是用于运行 Java Web 应用程序的服务器环境,支持 Servlet、JSP 等 Web 组件。常见的 Web 容器包括 Apache Tomcat、Jetty等。

Spring MVC 是 Spring 框架的一部分,专门用于处理 Web 请求,基于 MVC(Model-View-Controller)设计模式。

springboot的好处(一面的问题又问了一遍)

Spring Boot 是一个开源的、用于简化 Spring 应用初始化和开发过程的框架。提供了一套默认配置,约定优于配置,来帮助我们快速搭建 Spring 项目骨架,极大地提高了我们的生产效率,再也不用为 Spring 的繁琐配置而烦恼了。

以前的 Spring 开发需要配置大量的 xml 文件,并且需要引入大量的第三方 jar 包,还需要手动放到 classpath 下。

SpringBoot图标

Spring Boot 的优点非常多,比如说:

  1. 通过 Intellij IDEA 或者官方的 Spring Initializr 就可以快速创建新项目,只需要选择需要的依赖就可以五分钟内搭建一个项目骨架。
  2. Spring Boot 内嵌了 Tomcat、Jetty、Undertow 等容器,不需要在服务器上部署 WAR 包了,直接运行 jar 包就可以启动项目,超级方便。
  3. Spring Boot 无需再像以前一样在 web.xml、applicationContext.xml 等配置文件里配置大量的内容,大部分初始工作 Spring Boot 都帮我们做好了。例如,如果项目中添加了 spring-boot-starter-web,Spring Boot 会自动配置 Tomcat 和 Spring MVC。
  4. Spring Boot 允许我们通过 yaml 来管理应用的配置,比传统的 properties 文件更加简洁。
  5. Spring Boot 提供了一系列的 Starter,可以快速集成常用的框架,例如 Spring Data JPA、Spring Security、MyBatis 等。
  6. Spring Boot 提供了一系列的 Actuator,可以帮助我们监控和管理应用,比如健康检查、审计、统计等。
  7. 配合 Spring Cloud 可以快速构建微服务架构。

mysql为什么用索引

数据库文件是存储在磁盘上的,磁盘 I/O 是数据库操作中最耗时的部分之一。没有索引时,数据库会进行全表扫描(Sequential Scan),这意味着它必须读取表中的每一行数据来查找匹配的行(时间效率为 O(n))。当表的数据量非常大时,就会导致大量的磁盘 I/O 操作。

有了索引,就可以直接跳到索引指示的数据位置,而不必扫描整张表,从而大大减少了磁盘 I/O 操作的次数。

MySQL 的 InnoDB 存储引擎默认使用 B+ 树来作为索引的数据结构,而 B+ 树的查询效率非常高,时间复杂度为 O(logN)。

索引文件相较于数据库文件,体积小得多,查到索引之后再映射到数据库记录,查询效率就会高很多。

索引就好像书的目录,通过目录去查找对应的章节内容会比一页一页的翻书快很多。

三分恶面渣逆袭:索引加快查询远离

事务的理解 事务的隔离属性

事务是一个或多个 SQL 语句组成的一个执行单元,这些 SQL 语句要么全部执行成功,要么全部不执行,不会出现部分执行的情况。事务是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。

事务的主要作用是保证数据库操作的一致性,即事务内的操作,要么全部成功,要么全部失败回滚,不会出现中间状态。这对于维护数据库的完整性和一致性非常重要。

事务具有四个基本特性,也就是通常所说的 ACID 特性,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。

三分恶面渣逆袭:事务四大特性

什么是原子性?

原子性子性意味着事务中的所有操作要么全部完成,要么全部不完成,它是不可分割的单位。如果事务中的任何一个操作失败了,整个事务都会回滚到事务开始之前的状态,如同这些操作从未被执行过一样。

什么是一致性?

一致性确保事务从一个一致的状态转换到另一个一致的状态。

比如在银行转账事务中,无论发生什么,转账前后两个账户的总金额应保持不变。假如 A 账户(100 块)给 B 账户(10 块)转了 10 块钱,不管成功与否,A 和 B 的总金额都是 110 块。

什么是隔离性?

隔离性意味着并发执行的事务是彼此隔离的,一个事务的执行不会被其他事务干扰。就是事务之间是井水不犯河水的。

隔离性主要是为了解决事务并发执行时可能出现的问题,如脏读、不可重复读、幻读等。

数据库系统通过事务隔离级别(如读未提交、读已提交、可重复读、串行化)来实现事务的隔离性。

什么是持久性?

持久性确保事务一旦提交,它对数据库所做的更改就是永久性的,即使发生系统崩溃,数据库也能恢复到最近一次提交的状态。通常,持久性是通过数据库的恢复和日志机制来实现的,确保提交的事务更改不会丢失。

redis内存淘汰和过期策略

当 Redis 内存使用达到设置的最大值时(通过 maxmemory 参数设置),它会根据配置的内存淘汰策略来决定如何处理新的写请求。

三分恶面渣逆袭:Redis六种内存溢出控制策略

我先说三种淘汰策略:

  1. noeviction:默认策略,不进行任何数据淘汰,直接返回错误信息。适用于不能丢失数据的场景。
  2. volatile-lru:从设置了过期时间的键中,使用 LRU 算法淘汰最不常用的键。
  3. allkeys-lru:从所有键中,使用 LRU(最近最少使用)算法淘汰最不常用的键。适用于缓存场景,优先保留最近使用的数据。

Redis 支持为键设置过期时间,当键的过期时间到达后,Redis 会自动删除这些键。过期回收策略主要有两种:惰性删除和定期删除。

二哥的 Java 进阶之路:Redis 的过期淘汰策略

什么是惰性删除?

当某个键被访问时,如果发现它已经过期,Redis 会立即删除该键。这意味着如果一个已过期的键从未被访问,它不会被自动删除,可能会占用额外的内存。

什么是定期删除?

Redis 会定期随机测试一些键,并删除其中已过期的键。这个过程是 Redis 内部自动执行的,旨在减少过期键对内存的占用。

redis和本地缓存的区别,哪个效率高

Redis 可以部署在多个节点上,支持数据分片,适用于跨服务器的缓存共享。而本地缓存只能在单个服务器上使用。

Redis 还可以持久化数据,支持数据备份和恢复,适用于对数据安全性要求较高的场景。并且支持发布/订阅、事务、Lua 脚本等高级功能。

效率上,Redis 和本地缓存都是存储在内存中,读写速度都非常快。

JMM模型

Java 内存模型(Java Memory Model)是一种抽象的模型,简称 JMM,主要用来定义多线程中变量的访问规则,用来解决变量的可见性、有序性和原子性问题,确保在并发环境中安全地访问共享变量。

深入浅出 Java 多线程:Java内存模型

JMM 定义了线程内存和主内存之间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory),本地内存中存储了共享变量的副本,用来进行线程内部的读写操作。

  • 当一个线程更改了本地内存中共享变量的副本后,它需要将这些更改刷新到主内存中,以确保其他线程可以看到这些更改。
  • 当一个线程需要读取共享变量时,它可能首先从本地内存中读取。如果本地内存中的副本是过时的,线程将从主内存中重新加载共享变量的最新值到本地内存中。

内容来源

  • 星球嘉宾三分恶的面渣逆袭:https://javabetter.cn/sidebar/sanfene/nixi.html
  • 二哥的 Java 进阶之路(GitHub 已有 12000+star):https://javabetter.cn

ending

一个人可以走得很快,但一群人才能走得更远。二哥的编程星球已经有 5700 多名球友加入了,如果你也需要一个良好的学习环境,戳链接 🔗 加入我们吧。这是一个编程学习指南 + Java 项目实战 + LeetCode 刷题的私密圈子,你可以阅读星球专栏、向二哥提问、帮你制定学习计划、和球友一起打卡成长。

两个置顶帖「球友必看」和「知识图谱」里已经沉淀了非常多优质的学习资源,相信能帮助你走的更快、更稳、更远

欢迎点击左下角阅读原文了解二哥的编程星球,这可能是你学习求职路上最有含金量的一次点击。

最后,把二哥的座右铭送给大家:没有什么使我停留——除了目的,纵然岸旁有玫瑰、有绿荫、有宁静的港湾,我是不系之舟。共勉 💪。

浏览 2596
14点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
14点赞
评论
收藏
分享

手机扫一扫分享

分享
举报