大家好,我是小林。
春节过的很快,是时候收拾好心情了!春招会在这个月陆陆续续开始了,等到 3 月份的时候,就会有大量的公司开启春招了, 社招有金三银四,25 届校招有春季校园招聘,26 届有春招实习的招聘,想要备战春招的同学,得恢复学习的状态了。
Java 技术面试主要是考察三大方向:八股+项目+算法
- Java八股:Java基础、JUC、JVM、SSM、MySQL、Redis、消息队列、操作系统、计算机网络、微服务等基础知识,这些都是互联网公司喜欢考察的知识点,大家可以根据面经,去抓住这些技术专项里哪些是热点题目,以面试作为驱动力 ,高效地复习八股文,击破热点题目,大家可能会遇到两个问题:一个是感觉到很慌,认为自己什么都不会,看一个面经慌一次;另一个是感觉到很枯燥但又不敢不看。不管是哪一个问题,都需要静下心来,戒骄戒躁,迅速调整心态,不要乱了阵脚,那么面试八股这一关就没什么问题了。
- 项目:准备的项目一定要是自己非常熟悉的项目,起码写到简历当中的功能点能够经得起面试官的盘问。一般面试官也不会问的特别复杂,只要准备充分,都是可以回答上来的,比如最常问的几个项目问题:「你项目最大的难点是什么?怎么解决的?」、「你项目的架构是怎么样的?」等等,不熟悉的功能点最好不要写,不打无准备之仗。准备的项目最好是两个以上,一个可以为Web前后端项目,另一个可以为框架开发、中间件开发。这样一方面可以体现你业务能力okay,熟悉常见的开发场景,当mentor或leader派活的时候,你知道如何下手去做,有自己的实现思路;另一方面可以体现你有一定的钻研自学能力与解决问题能力,能够啃动硬骨头。
- 算法:对于算法题准备,没有任何捷径除非天赋加持,刷就完了,时间长且充裕的刷《代码随想录]》就够应对绝大多数互联网公司的算法手撕了,时间短且紧的刷 leetcode Top100 也可以,看题10分钟没思路的话就直接看题解,重复的刷题,反复不断地刻意训练,直到背过为止。
之前带大家拆解过阿里巴巴的淘宝、蚂蚁金服、饿了么、阿里云的岗位,这次我们来看看阿里旗下的高德地图的!
先来看看高德地图 25 届开发岗的校招薪资,目前收集到的信息不多,就下面两位同学,不过阿里系旗下的集团校招薪资都差距不大,所以可以用淘宝、阿里云等阿里集团开奖的情况来作为参考,那么第一个同学估计 sp offer、第二同学估计是 ssp offer。
- 26k x 16 = 41.6w,同学背景硕士 211,工作地点北京
- 30k x 16 + 6w 签字费 = 54w(第一年的年包),同学背景硕士 985,工作地点北京
那么接下来,咱们来看看阿里高德地图的 Java 实习二面面经,算是比较简单和基础的,就考察了这几个方面:Java基础、Java 并发、JVM、Linux 命令、SQL。
虽然简单,但是还是很经典的问题,可以作为一个快速考察自己的基础的面经,大家自己对照看看,如果是你面临这场面试,是否能够流畅的口述出来。
高德二面八股
Java基础类型有哪些?
Java支持数据类型分为两类: 基本数据类型和引用数据类型。
基本数据类型共有8种,可以分为三类:
- 数值型:整数类型(byte、short、int、long)和浮点类型(float、double)
- 字符型:char
- 布尔型:boolean
8种基本数据类型的默认值、位数、取值范围,如下表所示:
Float和Double的最小值和最大值都是以科学记数法的形式输出的,结尾的"E+数字"表示E之前的数字要乘以10的多少倍。比如3.14E3
就是3.14×1000=3140
,3.14E-3
就是3.14/1000=0.00314
。
注意一下几点:
- Java八种基本数据类型的字节数:1字节(byte、boolean)、 2字节(short、char)、4字节(int、float)、8字节(long、double)
- 浮点数的默认类型为double(如果需要声明一个常量为float型,则必须要在末尾加上f或F)
- 整数的默认类型为int(声明Long型在末尾加上l或者L)
- 八种基本数据类型的包装类:除了char的是Character、int类型的是Integer,其他都是首字母大写
- char类型是无符号的,不能为负,所以是0开始的
Java常用数据结构有哪些?
List是有序的Collection,使用此接口能够精确的控制每个元素的插入位置,用户能根据索引访问List中元素。常用的实现List的类有LinkedList,ArrayList,Vector,Stack。
- ArrayList 是容量可变的非线程安全列表,其底层使用数组实现。当发生扩容时,会创建更大的数组,并把原数组复制到新数组。ArrayList支持对元素的快速随机访问,但插入与删除速度很慢。
- LinkedList本质是一个双向链表,与ArrayList相比,其插入和删除速度更快,但随机访问速度更慢。
- Vector 与 ArrayList 类似,底层也是基于数组实现,特点是线程安全,但效率相对较低,因为其方法大多被 synchronized 修饰
Map 是一个键值对集合,存储键、值和之间的映射。Key 无序,唯一;value 不要求有序,允许重复。Map 没有继承于 Collection 接口,从 Map 集合中检索元素时,只要给出键对象,就会返回对应的值对象。主要实现有TreeMap、HashMap、HashTable、LinkedHashMap、ConcurrentHashMap
- HashMap:JDK1.8 之前 HashMap 由数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的("拉链法"解决冲突),JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)时,将链表转化为红黑树,以减少搜索时间
- LinkedHashMap:LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。
- HashTable:数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的,HashTable 是线程安全的,其方法使用 synchronized 修饰,且不允许 null 键和 null 值。
- TreeMap:红黑树(自平衡的排序二叉树)实现,Key 按自然顺序或 Comparator 排序。
- ConcurrentHashMap:采用 Node 数组 + 链表 + 红黑树实现,是线程安全的。JDK1.8 以前使用 Segment 锁(分段锁),JDK1.8 以后使用 volatile + CAS 或者 synchronized 保证线程安全。其中,Segment 锁是将整个数据结构分成多个段,每个段有独立的锁,不同段的操作可以并发进行;volatile 保证变量的可见性,CAS 是一种无锁算法,synchronized 用于对链表或红黑树进行加锁操作。
Set不允许存在重复的元素,与List不同,Set中的元素是无序的(TreeSet 除外)。常用的实现有HashSet,LinkedHashSet和TreeSet。
- HashSet通过HashMap实现,HashMap的Key即HashSet存储的元素,所有Key都是用相同的Value,一个名为PRESENT的Object类型常量。使用Key保证元素唯一性,但不保证有序性。由于HashSet是HashMap实现的,因此线程不安全。
- LinkedHashSet继承自HashSet,通过LinkedHashMap实现,使用双向链表维护元素插入顺序。
- TreeSet通过TreeMap实现的,添加元素到集合时按照比较规则将其插入合适的位置,保证插入后的集合仍然有序。
hashmap为什么红黑树?
如果多个键映射到同一个槽位,hashmap会以链表的形式存储在同一个槽位上,因为链表的查询时间是O(n),所以冲突很严重,一个索引上的链表非常长,效率就很低了。
所以在 JDK 1.8 版本的时候做了优化,当一个链表的长度超过8的时候就转换数据结构,不再使用链表存储,而是使用红黑树,查找时使用红黑树,时间复杂度O(log n),可以提高查询性能,但是在数量较少时,即数量小于6时,会将红黑树转换回链表。
线程间通信方式有哪些?
1、Object 类的 wait()、notify() 和 notifyAll() 方法。这是 Java 中最基础的线程间通信方式,基于对象的监视器(锁)机制。
**wait()**
:使当前线程进入等待状态,直到其他线程调用该对象的notify()
或notifyAll()
方法。**notify()**
:唤醒在此对象监视器上等待的单个线程。**notifyAll()**
:唤醒在此对象监视器上等待的所有线程。
java
class SharedObject {
public synchronized void consumerMethod() throws InterruptedException {
while (/* 条件不满足 */) {
wait();
}
// 执行相应操作
}
public synchronized void producerMethod() {
// 执行相应操作
notify(); // 或者 notifyAll()
}
}
2、Lock
和 Condition
接口。Lock
接口提供了比 synchronized
更灵活的锁机制,Condition
接口则配合 Lock
实现线程间的等待 / 通知机制。
**await()**
:使当前线程进入等待状态,直到被其他线程唤醒。**signal()**
:唤醒一个等待在该Condition
上的线程。**signalAll()**
:唤醒所有等待在该Condition
上的线程。
java
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class SharedResource {
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
public void consumer() throws InterruptedException {
lock.lock();
try {
while (/* 条件不满足 */) {
condition.await();
}
// 执行相应操作
} finally {
lock.unlock();
}
}
public void producer() {
lock.lock();
try {
// 执行相应操作
condition.signal(); // 或者 signalAll()
} finally {
lock.unlock();
}
}
}
3、volatile
关键字。volatile
关键字用于保证变量的可见性,即当一个变量被声明为 volatile
时,它会保证对该变量的写操作会立即刷新到主内存中,而读操作会从主内存中读取最新的值。
java
class VolatileExample {
private volatile boolean flag = false;
public void writer() {
flag = true;
}
public void reader() {
while (!flag) {
// 等待
}
// 执行相应操作
}
}
4、CountDownLatch。CountDownLatch
是一个同步辅助类,它允许一个或多个线程等待其他线程完成操作。
**CountDownLatch(int count)**
:构造函数,指定需要等待的线程数量。**countDown()**
:减少计数器的值。**await()**
:使当前线程等待,直到计数器的值为 0。
java
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
int threadCount = 3;
CountDownLatch latch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
try {
// 执行任务
System.out.println(Thread.currentThread().getName() + " 完成任务");
} finally {
latch.countDown();
}
}).start();
}
latch.await();
System.out.println("所有线程任务完成");
}
}
5、CyclicBarrier。CyclicBarrier
是一个同步辅助类,它允许一组线程相互等待,直到所有线程都到达某个公共屏障点。
**CyclicBarrier(int parties, Runnable barrierAction)**
:构造函数,指定参与的线程数量和所有线程到达屏障点后要执行的操作。**await()**
:使当前线程等待,直到所有线程都到达屏障点。
java
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierExample {
public static void main(String[] args) {
int threadCount = 3;
CyclicBarrier barrier = new CyclicBarrier(threadCount, () -> {
System.out.println("所有线程都到达屏障点");
});
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
try {
// 执行任务
System.out.println(Thread.currentThread().getName() + " 到达屏障点");
barrier.await();
// 继续执行后续任务
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
6、Semaphore。Semaphore
是一个计数信号量,它可以控制同时访问特定资源的线程数量。
**Semaphore(int permits)**
:构造函数,指定信号量的初始许可数量。**acquire()**
:获取一个许可,如果没有可用许可则阻塞。**release()**
:释放一个许可。
java
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
public static void main(String[] args) {
int permitCount = 2;
Semaphore semaphore = new Semaphore(permitCount);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " 获得许可");
// 执行任务
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
System.out.println(Thread.currentThread().getName() + " 释放许可");
}
}).start();
}
}
}
如何保证线程安全?
- synchronized关键字 :可以使用
synchronized
关键字来同步代码块或方法,确保同一时刻只有一个线程可以访问这些代码。对象锁是通过synchronized
关键字锁定对象的监视器(monitor)来实现的。
java
public synchronized void someMethod() { /* ... */ }
public void anotherMethod() {
synchronized (someObject) {
/* ... */
}
}
- volatile关键字 :
volatile
关键字用于变量,确保所有线程看到的是该变量的最新值,而不是可能存储在本地寄存器中的副本。
java
public volatile int sharedVariable;
- Lock接口和ReentrantLock类 :
java.util.concurrent.locks.Lock
接口提供了比synchronized
更强大的锁定机制,ReentrantLock
是一个实现该接口的例子,提供了更灵活的锁管理和更高的性能。
java
private final ReentrantLock lock = new ReentrantLock();
public void someMethod() {
lock.lock();
try {
/* ... */
} finally {
lock.unlock();
}
}
- 原子类 :Java并发库(
java.util.concurrent.atomic
)提供了原子类,如AtomicInteger
、AtomicLong
等,这些类提供了原子操作,可以用于更新基本类型的变量而无需额外的同步。示例:
java
AtomicInteger counter = new AtomicInteger(0);
int newValue = counter.incrementAndGet();
- 线程局部变量 :
ThreadLocal
类可以为每个线程提供独立的变量副本,这样每个线程都拥有自己的变量,消除了竞争条件。
java
ThreadLocal<Integer> threadLocalVar = new ThreadLocal<>();
threadLocalVar.set(10);
int value = threadLocalVar.get();
- 并发集合 :使用
java.util.concurrent
包中的线程安全集合,如ConcurrentHashMap
、ConcurrentLinkedQueue
等,这些集合内部已经实现了线程安全的逻辑。 - JUC工具类 : 使用
java.util.concurrent
包中的一些工具类可以用于控制线程间的同步和协作。例如:Semaphore
和CyclicBarrier
等。
synchronized锁静态方法和普通方法区别?
锁的对象不同:
- 普通方法 :锁的是当前对象实例(
this
)。同一对象实例的synchronized
普通方法,同一时间只能被一个线程访问;不同对象实例间互不影响,可被不同线程同时访问各自的同步普通方法。 - 静态方法 :锁的是当前类的
Class
对象。由于类的Class
对象全局唯一,无论多少个对象实例,该静态同步方法同一时间只能被一个线程访问。
作用范围不同:
- 普通方法:仅对同一对象实例的同步方法调用互斥,不同对象实例的同步普通方法可并行执行。
- 静态方法:对整个类的所有实例的该静态方法调用都互斥,一个线程进入静态同步方法,其他线程无法进入同一类任何实例的该方法。
多实例场景影响不同:
- 普通方法:多线程访问不同对象实例的同步普通方法时,可同时执行。
- 静态方法:不管有多少对象实例,同一时间仅一个线程能执行该静态同步方法。
jvm内存模型介绍一下?
根据 JDK 8 规范,JVM 运行时内存共分为虚拟机栈、堆、元空间、程序计数器、本地方法栈五个部分。还有一部分内存叫直接内存,属于操作系统的本地内存,也是可以直接操作的。
JVM的内存结构主要分为以下几个部分:
- 元空间:元空间的本质和永久代类似,都是对 JVM 规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。
- Java 虚拟机栈:每个线程有一个私有的栈,随着线程的创建而创建。栈里面存着的是一种叫 "栈帧" 的东西,每个方法会创建一个栈帧,栈帧中存放了局部变量表(基本数据类型和对象引用)、操作数栈、方法出口等信息。栈的大小可以固定也可以动态扩展。
- 本地方法栈:与虚拟机栈类似,区别是虚拟机栈执行 Java 方法,本地方法栈执行 native 方法。在虚拟机规范中对本地方法栈中方法使用的语言、使用方法与数据结构没有强制规定,因此虚拟机可以自由实现它。
- 程序计数器:程序计数器可以看成是当前线程所执行的字节码的行号指示器。在任何一个确定的时刻,一个处理器(对于多内核来说是一个内核)都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器,我们称这类内存区域为 "线程私有" 内存。
- 堆内存:堆内存是 JVM 所有线程共享的部分,在虚拟机启动的时候就已经创建。所有的对象实例和数组都在堆上分配,这部分空间可通过 GC 进行回收。当申请不到空间时会抛出 OutOfMemoryError。堆是 JVM 内存占用最大、管理最复杂的一个区域。JDK 1.8 后,字符串常量池和运行时常量池从永久代中剥离出来,存放在堆中。
- 直接内存:直接内存并不是虚拟机运行时数据区的一部分,也不是 Java 虚拟机规范中定义的内存区域。在 JDK 1.4 中新加入了 NIO 类,引入了一种基于通道 (Channel) 与缓冲区(Buffer)的 I/O 方式,它可以使用 native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。
linux命令 查看当前目录下文件?
ls 命令是查看文件和目录的命令,它能列出当前目录下的内容。
带上 -l 参数,则以长格式显示文件和目录的详细信息,包括文件权限、所有者、组、大小、修改时间等。
linux命令 如何查看当前目录下大文件?
du
(disk usage)命令用于估算文件空间使用量,sort
命令用于对文本进行排序。结合这两个命令可以找出当前目录下占用空间较大的文件。
下面的命令会列出当前目录下所有文件和子目录的磁盘使用情况,并按文件大小降序排序,显示前 10 个最大的文件或目录:
shell
# du 命令的参数:
# -a:显示所有文件和目录的磁盘使用情况,而不仅仅是目录。
# -h:以人类可读的格式显示文件大小(如 K、M、G)。
# .:表示当前目录。
# sort 命令的参数:
# -r:以降序排序。
# -h:按照人类可读的数字大小进行排序。
# head 命令的参数:
# -n 10:只显示前 10 行。
du -ah . | sort -rh | head -n 10
linux命令日志怎么查询关键字?
可以用 grep 命令来查询文件中的关键字。
比如,要在日志文件(如 example.log
)中查找包含 "error" 关键字的行,可使用如下命令:
shell
grep "error" example.log
SQL题:查某个班级下所有学生的选课情况
有三张表:学生信息表、学生选课表、学生班级表
学生信息表(students)结构如下:
sql
CREATE TABLE students (
student_id INT PRIMARY KEY, //学生的唯一标识,主键。
student_name VARCHAR(50), //学生姓名。
class_id INT //学生所属班级的标识,用于关联班级表。
);
学生选课表(course_selections
)结构如下:
sql
CREATE TABLE course_selections (
selection_id INT PRIMARY KEY, //选课记录的唯一标识,主键。
student_id INT, //选课学生的标识,用于关联学生信息表。
course_name VARCHAR(50), //所选课程的名称。
);
学生班级表(classes
)结构如下:
sql
CREATE TABLE classes (
class_id INT PRIMARY KEY, //班级的唯一标识,主键。
class_name VARCHAR(50) //班级名称。
);
要查询某个班级(例如班级名称为 'Class A')下所有学生的选课情况,可以使用以下 SQL 查询语句:
sql
SELECT
s.student_id,
s.student_name,
cs.course_name
FROM
students s
JOIN
course_selections cs ON s.student_id = cs.student_id
JOIN
classes c ON s.class_id = c.class_id
WHERE
c.class_name = 'Class A';
其他
- 再详细介绍下实习,根据实习随便问了问
- 闲聊,喜欢的业务,导师情况,实习时间等等。
必看图解
- 《图解网络》 :500 张图 + 15 万字贯穿计算机网络重点知识,如HTTP、HTTPS、TCP、UDP、IP等协议
- 《图解系统》:400 张图 + 16 万字贯穿操作系统重点知识,如进程管理、内存管理、文件系统、网络系统等
- 《图解MySQL》:重点突击 MySQL 索引、存储引擎、事务、MVCC、锁、日志等面试高频知识
- 《图解Redis》:重点突击 Redis 数据结构、持久化、缓存淘汰、高可用、缓存数据一致性等面试高频知识
- 《Java后端面试题》:涵盖Java基础、Java并发、Java虚拟机、Spring、MySQL、Redis、计算机网络等企业面试题
- 《大厂真实面经》:涵盖互联网大厂、互联网中厂、手机厂、通信厂、新能源汽车厂、银行等企业真实面试题