声明:
- 背景:本人为24届双非硕校招生,已经完整经历了一次秋招,拿到了三个offer。
- 本专题旨在分享自己的一些Java开发岗面试经验(主要是校招),包括我自己总结的八股文、算法、项目介绍、HR面和面试技巧等等,如有建议,可以友好指出,感谢,我也会不断完善。
- 想了解我个人情况的,可以关注我的B站账号:东瓜Lee
Java和C++面向对象的区别?
public,private,protected,default的区别
基本数据类型vs包装类
为了更好的实现面向对象的思想,引出了8种基本数据类型对应的8种包装类:
它们均继承自抽象类Number
自动装箱和拆箱:
int和Integer的区别(其他也一样)
注意:
Integer.parseInt()和Integer.valueOf()的区别:
double类型数值的计算经常会出现这种精度丢失的问题,尤其是有小数点的情况下,常常会因为精度丢失而导致程序出错。所以我们在运算高精度的数据的时候,可以使用 Java.math.BigDecimal
类(涉及到金钱都最好用此类,理论不能用double)
switch
的设计按道理来说,是比if-else
要快的,但是在99.99%的情况下,他们性能差不多,除非case
分支量巨大。switch
只支持byte,int,short,char,enum,String
的类型switch
虽然支持byte,int,short,char,enum,String
但是本质上都是int
,其他的只是编译器帮你进行了语法糖优化而已。如何跳出当前的多重嵌套循环
使用 break 带上标识(类似于goto,最好不用)
//标识
ok:
for(int i=0;i<10;i++){
for(int j=0;j<10;j++){
if(j==5){
//跳到循环外的ok出,即终止整个循环
break ok;
}
}
}
定义flag来进行判断
boolean flag = false;
for(int i=0;i<10;i++){
for(int j=0;j<10;j++){
if(j==5){
//跳到循环外的ok出,即终止整个循环
flag = true;
break;
}
}
if(flag) {
break;
}
}
重写和重载:
重载:对于同一个类,如果这个类里面有两个或者多个重名的方法,但是方法的参数个数、类型、顺序至少有一个不一样,就构成方法重载(重载overload,方法重载就是对不同数据类型的数据实现相似的操作)
重写:当一个子类继承父类,而子类中的方法与父类中的方法的名称,参数个数、类型都完全一致时,就称子类中的这个方法重写了父类中的方法(重写@Override),或者实现类重写接口中的方法
构造器是否可以被重写@Override
构造器是不能被继承的,因为每个类的类名都不相同,而构造器名称与类名相同,所以根本谈不上继承。
构造器不能被继承,所以就不能被重写。但是,在同一个类中,构造器是可以被重载的。
==和equals区别
==
equals()方法
是Object提供的一个方法
Object中equals()方法的默认实现就是返回两个对象==的比较结果。但是equals()可以被重写,所以我们在具体使用的时候需要关注equals()方法有没有被重写。
public boolean equals(Object obj) {
return (this == obj);
}
是否可以继承String类
String 类是不能被继承的,因为他是被final关键字修饰的
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
从String的源码能看出来:
抽象类和接口的区别:
抽象类是对类的抽象(是一种模板设计方法)、而接口是对行为的抽象(是一种行为的规范)
内部类和静态内部类:
static关键字可以修饰什么?有什么语义?
如何实现对象拷贝(复制):
将要拷贝的对象对应的类:
将一个对象a克隆给另一个对象b
浅克隆:a和b指向的是同一个地址
深克隆:a和b指向的是不同的地址,但是内容一样
Java中有几种类型的io流:
其他流:
简单介绍Java I/O,其中NIO、BIO、AIO三种I/O模式的区别【待学习】
String driver = "com.mysql.jdbc.Driver";
// 数据库连接串
String url = "jdbc:mysql://127.0.0.1:3306/jdbctest";
// 用户名
String username = "root";
// 密码
String password = "123456";
// 1、加载驱动
Class.forName(driver);
// 2、获取数据库连接
Connection conn = DriverManager.getConnection(url, username, password);
// 3、获取数据库操作对象
Statement stmt = conn.createStatement();
// 4、定义操作的SQL语句
String sql = "select * from user where id = 100";
// 5、执行数据库操作
ResultSet rs = stmt.executeQuery(sql);
// 6、获取并操作结果集
while (rs.next()) {
}
// 7、关闭对象,回收数据库资源
rs.close();
stmt.close();
conn.close();
Statement和PreparedStatement的区别
Statement是Java执行数据库操作的一个重要方法,用于在已经建立数据库连接的基础上,向数据库发送要执行的SQL语句,PreparedStatement继承自Statement 。
数据库连接池的作用与基本原理
正常的创建数据库连接的流程就是,先加载数据库驱动,再得到数据库连接,在数据库访问量比较大的情况下,频繁的创建连接会带来较大的性能开销,而数据库连接池就是维护了一定数量的数据库连接,当需要用的时候,就可以直接获取。
好处:
1. 资源复用,避免频繁创建数据库连接,造成资源的消耗
2. 更快的系统响应速度,数据库连接池初始化后,就已经有了若干数据库连接对象,不需要重新创建
3. 统一的连接管理,避免数据库连接泄露
常见数据库连接池:
1. Druid,最好的选择之一(高效的性能、高度的可扩展和可配置性、安全性)
2. C3P0
连接池的几个关键参数:
1. 初始化连接数:表示启动的时候初始化多少个数据库连接
2. 最大连接数:表示最多同时能使用多少个连接
3. 最大等待时间:表示连接池里面的连接用完了以后,新的请求要获取连接 要等待的时间,超过这个时间就会提示超时异常
什么是DAO模式
一种数据访问模式,DAO位于业务逻辑和持久化数据之间,实现对持久化数据的访问。通俗来讲,就是将数据库操作都封装起来,对外提供相应的接口。
JDK、JRE、JVM三者的关系
对于开发者而言需要jdk,如果只是运行Java程序,配置jre环境即可
Java中的finally一定会被执行吗?
不一定
Java反射了解吗?
实际开发中基本上没怎么用过,但是有学习过反射的原理。
反射的优点:
缺点:
Java泛型是什么?作用?
在定义类、接口、方法时,同时声明了一个或者多个类型变量(如: ), 就称为泛型类、泛型接口,泛型方法,它们统称为泛型。
Java注解是什么?
注解就是代码中的特殊标记,这些标记可以在编译、类加载、程序运行时被读取,然后就可以执行相应的处理。
实际的开发中,比如在Spring、SpringMVC、SpringBoot这些框架中都有对应的注解,还比如会用到Lombok注解,记录日志之类的。除了在框架中的注解,Java原生也有一些常用的注解,比如说重写的注解@Overried等等。
还有一种注解叫做元注解,它其实就是用来修饰注解的。
throw是语句主动抛出一个异常,throw + 一个异常对象
throws是方法可能抛出的异常的声明,在声明方法的时候,throws + 该方法可能会抛出的异常的种类
我们自定义的类需要进行比较操作时,就需要重写 equals() 和 hashCode() 方法,如果不重写hashCode() 方法就会导致 该类无法使用基于散列的集合,比如HashMap、HashSet
比如说自定义了一个Student类,只重写了equals()方法,那么比较的就是两个对象的属性,如果两个对象的全部属性相同,equals()返回的就是true,但是此时两个对象的hashcode值是不一样的(采用的Object类默认的hashCode()方法),那这个时候,如果要把这两个对象放入map或者set中,因为hashcode值不一样,就会放到两个不同的位置,这样就违背了map和set的原理,所以也必须要重写hashCode()方法。
使得如果两个对象equlas()相等,那么hashcode也得相等,就不会在map或者set中放入重复的元素。
String可以是:
String对象都是不可变字符串对象
变量\常量放在哪个内存空间?
不能被重写的方法
序列化和反序列化是什么?
序列化是将对象转换为字节流,方便传输和存储,反序列化是将字节流转换为对象。
实现一个Serializable接口,用于标记这个类可以被序列化,里面没有需要重写的方法,如果不实现这个接口的话,就会报异常。
序列化ID的作用:
它决定着能否成功的反序列化,在反序列化时,JVM会把字节流中的序列号ID 和 本地实体类中的序列号ID做比对,只有两者一致,才能反序列化,否则就会报序列化版本不一致的异常。
一个类默认会有一个序列化ID,如果不变动这个类的话,那么反序列话的时候 序列化ID也会和这个实体类中的序列化ID保持一致,能够成功反序列化。但是如果后续对这个类进行字段的更改,序列化ID也会跟着改变,那再反序列化的时候,序列化ID就不一致了,就会报异常。所以最好不适用它默认的序列化ID,而是自己显式的声明一个确定的值。
如果要使得某个字段不被序列化,可以使用transient关键字修饰(前提是通过实现Serializable接口来实现序列化)
为什么数组索引从0开始呢?假如从1开始不行吗?
因为在根据数组索引获取元素的时候,会用索引和寻址公式来计算内存所对应的元素数据,寻址公式是:数组的首地址+索引*存储数据的类型大小
如果数组的索引从1开始,寻址公式中,就需要增加一次减法操作,对于CPU来说就多了一次指令,性能不高。
ArrayList底层的实现原理是什么?
ArrayList list=new ArrayList(10)中的list扩容几次?
如何实现数组和List之间的转换?
数组转List,使用JDK中Java.util.Arrays. 工具类的asList方法
List< String> list = Arrays.asList(strs);
用Arrays.asList转List后,如果修改了数组内容,list受影响(指向同一块内存)
List转数组,使用List的toArray方法。无参toArray方法返回Object数组,传入初始化长度的数组对象,返回该对象数组,
String[] array = list.toArray(new String[list.size()]);
List用toArray转数组后,如果修改了List内容,数组不受影响(不指向同一块内存)
ArrayList和LinkedList的区别是什么?
Collections.synchronizedList(new ArrayList<>();
说一下HashMap的实现原理?
HashMap的put方法的具体流程 put(key,value)
put()方法就是hashmap保存键值对的方法,传入一个key和value,然后具体的流程就是:
HashMap的扩容机制
HashMap和HashTable有什么区别?
HashMap不是线程安全的,HashTable是线程安全的
HashTable的性能要差一些,因为加了synchronized锁来保证线程安全性。
(通过加synchronized锁来保证线程安全,类似的还有StringBuilder和StringBuffer)
HashTable底层的哈希表就是数组+链表,HashMap在jdk1.8之后变成了数组+链表+红黑树
HashTable的初始化容量为11,HashMap的初始化容量为16
LinkedHashMap和TreeMap底层数据结构是什么?
多线程基础知识:
线程与进程的区别
并行与并发的区别
单核CPU:
由于只有一个CPU核心,所以所有线程都不能同时执行,但是CPU对于执行线程的切换速度很快,所以对外感觉多个线程是同时执行的,所以单核CPU没有并行,本质上还是并发。
多核CPU:
CPU有多个核心,所以有的线程就可以同时被多个CPU核心执行,那么就达到了并行(真正的同时执行线程),但是一般线程数肯定大于CPU核心数,所以还是会有并发存在,所以多核CPU就是并行和并发同时执行。
线程创建的方式有那些
runnable和callable有什么区别
线程的run()和start()有什么区别
线程包括哪些状态,状态之间是如何变化的
六种状态:State枚举类里面有定义
新建三个线程,如何保证它们按顺序执行
可以使用线程中的join方法解决
在Java中wait和sleep方法的不同
notify()和notifyAll()有什么区别
如何停止一个正在运行的线程
什么是ThreadLocal?
ThreadLocal是一种线程隔离机制,它提供了多线程环境下对于共享变量访问的安全性。
在多线程中访问共享变量,要保证安全性,可以通过:
而且ThreadLocal提供了线程本地存储机制,利用该机制将数据缓存到某个线程内部,该线程可以在任意方法中获取缓存的数据。
应用场景:
瑞吉外卖项目,有个场景就是在某个方法中要获取当前登录用户的id来进行数据库的操作,但是不能直接拿到这个id,此时就想到可以使用ThreadLocal来解决,因为这个用户id可以在另一个方法中获取到(过滤器的doFilter方法中,如果session中有登录信息,就放行页面,并且将登录信息放到ThreadLocal中),而这两个方法同属于一个线程,一个线程内的变量,在不同的方法内可以共享访问,那就可以在那个方法中set一下这个变量,然后在当前方法中get一下这个变量即可。
ThreadLocal的底层原理?
在 Thread 类里面有一个成员变量ThreadLocalMap,它专门来存储当前线程的共享变量副本,后续这个线程对于共享变量的操作,都是从这个 ThreadLocalMap 里面进行变更,不会影响全局共享变量的值,key为threadLocal对象,value为具体的值,具体涉及的方法一般就是set()、get()、remove()。
为了放置ThreadLocal出现内存泄漏的问题(key是弱引用的变量,value为强引用,key会被GC回收,value不会),可以使用remove()方法清理threadLocal对象。
并发安全:
你谈谈JMM (Java 内存模型)
JMM:Java memory model Java内存模型(不是jvm中的内存结构)
定义了共享内存中 多线程程序读写操作的行为规范,通过这些规则 来规范对内存的读写操作 从而确保指令的正确性。
JMM把内存分为两块:
在Java中有什么锁?(Java的锁机制)
按照锁的思想可以分为两类:
乐观锁:CAS
乐观锁的思想就是线程即使没有拿到锁,也会继续去尝试
悲观锁:
悲观锁的思想就是线程如果没有拿到锁,就会被阻塞
Java常见的加锁方式
在并发编程中,加锁是一种常用的方法,用于保护临界区资源的访问安全
synchronized关键字:修饰代码块或者方法,保证只有一个线程能够获取对象锁,其他线程阻塞,访问完毕后自动释放锁
public synchronized void increment() {
count++;
}
ReentryLock类:是一个可重入锁,相比于synchronized,ReentrantLock提供了更多的灵活性和可控性,例如可实现公平锁,可指定等待时间等。
需要new一个ReentryLock对象,调用lock()方法主动开启锁,访问完毕后需要调用unlock()方法主动释放锁
private Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
ReadWriteLock接口:是Java提供的一种读写锁机制,它允许多个线程同时读取共享资源,但只允许一个线程写入共享资源,这种机制可以提高读操作的并发性能。
需要new一个ReentrantReadWriteLock对象(ReadWriteLock接口的实现类),
调用writeLock().lock()方法主动开启写锁,修改完毕后 需要调用writeLock().unlock()方法主动释放锁
调用readLock().lock()方法主动开启读锁,读取完毕后 需要调用readLock().unlock()方法主动释放锁
private ReadWriteLock lock = new ReentrantReadWriteLock();
public void increment() {
lock.writeLock().lock();
try {
count++;
} finally {
lock.writeLock().unlock();
}
}
public int getCount() {
lock.readLock().lock();
try {
return count;
} finally {
lock.readLock().unlock();
}
}
总结:
synchronized关键字的底层原理
synchronized在jdk1.8之前用的比较少,因为性能比较低,jdk1.8之后对锁进行了一些优化,具体有什么优化?
什么是AQS
什么是ReentrantLock
synchronized和Lock有什么区别
死锁产生的条件是什么
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
死锁需要必要的条件才能产生,死锁的四个必要条件:
解决死锁的方法:
(1)死锁要尽可能的避免,也就是打破其中的必要条件(预防死锁)
(2)真正的出现了死锁,就只能诊断到死锁的原因,然后采取一些措施,将死锁清除掉
在Java程序中出现了死锁,如何进行诊断
当程序出现了死锁现象,我们可以使用jdk自带的工具: jps和 jstack
还可以用其他可视化工具,例如jconsole(用于对jvm的内存、线程、类的监控,是一个基于jmx的GUI性能监控工具)
聊一下ConcurrentHashMap
请谈谈你对volatile的理解
volatile关键字可以作用于变量(一般是共享变量,类的成员变量、静态成员变量),它使得变被修饰的变量具有两种语义:
保证变量对所有线程的可见性,也就是说当一个线程修改了volatile修饰的变量的值,这对于其他线程来说都能够立即知晓新值。
禁止进行指令重排序,正常来讲,如果对变量不使用volatile关键字修饰的话,编译器会对指令进行重排序优化来获得更好的性能,但是有时候这种编译器优化可能会导致程序没有产生预期的结果,所以如果需要阻止编译器对变量作重排序, 就可以使用volatile关键字来修饰。
导致并发程序出现问题的根本原因是什么
并发问题出现,主要体现在三个方面:
如果没有满足其中的一点,就有可能出现并发问题。
volatile关键字修饰的变量是可以保证可见性和有序性的,但是并不可以保证原子性,所以不能解决并发编程的问题。如果要满足并发编程的三个特性,可以使用synchronized或者lock锁来实现。
线程池:
线程池是一种池化技术,实现了资源的复用,具体好处有:
1. 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
2. 提高响应速度:当任务到达时,任务可以不需要的等到线程创建就能立即执行。
3. 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
线程池的核心参数、线程池的执行原理(ThreadPoolExecutor类的七大参数)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
线程池的执行原理:
线程池中有哪些常见的阻塞队列
ArrayBlockingQueue: 基于数组结构的有界阻塞队列,FIFO
LinkedBlockingQueue: 基于单向链表结构的有界阻塞队列,FIFO
DelayedWorkQueue: 是一个优先级队列,它可以保证每次出队的任务都是当前队列中执行时间最靠前的
SynchronousQueue: 不存储元素的阻塞队列,每个插入操作都必须等待一个移出操作。
主要使用的是数组和链表阻塞队列,具体的区别:
线程池的拒绝策略有哪些?
当核心线程数满了、阻塞队列满了、临时线程数满了,再来新任务就会引发拒绝策略
如何确定核心线程数
lO密集型任务(比如文件读写、数据库读写):设置2N+1(N为CPU核数),大多数时候的业务场景
CPU密集型任务(比如计算类型的代码):设置N+1
//查看机器的CPU核数N
Runtime.getRuntime().availableProcessors();
线程池的种类有哪些
为什么不建议用Executors创建线程池
涉及到一个Integer.MAX_INT,容易导致OOM内存溢出,一般还是采用ThreadPoolExecutor,设置七个参数来创建线程池。
【后续继续补充,敬请期待】